diff --git a/patches/api/0001-Convert-project-to-Gradle.patch b/patches/api/0001-Convert-project-to-Gradle.patch index 509ab41f15f0..4bcaee929525 100644 --- a/patches/api/0001-Convert-project-to-Gradle.patch +++ b/patches/api/0001-Convert-project-to-Gradle.patch @@ -124,7 +124,7 @@ index 0000000000000000000000000000000000000000..4311f6dde7372a069f462158fba76d9d +} diff --git a/pom.xml b/pom.xml deleted file mode 100644 -index 741ea21a1ccd141d065f5e12349af38f097a8915..0000000000000000000000000000000000000000 +index 0861b44936958613beba670b0d82e21d3aaf388b..0000000000000000000000000000000000000000 --- a/pom.xml +++ /dev/null @@ -1,277 +0,0 @@ @@ -144,8 +144,8 @@ index 741ea21a1ccd141d065f5e12349af38f097a8915..00000000000000000000000000000000 - - - true -- 1.8 -- 1.8 +- 17 +- 17 - UTF-8 - - diff --git a/patches/api/0006-Adventure.patch b/patches/api/0006-Adventure.patch index 1dbe87620276..eb57e4f25e3e 100644 --- a/patches/api/0006-Adventure.patch +++ b/patches/api/0006-Adventure.patch @@ -779,10 +779,10 @@ index 0000000000000000000000000000000000000000..6e94562d79206d88b74b53814f9423f1 + } +} diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 5680a77cc98cdb4b4c002d1366e775acaf747e51..a0fce496d2b90f484d2723c4ea4099ad29dc438f 100644 +index 2e619279cb06cbe26bb4933a0312b245f8691d0b..bd1a010bb4e18a16d02549d64333ce7641be7910 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -400,7 +400,9 @@ public final class Bukkit { +@@ -420,7 +420,9 @@ public final class Bukkit { * * @param message the message * @return the number of players @@ -792,7 +792,7 @@ index 5680a77cc98cdb4b4c002d1366e775acaf747e51..a0fce496d2b90f484d2723c4ea4099ad public static int broadcastMessage(@NotNull String message) { return server.broadcastMessage(message); } -@@ -1191,6 +1193,19 @@ public final class Bukkit { +@@ -1211,6 +1213,19 @@ public final class Bukkit { server.shutdown(); } @@ -812,7 +812,7 @@ index 5680a77cc98cdb4b4c002d1366e775acaf747e51..a0fce496d2b90f484d2723c4ea4099ad /** * Broadcasts the specified message to every user with the given * permission name. -@@ -1200,6 +1215,21 @@ public final class Bukkit { +@@ -1220,6 +1235,21 @@ public final class Bukkit { * permissibles} must have to receive the broadcast * @return number of message recipients */ @@ -834,7 +834,7 @@ index 5680a77cc98cdb4b4c002d1366e775acaf747e51..a0fce496d2b90f484d2723c4ea4099ad public static int broadcast(@NotNull String message, @NotNull String permission) { return server.broadcast(message, permission); } -@@ -1461,6 +1491,7 @@ public final class Bukkit { +@@ -1481,6 +1511,7 @@ public final class Bukkit { return server.createInventory(owner, type); } @@ -842,7 +842,7 @@ index 5680a77cc98cdb4b4c002d1366e775acaf747e51..a0fce496d2b90f484d2723c4ea4099ad /** * Creates an empty inventory with the specified type and title. If the type * is {@link InventoryType#CHEST}, the new inventory has a size of 27; -@@ -1486,6 +1517,38 @@ public final class Bukkit { +@@ -1506,6 +1537,38 @@ public final class Bukkit { * @see InventoryType#isCreatable() */ @NotNull @@ -881,7 +881,7 @@ index 5680a77cc98cdb4b4c002d1366e775acaf747e51..a0fce496d2b90f484d2723c4ea4099ad public static Inventory createInventory(@Nullable InventoryHolder owner, @NotNull InventoryType type, @NotNull String title) { return server.createInventory(owner, type, title); } -@@ -1504,6 +1567,7 @@ public final class Bukkit { +@@ -1524,6 +1587,7 @@ public final class Bukkit { return server.createInventory(owner, size); } @@ -889,7 +889,7 @@ index 5680a77cc98cdb4b4c002d1366e775acaf747e51..a0fce496d2b90f484d2723c4ea4099ad /** * Creates an empty inventory of type {@link InventoryType#CHEST} with the * specified size and title. -@@ -1516,10 +1580,30 @@ public final class Bukkit { +@@ -1536,10 +1600,30 @@ public final class Bukkit { * @throws IllegalArgumentException if the size is not a multiple of 9 */ @NotNull @@ -920,7 +920,7 @@ index 5680a77cc98cdb4b4c002d1366e775acaf747e51..a0fce496d2b90f484d2723c4ea4099ad /** * Creates an empty merchant. * -@@ -1527,7 +1611,20 @@ public final class Bukkit { +@@ -1547,7 +1631,20 @@ public final class Bukkit { * when the merchant inventory is viewed * @return a new merchant */ @@ -941,7 +941,7 @@ index 5680a77cc98cdb4b4c002d1366e775acaf747e51..a0fce496d2b90f484d2723c4ea4099ad public static Merchant createMerchant(@Nullable String title) { return server.createMerchant(title); } -@@ -1644,12 +1741,43 @@ public final class Bukkit { +@@ -1664,12 +1761,43 @@ public final class Bukkit { return server.isPrimaryThread(); } @@ -985,7 +985,7 @@ index 5680a77cc98cdb4b4c002d1366e775acaf747e51..a0fce496d2b90f484d2723c4ea4099ad public static String getMotd() { return server.getMotd(); } -@@ -1658,7 +1786,9 @@ public final class Bukkit { +@@ -1678,7 +1806,9 @@ public final class Bukkit { * Set the message that is displayed on the server list. * * @param motd The message to be displayed @@ -995,7 +995,7 @@ index 5680a77cc98cdb4b4c002d1366e775acaf747e51..a0fce496d2b90f484d2723c4ea4099ad public static void setMotd(@NotNull String motd) { server.setMotd(motd); } -@@ -1667,8 +1797,10 @@ public final class Bukkit { +@@ -1687,8 +1817,10 @@ public final class Bukkit { * Gets the default message that is displayed when the server is stopped. * * @return the shutdown message @@ -1177,10 +1177,10 @@ index ae7b51341fb66c41b8a7c4604fd273d876e311be..4034fcb9abc39b12f0de47c4b679f2ef + // Paper end } diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index c394954cab8213bd1073356524cdd5705ef54d12..83ae1490f6c48d931541e13f76950fcdc62a5c01 100644 +index 76b365f98b81234ae1c35014387b0e44f722d5ea..6aed59819cc3d70f1b5975c3c7df40cc0b0afd8a 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -62,13 +62,13 @@ import org.jetbrains.annotations.Nullable; +@@ -63,13 +63,13 @@ import org.jetbrains.annotations.Nullable; /** * Represents a server implementation. */ @@ -1196,7 +1196,7 @@ index c394954cab8213bd1073356524cdd5705ef54d12..83ae1490f6c48d931541e13f76950fcd */ public static final String BROADCAST_CHANNEL_ADMINISTRATIVE = "bukkit.broadcast.admin"; -@@ -76,7 +76,7 @@ public interface Server extends PluginMessageRecipient { +@@ -77,7 +77,7 @@ public interface Server extends PluginMessageRecipient { * Used for all announcement messages, such as informing users that a * player has joined. *

@@ -1205,7 +1205,7 @@ index c394954cab8213bd1073356524cdd5705ef54d12..83ae1490f6c48d931541e13f76950fcd */ public static final String BROADCAST_CHANNEL_USERS = "bukkit.broadcast.user"; -@@ -337,7 +337,9 @@ public interface Server extends PluginMessageRecipient { +@@ -353,7 +353,9 @@ public interface Server extends PluginMessageRecipient { * * @param message the message * @return the number of players @@ -1215,7 +1215,7 @@ index c394954cab8213bd1073356524cdd5705ef54d12..83ae1490f6c48d931541e13f76950fcd public int broadcastMessage(@NotNull String message); /** -@@ -1023,8 +1025,33 @@ public interface Server extends PluginMessageRecipient { +@@ -1039,8 +1041,33 @@ public interface Server extends PluginMessageRecipient { * @param permission the required permission {@link Permissible * permissibles} must have to receive the broadcast * @return number of message recipients @@ -1249,7 +1249,7 @@ index c394954cab8213bd1073356524cdd5705ef54d12..83ae1490f6c48d931541e13f76950fcd /** * Gets the player by the given name, regardless if they are offline or -@@ -1241,6 +1268,35 @@ public interface Server extends PluginMessageRecipient { +@@ -1257,6 +1284,35 @@ public interface Server extends PluginMessageRecipient { @NotNull Inventory createInventory(@Nullable InventoryHolder owner, @NotNull InventoryType type); @@ -1285,7 +1285,7 @@ index c394954cab8213bd1073356524cdd5705ef54d12..83ae1490f6c48d931541e13f76950fcd /** * Creates an empty inventory with the specified type and title. If the type * is {@link InventoryType#CHEST}, the new inventory has a size of 27; -@@ -1262,9 +1318,11 @@ public interface Server extends PluginMessageRecipient { +@@ -1278,9 +1334,11 @@ public interface Server extends PluginMessageRecipient { * @return The new inventory. * @throws IllegalArgumentException if the {@link InventoryType} cannot be * viewed. @@ -1297,7 +1297,7 @@ index c394954cab8213bd1073356524cdd5705ef54d12..83ae1490f6c48d931541e13f76950fcd @NotNull Inventory createInventory(@Nullable InventoryHolder owner, @NotNull InventoryType type, @NotNull String title); -@@ -1280,6 +1338,22 @@ public interface Server extends PluginMessageRecipient { +@@ -1296,6 +1354,22 @@ public interface Server extends PluginMessageRecipient { @NotNull Inventory createInventory(@Nullable InventoryHolder owner, int size) throws IllegalArgumentException; @@ -1320,7 +1320,7 @@ index c394954cab8213bd1073356524cdd5705ef54d12..83ae1490f6c48d931541e13f76950fcd /** * Creates an empty inventory of type {@link InventoryType#CHEST} with the * specified size and title. -@@ -1290,18 +1364,32 @@ public interface Server extends PluginMessageRecipient { +@@ -1306,18 +1380,32 @@ public interface Server extends PluginMessageRecipient { * viewed * @return a new inventory * @throws IllegalArgumentException if the size is not a multiple of 9 @@ -1353,7 +1353,7 @@ index c394954cab8213bd1073356524cdd5705ef54d12..83ae1490f6c48d931541e13f76950fcd Merchant createMerchant(@Nullable String title); /** -@@ -1397,27 +1485,56 @@ public interface Server extends PluginMessageRecipient { +@@ -1413,27 +1501,56 @@ public interface Server extends PluginMessageRecipient { */ boolean isPrimaryThread(); @@ -1410,7 +1410,7 @@ index c394954cab8213bd1073356524cdd5705ef54d12..83ae1490f6c48d931541e13f76950fcd String getShutdownMessage(); /** -@@ -1799,7 +1916,9 @@ public interface Server extends PluginMessageRecipient { +@@ -1815,7 +1932,9 @@ public interface Server extends PluginMessageRecipient { * Sends the component to the player * * @param component the components to send @@ -1420,7 +1420,7 @@ index c394954cab8213bd1073356524cdd5705ef54d12..83ae1490f6c48d931541e13f76950fcd public void broadcast(@NotNull net.md_5.bungee.api.chat.BaseComponent component) { throw new UnsupportedOperationException("Not supported yet."); } -@@ -1808,7 +1927,9 @@ public interface Server extends PluginMessageRecipient { +@@ -1824,7 +1943,9 @@ public interface Server extends PluginMessageRecipient { * Sends an array of components as a single message to the player * * @param components the components to send @@ -1492,10 +1492,10 @@ index ac5e263d737973af077e3406a84a84baca4370db..2d91924b7f5ef16a91d40cdc1bfc3d68 + // Paper end } diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 4a9a82540e40685d2b837c76c431827a00cf4ba3..a3ee6f2f588bc9c87e49378359f450820b9af1b9 100644 +index 6ca5f7a85087ca1c7e5e2d940bd3c65b84d2d356..0f902aec66ec550f80709f7f314ca90d374ebb53 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -26,6 +26,15 @@ import org.jetbrains.annotations.Nullable; +@@ -30,6 +30,15 @@ import org.jetbrains.annotations.Nullable; */ @Deprecated public interface UnsafeValues { @@ -1524,10 +1524,10 @@ index efb97712cc9dc7c1e12a59f5b94e4f2ad7c6b7d8..3024468af4c073324e536c1cb26beffb return warning == null || warning.value(); } diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index d453a9753620e23e93e24b0c62bec5515d11dbcc..4c878531d9d8d0aee6832fd10b339a32f219fa9a 100644 +index 0fd2e9ee1a8cf4456a1e5fe6579d767c5dcb4d9a..d76db156a7eeefaac3c96d2d547fddecefbd863e 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -45,7 +45,7 @@ import org.jetbrains.annotations.Nullable; +@@ -47,7 +47,7 @@ import org.jetbrains.annotations.Nullable; /** * Represents a world, which may contain entities, chunks and blocks */ @@ -1536,7 +1536,7 @@ index d453a9753620e23e93e24b0c62bec5515d11dbcc..4c878531d9d8d0aee6832fd10b339a32 /** * Gets the {@link Block} at the given coordinates -@@ -606,6 +606,14 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -608,6 +608,14 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient @NotNull public List getPlayers(); @@ -2116,7 +2116,7 @@ index 7ad7bcf9a9333c8d6d1d7cab53a6d457ec20bbf6..c4f86ba1037f3f0e5d697a0962d71d6f + // Paper end } diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 8532b8484d5a493c1c37ad7508597f624f1831c8..978431fd88cfb7d42fcdea8c904633df5c64daed 100644 +index cc577b5af6832ea9c98aceb587de378095277ada..0025139f403cfc80ace9f8584ccf42d3a5cbb718 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java @@ -30,7 +30,7 @@ import org.jetbrains.annotations.Nullable; @@ -2128,7 +2128,7 @@ index 8532b8484d5a493c1c37ad7508597f624f1831c8..978431fd88cfb7d42fcdea8c904633df /** * Gets the entity's current position -@@ -768,4 +768,20 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -763,4 +763,20 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent @Override Spigot spigot(); // Spigot end @@ -2150,10 +2150,10 @@ index 8532b8484d5a493c1c37ad7508597f624f1831c8..978431fd88cfb7d42fcdea8c904633df + // Paper end } diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e87ca26dec 100644 +index 764390e5b258ab3cadedb14be00d0c0d601b8a1f..5c55715c8cdd79f2214b265a81d94a8904c998ea 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -52,7 +52,41 @@ import org.jetbrains.annotations.Nullable; +@@ -54,7 +54,41 @@ import org.jetbrains.annotations.Nullable; /** * Represents a player, connected or not */ @@ -2196,7 +2196,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 /** * {@inheritDoc} -@@ -69,7 +103,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -71,7 +105,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * places defined by plugins. * * @return the friendly name @@ -2206,7 +2206,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 @NotNull public String getDisplayName(); -@@ -81,15 +117,50 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -83,15 +119,50 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * places defined by plugins. * * @param name The new display name. @@ -2257,7 +2257,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 public String getPlayerListName(); /** -@@ -98,14 +169,18 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -100,14 +171,18 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * If the value is null, the name will be identical to {@link #getName()}. * * @param name new player list name @@ -2276,7 +2276,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 @Nullable public String getPlayerListHeader(); -@@ -113,7 +188,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -115,7 +190,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * Gets the currently displayed player list footer for this player. * * @return player list header or null @@ -2286,7 +2286,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 @Nullable public String getPlayerListFooter(); -@@ -121,14 +198,18 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -123,14 +200,18 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * Sets the currently displayed player list header for this player. * * @param header player list header, null for empty @@ -2305,7 +2305,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 public void setPlayerListFooter(@Nullable String footer); /** -@@ -137,7 +218,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -139,7 +220,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * * @param header player list header, null for empty * @param footer player list footer, null for empty @@ -2315,7 +2315,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 public void setPlayerListHeaderFooter(@Nullable String header, @Nullable String footer); /** -@@ -175,9 +258,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -177,9 +260,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * Kicks player with custom kick message. * * @param message kick message @@ -2341,7 +2341,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 /** * Adds this user to the {@link ProfileBanList}. If a previous ban exists, this will * update the entry. -@@ -840,6 +939,106 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -842,6 +941,106 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public void sendEquipmentChange(@NotNull LivingEntity entity, @NotNull Map items); @@ -2448,7 +2448,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 /** * Send a sign change. This fakes a sign change packet for a user at * a certain location. This will not actually change the world in any way. -@@ -857,7 +1056,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -859,7 +1058,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param lines the new text on the sign or null to clear it * @throws IllegalArgumentException if location is null * @throws IllegalArgumentException if lines is non-null and has a length less than 4 @@ -2460,7 +2460,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 public void sendSignChange(@NotNull Location loc, @Nullable String[] lines) throws IllegalArgumentException; /** -@@ -879,7 +1082,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -881,7 +1084,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @throws IllegalArgumentException if location is null * @throws IllegalArgumentException if dyeColor is null * @throws IllegalArgumentException if lines is non-null and has a length less than 4 @@ -2472,7 +2472,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 public void sendSignChange(@NotNull Location loc, @Nullable String[] lines, @NotNull DyeColor dyeColor) throws IllegalArgumentException; /** -@@ -902,7 +1109,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -904,7 +1111,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @throws IllegalArgumentException if location is null * @throws IllegalArgumentException if dyeColor is null * @throws IllegalArgumentException if lines is non-null and has a length less than 4 @@ -2484,7 +2484,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 public void sendSignChange(@NotNull Location loc, @Nullable String[] lines, @NotNull DyeColor dyeColor, boolean hasGlowingText) throws IllegalArgumentException; /** -@@ -1350,7 +1561,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1372,7 +1583,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @throws IllegalArgumentException Thrown if the URL is null. * @throws IllegalArgumentException Thrown if the URL is too long. * @deprecated Minecraft no longer uses textures packs. Instead you @@ -2493,7 +2493,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 */ @Deprecated public void setTexturePack(@NotNull String url); -@@ -1386,7 +1597,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1408,7 +1619,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @throws IllegalArgumentException Thrown if the URL is null. * @throws IllegalArgumentException Thrown if the URL is too long. The * length restriction is an implementation specific arbitrary value. @@ -2503,7 +2503,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 public void setResourcePack(@NotNull String url); /** -@@ -1418,6 +1631,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1440,6 +1653,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * pack correctly. * * @@ -2511,7 +2511,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 * @param url The URL from which the client will download the resource * pack. The string must contain only US-ASCII characters and should * be encoded as per RFC 1738. -@@ -1430,6 +1644,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1452,6 +1666,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @throws IllegalArgumentException Thrown if the hash is not 20 bytes * long. */ @@ -2519,7 +2519,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 public void setResourcePack(@NotNull String url, @Nullable byte[] hash); /** -@@ -1454,12 +1669,13 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1476,12 +1691,13 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * {@link PlayerResourcePackStatusEvent} to figure out whether or not * the player loaded the pack! *

  • To remove a resource pack you can use @@ -2534,7 +2534,7 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 * @param url The URL from which the client will download the resource * pack. The string must contain only US-ASCII characters and should * be encoded as per RFC 1738. -@@ -1473,8 +1689,57 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1495,8 +1711,10 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @throws IllegalArgumentException Thrown if the hash is not 20 bytes * long. */ @@ -2542,28 +2542,14 @@ index b2925af90c531f0e8a5c39a915834f25d0015460..696efe5b86346209e9780b73229236e8 public void setResourcePack(@NotNull String url, @Nullable byte[] hash, @Nullable String prompt); + // Paper start -+ /** -+ * Request that the player's client download and switch resource packs. -+ *

    -+ * The player's client will download the new resource pack asynchronously -+ * in the background, and will automatically switch to it once the -+ * download is complete. If the client has downloaded and cached a -+ * resource pack with the same hash in the past it will not download but -+ * directly apply the cached pack. If the hash is null and the client has -+ * downloaded and cached the same resource pack in the past, it will -+ * perform a file size check against the response content to determine if -+ * the resource pack has changed and needs to be downloaded again. When -+ * this request is sent for the very first time from a given server, the -+ * client will first display a confirmation GUI to the player before -+ * proceeding with the download. -+ *

    -+ * Notes: -+ *

    -@@ -1780,9 +1777,6 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1802,9 +1799,6 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * case this method will have no affect on them. Use the * {@link PlayerResourcePackStatusEvent} to figure out whether or not * the player loaded the pack! @@ -505,7 +505,7 @@ index c1ab6640003ec02f8e8f9ebd4060854c68d46baa..fa011b40e1369d5161f6af22b23885e3 *
  • The request is send with empty string as the hash. This might result * in newer versions not loading the pack correctly. * -@@ -1819,9 +1813,6 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1841,9 +1835,6 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * case this method will have no affect on them. Use the * {@link PlayerResourcePackStatusEvent} to figure out whether or not * the player loaded the pack! diff --git a/patches/api/0060-Basic-PlayerProfile-API.patch b/patches/api/0060-Basic-PlayerProfile-API.patch index 05b288bad7df..4a664dab73a8 100644 --- a/patches/api/0060-Basic-PlayerProfile-API.patch +++ b/patches/api/0060-Basic-PlayerProfile-API.patch @@ -321,10 +321,10 @@ index 0000000000000000000000000000000000000000..7b3b6ef533d32169fbeca389bd61cfc6 + } +} diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 22d63e82c3c7d54e90c5cd7adef09882f01c7da2..cf00ff0c0332b31167f8f1b7b386674458cdf15a 100644 +index 2bdba56855d3427a1c48bfada0e6416085386cdb..48cce5c4a31ce9df3f2fe0aba4dd50e0547493b6 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -2314,6 +2314,83 @@ public final class Bukkit { +@@ -2334,6 +2334,83 @@ public final class Bukkit { public static boolean suggestPlayerNamesWhenNullTabCompletions() { return server.suggestPlayerNamesWhenNullTabCompletions(); } @@ -409,10 +409,10 @@ index 22d63e82c3c7d54e90c5cd7adef09882f01c7da2..cf00ff0c0332b31167f8f1b7b3866744 @NotNull diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 5a8f15195b0a87bb7a49983e5ee0dee6d2ce242c..4016129ead172c5f5b550482f523921d39df046f 100644 +index 50e8c25cc378b02b09ef57643cc753fa58ec1166..7bbd014aa3ecbae15518d9ebe4e6ec03a870ed5e 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -2032,5 +2032,74 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -2048,5 +2048,74 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * @return true if player names should be suggested */ boolean suggestPlayerNamesWhenNullTabCompletions(); diff --git a/patches/api/0063-Entity-fromMobSpawner.patch b/patches/api/0063-Entity-fromMobSpawner.patch index 98664b4d0a0d..8922abb73e1e 100644 --- a/patches/api/0063-Entity-fromMobSpawner.patch +++ b/patches/api/0063-Entity-fromMobSpawner.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Entity#fromMobSpawner() diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 24341516dd7d358b391f5e895cd837b5a10a8802..754097d9858b3a74f73c6ca483c2577c837f9f3a 100644 +index fd3b707893ba59aa6f6e4009113c5a94be5d7536..28e2d003db578e7c0468c0d443fda3a40e2a0efa 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -793,5 +793,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -788,5 +788,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ @Nullable Location getOrigin(); diff --git a/patches/api/0077-Expose-client-protocol-version-and-virtual-host.patch b/patches/api/0077-Expose-client-protocol-version-and-virtual-host.patch index bc5b41f1a8fd..95708e688a8f 100644 --- a/patches/api/0077-Expose-client-protocol-version-and-virtual-host.patch +++ b/patches/api/0077-Expose-client-protocol-version-and-virtual-host.patch @@ -57,10 +57,10 @@ index 0000000000000000000000000000000000000000..7b2af1bd72dfbcf4e962a982940fc49b + +} diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index cdc797fd351ffb261a0233e48b684dfd3bb62386..3f28d02d39c937abf001ae286632b84a5814fb61 100644 +index d507aeb5b906b5b68d1daa5bfd2d98ede1b0e7b6..74ccef3361a8089a2bf03cc3d2e0826f067b647e 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -52,7 +52,7 @@ import org.jetbrains.annotations.Nullable; +@@ -54,7 +54,7 @@ import org.jetbrains.annotations.Nullable; /** * Represents a player, connected or not */ diff --git a/patches/api/0081-Ability-to-apply-mending-to-XP-API.patch b/patches/api/0081-Ability-to-apply-mending-to-XP-API.patch index 34e7521d4796..5515a779ea5a 100644 --- a/patches/api/0081-Ability-to-apply-mending-to-XP-API.patch +++ b/patches/api/0081-Ability-to-apply-mending-to-XP-API.patch @@ -10,10 +10,10 @@ of giving the player experience points. Both an API To standalone mend, and apply mending logic to .giveExp has been added. diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 8d09741cbb66ae6f0037f3195c1f1f57850307e4..90930f0f4eb30534c94165079d8c3ad923e6de0e 100644 +index 74ccef3361a8089a2bf03cc3d2e0826f067b647e..8ec100779944579f83cfc1be4b124a4c780cdc07 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1471,6 +1471,15 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1499,6 +1499,15 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public void resetPlayerWeather(); @@ -29,7 +29,7 @@ index 8d09741cbb66ae6f0037f3195c1f1f57850307e4..90930f0f4eb30534c94165079d8c3ad9 /** * Gets the player's cooldown between picking up experience orbs. * -@@ -1496,8 +1505,20 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1524,8 +1533,20 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * Gives the player the amount of experience specified. * * @param amount Exp amount to give diff --git a/patches/api/0092-Player.setPlayerProfile-API.patch b/patches/api/0092-Player.setPlayerProfile-API.patch index 037d277caa33..0d0b10e01967 100644 --- a/patches/api/0092-Player.setPlayerProfile-API.patch +++ b/patches/api/0092-Player.setPlayerProfile-API.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Player.setPlayerProfile API This can be useful for changing name or skins after a player has logged in. diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index cf00ff0c0332b31167f8f1b7b386674458cdf15a..11288c4e1cb6a1f8322c6cbacb3750e6b08dad5e 100644 +index 48cce5c4a31ce9df3f2fe0aba4dd50e0547493b6..990436521c4d080d7adbd0a8c55f03690f17c1ec 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -1316,8 +1316,10 @@ public final class Bukkit { +@@ -1336,8 +1336,10 @@ public final class Bukkit { * @return the new PlayerProfile * @throws IllegalArgumentException if both the unique id is * null and the name is null or blank @@ -20,7 +20,7 @@ index cf00ff0c0332b31167f8f1b7b386674458cdf15a..11288c4e1cb6a1f8322c6cbacb3750e6 public static PlayerProfile createPlayerProfile(@Nullable UUID uniqueId, @Nullable String name) { return server.createPlayerProfile(uniqueId, name); } -@@ -1328,8 +1330,10 @@ public final class Bukkit { +@@ -1348,8 +1350,10 @@ public final class Bukkit { * @param uniqueId the unique id * @return the new PlayerProfile * @throws IllegalArgumentException if the unique id is null @@ -31,7 +31,7 @@ index cf00ff0c0332b31167f8f1b7b386674458cdf15a..11288c4e1cb6a1f8322c6cbacb3750e6 public static PlayerProfile createPlayerProfile(@NotNull UUID uniqueId) { return server.createPlayerProfile(uniqueId); } -@@ -1341,8 +1345,10 @@ public final class Bukkit { +@@ -1361,8 +1365,10 @@ public final class Bukkit { * @return the new PlayerProfile * @throws IllegalArgumentException if the name is null or * blank @@ -56,10 +56,10 @@ index ff59479f4782ac7726504aab239de79fdc840cde..abbf3d6f11350ab2dd47a277771d9f46 /** * Checks if this player has had their profile banned. diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 4016129ead172c5f5b550482f523921d39df046f..4d500904b28375f8517aa05075667da22f23f754 100644 +index 7bbd014aa3ecbae15518d9ebe4e6ec03a870ed5e..c60be47a0ac646133211ab4bf17b4fad4d1893db 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -1127,8 +1127,10 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -1143,8 +1143,10 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * @return the new PlayerProfile * @throws IllegalArgumentException if both the unique id is * null and the name is null or blank @@ -70,7 +70,7 @@ index 4016129ead172c5f5b550482f523921d39df046f..4d500904b28375f8517aa05075667da2 PlayerProfile createPlayerProfile(@Nullable UUID uniqueId, @Nullable String name); /** -@@ -1137,8 +1139,10 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -1153,8 +1155,10 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * @param uniqueId the unique id * @return the new PlayerProfile * @throws IllegalArgumentException if the unique id is null @@ -81,7 +81,7 @@ index 4016129ead172c5f5b550482f523921d39df046f..4d500904b28375f8517aa05075667da2 PlayerProfile createPlayerProfile(@NotNull UUID uniqueId); /** -@@ -1148,8 +1152,10 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -1164,8 +1168,10 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * @return the new PlayerProfile * @throws IllegalArgumentException if the name is null or * blank @@ -93,10 +93,10 @@ index 4016129ead172c5f5b550482f523921d39df046f..4d500904b28375f8517aa05075667da2 /** diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 90930f0f4eb30534c94165079d8c3ad923e6de0e..95f791b1f0af0735e565c0150fb2a4a3597b9997 100644 +index 8ec100779944579f83cfc1be4b124a4c780cdc07..ecc99524f20e1d7072bfad3ac310cccc4514e40f 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -2949,6 +2949,26 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3017,6 +3017,26 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM } // Paper end diff --git a/patches/api/0093-getPlayerUniqueId-API.patch b/patches/api/0093-getPlayerUniqueId-API.patch index 9b6ebd84649c..64497be9584c 100644 --- a/patches/api/0093-getPlayerUniqueId-API.patch +++ b/patches/api/0093-getPlayerUniqueId-API.patch @@ -9,10 +9,10 @@ In Offline Mode, will return an Offline UUID This is a more performant way to obtain a UUID for a name than loading an OfflinePlayer diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 11288c4e1cb6a1f8322c6cbacb3750e6b08dad5e..579b002425024a942091c95880366ba8f1761e1c 100644 +index 990436521c4d080d7adbd0a8c55f03690f17c1ec..4f9ebcd991875d0bf486a8ebb39909f2ac32493b 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -699,6 +699,20 @@ public final class Bukkit { +@@ -719,6 +719,20 @@ public final class Bukkit { return server.getPlayer(id); } @@ -34,10 +34,10 @@ index 11288c4e1cb6a1f8322c6cbacb3750e6b08dad5e..579b002425024a942091c95880366ba8 * Gets the plugin manager for interfacing with plugins. * diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 4d500904b28375f8517aa05075667da22f23f754..4c16d026c6850f38295e71f4f4299e81f3e4c856 100644 +index c60be47a0ac646133211ab4bf17b4fad4d1893db..7c53979f407a6a24c6d16bbfb205a8ac0321e5dc 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -601,6 +601,18 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -617,6 +617,18 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi @Nullable public Player getPlayer(@NotNull UUID id); diff --git a/patches/api/0095-Add-openSign-method-to-HumanEntity.patch b/patches/api/0095-Add-openSign-method-to-HumanEntity.patch index 18673117dc77..513d2c7b96b5 100644 --- a/patches/api/0095-Add-openSign-method-to-HumanEntity.patch +++ b/patches/api/0095-Add-openSign-method-to-HumanEntity.patch @@ -36,10 +36,10 @@ index abdca9fe5acc90f167219eb769ece66c35682bb1..b3aa3dc6aa5afbc36cc86741b4cba56f /** * Make the entity drop the item in their hand. diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 95f791b1f0af0735e565c0150fb2a4a3597b9997..40853a37eb433b587ab63741367bbaf0ca60c0c1 100644 +index ecc99524f20e1d7072bfad3ac310cccc4514e40f..d06f9b4d0117515fb8fcf78d416dcd2b4ef6fb4b 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -2910,10 +2910,12 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2978,10 +2978,12 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM /** * Open a Sign for editing by the Player. * diff --git a/patches/api/0096-Add-Ban-Methods-to-Player-Objects.patch b/patches/api/0096-Add-Ban-Methods-to-Player-Objects.patch index b300e1c039e2..02815e865bd4 100644 --- a/patches/api/0096-Add-Ban-Methods-to-Player-Objects.patch +++ b/patches/api/0096-Add-Ban-Methods-to-Player-Objects.patch @@ -74,10 +74,10 @@ index abbf3d6f11350ab2dd47a277771d9f46221036bd..d8a3b6cb2d0cb035b2ab09e0327bc4f0 /** * Adds this user to the {@link ProfileBanList}. If a previous ban exists, this will diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 40853a37eb433b587ab63741367bbaf0ca60c0c1..62707884e012badec14f74665f32058812bbc46e 100644 +index d06f9b4d0117515fb8fcf78d416dcd2b4ef6fb4b..07dde016220eef654901e3d78e2d37fb4ee4a128 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1145,6 +1145,162 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1173,6 +1173,162 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM public void sendMap(@NotNull MapView map); // Paper start diff --git a/patches/api/0098-Enderman.teleportRandomly.patch b/patches/api/0098-Enderman.teleportRandomly.patch index 907a74cf51f5..5fedcbb7461c 100644 --- a/patches/api/0098-Enderman.teleportRandomly.patch +++ b/patches/api/0098-Enderman.teleportRandomly.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Enderman.teleportRandomly() Ability to trigger the vanilla "teleport randomly" mechanic of an enderman. diff --git a/src/main/java/org/bukkit/entity/Enderman.java b/src/main/java/org/bukkit/entity/Enderman.java -index 0a03dc437973888ba57ba00fec47193fee38b5a9..605af1a9fc48602643aec57dd14e8c4ab657a0bc 100644 +index cc4648528c08980e6191e2fbdd7ba366617491b5..3afe2787de576f7190d87c796bea0ab34dc30248 100644 --- a/src/main/java/org/bukkit/entity/Enderman.java +++ b/src/main/java/org/bukkit/entity/Enderman.java -@@ -11,6 +11,17 @@ import org.jetbrains.annotations.Nullable; +@@ -10,6 +10,17 @@ import org.jetbrains.annotations.Nullable; */ public interface Enderman extends Monster { diff --git a/patches/api/0099-Additional-world.getNearbyEntities-API-s.patch b/patches/api/0099-Additional-world.getNearbyEntities-API-s.patch index 41ef5f3eb305..631c04c408b6 100644 --- a/patches/api/0099-Additional-world.getNearbyEntities-API-s.patch +++ b/patches/api/0099-Additional-world.getNearbyEntities-API-s.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Additional world.getNearbyEntities API's Provides more methods to get nearby entities, and filter by types and predicates diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index faedd3857023513340b6e9fc67b78c79e3989cbe..58a15d8fd57d55848b37bfc8fffa101692efce87 100644 +index 6decacdf85827305dbee9d34dadef4bc7c69e20a..fa4081c62f86245fef5a273f01837f9ac6998faa 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -1,6 +1,9 @@ @@ -19,7 +19,7 @@ index faedd3857023513340b6e9fc67b78c79e3989cbe..58a15d8fd57d55848b37bfc8fffa1016 import java.util.Collection; import java.util.HashMap; import java.util.List; -@@ -625,6 +628,238 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -627,6 +630,238 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient @NotNull public Collection getEntitiesByClasses(@NotNull Class... classes); diff --git a/patches/api/0101-Expand-World.spawnParticle-API-and-add-Builder.patch b/patches/api/0101-Expand-World.spawnParticle-API-and-add-Builder.patch index 4709adf7e93e..a491dad5b07d 100644 --- a/patches/api/0101-Expand-World.spawnParticle-API-and-add-Builder.patch +++ b/patches/api/0101-Expand-World.spawnParticle-API-and-add-Builder.patch @@ -523,10 +523,10 @@ index ca6d0eaa9d9a37c07f3e1630b83a79bf98211edb..26d02aa5da444112f8fa84c07e3080bb * Options which can be applied to redstone dust particles - a particle * color and size. diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 27b6825a04e7857e7924ba32f9cc43e960edd238..89d948229eee47c90d078ffa26b019bfe24a8115 100644 +index fa4081c62f86245fef5a273f01837f9ac6998faa..fa31ba9d30cd0d1b23e15667c4c9bd9c13b5b9d3 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -2855,7 +2855,57 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -2896,7 +2896,57 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient * @param data the data to use for the particle or null, * the type of this depends on {@link Particle#getDataType()} */ diff --git a/patches/api/0114-Add-EntityKnockbackByEntityEvent-and-EntityPushedByE.patch b/patches/api/0114-Add-EntityKnockbackByEntityEvent-and-EntityPushedByE.patch index e66a38f9c9b0..d6d986c4503b 100644 --- a/patches/api/0114-Add-EntityKnockbackByEntityEvent-and-EntityPushedByE.patch +++ b/patches/api/0114-Add-EntityKnockbackByEntityEvent-and-EntityPushedByE.patch @@ -62,10 +62,10 @@ index 0000000000000000000000000000000000000000..e0ba692c9b107f2b042a9c06549185e1 +} diff --git a/src/main/java/io/papermc/paper/event/entity/EntityPushedByEntityAttackEvent.java b/src/main/java/io/papermc/paper/event/entity/EntityPushedByEntityAttackEvent.java new file mode 100644 -index 0000000000000000000000000000000000000000..70d6e72d4dc0c040c8bccf7acc383e84db472514 +index 0000000000000000000000000000000000000000..404bec776244fd776566c81f671f1009830c6d6e --- /dev/null +++ b/src/main/java/io/papermc/paper/event/entity/EntityPushedByEntityAttackEvent.java -@@ -0,0 +1,74 @@ +@@ -0,0 +1,82 @@ +package io.papermc.paper.event.entity; + +import org.bukkit.entity.Entity; @@ -88,8 +88,7 @@ index 0000000000000000000000000000000000000000..70d6e72d4dc0c040c8bccf7acc383e84 + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final @NotNull Entity pushedBy; -+ private final @NotNull Vector acceleration; -+ ++ private @NotNull Vector acceleration; + private boolean cancelled; + + @ApiStatus.Internal @@ -116,7 +115,16 @@ index 0000000000000000000000000000000000000000..70d6e72d4dc0c040c8bccf7acc383e84 + */ + @NotNull + public Vector getAcceleration() { -+ return this.acceleration; ++ return this.acceleration; // TODO Clone in 1.21 to not instantly break what was technically already modifiable ++ } ++ ++ /** ++ * Sets the relative acceleration that will be applied to the affected entity. ++ * ++ * @param acceleration the new acceleration vector ++ */ ++ public void setAcceleration(final @NotNull Vector acceleration) { ++ this.acceleration = acceleration.clone(); + } + + @Override @@ -140,3 +148,33 @@ index 0000000000000000000000000000000000000000..70d6e72d4dc0c040c8bccf7acc383e84 + return HANDLER_LIST; + } +} +diff --git a/src/main/java/org/bukkit/event/entity/EntityKnockbackByEntityEvent.java b/src/main/java/org/bukkit/event/entity/EntityKnockbackByEntityEvent.java +index 3f17290c0863cc1d452bb50c524c18b6ab255d70..bd44bc5ed9e20148f9b2ab3d2049187280f3eb18 100644 +--- a/src/main/java/org/bukkit/event/entity/EntityKnockbackByEntityEvent.java ++++ b/src/main/java/org/bukkit/event/entity/EntityKnockbackByEntityEvent.java +@@ -7,7 +7,10 @@ import org.jetbrains.annotations.NotNull; + + /** + * Called when an entity receives knockback from another entity. ++ * ++ * @deprecated use {@link com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent} + */ ++@Deprecated(forRemoval = true) // Paper + public class EntityKnockbackByEntityEvent extends EntityKnockbackEvent { + + private final Entity source; +diff --git a/src/main/java/org/bukkit/event/entity/EntityKnockbackEvent.java b/src/main/java/org/bukkit/event/entity/EntityKnockbackEvent.java +index 9355efbbd4625e34d6c9d26bcbd02272202dec79..fe3374fbbfef728358e4a15bbf2deb238a1e0bfd 100644 +--- a/src/main/java/org/bukkit/event/entity/EntityKnockbackEvent.java ++++ b/src/main/java/org/bukkit/event/entity/EntityKnockbackEvent.java +@@ -11,7 +11,10 @@ import org.jetbrains.annotations.NotNull; + + /** + * Called when a living entity receives knockback. ++ * ++ * @deprecated use {@link com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent} or {@link io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent} + */ ++@Deprecated(forRemoval = true) // Paper + public class EntityKnockbackEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); diff --git a/patches/api/0115-Expand-Explosions-API.patch b/patches/api/0115-Expand-Explosions-API.patch index 3be51b350ef8..c74930f1a29d 100644 --- a/patches/api/0115-Expand-Explosions-API.patch +++ b/patches/api/0115-Expand-Explosions-API.patch @@ -108,10 +108,10 @@ index 3161eae2fa5f03b7d3a5e9945ab659c15cf568c6..af737017ee397f80c44ee02c6cc60cef /** * Returns a list of entities within a bounding box centered around a Location. diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 93c46d40efd73293d84c7eaadf4e360cc3706e2a..cd946f2de8a09fdb6ff9b256ca7eba64e6ed9aab 100644 +index fa31ba9d30cd0d1b23e15667c4c9bd9c13b5b9d3..ab0f76c74205f2fe1faf9aecc57ac3fb57431b06 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -1380,6 +1380,88 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -1382,6 +1382,88 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient */ public boolean createExplosion(@NotNull Location loc, float power, boolean setFire); diff --git a/patches/api/0119-Add-World.getEntity-UUID-API.patch b/patches/api/0119-Add-World.getEntity-UUID-API.patch index fbf6d332454b..f1e054d2acbd 100644 --- a/patches/api/0119-Add-World.getEntity-UUID-API.patch +++ b/patches/api/0119-Add-World.getEntity-UUID-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add World.getEntity(UUID) API diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index cd946f2de8a09fdb6ff9b256ca7eba64e6ed9aab..7ebe35096db30854443932add9d1f737f328ec92 100644 +index ab0f76c74205f2fe1faf9aecc57ac3fb57431b06..ad7e2b624d5929beee5268151bda3abe9dfbb786 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -894,6 +894,17 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -896,6 +896,17 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient @NotNull public Collection getNearbyEntities(@NotNull Location location, double x, double y, double z); diff --git a/patches/api/0122-Entity-getChunk-API.patch b/patches/api/0122-Entity-getChunk-API.patch index 55dee522c44c..069bb8834ea8 100644 --- a/patches/api/0122-Entity-getChunk-API.patch +++ b/patches/api/0122-Entity-getChunk-API.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Entity#getChunk API Get the chunk the entity is currently registered to diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 754097d9858b3a74f73c6ca483c2577c837f9f3a..40affe35704aa1a5e5a6f3661be966391c423ad2 100644 +index 28e2d003db578e7c0468c0d443fda3a40e2a0efa..77a706dde5995a8a6306b1d0a144dd37d580dea3 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java @@ -3,6 +3,7 @@ package org.bukkit.entity; @@ -17,7 +17,7 @@ index 754097d9858b3a74f73c6ca483c2577c837f9f3a..40affe35704aa1a5e5a6f3661be96639 import org.bukkit.EntityEffect; import org.bukkit.Location; import org.bukkit.Nameable; -@@ -800,5 +801,16 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -795,5 +796,16 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent * @return True if entity spawned from a mob spawner */ boolean fromMobSpawner(); diff --git a/patches/api/0133-Provide-Chunk-Coordinates-as-a-Long-API.patch b/patches/api/0133-Provide-Chunk-Coordinates-as-a-Long-API.patch index eb07065fa40a..55e2525efcc6 100644 --- a/patches/api/0133-Provide-Chunk-Coordinates-as-a-Long-API.patch +++ b/patches/api/0133-Provide-Chunk-Coordinates-as-a-Long-API.patch @@ -7,10 +7,10 @@ Allows you to easily access the chunks X/z as a long, and a method to look up by the long key too. diff --git a/src/main/java/org/bukkit/Chunk.java b/src/main/java/org/bukkit/Chunk.java -index efbfed855248cff8b4bdbfc181d3e82058df4749..766d643f0fe79660942fdad25e39e488e9379419 100644 +index a25f112f4d679946ddcb5ec9b4d0a0e2d1795bd3..57976bbe682d2309f7d15d5dcd3ad7f8049429ec 100644 --- a/src/main/java/org/bukkit/Chunk.java +++ b/src/main/java/org/bukkit/Chunk.java -@@ -33,6 +33,32 @@ public interface Chunk extends PersistentDataHolder { +@@ -35,6 +35,32 @@ public interface Chunk extends PersistentDataHolder { */ int getZ(); @@ -44,10 +44,10 @@ index efbfed855248cff8b4bdbfc181d3e82058df4749..766d643f0fe79660942fdad25e39e488 * Gets the world containing this chunk * diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 7ebe35096db30854443932add9d1f737f328ec92..be0e1ad34c526f2bd7b80f035c79b07e3b3ef5fb 100644 +index ad7e2b624d5929beee5268151bda3abe9dfbb786..9c22650c299ab063a7e558408ce6c203c79d11fd 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -180,6 +180,37 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -182,6 +182,37 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient @NotNull public Chunk getChunkAt(@NotNull Block block); diff --git a/patches/api/0134-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch b/patches/api/0134-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch index c6a30483777b..1ec0eed80554 100644 --- a/patches/api/0134-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch +++ b/patches/api/0134-Ability-to-get-Tile-Entities-from-a-chunk-without-sn.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Ability to get Tile Entities from a chunk without snapshots diff --git a/src/main/java/org/bukkit/Chunk.java b/src/main/java/org/bukkit/Chunk.java -index 766d643f0fe79660942fdad25e39e488e9379419..eca55d8d3464f0e13a3b7984f74559ccda87edba 100644 +index 57976bbe682d2309f7d15d5dcd3ad7f8049429ec..546888898d9d6827079fe041c7bc6eb4e1e4605c 100644 --- a/src/main/java/org/bukkit/Chunk.java +++ b/src/main/java/org/bukkit/Chunk.java -@@ -122,7 +122,30 @@ public interface Chunk extends PersistentDataHolder { +@@ -124,7 +124,30 @@ public interface Chunk extends PersistentDataHolder { * @return The tile entities. */ @NotNull diff --git a/patches/api/0136-Allow-Blocks-to-be-accessed-via-a-long-key.patch b/patches/api/0136-Allow-Blocks-to-be-accessed-via-a-long-key.patch index c58b8692c94e..fa5588a81f82 100644 --- a/patches/api/0136-Allow-Blocks-to-be-accessed-via-a-long-key.patch +++ b/patches/api/0136-Allow-Blocks-to-be-accessed-via-a-long-key.patch @@ -50,10 +50,10 @@ index 41125de49db8eafce4be59cc110ce5be06836a47..042d69e6d4584eb6d678b8ea13a3e4be * @return A new location where X/Y/Z are the center of the block */ diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index f15e1a6a43c0353ac4834f74fffb9adc2049dfcb..d5ebfdefe015e5509f0ecf53accfee2afbd4aadd 100644 +index 9c22650c299ab063a7e558408ce6c203c79d11fd..036a8ddc82a82800275d654d3fc00a9d30215350 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -97,6 +97,41 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -99,6 +99,41 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient @NotNull public Block getBlockAt(@NotNull Location location); @@ -96,7 +96,7 @@ index f15e1a6a43c0353ac4834f74fffb9adc2049dfcb..d5ebfdefe015e5509f0ecf53accfee2a * Gets the highest non-empty (impassable) block at the given coordinates. * diff --git a/src/main/java/org/bukkit/block/Block.java b/src/main/java/org/bukkit/block/Block.java -index 9f4d383ea3d6b26d16b8b77ca4c29d2d839ad6dd..f3a18e337a579b602b1289bccdf454334a663fcf 100644 +index f3b3606dc5881e931853fc2631aad9ca9083474d..a71001677e2b1b0b6225a7be63b8ea5ce4456862 100644 --- a/src/main/java/org/bukkit/block/Block.java +++ b/src/main/java/org/bukkit/block/Block.java @@ -156,6 +156,82 @@ public interface Block extends Metadatable, Translatable { diff --git a/patches/api/0141-isChunkGenerated-API.patch b/patches/api/0141-isChunkGenerated-API.patch index c8e246532b95..642a714f6492 100644 --- a/patches/api/0141-isChunkGenerated-API.patch +++ b/patches/api/0141-isChunkGenerated-API.patch @@ -5,7 +5,7 @@ Subject: [PATCH] isChunkGenerated API diff --git a/src/main/java/org/bukkit/Location.java b/src/main/java/org/bukkit/Location.java -index 34eeed3ec165bee9d9172ea636b1cc2d7d05f938..0b202d378d50946f43434e70d9d511cac06749b0 100644 +index 042d69e6d4584eb6d678b8ea13a3e4bea78703b8..02b4ffa6b918269bd64f7c518fcceef1f6990737 100644 --- a/src/main/java/org/bukkit/Location.java +++ b/src/main/java/org/bukkit/Location.java @@ -3,6 +3,7 @@ package org.bukkit; @@ -37,10 +37,10 @@ index 34eeed3ec165bee9d9172ea636b1cc2d7d05f938..0b202d378d50946f43434e70d9d511ca /** diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 55c0ad31ae8f1831c43404abb7e2e62da63885ce..07f723d5fb72e2eb776af130dc1d5caea16c5295 100644 +index 036a8ddc82a82800275d654d3fc00a9d30215350..25ff747e23e7373bb96ca9109df7e46cdefdcd2e 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -246,6 +246,19 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -248,6 +248,19 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient } // Paper end - chunk long key API diff --git a/patches/api/0143-Async-Chunks-API.patch b/patches/api/0143-Async-Chunks-API.patch index a04a93a06c55..2473d4c47f9f 100644 --- a/patches/api/0143-Async-Chunks-API.patch +++ b/patches/api/0143-Async-Chunks-API.patch @@ -8,10 +8,10 @@ Adds API's to load or generate chunks asynchronously. Also adds utility methods to Entity to teleport asynchronously. diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 07f723d5fb72e2eb776af130dc1d5caea16c5295..99a31572fa393a2482548ec55a96cb8568d07199 100644 +index 25ff747e23e7373bb96ca9109df7e46cdefdcd2e..116bad653e92efbfd576f3b146c0a9e40afbbe10 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -939,6 +939,472 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -941,6 +941,472 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient } // Paper end - additional getNearbyEntities API @@ -485,7 +485,7 @@ index 07f723d5fb72e2eb776af130dc1d5caea16c5295..99a31572fa393a2482548ec55a96cb85 * Get a list of all players in this World * diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 40affe35704aa1a5e5a6f3661be966391c423ad2..bdc547298fd29fe55016a17ff1be0d51619c0e2d 100644 +index 77a706dde5995a8a6306b1d0a144dd37d580dea3..14e42959033919ff6409e48ddf01c0f15c28eb10 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java @@ -168,6 +168,33 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent diff --git a/patches/api/0145-Expose-attack-cooldown-methods-for-Player.patch b/patches/api/0145-Expose-attack-cooldown-methods-for-Player.patch index 6c649269e814..7b7377559d10 100644 --- a/patches/api/0145-Expose-attack-cooldown-methods-for-Player.patch +++ b/patches/api/0145-Expose-attack-cooldown-methods-for-Player.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Expose attack cooldown methods for Player diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 62707884e012badec14f74665f32058812bbc46e..76f70e0db9409aa731d79e2c7b16d39b4c2dfc4c 100644 +index 07dde016220eef654901e3d78e2d37fb4ee4a128..7fe44fd8a1e93551365ea434e750f7dece9088de 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3127,6 +3127,28 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3195,6 +3195,28 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM void setPlayerProfile(com.destroystokyo.paper.profile.@NotNull PlayerProfile profile); // Paper end - Player Profile API diff --git a/patches/api/0147-Add-Git-information-to-version-command-on-startup.patch b/patches/api/0147-Add-Git-information-to-version-command-on-startup.patch index 9d6dcb06b43e..847ce11745af 100644 --- a/patches/api/0147-Add-Git-information-to-version-command-on-startup.patch +++ b/patches/api/0147-Add-Git-information-to-version-command-on-startup.patch @@ -48,10 +48,10 @@ index 0000000000000000000000000000000000000000..909617079db61b675cc7b60b44ef96b3 + } +} diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 9f52c092d906622e2eade3bc9d8baac6816f9f3a..869d57b429fbe2694d1444db56c56619abfae920 100644 +index 4f9ebcd991875d0bf486a8ebb39909f2ac32493b..c61096f724ea6a1ae7bc8990e9eaa44a16f5847e 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -57,6 +57,7 @@ import org.bukkit.util.CachedServerIcon; +@@ -58,6 +58,7 @@ import org.bukkit.util.CachedServerIcon; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -59,7 +59,7 @@ index 9f52c092d906622e2eade3bc9d8baac6816f9f3a..869d57b429fbe2694d1444db56c56619 /** * Represents the Bukkit core, for version and Server singleton handling -@@ -106,7 +107,25 @@ public final class Bukkit { +@@ -107,7 +108,25 @@ public final class Bukkit { } Bukkit.server = server; diff --git a/patches/api/0157-Add-sun-related-API.patch b/patches/api/0157-Add-sun-related-API.patch index 540087547e6f..df9c9b7818ec 100644 --- a/patches/api/0157-Add-sun-related-API.patch +++ b/patches/api/0157-Add-sun-related-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add sun related API diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 99a31572fa393a2482548ec55a96cb8568d07199..561cfe701d76fa0c85cea4a76affa7e90de82da0 100644 +index 116bad653e92efbfd576f3b146c0a9e40afbbe10..bc0f09b2d555682d0bf7937e9aa6c97258e66635 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -1760,6 +1760,16 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -1762,6 +1762,16 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient */ public void setFullTime(long time); diff --git a/patches/api/0161-Make-the-default-permission-message-configurable.patch b/patches/api/0161-Make-the-default-permission-message-configurable.patch index 67be23ffad70..cad7e3c31056 100644 --- a/patches/api/0161-Make-the-default-permission-message-configurable.patch +++ b/patches/api/0161-Make-the-default-permission-message-configurable.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Make the default permission message configurable diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 378a457dee428238eb2c4827e9c1d2cc57d0f544..a06e6f51606612f5b9a69ce2d46be84231c08177 100644 +index c61096f724ea6a1ae7bc8990e9eaa44a16f5847e..ac84e6d10a337f767477177ef90ad10d754341e6 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -2354,6 +2354,28 @@ public final class Bukkit { +@@ -2374,6 +2374,28 @@ public final class Bukkit { return server.suggestPlayerNamesWhenNullTabCompletions(); } @@ -38,10 +38,10 @@ index 378a457dee428238eb2c4827e9c1d2cc57d0f544..a06e6f51606612f5b9a69ce2d46be842 * Creates a PlayerProfile for the specified uuid, with name as null. * diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 4c16d026c6850f38295e71f4f4299e81f3e4c856..904502029842e8c3be700be33b156f11db4724af 100644 +index 7c53979f407a6a24c6d16bbfb205a8ac0321e5dc..050cdd5147814b39d158f0ce0fa8f5aa20894cf7 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -2051,6 +2051,23 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -2067,6 +2067,23 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi */ boolean suggestPlayerNamesWhenNullTabCompletions(); diff --git a/patches/api/0172-Entity-getEntitySpawnReason.patch b/patches/api/0172-Entity-getEntitySpawnReason.patch index aa296072be57..a00834444c28 100644 --- a/patches/api/0172-Entity-getEntitySpawnReason.patch +++ b/patches/api/0172-Entity-getEntitySpawnReason.patch @@ -12,10 +12,10 @@ or DEFAULT since data was not stored. Co-authored-by: Aurora diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index bdc547298fd29fe55016a17ff1be0d51619c0e2d..3117bf9d61b507175cfb673095763a5d6bc802ba 100644 +index 14e42959033919ff6409e48ddf01c0f15c28eb10..469e85c3cb371842a78b32f8da97fe9c71bf409b 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -839,5 +839,11 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -834,5 +834,11 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent // TODO remove impl here return getLocation().getChunk(); } diff --git a/patches/api/0173-Fix-Spigot-annotation-mistakes.patch b/patches/api/0173-Fix-Spigot-annotation-mistakes.patch index b62df72e9d4e..837422c19fc7 100644 --- a/patches/api/0173-Fix-Spigot-annotation-mistakes.patch +++ b/patches/api/0173-Fix-Spigot-annotation-mistakes.patch @@ -40,10 +40,10 @@ index ac420f0059fc50d3e1294f85df7515c9e17ff78f..24daba85ce4129fb0babe67570059ca8 public static Art getById(int id) { return BY_ID.get(id); diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index a06e6f51606612f5b9a69ce2d46be84231c08177..3e072efdb4f552a0d010fe4c8efe523f59cdccd0 100644 +index ac84e6d10a337f767477177ef90ad10d754341e6..567a0cda774ce2e28e1466c0c6b1e07b931b4008 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -848,9 +848,8 @@ public final class Bukkit { +@@ -868,9 +868,8 @@ public final class Bukkit { * * @param id the id of the map to get * @return a map view if it exists, or null otherwise @@ -54,7 +54,7 @@ index a06e6f51606612f5b9a69ce2d46be84231c08177..3e072efdb4f552a0d010fe4c8efe523f @Nullable public static MapView getMap(int id) { return server.getMap(id); -@@ -1317,10 +1316,8 @@ public final class Bukkit { +@@ -1337,10 +1336,8 @@ public final class Bukkit { * @param name the name the player to retrieve * @return an offline player * @see #getOfflinePlayer(java.util.UUID) @@ -66,7 +66,7 @@ index a06e6f51606612f5b9a69ce2d46be84231c08177..3e072efdb4f552a0d010fe4c8efe523f @NotNull public static OfflinePlayer getOfflinePlayer(@NotNull String name) { return server.getOfflinePlayer(name); -@@ -1911,7 +1908,7 @@ public final class Bukkit { +@@ -1931,7 +1928,7 @@ public final class Bukkit { * * @return the scoreboard manager or null if no worlds are loaded. */ @@ -474,15 +474,14 @@ index 26d02aa5da444112f8fa84c07e3080bb669983a1..0cb15350704955f4a1aeff184a8b60d9 private final NamespacedKey key; diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java -index 6455163261082c444985bc58b188d224517d4529..613cd9c2e91a851c86e339d2be86981b57669311 100644 +index db45423936d48835dee35d01ee502cdfdce4f68a..b3f8a2611ceb57f5d0b5c300fa80d8bad121498d 100644 --- a/src/main/java/org/bukkit/Registry.java +++ b/src/main/java/org/bukkit/Registry.java -@@ -198,14 +198,14 @@ public interface Registry extends Iterable { +@@ -199,14 +199,12 @@ public interface Registry extends Iterable { * * @see TrimMaterial */ - @ApiStatus.Experimental -+ //@ApiStatus.Experimental // Paper Registry TRIM_MATERIAL = Bukkit.getRegistry(TrimMaterial.class); /** * Trim patterns. @@ -490,11 +489,10 @@ index 6455163261082c444985bc58b188d224517d4529..613cd9c2e91a851c86e339d2be86981b * @see TrimPattern */ - @ApiStatus.Experimental -+ //@ApiStatus.Experimental // Paper Registry TRIM_PATTERN = Bukkit.getRegistry(TrimPattern.class); /** - * Villager profession. -@@ -287,8 +287,11 @@ public interface Registry extends Iterable { + * Damage types. +@@ -295,8 +293,11 @@ public interface Registry extends Iterable { * * @param input non-null input * @return registered object or null if does not exist @@ -521,10 +519,10 @@ index 6277451c3c6c551078c237cd767b6d70c4f585ea..10f5cfb1885833a1d2c1027c03974da4 CRACKED(0x0), GLYPHED(0x1), diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 904502029842e8c3be700be33b156f11db4724af..b9c7ff1dcbc09980faf26ce3319f7ee09bafc6df 100644 +index 050cdd5147814b39d158f0ce0fa8f5aa20894cf7..e5844d4a9808984fa21049401ed117102a1c4db8 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -712,9 +712,8 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -728,9 +728,8 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * * @param id the id of the map to get * @return a map view if it exists, or null otherwise @@ -535,7 +533,7 @@ index 904502029842e8c3be700be33b156f11db4724af..b9c7ff1dcbc09980faf26ce3319f7ee0 @Nullable public MapView getMap(int id); -@@ -1111,10 +1110,8 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -1127,10 +1126,8 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * @param name the name the player to retrieve * @return an offline player * @see #getOfflinePlayer(java.util.UUID) @@ -547,7 +545,7 @@ index 904502029842e8c3be700be33b156f11db4724af..b9c7ff1dcbc09980faf26ce3319f7ee0 @NotNull public OfflinePlayer getOfflinePlayer(@NotNull String name); -@@ -1614,7 +1611,7 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -1630,7 +1627,7 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * * @return the scoreboard manager or null if no worlds are loaded. */ @@ -600,10 +598,10 @@ index e455eb21abf121dc6ff10ff8a13dd06f67096a8f..bbc01e7c192ae6689c301670047ff114 return origin; } diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 985dcea9ebed2fc5a3bfb8581cbd0ee4bf89ee8f..08eb8744104f1bbbd4f96972e0fb68f1aa4049f5 100644 +index bc0f09b2d555682d0bf7937e9aa6c97258e66635..bc5dd4182c5dd0a0d576410a68c445e3d006ae18 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -416,9 +416,8 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -418,9 +418,8 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient * @param z Z-coordinate of the chunk * @return Whether the chunk was actually refreshed * @@ -614,7 +612,7 @@ index 985dcea9ebed2fc5a3bfb8581cbd0ee4bf89ee8f..08eb8744104f1bbbd4f96972e0fb68f1 public boolean refreshChunk(int x, int z); /** -@@ -3704,6 +3703,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -3745,6 +3744,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient StructureSearchResult locateNearestStructure(@NotNull Location origin, @NotNull Structure structure, int radius, boolean findUnexplored); // Spigot start @@ -622,7 +620,7 @@ index 985dcea9ebed2fc5a3bfb8581cbd0ee4bf89ee8f..08eb8744104f1bbbd4f96972e0fb68f1 public class Spigot { /** -@@ -3737,7 +3737,11 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -3778,7 +3778,11 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient } } @@ -634,7 +632,7 @@ index 985dcea9ebed2fc5a3bfb8581cbd0ee4bf89ee8f..08eb8744104f1bbbd4f96972e0fb68f1 Spigot spigot(); // Spigot end -@@ -3928,9 +3932,9 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -3996,9 +4000,9 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient * Gets the dimension ID of this environment * * @return dimension ID @@ -646,7 +644,7 @@ index 985dcea9ebed2fc5a3bfb8581cbd0ee4bf89ee8f..08eb8744104f1bbbd4f96972e0fb68f1 public int getId() { return id; } -@@ -3940,9 +3944,9 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -4008,9 +4012,9 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient * * @param id The ID of the environment * @return The environment @@ -738,10 +736,10 @@ index b90f5dc345ad2cdd3ae353dc57f42a14c231d18a..a7b915ded9154d53ac8ca599119c1699 public static PistonMoveReaction getById(int id) { return byId.get(id); diff --git a/src/main/java/org/bukkit/entity/Enderman.java b/src/main/java/org/bukkit/entity/Enderman.java -index 605af1a9fc48602643aec57dd14e8c4ab657a0bc..b3085c7ff8b3e96083d209f6612c006578773c24 100644 +index 3afe2787de576f7190d87c796bea0ab34dc30248..58191017244f3949f6174fb108e3a245738a53c4 100644 --- a/src/main/java/org/bukkit/entity/Enderman.java +++ b/src/main/java/org/bukkit/entity/Enderman.java -@@ -26,15 +26,19 @@ public interface Enderman extends Monster { +@@ -25,15 +25,19 @@ public interface Enderman extends Monster { * Gets the id and data of the block that the Enderman is carrying. * * @return MaterialData containing the id and data of the block @@ -820,7 +818,7 @@ index f124b35ec76e6cb6a1a0dc464005087043c3efd0..f50aaddf8582be55fd4860ad374d8f22 +@Deprecated(forRemoval = true) // Paper public interface LingeringPotion extends ThrownPotion { } diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 4605153a2a8aa7b7cd0eed772c15f02f18707ec3..c58f7b4a7c05e35056a478a818a9a6cdfe99203f 100644 +index b2f66f80c90b9d716f43f94166ca015bdfd3173e..030d3b1b1fee0906d6a0fb2a47031b8a94caaf89 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java @@ -684,7 +684,9 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource @@ -859,10 +857,10 @@ index 95c79c5fa0c4e30201f887da6467ce5f81c8a255..7f9c4d4b430a3f0276461346ff2621ba /** diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 76f70e0db9409aa731d79e2c7b16d39b4c2dfc4c..ae324d684fa1fe256ae2b2c50bd0566ad0a27110 100644 +index 7fe44fd8a1e93551365ea434e750f7dece9088de..2778f72af5de5cd339a8648c5631ff94d5ee1352 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1539,11 +1539,8 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1567,11 +1567,8 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM /** * Forces an update of the player's entire inventory. @@ -1572,19 +1570,6 @@ index 597a18a767b68b47e81454b7d44613c7178c1366..bc3440eb72127824b3961fbdae583bb6 @NotNull public ItemStack getInput() { return this.ingredient.getItemStack(); -diff --git a/src/main/java/org/bukkit/inventory/meta/ColorableArmorMeta.java b/src/main/java/org/bukkit/inventory/meta/ColorableArmorMeta.java -index 5ccae862dbac393805a47fe26c18a2f33f1e140d..72281899817c5c140cdca2afff75fbcebd942532 100644 ---- a/src/main/java/org/bukkit/inventory/meta/ColorableArmorMeta.java -+++ b/src/main/java/org/bukkit/inventory/meta/ColorableArmorMeta.java -@@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull; - /** - * Represents armor that an entity can equip and can also be colored. - */ --@ApiStatus.Experimental -+//@ApiStatus.Experimental // Paper - public interface ColorableArmorMeta extends ArmorMeta, LeatherArmorMeta { - - @Override diff --git a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java index f800e776329c1b42f834cb30ebf2d0ace195d1a2..f23c0c942f200a68d2620f225ab90399a9057dfc 100644 --- a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java diff --git a/patches/api/0176-Add-Heightmap-API.patch b/patches/api/0176-Add-Heightmap-API.patch index bede72444539..848d5fcfcbbf 100644 --- a/patches/api/0176-Add-Heightmap-API.patch +++ b/patches/api/0176-Add-Heightmap-API.patch @@ -51,7 +51,7 @@ index 0000000000000000000000000000000000000000..1c832d69bb3717dcfccf21e45f6f060a + SOLID_OR_LIQUID_NO_LEAVES; +} diff --git a/src/main/java/org/bukkit/Location.java b/src/main/java/org/bukkit/Location.java -index cf42f6e57e96aa9cb4465e34a6e3f8709de4ca09..9bbd928f7d513ca317cd27beffa61e5111f5ffb0 100644 +index f0878c7539696cc0676e6010e88914d3850acf20..c6049747fc286acb4e8053901fcc517e5170afa2 100644 --- a/src/main/java/org/bukkit/Location.java +++ b/src/main/java/org/bukkit/Location.java @@ -649,6 +649,46 @@ public class Location implements Cloneable, ConfigurationSerializable, io.paperm @@ -102,10 +102,10 @@ index cf42f6e57e96aa9cb4465e34a6e3f8709de4ca09..9bbd928f7d513ca317cd27beffa61e51 /** * Creates explosion at this location with given power diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 8a200ffe1851b24110c92bb3a9f7ffc39b8c63f2..dd498e3ba46bd001028f7f9f94e18de42e875ff6 100644 +index bc5dd4182c5dd0a0d576410a68c445e3d006ae18..24ce9966140730c581c0709b14280e62a58331fa 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -151,6 +151,87 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -153,6 +153,87 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient @NotNull public Block getHighestBlockAt(@NotNull Location location); diff --git a/patches/api/0181-Expose-the-internal-current-tick.patch b/patches/api/0181-Expose-the-internal-current-tick.patch index cab5b202d723..9a0fe90fb69f 100644 --- a/patches/api/0181-Expose-the-internal-current-tick.patch +++ b/patches/api/0181-Expose-the-internal-current-tick.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Expose the internal current tick diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 3e072efdb4f552a0d010fe4c8efe523f59cdccd0..bff6eb5114e44bf04eb793ad78752dabee471543 100644 +index 567a0cda774ce2e28e1466c0c6b1e07b931b4008..80144dc81249f7198bee5e6281362fd704e006d4 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -2449,6 +2449,10 @@ public final class Bukkit { +@@ -2469,6 +2469,10 @@ public final class Bukkit { public static com.destroystokyo.paper.profile.PlayerProfile createProfileExact(@Nullable UUID uuid, @Nullable String name) { return server.createProfileExact(uuid, name); } @@ -20,10 +20,10 @@ index 3e072efdb4f552a0d010fe4c8efe523f59cdccd0..bff6eb5114e44bf04eb793ad78752dab @NotNull diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index b9c7ff1dcbc09980faf26ce3319f7ee09bafc6df..9cb3ab0ecfdab51e8dd1c397eb58bcdcde7a6a1a 100644 +index e5844d4a9808984fa21049401ed117102a1c4db8..031fd20bb361fb9befe62ec8006f42156bfe2747 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -2133,5 +2133,12 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -2149,5 +2149,12 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi */ @NotNull com.destroystokyo.paper.profile.PlayerProfile createProfileExact(@Nullable UUID uuid, @Nullable String name); diff --git a/patches/api/0187-Add-tick-times-API.patch b/patches/api/0187-Add-tick-times-API.patch index 6bc3b548d340..c2d69a38e701 100644 --- a/patches/api/0187-Add-tick-times-API.patch +++ b/patches/api/0187-Add-tick-times-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add tick times API diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index bff6eb5114e44bf04eb793ad78752dabee471543..36af91308b498b72639bef62aaf82a1ea26ec91c 100644 +index 80144dc81249f7198bee5e6281362fd704e006d4..a839e8684de0c915f8b7edfe5e87978d774f7bde 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -2121,6 +2121,25 @@ public final class Bukkit { +@@ -2141,6 +2141,25 @@ public final class Bukkit { public static double[] getTPS() { return server.getTPS(); } @@ -35,10 +35,10 @@ index bff6eb5114e44bf04eb793ad78752dabee471543..36af91308b498b72639bef62aaf82a1e /** diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 9cb3ab0ecfdab51e8dd1c397eb58bcdcde7a6a1a..037fab9568abda1ae2a54d3759ba74ec4fc09ff9 100644 +index 031fd20bb361fb9befe62ec8006f42156bfe2747..496e769cb77d856b4a0b4dc63ac466fdf6cbd3f9 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -1795,6 +1795,21 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -1811,6 +1811,21 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi */ @NotNull public double[] getTPS(); diff --git a/patches/api/0188-Expose-MinecraftServer-isRunning.patch b/patches/api/0188-Expose-MinecraftServer-isRunning.patch index 2297138cfa97..641ee5ac5445 100644 --- a/patches/api/0188-Expose-MinecraftServer-isRunning.patch +++ b/patches/api/0188-Expose-MinecraftServer-isRunning.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Expose MinecraftServer#isRunning This allows for plugins to detect if the server is actually turning off in onDisable rather than just plugins reloading. diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 36af91308b498b72639bef62aaf82a1ea26ec91c..27e62bd04426295e23134e2e601550a995ea7059 100644 +index a839e8684de0c915f8b7edfe5e87978d774f7bde..00320a15e4edbcbab182b4064b05d3a19dc441ce 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -2472,6 +2472,15 @@ public final class Bukkit { +@@ -2492,6 +2492,15 @@ public final class Bukkit { public static int getCurrentTick() { return server.getCurrentTick(); } @@ -26,10 +26,10 @@ index 36af91308b498b72639bef62aaf82a1ea26ec91c..27e62bd04426295e23134e2e601550a9 @NotNull diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 037fab9568abda1ae2a54d3759ba74ec4fc09ff9..7a292b24d90b36278321200e13fa12d7d46225da 100644 +index 496e769cb77d856b4a0b4dc63ac466fdf6cbd3f9..d606b1d1ac37a13607c893e14ad88b26d1296ab0 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -2155,5 +2155,12 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -2171,5 +2171,12 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * @return Current tick */ int getCurrentTick(); diff --git a/patches/api/0189-Add-Raw-Byte-ItemStack-Serialization.patch b/patches/api/0189-Add-Raw-Byte-ItemStack-Serialization.patch index 3a9050cf8819..ac702b7e9021 100644 --- a/patches/api/0189-Add-Raw-Byte-ItemStack-Serialization.patch +++ b/patches/api/0189-Add-Raw-Byte-ItemStack-Serialization.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Add Raw Byte ItemStack Serialization Serializes using NBT which is safer for server data migrations than bukkits format. diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 4604392831d19a789e4906cf1a5f0197105fd6f2..f063016f8a88dbff480ac3b4b3ef05c16a8e515a 100644 +index 688fccdbc5cf831008ef2f27db9d15b0921a7561..e4861a8be534bfeae0385f0197261fa6ec1e7bc0 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -134,5 +134,9 @@ public interface UnsafeValues { +@@ -151,5 +151,9 @@ public interface UnsafeValues { default com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { return new com.destroystokyo.paper.util.VersionFetcher.DummyVersionFetcher(); } diff --git a/patches/api/0190-Add-Player-Client-Options-API.patch b/patches/api/0190-Add-Player-Client-Options-API.patch index 4048aa206a88..f43e60785024 100644 --- a/patches/api/0190-Add-Player-Client-Options-API.patch +++ b/patches/api/0190-Add-Player-Client-Options-API.patch @@ -231,10 +231,10 @@ index 0000000000000000000000000000000000000000..1757055d821d9ec7c728aa6c1b52fa6a + } +} diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index ae324d684fa1fe256ae2b2c50bd0566ad0a27110..f36dc68a71e9799a638693ae5615743f94a85d40 100644 +index 2778f72af5de5cd339a8648c5631ff94d5ee1352..06872dcef2d7f158ca3f25fb69b56511a0a1a90e 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3146,6 +3146,13 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3214,6 +3214,13 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM void resetCooldown(); // Paper end - attack cooldown API diff --git a/patches/api/0195-Expose-game-version.patch b/patches/api/0195-Expose-game-version.patch index 9d1750b3aaae..5cd19ffff949 100644 --- a/patches/api/0195-Expose-game-version.patch +++ b/patches/api/0195-Expose-game-version.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Expose game version diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 4e8383432a3a8a07dbc31f77986b0f4790779f7d..29cf7359334144d6e718fed560771be35f580b16 100644 +index 00320a15e4edbcbab182b4064b05d3a19dc441ce..ed65e84868f6b3b9272c8266244d7ab3725a7699 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -158,6 +158,18 @@ public final class Bukkit { +@@ -159,6 +159,18 @@ public final class Bukkit { return server.getBukkitVersion(); } @@ -28,10 +28,10 @@ index 4e8383432a3a8a07dbc31f77986b0f4790779f7d..29cf7359334144d6e718fed560771be3 * Gets a view of all currently logged in players. This {@linkplain * Collections#unmodifiableCollection(Collection) view} is a reused diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 37caeff1416cf0e3c63260ba7ad82a92e95a5399..d97200a8816dbbbce07734b5547a942f8f3f0fdc 100644 +index d606b1d1ac37a13607c893e14ad88b26d1296ab0..e87718db19c902fe385328866767e46c397b7e86 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -116,6 +116,16 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -117,6 +117,16 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi @NotNull public String getBukkitVersion(); diff --git a/patches/api/0196-Add-Mob-Goal-API.patch b/patches/api/0196-Add-Mob-Goal-API.patch index ce84f29868ed..5f64cd9ab94d 100644 --- a/patches/api/0196-Add-Mob-Goal-API.patch +++ b/patches/api/0196-Add-Mob-Goal-API.patch @@ -226,10 +226,10 @@ index 0000000000000000000000000000000000000000..e21f7574763dd4f13794f91bbef192ef + Collection> getRunningGoalsWithout(@NotNull T mob, @NotNull GoalType type); +} diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 29cf7359334144d6e718fed560771be35f580b16..5c508045a53d9f6efe6358648daa47c0096ad55e 100644 +index ed65e84868f6b3b9272c8266244d7ab3725a7699..946dc3c3bde31c7a02ac4733ee8c88e63d8baff8 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -2493,6 +2493,16 @@ public final class Bukkit { +@@ -2513,6 +2513,16 @@ public final class Bukkit { public static boolean isStopping() { return server.isStopping(); } @@ -247,10 +247,10 @@ index 29cf7359334144d6e718fed560771be35f580b16..5c508045a53d9f6efe6358648daa47c0 @NotNull diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index d97200a8816dbbbce07734b5547a942f8f3f0fdc..aec7814485efb0b827ccfde92372a436d47ed2f5 100644 +index e87718db19c902fe385328866767e46c397b7e86..000713e09564c96a7d3c9ceb0371de26a11ff82e 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -2172,5 +2172,13 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -2188,5 +2188,13 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * @return true if server is in the process of being shutdown */ boolean isStopping(); diff --git a/patches/api/0205-Add-entity-liquid-API.patch b/patches/api/0205-Add-entity-liquid-API.patch index cefdd0dc5aef..286f7e4e5a42 100644 --- a/patches/api/0205-Add-entity-liquid-API.patch +++ b/patches/api/0205-Add-entity-liquid-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add entity liquid API diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 3117bf9d61b507175cfb673095763a5d6bc802ba..72878285779dcfc6f365dc983f79b6e542792b89 100644 +index 469e85c3cb371842a78b32f8da97fe9c71bf409b..1e766f77e39b86b0561884a478d10cedc3d98fc2 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -845,5 +845,40 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -840,5 +840,40 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ @NotNull org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason getEntitySpawnReason(); diff --git a/patches/api/0208-Brand-support.patch b/patches/api/0208-Brand-support.patch index c56833b9dff4..393665178f1f 100644 --- a/patches/api/0208-Brand-support.patch +++ b/patches/api/0208-Brand-support.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Brand support diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index f36dc68a71e9799a638693ae5615743f94a85d40..f57c4a1ce6d1324c2adffed14b9ecb7700bea81c 100644 +index 06872dcef2d7f158ca3f25fb69b56511a0a1a90e..74dd447e74bae93a568acce75a7e3818ab6e5568 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3259,6 +3259,16 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3327,6 +3327,16 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM // Paper end } diff --git a/patches/api/0215-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch b/patches/api/0215-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch index 0118683bc24b..8d1a16ed4299 100644 --- a/patches/api/0215-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch +++ b/patches/api/0215-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Expose the Entity Counter to allow plugins to use valid and diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index f063016f8a88dbff480ac3b4b3ef05c16a8e515a..96b66f4f6fb8637ab3ad275ddd980d5b71711a6c 100644 +index e4861a8be534bfeae0385f0197261fa6ec1e7bc0..86bb2a6f46c7c39e7ac1923c3454785a3dc76648 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -138,5 +138,12 @@ public interface UnsafeValues { +@@ -155,5 +155,12 @@ public interface UnsafeValues { byte[] serializeItem(ItemStack item); ItemStack deserializeItem(byte[] data); diff --git a/patches/api/0216-Entity-isTicking.patch b/patches/api/0216-Entity-isTicking.patch index 8d63d599c7c6..b56340aad506 100644 --- a/patches/api/0216-Entity-isTicking.patch +++ b/patches/api/0216-Entity-isTicking.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Entity#isTicking diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 72878285779dcfc6f365dc983f79b6e542792b89..9eb90abcdd5ee78e495b2b53ed8643593e9485d3 100644 +index 1e766f77e39b86b0561884a478d10cedc3d98fc2..387413af95421159eae04737cc04c8221e88357b 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -880,5 +880,10 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -875,5 +875,10 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent * Check if entity is in lava */ boolean isInLava(); diff --git a/patches/api/0218-Player-elytra-boost-API.patch b/patches/api/0218-Player-elytra-boost-API.patch index d6552aab0a49..7b432696c4b6 100644 --- a/patches/api/0218-Player-elytra-boost-API.patch +++ b/patches/api/0218-Player-elytra-boost-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Player elytra boost API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index f57c4a1ce6d1324c2adffed14b9ecb7700bea81c..2f083ef738681ae1219438541487eac093343cf0 100644 +index 74dd447e74bae93a568acce75a7e3818ab6e5568..3f9cdf1cfd19e61e3568af536b195f08b26418ab 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3153,6 +3153,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3221,6 +3221,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM @NotNull T getClientOption(com.destroystokyo.paper.@NotNull ClientOption option); // Paper end - client option API diff --git a/patches/api/0219-Add-getOfflinePlayerIfCached-String.patch b/patches/api/0219-Add-getOfflinePlayerIfCached-String.patch index bfbdc483f7b2..de11733eec57 100644 --- a/patches/api/0219-Add-getOfflinePlayerIfCached-String.patch +++ b/patches/api/0219-Add-getOfflinePlayerIfCached-String.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add getOfflinePlayerIfCached(String) diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 1f63a4f8ffd11fe04f8dc807ad993e4b59172fea..dfe5055cefe6a110732e0fcc936dddb866cbd9e3 100644 +index 946dc3c3bde31c7a02ac4733ee8c88e63d8baff8..b4341f5c9adcebd13a0f62ebc32d081a65d71ba4 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -1335,6 +1335,27 @@ public final class Bukkit { +@@ -1355,6 +1355,27 @@ public final class Bukkit { return server.getOfflinePlayer(name); } @@ -37,10 +37,10 @@ index 1f63a4f8ffd11fe04f8dc807ad993e4b59172fea..dfe5055cefe6a110732e0fcc936dddb8 * Gets the player by the given UUID, regardless if they are offline or * online. diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 1e88ad22e98cbbde118e6208b3502aee7391bac8..7986d51083c2c27709032b06731621d2e89bec57 100644 +index 000713e09564c96a7d3c9ceb0371de26a11ff82e..3c64eb18b7cb6ac371b094a581da8a033e90d00f 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -1125,6 +1125,25 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -1141,6 +1141,25 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi @NotNull public OfflinePlayer getOfflinePlayer(@NotNull String name); diff --git a/patches/api/0245-Add-sendOpLevel-API.patch b/patches/api/0245-Add-sendOpLevel-API.patch index fa031a8e079c..2d47c5e770f5 100644 --- a/patches/api/0245-Add-sendOpLevel-API.patch +++ b/patches/api/0245-Add-sendOpLevel-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add sendOpLevel API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 2f083ef738681ae1219438541487eac093343cf0..7a5839ac8f4d6bfd23d456e8e06fcfc86d24531f 100644 +index 3f9cdf1cfd19e61e3568af536b195f08b26418ab..59821aba66edbef2644bdd21646f556e773a898b 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3172,6 +3172,19 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3240,6 +3240,19 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM } // Paper end - elytra boost API diff --git a/patches/api/0247-Add-StructuresLocateEvent.patch b/patches/api/0247-Add-StructuresLocateEvent.patch index f3cf01ce6849..ae3aef510212 100644 --- a/patches/api/0247-Add-StructuresLocateEvent.patch +++ b/patches/api/0247-Add-StructuresLocateEvent.patch @@ -513,10 +513,10 @@ index 0000000000000000000000000000000000000000..1e7b53f9bc13dcd5a0a4a40004591e4f + } +} diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java -index 613cd9c2e91a851c86e339d2be86981b57669311..4331acfc9efd08011e339a1bc0a5190abc197506 100644 +index b3f8a2611ceb57f5d0b5c300fa80d8bad121498d..4bc53793aade0887fa650a4bbf51d2e57678bd90 100644 --- a/src/main/java/org/bukkit/Registry.java +++ b/src/main/java/org/bukkit/Registry.java -@@ -262,6 +262,17 @@ public interface Registry extends Iterable { +@@ -268,6 +268,17 @@ public interface Registry extends Iterable { * @see GameEvent */ Registry GAME_EVENT = Objects.requireNonNull(Bukkit.getRegistry(GameEvent.class), "No registry present for GameEvent. This is a bug."); diff --git a/patches/api/0259-Expose-Tracked-Players.patch b/patches/api/0259-Expose-Tracked-Players.patch index 1abd6a33aa58..72636f895de8 100644 --- a/patches/api/0259-Expose-Tracked-Players.patch +++ b/patches/api/0259-Expose-Tracked-Players.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Expose Tracked Players diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 9eb90abcdd5ee78e495b2b53ed8643593e9485d3..d98fe98f703ff478ea4427783fd68debe9a6f267 100644 +index 387413af95421159eae04737cc04c8221e88357b..9d3694c6e1144e04006425fb96b802c96e5fdc12 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -885,5 +885,14 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -880,5 +880,14 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent * Check if entity is inside a ticking chunk */ boolean isTicking(); diff --git a/patches/api/0265-Expand-world-key-API.patch b/patches/api/0265-Expand-world-key-API.patch index 761d7d83f5af..ee70bbcfd2e4 100644 --- a/patches/api/0265-Expand-world-key-API.patch +++ b/patches/api/0265-Expand-world-key-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Expand world key API diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index dfe5055cefe6a110732e0fcc936dddb866cbd9e3..c4a4a0b1295739070017e7d09dd5e04a798494e3 100644 +index b4341f5c9adcebd13a0f62ebc32d081a65d71ba4..7e049d9934df2259ea566aaa69ce118ce829529d 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -842,6 +842,18 @@ public final class Bukkit { +@@ -862,6 +862,18 @@ public final class Bukkit { public static World getWorld(@NotNull UUID uid) { return server.getWorld(uid); } @@ -56,10 +56,10 @@ index 27eff0826d5b5b48697fefd9571886e7bbce74b1..d8b1fa79dc24138dc71e32c14bda71c1 // Paper end } diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 7986d51083c2c27709032b06731621d2e89bec57..0e3e654fb0551c6f862ce14b75cf1186392b023f 100644 +index 3c64eb18b7cb6ac371b094a581da8a033e90d00f..c3e0b40a845a30790f31a0cf591bfa7f0cd87fee 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -704,6 +704,17 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -720,6 +720,17 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi @Nullable public World getWorld(@NotNull UUID uid); @@ -78,10 +78,10 @@ index 7986d51083c2c27709032b06731621d2e89bec57..0e3e654fb0551c6f862ce14b75cf1186 * Create a new virtual {@link WorldBorder}. *

    diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 96b66f4f6fb8637ab3ad275ddd980d5b71711a6c..27d5f37a9b2da92307e5b505e3b31cca8a067018 100644 +index 86bb2a6f46c7c39e7ac1923c3454785a3dc76648..72a29fff4c497a2a66e2746ad42553bcb712e20d 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -145,5 +145,10 @@ public interface UnsafeValues { +@@ -162,5 +162,10 @@ public interface UnsafeValues { * Use this when sending custom packets, so that there are no collisions on the client or server. */ public int nextEntityId(); diff --git a/patches/api/0266-Item-Rarity-API.patch b/patches/api/0266-Item-Rarity-API.patch index aeb3aa7f1eaf..5e0072532446 100644 --- a/patches/api/0266-Item-Rarity-API.patch +++ b/patches/api/0266-Item-Rarity-API.patch @@ -61,10 +61,10 @@ index 85604d2c364c41fed24257a9b02ceeb58712f6a2..8fd928cfe61ab6f06c52eb5e4561fd68 /** diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 27d5f37a9b2da92307e5b505e3b31cca8a067018..26b0d5c0a62e516db6eef9dedc81216d7ed5053c 100644 +index 72a29fff4c497a2a66e2746ad42553bcb712e20d..22db1d8645a450308fe91d0cd100c926dd8c6f08 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -150,5 +150,22 @@ public interface UnsafeValues { +@@ -167,5 +167,22 @@ public interface UnsafeValues { * Just don't use it. */ @org.jetbrains.annotations.NotNull String getMainLevelName(); diff --git a/patches/api/0267-Expose-protocol-version.patch b/patches/api/0267-Expose-protocol-version.patch index 18c7331272bf..0d34eacc4cb0 100644 --- a/patches/api/0267-Expose-protocol-version.patch +++ b/patches/api/0267-Expose-protocol-version.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Expose protocol version diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 26b0d5c0a62e516db6eef9dedc81216d7ed5053c..3ae23a15a2c8757d3003041425ced583187d3d21 100644 +index 22db1d8645a450308fe91d0cd100c926dd8c6f08..a3810c693d3748fba818e4a835ceb77762f433b9 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -167,5 +167,12 @@ public interface UnsafeValues { +@@ -184,5 +184,12 @@ public interface UnsafeValues { * @return the itemstack rarity */ public io.papermc.paper.inventory.ItemRarity getItemStackRarity(ItemStack itemStack); diff --git a/patches/api/0272-More-World-API.patch b/patches/api/0272-More-World-API.patch index 3c429371d9a4..a3d88eb648f1 100644 --- a/patches/api/0272-More-World-API.patch +++ b/patches/api/0272-More-World-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] More World API diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 81b4e5764f68ef38938e645ca8180fc5ce263811..3fecfdebe43e3b543725a43f73465ae9ea030a0c 100644 +index 24ce9966140730c581c0709b14280e62a58331fa..d09c6b7c7b80f1f59e052ddb4aa8ad05b63fca2b 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -3783,6 +3783,122 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -3824,6 +3824,122 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient @Nullable StructureSearchResult locateNearestStructure(@NotNull Location origin, @NotNull Structure structure, int radius, boolean findUnexplored); diff --git a/patches/api/0282-Add-basic-Datapack-API.patch b/patches/api/0282-Add-basic-Datapack-API.patch index 497ce52ec66c..dafb02a98deb 100644 --- a/patches/api/0282-Add-basic-Datapack-API.patch +++ b/patches/api/0282-Add-basic-Datapack-API.patch @@ -70,10 +70,10 @@ index 0000000000000000000000000000000000000000..58f78d5e91beacaf710f62461cf869f7 + +} diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index c4a4a0b1295739070017e7d09dd5e04a798494e3..2e29da775e60c149d2c251d78ee7c60b494215a4 100644 +index 7e049d9934df2259ea566aaa69ce118ce829529d..8ca65a413295c1f104cc47f2f71cbc80345a9812 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -318,9 +318,11 @@ public final class Bukkit { +@@ -328,9 +328,11 @@ public final class Bukkit { /** * Get the DataPack Manager. * @@ -85,7 +85,7 @@ index c4a4a0b1295739070017e7d09dd5e04a798494e3..2e29da775e60c149d2c251d78ee7c60b public static DataPackManager getDataPackManager() { return server.getDataPackManager(); } -@@ -2536,6 +2538,14 @@ public final class Bukkit { +@@ -2556,6 +2558,14 @@ public final class Bukkit { public static com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { return server.getMobGoals(); } @@ -101,10 +101,10 @@ index c4a4a0b1295739070017e7d09dd5e04a798494e3..2e29da775e60c149d2c251d78ee7c60b @NotNull diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 0e3e654fb0551c6f862ce14b75cf1186392b023f..61701b6e8291cb5816b0a7eb511152eed3db43e8 100644 +index c3e0b40a845a30790f31a0cf591bfa7f0cd87fee..be0d3ea564503441910aecdfe3d45b0303facc19 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -256,9 +256,11 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -264,9 +264,11 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi /** * Get the DataPack Manager. * @@ -116,7 +116,7 @@ index 0e3e654fb0551c6f862ce14b75cf1186392b023f..61701b6e8291cb5816b0a7eb511152ee public DataPackManager getDataPackManager(); /** -@@ -2210,5 +2212,11 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -2226,5 +2228,11 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi */ @NotNull com.destroystokyo.paper.entity.ai.MobGoals getMobGoals(); diff --git a/patches/api/0284-ItemStack-repair-check-API.patch b/patches/api/0284-ItemStack-repair-check-API.patch index c6f36eb4dea8..46650ec4b7e0 100644 --- a/patches/api/0284-ItemStack-repair-check-API.patch +++ b/patches/api/0284-ItemStack-repair-check-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] ItemStack repair check API diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 3ae23a15a2c8757d3003041425ced583187d3d21..6b8013b072c9ca0a6f5ba86f37de3744fc70979e 100644 +index a3810c693d3748fba818e4a835ceb77762f433b9..d9e3e4ad108a94ac6f0f5378d22d47845091efb4 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -168,6 +168,16 @@ public interface UnsafeValues { +@@ -185,6 +185,16 @@ public interface UnsafeValues { */ public io.papermc.paper.inventory.ItemRarity getItemStackRarity(ItemStack itemStack); diff --git a/patches/api/0289-Attributes-API-for-item-defaults.patch b/patches/api/0289-Attributes-API-for-item-defaults.patch index 6a98472b0976..66172a60274b 100644 --- a/patches/api/0289-Attributes-API-for-item-defaults.patch +++ b/patches/api/0289-Attributes-API-for-item-defaults.patch @@ -31,10 +31,10 @@ index 8fd928cfe61ab6f06c52eb5e4561fd6860e1f8d9..64ca3c676703eed55b4ac8a2d4561d48 /** diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 6b8013b072c9ca0a6f5ba86f37de3744fc70979e..0f09250d536b7405f0dc253afb1f3c4ccbaeb0da 100644 +index d9e3e4ad108a94ac6f0f5378d22d47845091efb4..bdee63fb97cbc0c9fbe7df74de5cb5f4ed7de3ca 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -178,6 +178,18 @@ public interface UnsafeValues { +@@ -195,6 +195,18 @@ public interface UnsafeValues { */ public boolean isValidRepairItemStack(@org.jetbrains.annotations.NotNull ItemStack itemToBeRepaired, @org.jetbrains.annotations.NotNull ItemStack repairMaterial); diff --git a/patches/api/0292-Add-PlayerKickEvent-causes.patch b/patches/api/0292-Add-PlayerKickEvent-causes.patch index d03d83031ad9..39657e4039d0 100644 --- a/patches/api/0292-Add-PlayerKickEvent-causes.patch +++ b/patches/api/0292-Add-PlayerKickEvent-causes.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add PlayerKickEvent causes diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 6e44845340f23a5b6ad8be46a1fa3dbb9dec5d67..a5e4b4447b5c68339d6b09749520048642a4eb83 100644 +index 59821aba66edbef2644bdd21646f556e773a898b..793df7533096efb0f60bddcb3e4e1575cbcc1069 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -275,6 +275,14 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -277,6 +277,14 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param message kick message */ void kick(final net.kyori.adventure.text.@Nullable Component message); diff --git a/patches/api/0299-Missing-Entity-API.patch b/patches/api/0299-Missing-Entity-API.patch index 0fbca326eb62..7dff1ccf46f9 100644 --- a/patches/api/0299-Missing-Entity-API.patch +++ b/patches/api/0299-Missing-Entity-API.patch @@ -402,12 +402,12 @@ index 191ce6c0e32ab3d05b1376e0fa56d1292c2d442c..8de09075e14a08a6c68f9c24e8960cc0 -public interface Cod extends Fish { } +public interface Cod extends io.papermc.paper.entity.SchoolableFish { } // Paper - Schooling Fish API diff --git a/src/main/java/org/bukkit/entity/Enderman.java b/src/main/java/org/bukkit/entity/Enderman.java -index b3085c7ff8b3e96083d209f6612c006578773c24..b8ad718dbc6bc6e4000480d35c499cc1542998fa 100644 +index 58191017244f3949f6174fb108e3a245738a53c4..61672c6faf94aa497145aadd634bb10103c7b05a 100644 --- a/src/main/java/org/bukkit/entity/Enderman.java +++ b/src/main/java/org/bukkit/entity/Enderman.java -@@ -89,4 +89,36 @@ public interface Enderman extends Monster { +@@ -86,4 +86,36 @@ public interface Enderman extends Monster { + * @return true if the teleport succeeded. */ - @ApiStatus.Experimental public boolean teleportTowards(@NotNull Entity entity); + + // Paper start @@ -556,7 +556,7 @@ index 6b3c9bef9a8a34ddc6ff42cf358541a2665bf5e3..9c618a27d590f186f29c5d9094fc565e + // Paper end } diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 129df18c471f1ec0c286746953ae8803a209cfa7..ca74b215d226db16259e9ca9c930a49b3e444a7a 100644 +index b1fb059fc2249814c9e509c219da2aed84d34fe0..6e6b80843a8669b422f93e98343e1da9f8546ee7 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java @@ -1016,6 +1016,57 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource @@ -722,10 +722,10 @@ index 11b6d1aba7d1f6ae1f3c822193486f5a1478e105..709c8fc3dde786f45ff13d6ee6c405ff + // Paper end } diff --git a/src/main/java/org/bukkit/entity/MushroomCow.java b/src/main/java/org/bukkit/entity/MushroomCow.java -index 939a3dbfcf38f38e4e39d28973ef723157ce0a50..e14194a130ebd872bbc1eb24c7759f0388f3da97 100644 +index cef1700834643fe28ed5737578d91ecefbe99e2f..794b7b4a870a0d289476074e3a3f46552604c954 100644 --- a/src/main/java/org/bukkit/entity/MushroomCow.java +++ b/src/main/java/org/bukkit/entity/MushroomCow.java -@@ -35,4 +35,75 @@ public interface MushroomCow extends Cow { +@@ -95,4 +95,75 @@ public interface MushroomCow extends Cow { */ BROWN; } diff --git a/patches/api/0315-Add-methods-to-find-targets-for-lightning-strikes.patch b/patches/api/0315-Add-methods-to-find-targets-for-lightning-strikes.patch index 209cde5c4233..c84e83d2de17 100644 --- a/patches/api/0315-Add-methods-to-find-targets-for-lightning-strikes.patch +++ b/patches/api/0315-Add-methods-to-find-targets-for-lightning-strikes.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add methods to find targets for lightning strikes diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 39fd31cb24d16b8279f54e666bb5e7bccf32468c..21835fcc70c4dcffb2474caa7be3cd07bb54e2cd 100644 +index d09c6b7c7b80f1f59e052ddb4aa8ad05b63fca2b..6897600a6879d88cea287cde350c1e9cd11ae96d 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -735,6 +735,37 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -737,6 +737,37 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient @NotNull public LightningStrike strikeLightningEffect(@NotNull Location loc); diff --git a/patches/api/0316-Get-entity-default-attributes.patch b/patches/api/0316-Get-entity-default-attributes.patch index d655cddecbdd..60dc6739f1e4 100644 --- a/patches/api/0316-Get-entity-default-attributes.patch +++ b/patches/api/0316-Get-entity-default-attributes.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Get entity default attributes diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 0f09250d536b7405f0dc253afb1f3c4ccbaeb0da..f08e03c71f1e59d31a22822701b4e9ae6cc1429d 100644 +index bdee63fb97cbc0c9fbe7df74de5cb5f4ed7de3ca..5d4721595a24c8c970f2500fc805c3efc4508157 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -196,5 +196,22 @@ public interface UnsafeValues { +@@ -213,5 +213,22 @@ public interface UnsafeValues { * @return the server's protocol version */ int getProtocolVersion(); diff --git a/patches/api/0318-Add-critical-damage-API.patch b/patches/api/0318-Add-critical-damage-API.patch index 12f98b0fead7..e0621399a2f1 100644 --- a/patches/api/0318-Add-critical-damage-API.patch +++ b/patches/api/0318-Add-critical-damage-API.patch @@ -5,47 +5,58 @@ Subject: [PATCH] Add critical damage API diff --git a/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java b/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java -index 869bad7405ec7fa67728e90d8b9f2e11b542611f..aec5a0c2882cf69e8802b9e754b14d0acc34b162 100644 +index 0a43b1ffdb03bb8b67e880dfd9a6a1ce5d02eb32..f79eb4a0f634354ac68995144821bc4e966d2dd8 100644 --- a/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java +++ b/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java -@@ -11,15 +11,40 @@ import org.jetbrains.annotations.NotNull; +@@ -4,6 +4,7 @@ import com.google.common.base.Function; + import java.util.Map; + import org.bukkit.damage.DamageSource; + import org.bukkit.entity.Entity; ++import org.jetbrains.annotations.ApiStatus; + import org.jetbrains.annotations.NotNull; + + /** +@@ -11,17 +12,43 @@ import org.jetbrains.annotations.NotNull; + */ public class EntityDamageByEntityEvent extends EntityDamageEvent { private final Entity damager; ++ private final boolean critical; // Paper -+ @Deprecated // Paper - add critical damage API - public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, final double damage) { - super(damagee, cause, damage); ++ @Deprecated + public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final DamageSource damageSource, final double damage) { + super(damagee, cause, damageSource, damage); this.damager = damager; + this.critical = false; // Paper - add critical damage API } -+ @Deprecated // Paper - add critical damage API - public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions) { -+ // Paper start - add critical damage API -+ this(damager, damagee, cause, modifiers, modifierFunctions, false); -+ } -+ -+ private final boolean critical; -+ public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions, boolean critical) { -+ // Paper end - super(damagee, cause, modifiers, modifierFunctions); ++ @Deprecated + public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final DamageSource damageSource, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions) { + super(damagee, cause, damageSource, modifiers, modifierFunctions); this.damager = damager; -+ // Paper start - add critical damage API ++ // Paper start ++ this.critical = false; + } + ++ @ApiStatus.Internal ++ public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final DamageSource damageSource, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions, boolean critical) { ++ super(damagee, cause, damageSource, modifiers, modifierFunctions); ++ this.damager = damager; + this.critical = critical; + } + + /** + * Shows this damage instance was critical. + * The damage instance can be critical if the attacking player met the respective conditions. -+ * Furthermore arrows may also cause a critical damage event if the arrow {@link org.bukkit.entity.AbstractArrow#isCritical()}. ++ * Furthermore, arrows may also cause a critical damage event if the arrow {@link org.bukkit.entity.AbstractArrow#isCritical()}. + * + * @return if the hit was critical. + * @see https://minecraft.wiki/wiki/Damage#Critical_hit + */ + public boolean isCritical() { + return this.critical; - } ++ } + // Paper end - ++ /** * Returns the entity that damaged the defender. + * diff --git a/patches/api/0321-Add-isCollidable-methods-to-various-places.patch b/patches/api/0321-Add-isCollidable-methods-to-various-places.patch index 4931b0b29f50..a3abc4242a76 100644 --- a/patches/api/0321-Add-isCollidable-methods-to-various-places.patch +++ b/patches/api/0321-Add-isCollidable-methods-to-various-places.patch @@ -26,10 +26,10 @@ index 64ca3c676703eed55b4ac8a2d4561d483c6935b1..9f86ad25a57b3f6e6bda1ce657833837 /** diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index f08e03c71f1e59d31a22822701b4e9ae6cc1429d..7dc808f94c42e821fa125073b1a9b853e16d5b41 100644 +index 5d4721595a24c8c970f2500fc805c3efc4508157..434fde52986ba07d7209ff47483f74fe31e8ebe7 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -213,5 +213,14 @@ public interface UnsafeValues { +@@ -230,5 +230,14 @@ public interface UnsafeValues { * @throws IllegalArgumentException if the entity does not exist of have default attributes (use {@link #hasDefaultEntityAttributes(NamespacedKey)} first) */ @org.jetbrains.annotations.NotNull org.bukkit.attribute.Attributable getDefaultEntityAttributes(@org.jetbrains.annotations.NotNull NamespacedKey entityKey); @@ -45,7 +45,7 @@ index f08e03c71f1e59d31a22822701b4e9ae6cc1429d..7dc808f94c42e821fa125073b1a9b853 // Paper end } diff --git a/src/main/java/org/bukkit/block/Block.java b/src/main/java/org/bukkit/block/Block.java -index 6de4c858ff6e542daa0879092e4f99a948225964..05fd2aa8a1639598b488712d2fe5f739019f41dc 100644 +index b5fe76a6353816a2d009dfa5921f8ada92984f34..42cc4f2ee960c0abf9c6688aeee4150754612c32 100644 --- a/src/main/java/org/bukkit/block/Block.java +++ b/src/main/java/org/bukkit/block/Block.java @@ -482,6 +482,13 @@ public interface Block extends Metadatable, Translatable, net.kyori.adventure.tr diff --git a/patches/api/0324-Add-Raw-Byte-Entity-Serialization.patch b/patches/api/0324-Add-Raw-Byte-Entity-Serialization.patch index 46b080adb128..a809fef9b66d 100644 --- a/patches/api/0324-Add-Raw-Byte-Entity-Serialization.patch +++ b/patches/api/0324-Add-Raw-Byte-Entity-Serialization.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add Raw Byte Entity Serialization diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 7dc808f94c42e821fa125073b1a9b853e16d5b41..fc0356b0600946af0a46312cee16b080a7769ae2 100644 +index 434fde52986ba07d7209ff47483f74fe31e8ebe7..0c7204e390f44b649fc26cd46152abeb2ad2d670 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -139,6 +139,14 @@ public interface UnsafeValues { +@@ -156,6 +156,14 @@ public interface UnsafeValues { ItemStack deserializeItem(byte[] data); @@ -24,10 +24,10 @@ index 7dc808f94c42e821fa125073b1a9b853e16d5b41..fc0356b0600946af0a46312cee16b080 * Creates and returns the next EntityId available. *

    diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index d98fe98f703ff478ea4427783fd68debe9a6f267..6356370eed537cec782f036dc73fe5bfbe4c1fb7 100644 +index 9d3694c6e1144e04006425fb96b802c96e5fdc12..f69a6bccb174ce48ce0393c633279f84553d1df8 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -894,5 +894,32 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -889,5 +889,32 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ @Deprecated @NotNull Set getTrackedPlayers(); diff --git a/patches/api/0326-Allow-delegation-to-vanilla-chunk-gen.patch b/patches/api/0326-Allow-delegation-to-vanilla-chunk-gen.patch index a88d2a93be0f..e8f75d98e2a1 100644 --- a/patches/api/0326-Allow-delegation-to-vanilla-chunk-gen.patch +++ b/patches/api/0326-Allow-delegation-to-vanilla-chunk-gen.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Allow delegation to vanilla chunk gen diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 2e29da775e60c149d2c251d78ee7c60b494215a4..27fd3ea4d0f96efa24167623c756f463995161bd 100644 +index 8ca65a413295c1f104cc47f2f71cbc80345a9812..03a94d0b332f531832bfad9839ec60a2ad91222c 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -2056,6 +2056,24 @@ public final class Bukkit { +@@ -2076,6 +2076,24 @@ public final class Bukkit { return server.createChunkData(world); } @@ -34,10 +34,10 @@ index 2e29da775e60c149d2c251d78ee7c60b494215a4..27fd3ea4d0f96efa24167623c756f463 * Creates a boss bar instance to display to players. The progress * defaults to 1.0 diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 61701b6e8291cb5816b0a7eb511152eed3db43e8..d2c4887184661b68726ff8539d022b8dee8313fa 100644 +index be0d3ea564503441910aecdfe3d45b0303facc19..a1a9ed907eaa89e2915855eb2946d27ef3521b61 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -1738,6 +1738,22 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -1754,6 +1754,22 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi @NotNull public ChunkGenerator.ChunkData createChunkData(@NotNull World world); diff --git a/patches/api/0330-Entity-powdered-snow-API.patch b/patches/api/0330-Entity-powdered-snow-API.patch index 2a4ece4cef87..2d4522029f19 100644 --- a/patches/api/0330-Entity-powdered-snow-API.patch +++ b/patches/api/0330-Entity-powdered-snow-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Entity powdered snow API diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 6356370eed537cec782f036dc73fe5bfbe4c1fb7..6456f7bfc8aa8cb5c9aaf0c06f9130242003ee3e 100644 +index f69a6bccb174ce48ce0393c633279f84553d1df8..1b9abd69483941f19ee7d0bb28e2655806dd3ca8 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -921,5 +921,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -916,5 +916,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent * @return Whether the entity was successfully spawned. */ public boolean spawnAt(@NotNull Location location, @NotNull org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason); diff --git a/patches/api/0338-Multi-Block-Change-API.patch b/patches/api/0338-Multi-Block-Change-API.patch index 21086887b139..45664f22b3be 100644 --- a/patches/api/0338-Multi-Block-Change-API.patch +++ b/patches/api/0338-Multi-Block-Change-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Multi Block Change API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 07ba09643077fcd90d073b2efd52ae9a3a99198b..5662e957e7609aa4cc3e5fcf6b8f12cde6ae4305 100644 +index 793df7533096efb0f60bddcb3e4e1575cbcc1069..c8a50647d34c70bc927c33c602f938a01bf6e7a9 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -893,6 +893,29 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -895,6 +895,29 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public void sendBlockDamage(@NotNull Location loc, float progress); diff --git a/patches/api/0342-More-PotionEffectType-API.patch b/patches/api/0342-More-PotionEffectType-API.patch index 3ccfee4a9dcf..7e3682f8b41d 100644 --- a/patches/api/0342-More-PotionEffectType-API.patch +++ b/patches/api/0342-More-PotionEffectType-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] More PotionEffectType API diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java -index 4331acfc9efd08011e339a1bc0a5190abc197506..800d23bb249e19d5cf924e7ba36684068624da02 100644 +index 4bc53793aade0887fa650a4bbf51d2e57678bd90..18c672f3855a329bf8f87a9de81b677e8e360b41 100644 --- a/src/main/java/org/bukkit/Registry.java +++ b/src/main/java/org/bukkit/Registry.java -@@ -271,6 +271,31 @@ public interface Registry extends Iterable { +@@ -277,6 +277,31 @@ public interface Registry extends Iterable { */ @Deprecated(forRemoval = true) Registry CONFIGURED_STRUCTURE = Bukkit.getRegistry(io.papermc.paper.world.structure.ConfiguredStructure.class); diff --git a/patches/api/0343-API-for-creating-command-sender-which-forwards-feedb.patch b/patches/api/0343-API-for-creating-command-sender-which-forwards-feedb.patch index 328823c1039f..5c1c336819a5 100644 --- a/patches/api/0343-API-for-creating-command-sender-which-forwards-feedb.patch +++ b/patches/api/0343-API-for-creating-command-sender-which-forwards-feedb.patch @@ -5,10 +5,10 @@ Subject: [PATCH] API for creating command sender which forwards feedback diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index c5ac78c4a4711e9c44d5eb7f66b28f448bcda63a..d7a9b5f9aeececb4070264bba6d2240628cdca12 100644 +index 03a94d0b332f531832bfad9839ec60a2ad91222c..c64f6ccfcd08c851900e734a194b1de4c8e32b07 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -1545,6 +1545,20 @@ public final class Bukkit { +@@ -1565,6 +1565,20 @@ public final class Bukkit { return server.getConsoleSender(); } @@ -30,10 +30,10 @@ index c5ac78c4a4711e9c44d5eb7f66b28f448bcda63a..d7a9b5f9aeececb4070264bba6d22406 * Gets the folder that contains all of the various {@link World}s. * diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index d2c4887184661b68726ff8539d022b8dee8313fa..1fbd8ce1b9fa60ba06512dfcbd025212bc6dedfc 100644 +index a1a9ed907eaa89e2915855eb2946d27ef3521b61..d6c3fefbd407ae3aa1be9e71d05b6370f09dd905 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -1302,6 +1302,18 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -1318,6 +1318,18 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi @NotNull public ConsoleCommandSender getConsoleSender(); diff --git a/patches/api/0344-Implement-regenerateChunk.patch b/patches/api/0344-Implement-regenerateChunk.patch index 4625c51a5ac4..8023a26d0fb1 100644 --- a/patches/api/0344-Implement-regenerateChunk.patch +++ b/patches/api/0344-Implement-regenerateChunk.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Implement regenerateChunk diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 21835fcc70c4dcffb2474caa7be3cd07bb54e2cd..86c7b4ba676f826344056b24b204b748cfce1580 100644 +index 6897600a6879d88cea287cde350c1e9cd11ae96d..0eb90bfff2e12cb3ed977a581912221ac4dee6f6 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -484,8 +484,8 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -486,8 +486,8 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient * @return Whether the chunk was actually regenerated * * @deprecated regenerating a single chunk is not likely to produce the same diff --git a/patches/api/0349-Custom-Potion-Mixes.patch b/patches/api/0349-Custom-Potion-Mixes.patch index 57bce6f5d1ae..3881038adb8f 100644 --- a/patches/api/0349-Custom-Potion-Mixes.patch +++ b/patches/api/0349-Custom-Potion-Mixes.patch @@ -155,10 +155,10 @@ index 0000000000000000000000000000000000000000..3ede1e8f7bf0436fdc5bf395c0f9eaf1 + } +} diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index d7a9b5f9aeececb4070264bba6d2240628cdca12..50d336fda7f549e50dc127767ca35107c99a3483 100644 +index c64f6ccfcd08c851900e734a194b1de4c8e32b07..986d506ae0eb91363e7fdfa19b1f9d0d840a5207 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -2578,6 +2578,15 @@ public final class Bukkit { +@@ -2598,6 +2598,15 @@ public final class Bukkit { public static io.papermc.paper.datapack.DatapackManager getDatapackManager() { return server.getDatapackManager(); } @@ -175,10 +175,10 @@ index d7a9b5f9aeececb4070264bba6d2240628cdca12..50d336fda7f549e50dc127767ca35107 @NotNull diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 1fbd8ce1b9fa60ba06512dfcbd025212bc6dedfc..eca2b37d2841f3d64b9c3134c6b40264ee49d255 100644 +index d6c3fefbd407ae3aa1be9e71d05b6370f09dd905..33458df42b41e9ef5d9728d526cc45b8199f25b4 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -2246,5 +2246,12 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -2262,5 +2262,12 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi */ @NotNull io.papermc.paper.datapack.DatapackManager getDatapackManager(); diff --git a/patches/api/0361-Expand-FallingBlock-API.patch b/patches/api/0361-Expand-FallingBlock-API.patch index 81549f87522b..bbff3129a4ec 100644 --- a/patches/api/0361-Expand-FallingBlock-API.patch +++ b/patches/api/0361-Expand-FallingBlock-API.patch @@ -10,10 +10,10 @@ Subject: [PATCH] Expand FallingBlock API Co-authored-by: Lukas Planz diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 86c7b4ba676f826344056b24b204b748cfce1580..0f60823426898974b7d61123fb848006fdc7bfcf 100644 +index 0eb90bfff2e12cb3ed977a581912221ac4dee6f6..5785dda1127106b529f0784df524e120aaf87f4f 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -2220,8 +2220,10 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -2261,8 +2261,10 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient * @return The spawned {@link FallingBlock} instance * @throws IllegalArgumentException if {@link Location} or {@link * MaterialData} are null or {@link Material} of the {@link MaterialData} is not a block @@ -24,7 +24,7 @@ index 86c7b4ba676f826344056b24b204b748cfce1580..0f60823426898974b7d61123fb848006 public FallingBlock spawnFallingBlock(@NotNull Location location, @NotNull MaterialData data) throws IllegalArgumentException; /** -@@ -2234,8 +2236,10 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -2275,8 +2277,10 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient * @return The spawned {@link FallingBlock} instance * @throws IllegalArgumentException if {@link Location} or {@link * BlockData} are null @@ -35,7 +35,7 @@ index 86c7b4ba676f826344056b24b204b748cfce1580..0f60823426898974b7d61123fb848006 public FallingBlock spawnFallingBlock(@NotNull Location location, @NotNull BlockData data) throws IllegalArgumentException; /** -@@ -2252,7 +2256,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -2293,7 +2297,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient * @return The spawned {@link FallingBlock} instance * @throws IllegalArgumentException if {@link Location} or {@link * Material} are null or {@link Material} is not a block diff --git a/patches/api/0362-Add-method-isTickingWorlds-to-Bukkit.patch b/patches/api/0362-Add-method-isTickingWorlds-to-Bukkit.patch index f25774fe607a..91e4d0fdac6d 100644 --- a/patches/api/0362-Add-method-isTickingWorlds-to-Bukkit.patch +++ b/patches/api/0362-Add-method-isTickingWorlds-to-Bukkit.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add method isTickingWorlds() to Bukkit. diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 50d336fda7f549e50dc127767ca35107c99a3483..d71b30d574e5cf7273ff831edb7f3ef2359bbadc 100644 +index 986d506ae0eb91363e7fdfa19b1f9d0d840a5207..da505ee90f49983a9458cdf371db32228791fb53 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -786,12 +786,26 @@ public final class Bukkit { +@@ -806,12 +806,26 @@ public final class Bukkit { return server.getWorlds(); } @@ -35,7 +35,7 @@ index 50d336fda7f549e50dc127767ca35107c99a3483..d71b30d574e5cf7273ff831edb7f3ef2 * * @param creator the options to use when creating the world * @return newly created or loaded world -@@ -803,6 +817,9 @@ public final class Bukkit { +@@ -823,6 +837,9 @@ public final class Bukkit { /** * Unloads a world with the given name. @@ -45,7 +45,7 @@ index 50d336fda7f549e50dc127767ca35107c99a3483..d71b30d574e5cf7273ff831edb7f3ef2 * * @param name Name of the world to unload * @param save whether to save the chunks before unloading -@@ -814,6 +831,9 @@ public final class Bukkit { +@@ -834,6 +851,9 @@ public final class Bukkit { /** * Unloads the given world. @@ -56,10 +56,10 @@ index 50d336fda7f549e50dc127767ca35107c99a3483..d71b30d574e5cf7273ff831edb7f3ef2 * @param world the world to unload * @param save whether to save the chunks before unloading diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index eca2b37d2841f3d64b9c3134c6b40264ee49d255..75f2b46c4fc1e12d1d02cd60865a5b76b1c2de49 100644 +index 33458df42b41e9ef5d9728d526cc45b8199f25b4..d433a9d2fe0bb487865fec33307cc4c45af475a0 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -657,34 +657,55 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -673,34 +673,55 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi @NotNull public List getWorlds(); diff --git a/patches/api/0366-More-Teleport-API.patch b/patches/api/0366-More-Teleport-API.patch index 7279598aea3b..a7561dc07914 100644 --- a/patches/api/0366-More-Teleport-API.patch +++ b/patches/api/0366-More-Teleport-API.patch @@ -120,7 +120,7 @@ index 0000000000000000000000000000000000000000..c8b5b570d44da9524bfc59c7e11b2ae5 + +} diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index f090368dbb5acf352e1bee34d78b55eb92373c6d..8322d22f34968d579b92c581b36544d42b7732cd 100644 +index 29a34fa6bef077550526e00d0cdd0d8f49872e0c..3905c12b23bbfc88c9667b04e60fad7ad2febd60 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java @@ -126,10 +126,32 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent @@ -158,10 +158,10 @@ index f090368dbb5acf352e1bee34d78b55eb92373c6d..8322d22f34968d579b92c581b36544d4 * Teleports this entity to the given location. If this entity is riding a * vehicle, it will be dismounted prior to teleportation. diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 5662e957e7609aa4cc3e5fcf6b8f12cde6ae4305..a74ef246913baf52b990838ce26c8a4590b8ab36 100644 +index c8a50647d34c70bc927c33c602f938a01bf6e7a9..14b744bd1a0595260c65d3870be5f3985fb95ccb 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3332,6 +3332,45 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3400,6 +3400,45 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM String getClientBrandName(); // Paper end diff --git a/patches/api/0368-Custom-Chat-Completion-Suggestions-API.patch b/patches/api/0368-Custom-Chat-Completion-Suggestions-API.patch index baab234d28b1..1eb0e934e4b6 100644 --- a/patches/api/0368-Custom-Chat-Completion-Suggestions-API.patch +++ b/patches/api/0368-Custom-Chat-Completion-Suggestions-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Custom Chat Completion Suggestions API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index a74ef246913baf52b990838ce26c8a4590b8ab36..5caca55d8fc53720ff3304e46af5cfdcfd1acfbc 100644 +index 14b744bd1a0595260c65d3870be5f3985fb95ccb..87d20d238071dabdbebf745d28e87b210b2436ca 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3216,6 +3216,31 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3284,6 +3284,31 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM void sendOpLevel(byte level); // Paper end - sendOpLevel API diff --git a/patches/api/0369-Collision-API.patch b/patches/api/0369-Collision-API.patch index 64087e5ba36c..607e11fb9e53 100644 --- a/patches/api/0369-Collision-API.patch +++ b/patches/api/0369-Collision-API.patch @@ -25,10 +25,10 @@ index 44ee56a5956cc17194c767a0c1071a2abffe818a..43dd6c59cceba12f27e6b265acc3ad97 // Paper end } diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 8322d22f34968d579b92c581b36544d42b7732cd..2a4ff1457a9b4aa17bf28083ed4c301467ae9735 100644 +index 3905c12b23bbfc88c9667b04e60fad7ad2febd60..3fec6816bcc9167314b021a9989409649da6f7a7 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -971,4 +971,26 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -966,4 +966,26 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ boolean isInPowderedSnow(); // Paper end diff --git a/patches/api/0371-Add-NamespacedKey-biome-methods.patch b/patches/api/0371-Add-NamespacedKey-biome-methods.patch index ae9059caa8b5..f11f727f1a17 100644 --- a/patches/api/0371-Add-NamespacedKey-biome-methods.patch +++ b/patches/api/0371-Add-NamespacedKey-biome-methods.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Add NamespacedKey biome methods Co-authored-by: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index fc0356b0600946af0a46312cee16b080a7769ae2..815b16338ed5f90daf24edc5be14a6f49e978832 100644 +index 0c7204e390f44b649fc26cd46152abeb2ad2d670..741ead65c89d4f9521d35dcbc9661d6772058499 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -230,5 +230,32 @@ public interface UnsafeValues { +@@ -247,5 +247,32 @@ public interface UnsafeValues { * @throws IllegalArgumentException if {@link Material#isBlock()} is false */ boolean isCollidable(@org.jetbrains.annotations.NotNull Material material); diff --git a/patches/api/0378-Elder-Guardian-appearance-API.patch b/patches/api/0378-Elder-Guardian-appearance-API.patch index a004acf776d7..338c1b65a3c0 100644 --- a/patches/api/0378-Elder-Guardian-appearance-API.patch +++ b/patches/api/0378-Elder-Guardian-appearance-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Elder Guardian appearance API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 5caca55d8fc53720ff3304e46af5cfdcfd1acfbc..73536de601692a75fc83ff8b6e6781c7ae320a02 100644 +index 87d20d238071dabdbebf745d28e87b210b2436ca..ada7cfe2070eae77591b3138b88d389fbaab281a 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3396,6 +3396,24 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3464,6 +3464,24 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM void lookAt(@NotNull org.bukkit.entity.Entity entity, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor, @NotNull io.papermc.paper.entity.LookAnchor entityAnchor); // Paper end - Teleport API diff --git a/patches/api/0386-Add-Player-Warden-Warning-API.patch b/patches/api/0386-Add-Player-Warden-Warning-API.patch index 54a7ac70eb9c..1bc7d6a95a69 100644 --- a/patches/api/0386-Add-Player-Warden-Warning-API.patch +++ b/patches/api/0386-Add-Player-Warden-Warning-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add Player Warden Warning API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 73536de601692a75fc83ff8b6e6781c7ae320a02..7a680e61038856743d1271bae9b3ce8b8230a40f 100644 +index ada7cfe2070eae77591b3138b88d389fbaab281a..8a862dbf093ca0812aa1ce8def1e650380c303d0 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3412,6 +3412,59 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3480,6 +3480,59 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param silent whether sound should be silenced */ void showElderGuardian(boolean silent); diff --git a/patches/api/0396-Add-Sneaking-API-for-Entities.patch b/patches/api/0396-Add-Sneaking-API-for-Entities.patch index 274d5fb7d6b1..b89804cae7fe 100644 --- a/patches/api/0396-Add-Sneaking-API-for-Entities.patch +++ b/patches/api/0396-Add-Sneaking-API-for-Entities.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add Sneaking API for Entities diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 351a7d0aeab5d83564dd543935ba503ea0b68d60..c9139bb7abc881fddd2d5610d9506cd4fce7eba3 100644 +index 3fec6816bcc9167314b021a9989409649da6f7a7..e5627ed4c6bac789ab847e9b423c0b78834b9430 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -782,6 +782,25 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -777,6 +777,25 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent @NotNull Pose getPose(); @@ -35,10 +35,10 @@ index 351a7d0aeab5d83564dd543935ba503ea0b68d60..c9139bb7abc881fddd2d5610d9506cd4 * Get the category of spawn to which this entity belongs. * diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 5b1233ff2a5cdda75263eae86f96052bc28e9f5c..e6191168df1032f5904ce5c580c7e41fb1249b22 100644 +index 8a862dbf093ca0812aa1ce8def1e650380c303d0..bfa5a218576c9db147da75ec39cf8a8f6e9f6157 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -415,6 +415,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -417,6 +417,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * * @return true if player is in sneak mode */ @@ -46,7 +46,7 @@ index 5b1233ff2a5cdda75263eae86f96052bc28e9f5c..e6191168df1032f5904ce5c580c7e41f public boolean isSneaking(); /** -@@ -422,6 +423,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -424,6 +425,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * * @param sneak true if player should appear sneaking */ diff --git a/patches/api/0398-Add-exploded-block-state-to-BlockExplodeEvent-and-En.patch b/patches/api/0398-Add-exploded-block-state-to-BlockExplodeEvent-and-En.patch index c9fd0c650180..e2cea1ed6d67 100644 --- a/patches/api/0398-Add-exploded-block-state-to-BlockExplodeEvent-and-En.patch +++ b/patches/api/0398-Add-exploded-block-state-to-BlockExplodeEvent-and-En.patch @@ -65,10 +65,10 @@ index 641c71ab66bd2499b35cf3c1d533fd105d096e10..7dcbb75170296c1dd1d784a032bf3696 * Returns the list of blocks that would have been removed or were removed * from the explosion event. diff --git a/src/main/java/org/bukkit/event/entity/EntityDamageByBlockEvent.java b/src/main/java/org/bukkit/event/entity/EntityDamageByBlockEvent.java -index 461727dc7f06efb3550fc370e0db5bd04ba89711..ab18f35b686ec79551c307dde9e43c7dfad1b182 100644 +index 5ba9f7edf3bb814844ba9599c622a5106394c897..03889ab9b26c6f8fa4c9f51b7f67ad3ff40ef5db 100644 --- a/src/main/java/org/bukkit/event/entity/EntityDamageByBlockEvent.java +++ b/src/main/java/org/bukkit/event/entity/EntityDamageByBlockEvent.java -@@ -9,18 +9,38 @@ import org.jetbrains.annotations.Nullable; +@@ -10,18 +10,25 @@ import org.jetbrains.annotations.Nullable; /** * Called when an entity is damaged by a block @@ -81,33 +81,22 @@ index 461727dc7f06efb3550fc370e0db5bd04ba89711..ab18f35b686ec79551c307dde9e43c7d private final Block damager; + private final org.bukkit.block.BlockState damagerBlockState; // Paper - public EntityDamageByBlockEvent(@Nullable final Block damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, final double damage) { -+ // Paper start -+ this(damager, damagee, cause, damage, null); -+ } -+ @org.jetbrains.annotations.ApiStatus.Internal -+ public EntityDamageByBlockEvent(@Nullable final Block damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, final double damage, final @Nullable org.bukkit.block.BlockState damagerBlockState) { -+ // Paper end - super(damagee, cause, damage); +- public EntityDamageByBlockEvent(@Nullable final Block damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final DamageSource damageSource, final double damage) { ++ public EntityDamageByBlockEvent(@Nullable final Block damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final DamageSource damageSource, final double damage, final @Nullable org.bukkit.block.BlockState damagerBlockState) { // Paper + super(damagee, cause, damageSource, damage); this.damager = damager; + this.damagerBlockState = damagerBlockState; // Paper } - public EntityDamageByBlockEvent(@Nullable final Block damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions) { -+ // Paper start -+ this(damager, damagee, cause, modifiers, modifierFunctions, null); -+ } -+ -+ @org.jetbrains.annotations.ApiStatus.Internal -+ public EntityDamageByBlockEvent(@Nullable final Block damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions, final @Nullable org.bukkit.block.BlockState damagerBlockState) { -+ // Paper end - super(damagee, cause, modifiers, modifierFunctions); +- public EntityDamageByBlockEvent(@Nullable final Block damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final DamageSource damageSource, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions) { ++ public EntityDamageByBlockEvent(@Nullable final Block damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final DamageSource damageSource, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions, final @Nullable org.bukkit.block.BlockState damagerBlockState) { // Paper + super(damagee, cause, damageSource, modifiers, modifierFunctions); this.damager = damager; + this.damagerBlockState = damagerBlockState; // Paper } /** -@@ -32,4 +52,20 @@ public class EntityDamageByBlockEvent extends EntityDamageEvent { +@@ -33,4 +40,20 @@ public class EntityDamageByBlockEvent extends EntityDamageEvent { public Block getDamager() { return damager; } diff --git a/patches/api/0399-Flying-Fall-Damage-API.patch b/patches/api/0399-Flying-Fall-Damage-API.patch index 680af0c62ed8..3c619f5b4302 100644 --- a/patches/api/0399-Flying-Fall-Damage-API.patch +++ b/patches/api/0399-Flying-Fall-Damage-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Flying Fall Damage API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index ef1efb5c2a66ee81ffc85a5bbf0b25784c65a90b..e5ef5f4a19ef181b7df3e06664ee77ebc3865d89 100644 +index bfa5a218576c9db147da75ec39cf8a8f6e9f6157..9d81aec1346d07faa47745a3cb79bac4a8a4ffa3 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1807,6 +1807,23 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1835,6 +1835,23 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public void setAllowFlight(boolean flight); diff --git a/patches/api/0401-Win-Screen-API.patch b/patches/api/0401-Win-Screen-API.patch index 9f6cf38d00d8..3863beaa63ee 100644 --- a/patches/api/0401-Win-Screen-API.patch +++ b/patches/api/0401-Win-Screen-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Win Screen API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index e5ef5f4a19ef181b7df3e06664ee77ebc3865d89..cacf86208957d3708b489467f1abde61672da3ee 100644 +index 9d81aec1346d07faa47745a3cb79bac4a8a4ffa3..b49294027712e8d0b8aaaee1c041bc731b4cb184 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1177,6 +1177,47 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1205,6 +1205,47 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM */ public void sendMap(@NotNull MapView map); diff --git a/patches/api/0402-Add-Entity-Body-Yaw-API.patch b/patches/api/0402-Add-Entity-Body-Yaw-API.patch index ca3ef262334d..c09a6aa96268 100644 --- a/patches/api/0402-Add-Entity-Body-Yaw-API.patch +++ b/patches/api/0402-Add-Entity-Body-Yaw-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add Entity Body Yaw API diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index e5df511d962a59260207e8390d7da7782af5dc77..90c46483928e70f37a9900cada7dc67957a031d0 100644 +index e5627ed4c6bac789ab847e9b423c0b78834b9430..79bd8e5fe58c99133a620e46237bd667bf70e508 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -989,6 +989,43 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -984,6 +984,43 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent * @return true if in powdered snow. */ boolean isInPowderedSnow(); @@ -53,7 +53,7 @@ index e5df511d962a59260207e8390d7da7782af5dc77..90c46483928e70f37a9900cada7dc679 // Paper start - Collision API diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 74cd1d4aee0d4ef79fd77ac689c1e9b5d082664d..15d26b5d4fd55a29680f7485c80e33b7ce787fbe 100644 +index b9f98279789fe26181aa9c7aa253877d916034be..2dbb61df25967739c5a8308d6d89be20136816ba 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java @@ -1278,5 +1278,21 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource diff --git a/patches/api/0406-Add-Shearable-API.patch b/patches/api/0406-Add-Shearable-API.patch index 617157b4995b..4e49b175724a 100644 --- a/patches/api/0406-Add-Shearable-API.patch +++ b/patches/api/0406-Add-Shearable-API.patch @@ -54,10 +54,10 @@ index 0000000000000000000000000000000000000000..0d5793790ab6a47525ad330335173612 + boolean readyToBeSheared(); +} diff --git a/src/main/java/org/bukkit/entity/MushroomCow.java b/src/main/java/org/bukkit/entity/MushroomCow.java -index e14194a130ebd872bbc1eb24c7759f0388f3da97..a6020bf1e927c1478b5ab90ec9a6ee21f03579da 100644 +index 794b7b4a870a0d289476074e3a3f46552604c954..2f9aefd38c43755c79b30abddd6643b26880bd0d 100644 --- a/src/main/java/org/bukkit/entity/MushroomCow.java +++ b/src/main/java/org/bukkit/entity/MushroomCow.java -@@ -5,7 +5,7 @@ import org.jetbrains.annotations.NotNull; +@@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull; /** * Represents a mushroom {@link Cow} */ @@ -65,7 +65,7 @@ index e14194a130ebd872bbc1eb24c7759f0388f3da97..a6020bf1e927c1478b5ab90ec9a6ee21 +public interface MushroomCow extends Cow, io.papermc.paper.entity.Shearable { // Paper /** - * Get the variant of this cow. + * Checks for the presence of custom potion effects to be applied to the diff --git a/src/main/java/org/bukkit/entity/Sheep.java b/src/main/java/org/bukkit/entity/Sheep.java index f4ce312ccd927a8b64f4266b35a0a53b85e591f3..97388d46cee225dedc0b61a12e7b60b3424732c8 100644 --- a/src/main/java/org/bukkit/entity/Sheep.java diff --git a/patches/api/0413-Folia-scheduler-and-owned-region-API.patch b/patches/api/0413-Folia-scheduler-and-owned-region-API.patch index 5eb0946bc3a5..d915fc3c77d5 100644 --- a/patches/api/0413-Folia-scheduler-and-owned-region-API.patch +++ b/patches/api/0413-Folia-scheduler-and-owned-region-API.patch @@ -499,10 +499,10 @@ index 0000000000000000000000000000000000000000..a6b50c9d8af589cc4747e14d343d2045 + } +} diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index d71b30d574e5cf7273ff831edb7f3ef2359bbadc..f98c8c41ad9685af327db9c44db5fc9e37e00590 100644 +index da505ee90f49983a9458cdf371db32228791fb53..63a4f5cc3f2e8fbff47543de76e42a79996dbe81 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -2609,6 +2609,141 @@ public final class Bukkit { +@@ -2629,6 +2629,141 @@ public final class Bukkit { } // Paper end @@ -645,10 +645,10 @@ index d71b30d574e5cf7273ff831edb7f3ef2359bbadc..f98c8c41ad9685af327db9c44db5fc9e public static Server.Spigot spigot() { return server.spigot(); diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 75f2b46c4fc1e12d1d02cd60865a5b76b1c2de49..f8f0a2b7fdd51c739c3f55801037a417872ce7d5 100644 +index d433a9d2fe0bb487865fec33307cc4c45af475a0..f819de247080d58803a2851a4cab28d2b3765495 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -2275,4 +2275,119 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -2291,4 +2291,119 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi */ @NotNull org.bukkit.potion.PotionBrewer getPotionBrewer(); // Paper end @@ -769,10 +769,10 @@ index 75f2b46c4fc1e12d1d02cd60865a5b76b1c2de49..f8f0a2b7fdd51c739c3f55801037a417 + // Paper end - Folia region threading API } diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 90c46483928e70f37a9900cada7dc67957a031d0..3bf0c76ef830fa54d7695711cd7903dc64c619b1 100644 +index 79bd8e5fe58c99133a620e46237bd667bf70e508..4e01980ea00917743a92787b00d0f964395a6dd5 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -1049,4 +1049,15 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -1044,4 +1044,15 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ boolean wouldCollideUsing(@NotNull BoundingBox boundingBox); // Paper end - Collision API diff --git a/patches/api/0416-Fix-BanList-API.patch b/patches/api/0416-Fix-BanList-API.patch index e6e242f0d1e7..351f4dae160c 100644 --- a/patches/api/0416-Fix-BanList-API.patch +++ b/patches/api/0416-Fix-BanList-API.patch @@ -36,7 +36,7 @@ index 548f6d28c28d74bed8b58ee82875909354afe132..a77c0411a68a9bad33ddfb335b7a996a /** * Gets if a {@link BanEntry} exists for the target, indicating an active diff --git a/src/main/java/org/bukkit/OfflinePlayer.java b/src/main/java/org/bukkit/OfflinePlayer.java -index 774f9ec6a57d5d16a74ebe998e9ad800b1828029..9b84cb5abdf3db55cbc7ba19c8cd6955bf4fc5ec 100644 +index 634629d8c591d0477dfa6af91fa99caf17ffa9b0..ef6cb124adc98cb5231dc44e243450a2340f74af 100644 --- a/src/main/java/org/bukkit/OfflinePlayer.java +++ b/src/main/java/org/bukkit/OfflinePlayer.java @@ -135,7 +135,7 @@ public interface OfflinePlayer extends ServerOperator, AnimalTamer, Configuratio @@ -130,10 +130,10 @@ index e805e629cede1c4c0674282c930cb67852718c3e..5248cf08ef83c7304dd76c42a2f646bb + // Paper end } diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 41e7e24c65c3ae8cb451608db7598297fd9ffff2..5b00c51157a724e1d3f89198e97ba7e33b3a163d 100644 +index b49294027712e8d0b8aaaee1c041bc731b4cb184..8a765921b7e9ed047cfce2577408e420762f16bb 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -299,7 +299,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -301,7 +301,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * (updated) previous ban */ @Nullable @@ -142,7 +142,7 @@ index 41e7e24c65c3ae8cb451608db7598297fd9ffff2..5b00c51157a724e1d3f89198e97ba7e3 /** * Adds this user to the {@link ProfileBanList}. If a previous ban exists, this will -@@ -315,7 +315,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -317,7 +317,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * (updated) previous ban */ @Nullable @@ -151,7 +151,7 @@ index 41e7e24c65c3ae8cb451608db7598297fd9ffff2..5b00c51157a724e1d3f89198e97ba7e3 /** * Adds this user to the {@link ProfileBanList}. If a previous ban exists, this will -@@ -331,7 +331,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -333,7 +333,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * (updated) previous ban */ @Nullable diff --git a/patches/api/0418-API-for-updating-recipes-on-clients.patch b/patches/api/0418-API-for-updating-recipes-on-clients.patch index 266d6a480db0..c1665b9ba05f 100644 --- a/patches/api/0418-API-for-updating-recipes-on-clients.patch +++ b/patches/api/0418-API-for-updating-recipes-on-clients.patch @@ -5,10 +5,10 @@ Subject: [PATCH] API for updating recipes on clients diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index f98c8c41ad9685af327db9c44db5fc9e37e00590..adfaf27b872aa5614a31ff5f32cf9336c6f2ee49 100644 +index 63a4f5cc3f2e8fbff47543de76e42a79996dbe81..b82260cdee74a82b78c103467f7e2888ba4d06c1 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -970,6 +970,26 @@ public final class Bukkit { +@@ -990,6 +990,26 @@ public final class Bukkit { server.reloadData(); } @@ -35,7 +35,7 @@ index f98c8c41ad9685af327db9c44db5fc9e37e00590..adfaf27b872aa5614a31ff5f32cf9336 /** * Returns the primary logger associated with this server instance. * -@@ -1024,6 +1044,20 @@ public final class Bukkit { +@@ -1044,6 +1064,20 @@ public final class Bukkit { return server.addRecipe(recipe); } @@ -56,7 +56,7 @@ index f98c8c41ad9685af327db9c44db5fc9e37e00590..adfaf27b872aa5614a31ff5f32cf9336 /** * Get a list of all recipes for a given item. The stack size is ignored * in comparisons. If the durability is -1, it will match any data value. -@@ -1215,6 +1249,24 @@ public final class Bukkit { +@@ -1235,6 +1269,24 @@ public final class Bukkit { return server.removeRecipe(key); } @@ -82,10 +82,10 @@ index f98c8c41ad9685af327db9c44db5fc9e37e00590..adfaf27b872aa5614a31ff5f32cf9336 * Gets a list of command aliases defined in the server properties. * diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index f8f0a2b7fdd51c739c3f55801037a417872ce7d5..36a21c39f834faa6fec29a319588ddc58715a747 100644 +index f819de247080d58803a2851a4cab28d2b3765495..f18f5db804053e072134508ef38252391895549a 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -820,6 +820,22 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -836,6 +836,22 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi */ public void reloadData(); @@ -108,7 +108,7 @@ index f8f0a2b7fdd51c739c3f55801037a417872ce7d5..36a21c39f834faa6fec29a319588ddc5 /** * Returns the primary logger associated with this server instance. * -@@ -855,15 +871,34 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -871,15 +887,34 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi public boolean dispatchCommand(@NotNull CommandSender sender, @NotNull String commandLine) throws CommandException; /** @@ -144,7 +144,7 @@ index f8f0a2b7fdd51c739c3f55801037a417872ce7d5..36a21c39f834faa6fec29a319588ddc5 /** * Get a list of all recipes for a given item. The stack size is ignored * in comparisons. If the durability is -1, it will match any data value. -@@ -1032,6 +1067,22 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -1048,6 +1083,22 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi */ public boolean removeRecipe(@NotNull NamespacedKey key); diff --git a/patches/api/0420-Fix-custom-statistic-criteria-creation.patch b/patches/api/0420-Fix-custom-statistic-criteria-creation.patch index 42a05e6c6bbb..4ce596796340 100644 --- a/patches/api/0420-Fix-custom-statistic-criteria-creation.patch +++ b/patches/api/0420-Fix-custom-statistic-criteria-creation.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix custom statistic criteria creation diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 815b16338ed5f90daf24edc5be14a6f49e978832..a4b38f284d4fea7df7f9df9bf44e4f68fefaf20f 100644 +index 741ead65c89d4f9521d35dcbc9661d6772058499..0cfbb77c4da58b0692cb1df5686657b5ce04ad87 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -257,5 +257,7 @@ public interface UnsafeValues { +@@ -274,5 +274,7 @@ public interface UnsafeValues { * @throws IllegalStateException if no biome by the given key is registered. */ void setBiomeKey(RegionAccessor accessor, int x, int y, int z, NamespacedKey biomeKey); diff --git a/patches/api/0422-API-for-an-entity-s-scoreboard-name.patch b/patches/api/0422-API-for-an-entity-s-scoreboard-name.patch index 736aeeac0326..33df5bea8929 100644 --- a/patches/api/0422-API-for-an-entity-s-scoreboard-name.patch +++ b/patches/api/0422-API-for-an-entity-s-scoreboard-name.patch @@ -7,10 +7,10 @@ Was obtainable through different methods, but you had to use different methods depending on the implementation of Entity you were working with. diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index b2f5be3227d5dad5b1ff0129930dcd1944c75cef..82c67193a2dbb7b0bd0d6381cd0e06af49415d18 100644 +index 4e01980ea00917743a92787b00d0f964395a6dd5..fded39b709ef4129b53eac0e021dbb1a37fe5770 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -1060,4 +1060,15 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -1055,4 +1055,15 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ @NotNull io.papermc.paper.threadedregions.scheduler.EntityScheduler getScheduler(); // Paper end - Folia schedulers diff --git a/patches/api/0423-Deprecate-and-replace-methods-with-old-StructureType.patch b/patches/api/0423-Deprecate-and-replace-methods-with-old-StructureType.patch index 2e0d35f59799..177761f8cde9 100644 --- a/patches/api/0423-Deprecate-and-replace-methods-with-old-StructureType.patch +++ b/patches/api/0423-Deprecate-and-replace-methods-with-old-StructureType.patch @@ -5,20 +5,10 @@ Subject: [PATCH] Deprecate and replace methods with old StructureType diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index adfaf27b872aa5614a31ff5f32cf9336c6f2ee49..48fab492609e0bae459d20cc2eae78b87e37ab75 100644 +index b82260cdee74a82b78c103467f7e2888ba4d06c1..ca27559cf4aa1c2e44fdca2022e213b1b1c80f4e 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java -@@ -926,7 +926,9 @@ public final class Bukkit { - * - * @see World#locateNearestStructure(org.bukkit.Location, - * org.bukkit.StructureType, int, boolean) -+ * @deprecated use {@link #createExplorerMap(World, Location, org.bukkit.generator.structure.StructureType, org.bukkit.map.MapCursor.Type)} - */ -+ @Deprecated // Paper - @NotNull - public static ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType) { - return server.createExplorerMap(world, location, structureType); -@@ -935,9 +937,6 @@ public final class Bukkit { +@@ -935,9 +935,6 @@ public final class Bukkit { /** * Create a new explorer map targeting the closest nearby structure of a * given {@link StructureType}. @@ -28,7 +18,17 @@ index adfaf27b872aa5614a31ff5f32cf9336c6f2ee49..48fab492609e0bae459d20cc2eae78b8 * * @param world the world the map will belong to * @param location the origin location to find the nearest structure -@@ -949,11 +948,54 @@ public final class Bukkit { +@@ -946,7 +943,9 @@ public final class Bukkit { + * + * @see World#locateNearestStructure(org.bukkit.Location, + * org.bukkit.StructureType, int, boolean) ++ * @deprecated use {@link #createExplorerMap(World, Location, org.bukkit.generator.structure.StructureType, org.bukkit.map.MapCursor.Type)} + */ ++ @Deprecated // Paper + @NotNull + public static ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType) { + return server.createExplorerMap(world, location, structureType); +@@ -969,11 +968,54 @@ public final class Bukkit { * * @see World#locateNearestStructure(org.bukkit.Location, * org.bukkit.StructureType, int, boolean) @@ -84,10 +84,10 @@ index adfaf27b872aa5614a31ff5f32cf9336c6f2ee49..48fab492609e0bae459d20cc2eae78b8 /** * Reloads the server, refreshing settings and plugin information. diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 36a21c39f834faa6fec29a319588ddc58715a747..9af4bc16da09e59009c47911219e99450cdf2aa5 100644 +index f18f5db804053e072134508ef38252391895549a..b4f8281d3797ec825a7671f38077cd65d5a1d76e 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -784,16 +784,15 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -800,16 +800,15 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * * @see World#locateNearestStructure(org.bukkit.Location, * org.bukkit.StructureType, int, boolean) @@ -106,7 +106,7 @@ index 36a21c39f834faa6fec29a319588ddc58715a747..9af4bc16da09e59009c47911219e9945 * * @param world the world the map will belong to * @param location the origin location to find the nearest structure -@@ -805,9 +804,50 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi +@@ -821,9 +820,50 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi * * @see World#locateNearestStructure(org.bukkit.Location, * org.bukkit.StructureType, int, boolean) diff --git a/patches/api/0424-Add-Listing-API-for-Player.patch b/patches/api/0424-Add-Listing-API-for-Player.patch index 5cd488a2154b..609dd75c8760 100644 --- a/patches/api/0424-Add-Listing-API-for-Player.patch +++ b/patches/api/0424-Add-Listing-API-for-Player.patch @@ -5,11 +5,11 @@ Subject: [PATCH] Add Listing API for Player diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index f5ea1e4709a3d72ea9fe3045f8bced7aa77bc3f4..bd1beef9615eb87297abab6abb55fae34c579626 100644 +index 8a765921b7e9ed047cfce2577408e420762f16bb..8839ccf9515ebc3d2962e5dd17e948a2a83a5241 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1943,6 +1943,32 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM - @ApiStatus.Experimental +@@ -1965,6 +1965,32 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM + */ public boolean canSee(@NotNull Entity entity); + // Paper start diff --git a/patches/api/0427-Expand-Pose-API.patch b/patches/api/0427-Expand-Pose-API.patch index 9ffa45bc3699..f18a16576e53 100644 --- a/patches/api/0427-Expand-Pose-API.patch +++ b/patches/api/0427-Expand-Pose-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Expand Pose API diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 82c67193a2dbb7b0bd0d6381cd0e06af49415d18..1d0fd7ff8449f815a7d980af0b378181ea8bf8d8 100644 +index fded39b709ef4129b53eac0e021dbb1a37fe5770..9022567f32a58b94ed030db22524876d8f1df4aa 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -799,6 +799,42 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -794,6 +794,42 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent * @param sneak true if the entity should be sneaking */ void setSneaking(boolean sneak); diff --git a/patches/api/0438-Add-player-idle-duration-API.patch b/patches/api/0438-Add-player-idle-duration-API.patch index 24af4683d66e..9c44c6b88953 100644 --- a/patches/api/0438-Add-player-idle-duration-API.patch +++ b/patches/api/0438-Add-player-idle-duration-API.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Add player idle duration API Implements API for getting and resetting a player's idle duration. diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index bd1beef9615eb87297abab6abb55fae34c579626..dfd58cde92f8799f5f4a406a29277d13a1aef46f 100644 +index 8839ccf9515ebc3d2962e5dd17e948a2a83a5241..5bad312df63eb6dea705c35e0f7e4dd980f526d7 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3553,6 +3553,29 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -3621,6 +3621,29 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM void increaseWardenWarningLevel(); // Paper end diff --git a/patches/api/0440-Add-predicate-for-blocks-when-raytracing.patch b/patches/api/0440-Add-predicate-for-blocks-when-raytracing.patch index 2d0f899ec00d..67d10e420ea5 100644 --- a/patches/api/0440-Add-predicate-for-blocks-when-raytracing.patch +++ b/patches/api/0440-Add-predicate-for-blocks-when-raytracing.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add predicate for blocks when raytracing diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 0f60823426898974b7d61123fb848006fdc7bfcf..e014a71f2fd20e41f985ce68301006c4a4e0c5e3 100644 +index 5785dda1127106b529f0784df524e120aaf87f4f..c0ad21a3dd1f3cd9a4c66000e937e89ffc183638 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -1692,6 +1692,27 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -1694,6 +1694,27 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient @Nullable public RayTraceResult rayTraceEntities(@NotNull Location start, @NotNull Vector direction, double maxDistance, double raySize, @Nullable Predicate filter); @@ -36,7 +36,7 @@ index 0f60823426898974b7d61123fb848006fdc7bfcf..e014a71f2fd20e41f985ce68301006c4 /** * Performs a ray trace that checks for block collisions using the blocks' * precise collision shapes. -@@ -1755,6 +1776,34 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -1757,6 +1778,34 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient @Nullable public RayTraceResult rayTraceBlocks(@NotNull Location start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks); @@ -71,7 +71,7 @@ index 0f60823426898974b7d61123fb848006fdc7bfcf..e014a71f2fd20e41f985ce68301006c4 /** * Performs a ray trace that checks for both block and entity collisions. *

    -@@ -1788,6 +1837,42 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -1790,6 +1839,42 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient @Nullable public RayTraceResult rayTrace(@NotNull Location start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, @Nullable Predicate filter); diff --git a/patches/api/0445-Add-Structure-check-API.patch b/patches/api/0445-Add-Structure-check-API.patch index d993eeba7f35..693f5dbf0950 100644 --- a/patches/api/0445-Add-Structure-check-API.patch +++ b/patches/api/0445-Add-Structure-check-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add Structure check API diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index e014a71f2fd20e41f985ce68301006c4a4e0c5e3..bf4b94ea2577e9d7e344385209fc0644a4e6bfbb 100644 +index c0ad21a3dd1f3cd9a4c66000e937e89ffc183638..c5fe36050eeaff80cfb989fe2f38370215af6fe5 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java -@@ -76,6 +76,30 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient +@@ -78,6 +78,30 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient */ int getPlayerCount(); // Paper end diff --git a/patches/api/0448-Improve-Registry.patch b/patches/api/0448-Improve-Registry.patch index 1082efe432b6..da9c8aeaebe4 100644 --- a/patches/api/0448-Improve-Registry.patch +++ b/patches/api/0448-Improve-Registry.patch @@ -10,10 +10,10 @@ getKey() methods on Keyed objects that have a registry are marked as Deprecated or Obsolete. diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java -index 800d23bb249e19d5cf924e7ba36684068624da02..57e97b424cebd205cb260556ab9fb9eb2ff1eebc 100644 +index 18c672f3855a329bf8f87a9de81b677e8e360b41..e1fb4d8cca6a9c59047b1396f5c40bea957d777a 100644 --- a/src/main/java/org/bukkit/Registry.java +++ b/src/main/java/org/bukkit/Registry.java -@@ -307,6 +307,49 @@ public interface Registry extends Iterable { +@@ -313,6 +313,49 @@ public interface Registry extends Iterable { @Nullable T get(@NotNull NamespacedKey key); @@ -63,7 +63,7 @@ index 800d23bb249e19d5cf924e7ba36684068624da02..57e97b424cebd205cb260556ab9fb9eb /** * Returns a new stream, which contains all registry items, which are registered to the registry. * -@@ -373,5 +416,12 @@ public interface Registry extends Iterable { +@@ -379,5 +422,12 @@ public interface Registry extends Iterable { public Iterator iterator() { return map.values().iterator(); } diff --git a/patches/api/0449-Add-experience-points-API.patch b/patches/api/0449-Add-experience-points-API.patch index 6223e3ae1ae4..eac15c72493c 100644 --- a/patches/api/0449-Add-experience-points-API.patch +++ b/patches/api/0449-Add-experience-points-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add experience points API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index dfd58cde92f8799f5f4a406a29277d13a1aef46f..31aa6ef5fc2b3b88c72f5a15b8cc7a0e50c29f46 100644 +index 5bad312df63eb6dea705c35e0f7e4dd980f526d7..254a02ddb5dc867c9dd6c2086791f7ab94247fd3 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1806,6 +1806,45 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1834,6 +1834,45 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param exp New total experience points */ public void setTotalExperience(int exp); diff --git a/patches/api/0458-Add-api-for-spawn-egg-texture-colors.patch b/patches/api/0458-Add-api-for-spawn-egg-texture-colors.patch index 9f9ef4707d3a..22ba7c25bfca 100644 --- a/patches/api/0458-Add-api-for-spawn-egg-texture-colors.patch +++ b/patches/api/0458-Add-api-for-spawn-egg-texture-colors.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add api for spawn egg texture colors diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index a4b38f284d4fea7df7f9df9bf44e4f68fefaf20f..923d8655a84e26960d35d8dc6e4ebc0b10c295d5 100644 +index 0cfbb77c4da58b0692cb1df5686657b5ce04ad87..43f779ff7eca7450f52ac3688744743b5c3647bf 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -260,4 +260,17 @@ public interface UnsafeValues { +@@ -277,4 +277,17 @@ public interface UnsafeValues { String getStatisticCriteriaKey(@NotNull org.bukkit.Statistic statistic); // Paper end diff --git a/patches/api/0459-Add-Lifecycle-Event-system.patch b/patches/api/0459-Add-Lifecycle-Event-system.patch index 2285a3eaa95a..635d4a73250a 100644 --- a/patches/api/0459-Add-Lifecycle-Event-system.patch +++ b/patches/api/0459-Add-Lifecycle-Event-system.patch @@ -528,10 +528,10 @@ index 0000000000000000000000000000000000000000..304f978e40e1759bb19704cc5cec3995 + } +} diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 923d8655a84e26960d35d8dc6e4ebc0b10c295d5..890c07cfc2e64a52752e96d518578b5eb1afbd19 100644 +index 43f779ff7eca7450f52ac3688744743b5c3647bf..f2163b5238e1667a44bf623cd52bab90d9f2d88d 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -273,4 +273,12 @@ public interface UnsafeValues { +@@ -290,4 +290,12 @@ public interface UnsafeValues { */ @Nullable org.bukkit.Color getSpawnEggLayerColor(org.bukkit.entity.EntityType entityType, int layer); // Paper end - spawn egg color visibility diff --git a/patches/api/0461-ItemStack-Tooltip-API.patch b/patches/api/0461-ItemStack-Tooltip-API.patch index c7710b7c4c96..9aa55ce1a905 100644 --- a/patches/api/0461-ItemStack-Tooltip-API.patch +++ b/patches/api/0461-ItemStack-Tooltip-API.patch @@ -108,10 +108,10 @@ index 0000000000000000000000000000000000000000..1d9bed6691f581529c53b577b26f1d0f + } +} diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 890c07cfc2e64a52752e96d518578b5eb1afbd19..c6222fe754dee53f269944c7578dde8106377f20 100644 +index f2163b5238e1667a44bf623cd52bab90d9f2d88d..9a65c4f614a6c358d74491794d7b25172a00bc11 100644 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -281,4 +281,6 @@ public interface UnsafeValues { +@@ -298,4 +298,6 @@ public interface UnsafeValues { @org.jetbrains.annotations.ApiStatus.Internal io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager createPluginLifecycleEventManager(final org.bukkit.plugin.java.JavaPlugin plugin, final java.util.function.BooleanSupplier registrationCheck); // Paper end - lifecycle event API diff --git a/patches/api/0462-Add-getChunkSnapshot-includeLightData-parameter.patch b/patches/api/0462-Add-getChunkSnapshot-includeLightData-parameter.patch index 288f6740ddb3..0437924b946e 100644 --- a/patches/api/0462-Add-getChunkSnapshot-includeLightData-parameter.patch +++ b/patches/api/0462-Add-getChunkSnapshot-includeLightData-parameter.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add getChunkSnapshot includeLightData parameter diff --git a/src/main/java/org/bukkit/Chunk.java b/src/main/java/org/bukkit/Chunk.java -index eca55d8d3464f0e13a3b7984f74559ccda87edba..0b83bf7fb7dac51ed1dbc005df36f0292167ec16 100644 +index 546888898d9d6827079fe041c7bc6eb4e1e4605c..d547ae2b20c58bc703de4532b3b591dd34ddb1c6 100644 --- a/src/main/java/org/bukkit/Chunk.java +++ b/src/main/java/org/bukkit/Chunk.java -@@ -100,6 +100,23 @@ public interface Chunk extends PersistentDataHolder { +@@ -102,6 +102,23 @@ public interface Chunk extends PersistentDataHolder { @NotNull ChunkSnapshot getChunkSnapshot(boolean includeMaxblocky, boolean includeBiome, boolean includeBiomeTempRain); diff --git a/patches/server/0002-Remap-fixes.patch b/patches/server/0002-Remap-fixes.patch index 5e367262c099..3f9503543f1c 100644 --- a/patches/server/0002-Remap-fixes.patch +++ b/patches/server/0002-Remap-fixes.patch @@ -187,10 +187,10 @@ index 5818bfa69a8573a2a8f350066f829d587cbc546b..8e421a1bee0c526e3024eab9ba4cc0b3 assertNotNull(bukkit, "Bukkit gene null for " + gene); diff --git a/src/test/java/org/bukkit/registry/RegistryConstantsTest.java b/src/test/java/org/bukkit/registry/RegistryConstantsTest.java -index 8acd2400dac0486d95a28cc07c21fc0c7183769b..8038e422cfcdda0eab19a2b3961c2597de25752b 100644 +index 8abeb88d1514d22b5095153f9537317006f39df0..82a1efcb3aaaceaf12ad21559dec6b5112fcb361 100644 --- a/src/test/java/org/bukkit/registry/RegistryConstantsTest.java +++ b/src/test/java/org/bukkit/registry/RegistryConstantsTest.java -@@ -21,17 +21,17 @@ public class RegistryConstantsTest extends AbstractTestingBase { +@@ -28,17 +28,17 @@ public class RegistryConstantsTest extends AbstractTestingBase { @Test public void testTrimMaterial() { diff --git a/patches/server/0005-Paper-config-files.patch b/patches/server/0005-Paper-config-files.patch index 27deead1df98..c9327e3fc279 100644 --- a/patches/server/0005-Paper-config-files.patch +++ b/patches/server/0005-Paper-config-files.patch @@ -4896,7 +4896,7 @@ index 1d611538d339345df065cd9c0c582d02e1fb0cc7..d200619b4aa820e273c26f2f6133589c @Nullable diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index dd16b56a478e1cd6f1b3c5563c9c02025d894802..c56c7293261ec2601ab02d051b37e820f023f0ff 100644 +index 57fdf24730d93e4dc48bacfa8f034fc99a48f0e4..8ddfd4a148c95d0d9e93edf03ddac46332936d9a 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -183,6 +183,10 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -4964,10 +4964,10 @@ index 8487fa452e4009c0f2a23a0d4eac4bf56f91447b..00ddf94c5bade8c0c486337ce920f59d this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 8318b4a56b4a0235a00caba9df4ba41c85eb13bc..b5256eefb64808ae15bd622a8eccbe13454b4564 100644 +index 680a308c466c0056d4213e61f69cf13ee3ff5c61..cd39509d383c47319b71797e5d1df41c2b8c7ab6 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -915,6 +915,7 @@ public final class CraftServer implements Server { +@@ -927,6 +927,7 @@ public final class CraftServer implements Server { } org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot diff --git a/patches/server/0008-CB-fixes.patch b/patches/server/0008-CB-fixes.patch index 5807a8e518b0..d0c88b547347 100644 --- a/patches/server/0008-CB-fixes.patch +++ b/patches/server/0008-CB-fixes.patch @@ -30,7 +30,7 @@ index 674c996af91de91ee6302cc67334b836ea4fa4de..93867b8883c6f5d5086e8fdc153e6d7c if ((this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) || env == org.bukkit.World.Environment.THE_END) { // CraftBukkit - Allow to create EnderDragonBattle in default and custom END this.dragonFight = new EndDragonFight(this, this.serverLevelData.worldGenOptions().seed(), this.serverLevelData.endDragonFightData()); // CraftBukkit diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -index 869f60e9407ed1c5bee536ef91a21f4d11f8f964..3aa98f7c282cb4884589cb83b1546b924e66f096 100644 +index 869f60e9407ed1c5bee536ef91a21f4d11f8f964..95e5d1d707e610c930b6098a86c5162fd29bf602 100644 --- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java +++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java @@ -445,9 +445,9 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl @@ -38,10 +38,10 @@ index 869f60e9407ed1c5bee536ef91a21f4d11f8f964..3aa98f7c282cb4884589cb83b1546b92 @Override - protected void actuallyHurt(DamageSource source, float amount) { -+ protected boolean damageEntity0(DamageSource source, float amount) { // Paper - fix CB method rename issue ++ protected boolean actuallyHurt(DamageSource source, float amount) { // Paper - Fix CB... this.standUpInstantly(); - super.actuallyHurt(source, amount); -+ return super.damageEntity0(source, amount); // Paper - fix CB method rename issue ++ return super.actuallyHurt(source, amount); // Paper - Fix CB... } @Override @@ -84,10 +84,10 @@ index 809fdf2da78293391aa5c60c04f4ad652b152eec..955b0abd4019fc45df84719eee6bf413 + // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 5f12fce84e0ec001dc43523753883a098434fcb6..d6a1b9bbf9737ed884ecf4af31e1521f46807405 100644 +index cd39509d383c47319b71797e5d1df41c2b8c7ab6..2cc1871c81056acd0582184bb684e672d14d3b6f 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2454,7 +2454,11 @@ public final class CraftServer implements Server { +@@ -2466,7 +2466,11 @@ public final class CraftServer implements Server { Preconditions.checkArgument(key != null, "NamespacedKey key cannot be null"); LootDataManager registry = this.getServer().getLootData(); diff --git a/patches/server/0009-MC-Utils.patch b/patches/server/0009-MC-Utils.patch index 34aea8b512a5..5de7f3ff8463 100644 --- a/patches/server/0009-MC-Utils.patch +++ b/patches/server/0009-MC-Utils.patch @@ -7028,7 +7028,7 @@ index 337e0a7b3c14e1b1a28744920e0dc0a69e0c5a87..f5829ae484d93b547a5437b85a962134 @Override public void tell(R runnable) { diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 8957b7082f310b2b8aed5f5a68847d6dc773177a..a1f658f4dafd47de0020d96667b090aa88c69f9c 100644 +index 449635563afcb6a43e036dc0f3988efddbb622b2..e945daf877068625d4ad0f0b534f2eb23ee658a9 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -330,6 +330,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S @@ -7044,10 +7044,10 @@ index 8957b7082f310b2b8aed5f5a68847d6dc773177a..a1f658f4dafd47de0020d96667b090aa public Entity(EntityType type, Level world) { this.id = Entity.ENTITY_COUNTER.incrementAndGet(); diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 7fda861b1c2fc7246f2df8d199b4e8bbe55bc647..b71b702471599fc8f1de42919ade8ee6a4e6247c 100644 +index db768130e7b7c21e111687ebe761608e7494a739..44bfb8778f2894be9633be7ddc4f3c6881c9b05d 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -257,6 +257,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -258,6 +258,7 @@ public abstract class LivingEntity extends Entity implements Attackable { public boolean collides = true; public Set collidableExemptions = new HashSet<>(); public boolean bukkitPickUpLoot; @@ -7056,7 +7056,7 @@ index 7fda861b1c2fc7246f2df8d199b4e8bbe55bc647..b71b702471599fc8f1de42919ade8ee6 @Override public float getBukkitYaw() { diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 1152735ac2cb8b193fdfb448f24517ad902b02a8..01761d37c9e4be4e498b62c7612885648b2968a6 100644 +index 0f24a32334e66b0c93287c9b2b14484da799738f..fc264864d4a6f56f94d884f4892257452b360b73 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java @@ -277,6 +277,7 @@ public abstract class Mob extends LivingEntity implements Targeting { @@ -7093,10 +7093,10 @@ index 86b65d66d895a4f02da002448739c122796feb4d..036d79baf372f4900681fee366bcd91c super(type, world); this.xpReward = 5; diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index d75bd8ab63930454d72299c378b6fef2ccc3725d..9861cd23b07f8fbacb1d125af835dee58c2debbb 100644 +index a7cbd0b9e37717215c03809e025f877e9c0a40b8..6291265ae4691bf7dffe196d20571c1c30e8d906 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -823,6 +823,25 @@ public final class ItemStack { +@@ -825,6 +825,25 @@ public final class ItemStack { return this.tag != null ? this.tag.getList("Enchantments", 10) : new ListTag(); } @@ -7122,7 +7122,7 @@ index d75bd8ab63930454d72299c378b6fef2ccc3725d..9861cd23b07f8fbacb1d125af835dee5 public void setTag(@Nullable CompoundTag nbt) { this.tag = nbt; if (this.getItem().canBeDepleted()) { -@@ -1212,6 +1231,7 @@ public final class ItemStack { +@@ -1214,6 +1233,7 @@ public final class ItemStack { // CraftBukkit start @Deprecated public void setItem(Item item) { @@ -7733,10 +7733,10 @@ index 98836000cbca2a21649cb8f2a466986373405ea1..bbbf6dd8e566ecdca8794e3b03765fe7 return false; } else { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index aa3032f9dac2700f2621a860deeec49ada801939..e51bed4771dcef6201f943e9289c4a53f029f1ab 100644 +index 2cc1871c81056acd0582184bb684e672d14d3b6f..5cf26c39b00ea1f7b1c5719f434af79fb20c6c60 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2531,4 +2531,9 @@ public final class CraftServer implements Server { +@@ -2543,4 +2543,9 @@ public final class CraftServer implements Server { return this.spigot; } // Spigot end @@ -7747,10 +7747,10 @@ index aa3032f9dac2700f2621a860deeec49ada801939..e51bed4771dcef6201f943e9289c4a53 + } } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 16785a8dab5a2e786e17db049c027ed1c13f5ef6..2b0b9994751557e69ee7aa48fcb8319c128a5bbf 100644 +index fcf06c157fa8ecdb742c301a95da8489e951ea20..1ce9787b04e28b3a50fdc7779a430c3be60a7646 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -245,8 +245,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -251,8 +251,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public Chunk[] getLoadedChunks() { @@ -7761,7 +7761,7 @@ index 16785a8dab5a2e786e17db049c027ed1c13f5ef6..2b0b9994751557e69ee7aa48fcb8319c } @Override -@@ -321,7 +321,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -327,7 +327,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean refreshChunk(int x, int z) { @@ -7770,7 +7770,7 @@ index 16785a8dab5a2e786e17db049c027ed1c13f5ef6..2b0b9994751557e69ee7aa48fcb8319c if (playerChunk == null) return false; playerChunk.getTickingChunkFuture().thenAccept(either -> { -@@ -2018,4 +2018,55 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -2055,4 +2055,55 @@ public class CraftWorld extends CraftRegionAccessor implements World { return this.spigot; } // Spigot end @@ -7869,10 +7869,10 @@ index 70165d287156f46b793eb23dd30b601289c0ffb1..758bf988432bb34aad9386e3f4e8bba6 + // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 070f119315a83be900c1a8e83962833d7df4c485..5d2ece0c415526d8c632b507f441977e6888c2cb 100644 +index ed501b794c222278dca98f8ece6096db1d8108a5..be3a8e54d64b3cc145ab09b0bc7abb3f4ee153c3 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2282,4 +2282,34 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2335,4 +2335,34 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return this.spigot; } // Spigot end @@ -8151,10 +8151,10 @@ index 0000000000000000000000000000000000000000..909b2c98e7a9117d2f737245e4661792 + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index d16df4f475151558593ac637c94391899f313829..32b73cd6d65abe1cd5fd33733d8c06467382acdc 100644 +index a2eb1e6dc8218157c1a5bbd769dd3b1d881fddb6..56c3aa7647eb2890cf7f546d35002b0c43724500 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -102,8 +102,17 @@ public final class CraftMagicNumbers implements UnsafeValues { +@@ -107,8 +107,17 @@ public final class CraftMagicNumbers implements UnsafeValues { private static final Map ITEM_MATERIAL = new HashMap<>(); private static final Map MATERIAL_ITEM = new HashMap<>(); private static final Map MATERIAL_BLOCK = new HashMap<>(); @@ -8172,7 +8172,7 @@ index d16df4f475151558593ac637c94391899f313829..32b73cd6d65abe1cd5fd33733d8c0646 for (Block block : BuiltInRegistries.BLOCK) { BLOCK_MATERIAL.put(block, Material.getMaterial(BuiltInRegistries.BLOCK.getKey(block).getPath().toUpperCase(Locale.ROOT))); } -@@ -154,6 +163,14 @@ public final class CraftMagicNumbers implements UnsafeValues { +@@ -159,6 +168,14 @@ public final class CraftMagicNumbers implements UnsafeValues { public static ResourceLocation key(Material mat) { return CraftNamespacedKey.toMinecraft(mat.getKey()); } diff --git a/patches/server/0010-Adventure.patch b/patches/server/0010-Adventure.patch index 8d235e570cf6..631f6c96d000 100644 --- a/patches/server/0010-Adventure.patch +++ b/patches/server/0010-Adventure.patch @@ -2933,7 +2933,7 @@ index a60fef571c94858998a91711b17d3670c28a81bd..04a728a16bb629adbae1cd8586764a6d @Override diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 520f0742210342b43aa854b1e8b0c42a84935a9c..d13a662895737180f3d75b6e357ff90c72b0fe08 100644 +index 58872976048a1162602d5f11a85eaead837cd805..acd53d9005fc5f43b94c80ec5e7d0e1f9c86ca98 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -44,6 +44,7 @@ import net.minecraft.nbt.ListTag; @@ -3354,10 +3354,10 @@ index 23bdb77690ba15bcbbfb0c70af23336d08ac7752..8f144a357174bbe096ac9b38a5e67a61 } collection = icons; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index e51bed4771dcef6201f943e9289c4a53f029f1ab..c9453c4689d02a39fa4a01aa14fc8d8aa29bc887 100644 +index 5cf26c39b00ea1f7b1c5719f434af79fb20c6c60..8441cbe4ebd676d1aacff223abdabeb32b5658e0 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -608,8 +608,10 @@ public final class CraftServer implements Server { +@@ -610,8 +610,10 @@ public final class CraftServer implements Server { } @Override @@ -3368,7 +3368,7 @@ index e51bed4771dcef6201f943e9289c4a53f029f1ab..c9453c4689d02a39fa4a01aa14fc8d8a } @Override -@@ -1569,7 +1571,15 @@ public final class CraftServer implements Server { +@@ -1581,7 +1583,15 @@ public final class CraftServer implements Server { return this.configuration.getInt("settings.spawn-radius", -1); } @@ -3384,7 +3384,7 @@ index e51bed4771dcef6201f943e9289c4a53f029f1ab..c9453c4689d02a39fa4a01aa14fc8d8a public String getShutdownMessage() { return this.configuration.getString("settings.shutdown-message"); } -@@ -1737,7 +1747,20 @@ public final class CraftServer implements Server { +@@ -1749,7 +1759,20 @@ public final class CraftServer implements Server { } @Override @@ -3405,7 +3405,7 @@ index e51bed4771dcef6201f943e9289c4a53f029f1ab..c9453c4689d02a39fa4a01aa14fc8d8a Set recipients = new HashSet<>(); for (Permissible permissible : this.getPluginManager().getPermissionSubscriptions(permission)) { if (permissible instanceof CommandSender && permissible.hasPermission(permission)) { -@@ -1745,14 +1768,14 @@ public final class CraftServer implements Server { +@@ -1757,14 +1780,14 @@ public final class CraftServer implements Server { } } @@ -3422,7 +3422,7 @@ index e51bed4771dcef6201f943e9289c4a53f029f1ab..c9453c4689d02a39fa4a01aa14fc8d8a for (CommandSender recipient : recipients) { recipient.sendMessage(message); -@@ -2014,6 +2037,14 @@ public final class CraftServer implements Server { +@@ -2026,6 +2049,14 @@ public final class CraftServer implements Server { return CraftInventoryCreator.INSTANCE.createInventory(owner, type); } @@ -3437,7 +3437,7 @@ index e51bed4771dcef6201f943e9289c4a53f029f1ab..c9453c4689d02a39fa4a01aa14fc8d8a @Override public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) { Preconditions.checkArgument(type != null, "InventoryType cannot be null"); -@@ -2028,13 +2059,28 @@ public final class CraftServer implements Server { +@@ -2040,13 +2071,28 @@ public final class CraftServer implements Server { return CraftInventoryCreator.INSTANCE.createInventory(owner, size); } @@ -3466,7 +3466,7 @@ index e51bed4771dcef6201f943e9289c4a53f029f1ab..c9453c4689d02a39fa4a01aa14fc8d8a public Merchant createMerchant(String title) { return new CraftMerchantCustom(title == null ? InventoryType.MERCHANT.getDefaultTitle() : title); } -@@ -2099,6 +2145,17 @@ public final class CraftServer implements Server { +@@ -2111,6 +2157,17 @@ public final class CraftServer implements Server { return Thread.currentThread().equals(this.console.serverThread) || this.console.hasStopped() || !org.spigotmc.AsyncCatcher.enabled; // All bets are off if we have shut down (e.g. due to watchdog) } @@ -3484,7 +3484,7 @@ index e51bed4771dcef6201f943e9289c4a53f029f1ab..c9453c4689d02a39fa4a01aa14fc8d8a @Override public String getMotd() { return this.console.getMotd(); -@@ -2536,4 +2593,57 @@ public final class CraftServer implements Server { +@@ -2548,4 +2605,57 @@ public final class CraftServer implements Server { public double[] getTPS() { return new double[]{0, 0, 0}; // TODO } @@ -3543,10 +3543,10 @@ index e51bed4771dcef6201f943e9289c4a53f029f1ab..c9453c4689d02a39fa4a01aa14fc8d8a + // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 2b0b9994751557e69ee7aa48fcb8319c128a5bbf..b2632cbc7903e23eb68e9901df039f5d8293bd77 100644 +index 1ce9787b04e28b3a50fdc7779a430c3be60a7646..8ff8d8174cd32d25b33c2e773d30c474b4e903d3 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -155,6 +155,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -161,6 +161,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { private final BlockMetadataStore blockMetadata = new BlockMetadataStore(this); private final Object2IntOpenHashMap spawnCategoryLimit = new Object2IntOpenHashMap<>(); private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftWorld.DATA_TYPE_REGISTRY); @@ -3554,7 +3554,7 @@ index 2b0b9994751557e69ee7aa48fcb8319c128a5bbf..b2632cbc7903e23eb68e9901df039f5d private static final Random rand = new Random(); -@@ -1646,6 +1647,42 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1659,6 +1660,42 @@ public class CraftWorld extends CraftRegionAccessor implements World { entityTracker.broadcastAndSend(packet); } } @@ -3597,7 +3597,7 @@ index 2b0b9994751557e69ee7aa48fcb8319c128a5bbf..b2632cbc7903e23eb68e9901df039f5d private static Map> gamerules; public static synchronized Map> getGameRulesNMS() { -@@ -2068,5 +2105,18 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -2105,5 +2142,18 @@ public class CraftWorld extends CraftRegionAccessor implements World { public void setSendViewDistance(final int viewDistance) { throw new UnsupportedOperationException("Not implemented yet"); } @@ -4137,10 +4137,10 @@ index 61759e8179d0f6342abf0c0294e5a024928db8d9..92e21126a9347f1ee2279ab09bb6abf2 public boolean isOp() { return true; diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 5d2ece0c415526d8c632b507f441977e6888c2cb..f53e223e2412846b82298233459ec9953bc0a63e 100644 +index be3a8e54d64b3cc145ab09b0bc7abb3f4ee153c3..e5330d41512dc59b5f94d9cacda340a46f45fd76 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -300,14 +300,39 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -306,14 +306,39 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public String getDisplayName() { @@ -4180,7 +4180,7 @@ index 5d2ece0c415526d8c632b507f441977e6888c2cb..f53e223e2412846b82298233459ec995 @Override public String getPlayerListName() { return this.getHandle().listName == null ? this.getName() : CraftChatMessage.fromComponent(this.getHandle().listName); -@@ -326,42 +351,42 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -332,42 +357,42 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } } @@ -4232,7 +4232,7 @@ index 5d2ece0c415526d8c632b507f441977e6888c2cb..f53e223e2412846b82298233459ec995 this.getHandle().connection.send(packet); } -@@ -393,6 +418,23 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -399,6 +424,23 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.getHandle().connection.disconnect(message == null ? "" : message); } @@ -4256,7 +4256,7 @@ index 5d2ece0c415526d8c632b507f441977e6888c2cb..f53e223e2412846b82298233459ec995 @Override public void setCompassTarget(Location loc) { Preconditions.checkArgument(loc != null, "Location cannot be null"); -@@ -689,6 +731,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -695,6 +737,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.getHandle().connection.send(packet); } @@ -4281,7 +4281,7 @@ index 5d2ece0c415526d8c632b507f441977e6888c2cb..f53e223e2412846b82298233459ec995 @Override public void sendSignChange(Location loc, String[] lines) { this.sendSignChange(loc, lines, DyeColor.BLACK); -@@ -712,6 +772,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -718,6 +778,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player { if (this.getHandle().connection == null) return; Component[] components = CraftSign.sanitizeLines(lines); @@ -4293,7 +4293,7 @@ index 5d2ece0c415526d8c632b507f441977e6888c2cb..f53e223e2412846b82298233459ec995 SignBlockEntity sign = new SignBlockEntity(CraftLocation.toBlockPosition(loc), Blocks.OAK_SIGN.defaultBlockState()); SignText text = sign.getFrontText(); text = text.setColor(net.minecraft.world.item.DyeColor.byId(dyeColor.getWoolData())); -@@ -721,7 +786,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -727,7 +792,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } sign.setText(text, true); @@ -4303,7 +4303,7 @@ index 5d2ece0c415526d8c632b507f441977e6888c2cb..f53e223e2412846b82298233459ec995 } @Override -@@ -1705,7 +1771,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1735,7 +1801,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public void setResourcePack(String url) { @@ -4312,7 +4312,7 @@ index 5d2ece0c415526d8c632b507f441977e6888c2cb..f53e223e2412846b82298233459ec995 } @Override -@@ -1720,7 +1786,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1750,7 +1816,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public void setResourcePack(String url, byte[] hash, boolean force) { @@ -4321,8 +4321,8 @@ index 5d2ece0c415526d8c632b507f441977e6888c2cb..f53e223e2412846b82298233459ec995 } @Override -@@ -1743,6 +1809,59 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } +@@ -1787,6 +1853,59 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.handlePushResourcePack(new ClientboundResourcePackPushPacket(id, url, hashStr, force, CraftChatMessage.fromStringOrNull(prompt, true)), false); } + // Paper start - adventure @@ -4381,7 +4381,7 @@ index 5d2ece0c415526d8c632b507f441977e6888c2cb..f53e223e2412846b82298233459ec995 @Override public void removeResourcePack(UUID id) { Preconditions.checkArgument(id != null, "Resource pack id cannot be null"); -@@ -2150,6 +2269,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2203,6 +2322,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return (this.getHandle().requestedViewDistance() == 0) ? Bukkit.getViewDistance() : this.getHandle().requestedViewDistance(); } @@ -4394,7 +4394,7 @@ index 5d2ece0c415526d8c632b507f441977e6888c2cb..f53e223e2412846b82298233459ec995 @Override public int getPing() { return this.getHandle().connection.latency(); -@@ -2200,6 +2325,252 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2253,6 +2378,252 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return this.getHandle().allowsListing(); } @@ -4670,7 +4670,7 @@ index 5725b0281ac53a2354b233223259d6784353bc6e..9ef939b76d06874b856e0c850addb364 @Override public int getLineWidth() { diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index ec1c0080642914ceeb931ee6ebdab31c2c6f1f59..a0dc52c805a82e267b66502a480cf76bc82a20d4 100644 +index 249d271338a75c49ec9bc886d034af637618bc7b..da100f451238a093bcaad83343a79254d4aa31ce 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -904,7 +904,7 @@ public class CraftEventFactory { @@ -4876,7 +4876,7 @@ index 9e05a8515c5f6f340182e91150fcad8bbf80a22b..adf22ce4f0bcd3bd57dc2030c6c92d3d @Override public CraftMerchant getCraftMerchant() { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java -index 7753018d4e36091d25badc030ed8a3c9e431a369..5e01357208fe52c1d270c68cb19029ea0f4057bb 100644 +index d1d4760ca6c392c1f1217b58d03a611e7fd6ee54..e0d4798e244add64cbe43201604ad9d57701515f 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java @@ -2,8 +2,9 @@ package org.bukkit.craftbukkit.inventory; @@ -4890,7 +4890,7 @@ index 7753018d4e36091d25badc030ed8a3c9e431a369..5e01357208fe52c1d270c68cb19029ea import java.util.ArrayList; import java.util.Arrays; import java.util.List; -@@ -261,6 +262,148 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { +@@ -262,6 +263,148 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { this.generation = (generation == null) ? null : generation.ordinal(); } @@ -5039,7 +5039,7 @@ index 7753018d4e36091d25badc030ed8a3c9e431a369..5e01357208fe52c1d270c68cb19029ea @Override public String getPage(final int page) { Preconditions.checkArgument(this.isValidPage(page), "Invalid page number (%s)", page); -@@ -402,7 +545,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { +@@ -403,7 +546,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { } @Override @@ -5459,10 +5459,10 @@ index d4fc39c4c450e675c5696b376576a4449a475497..516b3fef4d388366df09f0dd88deadbc boolean hadFormat = false; diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 32b73cd6d65abe1cd5fd33733d8c06467382acdc..1c98b1f1a1c6ab27bb31fd9b32927c97728f980c 100644 +index 56c3aa7647eb2890cf7f546d35002b0c43724500..5cbac19f3c5eaa1940b36891b5a289c425300b20 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -73,6 +73,43 @@ public final class CraftMagicNumbers implements UnsafeValues { +@@ -78,6 +78,43 @@ public final class CraftMagicNumbers implements UnsafeValues { private CraftMagicNumbers() {} diff --git a/patches/server/0011-Paper-command.patch b/patches/server/0011-Paper-command.patch index f330c0e862a0..7fe615b89f4b 100644 --- a/patches/server/0011-Paper-command.patch +++ b/patches/server/0011-Paper-command.patch @@ -605,7 +605,7 @@ index 0000000000000000000000000000000000000000..ae60bd96b5284d54676d8e7e4dd5d170 + } +} diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index c56c7293261ec2601ab02d051b37e820f023f0ff..faab5e8c952a2af6a286043617cded4e6ca5c3c6 100644 +index 8ddfd4a148c95d0d9e93edf03ddac46332936d9a..8499eb5128d3269925ffb2c61d8532e9da47be4a 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -187,6 +187,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -617,10 +617,10 @@ index c56c7293261ec2601ab02d051b37e820f023f0ff..faab5e8c952a2af6a286043617cded4e this.setPvpAllowed(dedicatedserverproperties.pvp); this.setFlightAllowed(dedicatedserverproperties.allowFlight); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 4c75f630ff99259bd1d0e116fa20d0f625a3cf30..e9aa6a3c7710ed0c4685c5d8661d5de3fabb0a0d 100644 +index 8441cbe4ebd676d1aacff223abdabeb32b5658e0..45ba78ae029f59efd16534a2d6453af50a7aa74a 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -939,6 +939,7 @@ public final class CraftServer implements Server { +@@ -951,6 +951,7 @@ public final class CraftServer implements Server { this.commandMap.clearCommands(); this.reloadData(); org.spigotmc.SpigotConfig.registerCommands(); // Spigot @@ -628,7 +628,7 @@ index 4c75f630ff99259bd1d0e116fa20d0f625a3cf30..e9aa6a3c7710ed0c4685c5d8661d5de3 this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); -@@ -2637,6 +2638,34 @@ public final class CraftServer implements Server { +@@ -2649,6 +2650,34 @@ public final class CraftServer implements Server { // Paper end // Paper start diff --git a/patches/server/0013-Paper-Plugins.patch b/patches/server/0013-Paper-Plugins.patch index db0f1755aae2..dcbfcbfaec31 100644 --- a/patches/server/0013-Paper-Plugins.patch +++ b/patches/server/0013-Paper-Plugins.patch @@ -7164,10 +7164,10 @@ index 403c57fc683bb0497602e1a9ec7b81b2722ecc01..ba58580f4c60205d1c7a7b7dfcdc22c4 Bootstrap.validate(); Util.startTimerHackThread(); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index e7aaa9cd3d89ddf46e2a4a41f5d2b8005ccadbbe..e05c01e18d9982f799196c8cdbd593b722475b49 100644 +index 45ba78ae029f59efd16534a2d6453af50a7aa74a..7bd9abe9166df3d120fe6580407a04f68279c55f 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -270,7 +270,8 @@ public final class CraftServer implements Server { +@@ -272,7 +272,8 @@ public final class CraftServer implements Server { private final CraftCommandMap commandMap = new CraftCommandMap(this); private final SimpleHelpMap helpMap = new SimpleHelpMap(this); private final StandardMessenger messenger = new StandardMessenger(); @@ -7177,7 +7177,7 @@ index e7aaa9cd3d89ddf46e2a4a41f5d2b8005ccadbbe..e05c01e18d9982f799196c8cdbd593b7 private final StructureManager structureManager; protected final DedicatedServer console; protected final DedicatedPlayerList playerList; -@@ -418,24 +419,7 @@ public final class CraftServer implements Server { +@@ -420,24 +421,7 @@ public final class CraftServer implements Server { } public void loadPlugins() { @@ -7203,7 +7203,7 @@ index e7aaa9cd3d89ddf46e2a4a41f5d2b8005ccadbbe..e05c01e18d9982f799196c8cdbd593b7 } public void enablePlugins(PluginLoadOrder type) { -@@ -524,15 +508,17 @@ public final class CraftServer implements Server { +@@ -526,15 +510,17 @@ public final class CraftServer implements Server { private void enablePlugin(Plugin plugin) { try { List perms = plugin.getDescription().getPermissions(); @@ -7227,7 +7227,7 @@ index e7aaa9cd3d89ddf46e2a4a41f5d2b8005ccadbbe..e05c01e18d9982f799196c8cdbd593b7 this.pluginManager.enablePlugin(plugin); } catch (Throwable ex) { -@@ -963,6 +949,7 @@ public final class CraftServer implements Server { +@@ -975,6 +961,7 @@ public final class CraftServer implements Server { "This plugin is not properly shutting down its async tasks when it is being reloaded. This may cause conflicts with the newly loaded version of the plugin" )); } @@ -7253,10 +7253,10 @@ index 909b2c98e7a9117d2f737245e4661792ffafb744..d96399e9bf1a58db5a4a22e58abb99e7 @Override public FileConfiguration getConfig() { diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 1c98b1f1a1c6ab27bb31fd9b32927c97728f980c..72c1b7f1468c47ad7053a7ff6b3248324b2bf604 100644 +index 5cbac19f3c5eaa1940b36891b5a289c425300b20..8db0df55c5e23657b3a99a56fefb8e7419618c16 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -421,6 +421,12 @@ public final class CraftMagicNumbers implements UnsafeValues { +@@ -426,6 +426,12 @@ public final class CraftMagicNumbers implements UnsafeValues { net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); return nmsItemStack.getItem().getDescriptionId(nmsItemStack); } diff --git a/patches/server/0014-Timings-v2.patch b/patches/server/0014-Timings-v2.patch index 528768a9ecde..4d99a43a439f 100644 --- a/patches/server/0014-Timings-v2.patch +++ b/patches/server/0014-Timings-v2.patch @@ -893,7 +893,7 @@ index 3691fd58c7baf98b15c50fa4dacf3a35f4f9b4b8..a6a8b5079ceaad90a79a09cab5c38a6f this.profiler.popPush("send chunks"); iterator = this.playerList.getPlayers().iterator(); diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 813fd87df0dfed8fe46389db8333d1d9f409fbe4..dfb37ddb89c2f43d9f9a34a6b2d38616575eb39b 100644 +index 4917aa64505870a0521bc09ce4d4d3e37c03eaae..65dbd05fa904eb8f0f0ef5d3fc3c1e885771f953 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -58,8 +58,9 @@ import org.apache.logging.log4j.Level; @@ -1244,7 +1244,7 @@ index 68a0192f3b1c9491a6f64309ccc919274cdfe178..73608abb5a39749c326ce6fe1bf01442 this.entityManager.saveAll(); } else { diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index d13a662895737180f3d75b6e357ff90c72b0fe08..ce7caf08865df9ff032ba6c42308ea3ce4de6226 100644 +index acd53d9005fc5f43b94c80ec5e7d0e1f9c86ca98..01c70d149b306030c775427f744c4dfab19411cf 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -322,7 +322,6 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl @@ -1315,7 +1315,7 @@ index 6e9f5a404511f3703298def67402b87eca2f28a0..f5a4191977e8675952fc689744c8a39e public UserWhiteList getWhiteList() { diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index a1f658f4dafd47de0020d96667b090aa88c69f9c..8811646495f37587e7976edd8b9558cda412edb1 100644 +index e945daf877068625d4ad0f0b534f2eb23ee658a9..0ecdc0d671f744d85072ca0d157ef37bb28bab9c 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -135,7 +135,6 @@ import org.bukkit.craftbukkit.event.CraftPortalEvent; @@ -1334,7 +1334,7 @@ index a1f658f4dafd47de0020d96667b090aa88c69f9c..8811646495f37587e7976edd8b9558cd // Spigot start public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); public final boolean defaultActivationState; -@@ -811,7 +809,6 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S +@@ -810,7 +808,6 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S } public void move(MoverType movementType, Vec3 movement) { @@ -1342,7 +1342,7 @@ index a1f658f4dafd47de0020d96667b090aa88c69f9c..8811646495f37587e7976edd8b9558cd if (this.noPhysics) { this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); } else { -@@ -972,7 +969,6 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S +@@ -971,7 +968,6 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S this.level().getProfiler().pop(); } } @@ -1393,10 +1393,10 @@ index c4bc491eed487c0a7e30538b0fb46fde91cd7b31..382b55167dede435b034866bd394455f } diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index b71b702471599fc8f1de42919ade8ee6a4e6247c..2e47008a8ff1bb56b752d4eb880173b9edfbc4ad 100644 +index 44bfb8778f2894be9633be7ddc4f3c6881c9b05d..e111bdb614f173322ed0cc0386db6870a984fff7 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -140,7 +140,7 @@ import org.bukkit.event.entity.EntityTeleportEvent; +@@ -141,7 +141,7 @@ import org.bukkit.event.entity.EntityTeleportEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; // CraftBukkit end @@ -1405,7 +1405,7 @@ index b71b702471599fc8f1de42919ade8ee6a4e6247c..2e47008a8ff1bb56b752d4eb880173b9 public abstract class LivingEntity extends Entity implements Attackable { -@@ -2866,7 +2866,6 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -2879,7 +2879,6 @@ public abstract class LivingEntity extends Entity implements Attackable { @Override public void tick() { @@ -1413,7 +1413,7 @@ index b71b702471599fc8f1de42919ade8ee6a4e6247c..2e47008a8ff1bb56b752d4eb880173b9 super.tick(); this.updatingUsingItem(); this.updateSwimAmount(); -@@ -2908,9 +2907,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -2921,9 +2920,7 @@ public abstract class LivingEntity extends Entity implements Attackable { } if (!this.isRemoved()) { @@ -1423,7 +1423,7 @@ index b71b702471599fc8f1de42919ade8ee6a4e6247c..2e47008a8ff1bb56b752d4eb880173b9 } double d0 = this.getX() - this.xo; -@@ -2994,7 +2991,6 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3007,7 +3004,6 @@ public abstract class LivingEntity extends Entity implements Attackable { } this.refreshDirtyAttributes(); @@ -1431,7 +1431,7 @@ index b71b702471599fc8f1de42919ade8ee6a4e6247c..2e47008a8ff1bb56b752d4eb880173b9 } public void detectEquipmentUpdatesPublic() { // CraftBukkit -@@ -3169,7 +3165,6 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3182,7 +3178,6 @@ public abstract class LivingEntity extends Entity implements Attackable { this.setDeltaMovement(d0, d1, d2); this.level().getProfiler().push("ai"); @@ -1439,7 +1439,7 @@ index b71b702471599fc8f1de42919ade8ee6a4e6247c..2e47008a8ff1bb56b752d4eb880173b9 if (this.isImmobile()) { this.jumping = false; this.xxa = 0.0F; -@@ -3179,7 +3174,6 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3192,7 +3187,6 @@ public abstract class LivingEntity extends Entity implements Attackable { this.serverAiStep(); this.level().getProfiler().pop(); } @@ -1447,7 +1447,7 @@ index b71b702471599fc8f1de42919ade8ee6a4e6247c..2e47008a8ff1bb56b752d4eb880173b9 this.level().getProfiler().pop(); this.level().getProfiler().push("jump"); -@@ -3219,7 +3213,6 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3232,7 +3226,6 @@ public abstract class LivingEntity extends Entity implements Attackable { this.resetFallDistance(); } @@ -1455,7 +1455,7 @@ index b71b702471599fc8f1de42919ade8ee6a4e6247c..2e47008a8ff1bb56b752d4eb880173b9 label104: { LivingEntity entityliving = this.getControllingPassenger(); -@@ -3235,7 +3228,6 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3248,7 +3241,6 @@ public abstract class LivingEntity extends Entity implements Attackable { this.travel(vec3d1); } @@ -1463,7 +1463,7 @@ index b71b702471599fc8f1de42919ade8ee6a4e6247c..2e47008a8ff1bb56b752d4eb880173b9 this.level().getProfiler().pop(); this.level().getProfiler().push("freezing"); -@@ -3262,9 +3254,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3275,9 +3267,7 @@ public abstract class LivingEntity extends Entity implements Attackable { this.checkAutoSpinAttack(axisalignedbb, this.getBoundingBox()); } @@ -1579,7 +1579,7 @@ index 409007f6e6940e5ea92e3cbaa74e55cdee50325b..c2179ea2a16ae0fac0b59d81a57abf13 } } diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 0eb09ce5c850d85ffd7229d27cf06b3e0edda11b..cc1d7626a82881c4410d65c6a33dadae7ab07172 100644 +index fff1a2f2b8b5235c0c56c68264db0a914f6cb15d..ba7a816bd9dd4aec79e2560f0968374dbb28442c 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java @@ -483,13 +483,10 @@ public class ChunkSerializer { @@ -1605,10 +1605,10 @@ index 0eb09ce5c850d85ffd7229d27cf06b3e0edda11b..cc1d7626a82881c4410d65c6a33dadae }; } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index ab7f7246ee7cd456dbf016aa4b3eed974cd0bca2..eccca51b91f3c8afeab19fe11b3e60df314c9c72 100644 +index 7bd9abe9166df3d120fe6580407a04f68279c55f..13b33785e13d5f9cc659dc48e5fea6451ca778fc 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -368,7 +368,7 @@ public final class CraftServer implements Server { +@@ -370,7 +370,7 @@ public final class CraftServer implements Server { this.saveCommandsConfig(); this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); @@ -1617,7 +1617,7 @@ index ab7f7246ee7cd456dbf016aa4b3eed974cd0bca2..eccca51b91f3c8afeab19fe11b3e60df this.overrideSpawnLimits(); console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); -@@ -2545,12 +2545,31 @@ public final class CraftServer implements Server { +@@ -2557,12 +2557,31 @@ public final class CraftServer implements Server { private final org.bukkit.Server.Spigot spigot = new org.bukkit.Server.Spigot() { @@ -1819,10 +1819,10 @@ index b0ffa23faf62629043dfd613315eaf9c5fcc2cfe..00000000000000000000000000000000 - } -} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index f53e223e2412846b82298233459ec9953bc0a63e..87e7071a381540be3b1db55f5d606e9e1e117b39 100644 +index e5330d41512dc59b5f94d9cacda340a46f45fd76..d53e15a3a70de8e2a405d3a39ff51a3551e82dbc 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2646,6 +2646,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2699,6 +2699,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player { CraftPlayer.this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundSystemChatPacket(components, position == net.md_5.bungee.api.ChatMessageType.ACTION_BAR)); } @@ -2019,10 +2019,10 @@ index f97eccb6a17c7876e1e002d798eb67bbe80571a0..76effc345d362047e64d064eb64a5222 + } // Paper } diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 72c1b7f1468c47ad7053a7ff6b3248324b2bf604..677335c3888adc25fbf3c5ec4d5a6c5ecf58ea5d 100644 +index 8db0df55c5e23657b3a99a56fefb8e7419618c16..5e541636b216d4f46be18b1031522bd92e667b67 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -209,6 +209,12 @@ public final class CraftMagicNumbers implements UnsafeValues { +@@ -214,6 +214,12 @@ public final class CraftMagicNumbers implements UnsafeValues { } // Paper end // ======================================================================== @@ -2035,20 +2035,19 @@ index 72c1b7f1468c47ad7053a7ff6b3248324b2bf604..677335c3888adc25fbf3c5ec4d5a6c5e public static byte toLegacyData(BlockState data) { return CraftLegacy.toLegacyData(data); -@@ -442,6 +448,13 @@ public final class CraftMagicNumbers implements UnsafeValues { - return new CraftPotionType(namespacedKey, potionRegistry); +@@ -457,6 +463,12 @@ public final class CraftMagicNumbers implements UnsafeValues { + public DamageSource.Builder createDamageSourceBuilder(DamageType damageType) { + return new CraftDamageSourceBuilder(damageType); } - + // Paper start + @Override + public String getTimingsServerName() { + return io.papermc.paper.configuration.GlobalConfiguration.get().timings.serverName; + } + // Paper end -+ + /** * This helper class represents the different NBT Tags. - *

    diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java index ff422d4d4f2b764370f0ee2af13034853c1d3fe1..a5da6c1cae0afbde684be250e2fc3c0c32a1265b 100644 --- a/src/main/java/org/spigotmc/ActivationRange.java diff --git a/patches/server/0016-Further-improve-server-tick-loop.patch b/patches/server/0016-Further-improve-server-tick-loop.patch index 70deed19e296..5c66e16921cf 100644 --- a/patches/server/0016-Further-improve-server-tick-loop.patch +++ b/patches/server/0016-Further-improve-server-tick-loop.patch @@ -146,10 +146,10 @@ index a6a8b5079ceaad90a79a09cab5c38a6fde6d33ee..f32aa4e03ae02d1449101c4aa89c8e0c this.startMetricsRecordingTick(); this.profiler.push("tick"); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index ac036223ca386a48caf1796ea65c3bbde4d6ed08..2e42f297ad27d9dea4b09cf19e9b4f00e4fc5a21 100644 +index 13b33785e13d5f9cc659dc48e5fea6451ca778fc..5a720c0a751e7a83976727d01cc5bc6282702255 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2598,7 +2598,11 @@ public final class CraftServer implements Server { +@@ -2610,7 +2610,11 @@ public final class CraftServer implements Server { @Override public double[] getTPS() { diff --git a/patches/server/0017-Add-command-line-option-to-load-extra-plugin-jars-no.patch b/patches/server/0017-Add-command-line-option-to-load-extra-plugin-jars-no.patch index 5dc280deb735..98abdd4851be 100644 --- a/patches/server/0017-Add-command-line-option-to-load-extra-plugin-jars-no.patch +++ b/patches/server/0017-Add-command-line-option-to-load-extra-plugin-jars-no.patch @@ -7,10 +7,10 @@ Subject: [PATCH] Add command line option to load extra plugin jars not in the ex: java -jar paperclip.jar nogui -add-plugin=/path/to/plugin.jar -add-plugin=/path/to/another/plugin_jar.jar diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 33b6605d1139e83a09054b6ed140e129e65a954d..3b2f99617a519c2c2f60b88a4679064c01e5d958 100644 +index 5a720c0a751e7a83976727d01cc5bc6282702255..48c40f12649735f14204c516eace6905b2ac0019 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -422,6 +422,35 @@ public final class CraftServer implements Server { +@@ -424,6 +424,35 @@ public final class CraftServer implements Server { io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.INSTANCE.enter(io.papermc.paper.plugin.entrypoint.Entrypoint.PLUGIN); // Paper - replace implementation } diff --git a/patches/server/0018-Keep-previous-behavior-for-setResourcePack.patch b/patches/server/0018-Keep-previous-behavior-for-setResourcePack.patch deleted file mode 100644 index 98e0dec4c489..000000000000 --- a/patches/server/0018-Keep-previous-behavior-for-setResourcePack.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 8 Dec 2023 15:06:16 -0800 -Subject: [PATCH] Keep previous behavior for setResourcePack - -Before multiple packs were allowed, setResourcePack -resulted in the client's existing server pack being -replaced. To keep this behavior, we will remove all -packs before sending the new pack. Other API exists -for adding a new pack to the existing packs on a client. - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 87e7071a381540be3b1db55f5d606e9e1e117b39..afdc702682af7ddf338fe00a1b1912766e728f41 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1803,8 +1803,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - if (hash != null) { - Preconditions.checkArgument(hash.length == 20, "Resource pack hash should be 20 bytes long but was %s", hash.length); - -+ this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundResourcePackPopPacket(Optional.empty())); // Paper - keep previous behavior of clearing packs - this.getHandle().connection.send(new ClientboundResourcePackPushPacket(id, url, BaseEncoding.base16().lowerCase().encode(hash), force, CraftChatMessage.fromStringOrNull(prompt, true))); - } else { -+ this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundResourcePackPopPacket(Optional.empty())); // Paper - keep previous behavior of clearing packs - this.getHandle().connection.send(new ClientboundResourcePackPushPacket(id, url, "", force, CraftChatMessage.fromStringOrNull(prompt, true))); - } - } diff --git a/patches/server/0019-Support-components-in-ItemMeta.patch b/patches/server/0018-Support-components-in-ItemMeta.patch similarity index 100% rename from patches/server/0019-Support-components-in-ItemMeta.patch rename to patches/server/0018-Support-components-in-ItemMeta.patch diff --git a/patches/server/0019-Configurable-cactus-bamboo-and-reed-growth-height.patch b/patches/server/0019-Configurable-cactus-bamboo-and-reed-growth-height.patch new file mode 100644 index 000000000000..6f7a9c6719af --- /dev/null +++ b/patches/server/0019-Configurable-cactus-bamboo-and-reed-growth-height.patch @@ -0,0 +1,92 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 13:02:51 -0600 +Subject: [PATCH] Configurable cactus bamboo and reed growth height + +Bamboo - Both the minimum fully-grown height and the maximum are configurable +- Machine_Maker + +diff --git a/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java b/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java +index bd72deadb59289ae90afc379ee61e8198ddaf4ed..e8dc4ea90d74036dacb0785fcb9125df192a4c22 100644 +--- a/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java +@@ -137,7 +137,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { + if (random.nextFloat() < (world.spigotConfig.bambooModifier / (100.0f * 3)) && world.isEmptyBlock(pos.above()) && world.getRawBrightness(pos.above(), 0) >= 9) { // Spigot - SPIGOT-7159: Better modifier resolution + int i = this.getHeightBelowUpToMax(world, pos) + 1; + +- if (i < 16) { ++ if (i < world.paperConfig().maxGrowthHeight.bamboo.max) { // Paper - Configurable cactus/bamboo/reed growth height + this.growBamboo(state, world, pos, random, i); + } + } +@@ -168,7 +168,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { + int i = this.getHeightAboveUpToMax(world, pos); + int j = this.getHeightBelowUpToMax(world, pos); + +- return i + j + 1 < 16 && (Integer) world.getBlockState(pos.above(i)).getValue(BambooStalkBlock.STAGE) != 1; ++ return i + j + 1 < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && (Integer) world.getBlockState(pos.above(i)).getValue(BambooStalkBlock.STAGE) != 1; // Paper - Configurable cactus/bamboo/reed growth height + } + + @Override +@@ -187,7 +187,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { + BlockPos blockposition1 = pos.above(i); + BlockState iblockdata1 = world.getBlockState(blockposition1); + +- if (k >= 16 || !iblockdata1.is(Blocks.BAMBOO) || (Integer) iblockdata1.getValue(BambooStalkBlock.STAGE) == 1 || !world.isEmptyBlock(blockposition1.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here ++ if (k >= world.paperConfig().maxGrowthHeight.bamboo.max || !iblockdata1.is(Blocks.BAMBOO) || (Integer) iblockdata1.getValue(BambooStalkBlock.STAGE) == 1 || !world.isEmptyBlock(blockposition1.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here // Paper - Configurable cactus/bamboo/reed growth height + return; + } + +@@ -228,7 +228,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { + } + + int j = (Integer) state.getValue(BambooStalkBlock.AGE) != 1 && !iblockdata2.is(Blocks.BAMBOO) ? 0 : 1; +- int k = (height < 11 || random.nextFloat() >= 0.25F) && height != 15 ? 0 : 1; ++ int k = (height < world.paperConfig().maxGrowthHeight.bamboo.min || random.nextFloat() >= 0.25F) && height != (world.paperConfig().maxGrowthHeight.bamboo.max - 1) ? 0 : 1; // Paper - Configurable cactus/bamboo/reed growth height + + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, pos.above(), (BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(BambooStalkBlock.AGE, j)).setValue(BambooStalkBlock.LEAVES, blockpropertybamboosize)).setValue(BambooStalkBlock.STAGE, k), 3)) { +@@ -243,7 +243,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { + protected int getHeightAboveUpToMax(BlockGetter world, BlockPos pos) { + int i; + +- for (i = 0; i < 16 && world.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO); ++i) { ++ for (i = 0; i < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && world.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO); ++i) { // Paper - Configurable cactus/bamboo/reed growth height + ; + } + +@@ -253,7 +253,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { + protected int getHeightBelowUpToMax(BlockGetter world, BlockPos pos) { + int i; + +- for (i = 0; i < 16 && world.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO); ++i) { ++ for (i = 0; i < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && world.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO); ++i) { // Paper - Configurable cactus/bamboo/reed growth height + ; + } + +diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java +index 2f343eaf0c2216702fd614dbec98ac3d46ef5b2e..0e8c337dde0cfa2ac289c79904ecd2affc86d70a 100644 +--- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java +@@ -61,7 +61,7 @@ public class CactusBlock extends Block { + ; + } + +- if (i < 3) { ++ if (i < world.paperConfig().maxGrowthHeight.cactus) { // Paper - Configurable cactus/bamboo/reed growth height + int j = (Integer) state.getValue(CactusBlock.AGE); + + int modifier = world.spigotConfig.cactusModifier; // Spigot - SPIGOT-7159: Better modifier resolution +diff --git a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java +index 47d4fc5bcf2657078abc7a2637b6337fc0ea0977..04957d461d0e968d443737068aaeec1d0bce78b2 100644 +--- a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java +@@ -59,7 +59,7 @@ public class SugarCaneBlock extends Block { + ; + } + +- if (i < 3) { ++ if (i < world.paperConfig().maxGrowthHeight.reeds) { // Paper - Configurable cactus/bamboo/reed growth heigh + int j = (Integer) state.getValue(SugarCaneBlock.AGE); + + int modifier = world.spigotConfig.caneModifier; // Spigot - SPIGOT-7159: Better modifier resolution diff --git a/patches/server/0021-Configurable-baby-zombie-movement-speed.patch b/patches/server/0020-Configurable-baby-zombie-movement-speed.patch similarity index 100% rename from patches/server/0021-Configurable-baby-zombie-movement-speed.patch rename to patches/server/0020-Configurable-baby-zombie-movement-speed.patch diff --git a/patches/server/0020-Configurable-cactus-bamboo-and-reed-growth-height.patch b/patches/server/0020-Configurable-cactus-bamboo-and-reed-growth-height.patch deleted file mode 100644 index 195e55198ac5..000000000000 --- a/patches/server/0020-Configurable-cactus-bamboo-and-reed-growth-height.patch +++ /dev/null @@ -1,92 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Tue, 1 Mar 2016 13:02:51 -0600 -Subject: [PATCH] Configurable cactus bamboo and reed growth height - -Bamboo - Both the minimum fully-grown height and the maximum are configurable -- Machine_Maker - -diff --git a/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java b/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java -index bd72deadb59289ae90afc379ee61e8198ddaf4ed..e8dc4ea90d74036dacb0785fcb9125df192a4c22 100644 ---- a/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java -@@ -137,7 +137,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { - if (random.nextFloat() < (world.spigotConfig.bambooModifier / (100.0f * 3)) && world.isEmptyBlock(pos.above()) && world.getRawBrightness(pos.above(), 0) >= 9) { // Spigot - SPIGOT-7159: Better modifier resolution - int i = this.getHeightBelowUpToMax(world, pos) + 1; - -- if (i < 16) { -+ if (i < world.paperConfig().maxGrowthHeight.bamboo.max) { // Paper - Configurable cactus/bamboo/reed growth height - this.growBamboo(state, world, pos, random, i); - } - } -@@ -168,7 +168,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { - int i = this.getHeightAboveUpToMax(world, pos); - int j = this.getHeightBelowUpToMax(world, pos); - -- return i + j + 1 < 16 && (Integer) world.getBlockState(pos.above(i)).getValue(BambooStalkBlock.STAGE) != 1; -+ return i + j + 1 < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && (Integer) world.getBlockState(pos.above(i)).getValue(BambooStalkBlock.STAGE) != 1; // Paper - Configurable cactus/bamboo/reed growth height - } - - @Override -@@ -187,7 +187,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { - BlockPos blockposition1 = pos.above(i); - BlockState iblockdata1 = world.getBlockState(blockposition1); - -- if (k >= 16 || !iblockdata1.is(Blocks.BAMBOO) || (Integer) iblockdata1.getValue(BambooStalkBlock.STAGE) == 1 || !world.isEmptyBlock(blockposition1.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here -+ if (k >= world.paperConfig().maxGrowthHeight.bamboo.max || !iblockdata1.is(Blocks.BAMBOO) || (Integer) iblockdata1.getValue(BambooStalkBlock.STAGE) == 1 || !world.isEmptyBlock(blockposition1.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here // Paper - Configurable cactus/bamboo/reed growth height - return; - } - -@@ -228,7 +228,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { - } - - int j = (Integer) state.getValue(BambooStalkBlock.AGE) != 1 && !iblockdata2.is(Blocks.BAMBOO) ? 0 : 1; -- int k = (height < 11 || random.nextFloat() >= 0.25F) && height != 15 ? 0 : 1; -+ int k = (height < world.paperConfig().maxGrowthHeight.bamboo.min || random.nextFloat() >= 0.25F) && height != (world.paperConfig().maxGrowthHeight.bamboo.max - 1) ? 0 : 1; // Paper - Configurable cactus/bamboo/reed growth height - - // CraftBukkit start - if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, pos.above(), (BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(BambooStalkBlock.AGE, j)).setValue(BambooStalkBlock.LEAVES, blockpropertybamboosize)).setValue(BambooStalkBlock.STAGE, k), 3)) { -@@ -243,7 +243,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { - protected int getHeightAboveUpToMax(BlockGetter world, BlockPos pos) { - int i; - -- for (i = 0; i < 16 && world.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO); ++i) { -+ for (i = 0; i < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && world.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO); ++i) { // Paper - Configurable cactus/bamboo/reed growth height - ; - } - -@@ -253,7 +253,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { - protected int getHeightBelowUpToMax(BlockGetter world, BlockPos pos) { - int i; - -- for (i = 0; i < 16 && world.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO); ++i) { -+ for (i = 0; i < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && world.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO); ++i) { // Paper - Configurable cactus/bamboo/reed growth height - ; - } - -diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -index fcd5b593c79aab42928cb1ddd0e6c1b03b7bafaf..581870d23e4deca85ae94bffac2011b69650c23d 100644 ---- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -@@ -61,7 +61,7 @@ public class CactusBlock extends Block { - ; - } - -- if (i < 3) { -+ if (i < world.paperConfig().maxGrowthHeight.cactus) { // Paper - Configurable cactus/bamboo/reed growth height - int j = (Integer) state.getValue(CactusBlock.AGE); - - int modifier = world.spigotConfig.cactusModifier; // Spigot - SPIGOT-7159: Better modifier resolution -diff --git a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java -index 47d4fc5bcf2657078abc7a2637b6337fc0ea0977..04957d461d0e968d443737068aaeec1d0bce78b2 100644 ---- a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java -@@ -59,7 +59,7 @@ public class SugarCaneBlock extends Block { - ; - } - -- if (i < 3) { -+ if (i < world.paperConfig().maxGrowthHeight.reeds) { // Paper - Configurable cactus/bamboo/reed growth heigh - int j = (Integer) state.getValue(SugarCaneBlock.AGE); - - int modifier = world.spigotConfig.caneModifier; // Spigot - SPIGOT-7159: Better modifier resolution diff --git a/patches/server/0022-Configurable-fishing-time-ranges.patch b/patches/server/0021-Configurable-fishing-time-ranges.patch similarity index 100% rename from patches/server/0022-Configurable-fishing-time-ranges.patch rename to patches/server/0021-Configurable-fishing-time-ranges.patch diff --git a/patches/server/0022-Allow-nerfed-mobs-to-jump.patch b/patches/server/0022-Allow-nerfed-mobs-to-jump.patch new file mode 100644 index 000000000000..963c4fa03270 --- /dev/null +++ b/patches/server/0022-Allow-nerfed-mobs-to-jump.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 13:24:16 -0600 +Subject: [PATCH] Allow nerfed mobs to jump + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index fc264864d4a6f56f94d884f4892257452b360b73..b86ad4fbf1245c4c040d84daf9f93bd1aee6beae 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -112,6 +112,7 @@ public abstract class Mob extends LivingEntity implements Targeting { + private final BodyRotationControl bodyRotationControl; + protected PathNavigation navigation; + public GoalSelector goalSelector; ++ @Nullable public net.minecraft.world.entity.ai.goal.FloatGoal goalFloat; // Paper - Allow nerfed mobs to jump and float + public GoalSelector targetSelector; + @Nullable + private LivingEntity target; +@@ -877,7 +878,15 @@ public abstract class Mob extends LivingEntity implements Targeting { + @Override + protected final void serverAiStep() { + ++this.noActionTime; +- if (!this.aware) return; // CraftBukkit ++ // Paper start - Allow nerfed mobs to jump and float ++ if (!this.aware) { ++ if (goalFloat != null) { ++ if (goalFloat.canUse()) goalFloat.tick(); ++ this.getJumpControl().tick(); ++ } ++ return; ++ } ++ // Paper end - Allow nerfed mobs to jump and float + this.level().getProfiler().push("sensing"); + this.sensing.tick(); + this.level().getProfiler().pop(); +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java +index 01950951ea06e43bedeeede489a112e577617829..60a62781fcfe4c598c308a7ce2b0dcf72c0895ae 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java +@@ -9,6 +9,7 @@ public class FloatGoal extends Goal { + + public FloatGoal(Mob mob) { + this.mob = mob; ++ if (mob.getCommandSenderWorld().paperConfig().entities.behavior.spawnerNerfedMobsShouldJump) mob.goalFloat = this; // Paper - Allow nerfed mobs to jump and float + this.setFlags(EnumSet.of(Goal.Flag.JUMP)); + mob.getNavigation().setCanFloat(true); + } diff --git a/patches/server/0023-Add-configurable-entity-despawn-distances.patch b/patches/server/0023-Add-configurable-entity-despawn-distances.patch new file mode 100644 index 000000000000..dca9b87b3b4e --- /dev/null +++ b/patches/server/0023-Add-configurable-entity-despawn-distances.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Suddenly +Date: Tue, 1 Mar 2016 13:51:54 -0600 +Subject: [PATCH] Add configurable entity despawn distances + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index b86ad4fbf1245c4c040d84daf9f93bd1aee6beae..1de4c45f1a3a69e63eccf063be5516b163f7882a 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -853,14 +853,14 @@ public abstract class Mob extends LivingEntity implements Targeting { + + if (entityhuman != null) { + double d0 = entityhuman.distanceToSqr((Entity) this); +- int i = this.getType().getCategory().getDespawnDistance(); ++ int i = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()).hard(); // Paper - Configurable despawn distances + int j = i * i; + + if (d0 > (double) j && this.removeWhenFarAway(d0)) { + this.discard(); + } + +- int k = this.getType().getCategory().getNoDespawnDistance(); ++ int k = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()).soft(); // Paper - Configurable despawn distances + int l = k * k; + + if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && d0 > (double) l && this.removeWhenFarAway(d0)) { diff --git a/patches/server/0023-Allow-nerfed-mobs-to-jump.patch b/patches/server/0023-Allow-nerfed-mobs-to-jump.patch deleted file mode 100644 index 6cd56c73a4ee..000000000000 --- a/patches/server/0023-Allow-nerfed-mobs-to-jump.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Tue, 1 Mar 2016 13:24:16 -0600 -Subject: [PATCH] Allow nerfed mobs to jump - - -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 01761d37c9e4be4e498b62c7612885648b2968a6..093c60df0948fd1a46aef86223ecc63bc9e8b440 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -112,6 +112,7 @@ public abstract class Mob extends LivingEntity implements Targeting { - private final BodyRotationControl bodyRotationControl; - protected PathNavigation navigation; - public GoalSelector goalSelector; -+ @Nullable public net.minecraft.world.entity.ai.goal.FloatGoal goalFloat; // Paper - Allow nerfed mobs to jump and float - public GoalSelector targetSelector; - @Nullable - private LivingEntity target; -@@ -877,7 +878,15 @@ public abstract class Mob extends LivingEntity implements Targeting { - @Override - protected final void serverAiStep() { - ++this.noActionTime; -- if (!this.aware) return; // CraftBukkit -+ // Paper start - Allow nerfed mobs to jump and float -+ if (!this.aware) { -+ if (goalFloat != null) { -+ if (goalFloat.canUse()) goalFloat.tick(); -+ this.getJumpControl().tick(); -+ } -+ return; -+ } -+ // Paper end - Allow nerfed mobs to jump and float - this.level().getProfiler().push("sensing"); - this.sensing.tick(); - this.level().getProfiler().pop(); -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java -index 01950951ea06e43bedeeede489a112e577617829..60a62781fcfe4c598c308a7ce2b0dcf72c0895ae 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java -@@ -9,6 +9,7 @@ public class FloatGoal extends Goal { - - public FloatGoal(Mob mob) { - this.mob = mob; -+ if (mob.getCommandSenderWorld().paperConfig().entities.behavior.spawnerNerfedMobsShouldJump) mob.goalFloat = this; // Paper - Allow nerfed mobs to jump and float - this.setFlags(EnumSet.of(Goal.Flag.JUMP)); - mob.getNavigation().setCanFloat(true); - } diff --git a/patches/server/0024-Add-configurable-entity-despawn-distances.patch b/patches/server/0024-Add-configurable-entity-despawn-distances.patch deleted file mode 100644 index 600e13736cd2..000000000000 --- a/patches/server/0024-Add-configurable-entity-despawn-distances.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Suddenly -Date: Tue, 1 Mar 2016 13:51:54 -0600 -Subject: [PATCH] Add configurable entity despawn distances - - -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 093c60df0948fd1a46aef86223ecc63bc9e8b440..7cbb74f4bc7b63af86b7b2c52783fb20c7739258 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -853,14 +853,14 @@ public abstract class Mob extends LivingEntity implements Targeting { - - if (entityhuman != null) { - double d0 = entityhuman.distanceToSqr((Entity) this); -- int i = this.getType().getCategory().getDespawnDistance(); -+ int i = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()).hard(); // Paper - Configurable despawn distances - int j = i * i; - - if (d0 > (double) j && this.removeWhenFarAway(d0)) { - this.discard(); - } - -- int k = this.getType().getCategory().getNoDespawnDistance(); -+ int k = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()).soft(); // Paper - Configurable despawn distances - int l = k * k; - - if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && d0 > (double) l && this.removeWhenFarAway(d0)) { diff --git a/patches/server/0025-Allow-for-toggling-of-spawn-chunks.patch b/patches/server/0024-Allow-for-toggling-of-spawn-chunks.patch similarity index 100% rename from patches/server/0025-Allow-for-toggling-of-spawn-chunks.patch rename to patches/server/0024-Allow-for-toggling-of-spawn-chunks.patch diff --git a/patches/server/0025-Drop-falling-block-and-tnt-entities-at-the-specified.patch b/patches/server/0025-Drop-falling-block-and-tnt-entities-at-the-specified.patch new file mode 100644 index 000000000000..79d4064bb479 --- /dev/null +++ b/patches/server/0025-Drop-falling-block-and-tnt-entities-at-the-specified.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Tue, 1 Mar 2016 14:14:15 -0600 +Subject: [PATCH] Drop falling block and tnt entities at the specified height + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +index 66c0b74a41cb9e7403826b4fd54a8575d6f16faa..ee5ef4fe16ce6397bba30900b9c6690e3c4f51e6 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -142,6 +142,16 @@ public class FallingBlockEntity extends Entity { + } + + this.move(MoverType.SELF, this.getDeltaMovement()); ++ // Paper start - Configurable falling blocks height nerf ++ if (this.level().paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) { ++ if (this.dropItem && this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { ++ this.spawnAtLocation(block); ++ } ++ ++ this.discard(); ++ return; ++ } ++ // Paper end - Configurable falling blocks height nerf + if (!this.level().isClientSide) { + BlockPos blockposition = this.blockPosition(); + boolean flag = this.blockState.getBlock() instanceof ConcretePowderBlock; +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 36d93ee421406302c05945db17d46b04b485e4c0..19e2f2005bd1fb4d199debd34e92a0794a3d3fe4 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -77,6 +77,12 @@ public class PrimedTnt extends Entity implements TraceableEntity { + } + + this.move(MoverType.SELF, this.getDeltaMovement()); ++ // Paper start - Configurable TNT height nerf ++ if (this.level().paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) { ++ this.discard(); ++ return; ++ } ++ // Paper end - Configurable TNT height nerf + this.setDeltaMovement(this.getDeltaMovement().scale(0.98D)); + if (this.onGround()) { + this.setDeltaMovement(this.getDeltaMovement().multiply(0.7D, -0.5D, 0.7D)); +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java b/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java +index 0fc58f38bbd855414ad36b682e60b069d7b68cb1..48f531da21fa0305ab1e8d5b50276e61e3155a38 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java +@@ -53,6 +53,12 @@ public class MinecartTNT extends AbstractMinecart { + public void tick() { + super.tick(); + if (this.fuse > 0) { ++ // Paper start - Configurable TNT height nerf ++ if (this.level().paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) { ++ this.discard(); ++ return; ++ } ++ // Paper end - Configurable TNT height nerf + --this.fuse; + this.level().addParticle(ParticleTypes.SMOKE, this.getX(), this.getY() + 0.5D, this.getZ(), 0.0D, 0.0D, 0.0D); + } else if (this.fuse == 0) { diff --git a/patches/server/0026-Drop-falling-block-and-tnt-entities-at-the-specified.patch b/patches/server/0026-Drop-falling-block-and-tnt-entities-at-the-specified.patch deleted file mode 100644 index 21eaaaa49e2e..000000000000 --- a/patches/server/0026-Drop-falling-block-and-tnt-entities-at-the-specified.patch +++ /dev/null @@ -1,62 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Byteflux -Date: Tue, 1 Mar 2016 14:14:15 -0600 -Subject: [PATCH] Drop falling block and tnt entities at the specified height - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -index d344b13e3726f0fe8a57c098769d1beea9705cdd..67088152004caeecf4a678618be19419862e7ff1 100644 ---- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -@@ -142,6 +142,16 @@ public class FallingBlockEntity extends Entity { - } - - this.move(MoverType.SELF, this.getDeltaMovement()); -+ // Paper start - Configurable falling blocks height nerf -+ if (this.level().paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) { -+ if (this.dropItem && this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { -+ this.spawnAtLocation(block); -+ } -+ -+ this.discard(); -+ return; -+ } -+ // Paper end - Configurable falling blocks height nerf - if (!this.level().isClientSide) { - BlockPos blockposition = this.blockPosition(); - boolean flag = this.blockState.getBlock() instanceof ConcretePowderBlock; -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 36d93ee421406302c05945db17d46b04b485e4c0..19e2f2005bd1fb4d199debd34e92a0794a3d3fe4 100644 ---- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -@@ -77,6 +77,12 @@ public class PrimedTnt extends Entity implements TraceableEntity { - } - - this.move(MoverType.SELF, this.getDeltaMovement()); -+ // Paper start - Configurable TNT height nerf -+ if (this.level().paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) { -+ this.discard(); -+ return; -+ } -+ // Paper end - Configurable TNT height nerf - this.setDeltaMovement(this.getDeltaMovement().scale(0.98D)); - if (this.onGround()) { - this.setDeltaMovement(this.getDeltaMovement().multiply(0.7D, -0.5D, 0.7D)); -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java b/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java -index 0fc58f38bbd855414ad36b682e60b069d7b68cb1..48f531da21fa0305ab1e8d5b50276e61e3155a38 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/MinecartTNT.java -@@ -53,6 +53,12 @@ public class MinecartTNT extends AbstractMinecart { - public void tick() { - super.tick(); - if (this.fuse > 0) { -+ // Paper start - Configurable TNT height nerf -+ if (this.level().paperConfig().fixes.tntEntityHeightNerf.test(v -> this.getY() > v)) { -+ this.discard(); -+ return; -+ } -+ // Paper end - Configurable TNT height nerf - --this.fuse; - this.level().addParticle(ParticleTypes.SMOKE, this.getX(), this.getY() + 0.5D, this.getZ(), 0.0D, 0.0D, 0.0D); - } else if (this.fuse == 0) { diff --git a/patches/server/0026-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch b/patches/server/0026-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch new file mode 100644 index 000000000000..06b6b9aded66 --- /dev/null +++ b/patches/server/0026-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch @@ -0,0 +1,104 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 14:32:43 -0600 +Subject: [PATCH] Show 'Paper' in client crashes, server lists, and Mojang + stats + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index f32aa4e03ae02d1449101c4aa89c8e0c0fa26ba5..c6ce55d48fc71ea097a4a279fcd0dd1d4086be9b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1615,7 +1615,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop // CraftBukkit - cb > vanilla! ++ return "Paper"; // Paper + } + + public SystemReport fillSystemReport(SystemReport details) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 48c40f12649735f14204c516eace6905b2ac0019..56a63adc3c0c919594c3f2646d4cf5b86b5c6f1e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -263,7 +263,7 @@ import org.yaml.snakeyaml.error.MarkedYAMLException; + import net.md_5.bungee.api.chat.BaseComponent; // Spigot + + public final class CraftServer implements Server { +- private final String serverName = "CraftBukkit"; ++ private final String serverName = "Paper"; // Paper + private final String serverVersion; + private final String bukkitVersion = Versioning.getBukkitVersion(); + private final Logger logger = Logger.getLogger("Minecraft"); +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index c312c450055965d63db0ccdee8aa8e34e7051d0b..9f4c5a6f22719ae30d88ca02a1db4a3f39957943 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -241,12 +241,25 @@ public class Main { + deadline.add(Calendar.DAY_OF_YEAR, -7); + if (buildDate.before(deadline.getTime())) { + System.err.println("*** Error, this build is outdated ***"); +- System.err.println("*** Please download a new build as per instructions from https://www.spigotmc.org/go/outdated-spigot ***"); ++ System.err.println("*** Please download a new build as per instructions from https://papermc.io/downloads/paper ***"); // Paper + System.err.println("*** Server will start in 20 seconds ***"); + Thread.sleep(TimeUnit.SECONDS.toMillis(20)); + } + } + ++ // Paper start - Log Java and OS versioning to help with debugging plugin issues ++ java.lang.management.RuntimeMXBean runtimeMX = java.lang.management.ManagementFactory.getRuntimeMXBean(); ++ java.lang.management.OperatingSystemMXBean osMX = java.lang.management.ManagementFactory.getOperatingSystemMXBean(); ++ if (runtimeMX != null && osMX != null) { ++ String javaInfo = "Java " + runtimeMX.getSpecVersion() + " (" + runtimeMX.getVmName() + " " + runtimeMX.getVmVersion() + ")"; ++ String osInfo = "Host: " + osMX.getName() + " " + osMX.getVersion() + " (" + osMX.getArch() + ")"; ++ ++ System.out.println("System Info: " + javaInfo + " " + osInfo); ++ } else { ++ System.out.println("Unable to read system info"); ++ } ++ // Paper end - Log Java and OS versioning to help with debugging plugin issues ++ + System.out.println("Loading libraries, please wait..."); + net.minecraft.server.Main.main(options); + } catch (Throwable t) { +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index c4bf7053d83d207caca0e13e19f5c1afa7062de3..231b4e3552b17f7803815a433a5ece440c227cc6 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -19,7 +19,7 @@ public class WatchdogThread extends Thread + + private WatchdogThread(long timeoutTime, boolean restart) + { +- super( "Spigot Watchdog Thread" ); ++ super( "Paper Watchdog Thread" ); + this.timeoutTime = timeoutTime; + this.restart = restart; + } +@@ -65,14 +65,14 @@ public class WatchdogThread extends Thread + { + Logger log = Bukkit.getServer().getLogger(); + log.log( Level.SEVERE, "------------------------------" ); +- log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Spigot bug." ); ++ log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper + log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" ); + log.log( Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring" ); + log.log( Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once" ); + log.log( Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes" ); +- log.log( Level.SEVERE, "If you are unsure or still think this is a Spigot bug, please report to https://www.spigotmc.org/" ); ++ log.log( Level.SEVERE, "If you are unsure or still think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues" ); + log.log( Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports" ); +- log.log( Level.SEVERE, "Spigot version: " + Bukkit.getServer().getVersion() ); ++ log.log( Level.SEVERE, "Paper version: " + Bukkit.getServer().getVersion() ); + // + if ( net.minecraft.world.level.Level.lastPhysicsProblem != null ) + { +@@ -82,7 +82,7 @@ public class WatchdogThread extends Thread + } + // + log.log( Level.SEVERE, "------------------------------" ); +- log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Spigot!):" ); ++ log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper + WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // diff --git a/patches/server/0027-Implement-Paper-VersionChecker.patch b/patches/server/0027-Implement-Paper-VersionChecker.patch new file mode 100644 index 000000000000..11cae6752054 --- /dev/null +++ b/patches/server/0027-Implement-Paper-VersionChecker.patch @@ -0,0 +1,157 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Mon, 27 May 2019 03:40:05 -0500 +Subject: [PATCH] Implement Paper VersionChecker + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +new file mode 100644 +index 0000000000000000000000000000000000000000..22a55be34fde453fedd987173d95b8b347a03588 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +@@ -0,0 +1,129 @@ ++package com.destroystokyo.paper; ++ ++import com.destroystokyo.paper.util.VersionFetcher; ++import com.google.common.base.Charsets; ++import com.google.common.io.Resources; ++import com.google.gson.*; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.event.ClickEvent; ++import net.kyori.adventure.text.format.NamedTextColor; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++import java.io.*; ++import java.net.HttpURLConnection; ++import java.net.URL; ++import java.util.stream.StreamSupport; ++ ++public class PaperVersionFetcher implements VersionFetcher { ++ private static final java.util.regex.Pattern VER_PATTERN = java.util.regex.Pattern.compile("^([0-9\\.]*)\\-.*R"); // R is an anchor, will always give '-R' at end ++ private static final String GITHUB_BRANCH_NAME = "master"; ++ private static final String DOWNLOAD_PAGE = "https://papermc.io/downloads/paper"; ++ private static @Nullable String mcVer; ++ ++ @Override ++ public long getCacheTime() { ++ return 720000; ++ } ++ ++ @Nonnull ++ @Override ++ public Component getVersionMessage(@Nonnull String serverVersion) { ++ String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]"); ++ return getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); ++ } ++ ++ private static @Nullable String getMinecraftVersion() { ++ if (mcVer == null) { ++ java.util.regex.Matcher matcher = VER_PATTERN.matcher(org.bukkit.Bukkit.getBukkitVersion()); ++ if (matcher.find()) { ++ String result = matcher.group(); ++ mcVer = result.substring(0, result.length() - 2); // strip 'R' anchor and trailing '-' ++ } else { ++ org.bukkit.Bukkit.getLogger().warning("Unable to match version to pattern! Report to PaperMC!"); ++ org.bukkit.Bukkit.getLogger().warning("Pattern: " + VER_PATTERN.toString()); ++ org.bukkit.Bukkit.getLogger().warning("Version: " + org.bukkit.Bukkit.getBukkitVersion()); ++ } ++ } ++ ++ return mcVer; ++ } ++ ++ private static Component getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) { ++ int distance; ++ try { ++ int jenkinsBuild = Integer.parseInt(versionInfo); ++ distance = fetchDistanceFromSiteApi(jenkinsBuild, getMinecraftVersion()); ++ } catch (NumberFormatException ignored) { ++ versionInfo = versionInfo.replace("\"", ""); ++ distance = fetchDistanceFromGitHub(repo, branch, versionInfo); ++ } ++ ++ switch (distance) { ++ case -1: ++ return Component.text("Error obtaining version information", NamedTextColor.YELLOW); ++ case 0: ++ return Component.text("You are running the latest version", NamedTextColor.GREEN); ++ case -2: ++ return Component.text("Unknown version", NamedTextColor.YELLOW); ++ default: ++ return Component.text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW) ++ .append(Component.newline()) ++ .append(Component.text("Download the new version at: ") ++ .append(Component.text(DOWNLOAD_PAGE, NamedTextColor.GOLD) ++ .hoverEvent(Component.text("Click to open", NamedTextColor.WHITE)) ++ .clickEvent(ClickEvent.openUrl(DOWNLOAD_PAGE)))); ++ } ++ } ++ ++ private static int fetchDistanceFromSiteApi(int jenkinsBuild, @Nullable String siteApiVersion) { ++ if (siteApiVersion == null) { return -1; } ++ try { ++ try (BufferedReader reader = Resources.asCharSource( ++ new URL("https://api.papermc.io/v2/projects/paper/versions/" + siteApiVersion), ++ Charsets.UTF_8 ++ ).openBufferedStream()) { ++ JsonObject json = new Gson().fromJson(reader, JsonObject.class); ++ JsonArray builds = json.getAsJsonArray("builds"); ++ int latest = StreamSupport.stream(builds.spliterator(), false) ++ .mapToInt(e -> e.getAsInt()) ++ .max() ++ .getAsInt(); ++ return latest - jenkinsBuild; ++ } catch (JsonSyntaxException ex) { ++ ex.printStackTrace(); ++ return -1; ++ } ++ } catch (IOException e) { ++ e.printStackTrace(); ++ return -1; ++ } ++ } ++ ++ // Contributed by Techcable in GH-65 ++ private static int fetchDistanceFromGitHub(@Nonnull String repo, @Nonnull String branch, @Nonnull String hash) { ++ try { ++ HttpURLConnection connection = (HttpURLConnection) new URL("https://api.github.com/repos/" + repo + "/compare/" + branch + "..." + hash).openConnection(); ++ connection.connect(); ++ if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) return -2; // Unknown commit ++ try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) { ++ JsonObject obj = new Gson().fromJson(reader, JsonObject.class); ++ String status = obj.get("status").getAsString(); ++ switch (status) { ++ case "identical": ++ return 0; ++ case "behind": ++ return obj.get("behind_by").getAsInt(); ++ default: ++ return -1; ++ } ++ } catch (JsonSyntaxException | NumberFormatException e) { ++ e.printStackTrace(); ++ return -1; ++ } ++ } catch (IOException e) { ++ e.printStackTrace(); ++ return -1; ++ } ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 5e541636b216d4f46be18b1031522bd92e667b67..d11773ed44936f8836f2f8e582d5e9573af5b439 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -468,6 +468,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + public String getTimingsServerName() { + return io.papermc.paper.configuration.GlobalConfiguration.get().timings.serverName; + } ++ ++ @Override ++ public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { ++ return new com.destroystokyo.paper.PaperVersionFetcher(); ++ } + // Paper end + + /** diff --git a/patches/server/0027-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch b/patches/server/0027-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch deleted file mode 100644 index 139de649848e..000000000000 --- a/patches/server/0027-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch +++ /dev/null @@ -1,104 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Tue, 1 Mar 2016 14:32:43 -0600 -Subject: [PATCH] Show 'Paper' in client crashes, server lists, and Mojang - stats - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index c99da8c4810385600470a0c3b4c03670edd0759b..107a04f6c5889e98c183a932ad158fb5b6591a10 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1615,7 +1615,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop // CraftBukkit - cb > vanilla! -+ return "Paper"; // Paper - } - - public SystemReport fillSystemReport(SystemReport details) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 3b2f99617a519c2c2f60b88a4679064c01e5d958..9b85ec9e9a66a5bc0c98598531102f01e597f525 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -261,7 +261,7 @@ import org.yaml.snakeyaml.error.MarkedYAMLException; - import net.md_5.bungee.api.chat.BaseComponent; // Spigot - - public final class CraftServer implements Server { -- private final String serverName = "CraftBukkit"; -+ private final String serverName = "Paper"; // Paper - private final String serverVersion; - private final String bukkitVersion = Versioning.getBukkitVersion(); - private final Logger logger = Logger.getLogger("Minecraft"); -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index c312c450055965d63db0ccdee8aa8e34e7051d0b..9f4c5a6f22719ae30d88ca02a1db4a3f39957943 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -241,12 +241,25 @@ public class Main { - deadline.add(Calendar.DAY_OF_YEAR, -7); - if (buildDate.before(deadline.getTime())) { - System.err.println("*** Error, this build is outdated ***"); -- System.err.println("*** Please download a new build as per instructions from https://www.spigotmc.org/go/outdated-spigot ***"); -+ System.err.println("*** Please download a new build as per instructions from https://papermc.io/downloads/paper ***"); // Paper - System.err.println("*** Server will start in 20 seconds ***"); - Thread.sleep(TimeUnit.SECONDS.toMillis(20)); - } - } - -+ // Paper start - Log Java and OS versioning to help with debugging plugin issues -+ java.lang.management.RuntimeMXBean runtimeMX = java.lang.management.ManagementFactory.getRuntimeMXBean(); -+ java.lang.management.OperatingSystemMXBean osMX = java.lang.management.ManagementFactory.getOperatingSystemMXBean(); -+ if (runtimeMX != null && osMX != null) { -+ String javaInfo = "Java " + runtimeMX.getSpecVersion() + " (" + runtimeMX.getVmName() + " " + runtimeMX.getVmVersion() + ")"; -+ String osInfo = "Host: " + osMX.getName() + " " + osMX.getVersion() + " (" + osMX.getArch() + ")"; -+ -+ System.out.println("System Info: " + javaInfo + " " + osInfo); -+ } else { -+ System.out.println("Unable to read system info"); -+ } -+ // Paper end - Log Java and OS versioning to help with debugging plugin issues -+ - System.out.println("Loading libraries, please wait..."); - net.minecraft.server.Main.main(options); - } catch (Throwable t) { -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index c4bf7053d83d207caca0e13e19f5c1afa7062de3..231b4e3552b17f7803815a433a5ece440c227cc6 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -19,7 +19,7 @@ public class WatchdogThread extends Thread - - private WatchdogThread(long timeoutTime, boolean restart) - { -- super( "Spigot Watchdog Thread" ); -+ super( "Paper Watchdog Thread" ); - this.timeoutTime = timeoutTime; - this.restart = restart; - } -@@ -65,14 +65,14 @@ public class WatchdogThread extends Thread - { - Logger log = Bukkit.getServer().getLogger(); - log.log( Level.SEVERE, "------------------------------" ); -- log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Spigot bug." ); -+ log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper - log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" ); - log.log( Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring" ); - log.log( Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once" ); - log.log( Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes" ); -- log.log( Level.SEVERE, "If you are unsure or still think this is a Spigot bug, please report to https://www.spigotmc.org/" ); -+ log.log( Level.SEVERE, "If you are unsure or still think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues" ); - log.log( Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports" ); -- log.log( Level.SEVERE, "Spigot version: " + Bukkit.getServer().getVersion() ); -+ log.log( Level.SEVERE, "Paper version: " + Bukkit.getServer().getVersion() ); - // - if ( net.minecraft.world.level.Level.lastPhysicsProblem != null ) - { -@@ -82,7 +82,7 @@ public class WatchdogThread extends Thread - } - // - log.log( Level.SEVERE, "------------------------------" ); -- log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Spigot!):" ); -+ log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper - WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); - log.log( Level.SEVERE, "------------------------------" ); - // diff --git a/patches/server/0028-Add-version-history-to-version-command.patch b/patches/server/0028-Add-version-history-to-version-command.patch new file mode 100644 index 000000000000..53590f6fd671 --- /dev/null +++ b/patches/server/0028-Add-version-history-to-version-command.patch @@ -0,0 +1,222 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kyle Wood +Date: Thu, 1 Mar 2018 19:37:52 -0600 +Subject: [PATCH] Add version history to version command + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +index 22a55be34fde453fedd987173d95b8b347a03588..9d687da5bdf398bb3f6c84cdf1249a7213d09f2e 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java ++++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +@@ -7,6 +7,8 @@ import com.google.gson.*; + import net.kyori.adventure.text.Component; + import net.kyori.adventure.text.event.ClickEvent; + import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextDecoration; ++import net.kyori.adventure.text.TextComponent; + + import javax.annotation.Nonnull; + import javax.annotation.Nullable; +@@ -30,7 +32,10 @@ public class PaperVersionFetcher implements VersionFetcher { + @Override + public Component getVersionMessage(@Nonnull String serverVersion) { + String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]"); +- return getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); ++ final Component updateMessage = getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); ++ final Component history = getHistory(); ++ ++ return history != null ? TextComponent.ofChildren(updateMessage, Component.newline(), history) : updateMessage; + } + + private static @Nullable String getMinecraftVersion() { +@@ -126,4 +131,19 @@ public class PaperVersionFetcher implements VersionFetcher { + return -1; + } + } ++ ++ @Nullable ++ private Component getHistory() { ++ final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData(); ++ if (data == null) { ++ return null; ++ } ++ ++ final String oldVersion = data.getOldVersion(); ++ if (oldVersion == null) { ++ return null; ++ } ++ ++ return Component.text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); ++ } + } +diff --git a/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java b/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..660b2ec6b63a4ceffee44ab11f54dfa7c0d0996f +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java +@@ -0,0 +1,153 @@ ++package com.destroystokyo.paper; ++ ++import com.google.common.base.MoreObjects; ++import com.google.gson.Gson; ++import com.google.gson.JsonSyntaxException; ++import java.io.BufferedReader; ++import java.io.BufferedWriter; ++import java.io.IOException; ++import java.nio.charset.StandardCharsets; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.nio.file.Paths; ++import java.nio.file.StandardOpenOption; ++import java.util.Objects; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++import org.bukkit.Bukkit; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++ ++public enum VersionHistoryManager { ++ INSTANCE; ++ ++ private final Gson gson = new Gson(); ++ ++ private final Logger logger = Bukkit.getLogger(); ++ ++ private VersionData currentData = null; ++ ++ VersionHistoryManager() { ++ final Path path = Paths.get("version_history.json"); ++ ++ if (Files.exists(path)) { ++ // Basic file santiy checks ++ if (!Files.isRegularFile(path)) { ++ if (Files.isDirectory(path)) { ++ logger.severe(path + " is a directory, cannot be used for version history"); ++ } else { ++ logger.severe(path + " is not a regular file, cannot be used for version history"); ++ } ++ // We can't continue ++ return; ++ } ++ ++ try (final BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { ++ currentData = gson.fromJson(reader, VersionData.class); ++ } catch (final IOException e) { ++ logger.log(Level.SEVERE, "Failed to read version history file '" + path + "'", e); ++ return; ++ } catch (final JsonSyntaxException e) { ++ logger.log(Level.SEVERE, "Invalid json syntax for file '" + path + "'", e); ++ return; ++ } ++ ++ final String version = Bukkit.getVersion(); ++ if (version == null) { ++ logger.severe("Failed to retrieve current version"); ++ return; ++ } ++ ++ if (currentData == null) { ++ // Empty file ++ currentData = new VersionData(); ++ currentData.setCurrentVersion(version); ++ writeFile(path); ++ return; ++ } ++ ++ if (!version.equals(currentData.getCurrentVersion())) { ++ // The version appears to have changed ++ currentData.setOldVersion(currentData.getCurrentVersion()); ++ currentData.setCurrentVersion(version); ++ writeFile(path); ++ } ++ } else { ++ // File doesn't exist, start fresh ++ currentData = new VersionData(); ++ // oldVersion is null ++ currentData.setCurrentVersion(Bukkit.getVersion()); ++ writeFile(path); ++ } ++ } ++ ++ private void writeFile(@Nonnull final Path path) { ++ try (final BufferedWriter writer = Files.newBufferedWriter( ++ path, ++ StandardCharsets.UTF_8, ++ StandardOpenOption.WRITE, ++ StandardOpenOption.CREATE, ++ StandardOpenOption.TRUNCATE_EXISTING ++ )) { ++ gson.toJson(currentData, writer); ++ } catch (final IOException e) { ++ logger.log(Level.SEVERE, "Failed to write to version history file", e); ++ } ++ } ++ ++ @Nullable ++ public VersionData getVersionData() { ++ return currentData; ++ } ++ ++ public static class VersionData { ++ private String oldVersion; ++ ++ private String currentVersion; ++ ++ @Nullable ++ public String getOldVersion() { ++ return oldVersion; ++ } ++ ++ public void setOldVersion(@Nullable String oldVersion) { ++ this.oldVersion = oldVersion; ++ } ++ ++ @Nullable ++ public String getCurrentVersion() { ++ return currentVersion; ++ } ++ ++ public void setCurrentVersion(@Nullable String currentVersion) { ++ this.currentVersion = currentVersion; ++ } ++ ++ @Override ++ public String toString() { ++ return MoreObjects.toStringHelper(this) ++ .add("oldVersion", oldVersion) ++ .add("currentVersion", currentVersion) ++ .toString(); ++ } ++ ++ @Override ++ public boolean equals(@Nullable Object o) { ++ if (this == o) { ++ return true; ++ } ++ if (o == null || getClass() != o.getClass()) { ++ return false; ++ } ++ final VersionData versionData = (VersionData) o; ++ return Objects.equals(oldVersion, versionData.oldVersion) && ++ Objects.equals(currentVersion, versionData.currentVersion); ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hash(oldVersion, currentVersion); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 65dbd05fa904eb8f0f0ef5d3fc3c1e885771f953..88d45e52bc1105c4351ec5be39e3829ecfa8c1b6 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -190,6 +190,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + // Paper end - initialize global and world-defaults configuration + io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics ++ com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now + + this.setPvpAllowed(dedicatedserverproperties.pvp); + this.setFlightAllowed(dedicatedserverproperties.allowFlight); diff --git a/patches/server/0028-Implement-Paper-VersionChecker.patch b/patches/server/0028-Implement-Paper-VersionChecker.patch deleted file mode 100644 index 1d47f57f92cd..000000000000 --- a/patches/server/0028-Implement-Paper-VersionChecker.patch +++ /dev/null @@ -1,157 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Mon, 27 May 2019 03:40:05 -0500 -Subject: [PATCH] Implement Paper VersionChecker - - -diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -new file mode 100644 -index 0000000000000000000000000000000000000000..22a55be34fde453fedd987173d95b8b347a03588 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -@@ -0,0 +1,129 @@ -+package com.destroystokyo.paper; -+ -+import com.destroystokyo.paper.util.VersionFetcher; -+import com.google.common.base.Charsets; -+import com.google.common.io.Resources; -+import com.google.gson.*; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.event.ClickEvent; -+import net.kyori.adventure.text.format.NamedTextColor; -+ -+import javax.annotation.Nonnull; -+import javax.annotation.Nullable; -+import java.io.*; -+import java.net.HttpURLConnection; -+import java.net.URL; -+import java.util.stream.StreamSupport; -+ -+public class PaperVersionFetcher implements VersionFetcher { -+ private static final java.util.regex.Pattern VER_PATTERN = java.util.regex.Pattern.compile("^([0-9\\.]*)\\-.*R"); // R is an anchor, will always give '-R' at end -+ private static final String GITHUB_BRANCH_NAME = "master"; -+ private static final String DOWNLOAD_PAGE = "https://papermc.io/downloads/paper"; -+ private static @Nullable String mcVer; -+ -+ @Override -+ public long getCacheTime() { -+ return 720000; -+ } -+ -+ @Nonnull -+ @Override -+ public Component getVersionMessage(@Nonnull String serverVersion) { -+ String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]"); -+ return getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); -+ } -+ -+ private static @Nullable String getMinecraftVersion() { -+ if (mcVer == null) { -+ java.util.regex.Matcher matcher = VER_PATTERN.matcher(org.bukkit.Bukkit.getBukkitVersion()); -+ if (matcher.find()) { -+ String result = matcher.group(); -+ mcVer = result.substring(0, result.length() - 2); // strip 'R' anchor and trailing '-' -+ } else { -+ org.bukkit.Bukkit.getLogger().warning("Unable to match version to pattern! Report to PaperMC!"); -+ org.bukkit.Bukkit.getLogger().warning("Pattern: " + VER_PATTERN.toString()); -+ org.bukkit.Bukkit.getLogger().warning("Version: " + org.bukkit.Bukkit.getBukkitVersion()); -+ } -+ } -+ -+ return mcVer; -+ } -+ -+ private static Component getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) { -+ int distance; -+ try { -+ int jenkinsBuild = Integer.parseInt(versionInfo); -+ distance = fetchDistanceFromSiteApi(jenkinsBuild, getMinecraftVersion()); -+ } catch (NumberFormatException ignored) { -+ versionInfo = versionInfo.replace("\"", ""); -+ distance = fetchDistanceFromGitHub(repo, branch, versionInfo); -+ } -+ -+ switch (distance) { -+ case -1: -+ return Component.text("Error obtaining version information", NamedTextColor.YELLOW); -+ case 0: -+ return Component.text("You are running the latest version", NamedTextColor.GREEN); -+ case -2: -+ return Component.text("Unknown version", NamedTextColor.YELLOW); -+ default: -+ return Component.text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW) -+ .append(Component.newline()) -+ .append(Component.text("Download the new version at: ") -+ .append(Component.text(DOWNLOAD_PAGE, NamedTextColor.GOLD) -+ .hoverEvent(Component.text("Click to open", NamedTextColor.WHITE)) -+ .clickEvent(ClickEvent.openUrl(DOWNLOAD_PAGE)))); -+ } -+ } -+ -+ private static int fetchDistanceFromSiteApi(int jenkinsBuild, @Nullable String siteApiVersion) { -+ if (siteApiVersion == null) { return -1; } -+ try { -+ try (BufferedReader reader = Resources.asCharSource( -+ new URL("https://api.papermc.io/v2/projects/paper/versions/" + siteApiVersion), -+ Charsets.UTF_8 -+ ).openBufferedStream()) { -+ JsonObject json = new Gson().fromJson(reader, JsonObject.class); -+ JsonArray builds = json.getAsJsonArray("builds"); -+ int latest = StreamSupport.stream(builds.spliterator(), false) -+ .mapToInt(e -> e.getAsInt()) -+ .max() -+ .getAsInt(); -+ return latest - jenkinsBuild; -+ } catch (JsonSyntaxException ex) { -+ ex.printStackTrace(); -+ return -1; -+ } -+ } catch (IOException e) { -+ e.printStackTrace(); -+ return -1; -+ } -+ } -+ -+ // Contributed by Techcable in GH-65 -+ private static int fetchDistanceFromGitHub(@Nonnull String repo, @Nonnull String branch, @Nonnull String hash) { -+ try { -+ HttpURLConnection connection = (HttpURLConnection) new URL("https://api.github.com/repos/" + repo + "/compare/" + branch + "..." + hash).openConnection(); -+ connection.connect(); -+ if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) return -2; // Unknown commit -+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) { -+ JsonObject obj = new Gson().fromJson(reader, JsonObject.class); -+ String status = obj.get("status").getAsString(); -+ switch (status) { -+ case "identical": -+ return 0; -+ case "behind": -+ return obj.get("behind_by").getAsInt(); -+ default: -+ return -1; -+ } -+ } catch (JsonSyntaxException | NumberFormatException e) { -+ e.printStackTrace(); -+ return -1; -+ } -+ } catch (IOException e) { -+ e.printStackTrace(); -+ return -1; -+ } -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 677335c3888adc25fbf3c5ec4d5a6c5ecf58ea5d..e31ead0d99203a018757cb2e765b5d28dd373eef 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -453,6 +453,11 @@ public final class CraftMagicNumbers implements UnsafeValues { - public String getTimingsServerName() { - return io.papermc.paper.configuration.GlobalConfiguration.get().timings.serverName; - } -+ -+ @Override -+ public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { -+ return new com.destroystokyo.paper.PaperVersionFetcher(); -+ } - // Paper end - - /** diff --git a/patches/server/0029-Add-version-history-to-version-command.patch b/patches/server/0029-Add-version-history-to-version-command.patch deleted file mode 100644 index bddc3de96dc8..000000000000 --- a/patches/server/0029-Add-version-history-to-version-command.patch +++ /dev/null @@ -1,222 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Kyle Wood -Date: Thu, 1 Mar 2018 19:37:52 -0600 -Subject: [PATCH] Add version history to version command - - -diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -index 22a55be34fde453fedd987173d95b8b347a03588..9d687da5bdf398bb3f6c84cdf1249a7213d09f2e 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -@@ -7,6 +7,8 @@ import com.google.gson.*; - import net.kyori.adventure.text.Component; - import net.kyori.adventure.text.event.ClickEvent; - import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.format.TextDecoration; -+import net.kyori.adventure.text.TextComponent; - - import javax.annotation.Nonnull; - import javax.annotation.Nullable; -@@ -30,7 +32,10 @@ public class PaperVersionFetcher implements VersionFetcher { - @Override - public Component getVersionMessage(@Nonnull String serverVersion) { - String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]"); -- return getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); -+ final Component updateMessage = getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); -+ final Component history = getHistory(); -+ -+ return history != null ? TextComponent.ofChildren(updateMessage, Component.newline(), history) : updateMessage; - } - - private static @Nullable String getMinecraftVersion() { -@@ -126,4 +131,19 @@ public class PaperVersionFetcher implements VersionFetcher { - return -1; - } - } -+ -+ @Nullable -+ private Component getHistory() { -+ final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData(); -+ if (data == null) { -+ return null; -+ } -+ -+ final String oldVersion = data.getOldVersion(); -+ if (oldVersion == null) { -+ return null; -+ } -+ -+ return Component.text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); -+ } - } -diff --git a/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java b/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..660b2ec6b63a4ceffee44ab11f54dfa7c0d0996f ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java -@@ -0,0 +1,153 @@ -+package com.destroystokyo.paper; -+ -+import com.google.common.base.MoreObjects; -+import com.google.gson.Gson; -+import com.google.gson.JsonSyntaxException; -+import java.io.BufferedReader; -+import java.io.BufferedWriter; -+import java.io.IOException; -+import java.nio.charset.StandardCharsets; -+import java.nio.file.Files; -+import java.nio.file.Path; -+import java.nio.file.Paths; -+import java.nio.file.StandardOpenOption; -+import java.util.Objects; -+import java.util.logging.Level; -+import java.util.logging.Logger; -+import org.bukkit.Bukkit; -+ -+import javax.annotation.Nonnull; -+import javax.annotation.Nullable; -+ -+public enum VersionHistoryManager { -+ INSTANCE; -+ -+ private final Gson gson = new Gson(); -+ -+ private final Logger logger = Bukkit.getLogger(); -+ -+ private VersionData currentData = null; -+ -+ VersionHistoryManager() { -+ final Path path = Paths.get("version_history.json"); -+ -+ if (Files.exists(path)) { -+ // Basic file santiy checks -+ if (!Files.isRegularFile(path)) { -+ if (Files.isDirectory(path)) { -+ logger.severe(path + " is a directory, cannot be used for version history"); -+ } else { -+ logger.severe(path + " is not a regular file, cannot be used for version history"); -+ } -+ // We can't continue -+ return; -+ } -+ -+ try (final BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { -+ currentData = gson.fromJson(reader, VersionData.class); -+ } catch (final IOException e) { -+ logger.log(Level.SEVERE, "Failed to read version history file '" + path + "'", e); -+ return; -+ } catch (final JsonSyntaxException e) { -+ logger.log(Level.SEVERE, "Invalid json syntax for file '" + path + "'", e); -+ return; -+ } -+ -+ final String version = Bukkit.getVersion(); -+ if (version == null) { -+ logger.severe("Failed to retrieve current version"); -+ return; -+ } -+ -+ if (currentData == null) { -+ // Empty file -+ currentData = new VersionData(); -+ currentData.setCurrentVersion(version); -+ writeFile(path); -+ return; -+ } -+ -+ if (!version.equals(currentData.getCurrentVersion())) { -+ // The version appears to have changed -+ currentData.setOldVersion(currentData.getCurrentVersion()); -+ currentData.setCurrentVersion(version); -+ writeFile(path); -+ } -+ } else { -+ // File doesn't exist, start fresh -+ currentData = new VersionData(); -+ // oldVersion is null -+ currentData.setCurrentVersion(Bukkit.getVersion()); -+ writeFile(path); -+ } -+ } -+ -+ private void writeFile(@Nonnull final Path path) { -+ try (final BufferedWriter writer = Files.newBufferedWriter( -+ path, -+ StandardCharsets.UTF_8, -+ StandardOpenOption.WRITE, -+ StandardOpenOption.CREATE, -+ StandardOpenOption.TRUNCATE_EXISTING -+ )) { -+ gson.toJson(currentData, writer); -+ } catch (final IOException e) { -+ logger.log(Level.SEVERE, "Failed to write to version history file", e); -+ } -+ } -+ -+ @Nullable -+ public VersionData getVersionData() { -+ return currentData; -+ } -+ -+ public static class VersionData { -+ private String oldVersion; -+ -+ private String currentVersion; -+ -+ @Nullable -+ public String getOldVersion() { -+ return oldVersion; -+ } -+ -+ public void setOldVersion(@Nullable String oldVersion) { -+ this.oldVersion = oldVersion; -+ } -+ -+ @Nullable -+ public String getCurrentVersion() { -+ return currentVersion; -+ } -+ -+ public void setCurrentVersion(@Nullable String currentVersion) { -+ this.currentVersion = currentVersion; -+ } -+ -+ @Override -+ public String toString() { -+ return MoreObjects.toStringHelper(this) -+ .add("oldVersion", oldVersion) -+ .add("currentVersion", currentVersion) -+ .toString(); -+ } -+ -+ @Override -+ public boolean equals(@Nullable Object o) { -+ if (this == o) { -+ return true; -+ } -+ if (o == null || getClass() != o.getClass()) { -+ return false; -+ } -+ final VersionData versionData = (VersionData) o; -+ return Objects.equals(oldVersion, versionData.oldVersion) && -+ Objects.equals(currentVersion, versionData.currentVersion); -+ } -+ -+ @Override -+ public int hashCode() { -+ return Objects.hash(oldVersion, currentVersion); -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index dfb37ddb89c2f43d9f9a34a6b2d38616575eb39b..10bf1f9237436b68b855fb63b83a4aeeddf0f0c0 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -190,6 +190,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - // Paper end - initialize global and world-defaults configuration - io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command - com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics -+ com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now - - this.setPvpAllowed(dedicatedserverproperties.pvp); - this.setFlightAllowed(dedicatedserverproperties.allowFlight); diff --git a/patches/server/0029-Player-affects-spawning-API.patch b/patches/server/0029-Player-affects-spawning-API.patch new file mode 100644 index 000000000000..5b8a0b37ce66 --- /dev/null +++ b/patches/server/0029-Player-affects-spawning-API.patch @@ -0,0 +1,158 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jedediah Smith +Date: Tue, 1 Mar 2016 14:47:52 -0600 +Subject: [PATCH] Player affects spawning API + + +diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +index 3126e8cab3c40e3af47f4c8925e1c6a9523309ba..3207166061bf9c4d7bf3f38e5a9f7aff23ccd5c1 100644 +--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +@@ -30,6 +30,11 @@ public final class EntitySelector { + public static final Predicate CAN_BE_COLLIDED_WITH = EntitySelector.NO_SPECTATORS.and(Entity::canBeCollidedWith); + + private EntitySelector() {} ++ // Paper start - Affects Spawning API ++ public static final Predicate PLAYER_AFFECTS_SPAWNING = (entity) -> { ++ return !entity.isSpectator() && entity.isAlive() && entity instanceof Player player && player.affectsSpawning; ++ }; ++ // Paper end - Affects Spawning API + + public static Predicate withinDistance(double x, double y, double z, double max) { + double d4 = max * max; +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 1de4c45f1a3a69e63eccf063be5516b163f7882a..fa5d8a041858d17c785f033dd2aa3ab242069749 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -849,7 +849,7 @@ public abstract class Mob extends LivingEntity implements Targeting { + if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { + this.discard(); + } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { +- Player entityhuman = this.level().getNearestPlayer(this, -1.0D); ++ Player entityhuman = this.level().findNearbyPlayer(this, -1.0D, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API + + if (entityhuman != null) { + double d0 = entityhuman.distanceToSqr((Entity) this); +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java +index c7ab59aa0e2fd0f3e7252647ddb25b82ac604830..8f20239f3ef7ebe41fac8ee6e024c36dafec33c4 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java +@@ -25,7 +25,7 @@ public class SkeletonTrapGoal extends Goal { + + @Override + public boolean canUse() { +- return this.horse.level().hasNearbyAlivePlayer(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D); ++ return this.horse.level().hasNearbyAlivePlayerThatAffectsSpawning(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D); // Paper - Affects Spawning API + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java +index 96181e8925aef7f3d0a2010305caf1f6d9bcfcc9..6f452605e9dc9ebd9980eae9fdeea34417a37a88 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java +@@ -127,7 +127,7 @@ public class Silverfish extends Monster { + if (checkAnyLightMonsterSpawnRules(type, world, spawnReason, pos, random)) { + Player entityhuman = world.getNearestPlayer((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, 5.0D, true); + +- return entityhuman == null; ++ return !(entityhuman != null && !entityhuman.affectsSpawning) && entityhuman == null; // Paper - Affects Spawning API + } else { + return false; + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index a779d32f87b59f347408974e402fad22fdc47f09..15ccde8ee8bac1f70c6047464595aff6db073646 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -326,7 +326,7 @@ public class Zombie extends Monster { + + if (NaturalSpawner.isSpawnPositionOk(entitypositiontypes_surface, this.level(), blockposition, entitytypes) && SpawnPlacements.checkSpawnRules(entitytypes, worldserver, MobSpawnType.REINFORCEMENT, blockposition, this.level().random)) { + entityzombie.setPos((double) i1, (double) j1, (double) k1); +- if (!this.level().hasNearbyAlivePlayer((double) i1, (double) j1, (double) k1, 7.0D) && this.level().isUnobstructed(entityzombie) && this.level().noCollision((Entity) entityzombie) && !this.level().containsAnyLiquid(entityzombie.getBoundingBox())) { ++ if (!this.level().hasNearbyAlivePlayerThatAffectsSpawning((double) i1, (double) j1, (double) k1, 7.0D) && this.level().isUnobstructed(entityzombie) && this.level().noCollision((Entity) entityzombie) && !this.level().containsAnyLiquid(entityzombie.getBoundingBox())) { // Paper - Affects Spawning API + entityzombie.setTarget(entityliving, EntityTargetEvent.TargetReason.REINFORCEMENT_TARGET, true); // CraftBukkit + entityzombie.finalizeSpawn(worldserver, this.level().getCurrentDifficultyAt(entityzombie.blockPosition()), MobSpawnType.REINFORCEMENT, (SpawnGroupData) null, (CompoundTag) null); + worldserver.addFreshEntityWithPassengers(entityzombie, CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); // CraftBukkit +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 79f823c2ce2c5d03c34e580555a7e44a6747bf02..10c2c9d7c7feb878319eb19cd1fb6401da3b9189 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -180,6 +180,7 @@ public abstract class Player extends LivingEntity { + @Nullable + public FishingHook fishing; + protected float hurtDir; ++ public boolean affectsSpawning = true; // Paper - Affects Spawning API + + // CraftBukkit start + public boolean fauxSleeping; +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index d156f7e3430685947d2b4c30aa867e8002ca70ad..e888cf862662ae6baa6d0de8188aa74a9c6a8e00 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -56,7 +56,7 @@ public abstract class BaseSpawner { + } + + public boolean isNearPlayer(Level world, BlockPos pos) { +- return world.hasNearbyAlivePlayer((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); ++ return world.hasNearbyAlivePlayerThatAffectsSpawning((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); // Paper - Affects Spawning API + } + + public void clientTick(Level world, BlockPos pos) { +diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java +index b6a3c75b84d9f768afee33aa0f226207b307c1c0..07e7851ca27ea0f8166be52b086a6504c0deea09 100644 +--- a/src/main/java/net/minecraft/world/level/EntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +@@ -70,6 +70,11 @@ public interface EntityGetter { + } + } + ++ // Paper start - Affects Spawning API ++ default @Nullable Player findNearbyPlayer(Entity entity, double maxDistance, @Nullable Predicate predicate) { ++ return this.getNearestPlayer(entity.getX(), entity.getY(), entity.getZ(), maxDistance, predicate); ++ } ++ // Paper end - Affects Spawning API + @Nullable + default Player getNearestPlayer(double x, double y, double z, double maxDistance, @Nullable Predicate targetPredicate) { + double d = -1.0D; +@@ -99,6 +104,20 @@ public interface EntityGetter { + return this.getNearestPlayer(x, y, z, maxDistance, predicate); + } + ++ // Paper start - Affects Spawning API ++ default boolean hasNearbyAlivePlayerThatAffectsSpawning(double x, double y, double z, double range) { ++ for (Player player : this.players()) { ++ if (EntitySelector.PLAYER_AFFECTS_SPAWNING.test(player)) { // combines NO_SPECTATORS and LIVING_ENTITY_STILL_ALIVE with an "affects spawning" check ++ double distanceSqr = player.distanceToSqr(x, y, z); ++ if (range < 0.0D || distanceSqr < range * range) { ++ return true; ++ } ++ } ++ } ++ return false; ++ } ++ // Paper end - Affects Spawning API ++ + default boolean hasNearbyAlivePlayer(double x, double y, double z, double range) { + for(Player player : this.players()) { + if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index d53e15a3a70de8e2a405d3a39ff51a3551e82dbc..a592ee955a823309f1936a607823ff93822cd369 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2338,6 +2338,17 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return this.getHandle().language; + } + ++ // Paper start ++ public void setAffectsSpawning(boolean affects) { ++ this.getHandle().affectsSpawning = affects; ++ } ++ ++ @Override ++ public boolean getAffectsSpawning() { ++ return this.getHandle().affectsSpawning; ++ } ++ // Paper end ++ + @Override + public void updateCommands() { + if (this.getHandle().connection == null) return; diff --git a/patches/server/0030-Only-refresh-abilities-if-needed.patch b/patches/server/0030-Only-refresh-abilities-if-needed.patch new file mode 100644 index 000000000000..47aaec74c5e3 --- /dev/null +++ b/patches/server/0030-Only-refresh-abilities-if-needed.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 23:12:03 -0600 +Subject: [PATCH] Only refresh abilities if needed + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index a592ee955a823309f1936a607823ff93822cd369..6e47cd5cc17ad7edff3d946364485bb01bf87a41 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2010,12 +2010,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void setFlying(boolean value) { ++ boolean needsUpdate = getHandle().getAbilities().flying != value; // Paper - Only refresh abilities if needed + if (!this.getAllowFlight()) { + Preconditions.checkArgument(!value, "Player is not allowed to fly (check #getAllowFlight())"); + } + + this.getHandle().getAbilities().flying = value; +- this.getHandle().onUpdateAbilities(); ++ if (needsUpdate) this.getHandle().onUpdateAbilities(); // Paper - Only refresh abilities if needed + } + + @Override diff --git a/patches/server/0030-Player-affects-spawning-API.patch b/patches/server/0030-Player-affects-spawning-API.patch deleted file mode 100644 index f3dbfb67957f..000000000000 --- a/patches/server/0030-Player-affects-spawning-API.patch +++ /dev/null @@ -1,158 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jedediah Smith -Date: Tue, 1 Mar 2016 14:47:52 -0600 -Subject: [PATCH] Player affects spawning API - - -diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java -index 3126e8cab3c40e3af47f4c8925e1c6a9523309ba..3207166061bf9c4d7bf3f38e5a9f7aff23ccd5c1 100644 ---- a/src/main/java/net/minecraft/world/entity/EntitySelector.java -+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java -@@ -30,6 +30,11 @@ public final class EntitySelector { - public static final Predicate CAN_BE_COLLIDED_WITH = EntitySelector.NO_SPECTATORS.and(Entity::canBeCollidedWith); - - private EntitySelector() {} -+ // Paper start - Affects Spawning API -+ public static final Predicate PLAYER_AFFECTS_SPAWNING = (entity) -> { -+ return !entity.isSpectator() && entity.isAlive() && entity instanceof Player player && player.affectsSpawning; -+ }; -+ // Paper end - Affects Spawning API - - public static Predicate withinDistance(double x, double y, double z, double max) { - double d4 = max * max; -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 7cbb74f4bc7b63af86b7b2c52783fb20c7739258..3fcd93f6d5a7553b032b44e7e919838ad2120dc9 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -849,7 +849,7 @@ public abstract class Mob extends LivingEntity implements Targeting { - if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { - this.discard(); - } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { -- Player entityhuman = this.level().getNearestPlayer(this, -1.0D); -+ Player entityhuman = this.level().findNearbyPlayer(this, -1.0D, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API - - if (entityhuman != null) { - double d0 = entityhuman.distanceToSqr((Entity) this); -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java -index c7ab59aa0e2fd0f3e7252647ddb25b82ac604830..8f20239f3ef7ebe41fac8ee6e024c36dafec33c4 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java -@@ -25,7 +25,7 @@ public class SkeletonTrapGoal extends Goal { - - @Override - public boolean canUse() { -- return this.horse.level().hasNearbyAlivePlayer(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D); -+ return this.horse.level().hasNearbyAlivePlayerThatAffectsSpawning(this.horse.getX(), this.horse.getY(), this.horse.getZ(), 10.0D); // Paper - Affects Spawning API - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -index 96181e8925aef7f3d0a2010305caf1f6d9bcfcc9..6f452605e9dc9ebd9980eae9fdeea34417a37a88 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -@@ -127,7 +127,7 @@ public class Silverfish extends Monster { - if (checkAnyLightMonsterSpawnRules(type, world, spawnReason, pos, random)) { - Player entityhuman = world.getNearestPlayer((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, 5.0D, true); - -- return entityhuman == null; -+ return !(entityhuman != null && !entityhuman.affectsSpawning) && entityhuman == null; // Paper - Affects Spawning API - } else { - return false; - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index a779d32f87b59f347408974e402fad22fdc47f09..15ccde8ee8bac1f70c6047464595aff6db073646 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -326,7 +326,7 @@ public class Zombie extends Monster { - - if (NaturalSpawner.isSpawnPositionOk(entitypositiontypes_surface, this.level(), blockposition, entitytypes) && SpawnPlacements.checkSpawnRules(entitytypes, worldserver, MobSpawnType.REINFORCEMENT, blockposition, this.level().random)) { - entityzombie.setPos((double) i1, (double) j1, (double) k1); -- if (!this.level().hasNearbyAlivePlayer((double) i1, (double) j1, (double) k1, 7.0D) && this.level().isUnobstructed(entityzombie) && this.level().noCollision((Entity) entityzombie) && !this.level().containsAnyLiquid(entityzombie.getBoundingBox())) { -+ if (!this.level().hasNearbyAlivePlayerThatAffectsSpawning((double) i1, (double) j1, (double) k1, 7.0D) && this.level().isUnobstructed(entityzombie) && this.level().noCollision((Entity) entityzombie) && !this.level().containsAnyLiquid(entityzombie.getBoundingBox())) { // Paper - Affects Spawning API - entityzombie.setTarget(entityliving, EntityTargetEvent.TargetReason.REINFORCEMENT_TARGET, true); // CraftBukkit - entityzombie.finalizeSpawn(worldserver, this.level().getCurrentDifficultyAt(entityzombie.blockPosition()), MobSpawnType.REINFORCEMENT, (SpawnGroupData) null, (CompoundTag) null); - worldserver.addFreshEntityWithPassengers(entityzombie, CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); // CraftBukkit -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 eed6481bc88ed15a5e3fe7056b545ab44ed81983..f8d3e195c094ff200c0a7bd8cd4829ef36d328da 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -179,6 +179,7 @@ public abstract class Player extends LivingEntity { - @Nullable - public FishingHook fishing; - protected float hurtDir; -+ public boolean affectsSpawning = true; // Paper - Affects Spawning API - - // CraftBukkit start - public boolean fauxSleeping; -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index d156f7e3430685947d2b4c30aa867e8002ca70ad..e888cf862662ae6baa6d0de8188aa74a9c6a8e00 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -56,7 +56,7 @@ public abstract class BaseSpawner { - } - - public boolean isNearPlayer(Level world, BlockPos pos) { -- return world.hasNearbyAlivePlayer((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); -+ return world.hasNearbyAlivePlayerThatAffectsSpawning((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); // Paper - Affects Spawning API - } - - public void clientTick(Level world, BlockPos pos) { -diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index b6a3c75b84d9f768afee33aa0f226207b307c1c0..07e7851ca27ea0f8166be52b086a6504c0deea09 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -70,6 +70,11 @@ public interface EntityGetter { - } - } - -+ // Paper start - Affects Spawning API -+ default @Nullable Player findNearbyPlayer(Entity entity, double maxDistance, @Nullable Predicate predicate) { -+ return this.getNearestPlayer(entity.getX(), entity.getY(), entity.getZ(), maxDistance, predicate); -+ } -+ // Paper end - Affects Spawning API - @Nullable - default Player getNearestPlayer(double x, double y, double z, double maxDistance, @Nullable Predicate targetPredicate) { - double d = -1.0D; -@@ -99,6 +104,20 @@ public interface EntityGetter { - return this.getNearestPlayer(x, y, z, maxDistance, predicate); - } - -+ // Paper start - Affects Spawning API -+ default boolean hasNearbyAlivePlayerThatAffectsSpawning(double x, double y, double z, double range) { -+ for (Player player : this.players()) { -+ if (EntitySelector.PLAYER_AFFECTS_SPAWNING.test(player)) { // combines NO_SPECTATORS and LIVING_ENTITY_STILL_ALIVE with an "affects spawning" check -+ double distanceSqr = player.distanceToSqr(x, y, z); -+ if (range < 0.0D || distanceSqr < range * range) { -+ return true; -+ } -+ } -+ } -+ return false; -+ } -+ // Paper end - Affects Spawning API -+ - default boolean hasNearbyAlivePlayer(double x, double y, double z, double range) { - for(Player player : this.players()) { - if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index afdc702682af7ddf338fe00a1b1912766e728f41..aafab28ed643e9f5f4b28a12ecd7bde3bc993abf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2287,6 +2287,17 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - return this.getHandle().language; - } - -+ // Paper start -+ public void setAffectsSpawning(boolean affects) { -+ this.getHandle().affectsSpawning = affects; -+ } -+ -+ @Override -+ public boolean getAffectsSpawning() { -+ return this.getHandle().affectsSpawning; -+ } -+ // Paper end -+ - @Override - public void updateCommands() { - if (this.getHandle().connection == null) return; diff --git a/patches/server/0031-Only-refresh-abilities-if-needed.patch b/patches/server/0031-Only-refresh-abilities-if-needed.patch deleted file mode 100644 index 58032170df66..000000000000 --- a/patches/server/0031-Only-refresh-abilities-if-needed.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Tue, 1 Mar 2016 23:12:03 -0600 -Subject: [PATCH] Only refresh abilities if needed - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index aafab28ed643e9f5f4b28a12ecd7bde3bc993abf..8b7f6ac3214d318bff41779ae80c48ce3ecd54ce 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1959,12 +1959,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void setFlying(boolean value) { -+ boolean needsUpdate = getHandle().getAbilities().flying != value; // Paper - Only refresh abilities if needed - if (!this.getAllowFlight()) { - Preconditions.checkArgument(!value, "Player is not allowed to fly (check #getAllowFlight())"); - } - - this.getHandle().getAbilities().flying = value; -- this.getHandle().onUpdateAbilities(); -+ if (needsUpdate) this.getHandle().onUpdateAbilities(); // Paper - Only refresh abilities if needed - } - - @Override diff --git a/patches/server/0031-fix-ItemMeta-removing-CustomModelData.patch b/patches/server/0031-fix-ItemMeta-removing-CustomModelData.patch new file mode 100644 index 000000000000..e42dfffc097b --- /dev/null +++ b/patches/server/0031-fix-ItemMeta-removing-CustomModelData.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 2 Jan 2024 10:35:46 -0800 +Subject: [PATCH] fix ItemMeta removing CustomModelData + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 2157034e735c3921d4ef128688c30917aaad7161..ffdea312f93d00289364ef4d41a820cd1338f3bd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -353,7 +353,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + +- if (tag.contains(CraftMetaItem.CUSTOM_MODEL_DATA.NBT, CraftMagicNumbers.NBT.TAG_INT)) { ++ if (tag.contains(CraftMetaItem.CUSTOM_MODEL_DATA.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER)) { // Paper - correctly allow any number type + this.customModelData = tag.getInt(CraftMetaItem.CUSTOM_MODEL_DATA.NBT); + } + if (tag.contains(CraftMetaItem.BLOCK_DATA.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND)) { diff --git a/patches/server/0032-Entity-Origin-API.patch b/patches/server/0032-Entity-Origin-API.patch new file mode 100644 index 000000000000..b5fb102df6db --- /dev/null +++ b/patches/server/0032-Entity-Origin-API.patch @@ -0,0 +1,120 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Tue, 1 Mar 2016 23:45:08 -0600 +Subject: [PATCH] Entity Origin API + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 73608abb5a39749c326ce6fe1bf014422941b2d1..1315822ce37e5011880b24afb4fbe18cd1dfe80f 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2149,6 +2149,15 @@ public class ServerLevel extends Level implements WorldGenLevel { + entity.updateDynamicGameEventListener(DynamicGameEventListener::add); + entity.inWorld = true; // CraftBukkit - Mark entity as in world + entity.valid = true; // CraftBukkit ++ // Paper start - Entity origin API ++ if (entity.getOriginVector() == null) { ++ entity.setOrigin(entity.getBukkitEntity().getLocation()); ++ } ++ // Default to current world if unknown, gross assumption but entities rarely change world ++ if (entity.getOriginWorld() == null) { ++ entity.setOrigin(entity.getOriginVector().toLocation(getWorld())); ++ } ++ // Paper end - Entity origin API + } + + public void onTrackingEnd(Entity entity) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 0ecdc0d671f744d85072ca0d157ef37bb28bab9c..1212943cf64a716ceae187c76ff9be6241595ba2 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -319,7 +319,27 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + public long activatedTick = Integer.MIN_VALUE; + public void inactiveTick() { } + // Spigot end ++ // Paper start - Entity origin API ++ @javax.annotation.Nullable ++ private org.bukkit.util.Vector origin; ++ @javax.annotation.Nullable ++ private UUID originWorld; + ++ public void setOrigin(@javax.annotation.Nonnull Location location) { ++ this.origin = location.toVector(); ++ this.originWorld = location.getWorld().getUID(); ++ } ++ ++ @javax.annotation.Nullable ++ public org.bukkit.util.Vector getOriginVector() { ++ return this.origin != null ? this.origin.clone() : null; ++ } ++ ++ @javax.annotation.Nullable ++ public UUID getOriginWorld() { ++ return this.originWorld; ++ } ++ // Paper end - Entity origin API + public float getBukkitYaw() { + return this.yRot; + } +@@ -2042,6 +2062,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + this.bukkitEntity.storeBukkitValues(nbttagcompound); + } + // CraftBukkit end ++ // Paper start ++ if (this.origin != null) { ++ UUID originWorld = this.originWorld != null ? this.originWorld : this.level != null ? this.level.getWorld().getUID() : null; ++ if (originWorld != null) { ++ nbttagcompound.putUUID("Paper.OriginWorld", originWorld); ++ } ++ nbttagcompound.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ())); ++ } ++ // Paper end + return nbttagcompound; + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT"); +@@ -2169,6 +2198,20 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + // CraftBukkit end + ++ // Paper start ++ ListTag originTag = nbt.getList("Paper.Origin", net.minecraft.nbt.Tag.TAG_DOUBLE); ++ if (!originTag.isEmpty()) { ++ UUID originWorld = null; ++ if (nbt.contains("Paper.OriginWorld")) { ++ originWorld = nbt.getUUID("Paper.OriginWorld"); ++ } else if (this.level != null) { ++ originWorld = this.level.getWorld().getUID(); ++ } ++ this.originWorld = originWorld; ++ origin = new org.bukkit.util.Vector(originTag.getDouble(0), originTag.getDouble(1), originTag.getDouble(2)); ++ } ++ // Paper end ++ + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being loaded"); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index e269812e6193492afc3f25612edafa1a58325fa3..49294a8d580d891f21d8d4cbae14ae477c01ff8d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -967,5 +967,20 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + + return ret; + } ++ ++ @Override ++ public Location getOrigin() { ++ Vector originVector = this.getHandle().getOriginVector(); ++ if (originVector == null) { ++ return null; ++ } ++ World world = this.getWorld(); ++ if (this.getHandle().getOriginWorld() != null) { ++ world = org.bukkit.Bukkit.getWorld(this.getHandle().getOriginWorld()); ++ } ++ ++ //noinspection ConstantConditions ++ return originVector.toLocation(world); ++ } + // Paper end + } diff --git a/patches/server/0032-fix-ItemMeta-removing-CustomModelData.patch b/patches/server/0032-fix-ItemMeta-removing-CustomModelData.patch deleted file mode 100644 index 5d5438460cf5..000000000000 --- a/patches/server/0032-fix-ItemMeta-removing-CustomModelData.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 2 Jan 2024 10:35:46 -0800 -Subject: [PATCH] fix ItemMeta removing CustomModelData - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index aad1bfb515556ed98a55b181057d3ebb178ee52d..9e11d7bccd04b4c2e6913de56a299190c22aa9c1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -353,7 +353,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - } - } - -- if (tag.contains(CraftMetaItem.CUSTOM_MODEL_DATA.NBT, CraftMagicNumbers.NBT.TAG_INT)) { -+ if (tag.contains(CraftMetaItem.CUSTOM_MODEL_DATA.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER)) { // Paper - correctly allow any number type - this.customModelData = tag.getInt(CraftMetaItem.CUSTOM_MODEL_DATA.NBT); - } - if (tag.contains(CraftMetaItem.BLOCK_DATA.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND)) { diff --git a/patches/server/0033-Entity-Origin-API.patch b/patches/server/0033-Entity-Origin-API.patch deleted file mode 100644 index ec30ee3e45f0..000000000000 --- a/patches/server/0033-Entity-Origin-API.patch +++ /dev/null @@ -1,120 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Byteflux -Date: Tue, 1 Mar 2016 23:45:08 -0600 -Subject: [PATCH] Entity Origin API - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index d00b789d8deb0163726acbcb10edb0965ac9f326..5b304c0c701a74398ba46ef8766a3d707bbe6a07 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2149,6 +2149,15 @@ public class ServerLevel extends Level implements WorldGenLevel { - entity.updateDynamicGameEventListener(DynamicGameEventListener::add); - entity.inWorld = true; // CraftBukkit - Mark entity as in world - entity.valid = true; // CraftBukkit -+ // Paper start - Entity origin API -+ if (entity.getOriginVector() == null) { -+ entity.setOrigin(entity.getBukkitEntity().getLocation()); -+ } -+ // Default to current world if unknown, gross assumption but entities rarely change world -+ if (entity.getOriginWorld() == null) { -+ entity.setOrigin(entity.getOriginVector().toLocation(getWorld())); -+ } -+ // Paper end - Entity origin API - } - - public void onTrackingEnd(Entity entity) { -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 8811646495f37587e7976edd8b9558cda412edb1..b58b5b5fa710dc453966f7ff0dea6ac16a88c99d 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -319,7 +319,27 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - public long activatedTick = Integer.MIN_VALUE; - public void inactiveTick() { } - // Spigot end -+ // Paper start - Entity origin API -+ @javax.annotation.Nullable -+ private org.bukkit.util.Vector origin; -+ @javax.annotation.Nullable -+ private UUID originWorld; - -+ public void setOrigin(@javax.annotation.Nonnull Location location) { -+ this.origin = location.toVector(); -+ this.originWorld = location.getWorld().getUID(); -+ } -+ -+ @javax.annotation.Nullable -+ public org.bukkit.util.Vector getOriginVector() { -+ return this.origin != null ? this.origin.clone() : null; -+ } -+ -+ @javax.annotation.Nullable -+ public UUID getOriginWorld() { -+ return this.originWorld; -+ } -+ // Paper end - Entity origin API - public float getBukkitYaw() { - return this.yRot; - } -@@ -2043,6 +2063,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - this.bukkitEntity.storeBukkitValues(nbttagcompound); - } - // CraftBukkit end -+ // Paper start -+ if (this.origin != null) { -+ UUID originWorld = this.originWorld != null ? this.originWorld : this.level != null ? this.level.getWorld().getUID() : null; -+ if (originWorld != null) { -+ nbttagcompound.putUUID("Paper.OriginWorld", originWorld); -+ } -+ nbttagcompound.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ())); -+ } -+ // Paper end - return nbttagcompound; - } catch (Throwable throwable) { - CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT"); -@@ -2170,6 +2199,20 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - // CraftBukkit end - -+ // Paper start -+ ListTag originTag = nbt.getList("Paper.Origin", net.minecraft.nbt.Tag.TAG_DOUBLE); -+ if (!originTag.isEmpty()) { -+ UUID originWorld = null; -+ if (nbt.contains("Paper.OriginWorld")) { -+ originWorld = nbt.getUUID("Paper.OriginWorld"); -+ } else if (this.level != null) { -+ originWorld = this.level.getWorld().getUID(); -+ } -+ this.originWorld = originWorld; -+ origin = new org.bukkit.util.Vector(originTag.getDouble(0), originTag.getDouble(1), originTag.getDouble(2)); -+ } -+ // Paper end -+ - } catch (Throwable throwable) { - CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT"); - CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Entity being loaded"); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index e269812e6193492afc3f25612edafa1a58325fa3..49294a8d580d891f21d8d4cbae14ae477c01ff8d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -967,5 +967,20 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - - return ret; - } -+ -+ @Override -+ public Location getOrigin() { -+ Vector originVector = this.getHandle().getOriginVector(); -+ if (originVector == null) { -+ return null; -+ } -+ World world = this.getWorld(); -+ if (this.getHandle().getOriginWorld() != null) { -+ world = org.bukkit.Bukkit.getWorld(this.getHandle().getOriginWorld()); -+ } -+ -+ //noinspection ConstantConditions -+ return originVector.toLocation(world); -+ } - // Paper end - } diff --git a/patches/server/0034-Prevent-block-entity-and-entity-crashes.patch b/patches/server/0033-Prevent-block-entity-and-entity-crashes.patch similarity index 100% rename from patches/server/0034-Prevent-block-entity-and-entity-crashes.patch rename to patches/server/0033-Prevent-block-entity-and-entity-crashes.patch diff --git a/patches/server/0034-Configurable-top-of-nether-void-damage.patch b/patches/server/0034-Configurable-top-of-nether-void-damage.patch new file mode 100644 index 000000000000..e8fedeba3f69 --- /dev/null +++ b/patches/server/0034-Configurable-top-of-nether-void-damage.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 1 Mar 2016 23:58:50 -0600 +Subject: [PATCH] Configurable top of nether void damage + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 1212943cf64a716ceae187c76ff9be6241595ba2..3577829c2386779913f4d2aebd1825051da2d364 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -670,7 +670,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + public void checkBelowWorld() { +- if (this.getY() < (double) (this.level().getMinBuildHeight() - 64)) { ++ // Paper start - Configurable nether ceiling damage ++ if (this.getY() < (double) (this.level.getMinBuildHeight() - 64) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER ++ && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v) ++ && (!(this instanceof Player player) || !player.getAbilities().invulnerable))) { ++ // Paper end - Configurable nether ceiling damage + this.onBelowWorld(); + } + +diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +index 02e3322ec41108fe9275510e2daa833d180353dc..550b7bc694d861c084769265f6c49c4d44033296 100644 +--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java ++++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +@@ -55,7 +55,7 @@ public class PortalForcer { + Optional optional = villageplace.getInSquare((holder) -> { + return holder.is(PoiTypes.NETHER_PORTAL); + }, blockposition, i, PoiManager.Occupancy.ANY).filter((villageplacerecord) -> { +- return worldborder.isWithinBounds(villageplacerecord.getPos()); ++ return worldborder.isWithinBounds(villageplacerecord.getPos()) && !(this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> villageplacerecord.getPos().getY() >= v)); // Paper - Configurable nether ceiling damage + }).sorted(Comparator.comparingDouble((PoiRecord villageplacerecord) -> { // CraftBukkit - decompile error + return villageplacerecord.getPos().distSqr(blockposition); + }).thenComparingInt((villageplacerecord) -> { +@@ -90,6 +90,11 @@ public class PortalForcer { + BlockPos blockposition2 = null; + WorldBorder worldborder = this.level.getWorldBorder(); + int i = Math.min(this.level.getMaxBuildHeight(), this.level.getMinBuildHeight() + this.level.getLogicalHeight()) - 1; ++ // Paper start - Configurable nether ceiling damage; make sure the max height doesn't exceed the void damage height ++ if (this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.enabled()) { ++ i = Math.min(i, this.level.paperConfig().environment.netherCeilingVoidDamageHeight.intValue() - 1); ++ } ++ // Paper end - Configurable nether ceiling damage + BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable(); + Iterator iterator = BlockPos.spiralAround(blockposition, createRadius, Direction.EAST, Direction.SOUTH).iterator(); // CraftBukkit + diff --git a/patches/server/0035-Check-online-mode-before-converting-and-renaming-pla.patch b/patches/server/0035-Check-online-mode-before-converting-and-renaming-pla.patch new file mode 100644 index 000000000000..d6d157d97344 --- /dev/null +++ b/patches/server/0035-Check-online-mode-before-converting-and-renaming-pla.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 2 Mar 2016 00:03:55 -0600 +Subject: [PATCH] Check online mode before converting and renaming player data + + +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 8292978e1386c55d99241c3ee2ead3440b9e2570..2a167a0131d866b4368fc30849c17acdf0ab9af0 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -60,7 +60,7 @@ public class PlayerDataStorage { + File file = new File(this.playerDir, player.getStringUUID() + ".dat"); + // Spigot Start + boolean usingWrongFile = false; +- if ( !file.exists() ) ++ if ( org.bukkit.Bukkit.getOnlineMode() && !file.exists() ) // Paper - Check online mode first + { + file = new File( this.playerDir, java.util.UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + player.getScoreboardName() ).getBytes( "UTF-8" ) ).toString() + ".dat"); + if ( file.exists() ) diff --git a/patches/server/0035-Configurable-top-of-nether-void-damage.patch b/patches/server/0035-Configurable-top-of-nether-void-damage.patch deleted file mode 100644 index 778ca74fbf3d..000000000000 --- a/patches/server/0035-Configurable-top-of-nether-void-damage.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Tue, 1 Mar 2016 23:58:50 -0600 -Subject: [PATCH] Configurable top of nether void damage - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index b58b5b5fa710dc453966f7ff0dea6ac16a88c99d..8e4f8849acfede8eec04527b67a3405ae805633d 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -670,7 +670,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - public void checkBelowWorld() { -- if (this.getY() < (double) (this.level().getMinBuildHeight() - 64)) { -+ // Paper start - Configurable nether ceiling damage -+ if (this.getY() < (double) (this.level.getMinBuildHeight() - 64) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER -+ && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v) -+ && (!(this instanceof Player player) || !player.getAbilities().invulnerable))) { -+ // Paper end - Configurable nether ceiling damage - this.onBelowWorld(); - } - -diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -index 02e3322ec41108fe9275510e2daa833d180353dc..550b7bc694d861c084769265f6c49c4d44033296 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -@@ -55,7 +55,7 @@ public class PortalForcer { - Optional optional = villageplace.getInSquare((holder) -> { - return holder.is(PoiTypes.NETHER_PORTAL); - }, blockposition, i, PoiManager.Occupancy.ANY).filter((villageplacerecord) -> { -- return worldborder.isWithinBounds(villageplacerecord.getPos()); -+ return worldborder.isWithinBounds(villageplacerecord.getPos()) && !(this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> villageplacerecord.getPos().getY() >= v)); // Paper - Configurable nether ceiling damage - }).sorted(Comparator.comparingDouble((PoiRecord villageplacerecord) -> { // CraftBukkit - decompile error - return villageplacerecord.getPos().distSqr(blockposition); - }).thenComparingInt((villageplacerecord) -> { -@@ -90,6 +90,11 @@ public class PortalForcer { - BlockPos blockposition2 = null; - WorldBorder worldborder = this.level.getWorldBorder(); - int i = Math.min(this.level.getMaxBuildHeight(), this.level.getMinBuildHeight() + this.level.getLogicalHeight()) - 1; -+ // Paper start - Configurable nether ceiling damage; make sure the max height doesn't exceed the void damage height -+ if (this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.enabled()) { -+ i = Math.min(i, this.level.paperConfig().environment.netherCeilingVoidDamageHeight.intValue() - 1); -+ } -+ // Paper end - Configurable nether ceiling damage - BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable(); - Iterator iterator = BlockPos.spiralAround(blockposition, createRadius, Direction.EAST, Direction.SOUTH).iterator(); // CraftBukkit - diff --git a/patches/server/0037-Always-tick-falling-blocks.patch b/patches/server/0036-Always-tick-falling-blocks.patch similarity index 100% rename from patches/server/0037-Always-tick-falling-blocks.patch rename to patches/server/0036-Always-tick-falling-blocks.patch diff --git a/patches/server/0036-Check-online-mode-before-converting-and-renaming-pla.patch b/patches/server/0036-Check-online-mode-before-converting-and-renaming-pla.patch deleted file mode 100644 index 5d700a45742d..000000000000 --- a/patches/server/0036-Check-online-mode-before-converting-and-renaming-pla.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Wed, 2 Mar 2016 00:03:55 -0600 -Subject: [PATCH] Check online mode before converting and renaming player data - - -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 3d74ffc6e38b0dbe7ca6d8d84a63f78d6b1908a7..08f4cc47ec3aa4dd6980ba543219891a510b010b 100644 ---- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -+++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -@@ -60,7 +60,7 @@ public class PlayerDataStorage { - File file = new File(this.playerDir, player.getStringUUID() + ".dat"); - // Spigot Start - boolean usingWrongFile = false; -- if ( !file.exists() ) -+ if ( org.bukkit.Bukkit.getOnlineMode() && !file.exists() ) // Paper - Check online mode first - { - file = new File( this.playerDir, java.util.UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + player.getScoreboardName() ).getBytes( "UTF-8" ) ).toString() + ".dat"); - if ( file.exists() ) diff --git a/patches/server/0037-Configurable-end-credits.patch b/patches/server/0037-Configurable-end-credits.patch new file mode 100644 index 000000000000..d1085599d140 --- /dev/null +++ b/patches/server/0037-Configurable-end-credits.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: DoctorDark +Date: Wed, 16 Mar 2016 02:21:39 -0500 +Subject: [PATCH] Configurable end credits + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 27a1450696a633578cd44567f240cbc1a4c578ac..3bb3312da2b91616d9d3bb4cb79259ee9e3479bd 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1030,6 +1030,7 @@ public class ServerPlayer extends Player { + this.unRide(); + this.serverLevel().removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); + if (!this.wonGame) { ++ if (this.level().paperConfig().misc.disableEndCredits) this.seenCredits = true; // Paper - Option to disable end credits + this.wonGame = true; + this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, this.seenCredits ? 0.0F : 1.0F)); + this.seenCredits = true; diff --git a/patches/server/0038-Configurable-end-credits.patch b/patches/server/0038-Configurable-end-credits.patch deleted file mode 100644 index e20735f6d61e..000000000000 --- a/patches/server/0038-Configurable-end-credits.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: DoctorDark -Date: Wed, 16 Mar 2016 02:21:39 -0500 -Subject: [PATCH] Configurable end credits - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 61d4afb6b76fdffdda9f01af5005f005e21f4807..c1006a1991e294fa69b1eb35f4224154b064671b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1030,6 +1030,7 @@ public class ServerPlayer extends Player { - this.unRide(); - this.serverLevel().removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); - if (!this.wonGame) { -+ if (this.level().paperConfig().misc.disableEndCredits) this.seenCredits = true; // Paper - Option to disable end credits - this.wonGame = true; - this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, this.seenCredits ? 0.0F : 1.0F)); - this.seenCredits = true; diff --git a/patches/server/0038-Fix-lag-from-explosions-processing-dead-entities.patch b/patches/server/0038-Fix-lag-from-explosions-processing-dead-entities.patch new file mode 100644 index 000000000000..fb7366543b1c --- /dev/null +++ b/patches/server/0038-Fix-lag-from-explosions-processing-dead-entities.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Iceee +Date: Wed, 2 Mar 2016 01:39:52 -0600 +Subject: [PATCH] Fix lag from explosions processing dead entities + + +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index f8dafdf67447c9627f677fca7160e212239709c4..b600abae1c17e1196ff2b171e054db95cca5c9ff 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -225,7 +225,7 @@ public class Explosion { + int i1 = Mth.floor(this.y + (double) f2 + 1.0D); + int j1 = Mth.floor(this.z - (double) f2 - 1.0D); + int k1 = Mth.floor(this.z + (double) f2 + 1.0D); +- List list = this.level.getEntities(this.source, new AABB((double) i, (double) l, (double) j1, (double) j, (double) i1, (double) k1)); ++ List list = this.level.getEntities(this.source, new AABB((double) i, (double) l, (double) j1, (double) j, (double) i1, (double) k1), (com.google.common.base.Predicate) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities + Vec3 vec3d = new Vec3(this.x, this.y, this.z); + Iterator iterator = list.iterator(); + diff --git a/patches/server/0039-Fix-lag-from-explosions-processing-dead-entities.patch b/patches/server/0039-Fix-lag-from-explosions-processing-dead-entities.patch deleted file mode 100644 index 305920cc5511..000000000000 --- a/patches/server/0039-Fix-lag-from-explosions-processing-dead-entities.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Iceee -Date: Wed, 2 Mar 2016 01:39:52 -0600 -Subject: [PATCH] Fix lag from explosions processing dead entities - - -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index 725ea53e6143d29f9619e1045341bc30155b248e..c5d644876e0c92a444a574ee7571227e19b28482 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -225,7 +225,7 @@ public class Explosion { - int i1 = Mth.floor(this.y + (double) f2 + 1.0D); - int j1 = Mth.floor(this.z - (double) f2 - 1.0D); - int k1 = Mth.floor(this.z + (double) f2 + 1.0D); -- List list = this.level.getEntities(this.source, new AABB((double) i, (double) l, (double) j1, (double) j, (double) i1, (double) k1)); -+ List list = this.level.getEntities(this.source, new AABB((double) i, (double) l, (double) j1, (double) j, (double) i1, (double) k1), (com.google.common.base.Predicate) entity -> entity.isAlive() && !entity.isSpectator()); // Paper - Fix lag from explosions processing dead entities - Vec3 vec3d = new Vec3(this.x, this.y, this.z); - Iterator iterator = list.iterator(); - diff --git a/patches/server/0039-Optimize-explosions.patch b/patches/server/0039-Optimize-explosions.patch new file mode 100644 index 000000000000..9434cb4e986c --- /dev/null +++ b/patches/server/0039-Optimize-explosions.patch @@ -0,0 +1,133 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Wed, 2 Mar 2016 11:59:48 -0600 +Subject: [PATCH] Optimize explosions + +The process of determining an entity's exposure from explosions can be +expensive when there are hundreds or more entities in range. + +This patch adds a per-tick cache that is used for storing and retrieving +an entity's exposure during an explosion. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index c6ce55d48fc71ea097a4a279fcd0dd1d4086be9b..cc2775d94f3b6ebd7f97e14e324bf292d5874de4 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1489,6 +1489,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop>> 32)); ++ temp = Double.doubleToLongBits(posY); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(posZ); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(minX); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(minY); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(minZ); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(maxX); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(maxY); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(maxZ); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ return result; ++ } ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index dfe8eea789a6dd5476da0c194236dba3868a2cb7..7ead83d46e7bb3ee3586acf695b08f197f61f04a 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -170,6 +170,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + private org.spigotmc.TickLimiter entityLimiter; + private org.spigotmc.TickLimiter tileLimiter; + private int tileTickPosition; ++ public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions + + public CraftWorld getWorld() { + return this.world; diff --git a/patches/server/0040-Disable-explosion-knockback.patch b/patches/server/0040-Disable-explosion-knockback.patch new file mode 100644 index 000000000000..880de82afd78 --- /dev/null +++ b/patches/server/0040-Disable-explosion-knockback.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sudzzy +Date: Wed, 2 Mar 2016 14:48:03 -0600 +Subject: [PATCH] Disable explosion knockback + + +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index df17b8cddd7f8bd3f13641c99ad0d25e8a596675..00cfa26783ce0772c75166266ead258a415097bc 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -283,7 +283,7 @@ public class Explosion { + if (entity instanceof LivingEntity) { + LivingEntity entityliving = (LivingEntity) entity; + +- d13 = ProtectionEnchantment.getExplosionKnockbackAfterDampener(entityliving, d12); ++ d13 = entity instanceof Player && level.paperConfig().environment.disableExplosionKnockback ? 0 : ProtectionEnchantment.getExplosionKnockbackAfterDampener(entityliving, d12); // Paper - Option to disable explosion knockback + } else { + d13 = d12; + } +@@ -305,7 +305,7 @@ public class Explosion { + if (entity instanceof Player) { + Player entityhuman = (Player) entity; + +- if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying)) { ++ if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying) && !level.paperConfig().environment.disableExplosionKnockback) { // Paper - Option to disable explosion knockback + this.hitPlayers.put(entityhuman, vec3d1); + } + } diff --git a/patches/server/0040-Optimize-explosions.patch b/patches/server/0040-Optimize-explosions.patch deleted file mode 100644 index 7ecbc0cda475..000000000000 --- a/patches/server/0040-Optimize-explosions.patch +++ /dev/null @@ -1,133 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Byteflux -Date: Wed, 2 Mar 2016 11:59:48 -0600 -Subject: [PATCH] Optimize explosions - -The process of determining an entity's exposure from explosions can be -expensive when there are hundreds or more entities in range. - -This patch adds a per-tick cache that is used for storing and retrieving -an entity's exposure during an explosion. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 107a04f6c5889e98c183a932ad158fb5b6591a10..455f72f2a653154d4528c53d39866d3cd85b6862 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1489,6 +1489,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop>> 32)); -+ temp = Double.doubleToLongBits(posY); -+ result = 31 * result + (int) (temp ^ (temp >>> 32)); -+ temp = Double.doubleToLongBits(posZ); -+ result = 31 * result + (int) (temp ^ (temp >>> 32)); -+ temp = Double.doubleToLongBits(minX); -+ result = 31 * result + (int) (temp ^ (temp >>> 32)); -+ temp = Double.doubleToLongBits(minY); -+ result = 31 * result + (int) (temp ^ (temp >>> 32)); -+ temp = Double.doubleToLongBits(minZ); -+ result = 31 * result + (int) (temp ^ (temp >>> 32)); -+ temp = Double.doubleToLongBits(maxX); -+ result = 31 * result + (int) (temp ^ (temp >>> 32)); -+ temp = Double.doubleToLongBits(maxY); -+ result = 31 * result + (int) (temp ^ (temp >>> 32)); -+ temp = Double.doubleToLongBits(maxZ); -+ result = 31 * result + (int) (temp ^ (temp >>> 32)); -+ return result; -+ } -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index dfe8eea789a6dd5476da0c194236dba3868a2cb7..7ead83d46e7bb3ee3586acf695b08f197f61f04a 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -170,6 +170,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - private org.spigotmc.TickLimiter entityLimiter; - private org.spigotmc.TickLimiter tileLimiter; - private int tileTickPosition; -+ public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions - - public CraftWorld getWorld() { - return this.world; diff --git a/patches/server/0041-Disable-explosion-knockback.patch b/patches/server/0041-Disable-explosion-knockback.patch deleted file mode 100644 index 68b32b3813ce..000000000000 --- a/patches/server/0041-Disable-explosion-knockback.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Sudzzy -Date: Wed, 2 Mar 2016 14:48:03 -0600 -Subject: [PATCH] Disable explosion knockback - - -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index 4e382cfae7a3a1889bc36bad24c6ea38b7eb14a6..219f3abc6d3280077b53cfff97db7e724133f5a1 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -285,7 +285,7 @@ public class Explosion { - if (entity instanceof LivingEntity) { - LivingEntity entityliving = (LivingEntity) entity; - -- d13 = ProtectionEnchantment.getExplosionKnockbackAfterDampener(entityliving, d12); -+ d13 = entity instanceof Player && level.paperConfig().environment.disableExplosionKnockback ? 0 : ProtectionEnchantment.getExplosionKnockbackAfterDampener(entityliving, d12); // Paper - Option to disable explosion knockback - } else { - d13 = d12; - } -@@ -299,7 +299,7 @@ public class Explosion { - if (entity instanceof Player) { - Player entityhuman = (Player) entity; - -- if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying)) { -+ if (!entityhuman.isSpectator() && (!entityhuman.isCreative() || !entityhuman.getAbilities().flying) && !level.paperConfig().environment.disableExplosionKnockback) { // Paper - Option to disable explosion knockback - this.hitPlayers.put(entityhuman, vec3d1); - } - } diff --git a/patches/server/0041-Disable-thunder.patch b/patches/server/0041-Disable-thunder.patch new file mode 100644 index 000000000000..812330feaa0a --- /dev/null +++ b/patches/server/0041-Disable-thunder.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sudzzy +Date: Wed, 2 Mar 2016 14:52:43 -0600 +Subject: [PATCH] Disable thunder + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 1315822ce37e5011880b24afb4fbe18cd1dfe80f..ae4f429ccacc01e3f970fadf01570ec7da454721 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -609,7 +609,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + ProfilerFiller gameprofilerfiller = this.getProfiler(); + + gameprofilerfiller.push("thunder"); +- if (flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot ++ if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder + BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); + + if (this.isRainingAt(blockposition)) { diff --git a/patches/server/0042-Disable-ice-and-snow.patch b/patches/server/0042-Disable-ice-and-snow.patch new file mode 100644 index 000000000000..b67a99ca1132 --- /dev/null +++ b/patches/server/0042-Disable-ice-and-snow.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sudzzy +Date: Wed, 2 Mar 2016 14:57:24 -0600 +Subject: [PATCH] Disable ice and snow + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index ae4f429ccacc01e3f970fadf01570ec7da454721..c9113f7e0d9e1b9861f667c40e2702c6bb1d4e53 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -639,11 +639,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + + gameprofilerfiller.popPush("iceandsnow"); + ++ if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow + for (int l = 0; l < randomTickSpeed; ++l) { + if (this.random.nextInt(48) == 0) { + this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15)); + } + } ++ } // Paper - Option to disable ice and snow + + gameprofilerfiller.popPush("tickBlocks"); + timings.chunkTicksBlocks.startTiming(); // Paper diff --git a/patches/server/0042-Disable-thunder.patch b/patches/server/0042-Disable-thunder.patch deleted file mode 100644 index ac0e1ec07294..000000000000 --- a/patches/server/0042-Disable-thunder.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Sudzzy -Date: Wed, 2 Mar 2016 14:52:43 -0600 -Subject: [PATCH] Disable thunder - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 5b304c0c701a74398ba46ef8766a3d707bbe6a07..40287f5dbdfb5526f4cba901ded9740009e29b00 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -609,7 +609,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - ProfilerFiller gameprofilerfiller = this.getProfiler(); - - gameprofilerfiller.push("thunder"); -- if (flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot -+ if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder - BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); - - if (this.isRainingAt(blockposition)) { diff --git a/patches/server/0044-Configurable-mob-spawner-tick-rate.patch b/patches/server/0043-Configurable-mob-spawner-tick-rate.patch similarity index 100% rename from patches/server/0044-Configurable-mob-spawner-tick-rate.patch rename to patches/server/0043-Configurable-mob-spawner-tick-rate.patch diff --git a/patches/server/0043-Disable-ice-and-snow.patch b/patches/server/0043-Disable-ice-and-snow.patch deleted file mode 100644 index 4ec16311b0af..000000000000 --- a/patches/server/0043-Disable-ice-and-snow.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Sudzzy -Date: Wed, 2 Mar 2016 14:57:24 -0600 -Subject: [PATCH] Disable ice and snow - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 40287f5dbdfb5526f4cba901ded9740009e29b00..4b6fc197c6e0544f2ec993ea863e9dd560b05f11 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -639,11 +639,13 @@ public class ServerLevel extends Level implements WorldGenLevel { - - gameprofilerfiller.popPush("iceandsnow"); - -+ if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow - for (int l = 0; l < randomTickSpeed; ++l) { - if (this.random.nextInt(48) == 0) { - this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15)); - } - } -+ } // Paper - Option to disable ice and snow - - gameprofilerfiller.popPush("tickBlocks"); - timings.chunkTicksBlocks.startTiming(); // Paper diff --git a/patches/server/0044-Implement-PlayerLocaleChangeEvent.patch b/patches/server/0044-Implement-PlayerLocaleChangeEvent.patch new file mode 100644 index 000000000000..fa3a977c1000 --- /dev/null +++ b/patches/server/0044-Implement-PlayerLocaleChangeEvent.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Isaac Moore +Date: Tue, 19 Apr 2016 14:09:31 -0500 +Subject: [PATCH] Implement PlayerLocaleChangeEvent + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 3bb3312da2b91616d9d3bb4cb79259ee9e3479bd..f21174c1d50158330b9a48dba8409919a2229927 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -222,7 +222,7 @@ public class ServerPlayer extends Player { + private int levitationStartTime; + private boolean disconnected; + private int requestedViewDistance; +- public String language = "en_us"; // CraftBukkit - default ++ public String language = null; // CraftBukkit - default // Paper - default to null + public java.util.Locale adventure$locale = java.util.Locale.US; // Paper + @Nullable + private Vec3 startingToFallPosition; +@@ -272,7 +272,7 @@ public class ServerPlayer extends Player { + this.lastActionTime = Util.getMillis(); + this.recipeBook = new ServerRecipeBook(); + this.requestedViewDistance = 2; +- this.language = "en_us"; ++ this.language = null; // Paper - default to null + this.lastSectionPos = SectionPos.of(0, 0, 0); + this.chunkTrackingView = ChunkTrackingView.EMPTY; + this.respawnDimension = Level.OVERWORLD; +@@ -1920,9 +1920,10 @@ public class ServerPlayer extends Player { + PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), this.getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); + this.server.server.getPluginManager().callEvent(event); + } +- if (!this.language.equals(clientOptions.language())) { ++ if (this.language == null || !this.language.equals(clientOptions.language())) { // Paper + PlayerLocaleChangeEvent event = new PlayerLocaleChangeEvent(this.getBukkitEntity(), clientOptions.language()); + this.server.server.getPluginManager().callEvent(event); ++ this.server.server.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerLocaleChangeEvent(this.getBukkitEntity(), this.language, clientOptions.language())); // Paper + } + // CraftBukkit end + this.language = clientOptions.language(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 6e47cd5cc17ad7edff3d946364485bb01bf87a41..42b27e1b8bc317a4cd0fcc27a5d7ce2d69d33aeb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2336,7 +2336,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public String getLocale() { +- return this.getHandle().language; ++ // Paper start - Locale change event ++ final String locale = this.getHandle().language; ++ return locale != null ? locale : "en_us"; ++ // Paper end + } + + // Paper start diff --git a/patches/server/0046-Add-BeaconEffectEvent.patch b/patches/server/0045-Add-BeaconEffectEvent.patch similarity index 100% rename from patches/server/0046-Add-BeaconEffectEvent.patch rename to patches/server/0045-Add-BeaconEffectEvent.patch diff --git a/patches/server/0045-Implement-PlayerLocaleChangeEvent.patch b/patches/server/0045-Implement-PlayerLocaleChangeEvent.patch deleted file mode 100644 index 914926f06ecb..000000000000 --- a/patches/server/0045-Implement-PlayerLocaleChangeEvent.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Isaac Moore -Date: Tue, 19 Apr 2016 14:09:31 -0500 -Subject: [PATCH] Implement PlayerLocaleChangeEvent - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index c1006a1991e294fa69b1eb35f4224154b064671b..b0a548ed0abbe08df2cae771248a811fba3891da 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -222,7 +222,7 @@ public class ServerPlayer extends Player { - private int levitationStartTime; - private boolean disconnected; - private int requestedViewDistance; -- public String language = "en_us"; // CraftBukkit - default -+ public String language = null; // CraftBukkit - default // Paper - default to null - public java.util.Locale adventure$locale = java.util.Locale.US; // Paper - @Nullable - private Vec3 startingToFallPosition; -@@ -272,7 +272,7 @@ public class ServerPlayer extends Player { - this.lastActionTime = Util.getMillis(); - this.recipeBook = new ServerRecipeBook(); - this.requestedViewDistance = 2; -- this.language = "en_us"; -+ this.language = null; // Paper - default to null - this.lastSectionPos = SectionPos.of(0, 0, 0); - this.chunkTrackingView = ChunkTrackingView.EMPTY; - this.respawnDimension = Level.OVERWORLD; -@@ -1920,9 +1920,10 @@ public class ServerPlayer extends Player { - PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), this.getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); - this.server.server.getPluginManager().callEvent(event); - } -- if (!this.language.equals(clientOptions.language())) { -+ if (this.language == null || !this.language.equals(clientOptions.language())) { // Paper - PlayerLocaleChangeEvent event = new PlayerLocaleChangeEvent(this.getBukkitEntity(), clientOptions.language()); - this.server.server.getPluginManager().callEvent(event); -+ this.server.server.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerLocaleChangeEvent(this.getBukkitEntity(), this.language, clientOptions.language())); // Paper - } - // CraftBukkit end - this.language = clientOptions.language(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 8b7f6ac3214d318bff41779ae80c48ce3ecd54ce..3eb1a36eb171d0b98ae005a61902274ef4b12b6e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2285,7 +2285,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public String getLocale() { -- return this.getHandle().language; -+ // Paper start - Locale change event -+ final String locale = this.getHandle().language; -+ return locale != null ? locale : "en_us"; -+ // Paper end - } - - // Paper start diff --git a/patches/server/0046-Configurable-container-update-tick-rate.patch b/patches/server/0046-Configurable-container-update-tick-rate.patch new file mode 100644 index 000000000000..7a2ea2cb5110 --- /dev/null +++ b/patches/server/0046-Configurable-container-update-tick-rate.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sudzzy +Date: Wed, 2 Mar 2016 23:34:44 -0600 +Subject: [PATCH] Configurable container update tick rate + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index f21174c1d50158330b9a48dba8409919a2229927..429f3ed00c0c6d7c89138aa9a4b770e3e68b7ed7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -247,6 +247,7 @@ public class ServerPlayer extends Player { + private RemoteChatSession chatSession; + private int containerCounter; + public boolean wonGame; ++ private int containerUpdateDelay; // Paper - Configurable container update tick rate + + // CraftBukkit start + public String displayName; +@@ -639,7 +640,12 @@ public class ServerPlayer extends Player { + --this.invulnerableTime; + } + +- this.containerMenu.broadcastChanges(); ++ // Paper start - Configurable container update tick rate ++ if (--containerUpdateDelay <= 0) { ++ this.containerMenu.broadcastChanges(); ++ containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate; ++ } ++ // Paper end - Configurable container update tick rate + if (!this.level().isClientSide && !this.containerMenu.stillValid(this)) { + this.closeContainer(); + this.containerMenu = this.inventoryMenu; diff --git a/patches/server/0047-Configurable-container-update-tick-rate.patch b/patches/server/0047-Configurable-container-update-tick-rate.patch deleted file mode 100644 index 15658964ad6d..000000000000 --- a/patches/server/0047-Configurable-container-update-tick-rate.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Sudzzy -Date: Wed, 2 Mar 2016 23:34:44 -0600 -Subject: [PATCH] Configurable container update tick rate - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index b0a548ed0abbe08df2cae771248a811fba3891da..7e777ab6998203e031fb8387b1521bff3d86f11a 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -247,6 +247,7 @@ public class ServerPlayer extends Player { - private RemoteChatSession chatSession; - private int containerCounter; - public boolean wonGame; -+ private int containerUpdateDelay; // Paper - Configurable container update tick rate - - // CraftBukkit start - public String displayName; -@@ -639,7 +640,12 @@ public class ServerPlayer extends Player { - --this.invulnerableTime; - } - -- this.containerMenu.broadcastChanges(); -+ // Paper start - Configurable container update tick rate -+ if (--containerUpdateDelay <= 0) { -+ this.containerMenu.broadcastChanges(); -+ containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate; -+ } -+ // Paper end - Configurable container update tick rate - if (!this.level().isClientSide && !this.containerMenu.stillValid(this)) { - this.closeContainer(); - this.containerMenu = this.inventoryMenu; diff --git a/patches/server/0048-Use-UserCache-for-player-heads.patch b/patches/server/0047-Use-UserCache-for-player-heads.patch similarity index 100% rename from patches/server/0048-Use-UserCache-for-player-heads.patch rename to patches/server/0047-Use-UserCache-for-player-heads.patch diff --git a/patches/server/0049-Disable-spigot-tick-limiters.patch b/patches/server/0048-Disable-spigot-tick-limiters.patch similarity index 100% rename from patches/server/0049-Disable-spigot-tick-limiters.patch rename to patches/server/0048-Disable-spigot-tick-limiters.patch diff --git a/patches/server/0050-Add-PlayerInitialSpawnEvent.patch b/patches/server/0049-Add-PlayerInitialSpawnEvent.patch similarity index 100% rename from patches/server/0050-Add-PlayerInitialSpawnEvent.patch rename to patches/server/0049-Add-PlayerInitialSpawnEvent.patch diff --git a/patches/server/0051-Configurable-Disabling-Cat-Chest-Detection.patch b/patches/server/0050-Configurable-Disabling-Cat-Chest-Detection.patch similarity index 100% rename from patches/server/0051-Configurable-Disabling-Cat-Chest-Detection.patch rename to patches/server/0050-Configurable-Disabling-Cat-Chest-Detection.patch diff --git a/patches/server/0051-Improve-Player-chat-API-handling.patch b/patches/server/0051-Improve-Player-chat-API-handling.patch new file mode 100644 index 000000000000..80ec033d0404 --- /dev/null +++ b/patches/server/0051-Improve-Player-chat-API-handling.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 3 Mar 2016 01:17:12 -0600 +Subject: [PATCH] Improve Player chat API handling + +Properly split up the chat and command handling to reflect the server now +having separate packets for both, and the client always using the correct packet. Text +from a chat packet should never be parsed into a command, even if it starts with the `/` +character. + +Add a missing async catcher and improve Spigot's async catcher error message. + +== AT == +public net.minecraft.server.network.ServerGamePacketListenerImpl isChatMessageIllegal(Ljava/lang/String;)Z + +Co-authored-by: Jake Potrebic +Co-authored-by: SoSeDiK + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 01c70d149b306030c775427f744c4dfab19411cf..dedd0aa332412eec5f976959b67da5405d980ded 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1930,7 +1930,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + OutgoingChatMessage outgoing = OutgoingChatMessage.create(original); + +- if (!async && s.startsWith("/")) { ++ if (false && !async && s.startsWith("/")) { // Paper - Don't handle commands in chat logic + this.handleCommand(s); + } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { + // Do nothing, this is coming from a plugin +@@ -2017,7 +2017,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + } + +- private void handleCommand(String s) { ++ public void handleCommand(String s) { // Paper - private -> public ++ org.spigotmc.AsyncCatcher.catchOp("Command Dispatched Async: " + s); // Paper - Add async catcher + co.aikar.timings.MinecraftTimings.playerCommandTimer.startTiming(); // Paper + if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot + this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 56a63adc3c0c919594c3f2646d4cf5b86b5c6f1e..5b22714d6f5eb8318275ca0bf01597be8d55f118 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -897,7 +897,7 @@ public final class CraftServer implements Server { + public boolean dispatchCommand(CommandSender sender, String commandLine) { + Preconditions.checkArgument(sender != null, "sender cannot be null"); + Preconditions.checkArgument(commandLine != null, "commandLine cannot be null"); +- org.spigotmc.AsyncCatcher.catchOp("command dispatch"); // Spigot ++ org.spigotmc.AsyncCatcher.catchOp("Command Dispatched Async: " + commandLine); // Spigot // Paper - Include command in error message + + if (this.commandMap.dispatch(sender, commandLine)) { + return true; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 42b27e1b8bc317a4cd0fcc27a5d7ce2d69d33aeb..6f3c450cd03fa4848175cada63be2dcf5abe45ba 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -462,7 +462,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + if (this.getHandle().connection == null) return; + +- this.getHandle().connection.chat(msg, PlayerChatMessage.system(msg), false); ++ // Paper start - Improve chat handling ++ if (ServerGamePacketListenerImpl.isChatMessageIllegal(msg)) { ++ this.getHandle().connection.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); ++ } else { ++ if (msg.startsWith("/")) { ++ this.getHandle().connection.handleCommand(msg); ++ } else { ++ final PlayerChatMessage playerChatMessage = PlayerChatMessage.system(msg).withResult(new net.minecraft.network.chat.ChatDecorator.ModernResult(Component.literal(msg), true, false)); ++ // TODO chat decorating ++ // TODO text filtering ++ this.getHandle().connection.chat(msg, playerChatMessage, false); ++ } ++ } ++ // Paper end - Improve chat handling + } + + @Override diff --git a/patches/server/0052-All-chunks-are-slime-spawn-chunks-toggle.patch b/patches/server/0052-All-chunks-are-slime-spawn-chunks-toggle.patch new file mode 100644 index 000000000000..a1e73b56c2b2 --- /dev/null +++ b/patches/server/0052-All-chunks-are-slime-spawn-chunks-toggle.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: vemacs +Date: Thu, 3 Mar 2016 01:19:22 -0600 +Subject: [PATCH] All chunks are slime spawn chunks toggle + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java +index 8b2206c5c56341ecd96837bdb3e0c6ab7c874af5..897c815fb448d1e9ca75b7f8b93b4021dccf7596 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Slime.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java +@@ -338,7 +338,7 @@ public class Slime extends Mob implements Enemy { + } + + ChunkPos chunkcoordintpair = new ChunkPos(pos); +- boolean flag = WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot ++ boolean flag = world.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper + + if (random.nextInt(10) == 0 && flag && pos.getY() < 40) { + return checkMobSpawnRules(type, world, spawnReason, pos, random); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index d09f0f68031135f86a864e69eb8d65ceddd7a9b1..70999e95116d50de1a7fecdd91bbad0bac2bf1d8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -217,7 +217,7 @@ public class CraftChunk implements Chunk { + @Override + public boolean isSlimeChunk() { + // 987234911L is deterimined in EntitySlime when seeing if a slime can spawn in a chunk +- return WorldgenRandom.seedSlimeChunk(this.getX(), this.getZ(), this.getWorld().getSeed(), this.worldServer.spigotConfig.slimeSeed).nextInt(10) == 0; ++ return this.worldServer.paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(this.getX(), this.getZ(), this.getWorld().getSeed(), worldServer.spigotConfig.slimeSeed).nextInt(10) == 0; // Paper + } + + @Override diff --git a/patches/server/0052-Improve-Player-chat-API-handling.patch b/patches/server/0052-Improve-Player-chat-API-handling.patch deleted file mode 100644 index 5de45ed559d5..000000000000 --- a/patches/server/0052-Improve-Player-chat-API-handling.patch +++ /dev/null @@ -1,80 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 3 Mar 2016 01:17:12 -0600 -Subject: [PATCH] Improve Player chat API handling - -Properly split up the chat and command handling to reflect the server now -having separate packets for both, and the client always using the correct packet. Text -from a chat packet should never be parsed into a command, even if it starts with the `/` -character. - -Add a missing async catcher and improve Spigot's async catcher error message. - -== AT == -public net.minecraft.server.network.ServerGamePacketListenerImpl isChatMessageIllegal(Ljava/lang/String;)Z - -Co-authored-by: Jake Potrebic -Co-authored-by: SoSeDiK - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index ce7caf08865df9ff032ba6c42308ea3ce4de6226..04e68886a5385234701962f2502948052074515e 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1930,7 +1930,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - OutgoingChatMessage outgoing = OutgoingChatMessage.create(original); - -- if (!async && s.startsWith("/")) { -+ if (false && !async && s.startsWith("/")) { // Paper - Don't handle commands in chat logic - this.handleCommand(s); - } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { - // Do nothing, this is coming from a plugin -@@ -2017,7 +2017,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - } - -- private void handleCommand(String s) { -+ public void handleCommand(String s) { // Paper - private -> public -+ org.spigotmc.AsyncCatcher.catchOp("Command Dispatched Async: " + s); // Paper - Add async catcher - co.aikar.timings.MinecraftTimings.playerCommandTimer.startTiming(); // Paper - if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot - this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 4fbc98bc2fe808a15f974b0abf614781fe66e1c4..6568af2428be41c6d8baa8cf2a486ec4942d44d7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -885,7 +885,7 @@ public final class CraftServer implements Server { - public boolean dispatchCommand(CommandSender sender, String commandLine) { - Preconditions.checkArgument(sender != null, "sender cannot be null"); - Preconditions.checkArgument(commandLine != null, "commandLine cannot be null"); -- org.spigotmc.AsyncCatcher.catchOp("command dispatch"); // Spigot -+ org.spigotmc.AsyncCatcher.catchOp("Command Dispatched Async: " + commandLine); // Spigot // Paper - Include command in error message - - if (this.commandMap.dispatch(sender, commandLine)) { - return true; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index ce54892f4a9b745779613af769783cb388336103..0017679b115de12f2e272db7ba3c11315086680f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -456,7 +456,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - if (this.getHandle().connection == null) return; - -- this.getHandle().connection.chat(msg, PlayerChatMessage.system(msg), false); -+ // Paper start - Improve chat handling -+ if (ServerGamePacketListenerImpl.isChatMessageIllegal(msg)) { -+ this.getHandle().connection.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); -+ } else { -+ if (msg.startsWith("/")) { -+ this.getHandle().connection.handleCommand(msg); -+ } else { -+ final PlayerChatMessage playerChatMessage = PlayerChatMessage.system(msg).withResult(new net.minecraft.network.chat.ChatDecorator.ModernResult(Component.literal(msg), true, false)); -+ // TODO chat decorating -+ // TODO text filtering -+ this.getHandle().connection.chat(msg, playerChatMessage, false); -+ } -+ } -+ // Paper end - Improve chat handling - } - - @Override diff --git a/patches/server/0053-All-chunks-are-slime-spawn-chunks-toggle.patch b/patches/server/0053-All-chunks-are-slime-spawn-chunks-toggle.patch deleted file mode 100644 index 55c1f1b35bcf..000000000000 --- a/patches/server/0053-All-chunks-are-slime-spawn-chunks-toggle.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: vemacs -Date: Thu, 3 Mar 2016 01:19:22 -0600 -Subject: [PATCH] All chunks are slime spawn chunks toggle - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java -index 8b2206c5c56341ecd96837bdb3e0c6ab7c874af5..897c815fb448d1e9ca75b7f8b93b4021dccf7596 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java -@@ -338,7 +338,7 @@ public class Slime extends Mob implements Enemy { - } - - ChunkPos chunkcoordintpair = new ChunkPos(pos); -- boolean flag = WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot -+ boolean flag = world.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper - - if (random.nextInt(10) == 0 && flag && pos.getY() < 40) { - return checkMobSpawnRules(type, world, spawnReason, pos, random); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index b1aeb021e53a233bfb0439d38f1a889ed6fc301d..491416754e1c5e8c2b345b57f45289906c7932ba 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -215,7 +215,7 @@ public class CraftChunk implements Chunk { - @Override - public boolean isSlimeChunk() { - // 987234911L is deterimined in EntitySlime when seeing if a slime can spawn in a chunk -- return WorldgenRandom.seedSlimeChunk(this.getX(), this.getZ(), this.getWorld().getSeed(), this.worldServer.spigotConfig.slimeSeed).nextInt(10) == 0; -+ return this.worldServer.paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(this.getX(), this.getZ(), this.getWorld().getSeed(), worldServer.spigotConfig.slimeSeed).nextInt(10) == 0; // Paper - } - - @Override diff --git a/patches/server/0053-Expose-server-CommandMap.patch b/patches/server/0053-Expose-server-CommandMap.patch new file mode 100644 index 000000000000..9e0239735d22 --- /dev/null +++ b/patches/server/0053-Expose-server-CommandMap.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Thu, 3 Mar 2016 02:15:57 -0600 +Subject: [PATCH] Expose server CommandMap + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 5b22714d6f5eb8318275ca0bf01597be8d55f118..6135e8c0669886260afacdaa7405bf55597b17a6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2124,6 +2124,7 @@ public final class CraftServer implements Server { + return this.helpMap; + } + ++ @Override // Paper - add override + public SimpleCommandMap getCommandMap() { + return this.commandMap; + } diff --git a/patches/server/0054-Be-a-bit-more-informative-in-maxHealth-exception.patch b/patches/server/0054-Be-a-bit-more-informative-in-maxHealth-exception.patch new file mode 100644 index 000000000000..aadcfbc397be --- /dev/null +++ b/patches/server/0054-Be-a-bit-more-informative-in-maxHealth-exception.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Thu, 3 Mar 2016 02:18:39 -0600 +Subject: [PATCH] Be a bit more informative in maxHealth exception + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index bcab4757869428e20c67907684e02b64d2958e88..a73a5a8291ddd954f2c7b73a4895933e715c5d68 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -102,7 +102,12 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + @Override + public void setHealth(double health) { + health = (float) health; +- Preconditions.checkArgument(health >= 0 && health <= this.getMaxHealth(), "Health value (%s) must be between 0 and %s", health, this.getMaxHealth()); ++ // Paper start - Be more informative ++ Preconditions.checkArgument(health >= 0 && health <= this.getMaxHealth(), ++ "Health value (%s) must be between 0 and %s. (attribute base value: %s%s)", ++ health, this.getMaxHealth(), this.getHandle().getAttribute(Attributes.MAX_HEALTH).getBaseValue(), this instanceof CraftPlayer ? ", player: " + this.getName() : "" ++ ); ++ // Paper end + + // during world generation, we don't want to run logic for dropping items and xp + if (this.getHandle().generation && health == 0) { diff --git a/patches/server/0054-Expose-server-CommandMap.patch b/patches/server/0054-Expose-server-CommandMap.patch deleted file mode 100644 index 03a0a271b530..000000000000 --- a/patches/server/0054-Expose-server-CommandMap.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kashike -Date: Thu, 3 Mar 2016 02:15:57 -0600 -Subject: [PATCH] Expose server CommandMap - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 6568af2428be41c6d8baa8cf2a486ec4942d44d7..669e75549d0cc1d9c506f362e27b2f1717ec8d5c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2112,6 +2112,7 @@ public final class CraftServer implements Server { - return this.helpMap; - } - -+ @Override // Paper - add override - public SimpleCommandMap getCommandMap() { - return this.commandMap; - } diff --git a/patches/server/0055-Be-a-bit-more-informative-in-maxHealth-exception.patch b/patches/server/0055-Be-a-bit-more-informative-in-maxHealth-exception.patch deleted file mode 100644 index 46b700c3222b..000000000000 --- a/patches/server/0055-Be-a-bit-more-informative-in-maxHealth-exception.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kashike -Date: Thu, 3 Mar 2016 02:18:39 -0600 -Subject: [PATCH] Be a bit more informative in maxHealth exception - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index bad29a542e31a6fe2c75bfd008653ca6aa409df8..5825f942db3b9870631ff093708dee0e930fccc8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -101,7 +101,12 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - @Override - public void setHealth(double health) { - health = (float) health; -- Preconditions.checkArgument(health >= 0 && health <= this.getMaxHealth(), "Health value (%s) must be between 0 and %s", health, this.getMaxHealth()); -+ // Paper start - Be more informative -+ Preconditions.checkArgument(health >= 0 && health <= this.getMaxHealth(), -+ "Health value (%s) must be between 0 and %s. (attribute base value: %s%s)", -+ health, this.getMaxHealth(), this.getHandle().getAttribute(Attributes.MAX_HEALTH).getBaseValue(), this instanceof CraftPlayer ? ", player: " + this.getName() : "" -+ ); -+ // Paper end - - // during world generation, we don't want to run logic for dropping items and xp - if (this.getHandle().generation && health == 0) { diff --git a/patches/server/0055-Player-Tab-List-and-Title-APIs.patch b/patches/server/0055-Player-Tab-List-and-Title-APIs.patch new file mode 100644 index 000000000000..ff7e5579f017 --- /dev/null +++ b/patches/server/0055-Player-Tab-List-and-Title-APIs.patch @@ -0,0 +1,177 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Techcable +Date: Thu, 3 Mar 2016 02:32:10 -0600 +Subject: [PATCH] Player Tab List and Title APIs + + +diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +index 53f033d2d887909f5f905c00122d1b09809e5e3c..b9dd91927cb259789ad71b2241024c5fa2586d57 100644 +--- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java ++++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java +@@ -545,6 +545,12 @@ public class FriendlyByteBuf extends ByteBuf { + return this.writeWithCodec(NbtOps.INSTANCE, ComponentSerialization.localizedCodec(this.adventure$locale), text); + // Paper end - adventure; support writing adventure components directly and server-side translations + } ++ // Paper start - deprecated Tab List & Title APIs ++ @Deprecated ++ public FriendlyByteBuf writeComponent(final net.md_5.bungee.api.chat.BaseComponent[] component) { ++ return this.writeComponent(java.util.Objects.requireNonNull(Component.Serializer.fromJson(net.md_5.bungee.chat.ComponentSerializer.toString(component)))); ++ } ++ // Paper end - deprecated Tab List & Title APIs + + public > T readEnum(Class enumClass) { + return ((T[]) enumClass.getEnumConstants())[this.readVarInt()]; // CraftBukkit - fix decompile error +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetSubtitleTextPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetSubtitleTextPacket.java +index e77bd5bb66279f579fad4fdcc8b0606410922e9e..4c05fee93eb20044a4198c43d5ae1eebebaabaf7 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetSubtitleTextPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetSubtitleTextPacket.java +@@ -7,6 +7,7 @@ import net.minecraft.network.protocol.Packet; + public class ClientboundSetSubtitleTextPacket implements Packet { + private final Component text; + public net.kyori.adventure.text.Component adventure$text; // Paper ++ public net.md_5.bungee.api.chat.BaseComponent[] components; // Paper + + public ClientboundSetSubtitleTextPacket(Component subtitle) { + this.text = subtitle; +@@ -21,6 +22,8 @@ public class ClientboundSetSubtitleTextPacket implements Packet { + private final Component text; + public net.kyori.adventure.text.Component adventure$text; // Paper ++ public net.md_5.bungee.api.chat.BaseComponent[] components; // Paper + + public ClientboundSetTitleTextPacket(Component title) { + this.text = title; +@@ -21,6 +22,8 @@ public class ClientboundSetTitleTextPacket implements Packet +Date: Thu, 3 Mar 2016 02:46:17 -0600 +Subject: [PATCH] Add configurable portal search radius + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 3577829c2386779913f4d2aebd1825051da2d364..4c7a2d64ab4a5269fdea0b30e6c91fa1cb9f509c 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3179,7 +3179,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + double d0 = DimensionType.getTeleportationScale(this.level().dimensionType(), destination.dimensionType()); + BlockPos blockposition = worldborder.clampToBounds(this.getX() * d0, this.getY(), this.getZ() * d0); + // CraftBukkit start +- CraftPortalEvent event = this.callPortalEvent(this, destination, new Vec3(blockposition.getX(), blockposition.getY(), blockposition.getZ()), PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, flag2 ? 16 : 128, 16); ++ // Paper start - Configurable portal search radius ++ int portalSearchRadius = destination.paperConfig().environment.portalSearchRadius; ++ if (level.paperConfig().environment.portalSearchVanillaDimensionScaling && flag2) { // == THE_NETHER ++ portalSearchRadius = (int) (portalSearchRadius / destination.dimensionType().coordinateScale()); ++ } ++ // Paper end - Configurable portal search radius ++ CraftPortalEvent event = this.callPortalEvent(this, destination, new Vec3(blockposition.getX(), blockposition.getY(), blockposition.getZ()), PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, portalSearchRadius, destination.paperConfig().environment.portalCreateRadius); // Paper start - configurable portal radius + if (event == null) { + return null; + } +diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +index 550b7bc694d861c084769265f6c49c4d44033296..afdd4ecbff21e2172b390bcbdf74f3c1bbddafcc 100644 +--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java ++++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +@@ -43,7 +43,7 @@ public class PortalForcer { + + public Optional findPortalAround(BlockPos pos, boolean destIsNether, WorldBorder worldBorder) { + // CraftBukkit start +- return this.findPortalAround(pos, worldBorder, destIsNether ? 16 : 128); // Search Radius ++ return this.findPortalAround(pos, worldBorder, destIsNether ? level.paperConfig().environment.portalCreateRadius : level.paperConfig().environment.portalSearchRadius); // Search Radius // Paper - Configurable portal search radius + } + + public Optional findPortalAround(BlockPos blockposition, WorldBorder worldborder, int i) { diff --git a/patches/server/0056-Player-Tab-List-and-Title-APIs.patch b/patches/server/0056-Player-Tab-List-and-Title-APIs.patch deleted file mode 100644 index 8776c8880aa9..000000000000 --- a/patches/server/0056-Player-Tab-List-and-Title-APIs.patch +++ /dev/null @@ -1,177 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Techcable -Date: Thu, 3 Mar 2016 02:32:10 -0600 -Subject: [PATCH] Player Tab List and Title APIs - - -diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java -index 53f033d2d887909f5f905c00122d1b09809e5e3c..b9dd91927cb259789ad71b2241024c5fa2586d57 100644 ---- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java -+++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java -@@ -545,6 +545,12 @@ public class FriendlyByteBuf extends ByteBuf { - return this.writeWithCodec(NbtOps.INSTANCE, ComponentSerialization.localizedCodec(this.adventure$locale), text); - // Paper end - adventure; support writing adventure components directly and server-side translations - } -+ // Paper start - deprecated Tab List & Title APIs -+ @Deprecated -+ public FriendlyByteBuf writeComponent(final net.md_5.bungee.api.chat.BaseComponent[] component) { -+ return this.writeComponent(java.util.Objects.requireNonNull(Component.Serializer.fromJson(net.md_5.bungee.chat.ComponentSerializer.toString(component)))); -+ } -+ // Paper end - deprecated Tab List & Title APIs - - public > T readEnum(Class enumClass) { - return ((T[]) enumClass.getEnumConstants())[this.readVarInt()]; // CraftBukkit - fix decompile error -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetSubtitleTextPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetSubtitleTextPacket.java -index e77bd5bb66279f579fad4fdcc8b0606410922e9e..4c05fee93eb20044a4198c43d5ae1eebebaabaf7 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetSubtitleTextPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetSubtitleTextPacket.java -@@ -7,6 +7,7 @@ import net.minecraft.network.protocol.Packet; - public class ClientboundSetSubtitleTextPacket implements Packet { - private final Component text; - public net.kyori.adventure.text.Component adventure$text; // Paper -+ public net.md_5.bungee.api.chat.BaseComponent[] components; // Paper - - public ClientboundSetSubtitleTextPacket(Component subtitle) { - this.text = subtitle; -@@ -21,6 +22,8 @@ public class ClientboundSetSubtitleTextPacket implements Packet { - private final Component text; - public net.kyori.adventure.text.Component adventure$text; // Paper -+ public net.md_5.bungee.api.chat.BaseComponent[] components; // Paper - - public ClientboundSetTitleTextPacket(Component title) { - this.text = title; -@@ -21,6 +22,8 @@ public class ClientboundSetTitleTextPacket implements Packet -Date: Thu, 3 Mar 2016 02:46:17 -0600 -Subject: [PATCH] Add configurable portal search radius - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 8e4f8849acfede8eec04527b67a3405ae805633d..bef8f3574ecb0d957f9041639b56c94b41913f99 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3181,7 +3181,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - double d0 = DimensionType.getTeleportationScale(this.level().dimensionType(), destination.dimensionType()); - BlockPos blockposition = worldborder.clampToBounds(this.getX() * d0, this.getY(), this.getZ() * d0); - // CraftBukkit start -- CraftPortalEvent event = this.callPortalEvent(this, destination, new Vec3(blockposition.getX(), blockposition.getY(), blockposition.getZ()), PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, flag2 ? 16 : 128, 16); -+ // Paper start - Configurable portal search radius -+ int portalSearchRadius = destination.paperConfig().environment.portalSearchRadius; -+ if (level.paperConfig().environment.portalSearchVanillaDimensionScaling && flag2) { // == THE_NETHER -+ portalSearchRadius = (int) (portalSearchRadius / destination.dimensionType().coordinateScale()); -+ } -+ // Paper end - Configurable portal search radius -+ CraftPortalEvent event = this.callPortalEvent(this, destination, new Vec3(blockposition.getX(), blockposition.getY(), blockposition.getZ()), PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, portalSearchRadius, destination.paperConfig().environment.portalCreateRadius); // Paper start - configurable portal radius - if (event == null) { - return null; - } -diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -index 550b7bc694d861c084769265f6c49c4d44033296..afdd4ecbff21e2172b390bcbdf74f3c1bbddafcc 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -@@ -43,7 +43,7 @@ public class PortalForcer { - - public Optional findPortalAround(BlockPos pos, boolean destIsNether, WorldBorder worldBorder) { - // CraftBukkit start -- return this.findPortalAround(pos, worldBorder, destIsNether ? 16 : 128); // Search Radius -+ return this.findPortalAround(pos, worldBorder, destIsNether ? level.paperConfig().environment.portalCreateRadius : level.paperConfig().environment.portalSearchRadius); // Search Radius // Paper - Configurable portal search radius - } - - public Optional findPortalAround(BlockPos blockposition, WorldBorder worldborder, int i) { diff --git a/patches/server/0057-Add-velocity-warnings.patch b/patches/server/0057-Add-velocity-warnings.patch new file mode 100644 index 000000000000..f81b57b34483 --- /dev/null +++ b/patches/server/0057-Add-velocity-warnings.patch @@ -0,0 +1,85 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Joseph Hirschfeld +Date: Thu, 3 Mar 2016 02:48:12 -0600 +Subject: [PATCH] Add velocity warnings + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 6135e8c0669886260afacdaa7405bf55597b17a6..2b2d0c8ed68eb86812877026a0bb5c4a6389c3d4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -300,6 +300,7 @@ public final class CraftServer implements Server { + public boolean ignoreVanillaPermissions = false; + private final List playerView; + public int reloadCount; ++ public static Exception excessiveVelEx; // Paper - Velocity warnings + + static { + ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 49294a8d580d891f21d8d4cbae14ae477c01ff8d..74937603e7b8308fd314d650d9d966e8abd2c725 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -128,10 +128,40 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public void setVelocity(Vector velocity) { + Preconditions.checkArgument(velocity != null, "velocity"); + velocity.checkFinite(); ++ // Paper start - Warn server owners when plugins try to set super high velocities ++ if (!(this instanceof org.bukkit.entity.Projectile || this instanceof org.bukkit.entity.Minecart) && isUnsafeVelocity(velocity)) { ++ CraftServer.excessiveVelEx = new Exception("Excessive velocity set detected: tried to set velocity of entity " + entity.getScoreboardName() + " id #" + getEntityId() + " to (" + velocity.getX() + "," + velocity.getY() + "," + velocity.getZ() + ")."); ++ } ++ // Paper end + this.entity.setDeltaMovement(CraftVector.toNMS(velocity)); + this.entity.hurtMarked = true; + } + ++ // Paper start ++ /** ++ * Checks if the given velocity is not necessarily safe in all situations. ++ * This function returning true does not mean the velocity is dangerous or to be avoided, only that it may be ++ * a detriment to performance on the server. ++ * ++ * It is not to be used as a hard rule of any sort. ++ * Paper only uses it to warn server owners in watchdog crashes. ++ * ++ * @param vel incoming velocity to check ++ * @return if the velocity has the potential to be a performance detriment ++ */ ++ private static boolean isUnsafeVelocity(Vector vel) { ++ final double x = vel.getX(); ++ final double y = vel.getY(); ++ final double z = vel.getZ(); ++ ++ if (x > 4 || x < -4 || y > 4 || y < -4 || z > 4 || z < -4) { ++ return true; ++ } ++ ++ return false; ++ } ++ // Paper end ++ + @Override + public double getHeight() { + return this.getHandle().getBbHeight(); +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 231b4e3552b17f7803815a433a5ece440c227cc6..4bfc2f1729e45e36307a98bd69de9c820123cb8e 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -81,6 +81,17 @@ public class WatchdogThread extends Thread + log.log( Level.SEVERE, "near " + net.minecraft.world.level.Level.lastPhysicsProblem ); + } + // ++ // Paper start - Warn in watchdog if an excessive velocity was ever set ++ if (org.bukkit.craftbukkit.CraftServer.excessiveVelEx != null) { ++ log.log(Level.SEVERE, "------------------------------"); ++ log.log(Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity"); ++ log.log(Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated"); ++ log.log(Level.SEVERE, org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getMessage()); ++ for (StackTraceElement stack : org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace()) { ++ log.log( Level.SEVERE, "\t\t" + stack ); ++ } ++ } ++ // Paper end + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper + WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); diff --git a/patches/server/0058-Add-velocity-warnings.patch b/patches/server/0058-Add-velocity-warnings.patch deleted file mode 100644 index ad923e70d1fa..000000000000 --- a/patches/server/0058-Add-velocity-warnings.patch +++ /dev/null @@ -1,85 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Joseph Hirschfeld -Date: Thu, 3 Mar 2016 02:48:12 -0600 -Subject: [PATCH] Add velocity warnings - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 669e75549d0cc1d9c506f362e27b2f1717ec8d5c..908808099d0b7c3320f447330a441a536ce07421 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -298,6 +298,7 @@ public final class CraftServer implements Server { - public boolean ignoreVanillaPermissions = false; - private final List playerView; - public int reloadCount; -+ public static Exception excessiveVelEx; // Paper - Velocity warnings - - static { - ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 49294a8d580d891f21d8d4cbae14ae477c01ff8d..74937603e7b8308fd314d650d9d966e8abd2c725 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -128,10 +128,40 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - public void setVelocity(Vector velocity) { - Preconditions.checkArgument(velocity != null, "velocity"); - velocity.checkFinite(); -+ // Paper start - Warn server owners when plugins try to set super high velocities -+ if (!(this instanceof org.bukkit.entity.Projectile || this instanceof org.bukkit.entity.Minecart) && isUnsafeVelocity(velocity)) { -+ CraftServer.excessiveVelEx = new Exception("Excessive velocity set detected: tried to set velocity of entity " + entity.getScoreboardName() + " id #" + getEntityId() + " to (" + velocity.getX() + "," + velocity.getY() + "," + velocity.getZ() + ")."); -+ } -+ // Paper end - this.entity.setDeltaMovement(CraftVector.toNMS(velocity)); - this.entity.hurtMarked = true; - } - -+ // Paper start -+ /** -+ * Checks if the given velocity is not necessarily safe in all situations. -+ * This function returning true does not mean the velocity is dangerous or to be avoided, only that it may be -+ * a detriment to performance on the server. -+ * -+ * It is not to be used as a hard rule of any sort. -+ * Paper only uses it to warn server owners in watchdog crashes. -+ * -+ * @param vel incoming velocity to check -+ * @return if the velocity has the potential to be a performance detriment -+ */ -+ private static boolean isUnsafeVelocity(Vector vel) { -+ final double x = vel.getX(); -+ final double y = vel.getY(); -+ final double z = vel.getZ(); -+ -+ if (x > 4 || x < -4 || y > 4 || y < -4 || z > 4 || z < -4) { -+ return true; -+ } -+ -+ return false; -+ } -+ // Paper end -+ - @Override - public double getHeight() { - return this.getHandle().getBbHeight(); -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index 231b4e3552b17f7803815a433a5ece440c227cc6..4bfc2f1729e45e36307a98bd69de9c820123cb8e 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -81,6 +81,17 @@ public class WatchdogThread extends Thread - log.log( Level.SEVERE, "near " + net.minecraft.world.level.Level.lastPhysicsProblem ); - } - // -+ // Paper start - Warn in watchdog if an excessive velocity was ever set -+ if (org.bukkit.craftbukkit.CraftServer.excessiveVelEx != null) { -+ log.log(Level.SEVERE, "------------------------------"); -+ log.log(Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity"); -+ log.log(Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated"); -+ log.log(Level.SEVERE, org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getMessage()); -+ for (StackTraceElement stack : org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace()) { -+ log.log( Level.SEVERE, "\t\t" + stack ); -+ } -+ } -+ // Paper end - log.log( Level.SEVERE, "------------------------------" ); - log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper - WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); diff --git a/patches/server/0058-Configurable-inter-world-teleportation-safety.patch b/patches/server/0058-Configurable-inter-world-teleportation-safety.patch new file mode 100644 index 000000000000..ca20bf2e171b --- /dev/null +++ b/patches/server/0058-Configurable-inter-world-teleportation-safety.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sudzzy +Date: Thu, 3 Mar 2016 02:50:31 -0600 +Subject: [PATCH] Configurable inter-world teleportation safety + +People are able to abuse the way Bukkit handles teleportation across worlds since it provides a built in teleportation +safety check. + +To abuse the safety check, players are required to get into a location deemed unsafe by Bukkit e.g. be within a chest +or door block. While they are in this block, they accept a teleport request from a player within a different world. Once +the player teleports, Minecraft will recursively search upwards for a safe location, this could eventually land within a +player's skybase. + +Example setup to perform the glitch: http://puu.sh/ng3PC/cf072dcbdb.png +The wanted destination was on top of the emerald block however the player ended on top of the diamond block. +This only is the case if the player is teleporting between worlds. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 03dad5aa44d4484e9a3064279c867f009104a9b2..fc2374c9ecc7603c310761e6196c026eded788f6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1156,7 +1156,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + entity.connection.teleport(to); + } else { + // The respawn reason should never be used if the passed location is non null. +- this.server.getHandle().respawn(entity, toWorld, true, to, true, null); ++ this.server.getHandle().respawn(entity, toWorld, true, to, !toWorld.paperConfig().environment.disableTeleportationSuffocationCheck, null); // Paper + } + return true; + } diff --git a/patches/server/0060-Add-exception-reporting-event.patch b/patches/server/0059-Add-exception-reporting-event.patch similarity index 100% rename from patches/server/0060-Add-exception-reporting-event.patch rename to patches/server/0059-Add-exception-reporting-event.patch diff --git a/patches/server/0059-Configurable-inter-world-teleportation-safety.patch b/patches/server/0059-Configurable-inter-world-teleportation-safety.patch deleted file mode 100644 index 3d04ecd59b0f..000000000000 --- a/patches/server/0059-Configurable-inter-world-teleportation-safety.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Sudzzy -Date: Thu, 3 Mar 2016 02:50:31 -0600 -Subject: [PATCH] Configurable inter-world teleportation safety - -People are able to abuse the way Bukkit handles teleportation across worlds since it provides a built in teleportation -safety check. - -To abuse the safety check, players are required to get into a location deemed unsafe by Bukkit e.g. be within a chest -or door block. While they are in this block, they accept a teleport request from a player within a different world. Once -the player teleports, Minecraft will recursively search upwards for a safe location, this could eventually land within a -player's skybase. - -Example setup to perform the glitch: http://puu.sh/ng3PC/cf072dcbdb.png -The wanted destination was on top of the emerald block however the player ended on top of the diamond block. -This only is the case if the player is teleporting between worlds. - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index cfa95917b39b32917bf32b2e97211d232d9037fd..2de70507a03e662afa45dd9f2e3c28c857a8037c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1126,7 +1126,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - entity.connection.teleport(to); - } else { - // The respawn reason should never be used if the passed location is non null. -- this.server.getHandle().respawn(entity, toWorld, true, to, true, null); -+ this.server.getHandle().respawn(entity, toWorld, true, to, !toWorld.paperConfig().environment.disableTeleportationSuffocationCheck, null); // Paper - } - return true; - } diff --git a/patches/server/0060-Disable-Scoreboards-for-non-players-by-default.patch b/patches/server/0060-Disable-Scoreboards-for-non-players-by-default.patch new file mode 100644 index 000000000000..b562b1b37394 --- /dev/null +++ b/patches/server/0060-Disable-Scoreboards-for-non-players-by-default.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 8 Mar 2016 23:25:45 -0500 +Subject: [PATCH] Disable Scoreboards for non players by default + +Entities collision is checking for scoreboards setting. +This is very heavy to do map lookups for every collision to check +this setting. + +So avoid looking up scoreboards and short circuit to the "not on a team" +logic which is most likely to be true. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 4c7a2d64ab4a5269fdea0b30e6c91fa1cb9f509c..e7157ff6cd6f2c52593bba63129fdaa60fcbf886 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2807,6 +2807,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + + @Nullable + public PlayerTeam getTeam() { ++ if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof Player)) { return null; } // Paper - Perf: Disable Scoreboards for non players by default + return this.level().getScoreboard().getPlayersTeam(this.getScoreboardName()); + } + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index e111bdb614f173322ed0cc0386db6870a984fff7..0a573b188111e84290317f2ce5620b6ea6c8b6de 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -833,6 +833,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (nbt.contains("Team", 8)) { + String s = nbt.getString("Team"); + PlayerTeam scoreboardteam = this.level().getScoreboard().getPlayerTeam(s); ++ if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof net.minecraft.world.entity.player.Player)) { scoreboardteam = null; } // Paper - Perf: Disable Scoreboards for non players by default + boolean flag = scoreboardteam != null && this.level().getScoreboard().addPlayerToTeam(this.getStringUUID(), scoreboardteam); + + if (!flag) { diff --git a/patches/server/0061-Add-methods-for-working-with-arrows-stuck-in-living-.patch b/patches/server/0061-Add-methods-for-working-with-arrows-stuck-in-living-.patch new file mode 100644 index 000000000000..59051e3b9718 --- /dev/null +++ b/patches/server/0061-Add-methods-for-working-with-arrows-stuck-in-living-.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: mrapple +Date: Sun, 25 Nov 2012 13:43:39 -0600 +Subject: [PATCH] Add methods for working with arrows stuck in living entities + +Upstream added methods for this, original methods are now +deprecated + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index a73a5a8291ddd954f2c7b73a4895933e715c5d68..4636e1ebf11025a551d398a6594b1506748a8390 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -267,10 +267,29 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } + + @Override +- public void setArrowsInBody(int count) { ++ public void setArrowsInBody(final int count, final boolean fireEvent) { // Paper + Preconditions.checkArgument(count >= 0, "New arrow amount must be >= 0"); ++ if (!fireEvent) { // Paper + this.getHandle().getEntityData().set(net.minecraft.world.entity.LivingEntity.DATA_ARROW_COUNT_ID, count); ++ // Paper start ++ } else { ++ this.getHandle().setArrowCount(count); ++ } ++ // Paper end ++ } ++ ++ // Paper start - Add methods for working with arrows stuck in living entities ++ @Override ++ public void setNextArrowRemoval(final int ticks) { ++ Preconditions.checkArgument(ticks >= 0, "New amount of ticks before next arrow removal must be >= 0"); ++ this.getHandle().removeArrowTime = ticks; ++ } ++ ++ @Override ++ public int getNextArrowRemoval() { ++ return this.getHandle().removeArrowTime; + } ++ // Paper end - Add methods for working with arrows stuck in living entities + + @Override + public void damage(double amount) { +@@ -799,4 +818,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + this.getHandle().persistentInvisibility = invisible; + this.getHandle().setSharedFlag(5, invisible); + } ++ ++ // Paper start ++ @Override ++ public int getArrowsStuck() { ++ return this.getHandle().getArrowCount(); ++ } ++ ++ @Override ++ public void setArrowsStuck(final int arrows) { ++ this.getHandle().setArrowCount(arrows); ++ } ++ // Paper end + } diff --git a/patches/server/0061-Disable-Scoreboards-for-non-players-by-default.patch b/patches/server/0061-Disable-Scoreboards-for-non-players-by-default.patch deleted file mode 100644 index 2ec136b21c13..000000000000 --- a/patches/server/0061-Disable-Scoreboards-for-non-players-by-default.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 8 Mar 2016 23:25:45 -0500 -Subject: [PATCH] Disable Scoreboards for non players by default - -Entities collision is checking for scoreboards setting. -This is very heavy to do map lookups for every collision to check -this setting. - -So avoid looking up scoreboards and short circuit to the "not on a team" -logic which is most likely to be true. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index bef8f3574ecb0d957f9041639b56c94b41913f99..c4168fa278dacf9f50058bb7dc98bb12aef717f2 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2808,6 +2808,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - - @Nullable - public PlayerTeam getTeam() { -+ if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof Player)) { return null; } // Paper - Perf: Disable Scoreboards for non players by default - return this.level().getScoreboard().getPlayersTeam(this.getScoreboardName()); - } - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 2e47008a8ff1bb56b752d4eb880173b9edfbc4ad..3eeb40c2176a80b9e2a472d43671ae0fe087d9e7 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -832,6 +832,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (nbt.contains("Team", 8)) { - String s = nbt.getString("Team"); - PlayerTeam scoreboardteam = this.level().getScoreboard().getPlayerTeam(s); -+ if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof net.minecraft.world.entity.player.Player)) { scoreboardteam = null; } // Paper - Perf: Disable Scoreboards for non players by default - boolean flag = scoreboardteam != null && this.level().getScoreboard().addPlayerToTeam(this.getStringUUID(), scoreboardteam); - - if (!flag) { diff --git a/patches/server/0062-Add-methods-for-working-with-arrows-stuck-in-living-.patch b/patches/server/0062-Add-methods-for-working-with-arrows-stuck-in-living-.patch deleted file mode 100644 index f7b2fb6ec119..000000000000 --- a/patches/server/0062-Add-methods-for-working-with-arrows-stuck-in-living-.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: mrapple -Date: Sun, 25 Nov 2012 13:43:39 -0600 -Subject: [PATCH] Add methods for working with arrows stuck in living entities - -Upstream added methods for this, original methods are now -deprecated - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 5825f942db3b9870631ff093708dee0e930fccc8..690364b874212cca2fe2078efa7b8f3f7880e39f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -266,10 +266,29 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } - - @Override -- public void setArrowsInBody(int count) { -+ public void setArrowsInBody(final int count, final boolean fireEvent) { // Paper - Preconditions.checkArgument(count >= 0, "New arrow amount must be >= 0"); -+ if (!fireEvent) { // Paper - this.getHandle().getEntityData().set(net.minecraft.world.entity.LivingEntity.DATA_ARROW_COUNT_ID, count); -+ // Paper start -+ } else { -+ this.getHandle().setArrowCount(count); -+ } -+ // Paper end -+ } -+ -+ // Paper start - Add methods for working with arrows stuck in living entities -+ @Override -+ public void setNextArrowRemoval(final int ticks) { -+ Preconditions.checkArgument(ticks >= 0, "New amount of ticks before next arrow removal must be >= 0"); -+ this.getHandle().removeArrowTime = ticks; -+ } -+ -+ @Override -+ public int getNextArrowRemoval() { -+ return this.getHandle().removeArrowTime; - } -+ // Paper end - Add methods for working with arrows stuck in living entities - - @Override - public void damage(double amount) { -@@ -786,4 +805,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - this.getHandle().persistentInvisibility = invisible; - this.getHandle().setSharedFlag(5, invisible); - } -+ -+ // Paper start -+ @Override -+ public int getArrowsStuck() { -+ return this.getHandle().getArrowCount(); -+ } -+ -+ @Override -+ public void setArrowsStuck(final int arrows) { -+ this.getHandle().setArrowCount(arrows); -+ } -+ // Paper end - } diff --git a/patches/server/0063-Chunk-Save-Reattempt.patch b/patches/server/0062-Chunk-Save-Reattempt.patch similarity index 100% rename from patches/server/0063-Chunk-Save-Reattempt.patch rename to patches/server/0062-Chunk-Save-Reattempt.patch diff --git a/patches/server/0063-Complete-resource-pack-API.patch b/patches/server/0063-Complete-resource-pack-API.patch new file mode 100644 index 000000000000..296b0713c6ec --- /dev/null +++ b/patches/server/0063-Complete-resource-pack-API.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jedediah Smith +Date: Sat, 4 Apr 2015 23:17:52 -0400 +Subject: [PATCH] Complete resource pack API + + +diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index c8041492b7b2a1ff67b95d9944cfccd476b3ee1d..66497960995dc30abe60d26200979a78513ff2c6 100644 +--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -169,7 +169,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + callback.packEventReceived(packet.id(), net.kyori.adventure.resource.ResourcePackStatus.valueOf(packet.action().name()), this.getCraftPlayer()); + } + // Paper end +- this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()])); // CraftBukkit ++ // Paper start - store last pack status ++ PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()]; ++ player.getBukkitEntity().resourcePackStatus = packStatus; ++ this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), packStatus)); // CraftBukkit ++ // Paper end - store last pack status + + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index fc2374c9ecc7603c310761e6196c026eded788f6..b6851bd629c4d3b9aa7efdf1112e1cf59cd63f60 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -195,6 +195,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + private double healthScale = 20; + private CraftWorldBorder clientWorldBorder = null; + private BorderChangeListener clientWorldBorderListener = this.createWorldBorderListener(); ++ public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; // Paper - more resource pack API + + public CraftPlayer(CraftServer server, ServerPlayer entity) { + super(server, entity); +@@ -2014,6 +2015,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + // Paper end - adventure + ++ // Paper start - more resource pack API ++ @Override ++ public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status getResourcePackStatus() { ++ return this.resourcePackStatus; ++ } ++ // Paper end - more resource pack API ++ + @Override + public void removeResourcePack(UUID id) { + Preconditions.checkArgument(id != null, "Resource pack id cannot be null"); diff --git a/patches/server/0064-Complete-resource-pack-API.patch b/patches/server/0064-Complete-resource-pack-API.patch deleted file mode 100644 index 645ad3f88d88..000000000000 --- a/patches/server/0064-Complete-resource-pack-API.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jedediah Smith -Date: Sat, 4 Apr 2015 23:17:52 -0400 -Subject: [PATCH] Complete resource pack API - - -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index c8041492b7b2a1ff67b95d9944cfccd476b3ee1d..66497960995dc30abe60d26200979a78513ff2c6 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -169,7 +169,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - callback.packEventReceived(packet.id(), net.kyori.adventure.resource.ResourcePackStatus.valueOf(packet.action().name()), this.getCraftPlayer()); - } - // Paper end -- this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()])); // CraftBukkit -+ // Paper start - store last pack status -+ PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()]; -+ player.getBukkitEntity().resourcePackStatus = packStatus; -+ this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), packStatus)); // CraftBukkit -+ // Paper end - store last pack status - - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 05a3d86c5861f237dcccb93c8a9f91cf6a7c18b6..8be468cb5305c0db5f46298aaf6c896b27ce4aee 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -189,6 +189,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - private double healthScale = 20; - private CraftWorldBorder clientWorldBorder = null; - private BorderChangeListener clientWorldBorderListener = this.createWorldBorderListener(); -+ public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; // Paper - more resource pack API - - public CraftPlayer(CraftServer server, ServerPlayer entity) { - super(server, entity); -@@ -1972,6 +1973,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - // Paper end - adventure - -+ // Paper start - more resource pack API -+ @Override -+ public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status getResourcePackStatus() { -+ return this.resourcePackStatus; -+ } -+ // Paper end - more resource pack API -+ - @Override - public void removeResourcePack(UUID id) { - Preconditions.checkArgument(id != null, "Resource pack id cannot be null"); diff --git a/patches/server/0064-Default-loading-permissions.yml-before-plugins.patch b/patches/server/0064-Default-loading-permissions.yml-before-plugins.patch new file mode 100644 index 000000000000..5cb350113eda --- /dev/null +++ b/patches/server/0064-Default-loading-permissions.yml-before-plugins.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 18 Mar 2016 13:17:38 -0400 +Subject: [PATCH] Default loading permissions.yml before plugins + +Under previous behavior, plugins were not able to check if a player had a permission +if it was defined in permissions.yml. there is no clean way for a plugin to fix that either. + +This will change the order so that by default, permissions.yml loads BEFORE plugins instead of after. + +This gives plugins expected permission checks. + +It also helps improve the expected logic, as servers should set the initial defaults, and then let plugins +modify that. Under the previous logic, plugins were unable (cleanly) override permissions.yml. + +A config option has been added for those who depend on the previous behavior, but I don't expect that. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 2b2d0c8ed68eb86812877026a0bb5c4a6389c3d4..1d85e64b30e872f12de7d84af26be6271e77387e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -458,6 +458,7 @@ public final class CraftServer implements Server { + if (type == PluginLoadOrder.STARTUP) { + this.helpMap.clear(); + this.helpMap.initializeGeneralTopics(); ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) loadCustomPermissions(); // Paper + } + + Plugin[] plugins = this.pluginManager.getPlugins(); +@@ -477,7 +478,7 @@ public final class CraftServer implements Server { + this.commandMap.registerServerAliases(); + DefaultPermissions.registerCorePermissions(); + CraftDefaultPermissions.registerCorePermissions(); +- this.loadCustomPermissions(); ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) this.loadCustomPermissions(); // Paper + this.helpMap.initializeCommands(); + this.syncCommands(); + } diff --git a/patches/server/0065-Allow-Reloading-of-Custom-Permissions.patch b/patches/server/0065-Allow-Reloading-of-Custom-Permissions.patch new file mode 100644 index 000000000000..78a798eca3fb --- /dev/null +++ b/patches/server/0065-Allow-Reloading-of-Custom-Permissions.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William +Date: Fri, 18 Mar 2016 03:30:17 -0400 +Subject: [PATCH] Allow Reloading of Custom Permissions + +https://github.com/PaperMC/Paper/issues/49 + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 1d85e64b30e872f12de7d84af26be6271e77387e..f7726d16d37c64228515b0791d921b047bc887e6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2728,5 +2728,23 @@ public final class CraftServer implements Server { + } + return this.adventure$audiences; + } ++ ++ @Override ++ public void reloadPermissions() { ++ pluginManager.clearPermissions(); ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) loadCustomPermissions(); ++ for (Plugin plugin : pluginManager.getPlugins()) { ++ for (Permission perm : plugin.getDescription().getPermissions()) { ++ try { ++ pluginManager.addPermission(perm); ++ } catch (IllegalArgumentException ex) { ++ getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + " tried to register permission '" + perm.getName() + "' but it's already registered", ex); ++ } ++ } ++ } ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) loadCustomPermissions(); ++ DefaultPermissions.registerCorePermissions(); ++ CraftDefaultPermissions.registerCorePermissions(); ++ } + // Paper end + } diff --git a/patches/server/0065-Default-loading-permissions.yml-before-plugins.patch b/patches/server/0065-Default-loading-permissions.yml-before-plugins.patch deleted file mode 100644 index 11685910665a..000000000000 --- a/patches/server/0065-Default-loading-permissions.yml-before-plugins.patch +++ /dev/null @@ -1,38 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 18 Mar 2016 13:17:38 -0400 -Subject: [PATCH] Default loading permissions.yml before plugins - -Under previous behavior, plugins were not able to check if a player had a permission -if it was defined in permissions.yml. there is no clean way for a plugin to fix that either. - -This will change the order so that by default, permissions.yml loads BEFORE plugins instead of after. - -This gives plugins expected permission checks. - -It also helps improve the expected logic, as servers should set the initial defaults, and then let plugins -modify that. Under the previous logic, plugins were unable (cleanly) override permissions.yml. - -A config option has been added for those who depend on the previous behavior, but I don't expect that. - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 908808099d0b7c3320f447330a441a536ce07421..9bec3b0d5c7eaccb334e2663bb1fdc42cd6eb367 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -456,6 +456,7 @@ public final class CraftServer implements Server { - if (type == PluginLoadOrder.STARTUP) { - this.helpMap.clear(); - this.helpMap.initializeGeneralTopics(); -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) loadCustomPermissions(); // Paper - } - - Plugin[] plugins = this.pluginManager.getPlugins(); -@@ -475,7 +476,7 @@ public final class CraftServer implements Server { - this.commandMap.registerServerAliases(); - DefaultPermissions.registerCorePermissions(); - CraftDefaultPermissions.registerCorePermissions(); -- this.loadCustomPermissions(); -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) this.loadCustomPermissions(); // Paper - this.helpMap.initializeCommands(); - this.syncCommands(); - } diff --git a/patches/server/0066-Allow-Reloading-of-Custom-Permissions.patch b/patches/server/0066-Allow-Reloading-of-Custom-Permissions.patch deleted file mode 100644 index 537c89fe417e..000000000000 --- a/patches/server/0066-Allow-Reloading-of-Custom-Permissions.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William -Date: Fri, 18 Mar 2016 03:30:17 -0400 -Subject: [PATCH] Allow Reloading of Custom Permissions - -https://github.com/PaperMC/Paper/issues/49 - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 7203f9b281305e83d7a31e49aab5fb73d603789b..58cf842c3bdc91233404ce907e3652abb1187e03 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2716,5 +2716,23 @@ public final class CraftServer implements Server { - } - return this.adventure$audiences; - } -+ -+ @Override -+ public void reloadPermissions() { -+ pluginManager.clearPermissions(); -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) loadCustomPermissions(); -+ for (Plugin plugin : pluginManager.getPlugins()) { -+ for (Permission perm : plugin.getDescription().getPermissions()) { -+ try { -+ pluginManager.addPermission(perm); -+ } catch (IllegalArgumentException ex) { -+ getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + " tried to register permission '" + perm.getName() + "' but it's already registered", ex); -+ } -+ } -+ } -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) loadCustomPermissions(); -+ DefaultPermissions.registerCorePermissions(); -+ CraftDefaultPermissions.registerCorePermissions(); -+ } - // Paper end - } diff --git a/patches/server/0066-Remove-Metadata-on-reload.patch b/patches/server/0066-Remove-Metadata-on-reload.patch new file mode 100644 index 000000000000..006d87514f1a --- /dev/null +++ b/patches/server/0066-Remove-Metadata-on-reload.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 18 Mar 2016 13:50:14 -0400 +Subject: [PATCH] Remove Metadata on reload + +Metadata is not meant to persist reload as things break badly with non primitive types +This will remove metadata on reload so it does not crash everything if a plugin uses it. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index f7726d16d37c64228515b0791d921b047bc887e6..f69d5e8f22fa8335b19f9e777ddbd33443eb08dc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -964,8 +964,16 @@ public final class CraftServer implements Server { + world.spigotConfig.init(); // Spigot + } + ++ Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper + this.pluginManager.clearPlugins(); + this.commandMap.clearCommands(); ++ // Paper start ++ for (Plugin plugin : pluginClone) { ++ entityMetadata.removeAll(plugin); ++ worldMetadata.removeAll(plugin); ++ playerMetadata.removeAll(plugin); ++ } ++ // Paper end + this.reloadData(); + org.spigotmc.SpigotConfig.registerCommands(); // Spigot + io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper diff --git a/patches/server/0067-Handle-Item-Meta-Inconsistencies.patch b/patches/server/0067-Handle-Item-Meta-Inconsistencies.patch new file mode 100644 index 000000000000..9528bf143127 --- /dev/null +++ b/patches/server/0067-Handle-Item-Meta-Inconsistencies.patch @@ -0,0 +1,306 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 28 May 2015 23:00:19 -0400 +Subject: [PATCH] Handle Item Meta Inconsistencies + +First, Enchantment order would blow away seeing 2 items as the same, +however the Client forces enchantment list in a certain order, as well +as does the /enchant command. Anvils can insert it into forced order, +causing 2 same items to be considered different. + +This change makes unhandled NBT Tags and Enchantments use a sorted tree map, +so they will always be in a consistent order. + +Additionally, the old enchantment API was never updated when ItemMeta +was added, resulting in 2 different ways to modify an items enchantments. + +For consistency, the old API methods now forward to use the +ItemMeta API equivalents, and should deprecate the old API's. + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 6291265ae4691bf7dffe196d20571c1c30e8d906..70511628eefc28163d07f50f18d9cc55dd93d68b 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -181,6 +181,23 @@ public final class ItemStack { + return this.getItem().getTooltipImage(this); + } + ++ // Paper start ++ private static final java.util.Comparator enchantSorter = java.util.Comparator.comparing(o -> o.getString("id")); ++ private void processEnchantOrder(@Nullable CompoundTag tag) { ++ if (tag == null || !tag.contains("Enchantments", net.minecraft.nbt.Tag.TAG_LIST)) { ++ return; ++ } ++ ListTag list = tag.getList("Enchantments", net.minecraft.nbt.Tag.TAG_COMPOUND); ++ if (list.size() < 2) { ++ return; ++ } ++ try { ++ //noinspection unchecked ++ list.sort((java.util.Comparator) enchantSorter); // Paper ++ } catch (Exception ignored) {} ++ } ++ // Paper end ++ + public ItemStack(ItemLike item) { + this(item, 1); + } +@@ -227,6 +244,7 @@ public final class ItemStack { + this.count = nbttagcompound.getByte("Count"); + if (nbttagcompound.contains("tag", 10)) { + this.tag = nbttagcompound.getCompound("tag").copy(); ++ this.processEnchantOrder(this.tag); // Paper + this.getItem().verifyTagAfterLoad(this.tag); + } + +@@ -846,6 +864,7 @@ public final class ItemStack { + + public void setTag(@Nullable CompoundTag nbt) { + this.tag = nbt; ++ this.processEnchantOrder(this.tag); // Paper + if (this.getItem().canBeDepleted()) { + this.setDamageValue(this.getDamageValue()); + } +@@ -1143,6 +1162,7 @@ public final class ItemStack { + ListTag nbttaglist = this.tag.getList("Enchantments", 10); + + nbttaglist.add(EnchantmentHelper.storeEnchantment(EnchantmentHelper.getEnchantmentId(enchantment), (byte) level)); ++ processEnchantOrder(this.tag); // Paper + } + + public boolean isEnchanted() { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 23b83f8e98d681895b4e23cda4f3d50f85c12dd9..c72a1a503f6e71228a1f82b37068ff7a83e983dc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -188,28 +188,11 @@ public final class CraftItemStack extends ItemStack { + public void addUnsafeEnchantment(Enchantment ench, int level) { + Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); + +- if (!CraftItemStack.makeTag(this.handle)) { +- return; +- } +- ListTag list = CraftItemStack.getEnchantmentList(this.handle); +- if (list == null) { +- list = new ListTag(); +- this.handle.getTag().put(ENCHANTMENTS.NBT, list); +- } +- int size = list.size(); +- +- for (int i = 0; i < size; i++) { +- CompoundTag tag = (CompoundTag) list.get(i); +- String id = tag.getString(ENCHANTMENTS_ID.NBT); +- if (ench.getKey().equals(NamespacedKey.fromString(id))) { +- tag.putShort(ENCHANTMENTS_LVL.NBT, (short) level); +- return; +- } +- } +- CompoundTag tag = new CompoundTag(); +- tag.putString(ENCHANTMENTS_ID.NBT, ench.getKey().toString()); +- tag.putShort(ENCHANTMENTS_LVL.NBT, (short) level); +- list.add(tag); ++ // Paper start - Replace whole method ++ final ItemMeta itemMeta = this.getItemMeta(); ++ itemMeta.addEnchant(ench, level, true); ++ this.setItemMeta(itemMeta); ++ // Paper end + } + + static boolean makeTag(net.minecraft.world.item.ItemStack item) { +@@ -242,43 +225,15 @@ public final class CraftItemStack extends ItemStack { + public int removeEnchantment(Enchantment ench) { + Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); + +- ListTag list = CraftItemStack.getEnchantmentList(this.handle), listCopy; +- if (list == null) { +- return 0; +- } +- int index = Integer.MIN_VALUE; +- int level = Integer.MIN_VALUE; +- int size = list.size(); +- +- for (int i = 0; i < size; i++) { +- CompoundTag enchantment = (CompoundTag) list.get(i); +- String id = enchantment.getString(ENCHANTMENTS_ID.NBT); +- if (ench.getKey().equals(NamespacedKey.fromString(id))) { +- index = i; +- level = 0xffff & enchantment.getShort(ENCHANTMENTS_LVL.NBT); +- break; +- } +- } +- +- if (index == Integer.MIN_VALUE) { +- return 0; +- } +- if (size == 1) { +- this.handle.getTag().remove(ENCHANTMENTS.NBT); +- if (this.handle.getTag().isEmpty()) { +- this.handle.setTag(null); +- } +- return level; +- } +- +- // This is workaround for not having an index removal +- listCopy = new ListTag(); +- for (int i = 0; i < size; i++) { +- if (i != index) { +- listCopy.add(list.get(i)); +- } ++ // Paper start - replace entire method ++ int level = getEnchantmentLevel(ench); ++ if (level > 0) { ++ final ItemMeta itemMeta = this.getItemMeta(); ++ if (itemMeta == null) return 0; ++ itemMeta.removeEnchant(ench); ++ this.setItemMeta(itemMeta); + } +- this.handle.getTag().put(ENCHANTMENTS.NBT, listCopy); ++ // Paper end + + return level; + } +@@ -290,7 +245,7 @@ public final class CraftItemStack extends ItemStack { + + @Override + public Map getEnchantments() { +- return CraftItemStack.getEnchantments(this.handle); ++ return this.hasItemMeta() ? this.getItemMeta().getEnchants() : ImmutableMap.of(); // Paper - use Item Meta + } + + static Map getEnchantments(net.minecraft.world.item.ItemStack item) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index ffdea312f93d00289364ef4d41a820cd1338f3bd..361268bcc0197c2f9f4bd065d8f7b51771d562a9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -6,6 +6,7 @@ import com.google.common.collect.ImmutableList; + import com.google.common.collect.ImmutableMap; + import com.google.common.collect.ImmutableMultimap; + import com.google.common.collect.LinkedHashMultimap; ++import com.google.common.collect.ImmutableSortedMap; // Paper + import com.google.common.collect.Lists; + import com.google.common.collect.Multimap; + import com.google.common.collect.SetMultimap; +@@ -23,6 +24,7 @@ import java.util.ArrayList; + import java.util.Arrays; + import java.util.Base64; + import java.util.Collection; ++import java.util.Comparator; // Paper + import java.util.EnumSet; + import java.util.HashMap; + import java.util.Iterator; +@@ -33,6 +35,7 @@ import java.util.Map; + import java.util.NoSuchElementException; + import java.util.Objects; + import java.util.Set; ++import java.util.TreeMap; // Paper + import java.util.logging.Level; + import java.util.logging.Logger; + import javax.annotation.Nonnull; +@@ -277,7 +280,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + private List lore; // null and empty are two different states internally + private Integer customModelData; + private CompoundTag blockData; +- private Map enchantments; ++ private EnchantmentMap enchantments; // Paper + private Multimap attributeModifiers; + private int repairCost; + private int hideFlag; +@@ -288,7 +291,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); + + private CompoundTag internalTag; +- final Map unhandledTags = new HashMap(); // Visible for testing only ++ final Map unhandledTags = new TreeMap(); // Visible for testing only // Paper + private CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftMetaItem.DATA_TYPE_REGISTRY); + + private int version = CraftMagicNumbers.INSTANCE.getDataVersion(); // Internal use only +@@ -309,7 +312,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + this.blockData = meta.blockData; + + if (meta.enchantments != null) { +- this.enchantments = new LinkedHashMap(meta.enchantments); ++ this.enchantments = new EnchantmentMap(meta.enchantments); // Paper + } + + if (meta.hasAttributeModifiers()) { +@@ -392,13 +395,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + +- static Map buildEnchantments(CompoundTag tag, ItemMetaKey key) { ++ static EnchantmentMap buildEnchantments(CompoundTag tag, ItemMetaKey key) { // Paper + if (!tag.contains(key.NBT)) { + return null; + } + + ListTag ench = tag.getList(key.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND); +- Map enchantments = new LinkedHashMap(ench.size()); ++ EnchantmentMap enchantments = new EnchantmentMap(); // Paper + + for (int i = 0; i < ench.size(); i++) { + String id = ((CompoundTag) ench.get(i)).getString(CraftMetaItem.ENCHANTMENTS_ID.NBT); +@@ -551,13 +554,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + +- static Map buildEnchantments(Map map, ItemMetaKey key) { ++ static EnchantmentMap buildEnchantments(Map map, ItemMetaKey key) { // Paper + Map ench = SerializableMeta.getObject(Map.class, map, key.BUKKIT, true); + if (ench == null) { + return null; + } + +- Map enchantments = new LinkedHashMap(ench.size()); ++ EnchantmentMap enchantments = new EnchantmentMap(); // Paper + for (Map.Entry entry : ench.entrySet()) { + // Doctor older enchants + String enchantKey = entry.getKey().toString(); +@@ -845,14 +848,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public Map getEnchants() { +- return this.hasEnchants() ? ImmutableMap.copyOf(this.enchantments) : ImmutableMap.of(); ++ return this.hasEnchants() ? ImmutableSortedMap.copyOfSorted(this.enchantments) : ImmutableMap.of(); // Paper + } + + @Override + public boolean addEnchant(Enchantment ench, int level, boolean ignoreRestrictions) { + Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); + if (this.enchantments == null) { +- this.enchantments = new LinkedHashMap(4); ++ this.enchantments = new EnchantmentMap(); // Paper + } + + if (ignoreRestrictions || level >= ench.getStartLevel() && level <= ench.getMaxLevel()) { +@@ -1269,7 +1272,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + clone.customModelData = this.customModelData; + clone.blockData = this.blockData; + if (this.enchantments != null) { +- clone.enchantments = new LinkedHashMap(this.enchantments); ++ clone.enchantments = new EnchantmentMap(this.enchantments); // Paper + } + if (this.hasAttributeModifiers()) { + clone.attributeModifiers = LinkedHashMultimap.create(this.attributeModifiers); +@@ -1516,4 +1519,22 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + return CraftMetaItem.HANDLED_TAGS; + } + } ++ ++ // Paper start ++ private static class EnchantmentMap extends TreeMap { ++ private EnchantmentMap(Map enchantments) { ++ this(); ++ putAll(enchantments); ++ } ++ ++ private EnchantmentMap() { ++ super(Comparator.comparing(o -> o.getKey().toString())); ++ } ++ ++ public EnchantmentMap clone() { ++ return (EnchantmentMap) super.clone(); ++ } ++ } ++ // Paper end ++ + } diff --git a/patches/server/0067-Remove-Metadata-on-reload.patch b/patches/server/0067-Remove-Metadata-on-reload.patch deleted file mode 100644 index d7f1fa2629bb..000000000000 --- a/patches/server/0067-Remove-Metadata-on-reload.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 18 Mar 2016 13:50:14 -0400 -Subject: [PATCH] Remove Metadata on reload - -Metadata is not meant to persist reload as things break badly with non primitive types -This will remove metadata on reload so it does not crash everything if a plugin uses it. - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index d2d2cc70eac66d503efe89ded205f1a904c95a26..fb6c21a43e771317526972c183d95402d941924b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -952,8 +952,16 @@ public final class CraftServer implements Server { - world.spigotConfig.init(); // Spigot - } - -+ Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper - this.pluginManager.clearPlugins(); - this.commandMap.clearCommands(); -+ // Paper start -+ for (Plugin plugin : pluginClone) { -+ entityMetadata.removeAll(plugin); -+ worldMetadata.removeAll(plugin); -+ playerMetadata.removeAll(plugin); -+ } -+ // Paper end - this.reloadData(); - org.spigotmc.SpigotConfig.registerCommands(); // Spigot - io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper diff --git a/patches/server/0069-Configurable-Non-Player-Arrow-Despawn-Rate.patch b/patches/server/0068-Configurable-Non-Player-Arrow-Despawn-Rate.patch similarity index 100% rename from patches/server/0069-Configurable-Non-Player-Arrow-Despawn-Rate.patch rename to patches/server/0068-Configurable-Non-Player-Arrow-Despawn-Rate.patch diff --git a/patches/server/0068-Handle-Item-Meta-Inconsistencies.patch b/patches/server/0068-Handle-Item-Meta-Inconsistencies.patch deleted file mode 100644 index e4b3d7b26b0f..000000000000 --- a/patches/server/0068-Handle-Item-Meta-Inconsistencies.patch +++ /dev/null @@ -1,306 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 28 May 2015 23:00:19 -0400 -Subject: [PATCH] Handle Item Meta Inconsistencies - -First, Enchantment order would blow away seeing 2 items as the same, -however the Client forces enchantment list in a certain order, as well -as does the /enchant command. Anvils can insert it into forced order, -causing 2 same items to be considered different. - -This change makes unhandled NBT Tags and Enchantments use a sorted tree map, -so they will always be in a consistent order. - -Additionally, the old enchantment API was never updated when ItemMeta -was added, resulting in 2 different ways to modify an items enchantments. - -For consistency, the old API methods now forward to use the -ItemMeta API equivalents, and should deprecate the old API's. - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 9861cd23b07f8fbacb1d125af835dee58c2debbb..b0ea04fc0bac640f7076100e44c16c03b86b2a0e 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -180,6 +180,23 @@ public final class ItemStack { - return this.getItem().getTooltipImage(this); - } - -+ // Paper start -+ private static final java.util.Comparator enchantSorter = java.util.Comparator.comparing(o -> o.getString("id")); -+ private void processEnchantOrder(@Nullable CompoundTag tag) { -+ if (tag == null || !tag.contains("Enchantments", net.minecraft.nbt.Tag.TAG_LIST)) { -+ return; -+ } -+ ListTag list = tag.getList("Enchantments", net.minecraft.nbt.Tag.TAG_COMPOUND); -+ if (list.size() < 2) { -+ return; -+ } -+ try { -+ //noinspection unchecked -+ list.sort((java.util.Comparator) enchantSorter); // Paper -+ } catch (Exception ignored) {} -+ } -+ // Paper end -+ - public ItemStack(ItemLike item) { - this(item, 1); - } -@@ -226,6 +243,7 @@ public final class ItemStack { - this.count = nbttagcompound.getByte("Count"); - if (nbttagcompound.contains("tag", 10)) { - this.tag = nbttagcompound.getCompound("tag").copy(); -+ this.processEnchantOrder(this.tag); // Paper - this.getItem().verifyTagAfterLoad(this.tag); - } - -@@ -844,6 +862,7 @@ public final class ItemStack { - - public void setTag(@Nullable CompoundTag nbt) { - this.tag = nbt; -+ this.processEnchantOrder(this.tag); // Paper - if (this.getItem().canBeDepleted()) { - this.setDamageValue(this.getDamageValue()); - } -@@ -1141,6 +1160,7 @@ public final class ItemStack { - ListTag nbttaglist = this.tag.getList("Enchantments", 10); - - nbttaglist.add(EnchantmentHelper.storeEnchantment(EnchantmentHelper.getEnchantmentId(enchantment), (byte) level)); -+ processEnchantOrder(this.tag); // Paper - } - - public boolean isEnchanted() { -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index 23b83f8e98d681895b4e23cda4f3d50f85c12dd9..c72a1a503f6e71228a1f82b37068ff7a83e983dc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -188,28 +188,11 @@ public final class CraftItemStack extends ItemStack { - public void addUnsafeEnchantment(Enchantment ench, int level) { - Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); - -- if (!CraftItemStack.makeTag(this.handle)) { -- return; -- } -- ListTag list = CraftItemStack.getEnchantmentList(this.handle); -- if (list == null) { -- list = new ListTag(); -- this.handle.getTag().put(ENCHANTMENTS.NBT, list); -- } -- int size = list.size(); -- -- for (int i = 0; i < size; i++) { -- CompoundTag tag = (CompoundTag) list.get(i); -- String id = tag.getString(ENCHANTMENTS_ID.NBT); -- if (ench.getKey().equals(NamespacedKey.fromString(id))) { -- tag.putShort(ENCHANTMENTS_LVL.NBT, (short) level); -- return; -- } -- } -- CompoundTag tag = new CompoundTag(); -- tag.putString(ENCHANTMENTS_ID.NBT, ench.getKey().toString()); -- tag.putShort(ENCHANTMENTS_LVL.NBT, (short) level); -- list.add(tag); -+ // Paper start - Replace whole method -+ final ItemMeta itemMeta = this.getItemMeta(); -+ itemMeta.addEnchant(ench, level, true); -+ this.setItemMeta(itemMeta); -+ // Paper end - } - - static boolean makeTag(net.minecraft.world.item.ItemStack item) { -@@ -242,43 +225,15 @@ public final class CraftItemStack extends ItemStack { - public int removeEnchantment(Enchantment ench) { - Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); - -- ListTag list = CraftItemStack.getEnchantmentList(this.handle), listCopy; -- if (list == null) { -- return 0; -- } -- int index = Integer.MIN_VALUE; -- int level = Integer.MIN_VALUE; -- int size = list.size(); -- -- for (int i = 0; i < size; i++) { -- CompoundTag enchantment = (CompoundTag) list.get(i); -- String id = enchantment.getString(ENCHANTMENTS_ID.NBT); -- if (ench.getKey().equals(NamespacedKey.fromString(id))) { -- index = i; -- level = 0xffff & enchantment.getShort(ENCHANTMENTS_LVL.NBT); -- break; -- } -- } -- -- if (index == Integer.MIN_VALUE) { -- return 0; -- } -- if (size == 1) { -- this.handle.getTag().remove(ENCHANTMENTS.NBT); -- if (this.handle.getTag().isEmpty()) { -- this.handle.setTag(null); -- } -- return level; -- } -- -- // This is workaround for not having an index removal -- listCopy = new ListTag(); -- for (int i = 0; i < size; i++) { -- if (i != index) { -- listCopy.add(list.get(i)); -- } -+ // Paper start - replace entire method -+ int level = getEnchantmentLevel(ench); -+ if (level > 0) { -+ final ItemMeta itemMeta = this.getItemMeta(); -+ if (itemMeta == null) return 0; -+ itemMeta.removeEnchant(ench); -+ this.setItemMeta(itemMeta); - } -- this.handle.getTag().put(ENCHANTMENTS.NBT, listCopy); -+ // Paper end - - return level; - } -@@ -290,7 +245,7 @@ public final class CraftItemStack extends ItemStack { - - @Override - public Map getEnchantments() { -- return CraftItemStack.getEnchantments(this.handle); -+ return this.hasItemMeta() ? this.getItemMeta().getEnchants() : ImmutableMap.of(); // Paper - use Item Meta - } - - static Map getEnchantments(net.minecraft.world.item.ItemStack item) { -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index ffdea312f93d00289364ef4d41a820cd1338f3bd..361268bcc0197c2f9f4bd065d8f7b51771d562a9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -6,6 +6,7 @@ import com.google.common.collect.ImmutableList; - import com.google.common.collect.ImmutableMap; - import com.google.common.collect.ImmutableMultimap; - import com.google.common.collect.LinkedHashMultimap; -+import com.google.common.collect.ImmutableSortedMap; // Paper - import com.google.common.collect.Lists; - import com.google.common.collect.Multimap; - import com.google.common.collect.SetMultimap; -@@ -23,6 +24,7 @@ import java.util.ArrayList; - import java.util.Arrays; - import java.util.Base64; - import java.util.Collection; -+import java.util.Comparator; // Paper - import java.util.EnumSet; - import java.util.HashMap; - import java.util.Iterator; -@@ -33,6 +35,7 @@ import java.util.Map; - import java.util.NoSuchElementException; - import java.util.Objects; - import java.util.Set; -+import java.util.TreeMap; // Paper - import java.util.logging.Level; - import java.util.logging.Logger; - import javax.annotation.Nonnull; -@@ -277,7 +280,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - private List lore; // null and empty are two different states internally - private Integer customModelData; - private CompoundTag blockData; -- private Map enchantments; -+ private EnchantmentMap enchantments; // Paper - private Multimap attributeModifiers; - private int repairCost; - private int hideFlag; -@@ -288,7 +291,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); - - private CompoundTag internalTag; -- final Map unhandledTags = new HashMap(); // Visible for testing only -+ final Map unhandledTags = new TreeMap(); // Visible for testing only // Paper - private CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftMetaItem.DATA_TYPE_REGISTRY); - - private int version = CraftMagicNumbers.INSTANCE.getDataVersion(); // Internal use only -@@ -309,7 +312,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - this.blockData = meta.blockData; - - if (meta.enchantments != null) { -- this.enchantments = new LinkedHashMap(meta.enchantments); -+ this.enchantments = new EnchantmentMap(meta.enchantments); // Paper - } - - if (meta.hasAttributeModifiers()) { -@@ -392,13 +395,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - } - } - -- static Map buildEnchantments(CompoundTag tag, ItemMetaKey key) { -+ static EnchantmentMap buildEnchantments(CompoundTag tag, ItemMetaKey key) { // Paper - if (!tag.contains(key.NBT)) { - return null; - } - - ListTag ench = tag.getList(key.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND); -- Map enchantments = new LinkedHashMap(ench.size()); -+ EnchantmentMap enchantments = new EnchantmentMap(); // Paper - - for (int i = 0; i < ench.size(); i++) { - String id = ((CompoundTag) ench.get(i)).getString(CraftMetaItem.ENCHANTMENTS_ID.NBT); -@@ -551,13 +554,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - } - } - -- static Map buildEnchantments(Map map, ItemMetaKey key) { -+ static EnchantmentMap buildEnchantments(Map map, ItemMetaKey key) { // Paper - Map ench = SerializableMeta.getObject(Map.class, map, key.BUKKIT, true); - if (ench == null) { - return null; - } - -- Map enchantments = new LinkedHashMap(ench.size()); -+ EnchantmentMap enchantments = new EnchantmentMap(); // Paper - for (Map.Entry entry : ench.entrySet()) { - // Doctor older enchants - String enchantKey = entry.getKey().toString(); -@@ -845,14 +848,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - - @Override - public Map getEnchants() { -- return this.hasEnchants() ? ImmutableMap.copyOf(this.enchantments) : ImmutableMap.of(); -+ return this.hasEnchants() ? ImmutableSortedMap.copyOfSorted(this.enchantments) : ImmutableMap.of(); // Paper - } - - @Override - public boolean addEnchant(Enchantment ench, int level, boolean ignoreRestrictions) { - Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); - if (this.enchantments == null) { -- this.enchantments = new LinkedHashMap(4); -+ this.enchantments = new EnchantmentMap(); // Paper - } - - if (ignoreRestrictions || level >= ench.getStartLevel() && level <= ench.getMaxLevel()) { -@@ -1269,7 +1272,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - clone.customModelData = this.customModelData; - clone.blockData = this.blockData; - if (this.enchantments != null) { -- clone.enchantments = new LinkedHashMap(this.enchantments); -+ clone.enchantments = new EnchantmentMap(this.enchantments); // Paper - } - if (this.hasAttributeModifiers()) { - clone.attributeModifiers = LinkedHashMultimap.create(this.attributeModifiers); -@@ -1516,4 +1519,22 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - return CraftMetaItem.HANDLED_TAGS; - } - } -+ -+ // Paper start -+ private static class EnchantmentMap extends TreeMap { -+ private EnchantmentMap(Map enchantments) { -+ this(); -+ putAll(enchantments); -+ } -+ -+ private EnchantmentMap() { -+ super(Comparator.comparing(o -> o.getKey().toString())); -+ } -+ -+ public EnchantmentMap clone() { -+ return (EnchantmentMap) super.clone(); -+ } -+ } -+ // Paper end -+ - } diff --git a/patches/server/0070-Add-World-Util-Methods.patch b/patches/server/0069-Add-World-Util-Methods.patch similarity index 100% rename from patches/server/0070-Add-World-Util-Methods.patch rename to patches/server/0069-Add-World-Util-Methods.patch diff --git a/patches/server/0070-Custom-replacement-for-eaten-items.patch b/patches/server/0070-Custom-replacement-for-eaten-items.patch new file mode 100644 index 000000000000..c44df3ecedbe --- /dev/null +++ b/patches/server/0070-Custom-replacement-for-eaten-items.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jedediah Smith +Date: Sun, 21 Jun 2015 15:07:20 -0400 +Subject: [PATCH] Custom replacement for eaten items + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 0a573b188111e84290317f2ce5620b6ea6c8b6de..13da8e684172f9f95fbcb63a322b559b733f2469 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3711,10 +3711,11 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.triggerItemUseEffects(this.useItem, 16); + // CraftBukkit start - fire PlayerItemConsumeEvent + ItemStack itemstack; ++ PlayerItemConsumeEvent event = null; // Paper + if (this instanceof ServerPlayer) { + org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitCopy(this.useItem); + org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(enumhand); +- PlayerItemConsumeEvent event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem, hand); ++ event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem, hand); // Paper + this.level().getCraftServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { +@@ -3728,6 +3729,12 @@ public abstract class LivingEntity extends Entity implements Attackable { + } else { + itemstack = this.useItem.finishUsingItem(this.level(), this); + } ++ // Paper start - save the default replacement item and change it if necessary ++ final ItemStack defaultReplacement = itemstack; ++ if (event != null && event.getReplacement() != null) { ++ itemstack = CraftItemStack.asNMSCopy(event.getReplacement()); ++ } ++ // Paper end + // CraftBukkit end + + if (itemstack != this.useItem) { +@@ -3735,6 +3742,11 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + this.stopUsingItem(); ++ // Paper start - if the replacement is anything but the default, update the client inventory ++ if (this instanceof ServerPlayer && !com.google.common.base.Objects.equal(defaultReplacement, itemstack)) { ++ ((ServerPlayer) this).getBukkitEntity().updateInventory(); ++ } ++ // Paper end + } + + } diff --git a/patches/server/0071-Custom-replacement-for-eaten-items.patch b/patches/server/0071-Custom-replacement-for-eaten-items.patch deleted file mode 100644 index 19948631fa23..000000000000 --- a/patches/server/0071-Custom-replacement-for-eaten-items.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jedediah Smith -Date: Sun, 21 Jun 2015 15:07:20 -0400 -Subject: [PATCH] Custom replacement for eaten items - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 3eeb40c2176a80b9e2a472d43671ae0fe087d9e7..3179fc1b33f503b0cdd462ad160edee760b5a7eb 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3698,10 +3698,11 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.triggerItemUseEffects(this.useItem, 16); - // CraftBukkit start - fire PlayerItemConsumeEvent - ItemStack itemstack; -+ PlayerItemConsumeEvent event = null; // Paper - if (this instanceof ServerPlayer) { - org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitCopy(this.useItem); - org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(enumhand); -- PlayerItemConsumeEvent event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem, hand); -+ event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem, hand); // Paper - this.level().getCraftServer().getPluginManager().callEvent(event); - - if (event.isCancelled()) { -@@ -3715,6 +3716,12 @@ public abstract class LivingEntity extends Entity implements Attackable { - } else { - itemstack = this.useItem.finishUsingItem(this.level(), this); - } -+ // Paper start - save the default replacement item and change it if necessary -+ final ItemStack defaultReplacement = itemstack; -+ if (event != null && event.getReplacement() != null) { -+ itemstack = CraftItemStack.asNMSCopy(event.getReplacement()); -+ } -+ // Paper end - // CraftBukkit end - - if (itemstack != this.useItem) { -@@ -3722,6 +3729,11 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - this.stopUsingItem(); -+ // Paper start - if the replacement is anything but the default, update the client inventory -+ if (this instanceof ServerPlayer && !com.google.common.base.Objects.equal(defaultReplacement, itemstack)) { -+ ((ServerPlayer) this).getBukkitEntity().updateInventory(); -+ } -+ // Paper end - } - - } diff --git a/patches/server/0071-handle-NaN-health-absorb-values-and-repair-bad-data.patch b/patches/server/0071-handle-NaN-health-absorb-values-and-repair-bad-data.patch new file mode 100644 index 000000000000..47da543ce464 --- /dev/null +++ b/patches/server/0071-handle-NaN-health-absorb-values-and-repair-bad-data.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 27 Sep 2015 01:18:02 -0400 +Subject: [PATCH] handle NaN health/absorb values and repair bad data + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 13da8e684172f9f95fbcb63a322b559b733f2469..406559f3a493e1be0d2bee8a6f3cc79668e2bb38 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -794,7 +794,13 @@ public abstract class LivingEntity extends Entity implements Attackable { + + @Override + public void readAdditionalSaveData(CompoundTag nbt) { +- this.internalSetAbsorptionAmount(nbt.getFloat("AbsorptionAmount")); ++ // Paper start - Check for NaN ++ float absorptionAmount = nbt.getFloat("AbsorptionAmount"); ++ if (Float.isNaN(absorptionAmount)) { ++ absorptionAmount = 0; ++ } ++ this.internalSetAbsorptionAmount(absorptionAmount); ++ // Paper end - Check for NaN + if (nbt.contains("Attributes", 9) && this.level() != null && !this.level().isClientSide) { + this.getAttributes().load(nbt.getList("Attributes", 10)); + } +@@ -1344,6 +1350,10 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + public void setHealth(float health) { ++ // Paper start - Check for NaN ++ if (Float.isNaN(health)) { health = getMaxHealth(); if (this.valid) { ++ System.err.println("[NAN-HEALTH] " + getScoreboardName() + " had NaN health set"); ++ } } // Paper end - Check for NaN + // CraftBukkit start - Handle scaled health + if (this instanceof ServerPlayer) { + org.bukkit.craftbukkit.entity.CraftPlayer player = ((ServerPlayer) this).getBukkitEntity(); +@@ -3544,7 +3554,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + public final void setAbsorptionAmount(float absorptionAmount) { +- this.internalSetAbsorptionAmount(Mth.clamp(absorptionAmount, 0.0F, this.getMaxAbsorption())); ++ this.internalSetAbsorptionAmount(!Float.isNaN(absorptionAmount) ? Mth.clamp(absorptionAmount, 0.0F, this.getMaxAbsorption()) : 0.0F); // Paper - Check for NaN + } + + protected void internalSetAbsorptionAmount(float absorptionAmount) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index b6851bd629c4d3b9aa7efdf1112e1cf59cd63f60..b79aea8ae49a4edbb45f0824535fd38d3686e67b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2258,6 +2258,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + public void setRealHealth(double health) { ++ if (Double.isNaN(health)) {return;} // Paper - Check for NaN + this.health = health; + } + diff --git a/patches/server/0072-Use-a-Shared-Random-for-Entities.patch b/patches/server/0072-Use-a-Shared-Random-for-Entities.patch new file mode 100644 index 000000000000..38fbe5985fe0 --- /dev/null +++ b/patches/server/0072-Use-a-Shared-Random-for-Entities.patch @@ -0,0 +1,113 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 22 Mar 2016 00:33:47 -0400 +Subject: [PATCH] Use a Shared Random for Entities + +Reduces memory usage and provides ensures more randomness, Especially since a lot of garbage entity objects get created. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index e7157ff6cd6f2c52593bba63129fdaa60fcbf886..dba9588d9c8b1291ec8fe401e4990f4750b790db 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -165,6 +165,79 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; + } + ++ // Paper start - Share random for entities to make them more random ++ public static RandomSource SHARED_RANDOM = new RandomRandomSource(); ++ private static final class RandomRandomSource extends java.util.Random implements net.minecraft.world.level.levelgen.BitRandomSource { ++ private boolean locked = false; ++ ++ @Override ++ public synchronized void setSeed(long seed) { ++ if (locked) { ++ LOGGER.error("Ignoring setSeed on Entity.SHARED_RANDOM", new Throwable()); ++ } else { ++ super.setSeed(seed); ++ locked = true; ++ } ++ } ++ ++ @Override ++ public RandomSource fork() { ++ return new net.minecraft.world.level.levelgen.LegacyRandomSource(this.nextLong()); ++ } ++ ++ @Override ++ public net.minecraft.world.level.levelgen.PositionalRandomFactory forkPositional() { ++ return new net.minecraft.world.level.levelgen.LegacyRandomSource.LegacyPositionalRandomFactory(this.nextLong()); ++ } ++ ++ // these below are added to fix reobf issues that I don't wanna deal with right now ++ @Override ++ public int next(int bits) { ++ return super.next(bits); ++ } ++ ++ @Override ++ public int nextInt(int origin, int bound) { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(origin, bound); ++ } ++ ++ @Override ++ public long nextLong() { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextLong(); ++ } ++ ++ @Override ++ public int nextInt() { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(); ++ } ++ ++ @Override ++ public int nextInt(int bound) { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(bound); ++ } ++ ++ @Override ++ public boolean nextBoolean() { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextBoolean(); ++ } ++ ++ @Override ++ public float nextFloat() { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextFloat(); ++ } ++ ++ @Override ++ public double nextDouble() { ++ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextDouble(); ++ } ++ ++ @Override ++ public double nextGaussian() { ++ return super.nextGaussian(); ++ } ++ } ++ // Paper end - Share random for entities to make them more random ++ + private CraftEntity bukkitEntity; + + public CraftEntity getBukkitEntity() { +@@ -361,7 +434,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + this.bb = Entity.INITIAL_AABB; + this.stuckSpeedMultiplier = Vec3.ZERO; + this.nextStep = 1.0F; +- this.random = RandomSource.create(); ++ this.random = SHARED_RANDOM; // Paper - Share random for entities to make them more random + this.remainingFireTicks = -this.getFireImmuneTicks(); + this.fluidHeight = new Object2DoubleArrayMap(2); + this.fluidOnEyes = new HashSet(); +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 891d8b4c8cb73d5e310970066831ab3e2af14e91..4f32597c7af34d599f6658fe4962d41624e60419 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 { + + public Squid(EntityType type, Level world) { + super(type, world); +- this.random.setSeed((long)this.getId()); ++ //this.random.setSeed((long)this.getId()); // Paper - Share random for entities to make them more random + this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F; + } + diff --git a/patches/server/0072-handle-NaN-health-absorb-values-and-repair-bad-data.patch b/patches/server/0072-handle-NaN-health-absorb-values-and-repair-bad-data.patch deleted file mode 100644 index fc8065c9453a..000000000000 --- a/patches/server/0072-handle-NaN-health-absorb-values-and-repair-bad-data.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 27 Sep 2015 01:18:02 -0400 -Subject: [PATCH] handle NaN health/absorb values and repair bad data - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 3179fc1b33f503b0cdd462ad160edee760b5a7eb..428a9d21b24e9c349bf766c16172ffd904f3b6a5 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -793,7 +793,13 @@ public abstract class LivingEntity extends Entity implements Attackable { - - @Override - public void readAdditionalSaveData(CompoundTag nbt) { -- this.internalSetAbsorptionAmount(nbt.getFloat("AbsorptionAmount")); -+ // Paper start - Check for NaN -+ float absorptionAmount = nbt.getFloat("AbsorptionAmount"); -+ if (Float.isNaN(absorptionAmount)) { -+ absorptionAmount = 0; -+ } -+ this.internalSetAbsorptionAmount(absorptionAmount); -+ // Paper end - Check for NaN - if (nbt.contains("Attributes", 9) && this.level() != null && !this.level().isClientSide) { - this.getAttributes().load(nbt.getList("Attributes", 10)); - } -@@ -1343,6 +1349,10 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public void setHealth(float health) { -+ // Paper start - Check for NaN -+ if (Float.isNaN(health)) { health = getMaxHealth(); if (this.valid) { -+ System.err.println("[NAN-HEALTH] " + getScoreboardName() + " had NaN health set"); -+ } } // Paper end - Check for NaN - // CraftBukkit start - Handle scaled health - if (this instanceof ServerPlayer) { - org.bukkit.craftbukkit.entity.CraftPlayer player = ((ServerPlayer) this).getBukkitEntity(); -@@ -3531,7 +3541,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public final void setAbsorptionAmount(float absorptionAmount) { -- this.internalSetAbsorptionAmount(Mth.clamp(absorptionAmount, 0.0F, this.getMaxAbsorption())); -+ this.internalSetAbsorptionAmount(!Float.isNaN(absorptionAmount) ? Mth.clamp(absorptionAmount, 0.0F, this.getMaxAbsorption()) : 0.0F); // Paper - Check for NaN - } - - protected void internalSetAbsorptionAmount(float absorptionAmount) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 8be468cb5305c0db5f46298aaf6c896b27ce4aee..af0b3ea4bdb474d15c3e0a8ad8e1d23abc4c3413 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2207,6 +2207,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - public void setRealHealth(double health) { -+ if (Double.isNaN(health)) {return;} // Paper - Check for NaN - this.health = health; - } - diff --git a/patches/server/0073-Configurable-spawn-chances-for-skeleton-horses.patch b/patches/server/0073-Configurable-spawn-chances-for-skeleton-horses.patch new file mode 100644 index 000000000000..604e850fb328 --- /dev/null +++ b/patches/server/0073-Configurable-spawn-chances-for-skeleton-horses.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 22 Mar 2016 12:04:28 -0500 +Subject: [PATCH] Configurable spawn chances for skeleton horses + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index c9113f7e0d9e1b9861f667c40e2702c6bb1d4e53..a127acc6c44bfd078e06c74b408d62df6e85d1fe 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -614,7 +614,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + + if (this.isRainingAt(blockposition)) { + DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); +- boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * 0.01D && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); ++ boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper - Configurable spawn chances for skeleton horses + + if (flag1) { + SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this); diff --git a/patches/server/0073-Use-a-Shared-Random-for-Entities.patch b/patches/server/0073-Use-a-Shared-Random-for-Entities.patch deleted file mode 100644 index c5c3e67cfe80..000000000000 --- a/patches/server/0073-Use-a-Shared-Random-for-Entities.patch +++ /dev/null @@ -1,113 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 22 Mar 2016 00:33:47 -0400 -Subject: [PATCH] Use a Shared Random for Entities - -Reduces memory usage and provides ensures more randomness, Especially since a lot of garbage entity objects get created. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index c4168fa278dacf9f50058bb7dc98bb12aef717f2..b5138df02005e30c1788c97bd9dcbcf2c5fb5d34 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -165,6 +165,79 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; - } - -+ // Paper start - Share random for entities to make them more random -+ public static RandomSource SHARED_RANDOM = new RandomRandomSource(); -+ private static final class RandomRandomSource extends java.util.Random implements net.minecraft.world.level.levelgen.BitRandomSource { -+ private boolean locked = false; -+ -+ @Override -+ public synchronized void setSeed(long seed) { -+ if (locked) { -+ LOGGER.error("Ignoring setSeed on Entity.SHARED_RANDOM", new Throwable()); -+ } else { -+ super.setSeed(seed); -+ locked = true; -+ } -+ } -+ -+ @Override -+ public RandomSource fork() { -+ return new net.minecraft.world.level.levelgen.LegacyRandomSource(this.nextLong()); -+ } -+ -+ @Override -+ public net.minecraft.world.level.levelgen.PositionalRandomFactory forkPositional() { -+ return new net.minecraft.world.level.levelgen.LegacyRandomSource.LegacyPositionalRandomFactory(this.nextLong()); -+ } -+ -+ // these below are added to fix reobf issues that I don't wanna deal with right now -+ @Override -+ public int next(int bits) { -+ return super.next(bits); -+ } -+ -+ @Override -+ public int nextInt(int origin, int bound) { -+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(origin, bound); -+ } -+ -+ @Override -+ public long nextLong() { -+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextLong(); -+ } -+ -+ @Override -+ public int nextInt() { -+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(); -+ } -+ -+ @Override -+ public int nextInt(int bound) { -+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextInt(bound); -+ } -+ -+ @Override -+ public boolean nextBoolean() { -+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextBoolean(); -+ } -+ -+ @Override -+ public float nextFloat() { -+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextFloat(); -+ } -+ -+ @Override -+ public double nextDouble() { -+ return net.minecraft.world.level.levelgen.BitRandomSource.super.nextDouble(); -+ } -+ -+ @Override -+ public double nextGaussian() { -+ return super.nextGaussian(); -+ } -+ } -+ // Paper end - Share random for entities to make them more random -+ - private CraftEntity bukkitEntity; - - public CraftEntity getBukkitEntity() { -@@ -361,7 +434,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - this.bb = Entity.INITIAL_AABB; - this.stuckSpeedMultiplier = Vec3.ZERO; - this.nextStep = 1.0F; -- this.random = RandomSource.create(); -+ this.random = SHARED_RANDOM; // Paper - Share random for entities to make them more random - this.remainingFireTicks = -this.getFireImmuneTicks(); - this.fluidHeight = new Object2DoubleArrayMap(2); - this.fluidOnEyes = new HashSet(); -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 891d8b4c8cb73d5e310970066831ab3e2af14e91..4f32597c7af34d599f6658fe4962d41624e60419 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 { - - public Squid(EntityType type, Level world) { - super(type, world); -- this.random.setSeed((long)this.getId()); -+ //this.random.setSeed((long)this.getId()); // Paper - Share random for entities to make them more random - this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F; - } - diff --git a/patches/server/0074-Configurable-spawn-chances-for-skeleton-horses.patch b/patches/server/0074-Configurable-spawn-chances-for-skeleton-horses.patch deleted file mode 100644 index d7d438869c10..000000000000 --- a/patches/server/0074-Configurable-spawn-chances-for-skeleton-horses.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Tue, 22 Mar 2016 12:04:28 -0500 -Subject: [PATCH] Configurable spawn chances for skeleton horses - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 4b6fc197c6e0544f2ec993ea863e9dd560b05f11..22f6260ffa7b9fb2f5f226e505e81b3e29760437 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -614,7 +614,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - - if (this.isRainingAt(blockposition)) { - DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); -- boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * 0.01D && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); -+ boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper - Configurable spawn chances for skeleton horses - - if (flag1) { - SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this); diff --git a/patches/server/0075-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch b/patches/server/0074-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch similarity index 100% rename from patches/server/0075-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch rename to patches/server/0074-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch diff --git a/patches/server/0075-Entity-AddTo-RemoveFrom-World-Events.patch b/patches/server/0075-Entity-AddTo-RemoveFrom-World-Events.patch new file mode 100644 index 000000000000..953744dca374 --- /dev/null +++ b/patches/server/0075-Entity-AddTo-RemoveFrom-World-Events.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 28 Mar 2016 20:32:58 -0400 +Subject: [PATCH] Entity AddTo/RemoveFrom World Events + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 5975cc2fa72609ea5f3e6f99155d6e5bc321a321..0da9a1f428e2fa412de08296e1bafaea0ddd05d0 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2161,6 +2161,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + entity.setOrigin(entity.getOriginVector().toLocation(getWorld())); + } + // Paper end - Entity origin API ++ new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid + } + + public void onTrackingEnd(Entity entity) { +@@ -2236,6 +2237,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + } + // CraftBukkit end ++ new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid + } + + public void onSectionChange(Entity entity) { diff --git a/patches/server/0077-Configurable-Chunk-Inhabited-Time.patch b/patches/server/0076-Configurable-Chunk-Inhabited-Time.patch similarity index 100% rename from patches/server/0077-Configurable-Chunk-Inhabited-Time.patch rename to patches/server/0076-Configurable-Chunk-Inhabited-Time.patch diff --git a/patches/server/0076-Entity-AddTo-RemoveFrom-World-Events.patch b/patches/server/0076-Entity-AddTo-RemoveFrom-World-Events.patch deleted file mode 100644 index 61e1e7d0aef4..000000000000 --- a/patches/server/0076-Entity-AddTo-RemoveFrom-World-Events.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Mon, 28 Mar 2016 20:32:58 -0400 -Subject: [PATCH] Entity AddTo/RemoveFrom World Events - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index b4fdcfa28347beafe75339782d6e0cd7fc0b6256..f5c271c604dc74d9d82b44591ffc09e04567e3ab 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2161,6 +2161,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - entity.setOrigin(entity.getOriginVector().toLocation(getWorld())); - } - // Paper end - Entity origin API -+ new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid - } - - public void onTrackingEnd(Entity entity) { -@@ -2236,6 +2237,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - } - // CraftBukkit end -+ new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid - } - - public void onSectionChange(Entity entity) { diff --git a/patches/server/0078-EntityPathfindEvent.patch b/patches/server/0077-EntityPathfindEvent.patch similarity index 100% rename from patches/server/0078-EntityPathfindEvent.patch rename to patches/server/0077-EntityPathfindEvent.patch diff --git a/patches/server/0079-Sanitise-RegionFileCache-and-make-configurable.patch b/patches/server/0078-Sanitise-RegionFileCache-and-make-configurable.patch similarity index 100% rename from patches/server/0079-Sanitise-RegionFileCache-and-make-configurable.patch rename to patches/server/0078-Sanitise-RegionFileCache-and-make-configurable.patch diff --git a/patches/server/0080-Do-not-load-chunks-for-Pathfinding.patch b/patches/server/0079-Do-not-load-chunks-for-Pathfinding.patch similarity index 100% rename from patches/server/0080-Do-not-load-chunks-for-Pathfinding.patch rename to patches/server/0079-Do-not-load-chunks-for-Pathfinding.patch diff --git a/patches/server/0080-Add-PlayerUseUnknownEntityEvent.patch b/patches/server/0080-Add-PlayerUseUnknownEntityEvent.patch new file mode 100644 index 000000000000..2c0bfdcfa8a8 --- /dev/null +++ b/patches/server/0080-Add-PlayerUseUnknownEntityEvent.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jedediah Smith +Date: Sat, 2 Apr 2016 05:09:16 -0400 +Subject: [PATCH] Add PlayerUseUnknownEntityEvent + +Adds the PlayerUseUnknownEntityEvent to be used by plugins dealing with +virtual entities/entities that are not actually known to the server. + +Co-authored-by: Nassim Jahnke + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundInteractPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundInteractPacket.java +index 644a0fdea6576647539b96528717dbaeab498d93..221e64a66ff12a8de5c75992fc26a54a03b317e7 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ServerboundInteractPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundInteractPacket.java +@@ -169,4 +169,14 @@ public class ServerboundInteractPacket implements Packet -Date: Sat, 2 Apr 2016 05:09:16 -0400 -Subject: [PATCH] Add PlayerUseUnknownEntityEvent - -Adds the PlayerUseUnknownEntityEvent to be used by plugins dealing with -virtual entities/entities that are not actually known to the server. - -Co-authored-by: Nassim Jahnke - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundInteractPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundInteractPacket.java -index 644a0fdea6576647539b96528717dbaeab498d93..221e64a66ff12a8de5c75992fc26a54a03b317e7 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ServerboundInteractPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundInteractPacket.java -@@ -169,4 +169,14 @@ public class ServerboundInteractPacket implements Packet +Date: Sun, 3 Apr 2016 17:48:50 -0400 +Subject: [PATCH] Fix Cancelling BlockPlaceEvent triggering physics + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 0da9a1f428e2fa412de08296e1bafaea0ddd05d0..ddab865f4237b91020f3c5f9726d93f8dbf0eda6 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1384,6 +1384,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + + @Override + public void updateNeighborsAt(BlockPos pos, Block sourceBlock) { ++ if (captureBlockStates) { return; } // Paper - Cancel all physics during placement + this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, sourceBlock, (Direction) null); + } + diff --git a/patches/server/0083-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch b/patches/server/0083-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch deleted file mode 100644 index 164408279e4f..000000000000 --- a/patches/server/0083-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 3 Apr 2016 17:48:50 -0400 -Subject: [PATCH] Fix Cancelling BlockPlaceEvent triggering physics - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index f5c271c604dc74d9d82b44591ffc09e04567e3ab..dcf2b9820eb9ad92dc7dc559e509a694d3949f02 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1384,6 +1384,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - - @Override - public void updateNeighborsAt(BlockPos pos, Block sourceBlock) { -+ if (captureBlockStates) { return; } // Paper - Cancel all physics during placement - this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, sourceBlock, (Direction) null); - } - diff --git a/patches/server/0084-Optimize-DataBits.patch b/patches/server/0083-Optimize-DataBits.patch similarity index 100% rename from patches/server/0084-Optimize-DataBits.patch rename to patches/server/0083-Optimize-DataBits.patch diff --git a/patches/server/0085-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch b/patches/server/0084-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch similarity index 100% rename from patches/server/0085-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch rename to patches/server/0084-Option-to-use-vanilla-per-world-scoreboard-coloring-.patch diff --git a/patches/server/0085-Configurable-Player-Collision.patch b/patches/server/0085-Configurable-Player-Collision.patch new file mode 100644 index 000000000000..61efd80be7ff --- /dev/null +++ b/patches/server/0085-Configurable-Player-Collision.patch @@ -0,0 +1,101 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 13 Apr 2016 02:10:49 -0400 +Subject: [PATCH] Configurable Player Collision + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java +index 3d2a689d11bfa230bab61a2a65c6051328f6b78d..cf31771648549ab6d7e4e38b30409ca48a976bea 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java +@@ -193,7 +193,7 @@ public class ClientboundSetPlayerTeamPacket implements Packet toRemove = scoreboard.getPlayerTeams().stream().filter(team -> team.getName().startsWith("collideRule_")).map(net.minecraft.world.scores.PlayerTeam::getName).collect(java.util.stream.Collectors.toList()); ++ for (String teamName : toRemove) { ++ scoreboard.removePlayerTeam(scoreboard.getPlayerTeam(teamName)); // Clean up after ourselves ++ } ++ ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions) { ++ this.getPlayerList().collideRuleTeamName = org.apache.commons.lang3.StringUtils.left("collideRule_" + java.util.concurrent.ThreadLocalRandom.current().nextInt(), 16); ++ net.minecraft.world.scores.PlayerTeam collideTeam = scoreboard.addPlayerTeam(this.getPlayerList().collideRuleTeamName); ++ collideTeam.setSeeFriendlyInvisibles(false); // Because we want to mimic them not being on a team at all ++ } ++ // Paper end - Configurable player collision ++ + this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD); + this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP)); + this.connection.acceptConnections(); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index e870be760f00bac1d5eb32ed6d22c4dc8a8ebf78..a8a0b00a6bcc52a94b99d520915c21cc3155bb06 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -156,6 +156,7 @@ public abstract class PlayerList { + // CraftBukkit start + private CraftServer cserver; + private final Map playersByName = new java.util.HashMap<>(); ++ public @Nullable String collideRuleTeamName; // Paper - Configurable player collision + + public PlayerList(MinecraftServer server, LayeredRegistryAccess registryManager, PlayerDataStorage saveHandler, int maxPlayers) { + this.cserver = server.server = new CraftServer((DedicatedServer) server, this); +@@ -398,6 +399,13 @@ public abstract class PlayerList { + + player.initInventoryMenu(); + // CraftBukkit - Moved from above, added world ++ // Paper start - Configurable player collision; Add to collideRule team if needed ++ final net.minecraft.world.scores.Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard(); ++ final PlayerTeam collideRuleTeam = scoreboard.getPlayerTeam(this.collideRuleTeamName); ++ if (this.collideRuleTeamName != null && collideRuleTeam != null && player.getTeam() == null) { ++ scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); ++ } ++ // Paper end - Configurable player collision + PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); + } + +@@ -521,6 +529,16 @@ public abstract class PlayerList { + entityplayer.doTick(); // SPIGOT-924 + // CraftBukkit end + ++ // Paper start - Configurable player collision; Remove from collideRule team if needed ++ 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); ++ } ++ } ++ // Paper end - Configurable player collision ++ + this.save(entityplayer); + if (entityplayer.isPassenger()) { + Entity entity = entityplayer.getRootVehicle(); +@@ -1161,6 +1179,13 @@ public abstract class PlayerList { + } + // CraftBukkit end + ++ // Paper start - Configurable player collision; Remove collideRule team if it exists ++ if (this.collideRuleTeamName != null) { ++ final net.minecraft.world.scores.Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard(); ++ final PlayerTeam team = scoreboard.getPlayersTeam(this.collideRuleTeamName); ++ if (team != null) scoreboard.removePlayerTeam(team); ++ } ++ // Paper end - Configurable player collision + } + + // CraftBukkit start diff --git a/patches/server/0087-Add-handshake-event-to-allow-plugins-to-handle-clien.patch b/patches/server/0086-Add-handshake-event-to-allow-plugins-to-handle-clien.patch similarity index 100% rename from patches/server/0087-Add-handshake-event-to-allow-plugins-to-handle-clien.patch rename to patches/server/0086-Add-handshake-event-to-allow-plugins-to-handle-clien.patch diff --git a/patches/server/0086-Configurable-Player-Collision.patch b/patches/server/0086-Configurable-Player-Collision.patch deleted file mode 100644 index 481f34f9ff80..000000000000 --- a/patches/server/0086-Configurable-Player-Collision.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 13 Apr 2016 02:10:49 -0400 -Subject: [PATCH] Configurable Player Collision - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java -index 3d2a689d11bfa230bab61a2a65c6051328f6b78d..cf31771648549ab6d7e4e38b30409ca48a976bea 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java -@@ -193,7 +193,7 @@ public class ClientboundSetPlayerTeamPacket implements Packet toRemove = scoreboard.getPlayerTeams().stream().filter(team -> team.getName().startsWith("collideRule_")).map(net.minecraft.world.scores.PlayerTeam::getName).collect(java.util.stream.Collectors.toList()); -+ for (String teamName : toRemove) { -+ scoreboard.removePlayerTeam(scoreboard.getPlayerTeam(teamName)); // Clean up after ourselves -+ } -+ -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions) { -+ this.getPlayerList().collideRuleTeamName = org.apache.commons.lang3.StringUtils.left("collideRule_" + java.util.concurrent.ThreadLocalRandom.current().nextInt(), 16); -+ net.minecraft.world.scores.PlayerTeam collideTeam = scoreboard.addPlayerTeam(this.getPlayerList().collideRuleTeamName); -+ collideTeam.setSeeFriendlyInvisibles(false); // Because we want to mimic them not being on a team at all -+ } -+ // Paper end - Configurable player collision -+ - this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD); - this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP)); - this.connection.acceptConnections(); -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index e870be760f00bac1d5eb32ed6d22c4dc8a8ebf78..a8a0b00a6bcc52a94b99d520915c21cc3155bb06 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -156,6 +156,7 @@ public abstract class PlayerList { - // CraftBukkit start - private CraftServer cserver; - private final Map playersByName = new java.util.HashMap<>(); -+ public @Nullable String collideRuleTeamName; // Paper - Configurable player collision - - public PlayerList(MinecraftServer server, LayeredRegistryAccess registryManager, PlayerDataStorage saveHandler, int maxPlayers) { - this.cserver = server.server = new CraftServer((DedicatedServer) server, this); -@@ -398,6 +399,13 @@ public abstract class PlayerList { - - player.initInventoryMenu(); - // CraftBukkit - Moved from above, added world -+ // Paper start - Configurable player collision; Add to collideRule team if needed -+ final net.minecraft.world.scores.Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard(); -+ final PlayerTeam collideRuleTeam = scoreboard.getPlayerTeam(this.collideRuleTeamName); -+ if (this.collideRuleTeamName != null && collideRuleTeam != null && player.getTeam() == null) { -+ scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); -+ } -+ // Paper end - Configurable player collision - PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); - } - -@@ -521,6 +529,16 @@ public abstract class PlayerList { - entityplayer.doTick(); // SPIGOT-924 - // CraftBukkit end - -+ // Paper start - Configurable player collision; Remove from collideRule team if needed -+ 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); -+ } -+ } -+ // Paper end - Configurable player collision -+ - this.save(entityplayer); - if (entityplayer.isPassenger()) { - Entity entity = entityplayer.getRootVehicle(); -@@ -1161,6 +1179,13 @@ public abstract class PlayerList { - } - // CraftBukkit end - -+ // Paper start - Configurable player collision; Remove collideRule team if it exists -+ if (this.collideRuleTeamName != null) { -+ final net.minecraft.world.scores.Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard(); -+ final PlayerTeam team = scoreboard.getPlayersTeam(this.collideRuleTeamName); -+ if (team != null) scoreboard.removePlayerTeam(team); -+ } -+ // Paper end - Configurable player collision - } - - // CraftBukkit start diff --git a/patches/server/0088-Configurable-RCON-IP-address.patch b/patches/server/0087-Configurable-RCON-IP-address.patch similarity index 100% rename from patches/server/0088-Configurable-RCON-IP-address.patch rename to patches/server/0087-Configurable-RCON-IP-address.patch diff --git a/patches/server/0088-EntityRegainHealthEvent-isFastRegen-API.patch b/patches/server/0088-EntityRegainHealthEvent-isFastRegen-API.patch new file mode 100644 index 000000000000..5b69457aa950 --- /dev/null +++ b/patches/server/0088-EntityRegainHealthEvent-isFastRegen-API.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Fri, 22 Apr 2016 01:43:11 -0500 +Subject: [PATCH] EntityRegainHealthEvent isFastRegen API + +Don't even get me started + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 406559f3a493e1be0d2bee8a6f3cc79668e2bb38..9b55a2b2863c546e88f1bfa9014eb84fd317f3c8 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1323,10 +1323,16 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason) { ++ // Paper start - Forward ++ heal(f, regainReason, false); ++ } ++ ++ public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason, boolean isFastRegen) { ++ // Paper end + float f1 = this.getHealth(); + + if (f1 > 0.0F) { +- EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), f, regainReason); ++ EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), f, regainReason, isFastRegen); // Paper + // Suppress during worldgen + if (this.valid) { + this.level().getCraftServer().getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/world/food/FoodData.java b/src/main/java/net/minecraft/world/food/FoodData.java +index 14b2c31909adc0e8e828d9a563ce7d33d73b2a5a..c3448707fd8a632b457cc97b35d08a9c6933d5ee 100644 +--- a/src/main/java/net/minecraft/world/food/FoodData.java ++++ b/src/main/java/net/minecraft/world/food/FoodData.java +@@ -83,7 +83,7 @@ public class FoodData { + if (this.tickTimer >= this.saturatedRegenRate) { // CraftBukkit + float f = Math.min(this.saturationLevel, 6.0F); + +- player.heal(f / 6.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED); // CraftBukkit - added RegainReason ++ player.heal(f / 6.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED, true); // CraftBukkit - added RegainReason // Paper - This is fast regen + // this.addExhaustion(f); CraftBukkit - EntityExhaustionEvent + player.causeFoodExhaustion(f, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.REGEN); // CraftBukkit - EntityExhaustionEvent + this.tickTimer = 0; diff --git a/patches/server/0090-Add-ability-to-configure-frosted_ice-properties.patch b/patches/server/0089-Add-ability-to-configure-frosted_ice-properties.patch similarity index 100% rename from patches/server/0090-Add-ability-to-configure-frosted_ice-properties.patch rename to patches/server/0089-Add-ability-to-configure-frosted_ice-properties.patch diff --git a/patches/server/0089-EntityRegainHealthEvent-isFastRegen-API.patch b/patches/server/0089-EntityRegainHealthEvent-isFastRegen-API.patch deleted file mode 100644 index 99f0670424df..000000000000 --- a/patches/server/0089-EntityRegainHealthEvent-isFastRegen-API.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Fri, 22 Apr 2016 01:43:11 -0500 -Subject: [PATCH] EntityRegainHealthEvent isFastRegen API - -Don't even get me started - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 428a9d21b24e9c349bf766c16172ffd904f3b6a5..fad14002768e55f5b300d1829f16b3d7b7504bf6 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1322,10 +1322,16 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason) { -+ // Paper start - Forward -+ heal(f, regainReason, false); -+ } -+ -+ public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason, boolean isFastRegen) { -+ // Paper end - float f1 = this.getHealth(); - - if (f1 > 0.0F) { -- EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), f, regainReason); -+ EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), f, regainReason, isFastRegen); // Paper - // Suppress during worldgen - if (this.valid) { - this.level().getCraftServer().getPluginManager().callEvent(event); -diff --git a/src/main/java/net/minecraft/world/food/FoodData.java b/src/main/java/net/minecraft/world/food/FoodData.java -index 14b2c31909adc0e8e828d9a563ce7d33d73b2a5a..c3448707fd8a632b457cc97b35d08a9c6933d5ee 100644 ---- a/src/main/java/net/minecraft/world/food/FoodData.java -+++ b/src/main/java/net/minecraft/world/food/FoodData.java -@@ -83,7 +83,7 @@ public class FoodData { - if (this.tickTimer >= this.saturatedRegenRate) { // CraftBukkit - float f = Math.min(this.saturationLevel, 6.0F); - -- player.heal(f / 6.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED); // CraftBukkit - added RegainReason -+ player.heal(f / 6.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED, true); // CraftBukkit - added RegainReason // Paper - This is fast regen - // this.addExhaustion(f); CraftBukkit - EntityExhaustionEvent - player.causeFoodExhaustion(f, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.REGEN); // CraftBukkit - EntityExhaustionEvent - this.tickTimer = 0; diff --git a/patches/server/0090-remove-null-possibility-for-getServer-singleton.patch b/patches/server/0090-remove-null-possibility-for-getServer-singleton.patch new file mode 100644 index 000000000000..928828b9e701 --- /dev/null +++ b/patches/server/0090-remove-null-possibility-for-getServer-singleton.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 28 Apr 2016 00:57:27 -0400 +Subject: [PATCH] remove null possibility for getServer singleton + +to stop IDE complaining about potential NPE + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 866ff31a6057eda7612cfa48c0028fb988deed64..2c500fed04982c502b3e6fb1687b38bfaaa37f69 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -190,6 +190,7 @@ import co.aikar.timings.MinecraftTimings; // Paper + + public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ServerInfo, CommandSource, AutoCloseable { + ++ private static MinecraftServer SERVER; // Paper + public static final Logger LOGGER = LogUtils.getLogger(); + public static final net.kyori.adventure.text.logger.slf4j.ComponentLogger COMPONENT_LOGGER = net.kyori.adventure.text.logger.slf4j.ComponentLogger.logger(LOGGER.getName()); // Paper + public static final String VANILLA_BRAND = "vanilla"; +@@ -321,6 +322,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { +@@ -2430,9 +2432,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Thu, 28 Apr 2016 00:57:27 -0400 -Subject: [PATCH] remove null possibility for getServer singleton - -to stop IDE complaining about potential NPE - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 2ce3a54a63b207940690508a277fae39baa032c3..712157ed10bff3ea5bc348623c4539990dbfc6fa 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -190,6 +190,7 @@ import co.aikar.timings.MinecraftTimings; // Paper - - public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ServerInfo, CommandSource, AutoCloseable { - -+ private static MinecraftServer SERVER; // Paper - public static final Logger LOGGER = LogUtils.getLogger(); - public static final net.kyori.adventure.text.logger.slf4j.ComponentLogger COMPONENT_LOGGER = net.kyori.adventure.text.logger.slf4j.ComponentLogger.logger(LOGGER.getName()); // Paper - public static final String VANILLA_BRAND = "vanilla"; -@@ -321,6 +322,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -@@ -2430,9 +2432,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sun, 1 May 2016 21:19:14 -0400 +Subject: [PATCH] LootTable API and replenishable lootables + +Provides an API to control the loot table for an object. +Also provides a feature that any Lootable Inventory (Chests in Structures) +can automatically replenish after a given time. + +This feature is good for long term worlds so that newer players +do not suffer with "Every chest has been looted" + +== AT == +public org.bukkit.craftbukkit.block.CraftBlockEntityState getTileEntity()Lnet/minecraft/world/level/block/entity/BlockEntity; +public org.bukkit.craftbukkit.block.CraftLootable setLootTable(Lorg/bukkit/loot/LootTable;J)V +public org.bukkit.craftbukkit.entity.CraftMinecartContainer setLootTable(Lorg/bukkit/loot/LootTable;J)V + +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9ca389ca789dc54bba3542cac0aac2e1dc66c870 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java +@@ -0,0 +1,63 @@ ++package com.destroystokyo.paper.loottable; ++ ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.vehicle.AbstractMinecartContainer; ++import net.minecraft.world.entity.vehicle.ContainerEntity; ++import net.minecraft.world.level.Level; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++ ++public class PaperContainerEntityLootableInventory implements PaperLootableEntityInventory { ++ ++ private final ContainerEntity entity; ++ ++ public PaperContainerEntityLootableInventory(ContainerEntity entity) { ++ this.entity = entity; ++ } ++ ++ @Override ++ public org.bukkit.loot.LootTable getLootTable() { ++ return entity.getLootTable() != null ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(entity.getLootTable())) : null; ++ } ++ ++ @Override ++ public void setLootTable(org.bukkit.loot.LootTable table, long seed) { ++ setLootTable(table); ++ setSeed(seed); ++ } ++ ++ @Override ++ public void setSeed(long seed) { ++ entity.setLootTableSeed(seed); ++ } ++ ++ @Override ++ public long getSeed() { ++ return entity.getLootTableSeed(); ++ } ++ ++ @Override ++ public void setLootTable(org.bukkit.loot.LootTable table) { ++ entity.setLootTable((table == null) ? null : CraftNamespacedKey.toMinecraft(table.getKey())); ++ } ++ ++ @Override ++ public PaperLootableInventoryData getLootableData() { ++ return entity.getLootableData(); ++ } ++ ++ @Override ++ public Entity getHandle() { ++ return entity.getEntity(); ++ } ++ ++ @Override ++ public LootableInventory getAPILootableInventory() { ++ return (LootableInventory) entity.getEntity().getBukkitEntity(); ++ } ++ ++ @Override ++ public Level getNMSWorld() { ++ return entity.level(); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..24c6ff57cd25533e71f8a1d0b3c0ece2fdbbf87e +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java +@@ -0,0 +1,33 @@ ++package com.destroystokyo.paper.loottable; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; ++import org.bukkit.Chunk; ++import org.bukkit.block.Block; ++ ++public interface PaperLootableBlockInventory extends LootableBlockInventory, PaperLootableInventory { ++ ++ RandomizableContainerBlockEntity getTileEntity(); ++ ++ @Override ++ default LootableInventory getAPILootableInventory() { ++ return this; ++ } ++ ++ @Override ++ default Level getNMSWorld() { ++ return this.getTileEntity().getLevel(); ++ } ++ ++ default Block getBlock() { ++ final BlockPos position = this.getTileEntity().getBlockPos(); ++ final Chunk bukkitChunk = this.getBukkitWorld().getChunkAt(org.bukkit.craftbukkit.block.CraftBlock.at(this.getNMSWorld(), position)); ++ return bukkitChunk.getBlock(position.getX(), position.getY(), position.getZ()); ++ } ++ ++ @Override ++ default PaperLootableInventoryData getLootableData() { ++ return this.getTileEntity().lootableData; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2fba5bc0f982e143ad5f5bda55d768edc5f847df +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java +@@ -0,0 +1,28 @@ ++package com.destroystokyo.paper.loottable; ++ ++import net.minecraft.world.level.Level; ++import org.bukkit.entity.Entity; ++ ++public interface PaperLootableEntityInventory extends LootableEntityInventory, PaperLootableInventory { ++ ++ net.minecraft.world.entity.Entity getHandle(); ++ ++ @Override ++ default LootableInventory getAPILootableInventory() { ++ return this; ++ } ++ ++ default Entity getEntity() { ++ return getHandle().getBukkitEntity(); ++ } ++ ++ @Override ++ default Level getNMSWorld() { ++ return getHandle().getCommandSenderWorld(); ++ } ++ ++ @Override ++ default PaperLootableInventoryData getLootableData() { ++ return getHandle().lootableData; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8e6dac2cef7af26ad74928eff631c1826c2980bb +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java +@@ -0,0 +1,75 @@ ++package com.destroystokyo.paper.loottable; ++ ++import org.bukkit.loot.Lootable; ++import java.util.UUID; ++import net.minecraft.world.level.Level; ++ ++public interface PaperLootableInventory extends LootableInventory, Lootable { ++ ++ PaperLootableInventoryData getLootableData(); ++ LootableInventory getAPILootableInventory(); ++ ++ Level getNMSWorld(); ++ ++ default org.bukkit.World getBukkitWorld() { ++ return getNMSWorld().getWorld(); ++ } ++ ++ @Override ++ default boolean isRefillEnabled() { ++ return getNMSWorld().paperConfig().lootables.autoReplenish; ++ } ++ ++ @Override ++ default boolean hasBeenFilled() { ++ return getLastFilled() != -1; ++ } ++ ++ @Override ++ default boolean hasPlayerLooted(UUID player) { ++ return getLootableData().hasPlayerLooted(player); ++ } ++ ++ @Override ++ default boolean canPlayerLoot(final UUID player) { ++ return getLootableData().canPlayerLoot(player, this.getNMSWorld().paperConfig()); ++ } ++ ++ @Override ++ default Long getLastLooted(UUID player) { ++ return getLootableData().getLastLooted(player); ++ } ++ ++ @Override ++ default boolean setHasPlayerLooted(UUID player, boolean looted) { ++ final boolean hasLooted = hasPlayerLooted(player); ++ if (hasLooted != looted) { ++ getLootableData().setPlayerLootedState(player, looted); ++ } ++ return hasLooted; ++ } ++ ++ @Override ++ default boolean hasPendingRefill() { ++ long nextRefill = getLootableData().getNextRefill(); ++ return nextRefill != -1 && nextRefill > getLootableData().getLastFill(); ++ } ++ ++ @Override ++ default long getLastFilled() { ++ return getLootableData().getLastFill(); ++ } ++ ++ @Override ++ default long getNextRefill() { ++ return getLootableData().getNextRefill(); ++ } ++ ++ @Override ++ default long setNextRefill(long refillAt) { ++ if (refillAt < -1) { ++ refillAt = -1; ++ } ++ return getLootableData().setNextRefill(refillAt); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6e72c43b9d3834eb91c02ce68e7d114ad907812d +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java +@@ -0,0 +1,188 @@ ++package com.destroystokyo.paper.loottable; ++ ++import io.papermc.paper.configuration.WorldConfiguration; ++import io.papermc.paper.configuration.type.DurationOrDisabled; ++import java.time.temporal.ChronoUnit; ++import java.util.concurrent.TimeUnit; ++import org.bukkit.entity.Player; ++import org.bukkit.loot.LootTable; ++import javax.annotation.Nullable; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.ListTag; ++import java.util.HashMap; ++import java.util.Map; ++import java.util.Random; ++import java.util.UUID; ++ ++public class PaperLootableInventoryData { ++ ++ private static final Random RANDOM = new Random(); ++ ++ private long lastFill = -1; ++ private long nextRefill = -1; ++ private int numRefills = 0; ++ private Map lootedPlayers; ++ private final PaperLootableInventory lootable; ++ ++ public PaperLootableInventoryData(PaperLootableInventory lootable) { ++ this.lootable = lootable; ++ } ++ ++ long getLastFill() { ++ return this.lastFill; ++ } ++ ++ long getNextRefill() { ++ return this.nextRefill; ++ } ++ ++ long setNextRefill(long nextRefill) { ++ long prev = this.nextRefill; ++ this.nextRefill = nextRefill; ++ return prev; ++ } ++ ++ public boolean shouldReplenish(@Nullable net.minecraft.world.entity.player.Player player) { ++ LootTable table = this.lootable.getLootTable(); ++ ++ // No Loot Table associated ++ if (table == null) { ++ return false; ++ } ++ ++ // ALWAYS process the first fill or if the feature is disabled ++ if (this.lastFill == -1 || !this.lootable.getNMSWorld().paperConfig().lootables.autoReplenish) { ++ return true; ++ } ++ ++ // Only process refills when a player is set ++ if (player == null) { ++ return false; ++ } ++ ++ // Chest is not scheduled for refill ++ if (this.nextRefill == -1) { ++ return false; ++ } ++ ++ final WorldConfiguration paperConfig = this.lootable.getNMSWorld().paperConfig(); ++ ++ // Check if max refills has been hit ++ if (paperConfig.lootables.maxRefills != -1 && this.numRefills >= paperConfig.lootables.maxRefills) { ++ return false; ++ } ++ ++ // Refill has not been reached ++ if (this.nextRefill > System.currentTimeMillis()) { ++ return false; ++ } ++ ++ ++ final Player bukkitPlayer = (Player) player.getBukkitEntity(); ++ LootableInventoryReplenishEvent event = new LootableInventoryReplenishEvent(bukkitPlayer, lootable.getAPILootableInventory()); ++ event.setCancelled(!canPlayerLoot(player.getUUID(), paperConfig)); ++ return event.callEvent(); ++ } ++ public void processRefill(@Nullable net.minecraft.world.entity.player.Player player) { ++ this.lastFill = System.currentTimeMillis(); ++ final WorldConfiguration paperConfig = this.lootable.getNMSWorld().paperConfig(); ++ if (paperConfig.lootables.autoReplenish) { ++ long min = paperConfig.lootables.refreshMin.seconds(); ++ long max = paperConfig.lootables.refreshMax.seconds(); ++ this.nextRefill = this.lastFill + (min + RANDOM.nextLong(max - min + 1)) * 1000L; ++ this.numRefills++; ++ if (paperConfig.lootables.resetSeedOnFill) { ++ this.lootable.setSeed(0); ++ } ++ if (player != null) { // This means that numRefills can be incremented without a player being in the lootedPlayers list - Seems to be EntityMinecartChest specific ++ this.setPlayerLootedState(player.getUUID(), true); ++ } ++ } else { ++ this.lootable.clearLootTable(); ++ } ++ } ++ ++ ++ public void loadNbt(CompoundTag base) { ++ if (!base.contains("Paper.LootableData", 10)) { // 10 = compound ++ return; ++ } ++ CompoundTag comp = base.getCompound("Paper.LootableData"); ++ if (comp.contains("lastFill")) { ++ this.lastFill = comp.getLong("lastFill"); ++ } ++ if (comp.contains("nextRefill")) { ++ this.nextRefill = comp.getLong("nextRefill"); ++ } ++ ++ if (comp.contains("numRefills")) { ++ this.numRefills = comp.getInt("numRefills"); ++ } ++ if (comp.contains("lootedPlayers", net.minecraft.nbt.Tag.TAG_LIST)) { ++ ListTag list = comp.getList("lootedPlayers", net.minecraft.nbt.Tag.TAG_COMPOUND); ++ final int size = list.size(); ++ if (size > 0) { ++ this.lootedPlayers = new HashMap<>(list.size()); ++ } ++ for (int i = 0; i < size; i++) { ++ final CompoundTag cmp = list.getCompound(i); ++ lootedPlayers.put(cmp.getUUID("UUID"), cmp.getLong("Time")); ++ } ++ } ++ } ++ public void saveNbt(CompoundTag base) { ++ CompoundTag comp = new CompoundTag(); ++ if (this.nextRefill != -1) { ++ comp.putLong("nextRefill", this.nextRefill); ++ } ++ if (this.lastFill != -1) { ++ comp.putLong("lastFill", this.lastFill); ++ } ++ if (this.numRefills != 0) { ++ comp.putInt("numRefills", this.numRefills); ++ } ++ if (this.lootedPlayers != null && !this.lootedPlayers.isEmpty()) { ++ ListTag list = new ListTag(); ++ for (Map.Entry entry : this.lootedPlayers.entrySet()) { ++ CompoundTag cmp = new CompoundTag(); ++ cmp.putUUID("UUID", entry.getKey()); ++ cmp.putLong("Time", entry.getValue()); ++ list.add(cmp); ++ } ++ comp.put("lootedPlayers", list); ++ } ++ ++ if (!comp.isEmpty()) { ++ base.put("Paper.LootableData", comp); ++ } ++ } ++ ++ void setPlayerLootedState(UUID player, boolean looted) { ++ if (looted && this.lootedPlayers == null) { ++ this.lootedPlayers = new HashMap<>(); ++ } ++ if (looted) { ++ this.lootedPlayers.put(player, System.currentTimeMillis()); ++ } else if (this.lootedPlayers != null) { ++ this.lootedPlayers.remove(player); ++ } ++ } ++ ++ boolean canPlayerLoot(final UUID player, final WorldConfiguration worldConfiguration) { ++ final Long lastLooted = getLastLooted(player); ++ if (!worldConfiguration.lootables.restrictPlayerReloot || lastLooted == null) return true; ++ ++ final DurationOrDisabled restrictPlayerRelootTime = worldConfiguration.lootables.restrictPlayerRelootTime; ++ if (restrictPlayerRelootTime.value().isEmpty()) return false; ++ ++ return TimeUnit.SECONDS.toMillis(restrictPlayerRelootTime.value().get().seconds()) + lastLooted < System.currentTimeMillis(); ++ } ++ ++ boolean hasPlayerLooted(UUID player) { ++ return this.lootedPlayers != null && this.lootedPlayers.containsKey(player); ++ } ++ ++ Long getLastLooted(UUID player) { ++ return lootedPlayers != null ? lootedPlayers.get(player) : null; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9cfa5d36a6991067a3866e0d437749fafcc0158e +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java +@@ -0,0 +1,65 @@ ++package com.destroystokyo.paper.loottable; ++ ++import io.papermc.paper.util.MCUtil; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++ ++public class PaperTileEntityLootableInventory implements PaperLootableBlockInventory { ++ private RandomizableContainerBlockEntity tileEntityLootable; ++ ++ public PaperTileEntityLootableInventory(RandomizableContainerBlockEntity tileEntityLootable) { ++ this.tileEntityLootable = tileEntityLootable; ++ } ++ ++ @Override ++ public org.bukkit.loot.LootTable getLootTable() { ++ return tileEntityLootable.lootTable != null ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(tileEntityLootable.lootTable)) : null; ++ } ++ ++ @Override ++ public void setLootTable(org.bukkit.loot.LootTable table, long seed) { ++ setLootTable(table); ++ setSeed(seed); ++ } ++ ++ @Override ++ public void setLootTable(org.bukkit.loot.LootTable table) { ++ tileEntityLootable.lootTable = (table == null) ? null : CraftNamespacedKey.toMinecraft(table.getKey()); ++ } ++ ++ @Override ++ public void setSeed(long seed) { ++ tileEntityLootable.lootTableSeed = seed; ++ } ++ ++ @Override ++ public long getSeed() { ++ return tileEntityLootable.lootTableSeed; ++ } ++ ++ @Override ++ public PaperLootableInventoryData getLootableData() { ++ return tileEntityLootable.lootableData; ++ } ++ ++ @Override ++ public RandomizableContainerBlockEntity getTileEntity() { ++ return tileEntityLootable; ++ } ++ ++ @Override ++ public LootableInventory getAPILootableInventory() { ++ Level world = tileEntityLootable.getLevel(); ++ if (world == null) { ++ return null; ++ } ++ return (LootableInventory) getBukkitWorld().getBlockAt(MCUtil.toLocation(world, tileEntityLootable.getBlockPos())).getState(); ++ } ++ ++ @Override ++ public Level getNMSWorld() { ++ return tileEntityLootable.getLevel(); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index dba9588d9c8b1291ec8fe401e4990f4750b790db..158d830e7615ed396f7edd6b82daa4e4f876c894 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -238,6 +238,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + // Paper end - Share random for entities to make them more random + ++ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper + private CraftEntity bukkitEntity; + + public CraftEntity getBukkitEntity() { +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java +index 364cfa220b5c7c5351f1eb909066bef933da2c08..6d23c39e4eadf23616080d6d08672e13b5d3c37d 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java +@@ -32,6 +32,20 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme + public ResourceLocation lootTable; + public long lootTableSeed; + ++ // Paper start ++ { ++ this.lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(new com.destroystokyo.paper.loottable.PaperContainerEntityLootableInventory(this)); ++ } ++ @Override ++ public Entity getEntity() { ++ return this; ++ } ++ ++ @Override ++ public com.destroystokyo.paper.loottable.PaperLootableInventoryData getLootableData() { ++ return this.lootableData; ++ } ++ // Paper end + // CraftBukkit start + public List transaction = new java.util.ArrayList(); + private int maxStack = MAX_STACK; +@@ -134,12 +148,14 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme + @Override + protected void addAdditionalSaveData(CompoundTag nbt) { + super.addAdditionalSaveData(nbt); ++ this.lootableData.saveNbt(nbt); // Paper + this.addChestVehicleSaveData(nbt); + } + + @Override + protected void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); ++ this.lootableData.loadNbt(nbt); // Paper + this.readChestVehicleSaveData(nbt); + } + +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java +index 7bfdffc9b3c637bd6ac8ac3eb10961abdc5b1a7a..bc3fe45d12ffc2069a03d1587b7623d31130565a 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java +@@ -66,12 +66,14 @@ public class ChestBoat extends Boat implements HasCustomInventoryScreen, Contain + @Override + protected void addAdditionalSaveData(CompoundTag nbt) { + super.addAdditionalSaveData(nbt); ++ this.lootableData.saveNbt(nbt); // Paper + this.addChestVehicleSaveData(nbt); + } + + @Override + protected void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); ++ this.lootableData.loadNbt(nbt); // Paper + this.readChestVehicleSaveData(nbt); + } + +@@ -246,6 +248,20 @@ public class ChestBoat extends Boat implements HasCustomInventoryScreen, Contain + this.level().gameEvent(GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of((Entity) player)); + } + ++ // Paper start ++ { ++ this.lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(new com.destroystokyo.paper.loottable.PaperContainerEntityLootableInventory(this)); ++ } ++ @Override ++ public Entity getEntity() { ++ return this; ++ } ++ ++ @Override ++ public com.destroystokyo.paper.loottable.PaperLootableInventoryData getLootableData() { ++ return this.lootableData; ++ } ++ // Paper end + // CraftBukkit start + public List transaction = new java.util.ArrayList(); + private int maxStack = MAX_STACK; +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java +index e0fbacd574e0c83c2e1d164ded8e9ccf4af30480..7529751afa2932fd16bc4591189b0358268a7b14 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java +@@ -59,10 +59,9 @@ public interface ContainerEntity extends Container, MenuProvider { + if (this.getLootTableSeed() != 0L) { + nbt.putLong("LootTableSeed", this.getLootTableSeed()); + } +- } else { +- ContainerHelper.saveAllItems(nbt, this.getItemStacks()); + } + ++ ContainerHelper.saveAllItems(nbt, this.getItemStacks()); // Paper - always save the items, table may still remain + } + + default void readChestVehicleSaveData(CompoundTag nbt) { +@@ -70,10 +69,9 @@ public interface ContainerEntity extends Container, MenuProvider { + if (nbt.contains("LootTable", 8)) { + this.setLootTable(new ResourceLocation(nbt.getString("LootTable"))); + this.setLootTableSeed(nbt.getLong("LootTableSeed")); +- } else { +- ContainerHelper.loadAllItems(nbt, this.getItemStacks()); + } + ++ ContainerHelper.loadAllItems(nbt, this.getItemStacks()); // Paper - always load the items, table may still remain + } + + default void chestVehicleDestroyed(DamageSource source, Level world, Entity vehicle) { +@@ -96,13 +94,13 @@ public interface ContainerEntity extends Container, MenuProvider { + + default void unpackChestVehicleLootTable(@Nullable Player player) { + MinecraftServer minecraftServer = this.level().getServer(); +- if (this.getLootTable() != null && minecraftServer != null) { ++ if (this.getLootableData().shouldReplenish(player) && minecraftServer != null) { // Paper + LootTable lootTable = minecraftServer.getLootData().getLootTable(this.getLootTable()); + if (player != null) { + CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, this.getLootTable()); + } + +- this.setLootTable((ResourceLocation)null); ++ this.getLootableData().processRefill(player); // Paper + LootParams.Builder builder = (new LootParams.Builder((ServerLevel)this.level())).withParameter(LootContextParams.ORIGIN, this.position()); + if (player != null) { + builder.withLuck(player.getLuck()).withParameter(LootContextParams.THIS_ENTITY, player); +@@ -176,4 +174,13 @@ public interface ContainerEntity extends Container, MenuProvider { + default boolean isChestVehicleStillValid(Player player) { + return !this.isRemoved() && this.position().closerThan(player.position(), 8.0D); + } ++ // Paper start ++ default Entity getEntity() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ default com.destroystokyo.paper.loottable.PaperLootableInventoryData getLootableData() { ++ throw new UnsupportedOperationException(); ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +index aa4181e59f88be04a3605352fa5ceb3e04149dd3..7cbd403f9e96e7ce35475c8102cd9f9c04819c27 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +@@ -17,6 +17,7 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc + @Nullable + public ResourceLocation lootTable; + public long lootTableSeed; ++ public final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(new com.destroystokyo.paper.loottable.PaperTileEntityLootableInventory(this)); // Paper + + protected RandomizableContainerBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); +@@ -43,6 +44,52 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc + this.lootTableSeed = lootTableSeed; + } + ++ // Paper start ++ @Override ++ public boolean tryLoadLootTable(final net.minecraft.nbt.CompoundTag nbt) { ++ // Copied from super with changes, always check the original method ++ this.lootableData.loadNbt(nbt); // Paper ++ if (nbt.contains("LootTable", 8)) { ++ this.setLootTable(new ResourceLocation(nbt.getString("LootTable"))); ++ try { org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.lootTable); } catch (IllegalArgumentException ex) { this.lootTable = null; } // Paper - validate ++ this.setLootTableSeed(nbt.getLong("LootTableSeed")); ++ return false; // Paper - always load the items, table may still remain ++ } else { ++ return false; ++ } ++ } ++ ++ @Override ++ public boolean trySaveLootTable(final net.minecraft.nbt.CompoundTag nbt) { ++ this.lootableData.saveNbt(nbt); ++ RandomizableContainer.super.trySaveLootTable(nbt); ++ return false; ++ } ++ ++ @Override ++ public void unpackLootTable(@org.jetbrains.annotations.Nullable final Player player) { ++ // Copied from super with changes, always check the original method ++ net.minecraft.world.level.Level level = this.getLevel(); ++ BlockPos blockPos = this.getBlockPos(); ++ ResourceLocation resourceLocation = this.getLootTable(); ++ if (this.lootableData.shouldReplenish(player) && level != null) { // Paper ++ net.minecraft.world.level.storage.loot.LootTable lootTable = level.getServer().getLootData().getLootTable(resourceLocation); ++ if (player instanceof net.minecraft.server.level.ServerPlayer) { ++ net.minecraft.advancements.CriteriaTriggers.GENERATE_LOOT.trigger((net.minecraft.server.level.ServerPlayer)player, resourceLocation); ++ } ++ ++ this.lootableData.processRefill(player); // Paper ++ net.minecraft.world.level.storage.loot.LootParams.Builder builder = (new net.minecraft.world.level.storage.loot.LootParams.Builder((net.minecraft.server.level.ServerLevel)level)).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.ORIGIN, net.minecraft.world.phys.Vec3.atCenterOf(blockPos)); ++ if (player != null) { ++ builder.withLuck(player.getLuck()).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.THIS_ENTITY, player); ++ } ++ ++ lootTable.fill(this, builder.create(net.minecraft.world.level.storage.loot.parameters.LootContextParamSets.CHEST), this.getLootTableSeed()); ++ } ++ ++ } ++ // Paper end ++ + @Override + public boolean isEmpty() { + this.unpackLootTable((Player)null); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java +index 86076a9d2a3b1044c96518cbaeee66d60a8a22c6..c268513bc5719d80e1c3d73de53b85ec7f852fa9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java +@@ -64,7 +64,7 @@ public class CraftBrushableBlock extends CraftBlockEntityState implements Chest { ++public class CraftChest extends CraftLootable implements Chest, PaperLootableBlockInventory { // Paper + + public CraftChest(World world, ChestBlockEntity tileEntity) { + super(world, tileEntity); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java +index 2803e34bd29fd7a965093b507f11b5ee83bc5f09..f6942cb3ef1f9ef03708d4bc932ea9aeb1c13894 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java +@@ -9,7 +9,7 @@ import org.bukkit.craftbukkit.util.CraftNamespacedKey; + import org.bukkit.loot.LootTable; + import org.bukkit.loot.Lootable; + +-public abstract class CraftLootable extends CraftContainer implements Nameable, Lootable { ++public abstract class CraftLootable extends CraftContainer implements Nameable, Lootable, com.destroystokyo.paper.loottable.PaperLootableBlockInventory { // Paper + + public CraftLootable(World world, T tileEntity) { + super(world, tileEntity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java +index cc3f70fafea2d5c3a878cfe3e0a2db39ae713bf6..f1844d697b91e61878ade5b922cf2a3a538365c7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java +@@ -10,8 +10,7 @@ import org.bukkit.craftbukkit.util.CraftNamespacedKey; + import org.bukkit.inventory.Inventory; + import org.bukkit.loot.LootTable; + +-public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.ChestBoat { +- ++public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.ChestBoat, com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper + private final Inventory inventory; + + public CraftChestBoat(CraftServer server, ChestBoat entity) { +@@ -60,7 +59,7 @@ public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.Chest + return this.getHandle().getLootTableSeed(); + } + +- private void setLootTable(LootTable table, long seed) { ++ public void setLootTable(LootTable table, long seed) { // Paper - change visibility since it overrides a public method + ResourceLocation newKey = (table == null) ? null : CraftNamespacedKey.toMinecraft(table.getKey()); + this.getHandle().setLootTable(newKey); + this.getHandle().setLootTableSeed(seed); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java +index fd42f0b20132d08039ca7735d31a61806a6b07dc..b1a708de6790bbe336202b13ab862ced78de084f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java +@@ -7,7 +7,7 @@ import org.bukkit.entity.minecart.StorageMinecart; + import org.bukkit.inventory.Inventory; + + @SuppressWarnings("deprecation") +-public class CraftMinecartChest extends CraftMinecartContainer implements StorageMinecart { ++public class CraftMinecartChest extends CraftMinecartContainer implements StorageMinecart, com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper + private final CraftInventory inventory; + + public CraftMinecartChest(CraftServer server, MinecartChest entity) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java +index 39427b4f284e9402663be2b160ccb5f03f8b91da..17f5684cba9d3ed22d9925d1951520cc4751dfe2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java +@@ -6,7 +6,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventory; + import org.bukkit.entity.minecart.HopperMinecart; + import org.bukkit.inventory.Inventory; + +-public final class CraftMinecartHopper extends CraftMinecartContainer implements HopperMinecart { ++public final class CraftMinecartHopper extends CraftMinecartContainer implements HopperMinecart, com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper + private final CraftInventory inventory; + + public CraftMinecartHopper(CraftServer server, MinecartHopper entity) { diff --git a/patches/server/0093-LootTable-API-and-replenishable-lootables.patch b/patches/server/0093-LootTable-API-and-replenishable-lootables.patch deleted file mode 100644 index 57a27d07687a..000000000000 --- a/patches/server/0093-LootTable-API-and-replenishable-lootables.patch +++ /dev/null @@ -1,810 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 1 May 2016 21:19:14 -0400 -Subject: [PATCH] LootTable API and replenishable lootables - -Provides an API to control the loot table for an object. -Also provides a feature that any Lootable Inventory (Chests in Structures) -can automatically replenish after a given time. - -This feature is good for long term worlds so that newer players -do not suffer with "Every chest has been looted" - -== AT == -public org.bukkit.craftbukkit.block.CraftBlockEntityState getTileEntity()Lnet/minecraft/world/level/block/entity/BlockEntity; -public org.bukkit.craftbukkit.block.CraftLootable setLootTable(Lorg/bukkit/loot/LootTable;J)V -public org.bukkit.craftbukkit.entity.CraftMinecartContainer setLootTable(Lorg/bukkit/loot/LootTable;J)V - -diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9ca389ca789dc54bba3542cac0aac2e1dc66c870 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java -@@ -0,0 +1,63 @@ -+package com.destroystokyo.paper.loottable; -+ -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.vehicle.AbstractMinecartContainer; -+import net.minecraft.world.entity.vehicle.ContainerEntity; -+import net.minecraft.world.level.Level; -+import org.bukkit.Bukkit; -+import org.bukkit.craftbukkit.util.CraftNamespacedKey; -+ -+public class PaperContainerEntityLootableInventory implements PaperLootableEntityInventory { -+ -+ private final ContainerEntity entity; -+ -+ public PaperContainerEntityLootableInventory(ContainerEntity entity) { -+ this.entity = entity; -+ } -+ -+ @Override -+ public org.bukkit.loot.LootTable getLootTable() { -+ return entity.getLootTable() != null ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(entity.getLootTable())) : null; -+ } -+ -+ @Override -+ public void setLootTable(org.bukkit.loot.LootTable table, long seed) { -+ setLootTable(table); -+ setSeed(seed); -+ } -+ -+ @Override -+ public void setSeed(long seed) { -+ entity.setLootTableSeed(seed); -+ } -+ -+ @Override -+ public long getSeed() { -+ return entity.getLootTableSeed(); -+ } -+ -+ @Override -+ public void setLootTable(org.bukkit.loot.LootTable table) { -+ entity.setLootTable((table == null) ? null : CraftNamespacedKey.toMinecraft(table.getKey())); -+ } -+ -+ @Override -+ public PaperLootableInventoryData getLootableData() { -+ return entity.getLootableData(); -+ } -+ -+ @Override -+ public Entity getHandle() { -+ return entity.getEntity(); -+ } -+ -+ @Override -+ public LootableInventory getAPILootableInventory() { -+ return (LootableInventory) entity.getEntity().getBukkitEntity(); -+ } -+ -+ @Override -+ public Level getNMSWorld() { -+ return entity.level(); -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java -new file mode 100644 -index 0000000000000000000000000000000000000000..24c6ff57cd25533e71f8a1d0b3c0ece2fdbbf87e ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java -@@ -0,0 +1,33 @@ -+package com.destroystokyo.paper.loottable; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; -+import org.bukkit.Chunk; -+import org.bukkit.block.Block; -+ -+public interface PaperLootableBlockInventory extends LootableBlockInventory, PaperLootableInventory { -+ -+ RandomizableContainerBlockEntity getTileEntity(); -+ -+ @Override -+ default LootableInventory getAPILootableInventory() { -+ return this; -+ } -+ -+ @Override -+ default Level getNMSWorld() { -+ return this.getTileEntity().getLevel(); -+ } -+ -+ default Block getBlock() { -+ final BlockPos position = this.getTileEntity().getBlockPos(); -+ final Chunk bukkitChunk = this.getBukkitWorld().getChunkAt(org.bukkit.craftbukkit.block.CraftBlock.at(this.getNMSWorld(), position)); -+ return bukkitChunk.getBlock(position.getX(), position.getY(), position.getZ()); -+ } -+ -+ @Override -+ default PaperLootableInventoryData getLootableData() { -+ return this.getTileEntity().lootableData; -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2fba5bc0f982e143ad5f5bda55d768edc5f847df ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java -@@ -0,0 +1,28 @@ -+package com.destroystokyo.paper.loottable; -+ -+import net.minecraft.world.level.Level; -+import org.bukkit.entity.Entity; -+ -+public interface PaperLootableEntityInventory extends LootableEntityInventory, PaperLootableInventory { -+ -+ net.minecraft.world.entity.Entity getHandle(); -+ -+ @Override -+ default LootableInventory getAPILootableInventory() { -+ return this; -+ } -+ -+ default Entity getEntity() { -+ return getHandle().getBukkitEntity(); -+ } -+ -+ @Override -+ default Level getNMSWorld() { -+ return getHandle().getCommandSenderWorld(); -+ } -+ -+ @Override -+ default PaperLootableInventoryData getLootableData() { -+ return getHandle().lootableData; -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8e6dac2cef7af26ad74928eff631c1826c2980bb ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java -@@ -0,0 +1,75 @@ -+package com.destroystokyo.paper.loottable; -+ -+import org.bukkit.loot.Lootable; -+import java.util.UUID; -+import net.minecraft.world.level.Level; -+ -+public interface PaperLootableInventory extends LootableInventory, Lootable { -+ -+ PaperLootableInventoryData getLootableData(); -+ LootableInventory getAPILootableInventory(); -+ -+ Level getNMSWorld(); -+ -+ default org.bukkit.World getBukkitWorld() { -+ return getNMSWorld().getWorld(); -+ } -+ -+ @Override -+ default boolean isRefillEnabled() { -+ return getNMSWorld().paperConfig().lootables.autoReplenish; -+ } -+ -+ @Override -+ default boolean hasBeenFilled() { -+ return getLastFilled() != -1; -+ } -+ -+ @Override -+ default boolean hasPlayerLooted(UUID player) { -+ return getLootableData().hasPlayerLooted(player); -+ } -+ -+ @Override -+ default boolean canPlayerLoot(final UUID player) { -+ return getLootableData().canPlayerLoot(player, this.getNMSWorld().paperConfig()); -+ } -+ -+ @Override -+ default Long getLastLooted(UUID player) { -+ return getLootableData().getLastLooted(player); -+ } -+ -+ @Override -+ default boolean setHasPlayerLooted(UUID player, boolean looted) { -+ final boolean hasLooted = hasPlayerLooted(player); -+ if (hasLooted != looted) { -+ getLootableData().setPlayerLootedState(player, looted); -+ } -+ return hasLooted; -+ } -+ -+ @Override -+ default boolean hasPendingRefill() { -+ long nextRefill = getLootableData().getNextRefill(); -+ return nextRefill != -1 && nextRefill > getLootableData().getLastFill(); -+ } -+ -+ @Override -+ default long getLastFilled() { -+ return getLootableData().getLastFill(); -+ } -+ -+ @Override -+ default long getNextRefill() { -+ return getLootableData().getNextRefill(); -+ } -+ -+ @Override -+ default long setNextRefill(long refillAt) { -+ if (refillAt < -1) { -+ refillAt = -1; -+ } -+ return getLootableData().setNextRefill(refillAt); -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6e72c43b9d3834eb91c02ce68e7d114ad907812d ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java -@@ -0,0 +1,188 @@ -+package com.destroystokyo.paper.loottable; -+ -+import io.papermc.paper.configuration.WorldConfiguration; -+import io.papermc.paper.configuration.type.DurationOrDisabled; -+import java.time.temporal.ChronoUnit; -+import java.util.concurrent.TimeUnit; -+import org.bukkit.entity.Player; -+import org.bukkit.loot.LootTable; -+import javax.annotation.Nullable; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.ListTag; -+import java.util.HashMap; -+import java.util.Map; -+import java.util.Random; -+import java.util.UUID; -+ -+public class PaperLootableInventoryData { -+ -+ private static final Random RANDOM = new Random(); -+ -+ private long lastFill = -1; -+ private long nextRefill = -1; -+ private int numRefills = 0; -+ private Map lootedPlayers; -+ private final PaperLootableInventory lootable; -+ -+ public PaperLootableInventoryData(PaperLootableInventory lootable) { -+ this.lootable = lootable; -+ } -+ -+ long getLastFill() { -+ return this.lastFill; -+ } -+ -+ long getNextRefill() { -+ return this.nextRefill; -+ } -+ -+ long setNextRefill(long nextRefill) { -+ long prev = this.nextRefill; -+ this.nextRefill = nextRefill; -+ return prev; -+ } -+ -+ public boolean shouldReplenish(@Nullable net.minecraft.world.entity.player.Player player) { -+ LootTable table = this.lootable.getLootTable(); -+ -+ // No Loot Table associated -+ if (table == null) { -+ return false; -+ } -+ -+ // ALWAYS process the first fill or if the feature is disabled -+ if (this.lastFill == -1 || !this.lootable.getNMSWorld().paperConfig().lootables.autoReplenish) { -+ return true; -+ } -+ -+ // Only process refills when a player is set -+ if (player == null) { -+ return false; -+ } -+ -+ // Chest is not scheduled for refill -+ if (this.nextRefill == -1) { -+ return false; -+ } -+ -+ final WorldConfiguration paperConfig = this.lootable.getNMSWorld().paperConfig(); -+ -+ // Check if max refills has been hit -+ if (paperConfig.lootables.maxRefills != -1 && this.numRefills >= paperConfig.lootables.maxRefills) { -+ return false; -+ } -+ -+ // Refill has not been reached -+ if (this.nextRefill > System.currentTimeMillis()) { -+ return false; -+ } -+ -+ -+ final Player bukkitPlayer = (Player) player.getBukkitEntity(); -+ LootableInventoryReplenishEvent event = new LootableInventoryReplenishEvent(bukkitPlayer, lootable.getAPILootableInventory()); -+ event.setCancelled(!canPlayerLoot(player.getUUID(), paperConfig)); -+ return event.callEvent(); -+ } -+ public void processRefill(@Nullable net.minecraft.world.entity.player.Player player) { -+ this.lastFill = System.currentTimeMillis(); -+ final WorldConfiguration paperConfig = this.lootable.getNMSWorld().paperConfig(); -+ if (paperConfig.lootables.autoReplenish) { -+ long min = paperConfig.lootables.refreshMin.seconds(); -+ long max = paperConfig.lootables.refreshMax.seconds(); -+ this.nextRefill = this.lastFill + (min + RANDOM.nextLong(max - min + 1)) * 1000L; -+ this.numRefills++; -+ if (paperConfig.lootables.resetSeedOnFill) { -+ this.lootable.setSeed(0); -+ } -+ if (player != null) { // This means that numRefills can be incremented without a player being in the lootedPlayers list - Seems to be EntityMinecartChest specific -+ this.setPlayerLootedState(player.getUUID(), true); -+ } -+ } else { -+ this.lootable.clearLootTable(); -+ } -+ } -+ -+ -+ public void loadNbt(CompoundTag base) { -+ if (!base.contains("Paper.LootableData", 10)) { // 10 = compound -+ return; -+ } -+ CompoundTag comp = base.getCompound("Paper.LootableData"); -+ if (comp.contains("lastFill")) { -+ this.lastFill = comp.getLong("lastFill"); -+ } -+ if (comp.contains("nextRefill")) { -+ this.nextRefill = comp.getLong("nextRefill"); -+ } -+ -+ if (comp.contains("numRefills")) { -+ this.numRefills = comp.getInt("numRefills"); -+ } -+ if (comp.contains("lootedPlayers", net.minecraft.nbt.Tag.TAG_LIST)) { -+ ListTag list = comp.getList("lootedPlayers", net.minecraft.nbt.Tag.TAG_COMPOUND); -+ final int size = list.size(); -+ if (size > 0) { -+ this.lootedPlayers = new HashMap<>(list.size()); -+ } -+ for (int i = 0; i < size; i++) { -+ final CompoundTag cmp = list.getCompound(i); -+ lootedPlayers.put(cmp.getUUID("UUID"), cmp.getLong("Time")); -+ } -+ } -+ } -+ public void saveNbt(CompoundTag base) { -+ CompoundTag comp = new CompoundTag(); -+ if (this.nextRefill != -1) { -+ comp.putLong("nextRefill", this.nextRefill); -+ } -+ if (this.lastFill != -1) { -+ comp.putLong("lastFill", this.lastFill); -+ } -+ if (this.numRefills != 0) { -+ comp.putInt("numRefills", this.numRefills); -+ } -+ if (this.lootedPlayers != null && !this.lootedPlayers.isEmpty()) { -+ ListTag list = new ListTag(); -+ for (Map.Entry entry : this.lootedPlayers.entrySet()) { -+ CompoundTag cmp = new CompoundTag(); -+ cmp.putUUID("UUID", entry.getKey()); -+ cmp.putLong("Time", entry.getValue()); -+ list.add(cmp); -+ } -+ comp.put("lootedPlayers", list); -+ } -+ -+ if (!comp.isEmpty()) { -+ base.put("Paper.LootableData", comp); -+ } -+ } -+ -+ void setPlayerLootedState(UUID player, boolean looted) { -+ if (looted && this.lootedPlayers == null) { -+ this.lootedPlayers = new HashMap<>(); -+ } -+ if (looted) { -+ this.lootedPlayers.put(player, System.currentTimeMillis()); -+ } else if (this.lootedPlayers != null) { -+ this.lootedPlayers.remove(player); -+ } -+ } -+ -+ boolean canPlayerLoot(final UUID player, final WorldConfiguration worldConfiguration) { -+ final Long lastLooted = getLastLooted(player); -+ if (!worldConfiguration.lootables.restrictPlayerReloot || lastLooted == null) return true; -+ -+ final DurationOrDisabled restrictPlayerRelootTime = worldConfiguration.lootables.restrictPlayerRelootTime; -+ if (restrictPlayerRelootTime.value().isEmpty()) return false; -+ -+ return TimeUnit.SECONDS.toMillis(restrictPlayerRelootTime.value().get().seconds()) + lastLooted < System.currentTimeMillis(); -+ } -+ -+ boolean hasPlayerLooted(UUID player) { -+ return this.lootedPlayers != null && this.lootedPlayers.containsKey(player); -+ } -+ -+ Long getLastLooted(UUID player) { -+ return lootedPlayers != null ? lootedPlayers.get(player) : null; -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9cfa5d36a6991067a3866e0d437749fafcc0158e ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java -@@ -0,0 +1,65 @@ -+package com.destroystokyo.paper.loottable; -+ -+import io.papermc.paper.util.MCUtil; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; -+import org.bukkit.Bukkit; -+import org.bukkit.craftbukkit.util.CraftNamespacedKey; -+ -+public class PaperTileEntityLootableInventory implements PaperLootableBlockInventory { -+ private RandomizableContainerBlockEntity tileEntityLootable; -+ -+ public PaperTileEntityLootableInventory(RandomizableContainerBlockEntity tileEntityLootable) { -+ this.tileEntityLootable = tileEntityLootable; -+ } -+ -+ @Override -+ public org.bukkit.loot.LootTable getLootTable() { -+ return tileEntityLootable.lootTable != null ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(tileEntityLootable.lootTable)) : null; -+ } -+ -+ @Override -+ public void setLootTable(org.bukkit.loot.LootTable table, long seed) { -+ setLootTable(table); -+ setSeed(seed); -+ } -+ -+ @Override -+ public void setLootTable(org.bukkit.loot.LootTable table) { -+ tileEntityLootable.lootTable = (table == null) ? null : CraftNamespacedKey.toMinecraft(table.getKey()); -+ } -+ -+ @Override -+ public void setSeed(long seed) { -+ tileEntityLootable.lootTableSeed = seed; -+ } -+ -+ @Override -+ public long getSeed() { -+ return tileEntityLootable.lootTableSeed; -+ } -+ -+ @Override -+ public PaperLootableInventoryData getLootableData() { -+ return tileEntityLootable.lootableData; -+ } -+ -+ @Override -+ public RandomizableContainerBlockEntity getTileEntity() { -+ return tileEntityLootable; -+ } -+ -+ @Override -+ public LootableInventory getAPILootableInventory() { -+ Level world = tileEntityLootable.getLevel(); -+ if (world == null) { -+ return null; -+ } -+ return (LootableInventory) getBukkitWorld().getBlockAt(MCUtil.toLocation(world, tileEntityLootable.getBlockPos())).getState(); -+ } -+ -+ @Override -+ public Level getNMSWorld() { -+ return tileEntityLootable.getLevel(); -+ } -+} -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index b5138df02005e30c1788c97bd9dcbcf2c5fb5d34..a914d2906ae7433164d7f439a0f2f0d781b14747 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -238,6 +238,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - // Paper end - Share random for entities to make them more random - -+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper - private CraftEntity bukkitEntity; - - public CraftEntity getBukkitEntity() { -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java -index 364cfa220b5c7c5351f1eb909066bef933da2c08..6d23c39e4eadf23616080d6d08672e13b5d3c37d 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java -@@ -32,6 +32,20 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme - public ResourceLocation lootTable; - public long lootTableSeed; - -+ // Paper start -+ { -+ this.lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(new com.destroystokyo.paper.loottable.PaperContainerEntityLootableInventory(this)); -+ } -+ @Override -+ public Entity getEntity() { -+ return this; -+ } -+ -+ @Override -+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData getLootableData() { -+ return this.lootableData; -+ } -+ // Paper end - // CraftBukkit start - public List transaction = new java.util.ArrayList(); - private int maxStack = MAX_STACK; -@@ -134,12 +148,14 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme - @Override - protected void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -+ this.lootableData.saveNbt(nbt); // Paper - this.addChestVehicleSaveData(nbt); - } - - @Override - protected void readAdditionalSaveData(CompoundTag nbt) { - super.readAdditionalSaveData(nbt); -+ this.lootableData.loadNbt(nbt); // Paper - this.readChestVehicleSaveData(nbt); - } - -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java -index 7bfdffc9b3c637bd6ac8ac3eb10961abdc5b1a7a..bc3fe45d12ffc2069a03d1587b7623d31130565a 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java -@@ -66,12 +66,14 @@ public class ChestBoat extends Boat implements HasCustomInventoryScreen, Contain - @Override - protected void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -+ this.lootableData.saveNbt(nbt); // Paper - this.addChestVehicleSaveData(nbt); - } - - @Override - protected void readAdditionalSaveData(CompoundTag nbt) { - super.readAdditionalSaveData(nbt); -+ this.lootableData.loadNbt(nbt); // Paper - this.readChestVehicleSaveData(nbt); - } - -@@ -246,6 +248,20 @@ public class ChestBoat extends Boat implements HasCustomInventoryScreen, Contain - this.level().gameEvent(GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of((Entity) player)); - } - -+ // Paper start -+ { -+ this.lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(new com.destroystokyo.paper.loottable.PaperContainerEntityLootableInventory(this)); -+ } -+ @Override -+ public Entity getEntity() { -+ return this; -+ } -+ -+ @Override -+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData getLootableData() { -+ return this.lootableData; -+ } -+ // Paper end - // CraftBukkit start - public List transaction = new java.util.ArrayList(); - private int maxStack = MAX_STACK; -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java -index e0fbacd574e0c83c2e1d164ded8e9ccf4af30480..7529751afa2932fd16bc4591189b0358268a7b14 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java -@@ -59,10 +59,9 @@ public interface ContainerEntity extends Container, MenuProvider { - if (this.getLootTableSeed() != 0L) { - nbt.putLong("LootTableSeed", this.getLootTableSeed()); - } -- } else { -- ContainerHelper.saveAllItems(nbt, this.getItemStacks()); - } - -+ ContainerHelper.saveAllItems(nbt, this.getItemStacks()); // Paper - always save the items, table may still remain - } - - default void readChestVehicleSaveData(CompoundTag nbt) { -@@ -70,10 +69,9 @@ public interface ContainerEntity extends Container, MenuProvider { - if (nbt.contains("LootTable", 8)) { - this.setLootTable(new ResourceLocation(nbt.getString("LootTable"))); - this.setLootTableSeed(nbt.getLong("LootTableSeed")); -- } else { -- ContainerHelper.loadAllItems(nbt, this.getItemStacks()); - } - -+ ContainerHelper.loadAllItems(nbt, this.getItemStacks()); // Paper - always load the items, table may still remain - } - - default void chestVehicleDestroyed(DamageSource source, Level world, Entity vehicle) { -@@ -96,13 +94,13 @@ public interface ContainerEntity extends Container, MenuProvider { - - default void unpackChestVehicleLootTable(@Nullable Player player) { - MinecraftServer minecraftServer = this.level().getServer(); -- if (this.getLootTable() != null && minecraftServer != null) { -+ if (this.getLootableData().shouldReplenish(player) && minecraftServer != null) { // Paper - LootTable lootTable = minecraftServer.getLootData().getLootTable(this.getLootTable()); - if (player != null) { - CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, this.getLootTable()); - } - -- this.setLootTable((ResourceLocation)null); -+ this.getLootableData().processRefill(player); // Paper - LootParams.Builder builder = (new LootParams.Builder((ServerLevel)this.level())).withParameter(LootContextParams.ORIGIN, this.position()); - if (player != null) { - builder.withLuck(player.getLuck()).withParameter(LootContextParams.THIS_ENTITY, player); -@@ -176,4 +174,13 @@ public interface ContainerEntity extends Container, MenuProvider { - default boolean isChestVehicleStillValid(Player player) { - return !this.isRemoved() && this.position().closerThan(player.position(), 8.0D); - } -+ // Paper start -+ default Entity getEntity() { -+ throw new UnsupportedOperationException(); -+ } -+ -+ default com.destroystokyo.paper.loottable.PaperLootableInventoryData getLootableData() { -+ throw new UnsupportedOperationException(); -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java -index aa4181e59f88be04a3605352fa5ceb3e04149dd3..7cbd403f9e96e7ce35475c8102cd9f9c04819c27 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java -@@ -17,6 +17,7 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc - @Nullable - public ResourceLocation lootTable; - public long lootTableSeed; -+ public final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(new com.destroystokyo.paper.loottable.PaperTileEntityLootableInventory(this)); // Paper - - protected RandomizableContainerBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { - super(type, pos, state); -@@ -43,6 +44,52 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc - this.lootTableSeed = lootTableSeed; - } - -+ // Paper start -+ @Override -+ public boolean tryLoadLootTable(final net.minecraft.nbt.CompoundTag nbt) { -+ // Copied from super with changes, always check the original method -+ this.lootableData.loadNbt(nbt); // Paper -+ if (nbt.contains("LootTable", 8)) { -+ this.setLootTable(new ResourceLocation(nbt.getString("LootTable"))); -+ try { org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.lootTable); } catch (IllegalArgumentException ex) { this.lootTable = null; } // Paper - validate -+ this.setLootTableSeed(nbt.getLong("LootTableSeed")); -+ return false; // Paper - always load the items, table may still remain -+ } else { -+ return false; -+ } -+ } -+ -+ @Override -+ public boolean trySaveLootTable(final net.minecraft.nbt.CompoundTag nbt) { -+ this.lootableData.saveNbt(nbt); -+ RandomizableContainer.super.trySaveLootTable(nbt); -+ return false; -+ } -+ -+ @Override -+ public void unpackLootTable(@org.jetbrains.annotations.Nullable final Player player) { -+ // Copied from super with changes, always check the original method -+ net.minecraft.world.level.Level level = this.getLevel(); -+ BlockPos blockPos = this.getBlockPos(); -+ ResourceLocation resourceLocation = this.getLootTable(); -+ if (this.lootableData.shouldReplenish(player) && level != null) { // Paper -+ net.minecraft.world.level.storage.loot.LootTable lootTable = level.getServer().getLootData().getLootTable(resourceLocation); -+ if (player instanceof net.minecraft.server.level.ServerPlayer) { -+ net.minecraft.advancements.CriteriaTriggers.GENERATE_LOOT.trigger((net.minecraft.server.level.ServerPlayer)player, resourceLocation); -+ } -+ -+ this.lootableData.processRefill(player); // Paper -+ net.minecraft.world.level.storage.loot.LootParams.Builder builder = (new net.minecraft.world.level.storage.loot.LootParams.Builder((net.minecraft.server.level.ServerLevel)level)).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.ORIGIN, net.minecraft.world.phys.Vec3.atCenterOf(blockPos)); -+ if (player != null) { -+ builder.withLuck(player.getLuck()).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.THIS_ENTITY, player); -+ } -+ -+ lootTable.fill(this, builder.create(net.minecraft.world.level.storage.loot.parameters.LootContextParamSets.CHEST), this.getLootTableSeed()); -+ } -+ -+ } -+ // Paper end -+ - @Override - public boolean isEmpty() { - this.unpackLootTable((Player)null); -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java -index 86076a9d2a3b1044c96518cbaeee66d60a8a22c6..c268513bc5719d80e1c3d73de53b85ec7f852fa9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java -@@ -64,7 +64,7 @@ public class CraftBrushableBlock extends CraftBlockEntityState implements Chest { -+public class CraftChest extends CraftLootable implements Chest, PaperLootableBlockInventory { // Paper - - public CraftChest(World world, ChestBlockEntity tileEntity) { - super(world, tileEntity); -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java -index 2803e34bd29fd7a965093b507f11b5ee83bc5f09..f6942cb3ef1f9ef03708d4bc932ea9aeb1c13894 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java -@@ -9,7 +9,7 @@ import org.bukkit.craftbukkit.util.CraftNamespacedKey; - import org.bukkit.loot.LootTable; - import org.bukkit.loot.Lootable; - --public abstract class CraftLootable extends CraftContainer implements Nameable, Lootable { -+public abstract class CraftLootable extends CraftContainer implements Nameable, Lootable, com.destroystokyo.paper.loottable.PaperLootableBlockInventory { // Paper - - public CraftLootable(World world, T tileEntity) { - super(world, tileEntity); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java -index cc3f70fafea2d5c3a878cfe3e0a2db39ae713bf6..f1844d697b91e61878ade5b922cf2a3a538365c7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java -@@ -10,8 +10,7 @@ import org.bukkit.craftbukkit.util.CraftNamespacedKey; - import org.bukkit.inventory.Inventory; - import org.bukkit.loot.LootTable; - --public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.ChestBoat { -- -+public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.ChestBoat, com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper - private final Inventory inventory; - - public CraftChestBoat(CraftServer server, ChestBoat entity) { -@@ -60,7 +59,7 @@ public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.Chest - return this.getHandle().getLootTableSeed(); - } - -- private void setLootTable(LootTable table, long seed) { -+ public void setLootTable(LootTable table, long seed) { // Paper - change visibility since it overrides a public method - ResourceLocation newKey = (table == null) ? null : CraftNamespacedKey.toMinecraft(table.getKey()); - this.getHandle().setLootTable(newKey); - this.getHandle().setLootTableSeed(seed); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java -index fd42f0b20132d08039ca7735d31a61806a6b07dc..b1a708de6790bbe336202b13ab862ced78de084f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java -@@ -7,7 +7,7 @@ import org.bukkit.entity.minecart.StorageMinecart; - import org.bukkit.inventory.Inventory; - - @SuppressWarnings("deprecation") --public class CraftMinecartChest extends CraftMinecartContainer implements StorageMinecart { -+public class CraftMinecartChest extends CraftMinecartContainer implements StorageMinecart, com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper - private final CraftInventory inventory; - - public CraftMinecartChest(CraftServer server, MinecartChest entity) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java -index 39427b4f284e9402663be2b160ccb5f03f8b91da..17f5684cba9d3ed22d9925d1951520cc4751dfe2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java -@@ -6,7 +6,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventory; - import org.bukkit.entity.minecart.HopperMinecart; - import org.bukkit.inventory.Inventory; - --public final class CraftMinecartHopper extends CraftMinecartContainer implements HopperMinecart { -+public final class CraftMinecartHopper extends CraftMinecartContainer implements HopperMinecart, com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper - private final CraftInventory inventory; - - public CraftMinecartHopper(CraftServer server, MinecartHopper entity) { diff --git a/patches/server/0093-System-property-for-disabling-watchdoge.patch b/patches/server/0093-System-property-for-disabling-watchdoge.patch new file mode 100644 index 000000000000..22e1b2ab7cee --- /dev/null +++ b/patches/server/0093-System-property-for-disabling-watchdoge.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Thu, 12 May 2016 23:02:58 -0500 +Subject: [PATCH] System property for disabling watchdoge + + +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 4bfc2f1729e45e36307a98bd69de9c820123cb8e..ce17005f8c255eb3096736ef9f0d1de64b612d79 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -61,7 +61,7 @@ public class WatchdogThread extends Thread + while ( !this.stopping ) + { + // +- if ( this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.monotonicMillis() > this.lastTick + this.timeoutTime ) ++ if ( this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.monotonicMillis() > this.lastTick + this.timeoutTime && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable + { + Logger log = Bukkit.getServer().getLogger(); + log.log( Level.SEVERE, "------------------------------" ); diff --git a/patches/server/0094-Async-GameProfileCache-saving.patch b/patches/server/0094-Async-GameProfileCache-saving.patch new file mode 100644 index 000000000000..702f55431345 --- /dev/null +++ b/patches/server/0094-Async-GameProfileCache-saving.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 16 May 2016 20:47:41 -0400 +Subject: [PATCH] Async GameProfileCache saving + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 2c500fed04982c502b3e6fb1687b38bfaaa37f69..fcaa31ccd6f6e6affaccf76403dbab26e6932571 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -980,7 +980,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { // Paper - Perf: Async GameProfileCache saving + + try { + BufferedWriter bufferedwriter = Files.newWriter(this.file, StandardCharsets.UTF_8); +@@ -294,6 +295,14 @@ public class GameProfileCache { + } catch (IOException ioexception) { + ; + } ++ // Paper start - Perf: Async GameProfileCache saving ++ }; ++ if (asyncSave) { ++ io.papermc.paper.util.MCUtil.scheduleAsyncTask(save); ++ } else { ++ save.run(); ++ } ++ // Paper end - Perf: Async GameProfileCache saving + + } + diff --git a/patches/server/0094-System-property-for-disabling-watchdoge.patch b/patches/server/0094-System-property-for-disabling-watchdoge.patch deleted file mode 100644 index f789f1a00677..000000000000 --- a/patches/server/0094-System-property-for-disabling-watchdoge.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Thu, 12 May 2016 23:02:58 -0500 -Subject: [PATCH] System property for disabling watchdoge - - -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index fd3fe8f00981230f1115ad6821a6be9c9f09f7d3..ab3279be007553cface478795ace34e455615c7b 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -61,7 +61,7 @@ public class WatchdogThread extends Thread - while ( !this.stopping ) - { - // -- if ( this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.monotonicMillis() > this.lastTick + this.timeoutTime ) -+ if ( this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.monotonicMillis() > this.lastTick + this.timeoutTime && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable - { - Logger log = Bukkit.getServer().getLogger(); - log.log( Level.SEVERE, "------------------------------" ); diff --git a/patches/server/0095-Async-GameProfileCache-saving.patch b/patches/server/0095-Async-GameProfileCache-saving.patch deleted file mode 100644 index a2b1d603c021..000000000000 --- a/patches/server/0095-Async-GameProfileCache-saving.patch +++ /dev/null @@ -1,86 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Mon, 16 May 2016 20:47:41 -0400 -Subject: [PATCH] Async GameProfileCache saving - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 712157ed10bff3ea5bc348623c4539990dbfc6fa..1429dd8ca37e0b268304b92596fea316706e5c01 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -980,7 +980,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { // Paper - Perf: Async GameProfileCache saving - - try { - BufferedWriter bufferedwriter = Files.newWriter(this.file, StandardCharsets.UTF_8); -@@ -294,6 +295,14 @@ public class GameProfileCache { - } catch (IOException ioexception) { - ; - } -+ // Paper start - Perf: Async GameProfileCache saving -+ }; -+ if (asyncSave) { -+ io.papermc.paper.util.MCUtil.scheduleAsyncTask(save); -+ } else { -+ save.run(); -+ } -+ // Paper end - Perf: Async GameProfileCache saving - - } - diff --git a/patches/server/0096-Optional-TNT-doesn-t-move-in-water.patch b/patches/server/0095-Optional-TNT-doesn-t-move-in-water.patch similarity index 100% rename from patches/server/0096-Optional-TNT-doesn-t-move-in-water.patch rename to patches/server/0095-Optional-TNT-doesn-t-move-in-water.patch diff --git a/patches/server/0097-Faster-redstone-torch-rapid-clock-removal.patch b/patches/server/0096-Faster-redstone-torch-rapid-clock-removal.patch similarity index 100% rename from patches/server/0097-Faster-redstone-torch-rapid-clock-removal.patch rename to patches/server/0096-Faster-redstone-torch-rapid-clock-removal.patch diff --git a/patches/server/0098-Add-server-name-parameter.patch b/patches/server/0097-Add-server-name-parameter.patch similarity index 100% rename from patches/server/0098-Add-server-name-parameter.patch rename to patches/server/0097-Add-server-name-parameter.patch diff --git a/patches/server/0099-Fix-global-sound-handling.patch b/patches/server/0098-Fix-global-sound-handling.patch similarity index 100% rename from patches/server/0099-Fix-global-sound-handling.patch rename to patches/server/0098-Fix-global-sound-handling.patch diff --git a/patches/server/0100-Avoid-blocking-on-Network-Manager-creation.patch b/patches/server/0099-Avoid-blocking-on-Network-Manager-creation.patch similarity index 100% rename from patches/server/0100-Avoid-blocking-on-Network-Manager-creation.patch rename to patches/server/0099-Avoid-blocking-on-Network-Manager-creation.patch diff --git a/patches/server/0101-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch b/patches/server/0100-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch similarity index 100% rename from patches/server/0101-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch rename to patches/server/0100-Don-t-lookup-game-profiles-that-have-no-UUID-and-no-.patch diff --git a/patches/server/0101-Add-setting-for-proxy-online-mode-status.patch b/patches/server/0101-Add-setting-for-proxy-online-mode-status.patch new file mode 100644 index 000000000000..424349ba657c --- /dev/null +++ b/patches/server/0101-Add-setting-for-proxy-online-mode-status.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Gabriele C +Date: Fri, 5 Aug 2016 01:03:08 +0200 +Subject: [PATCH] Add setting for proxy online mode status + +TODO: Add isProxyOnlineMode check to Metrics + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 590da3a7acef406589e665652ccc46e01cc28e1b..c8163a8645248765c0fa6c15bc8c37facdc70dc7 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -557,7 +557,11 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + public boolean enforceSecureProfile() { + DedicatedServerProperties dedicatedserverproperties = this.getProperties(); + +- return dedicatedserverproperties.enforceSecureProfile && dedicatedserverproperties.onlineMode && this.services.canValidateProfileKeys(); ++ // Paper start - Add setting for proxy online mode status ++ return dedicatedserverproperties.enforceSecureProfile ++ && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() ++ && this.services.canValidateProfileKeys(); ++ // Paper end - Add setting for proxy online mode status + } + + @Override +diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java +index 3c9c6a697143c7e980add58576ad288b8f51ae35..92c22dc10e385f1942f2ec375bbce9faf257462b 100644 +--- a/src/main/java/net/minecraft/server/players/GameProfileCache.java ++++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java +@@ -89,7 +89,8 @@ public class GameProfileCache { + } + }; + +- if (!org.apache.commons.lang3.StringUtils.isBlank(name)) // Paper - Don't lookup a profile with a blank name ++ if (!org.apache.commons.lang3.StringUtils.isBlank(name) // Paper - Don't lookup a profile with a blank name ++ && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) // Paper - Add setting for proxy online mode status + repository.findProfilesByNames(new String[]{name}, profilelookupcallback); + GameProfile gameprofile = (GameProfile) atomicreference.get(); + +@@ -106,7 +107,7 @@ public class GameProfileCache { + } + + private static boolean usesAuthentication() { +- return GameProfileCache.usesAuthentication; ++ return io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode(); // Paper - Add setting for proxy online mode status + } + + public void add(GameProfile profile) { +diff --git a/src/main/java/net/minecraft/server/players/OldUsersConverter.java b/src/main/java/net/minecraft/server/players/OldUsersConverter.java +index 9143ce01650b51e7f6a677c5941ade91a506449f..86c88e81e275d52576122a5083b419e64cb011fc 100644 +--- a/src/main/java/net/minecraft/server/players/OldUsersConverter.java ++++ b/src/main/java/net/minecraft/server/players/OldUsersConverter.java +@@ -65,7 +65,8 @@ public class OldUsersConverter { + return new String[i]; + }); + +- if (server.usesAuthentication() || org.spigotmc.SpigotConfig.bungee) { // Spigot: bungee = online mode, for now. ++ if (server.usesAuthentication() || ++ (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode())) { // Spigot: bungee = online mode, for now. // Paper - Add setting for proxy online mode status + server.getProfileRepository().findProfilesByNames(astring, callback); + } else { + String[] astring1 = astring; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index f69d5e8f22fa8335b19f9e777ddbd33443eb08dc..1bb70e2363b8d046090c8f5853de590fa6777e7d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1833,7 +1833,7 @@ public final class CraftServer implements Server { + if (result == null) { + GameProfile profile = null; + // Only fetch an online UUID in online mode +- if (this.getOnlineMode() || org.spigotmc.SpigotConfig.bungee) { // Spigot: bungee = online mode, for now. ++ if (this.getOnlineMode() || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) { // Paper - Add setting for proxy online mode status + // This is potentially blocking :( + profile = this.console.getProfileCache().get(name).orElse(null); + } diff --git a/patches/server/0102-Add-setting-for-proxy-online-mode-status.patch b/patches/server/0102-Add-setting-for-proxy-online-mode-status.patch deleted file mode 100644 index 327879d17d2d..000000000000 --- a/patches/server/0102-Add-setting-for-proxy-online-mode-status.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Gabriele C -Date: Fri, 5 Aug 2016 01:03:08 +0200 -Subject: [PATCH] Add setting for proxy online mode status - -TODO: Add isProxyOnlineMode check to Metrics - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 726bb3fcb25321d80caa5967cca86733a234b939..1e73010b292b4d46daaa33ea5b9480bf00944390 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -557,7 +557,11 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - public boolean enforceSecureProfile() { - DedicatedServerProperties dedicatedserverproperties = this.getProperties(); - -- return dedicatedserverproperties.enforceSecureProfile && dedicatedserverproperties.onlineMode && this.services.canValidateProfileKeys(); -+ // Paper start - Add setting for proxy online mode status -+ return dedicatedserverproperties.enforceSecureProfile -+ && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() -+ && this.services.canValidateProfileKeys(); -+ // Paper end - Add setting for proxy online mode status - } - - @Override -diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java -index 3c9c6a697143c7e980add58576ad288b8f51ae35..92c22dc10e385f1942f2ec375bbce9faf257462b 100644 ---- a/src/main/java/net/minecraft/server/players/GameProfileCache.java -+++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java -@@ -89,7 +89,8 @@ public class GameProfileCache { - } - }; - -- if (!org.apache.commons.lang3.StringUtils.isBlank(name)) // Paper - Don't lookup a profile with a blank name -+ if (!org.apache.commons.lang3.StringUtils.isBlank(name) // Paper - Don't lookup a profile with a blank name -+ && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) // Paper - Add setting for proxy online mode status - repository.findProfilesByNames(new String[]{name}, profilelookupcallback); - GameProfile gameprofile = (GameProfile) atomicreference.get(); - -@@ -106,7 +107,7 @@ public class GameProfileCache { - } - - private static boolean usesAuthentication() { -- return GameProfileCache.usesAuthentication; -+ return io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode(); // Paper - Add setting for proxy online mode status - } - - public void add(GameProfile profile) { -diff --git a/src/main/java/net/minecraft/server/players/OldUsersConverter.java b/src/main/java/net/minecraft/server/players/OldUsersConverter.java -index 9143ce01650b51e7f6a677c5941ade91a506449f..86c88e81e275d52576122a5083b419e64cb011fc 100644 ---- a/src/main/java/net/minecraft/server/players/OldUsersConverter.java -+++ b/src/main/java/net/minecraft/server/players/OldUsersConverter.java -@@ -65,7 +65,8 @@ public class OldUsersConverter { - return new String[i]; - }); - -- if (server.usesAuthentication() || org.spigotmc.SpigotConfig.bungee) { // Spigot: bungee = online mode, for now. -+ if (server.usesAuthentication() || -+ (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode())) { // Spigot: bungee = online mode, for now. // Paper - Add setting for proxy online mode status - server.getProfileRepository().findProfilesByNames(astring, callback); - } else { - String[] astring1 = astring; -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index ec7f1845e7d547761efb070bbd91d921f71a0509..edf6fb3db243e4c9b28641504e383f0b03e672d2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1821,7 +1821,7 @@ public final class CraftServer implements Server { - if (result == null) { - GameProfile profile = null; - // Only fetch an online UUID in online mode -- if (this.getOnlineMode() || org.spigotmc.SpigotConfig.bungee) { // Spigot: bungee = online mode, for now. -+ if (this.getOnlineMode() || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) { // Paper - Add setting for proxy online mode status - // This is potentially blocking :( - profile = this.console.getProfileCache().get(name).orElse(null); - } diff --git a/patches/server/0103-Optimise-BlockState-s-hashCode-equals.patch b/patches/server/0102-Optimise-BlockState-s-hashCode-equals.patch similarity index 100% rename from patches/server/0103-Optimise-BlockState-s-hashCode-equals.patch rename to patches/server/0102-Optimise-BlockState-s-hashCode-equals.patch diff --git a/patches/server/0103-Configurable-packet-in-spam-threshold.patch b/patches/server/0103-Configurable-packet-in-spam-threshold.patch new file mode 100644 index 000000000000..73f1819cfc2d --- /dev/null +++ b/patches/server/0103-Configurable-packet-in-spam-threshold.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sun, 11 Sep 2016 14:30:57 -0500 +Subject: [PATCH] Configurable packet in spam threshold + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index c1c1abd45cae5f1fe010e73199e4b228f919c6a6..f9581c2f53e7a73b942505be8f00ffc3ac1774a9 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1529,13 +1529,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // Spigot start - limit place/interactions + private int limitedPackets; + private long lastLimitedPacket = -1; ++ private static int getSpamThreshold() { return io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.incomingPacketThreshold; } // Paper - Configurable threshold + + private boolean checkLimit(long timestamp) { +- if (this.lastLimitedPacket != -1 && timestamp - this.lastLimitedPacket < 30 && this.limitedPackets++ >= 4) { ++ if (this.lastLimitedPacket != -1 && timestamp - this.lastLimitedPacket < getSpamThreshold() && this.limitedPackets++ >= 8) { // Paper - Configurable threshold; raise packet limit to 8 + return false; + } + +- if (this.lastLimitedPacket == -1 || timestamp - this.lastLimitedPacket >= 30) { ++ if (this.lastLimitedPacket == -1 || timestamp - this.lastLimitedPacket >= getSpamThreshold()) { // Paper - Configurable threshold + this.lastLimitedPacket = timestamp; + this.limitedPackets = 0; + return true; diff --git a/patches/server/0104-Configurable-flying-kick-messages.patch b/patches/server/0104-Configurable-flying-kick-messages.patch new file mode 100644 index 000000000000..055290f3587f --- /dev/null +++ b/patches/server/0104-Configurable-flying-kick-messages.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Tue, 20 Sep 2016 00:58:01 +0000 +Subject: [PATCH] Configurable flying kick messages + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f9581c2f53e7a73b942505be8f00ffc3ac1774a9..6fec49a0833300ff0e4ef0f22d21480dfac9a2c7 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -338,7 +338,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) { + if (++this.aboveGroundTickCount > 80) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); +- this.disconnect(Component.translatable("multiplayer.disconnect.flying")); ++ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer); // Paper - use configurable kick message + return; + } + } else { +@@ -357,7 +357,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + if (this.clientVehicleIsFloating && this.player.getRootVehicle().getControllingPassenger() == this.player) { + if (++this.aboveGroundVehicleTickCount > 80) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); +- this.disconnect(Component.translatable("multiplayer.disconnect.flying")); ++ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle); // Paper - use configurable kick message + return; + } + } else { diff --git a/patches/server/0104-Configurable-packet-in-spam-threshold.patch b/patches/server/0104-Configurable-packet-in-spam-threshold.patch deleted file mode 100644 index 632d6bbe9bb7..000000000000 --- a/patches/server/0104-Configurable-packet-in-spam-threshold.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Sun, 11 Sep 2016 14:30:57 -0500 -Subject: [PATCH] Configurable packet in spam threshold - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index a91ba515265c869529776521345887574e1d4076..4440648589d28d006dee314b6199d005ae17d7f8 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1529,13 +1529,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // Spigot start - limit place/interactions - private int limitedPackets; - private long lastLimitedPacket = -1; -+ private static int getSpamThreshold() { return io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.incomingPacketThreshold; } // Paper - Configurable threshold - - private boolean checkLimit(long timestamp) { -- if (this.lastLimitedPacket != -1 && timestamp - this.lastLimitedPacket < 30 && this.limitedPackets++ >= 4) { -+ if (this.lastLimitedPacket != -1 && timestamp - this.lastLimitedPacket < getSpamThreshold() && this.limitedPackets++ >= 8) { // Paper - Configurable threshold; raise packet limit to 8 - return false; - } - -- if (this.lastLimitedPacket == -1 || timestamp - this.lastLimitedPacket >= 30) { -+ if (this.lastLimitedPacket == -1 || timestamp - this.lastLimitedPacket >= getSpamThreshold()) { // Paper - Configurable threshold - this.lastLimitedPacket = timestamp; - this.limitedPackets = 0; - return true; diff --git a/patches/server/0105-Add-EntityZapEvent.patch b/patches/server/0105-Add-EntityZapEvent.patch new file mode 100644 index 000000000000..4b7810f5ba99 --- /dev/null +++ b/patches/server/0105-Add-EntityZapEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AlphaBlend +Date: Sun, 16 Oct 2016 23:19:30 -0700 +Subject: [PATCH] Add EntityZapEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index 22f7318711041bfc2847d519933c46b9fd523d01..4034b8e7503f611dc9be121d8da2020ae7155b8c 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -851,10 +851,17 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + @Override + public void thunderHit(ServerLevel world, LightningBolt lightning) { + if (world.getDifficulty() != Difficulty.PEACEFUL) { +- Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); ++ // Paper - Add EntityZapEvent; move log down, event can cancel + Witch entitywitch = (Witch) EntityType.WITCH.create(world); + + if (entitywitch != null) { ++ // Paper start - Add EntityZapEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityZapEvent(this, lightning, entitywitch).isCancelled()) { ++ return; ++ } ++ if (org.spigotmc.SpigotConfig.logVillagerDeaths) Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); // Move down ++ // Paper end - Add EntityZapEvent ++ + entitywitch.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); + entitywitch.finalizeSpawn(world, world.getCurrentDifficultyAt(entitywitch.blockPosition()), MobSpawnType.CONVERSION, (SpawnGroupData) null, (CompoundTag) null); + entitywitch.setNoAi(this.isNoAi()); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index b48c07a49f39e303bc5d1faed0c444b6f0118054..96b72d0194ccb1d8198f9a94306ddb1699aa6c06 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1196,6 +1196,14 @@ public class CraftEventFactory { + return !event.isCancelled(); + } + ++ // Paper start ++ public static com.destroystokyo.paper.event.entity.EntityZapEvent callEntityZapEvent(Entity entity, Entity lightning, Entity changedEntity) { ++ com.destroystokyo.paper.event.entity.EntityZapEvent event = new com.destroystokyo.paper.event.entity.EntityZapEvent(entity.getBukkitEntity(), (LightningStrike) lightning.getBukkitEntity(), changedEntity.getBukkitEntity()); ++ entity.getBukkitEntity().getServer().getPluginManager().callEvent(event); ++ return event; ++ } ++ // Paper end ++ + public static boolean callEntityChangeBlockEvent(Entity entity, BlockPos position, net.minecraft.world.level.block.state.BlockState newBlock) { + return CraftEventFactory.callEntityChangeBlockEvent(entity, position, newBlock, false); + } diff --git a/patches/server/0105-Configurable-flying-kick-messages.patch b/patches/server/0105-Configurable-flying-kick-messages.patch deleted file mode 100644 index 261f981c0c3b..000000000000 --- a/patches/server/0105-Configurable-flying-kick-messages.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kashike -Date: Tue, 20 Sep 2016 00:58:01 +0000 -Subject: [PATCH] Configurable flying kick messages - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 4440648589d28d006dee314b6199d005ae17d7f8..63b2028bc880b39e679637228ba86c2cc470490f 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -338,7 +338,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) { - if (++this.aboveGroundTickCount > 80) { - ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); -- this.disconnect(Component.translatable("multiplayer.disconnect.flying")); -+ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer); // Paper - use configurable kick message - return; - } - } else { -@@ -357,7 +357,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - if (this.clientVehicleIsFloating && this.player.getRootVehicle().getControllingPassenger() == this.player) { - if (++this.aboveGroundVehicleTickCount > 80) { - ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); -- this.disconnect(Component.translatable("multiplayer.disconnect.flying")); -+ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle); // Paper - use configurable kick message - return; - } - } else { diff --git a/patches/server/0106-Add-EntityZapEvent.patch b/patches/server/0106-Add-EntityZapEvent.patch deleted file mode 100644 index ea9e09aba387..000000000000 --- a/patches/server/0106-Add-EntityZapEvent.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: AlphaBlend -Date: Sun, 16 Oct 2016 23:19:30 -0700 -Subject: [PATCH] Add EntityZapEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index 22f7318711041bfc2847d519933c46b9fd523d01..4034b8e7503f611dc9be121d8da2020ae7155b8c 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -851,10 +851,17 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - @Override - public void thunderHit(ServerLevel world, LightningBolt lightning) { - if (world.getDifficulty() != Difficulty.PEACEFUL) { -- Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); -+ // Paper - Add EntityZapEvent; move log down, event can cancel - Witch entitywitch = (Witch) EntityType.WITCH.create(world); - - if (entitywitch != null) { -+ // Paper start - Add EntityZapEvent -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityZapEvent(this, lightning, entitywitch).isCancelled()) { -+ return; -+ } -+ if (org.spigotmc.SpigotConfig.logVillagerDeaths) Villager.LOGGER.info("Villager {} was struck by lightning {}.", this, lightning); // Move down -+ // Paper end - Add EntityZapEvent -+ - entitywitch.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); - entitywitch.finalizeSpawn(world, world.getCurrentDifficultyAt(entitywitch.blockPosition()), MobSpawnType.CONVERSION, (SpawnGroupData) null, (CompoundTag) null); - entitywitch.setNoAi(this.isNoAi()); -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 5d78797792977da434717d510004a548f461e6aa..60c0c8ac706386e089d1a4cbacb5442f63ea05dc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1265,6 +1265,14 @@ public class CraftEventFactory { - return !event.isCancelled(); - } - -+ // Paper start -+ public static com.destroystokyo.paper.event.entity.EntityZapEvent callEntityZapEvent(Entity entity, Entity lightning, Entity changedEntity) { -+ com.destroystokyo.paper.event.entity.EntityZapEvent event = new com.destroystokyo.paper.event.entity.EntityZapEvent(entity.getBukkitEntity(), (LightningStrike) lightning.getBukkitEntity(), changedEntity.getBukkitEntity()); -+ entity.getBukkitEntity().getServer().getPluginManager().callEvent(event); -+ return event; -+ } -+ // Paper end -+ - public static boolean callEntityChangeBlockEvent(Entity entity, BlockPos position, net.minecraft.world.level.block.state.BlockState newBlock) { - return CraftEventFactory.callEntityChangeBlockEvent(entity, position, newBlock, false); - } diff --git a/patches/server/0106-Filter-bad-block-entity-nbt-data-from-falling-blocks.patch b/patches/server/0106-Filter-bad-block-entity-nbt-data-from-falling-blocks.patch new file mode 100644 index 000000000000..6d8cb3969407 --- /dev/null +++ b/patches/server/0106-Filter-bad-block-entity-nbt-data-from-falling-blocks.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Sat, 12 Nov 2016 23:25:22 -0600 +Subject: [PATCH] Filter bad block entity nbt data from falling blocks + + +diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +index ee5ef4fe16ce6397bba30900b9c6690e3c4f51e6..e2f90b822f25bf100eaba0cf4518849f788ee2fa 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -332,7 +332,7 @@ public class FallingBlockEntity extends Entity { + this.dropItem = nbt.getBoolean("DropItem"); + } + +- if (nbt.contains("TileEntityData", 10)) { ++ if (nbt.contains("TileEntityData", 10) && !(this.level().paperConfig().entities.spawning.filterBadTileEntityNbtFromFallingBlocks && this.blockState.getBlock() instanceof net.minecraft.world.level.block.GameMasterBlock)) { // Paper - Filter bad block entity nbt data from falling blocks + this.blockData = nbt.getCompound("TileEntityData").copy(); + } + diff --git a/patches/server/0108-Cache-user-authenticator-threads.patch b/patches/server/0107-Cache-user-authenticator-threads.patch similarity index 100% rename from patches/server/0108-Cache-user-authenticator-threads.patch rename to patches/server/0107-Cache-user-authenticator-threads.patch diff --git a/patches/server/0107-Filter-bad-block-entity-nbt-data-from-falling-blocks.patch b/patches/server/0107-Filter-bad-block-entity-nbt-data-from-falling-blocks.patch deleted file mode 100644 index 30f320dca008..000000000000 --- a/patches/server/0107-Filter-bad-block-entity-nbt-data-from-falling-blocks.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Sat, 12 Nov 2016 23:25:22 -0600 -Subject: [PATCH] Filter bad block entity nbt data from falling blocks - - -diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -index 67088152004caeecf4a678618be19419862e7ff1..1b9dfc32ce13dc9ec2fab60750dc1184dbddc5bd 100644 ---- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -@@ -334,7 +334,7 @@ public class FallingBlockEntity extends Entity { - this.dropItem = nbt.getBoolean("DropItem"); - } - -- if (nbt.contains("TileEntityData", 10)) { -+ if (nbt.contains("TileEntityData", 10) && !(this.level().paperConfig().entities.spawning.filterBadTileEntityNbtFromFallingBlocks && this.blockState.getBlock() instanceof net.minecraft.world.level.block.GameMasterBlock)) { // Paper - Filter bad block entity nbt data from falling blocks - this.blockData = nbt.getCompound("TileEntityData").copy(); - } - diff --git a/patches/server/0108-Allow-Reloading-of-Command-Aliases.patch b/patches/server/0108-Allow-Reloading-of-Command-Aliases.patch new file mode 100644 index 000000000000..139cd4051d2a --- /dev/null +++ b/patches/server/0108-Allow-Reloading-of-Command-Aliases.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: willies952002 +Date: Mon, 28 Nov 2016 10:21:52 -0500 +Subject: [PATCH] Allow Reloading of Command Aliases + +Reload the aliases stored in commands.yml + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 1bb70e2363b8d046090c8f5853de590fa6777e7d..9978f3ffb3803b7f9278d49a688bc2d4bf740cf3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2754,5 +2754,24 @@ public final class CraftServer implements Server { + DefaultPermissions.registerCorePermissions(); + CraftDefaultPermissions.registerCorePermissions(); + } ++ ++ @Override ++ public boolean reloadCommandAliases() { ++ Set removals = getCommandAliases().keySet().stream() ++ .map(key -> key.toLowerCase(java.util.Locale.ENGLISH)) ++ .collect(java.util.stream.Collectors.toSet()); ++ getCommandMap().getKnownCommands().keySet().removeIf(removals::contains); ++ File file = getCommandsConfigFile(); ++ try { ++ commandsConfiguration.load(file); ++ } catch (FileNotFoundException ex) { ++ return false; ++ } catch (IOException | org.bukkit.configuration.InvalidConfigurationException ex) { ++ Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex); ++ return false; ++ } ++ commandMap.registerServerAliases(); ++ return true; ++ } + // Paper end + } diff --git a/patches/server/0109-Add-source-to-PlayerExpChangeEvent.patch b/patches/server/0109-Add-source-to-PlayerExpChangeEvent.patch new file mode 100644 index 000000000000..9d09ace4caec --- /dev/null +++ b/patches/server/0109-Add-source-to-PlayerExpChangeEvent.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AlphaBlend +Date: Thu, 8 Sep 2016 08:48:33 -0700 +Subject: [PATCH] Add source to PlayerExpChangeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +index 79943167c7e16111a81ff608fd9ed6c06bcb9468..59bad6c92cc421dd05c7315e2ab694a669433ab4 100644 +--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +@@ -254,7 +254,7 @@ public class ExperienceOrb extends Entity { + int i = this.repairPlayerItems(player, this.value); + + if (i > 0) { +- player.giveExperiencePoints(CraftEventFactory.callPlayerExpChangeEvent(player, i).getAmount()); // CraftBukkit - this.value -> event.getAmount() ++ player.giveExperiencePoints(CraftEventFactory.callPlayerExpChangeEvent(player, this).getAmount()); // CraftBukkit - this.value -> event.getAmount() // Paper - supply experience orb object + } + + --this.count; +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 96b72d0194ccb1d8198f9a94306ddb1699aa6c06..85340e48507cdb44d494e5cc6054560c61dddff9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1149,6 +1149,17 @@ public class CraftEventFactory { + return event; + } + ++ // Paper start - Add orb ++ public static PlayerExpChangeEvent callPlayerExpChangeEvent(net.minecraft.world.entity.player.Player entity, net.minecraft.world.entity.ExperienceOrb entityOrb) { ++ Player player = (Player) entity.getBukkitEntity(); ++ ExperienceOrb source = (ExperienceOrb) entityOrb.getBukkitEntity(); ++ int expAmount = source.getExperience(); ++ PlayerExpChangeEvent event = new PlayerExpChangeEvent(player, source, expAmount); ++ Bukkit.getPluginManager().callEvent(event); ++ return event; ++ } ++ // Paper end ++ + public static boolean handleBlockGrowEvent(Level world, BlockPos pos, net.minecraft.world.level.block.state.BlockState block) { + return CraftEventFactory.handleBlockGrowEvent(world, pos, block, 3); + } diff --git a/patches/server/0109-Allow-Reloading-of-Command-Aliases.patch b/patches/server/0109-Allow-Reloading-of-Command-Aliases.patch deleted file mode 100644 index 8c288098928b..000000000000 --- a/patches/server/0109-Allow-Reloading-of-Command-Aliases.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: willies952002 -Date: Mon, 28 Nov 2016 10:21:52 -0500 -Subject: [PATCH] Allow Reloading of Command Aliases - -Reload the aliases stored in commands.yml - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index edf6fb3db243e4c9b28641504e383f0b03e672d2..1983650dbbb634472e81984960b857f1e87b26b8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2742,5 +2742,24 @@ public final class CraftServer implements Server { - DefaultPermissions.registerCorePermissions(); - CraftDefaultPermissions.registerCorePermissions(); - } -+ -+ @Override -+ public boolean reloadCommandAliases() { -+ Set removals = getCommandAliases().keySet().stream() -+ .map(key -> key.toLowerCase(java.util.Locale.ENGLISH)) -+ .collect(java.util.stream.Collectors.toSet()); -+ getCommandMap().getKnownCommands().keySet().removeIf(removals::contains); -+ File file = getCommandsConfigFile(); -+ try { -+ commandsConfiguration.load(file); -+ } catch (FileNotFoundException ex) { -+ return false; -+ } catch (IOException | org.bukkit.configuration.InvalidConfigurationException ex) { -+ Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex); -+ return false; -+ } -+ commandMap.registerServerAliases(); -+ return true; -+ } - // Paper end - } diff --git a/patches/server/0110-Add-ProjectileCollideEvent.patch b/patches/server/0110-Add-ProjectileCollideEvent.patch new file mode 100644 index 000000000000..1a501079ca7f --- /dev/null +++ b/patches/server/0110-Add-ProjectileCollideEvent.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Techcable +Date: Fri, 16 Dec 2016 21:25:39 -0600 +Subject: [PATCH] Add ProjectileCollideEvent + +Deprecated now and replaced with ProjectileHitEvent + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 85340e48507cdb44d494e5cc6054560c61dddff9..d461f56cbc64efba422d748c42ec106f84423833 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1293,6 +1293,17 @@ public class CraftEventFactory { + return CraftItemStack.asNMSCopy(bitem); + } + ++ // Paper start ++ @Deprecated ++ public static com.destroystokyo.paper.event.entity.ProjectileCollideEvent callProjectileCollideEvent(Entity entity, EntityHitResult position) { ++ Projectile projectile = (Projectile) entity.getBukkitEntity(); ++ org.bukkit.entity.Entity collided = position.getEntity().getBukkitEntity(); ++ com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = new com.destroystokyo.paper.event.entity.ProjectileCollideEvent(projectile, collided); ++ Bukkit.getPluginManager().callEvent(event); ++ return event; ++ } ++ // Paper end ++ + public static ProjectileLaunchEvent callProjectileLaunchEvent(Entity entity) { + Projectile bukkitEntity = (Projectile) entity.getBukkitEntity(); + ProjectileLaunchEvent event = new ProjectileLaunchEvent(bukkitEntity); +@@ -1317,8 +1328,15 @@ public class CraftEventFactory { + if (position.getType() == HitResult.Type.ENTITY) { + hitEntity = ((EntityHitResult) position).getEntity().getBukkitEntity(); + } ++ // Paper start - legacy event ++ boolean cancelled = false; ++ if (hitEntity != null && position instanceof EntityHitResult entityHitResult) { ++ cancelled = callProjectileCollideEvent(entity, entityHitResult).isCancelled(); ++ } ++ // Paper end + + ProjectileHitEvent event = new ProjectileHitEvent((Projectile) entity.getBukkitEntity(), hitEntity, hitBlock, hitFace); ++ event.setCancelled(cancelled); // Paper - propagate legacy event cancellation to modern event + entity.level().getCraftServer().getPluginManager().callEvent(event); + return event; + } diff --git a/patches/server/0110-Add-source-to-PlayerExpChangeEvent.patch b/patches/server/0110-Add-source-to-PlayerExpChangeEvent.patch deleted file mode 100644 index 6a7a0c2b3603..000000000000 --- a/patches/server/0110-Add-source-to-PlayerExpChangeEvent.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: AlphaBlend -Date: Thu, 8 Sep 2016 08:48:33 -0700 -Subject: [PATCH] Add source to PlayerExpChangeEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -index 79943167c7e16111a81ff608fd9ed6c06bcb9468..59bad6c92cc421dd05c7315e2ab694a669433ab4 100644 ---- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -+++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -@@ -254,7 +254,7 @@ public class ExperienceOrb extends Entity { - int i = this.repairPlayerItems(player, this.value); - - if (i > 0) { -- player.giveExperiencePoints(CraftEventFactory.callPlayerExpChangeEvent(player, i).getAmount()); // CraftBukkit - this.value -> event.getAmount() -+ player.giveExperiencePoints(CraftEventFactory.callPlayerExpChangeEvent(player, this).getAmount()); // CraftBukkit - this.value -> event.getAmount() // Paper - supply experience orb object - } - - --this.count; -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 18cd6963863d030e3c360f5e00e8786f28ee04b9..0cadc81bfd86a1bcd4ec1d7a793c548cad7d8a9b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1218,6 +1218,17 @@ public class CraftEventFactory { - return event; - } - -+ // Paper start - Add orb -+ public static PlayerExpChangeEvent callPlayerExpChangeEvent(net.minecraft.world.entity.player.Player entity, net.minecraft.world.entity.ExperienceOrb entityOrb) { -+ Player player = (Player) entity.getBukkitEntity(); -+ ExperienceOrb source = (ExperienceOrb) entityOrb.getBukkitEntity(); -+ int expAmount = source.getExperience(); -+ PlayerExpChangeEvent event = new PlayerExpChangeEvent(player, source, expAmount); -+ Bukkit.getPluginManager().callEvent(event); -+ return event; -+ } -+ // Paper end -+ - public static boolean handleBlockGrowEvent(Level world, BlockPos pos, net.minecraft.world.level.block.state.BlockState block) { - return CraftEventFactory.handleBlockGrowEvent(world, pos, block, 3); - } diff --git a/patches/server/0111-Add-ProjectileCollideEvent.patch b/patches/server/0111-Add-ProjectileCollideEvent.patch deleted file mode 100644 index fd0c0df651d2..000000000000 --- a/patches/server/0111-Add-ProjectileCollideEvent.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Techcable -Date: Fri, 16 Dec 2016 21:25:39 -0600 -Subject: [PATCH] Add ProjectileCollideEvent - -Deprecated now and replaced with ProjectileHitEvent - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 0cadc81bfd86a1bcd4ec1d7a793c548cad7d8a9b..848bcabb5922c99ee78cb541c87b464cea032749 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1362,6 +1362,17 @@ public class CraftEventFactory { - return CraftItemStack.asNMSCopy(bitem); - } - -+ // Paper start -+ @Deprecated -+ public static com.destroystokyo.paper.event.entity.ProjectileCollideEvent callProjectileCollideEvent(Entity entity, EntityHitResult position) { -+ Projectile projectile = (Projectile) entity.getBukkitEntity(); -+ org.bukkit.entity.Entity collided = position.getEntity().getBukkitEntity(); -+ com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = new com.destroystokyo.paper.event.entity.ProjectileCollideEvent(projectile, collided); -+ Bukkit.getPluginManager().callEvent(event); -+ return event; -+ } -+ // Paper end -+ - public static ProjectileLaunchEvent callProjectileLaunchEvent(Entity entity) { - Projectile bukkitEntity = (Projectile) entity.getBukkitEntity(); - ProjectileLaunchEvent event = new ProjectileLaunchEvent(bukkitEntity); -@@ -1386,8 +1397,15 @@ public class CraftEventFactory { - if (position.getType() == HitResult.Type.ENTITY) { - hitEntity = ((EntityHitResult) position).getEntity().getBukkitEntity(); - } -+ // Paper start - legacy event -+ boolean cancelled = false; -+ if (hitEntity != null && position instanceof EntityHitResult entityHitResult) { -+ cancelled = callProjectileCollideEvent(entity, entityHitResult).isCancelled(); -+ } -+ // Paper end - - ProjectileHitEvent event = new ProjectileHitEvent((Projectile) entity.getBukkitEntity(), hitEntity, hitBlock, hitFace); -+ event.setCancelled(cancelled); // Paper - propagate legacy event cancellation to modern event - entity.level().getCraftServer().getPluginManager().callEvent(event); - return event; - } diff --git a/patches/server/0112-Prevent-Pathfinding-out-of-World-Border.patch b/patches/server/0111-Prevent-Pathfinding-out-of-World-Border.patch similarity index 100% rename from patches/server/0112-Prevent-Pathfinding-out-of-World-Border.patch rename to patches/server/0111-Prevent-Pathfinding-out-of-World-Border.patch diff --git a/patches/server/0113-Optimize-Level.hasChunkAt-BlockPosition-Z.patch b/patches/server/0112-Optimize-Level.hasChunkAt-BlockPosition-Z.patch similarity index 100% rename from patches/server/0113-Optimize-Level.hasChunkAt-BlockPosition-Z.patch rename to patches/server/0112-Optimize-Level.hasChunkAt-BlockPosition-Z.patch diff --git a/patches/server/0114-Bound-Treasure-Maps-to-World-Border.patch b/patches/server/0113-Bound-Treasure-Maps-to-World-Border.patch similarity index 100% rename from patches/server/0114-Bound-Treasure-Maps-to-World-Border.patch rename to patches/server/0113-Bound-Treasure-Maps-to-World-Border.patch diff --git a/patches/server/0115-Configurable-Cartographer-Treasure-Maps.patch b/patches/server/0114-Configurable-Cartographer-Treasure-Maps.patch similarity index 100% rename from patches/server/0115-Configurable-Cartographer-Treasure-Maps.patch rename to patches/server/0114-Configurable-Cartographer-Treasure-Maps.patch diff --git a/patches/server/0116-Add-API-methods-to-control-if-armor-stands-can-move.patch b/patches/server/0115-Add-API-methods-to-control-if-armor-stands-can-move.patch similarity index 100% rename from patches/server/0116-Add-API-methods-to-control-if-armor-stands-can-move.patch rename to patches/server/0115-Add-API-methods-to-control-if-armor-stands-can-move.patch diff --git a/patches/server/0116-String-based-Action-Bar-API.patch b/patches/server/0116-String-based-Action-Bar-API.patch new file mode 100644 index 000000000000..e26a39863123 --- /dev/null +++ b/patches/server/0116-String-based-Action-Bar-API.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 27 Dec 2016 15:02:42 -0500 +Subject: [PATCH] String based Action Bar API + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetActionBarTextPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetActionBarTextPacket.java +index cb88a3a4e4c87a6d6c838183c1640b13d82c9344..0b391b0dc6262ef482c4a253a074b593127cc1d4 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetActionBarTextPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetActionBarTextPacket.java +@@ -7,6 +7,7 @@ import net.minecraft.network.protocol.Packet; + public class ClientboundSetActionBarTextPacket implements Packet { + private final Component text; + public net.kyori.adventure.text.Component adventure$text; // Paper ++ public net.md_5.bungee.api.chat.BaseComponent[] components; // Paper + + public ClientboundSetActionBarTextPacket(Component message) { + this.text = message; +@@ -21,6 +22,8 @@ public class ClientboundSetActionBarTextPacket implements Packet +Date: Tue, 27 Dec 2016 01:57:57 +0000 +Subject: [PATCH] Properly fix item duplication bug + +Credit to prplz for figuring out the real issue + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 429f3ed00c0c6d7c89138aa9a4b770e3e68b7ed7..f1f090b7d9ac21f6430756ddb02b734f7dda0c7c 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2451,7 +2451,7 @@ public class ServerPlayer extends Player { + + @Override + public boolean isImmobile() { +- return super.isImmobile() || !this.getBukkitEntity().isOnline(); ++ return super.isImmobile() || (this.connection != null && this.connection.isDisconnected()); // Paper - Fix duplication bugs + } + + @Override +diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index 66497960995dc30abe60d26200979a78513ff2c6..4ff58939269f420fab18fea8fc3887e86297b99e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -146,7 +146,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } + + public final boolean isDisconnected() { +- return !this.player.joining && !this.connection.isConnected(); ++ return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper - Fix duplication bugs + } + // CraftBukkit end + diff --git a/patches/server/0117-String-based-Action-Bar-API.patch b/patches/server/0117-String-based-Action-Bar-API.patch deleted file mode 100644 index c07500957547..000000000000 --- a/patches/server/0117-String-based-Action-Bar-API.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 27 Dec 2016 15:02:42 -0500 -Subject: [PATCH] String based Action Bar API - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetActionBarTextPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetActionBarTextPacket.java -index cb88a3a4e4c87a6d6c838183c1640b13d82c9344..0b391b0dc6262ef482c4a253a074b593127cc1d4 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetActionBarTextPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetActionBarTextPacket.java -@@ -7,6 +7,7 @@ import net.minecraft.network.protocol.Packet; - public class ClientboundSetActionBarTextPacket implements Packet { - private final Component text; - public net.kyori.adventure.text.Component adventure$text; // Paper -+ public net.md_5.bungee.api.chat.BaseComponent[] components; // Paper - - public ClientboundSetActionBarTextPacket(Component message) { - this.text = message; -@@ -21,6 +22,8 @@ public class ClientboundSetActionBarTextPacket implements Packet +Date: Wed, 28 Dec 2016 07:18:33 +0100 +Subject: [PATCH] Firework API's + +== AT == +public net.minecraft.world.entity.projectile.FireworkRocketEntity attachedToEntity + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +index 764c15e6e35c358f0d04d15c20cf8f382f77c419..7005b3a26e9b5d79064981a4a41bee21b65a9fc3 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +@@ -37,6 +37,7 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { + public int lifetime; + @Nullable + public LivingEntity attachedToEntity; ++ public java.util.UUID spawningEntity; // Paper + + public FireworkRocketEntity(EntityType type, Level world) { + super(type, world); +@@ -312,6 +313,11 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { + } + + nbt.putBoolean("ShotAtAngle", (Boolean) this.entityData.get(FireworkRocketEntity.DATA_SHOT_AT_ANGLE)); ++ // Paper start ++ if (this.spawningEntity != null) { ++ nbt.putUUID("SpawningEntity", this.spawningEntity); ++ } ++ // Paper end + } + + @Override +@@ -328,7 +334,11 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { + if (nbt.contains("ShotAtAngle")) { + this.entityData.set(FireworkRocketEntity.DATA_SHOT_AT_ANGLE, nbt.getBoolean("ShotAtAngle")); + } +- ++ // Paper start ++ if (nbt.hasUUID("SpawningEntity")) { ++ this.spawningEntity = nbt.getUUID("SpawningEntity"); ++ } ++ // Paper end + } + + @Override +diff --git a/src/main/java/net/minecraft/world/item/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java +index ac7371882d15746e9353865635d0bb716f890c53..ba570f1c9654e1004e068a1efe2118f36c954505 100644 +--- a/src/main/java/net/minecraft/world/item/CrossbowItem.java ++++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java +@@ -217,6 +217,7 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { + + if (flag1) { + object = new FireworkRocketEntity(world, projectile, shooter, shooter.getX(), shooter.getEyeY() - 0.15000000596046448D, shooter.getZ(), true); ++ ((FireworkRocketEntity) object).spawningEntity = shooter.getUUID(); // Paper + } else { + object = CrossbowItem.getArrow(world, shooter, crossbow, projectile); + if (creative || simulated != 0.0F) { +diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +index 5cafb6f0b507127665393741b372286da098d603..7c627d27300247db9122ab2081049345ef306073 100644 +--- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java ++++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +@@ -46,6 +46,7 @@ public class FireworkRocketItem extends Item { + Vec3 vec3 = context.getClickLocation(); + Direction direction = context.getClickedFace(); + FireworkRocketEntity fireworkRocketEntity = new FireworkRocketEntity(level, context.getPlayer(), vec3.x + (double)direction.getStepX() * 0.15D, vec3.y + (double)direction.getStepY() * 0.15D, vec3.z + (double)direction.getStepZ() * 0.15D, itemStack); ++ fireworkRocketEntity.spawningEntity = context.getPlayer() == null ? null : context.getPlayer().getUUID(); // Paper + level.addFreshEntity(fireworkRocketEntity); + itemStack.shrink(1); + } +@@ -59,6 +60,7 @@ public class FireworkRocketItem extends Item { + ItemStack itemStack = user.getItemInHand(hand); + if (!world.isClientSide) { + FireworkRocketEntity fireworkRocketEntity = new FireworkRocketEntity(world, itemStack, user); ++ fireworkRocketEntity.spawningEntity = user.getUUID(); // Paper + world.addFreshEntity(fireworkRocketEntity); + if (!user.getAbilities().instabuild) { + itemStack.shrink(1); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +index 5ae87c370e47c545cef27a36e40da137e1ec656b..c9e15a9d82dee935293b2e7e233f5b9b2d822448 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +@@ -129,4 +129,11 @@ public class CraftFirework extends CraftProjectile implements Firework { + public void setShotAtAngle(boolean shotAtAngle) { + this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_SHOT_AT_ANGLE, shotAtAngle); + } ++ ++ // Paper start ++ @Override ++ public java.util.UUID getSpawningEntity() { ++ return getHandle().spawningEntity; ++ } ++ // Paper end + } diff --git a/patches/server/0118-Properly-fix-item-duplication-bug.patch b/patches/server/0118-Properly-fix-item-duplication-bug.patch deleted file mode 100644 index 6c85bd6ab9ac..000000000000 --- a/patches/server/0118-Properly-fix-item-duplication-bug.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alfie Cleveland -Date: Tue, 27 Dec 2016 01:57:57 +0000 -Subject: [PATCH] Properly fix item duplication bug - -Credit to prplz for figuring out the real issue - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 7e777ab6998203e031fb8387b1521bff3d86f11a..5197fc02a080c3f603030d5c4fa59e10f8c0e3b6 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2451,7 +2451,7 @@ public class ServerPlayer extends Player { - - @Override - public boolean isImmobile() { -- return super.isImmobile() || !this.getBukkitEntity().isOnline(); -+ return super.isImmobile() || (this.connection != null && this.connection.isDisconnected()); // Paper - Fix duplication bugs - } - - @Override -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 66497960995dc30abe60d26200979a78513ff2c6..4ff58939269f420fab18fea8fc3887e86297b99e 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -146,7 +146,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - } - - public final boolean isDisconnected() { -- return !this.player.joining && !this.connection.isConnected(); -+ return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper - Fix duplication bugs - } - // CraftBukkit end - diff --git a/patches/server/0119-Firework-API-s.patch b/patches/server/0119-Firework-API-s.patch deleted file mode 100644 index fa27f134b796..000000000000 --- a/patches/server/0119-Firework-API-s.patch +++ /dev/null @@ -1,93 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 28 Dec 2016 07:18:33 +0100 -Subject: [PATCH] Firework API's - -== AT == -public net.minecraft.world.entity.projectile.FireworkRocketEntity attachedToEntity - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java -index 36f096001d2df5e3cae921cf1f08473e51e91a19..b2f08889139dc447f7071f1c81456035bf8de31e 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java -@@ -38,6 +38,7 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { - public int lifetime; - @Nullable - public LivingEntity attachedToEntity; -+ public java.util.UUID spawningEntity; // Paper - - public FireworkRocketEntity(EntityType type, Level world) { - super(type, world); -@@ -317,6 +318,11 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { - } - - nbt.putBoolean("ShotAtAngle", (Boolean) this.entityData.get(FireworkRocketEntity.DATA_SHOT_AT_ANGLE)); -+ // Paper start -+ if (this.spawningEntity != null) { -+ nbt.putUUID("SpawningEntity", this.spawningEntity); -+ } -+ // Paper end - } - - @Override -@@ -333,7 +339,11 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { - if (nbt.contains("ShotAtAngle")) { - this.entityData.set(FireworkRocketEntity.DATA_SHOT_AT_ANGLE, nbt.getBoolean("ShotAtAngle")); - } -- -+ // Paper start -+ if (nbt.hasUUID("SpawningEntity")) { -+ this.spawningEntity = nbt.getUUID("SpawningEntity"); -+ } -+ // Paper end - } - - @Override -diff --git a/src/main/java/net/minecraft/world/item/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java -index ac7371882d15746e9353865635d0bb716f890c53..ba570f1c9654e1004e068a1efe2118f36c954505 100644 ---- a/src/main/java/net/minecraft/world/item/CrossbowItem.java -+++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java -@@ -217,6 +217,7 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { - - if (flag1) { - object = new FireworkRocketEntity(world, projectile, shooter, shooter.getX(), shooter.getEyeY() - 0.15000000596046448D, shooter.getZ(), true); -+ ((FireworkRocketEntity) object).spawningEntity = shooter.getUUID(); // Paper - } else { - object = CrossbowItem.getArrow(world, shooter, crossbow, projectile); - if (creative || simulated != 0.0F) { -diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -index 5cafb6f0b507127665393741b372286da098d603..7c627d27300247db9122ab2081049345ef306073 100644 ---- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -+++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -@@ -46,6 +46,7 @@ public class FireworkRocketItem extends Item { - Vec3 vec3 = context.getClickLocation(); - Direction direction = context.getClickedFace(); - FireworkRocketEntity fireworkRocketEntity = new FireworkRocketEntity(level, context.getPlayer(), vec3.x + (double)direction.getStepX() * 0.15D, vec3.y + (double)direction.getStepY() * 0.15D, vec3.z + (double)direction.getStepZ() * 0.15D, itemStack); -+ fireworkRocketEntity.spawningEntity = context.getPlayer() == null ? null : context.getPlayer().getUUID(); // Paper - level.addFreshEntity(fireworkRocketEntity); - itemStack.shrink(1); - } -@@ -59,6 +60,7 @@ public class FireworkRocketItem extends Item { - ItemStack itemStack = user.getItemInHand(hand); - if (!world.isClientSide) { - FireworkRocketEntity fireworkRocketEntity = new FireworkRocketEntity(world, itemStack, user); -+ fireworkRocketEntity.spawningEntity = user.getUUID(); // Paper - world.addFreshEntity(fireworkRocketEntity); - if (!user.getAbilities().instabuild) { - itemStack.shrink(1); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -index 5ae87c370e47c545cef27a36e40da137e1ec656b..c9e15a9d82dee935293b2e7e233f5b9b2d822448 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -@@ -129,4 +129,11 @@ public class CraftFirework extends CraftProjectile implements Firework { - public void setShotAtAngle(boolean shotAtAngle) { - this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_SHOT_AT_ANGLE, shotAtAngle); - } -+ -+ // Paper start -+ @Override -+ public java.util.UUID getSpawningEntity() { -+ return getHandle().spawningEntity; -+ } -+ // Paper end - } diff --git a/patches/server/0120-PlayerTeleportEndGatewayEvent.patch b/patches/server/0119-PlayerTeleportEndGatewayEvent.patch similarity index 100% rename from patches/server/0120-PlayerTeleportEndGatewayEvent.patch rename to patches/server/0119-PlayerTeleportEndGatewayEvent.patch diff --git a/patches/server/0120-Provide-E-TE-Chunk-count-stat-methods.patch b/patches/server/0120-Provide-E-TE-Chunk-count-stat-methods.patch new file mode 100644 index 000000000000..4a70577b2e94 --- /dev/null +++ b/patches/server/0120-Provide-E-TE-Chunk-count-stat-methods.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 7 Jan 2017 15:24:46 -0500 +Subject: [PATCH] Provide E/TE/Chunk count stat methods + +Provides counts without the ineffeciency of using .getEntities().size() +which creates copy of the collections. + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index e4a2afc20efcfd7a6107dc56217ae8451dd5a7d6..0045016cd22c34168c4ae5f2fe951d536394be9a 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -114,7 +114,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public static final int TICKS_PER_DAY = 24000; + public static final int MAX_ENTITY_SPAWN_Y = 20000000; + public static final int MIN_ENTITY_SPAWN_Y = -20000000; +- protected final List blockEntityTickers = Lists.newArrayList(); ++ public final List blockEntityTickers = Lists.newArrayList(); // Paper - public + protected final NeighborUpdater neighborUpdater; + private final List pendingBlockEntityTickers = Lists.newArrayList(); + private boolean tickingBlockEntities; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 8ff8d8174cd32d25b33c2e773d30c474b4e903d3..07aac886235e18a29420c494380e6b26bfa8f36e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -163,6 +163,56 @@ public class CraftWorld extends CraftRegionAccessor implements World { + private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftWorld.DATA_TYPE_REGISTRY); + private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers + ++ // Paper start - Provide fast information methods ++ @Override ++ public int getEntityCount() { ++ int ret = 0; ++ for (net.minecraft.world.entity.Entity entity : world.getEntities().getAll()) { ++ if (entity.isChunkLoaded()) { ++ ++ret; ++ } ++ } ++ return ret; ++ } ++ ++ @Override ++ public int getTileEntityCount() { ++ // We don't use the full world tile entity list, so we must iterate chunks ++ int size = 0; ++ for (ChunkHolder playerchunk : io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.world)) { ++ net.minecraft.world.level.chunk.LevelChunk chunk = playerchunk.getTickingChunk(); ++ if (chunk == null) { ++ continue; ++ } ++ size += chunk.blockEntities.size(); ++ } ++ return size; ++ } ++ ++ @Override ++ public int getTickableTileEntityCount() { ++ return world.blockEntityTickers.size(); ++ } ++ ++ @Override ++ public int getChunkCount() { ++ int ret = 0; ++ ++ for (ChunkHolder chunkHolder : io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.world)) { ++ if (chunkHolder.getTickingChunk() != null) { ++ ++ret; ++ } ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public int getPlayerCount() { ++ return world.players().size(); ++ } ++ // Paper end ++ + private static final Random rand = new Random(); + + public CraftWorld(ServerLevel world, ChunkGenerator gen, BiomeProvider biomeProvider, Environment env) { diff --git a/patches/server/0122-Enforce-Sync-Player-Saves.patch b/patches/server/0121-Enforce-Sync-Player-Saves.patch similarity index 100% rename from patches/server/0122-Enforce-Sync-Player-Saves.patch rename to patches/server/0121-Enforce-Sync-Player-Saves.patch diff --git a/patches/server/0121-Provide-E-TE-Chunk-count-stat-methods.patch b/patches/server/0121-Provide-E-TE-Chunk-count-stat-methods.patch deleted file mode 100644 index e5f350017b78..000000000000 --- a/patches/server/0121-Provide-E-TE-Chunk-count-stat-methods.patch +++ /dev/null @@ -1,82 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 7 Jan 2017 15:24:46 -0500 -Subject: [PATCH] Provide E/TE/Chunk count stat methods - -Provides counts without the ineffeciency of using .getEntities().size() -which creates copy of the collections. - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index e4a2afc20efcfd7a6107dc56217ae8451dd5a7d6..0045016cd22c34168c4ae5f2fe951d536394be9a 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -114,7 +114,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public static final int TICKS_PER_DAY = 24000; - public static final int MAX_ENTITY_SPAWN_Y = 20000000; - public static final int MIN_ENTITY_SPAWN_Y = -20000000; -- protected final List blockEntityTickers = Lists.newArrayList(); -+ public final List blockEntityTickers = Lists.newArrayList(); // Paper - public - protected final NeighborUpdater neighborUpdater; - private final List pendingBlockEntityTickers = Lists.newArrayList(); - private boolean tickingBlockEntities; -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index b2632cbc7903e23eb68e9901df039f5d8293bd77..e6b104e57381343f08a0a2a7b2e3a81b6ddadda4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -157,6 +157,56 @@ public class CraftWorld extends CraftRegionAccessor implements World { - private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftWorld.DATA_TYPE_REGISTRY); - private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers - -+ // Paper start - Provide fast information methods -+ @Override -+ public int getEntityCount() { -+ int ret = 0; -+ for (net.minecraft.world.entity.Entity entity : world.getEntities().getAll()) { -+ if (entity.isChunkLoaded()) { -+ ++ret; -+ } -+ } -+ return ret; -+ } -+ -+ @Override -+ public int getTileEntityCount() { -+ // We don't use the full world tile entity list, so we must iterate chunks -+ int size = 0; -+ for (ChunkHolder playerchunk : io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.world)) { -+ net.minecraft.world.level.chunk.LevelChunk chunk = playerchunk.getTickingChunk(); -+ if (chunk == null) { -+ continue; -+ } -+ size += chunk.blockEntities.size(); -+ } -+ return size; -+ } -+ -+ @Override -+ public int getTickableTileEntityCount() { -+ return world.blockEntityTickers.size(); -+ } -+ -+ @Override -+ public int getChunkCount() { -+ int ret = 0; -+ -+ for (ChunkHolder chunkHolder : io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.world)) { -+ if (chunkHolder.getTickingChunk() != null) { -+ ++ret; -+ } -+ } -+ -+ return ret; -+ } -+ -+ @Override -+ public int getPlayerCount() { -+ return world.players().size(); -+ } -+ // Paper end -+ - private static final Random rand = new Random(); - - public CraftWorld(ServerLevel world, ChunkGenerator gen, BiomeProvider biomeProvider, Environment env) { diff --git a/patches/server/0122-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch b/patches/server/0122-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch new file mode 100644 index 000000000000..60cce7817de4 --- /dev/null +++ b/patches/server/0122-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch @@ -0,0 +1,355 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 19 Dec 2017 16:31:46 -0500 +Subject: [PATCH] ExperienceOrbs API for Reason/Source/Triggering player + +Adds lots of information about why this orb exists. + +Replaces isFromBottle() with logic that persists entity reloads too. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 4f3c82f1b5ae24d5f70318fa96fae2a58ce7fd9f..45236a077d798d6a257a2e982b58901167ecd06e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -427,7 +427,7 @@ public class ServerPlayerGameMode { + + // Drop event experience + if (flag && event != null) { +- iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop()); ++ iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper + } + + return true; +diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +index 59bad6c92cc421dd05c7315e2ab694a669433ab4..ae70ad9d6c02fcb5631a3c45db283b29844bbb0d 100644 +--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +@@ -39,9 +39,63 @@ public class ExperienceOrb extends Entity { + public int value; + private int count; + private Player followingPlayer; ++ // Paper start ++ @javax.annotation.Nullable ++ public java.util.UUID sourceEntityId; ++ @javax.annotation.Nullable ++ public java.util.UUID triggerEntityId; ++ public org.bukkit.entity.ExperienceOrb.SpawnReason spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; ++ ++ private void loadPaperNBT(CompoundTag tag) { ++ if (!tag.contains("Paper.ExpData", net.minecraft.nbt.Tag.TAG_COMPOUND)) { ++ return; ++ } ++ CompoundTag comp = tag.getCompound("Paper.ExpData"); ++ if (comp.hasUUID("source")) { ++ this.sourceEntityId = comp.getUUID("source"); ++ } ++ if (comp.hasUUID("trigger")) { ++ this.triggerEntityId = comp.getUUID("trigger"); ++ } ++ if (comp.contains("reason")) { ++ String reason = comp.getString("reason"); ++ try { ++ this.spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.valueOf(reason); ++ } catch (Exception e) { ++ this.level().getCraftServer().getLogger().warning("Invalid spawnReason set for experience orb: " + e.getMessage() + " - " + reason); ++ } ++ } ++ } ++ private void savePaperNBT(CompoundTag tag) { ++ CompoundTag comp = new CompoundTag(); ++ if (this.sourceEntityId != null) { ++ comp.putUUID("source", this.sourceEntityId); ++ } ++ if (this.triggerEntityId != null) { ++ comp.putUUID("trigger", triggerEntityId); ++ } ++ if (this.spawnReason != null && this.spawnReason != org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN) { ++ comp.putString("reason", this.spawnReason.name()); ++ } ++ tag.put("Paper.ExpData", comp); ++ } + ++ @io.papermc.paper.annotation.DoNotUse ++ @Deprecated + public ExperienceOrb(Level world, double x, double y, double z, int amount) { ++ this(world, x, y, z, amount, null, null); ++ } ++ ++ public ExperienceOrb(Level world, double x, double y, double z, int amount, @javax.annotation.Nullable org.bukkit.entity.ExperienceOrb.SpawnReason reason, @javax.annotation.Nullable Entity triggerId) { ++ this(world, x, y, z, amount, reason, triggerId, null); ++ } ++ ++ public ExperienceOrb(Level world, double x, double y, double z, int amount, @javax.annotation.Nullable org.bukkit.entity.ExperienceOrb.SpawnReason reason, @javax.annotation.Nullable Entity triggerId, @javax.annotation.Nullable Entity sourceId) { + this(EntityType.EXPERIENCE_ORB, world); ++ this.sourceEntityId = sourceId != null ? sourceId.getUUID() : null; ++ this.triggerEntityId = triggerId != null ? triggerId.getUUID() : null; ++ this.spawnReason = reason != null ? reason : org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; ++ // Paper end + this.setPos(x, y, z); + this.setYRot((float) (this.random.nextDouble() * 360.0D)); + this.setDeltaMovement((this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D, this.random.nextDouble() * 0.2D * 2.0D, (this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D); +@@ -160,12 +214,20 @@ public class ExperienceOrb extends Entity { + } + + public static void award(ServerLevel world, Vec3 pos, int amount) { ++ // Paper start - add reasons for orbs ++ award(world, pos, amount, null, null, null); ++ } ++ public static void award(ServerLevel world, Vec3 pos, int amount, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId) { ++ award(world, pos, amount, reason, triggerId, null); ++ } ++ public static void award(ServerLevel world, Vec3 pos, int amount, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId, Entity sourceId) { ++ // Paper end - add reasons for orbs + while (amount > 0) { + int j = ExperienceOrb.getExperienceValue(amount); + + amount -= j; + if (!ExperienceOrb.tryMergeToExisting(world, pos, j)) { +- world.addFreshEntity(new ExperienceOrb(world, pos.x(), pos.y(), pos.z(), j)); ++ world.addFreshEntity(new ExperienceOrb(world, pos.x(), pos.y(), pos.z(), j, reason, triggerId, sourceId)); // Paper - add reason + } + } + +@@ -235,6 +297,7 @@ public class ExperienceOrb extends Entity { + nbt.putShort("Age", (short) this.age); + nbt.putShort("Value", (short) this.value); + nbt.putInt("Count", this.count); ++ this.savePaperNBT(nbt); // Paper + } + + @Override +@@ -243,6 +306,7 @@ public class ExperienceOrb extends Entity { + this.age = nbt.getShort("Age"); + this.value = nbt.getShort("Value"); + this.count = Math.max(nbt.getInt("Count"), 1); ++ this.loadPaperNBT(nbt); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 9b55a2b2863c546e88f1bfa9014eb84fd317f3c8..6ac549316d056a0de02e062fd4a28d7ac02a9d98 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1787,7 +1787,8 @@ public abstract class LivingEntity extends Entity implements Attackable { + protected void dropExperience() { + // CraftBukkit start - Update getExpReward() above if the removed if() changes! + if (true && !(this instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon)) { // CraftBukkit - SPIGOT-2420: Special case ender dragon will drop the xp over time +- ExperienceOrb.award((ServerLevel) this.level(), this.position(), this.expToDrop); ++ LivingEntity attacker = this.lastHurtByPlayer != null ? this.lastHurtByPlayer : this.lastHurtByMob; // Paper ++ ExperienceOrb.award((ServerLevel) this.level(), this.position(), this.expToDrop, this instanceof ServerPlayer ? org.bukkit.entity.ExperienceOrb.SpawnReason.PLAYER_DEATH : org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, attacker, this); // Paper + this.expToDrop = 0; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java +index f307f9077917f426a90523708c572b95cc7b6778..907ed82fea71254d6624eda878e2668cd26422a7 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Animal.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java +@@ -258,12 +258,14 @@ public abstract class Animal extends AgeableMob { + + public void finalizeSpawnChildFromBreeding(ServerLevel worldserver, Animal entityanimal, @Nullable AgeableMob entityageable, int experience) { + // CraftBukkit end +- Optional.ofNullable(this.getLoveCause()).or(() -> { +- return Optional.ofNullable(entityanimal.getLoveCause()); +- }).ifPresent((entityplayer) -> { ++ // Paper start ++ ServerPlayer entityplayer = this.getLoveCause(); ++ if (entityplayer == null) entityplayer = entityanimal.getLoveCause(); ++ if (entityplayer != null) { ++ // Paper end + entityplayer.awardStat(Stats.ANIMALS_BRED); + CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, entityanimal, entityageable); +- }); ++ } // Paper + this.setAge(6000); + entityanimal.setAge(6000); + this.resetLove(); +@@ -272,7 +274,7 @@ public abstract class Animal extends AgeableMob { + if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { + // CraftBukkit start - use event experience + if (experience > 0) { +- worldserver.addFreshEntity(new ExperienceOrb(worldserver, this.getX(), this.getY(), this.getZ(), experience)); ++ worldserver.addFreshEntity(new ExperienceOrb(worldserver, this.getX(), this.getY(), this.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityageable)); // Paper + } + // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java +index 335249181f34bfe8b0e359c591e4eae0af63b0fe..8670d8b2a08e96df787a91f36c48df8b345080dc 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java +@@ -909,7 +909,7 @@ public class Fox extends Animal implements VariantHolder { + if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { + // CraftBukkit start - use event experience + if (experience > 0) { +- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), experience)); ++ this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityfox)); // Paper + } + // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +index 5385f0a1d0c5522a94e2a5ded779d68826537883..11322066522a3268063bad7267ef4dd4f06d983e 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +@@ -457,7 +457,7 @@ public class Turtle extends Animal { + RandomSource randomsource = this.animal.getRandom(); + + if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { +- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1)); ++ this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper; + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index 2471800014d1661c2f422e5a24f0f3b00fa838f2..f5ce6423b8146cc741023e15004fe9814a035da8 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -673,7 +673,7 @@ public class EnderDragon extends Mob implements Enemy { + + if (this.level() instanceof ServerLevel) { + if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp +- ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.08F)); ++ ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.08F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper + } + + if (this.dragonDeathTime == 1 && !this.isSilent()) { +@@ -702,7 +702,7 @@ public class EnderDragon extends Mob implements Enemy { + this.move(MoverType.SELF, new Vec3(0.0D, 0.10000000149011612D, 0.0D)); + if (this.dragonDeathTime == 200 && this.level() instanceof ServerLevel) { + if (true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp +- ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.2F)); ++ ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.2F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper + } + + if (this.dragonFight != null) { +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index 4034b8e7503f611dc9be121d8da2020ae7155b8c..f0d5e45d0d6ac51106379d20690d34a032a24c39 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -638,7 +638,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + } + + if (offer.shouldRewardExp()) { +- this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i)); ++ this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +index d4d1cad7963a5adcc8c32e1bc02104eb70514331..0321b4bb622930bfe57661b0e6b893d7635668fb 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +@@ -207,7 +207,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + if (offer.shouldRewardExp()) { + int i = 3 + this.random.nextInt(4); + +- this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i)); ++ this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +index 8b27b5118cbeeb0b25fb6a23056e51899be32035..94bdd467108bc5fd0211f67a792787ef7c356619 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +@@ -522,7 +522,7 @@ public class FishingHook extends Projectile { + this.level().addFreshEntity(entityitem); + // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop() + if (playerFishEvent.getExpToDrop() > 0) { +- entityhuman.level().addFreshEntity(new ExperienceOrb(entityhuman.level(), entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop())); ++ entityhuman.level().addFreshEntity(new ExperienceOrb(entityhuman.level(), entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getPlayerOwner(), this)); // Paper + } + // CraftBukkit end + if (itemstack1.is(ItemTags.FISHES)) { +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java +index 5d0e2c53d41e897532a8ec3c0a7b33e9b60e9ab5..e53046c6d47b4fd3d82132bc980a31b9491df6a7 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java +@@ -51,7 +51,7 @@ public class ThrownExperienceBottle extends ThrowableItemProjectile { + } + // CraftBukkit end + +- ExperienceOrb.award((ServerLevel) this.level(), this.position(), i); ++ ExperienceOrb.award((ServerLevel) this.level(), this.position(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.EXP_BOTTLE, this.getOwner(), this); // Paper + this.discard(); + } + +diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +index 8d09c134058e55a23df4e23d965a7a783aed701e..45242f0ed5a0f98953df5f27fb76874d2d9e3473 100644 +--- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +@@ -97,7 +97,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { + public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) { + context.execute((world, blockposition) -> { + if (world instanceof ServerLevel) { +- ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), this.getExperienceAmount(world)); ++ ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), this.getExperienceAmount(world), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Paper + } + + world.levelEvent(1042, blockposition, 0); +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 756a8ae14ffc46d6ebe0a858a03fb2e89b8e118a..89a62fbeeb78c864938a1cea84178478c6dc1b34 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -369,8 +369,13 @@ public class Block extends BlockBehaviour implements ItemLike { + } + + public void popExperience(ServerLevel world, BlockPos pos, int size) { ++ // Paper start - add entity parameter ++ popExperience(world, pos, size, null); ++ } ++ public void popExperience(ServerLevel world, BlockPos pos, int size, net.minecraft.world.entity.Entity entity) { ++ // Paper end - add entity parameter + if (world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) { +- ExperienceOrb.award(world, Vec3.atCenterOf(pos), size); ++ ExperienceOrb.award(world, Vec3.atCenterOf(pos), size, org.bukkit.entity.ExperienceOrb.SpawnReason.BLOCK_BREAK, entity); // Paper + } + + } +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 65e7dcace8607c4d15938c697c882761c067f08e..7a13042631bea761952490cfd14dc20147405161 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 +@@ -651,7 +651,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + j = event.getExpToDrop(); + // CraftBukkit end + +- ExperienceOrb.award(worldserver, vec3d, j); ++ ExperienceOrb.award(worldserver, vec3d, j, org.bukkit.entity.ExperienceOrb.SpawnReason.FURNACE, entityhuman); // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +index c5547988e8e4ec5f1d090d264e14556f490d4e31..c3a55347b630c57aa36f9c761e1937dc95027bb7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +@@ -363,7 +363,7 @@ public final class CraftEntityTypes { + return item; + })); + register(new EntityTypeData<>(EntityType.EXPERIENCE_ORB, ExperienceOrb.class, CraftExperienceOrb::new, +- spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0) ++ spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null) // Paper + )); + register(new EntityTypeData<>(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class, CraftAreaEffectCloud::new, spawnData -> new net.minecraft.world.entity.AreaEffectCloud(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); + register(new EntityTypeData<>(EntityType.EGG, Egg.class, CraftEgg::new, spawnData -> new ThrownEgg(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java +index 9231511af4cba747594000364f0b8fceeeab4819..5a7d314ec0562e472f5dc45924a7b24841cff126 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java +@@ -18,6 +18,18 @@ public class CraftExperienceOrb extends CraftEntity implements ExperienceOrb { + this.getHandle().value = value; + } + ++ // Paper start ++ public java.util.UUID getTriggerEntityId() { ++ return getHandle().triggerEntityId; ++ } ++ public java.util.UUID getSourceEntityId() { ++ return getHandle().sourceEntityId; ++ } ++ public SpawnReason getSpawnReason() { ++ return getHandle().spawnReason; ++ } ++ // Paper end ++ + @Override + public net.minecraft.world.entity.ExperienceOrb getHandle() { + return (net.minecraft.world.entity.ExperienceOrb) this.entity; diff --git a/patches/server/0123-Cap-Entity-Collisions.patch b/patches/server/0123-Cap-Entity-Collisions.patch new file mode 100644 index 000000000000..3c1709eb9b14 --- /dev/null +++ b/patches/server/0123-Cap-Entity-Collisions.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 22 Jan 2017 18:07:56 -0500 +Subject: [PATCH] Cap Entity Collisions + +Limit a single entity to colliding a max of configurable times per tick. +This will alleviate issues where living entities are hoarded in 1x1 pens + +This is not tied to the maxEntityCramming rule. Cramming will still apply +just as it does in Vanilla, but entity pushing logic will be capped. + +You can set this to 0 to disable collisions. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 158d830e7615ed396f7edd6b82daa4e4f876c894..0a31747076225d1221dce554135cde704c76eec4 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -393,6 +393,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + public long activatedTick = Integer.MIN_VALUE; + public void inactiveTick() { } + // Spigot end ++ protected int numCollisions = 0; // Paper - Cap entity collisions + // Paper start - Entity origin API + @javax.annotation.Nullable + private org.bukkit.util.Vector origin; +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 6ac549316d056a0de02e062fd4a28d7ac02a9d98..ec862d3a70b9d9f71873a71c1f1d143bc95799ae 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3361,10 +3361,12 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + Iterator iterator1 = list.iterator(); ++ this.numCollisions = Math.max(0, this.numCollisions - this.level().paperConfig().collisions.maxEntityCollisions); // Paper - Cap entity collisions + +- while (iterator1.hasNext()) { ++ while (iterator1.hasNext() && this.numCollisions < this.level().paperConfig().collisions.maxEntityCollisions) { // Paper - Cap entity collisions + Entity entity1 = (Entity) iterator1.next(); +- ++ entity1.numCollisions++; // Paper - Cap entity collisions ++ this.numCollisions++; // Paper - Cap entity collisions + this.doPush(entity1); + } + } diff --git a/patches/server/0123-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch b/patches/server/0123-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch deleted file mode 100644 index 16a57c51f81a..000000000000 --- a/patches/server/0123-ExperienceOrbs-API-for-Reason-Source-Triggering-play.patch +++ /dev/null @@ -1,355 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 19 Dec 2017 16:31:46 -0500 -Subject: [PATCH] ExperienceOrbs API for Reason/Source/Triggering player - -Adds lots of information about why this orb exists. - -Replaces isFromBottle() with logic that persists entity reloads too. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 4f3c82f1b5ae24d5f70318fa96fae2a58ce7fd9f..45236a077d798d6a257a2e982b58901167ecd06e 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -427,7 +427,7 @@ public class ServerPlayerGameMode { - - // Drop event experience - if (flag && event != null) { -- iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop()); -+ iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper - } - - return true; -diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -index 59bad6c92cc421dd05c7315e2ab694a669433ab4..ae70ad9d6c02fcb5631a3c45db283b29844bbb0d 100644 ---- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -+++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -@@ -39,9 +39,63 @@ public class ExperienceOrb extends Entity { - public int value; - private int count; - private Player followingPlayer; -+ // Paper start -+ @javax.annotation.Nullable -+ public java.util.UUID sourceEntityId; -+ @javax.annotation.Nullable -+ public java.util.UUID triggerEntityId; -+ public org.bukkit.entity.ExperienceOrb.SpawnReason spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; -+ -+ private void loadPaperNBT(CompoundTag tag) { -+ if (!tag.contains("Paper.ExpData", net.minecraft.nbt.Tag.TAG_COMPOUND)) { -+ return; -+ } -+ CompoundTag comp = tag.getCompound("Paper.ExpData"); -+ if (comp.hasUUID("source")) { -+ this.sourceEntityId = comp.getUUID("source"); -+ } -+ if (comp.hasUUID("trigger")) { -+ this.triggerEntityId = comp.getUUID("trigger"); -+ } -+ if (comp.contains("reason")) { -+ String reason = comp.getString("reason"); -+ try { -+ this.spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.valueOf(reason); -+ } catch (Exception e) { -+ this.level().getCraftServer().getLogger().warning("Invalid spawnReason set for experience orb: " + e.getMessage() + " - " + reason); -+ } -+ } -+ } -+ private void savePaperNBT(CompoundTag tag) { -+ CompoundTag comp = new CompoundTag(); -+ if (this.sourceEntityId != null) { -+ comp.putUUID("source", this.sourceEntityId); -+ } -+ if (this.triggerEntityId != null) { -+ comp.putUUID("trigger", triggerEntityId); -+ } -+ if (this.spawnReason != null && this.spawnReason != org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN) { -+ comp.putString("reason", this.spawnReason.name()); -+ } -+ tag.put("Paper.ExpData", comp); -+ } - -+ @io.papermc.paper.annotation.DoNotUse -+ @Deprecated - public ExperienceOrb(Level world, double x, double y, double z, int amount) { -+ this(world, x, y, z, amount, null, null); -+ } -+ -+ public ExperienceOrb(Level world, double x, double y, double z, int amount, @javax.annotation.Nullable org.bukkit.entity.ExperienceOrb.SpawnReason reason, @javax.annotation.Nullable Entity triggerId) { -+ this(world, x, y, z, amount, reason, triggerId, null); -+ } -+ -+ public ExperienceOrb(Level world, double x, double y, double z, int amount, @javax.annotation.Nullable org.bukkit.entity.ExperienceOrb.SpawnReason reason, @javax.annotation.Nullable Entity triggerId, @javax.annotation.Nullable Entity sourceId) { - this(EntityType.EXPERIENCE_ORB, world); -+ this.sourceEntityId = sourceId != null ? sourceId.getUUID() : null; -+ this.triggerEntityId = triggerId != null ? triggerId.getUUID() : null; -+ this.spawnReason = reason != null ? reason : org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; -+ // Paper end - this.setPos(x, y, z); - this.setYRot((float) (this.random.nextDouble() * 360.0D)); - this.setDeltaMovement((this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D, this.random.nextDouble() * 0.2D * 2.0D, (this.random.nextDouble() * 0.20000000298023224D - 0.10000000149011612D) * 2.0D); -@@ -160,12 +214,20 @@ public class ExperienceOrb extends Entity { - } - - public static void award(ServerLevel world, Vec3 pos, int amount) { -+ // Paper start - add reasons for orbs -+ award(world, pos, amount, null, null, null); -+ } -+ public static void award(ServerLevel world, Vec3 pos, int amount, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId) { -+ award(world, pos, amount, reason, triggerId, null); -+ } -+ public static void award(ServerLevel world, Vec3 pos, int amount, org.bukkit.entity.ExperienceOrb.SpawnReason reason, Entity triggerId, Entity sourceId) { -+ // Paper end - add reasons for orbs - while (amount > 0) { - int j = ExperienceOrb.getExperienceValue(amount); - - amount -= j; - if (!ExperienceOrb.tryMergeToExisting(world, pos, j)) { -- world.addFreshEntity(new ExperienceOrb(world, pos.x(), pos.y(), pos.z(), j)); -+ world.addFreshEntity(new ExperienceOrb(world, pos.x(), pos.y(), pos.z(), j, reason, triggerId, sourceId)); // Paper - add reason - } - } - -@@ -235,6 +297,7 @@ public class ExperienceOrb extends Entity { - nbt.putShort("Age", (short) this.age); - nbt.putShort("Value", (short) this.value); - nbt.putInt("Count", this.count); -+ this.savePaperNBT(nbt); // Paper - } - - @Override -@@ -243,6 +306,7 @@ public class ExperienceOrb extends Entity { - this.age = nbt.getShort("Age"); - this.value = nbt.getShort("Value"); - this.count = Math.max(nbt.getInt("Count"), 1); -+ this.loadPaperNBT(nbt); // Paper - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index fad14002768e55f5b300d1829f16b3d7b7504bf6..ed8e0f5ca865dcde73a886e65f0fd1d7d4e7ac05 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1786,7 +1786,8 @@ public abstract class LivingEntity extends Entity implements Attackable { - protected void dropExperience() { - // CraftBukkit start - Update getExpReward() above if the removed if() changes! - if (true && !(this instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon)) { // CraftBukkit - SPIGOT-2420: Special case ender dragon will drop the xp over time -- ExperienceOrb.award((ServerLevel) this.level(), this.position(), this.expToDrop); -+ LivingEntity attacker = this.lastHurtByPlayer != null ? this.lastHurtByPlayer : this.lastHurtByMob; // Paper -+ ExperienceOrb.award((ServerLevel) this.level(), this.position(), this.expToDrop, this instanceof ServerPlayer ? org.bukkit.entity.ExperienceOrb.SpawnReason.PLAYER_DEATH : org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, attacker, this); // Paper - this.expToDrop = 0; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java -index f307f9077917f426a90523708c572b95cc7b6778..907ed82fea71254d6624eda878e2668cd26422a7 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Animal.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java -@@ -258,12 +258,14 @@ public abstract class Animal extends AgeableMob { - - public void finalizeSpawnChildFromBreeding(ServerLevel worldserver, Animal entityanimal, @Nullable AgeableMob entityageable, int experience) { - // CraftBukkit end -- Optional.ofNullable(this.getLoveCause()).or(() -> { -- return Optional.ofNullable(entityanimal.getLoveCause()); -- }).ifPresent((entityplayer) -> { -+ // Paper start -+ ServerPlayer entityplayer = this.getLoveCause(); -+ if (entityplayer == null) entityplayer = entityanimal.getLoveCause(); -+ if (entityplayer != null) { -+ // Paper end - entityplayer.awardStat(Stats.ANIMALS_BRED); - CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, entityanimal, entityageable); -- }); -+ } // Paper - this.setAge(6000); - entityanimal.setAge(6000); - this.resetLove(); -@@ -272,7 +274,7 @@ public abstract class Animal extends AgeableMob { - if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { - // CraftBukkit start - use event experience - if (experience > 0) { -- worldserver.addFreshEntity(new ExperienceOrb(worldserver, this.getX(), this.getY(), this.getZ(), experience)); -+ worldserver.addFreshEntity(new ExperienceOrb(worldserver, this.getX(), this.getY(), this.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityageable)); // Paper - } - // CraftBukkit end - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java -index 335249181f34bfe8b0e359c591e4eae0af63b0fe..8670d8b2a08e96df787a91f36c48df8b345080dc 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java -@@ -909,7 +909,7 @@ public class Fox extends Animal implements VariantHolder { - if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { - // CraftBukkit start - use event experience - if (experience > 0) { -- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), experience)); -+ this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), experience, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer, entityfox)); // Paper - } - // CraftBukkit end - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 83dbf663cd725adcbcfee4ac633f369240375381..0dab0da65788720e56a568918de458ab7195ef5c 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -459,7 +459,7 @@ public class Turtle extends Animal { - RandomSource randomsource = this.animal.getRandom(); - - if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { -- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1)); -+ this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper; - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index 2471800014d1661c2f422e5a24f0f3b00fa838f2..f5ce6423b8146cc741023e15004fe9814a035da8 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -673,7 +673,7 @@ public class EnderDragon extends Mob implements Enemy { - - if (this.level() instanceof ServerLevel) { - if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp -- ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.08F)); -+ ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.08F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper - } - - if (this.dragonDeathTime == 1 && !this.isSilent()) { -@@ -702,7 +702,7 @@ public class EnderDragon extends Mob implements Enemy { - this.move(MoverType.SELF, new Vec3(0.0D, 0.10000000149011612D, 0.0D)); - if (this.dragonDeathTime == 200 && this.level() instanceof ServerLevel) { - if (true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp -- ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.2F)); -+ ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.2F), org.bukkit.entity.ExperienceOrb.SpawnReason.ENTITY_DEATH, this.lastHurtByPlayer, this); // Paper - } - - if (this.dragonFight != null) { -diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index 4034b8e7503f611dc9be121d8da2020ae7155b8c..f0d5e45d0d6ac51106379d20690d34a032a24c39 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -638,7 +638,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - } - - if (offer.shouldRewardExp()) { -- this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i)); -+ this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -index d4d1cad7963a5adcc8c32e1bc02104eb70514331..0321b4bb622930bfe57661b0e6b893d7635668fb 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -@@ -207,7 +207,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - if (offer.shouldRewardExp()) { - int i = 3 + this.random.nextInt(4); - -- this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i)); -+ this.level().addFreshEntity(new ExperienceOrb(this.level(), this.getX(), this.getY() + 0.5D, this.getZ(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.getTradingPlayer(), this)); // Paper - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -index 8b27b5118cbeeb0b25fb6a23056e51899be32035..94bdd467108bc5fd0211f67a792787ef7c356619 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -@@ -522,7 +522,7 @@ public class FishingHook extends Projectile { - this.level().addFreshEntity(entityitem); - // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop() - if (playerFishEvent.getExpToDrop() > 0) { -- entityhuman.level().addFreshEntity(new ExperienceOrb(entityhuman.level(), entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop())); -+ entityhuman.level().addFreshEntity(new ExperienceOrb(entityhuman.level(), entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getPlayerOwner(), this)); // Paper - } - // CraftBukkit end - if (itemstack1.is(ItemTags.FISHES)) { -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java -index 5d0e2c53d41e897532a8ec3c0a7b33e9b60e9ab5..e53046c6d47b4fd3d82132bc980a31b9491df6a7 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java -@@ -51,7 +51,7 @@ public class ThrownExperienceBottle extends ThrowableItemProjectile { - } - // CraftBukkit end - -- ExperienceOrb.award((ServerLevel) this.level(), this.position(), i); -+ ExperienceOrb.award((ServerLevel) this.level(), this.position(), i, org.bukkit.entity.ExperienceOrb.SpawnReason.EXP_BOTTLE, this.getOwner(), this); // Paper - this.discard(); - } - -diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -index 8d09c134058e55a23df4e23d965a7a783aed701e..45242f0ed5a0f98953df5f27fb76874d2d9e3473 100644 ---- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -@@ -97,7 +97,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { - public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) { - context.execute((world, blockposition) -> { - if (world instanceof ServerLevel) { -- ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), this.getExperienceAmount(world)); -+ ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), this.getExperienceAmount(world), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Paper - } - - world.levelEvent(1042, blockposition, 0); -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 756a8ae14ffc46d6ebe0a858a03fb2e89b8e118a..89a62fbeeb78c864938a1cea84178478c6dc1b34 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -369,8 +369,13 @@ public class Block extends BlockBehaviour implements ItemLike { - } - - public void popExperience(ServerLevel world, BlockPos pos, int size) { -+ // Paper start - add entity parameter -+ popExperience(world, pos, size, null); -+ } -+ public void popExperience(ServerLevel world, BlockPos pos, int size, net.minecraft.world.entity.Entity entity) { -+ // Paper end - add entity parameter - if (world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) { -- ExperienceOrb.award(world, Vec3.atCenterOf(pos), size); -+ ExperienceOrb.award(world, Vec3.atCenterOf(pos), size, org.bukkit.entity.ExperienceOrb.SpawnReason.BLOCK_BREAK, entity); // Paper - } - - } -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 65e7dcace8607c4d15938c697c882761c067f08e..7a13042631bea761952490cfd14dc20147405161 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 -@@ -651,7 +651,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - j = event.getExpToDrop(); - // CraftBukkit end - -- ExperienceOrb.award(worldserver, vec3d, j); -+ ExperienceOrb.award(worldserver, vec3d, j, org.bukkit.entity.ExperienceOrb.SpawnReason.FURNACE, entityhuman); // Paper - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -index c5547988e8e4ec5f1d090d264e14556f490d4e31..c3a55347b630c57aa36f9c761e1937dc95027bb7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -@@ -363,7 +363,7 @@ public final class CraftEntityTypes { - return item; - })); - register(new EntityTypeData<>(EntityType.EXPERIENCE_ORB, ExperienceOrb.class, CraftExperienceOrb::new, -- spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0) -+ spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null) // Paper - )); - register(new EntityTypeData<>(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class, CraftAreaEffectCloud::new, spawnData -> new net.minecraft.world.entity.AreaEffectCloud(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); - register(new EntityTypeData<>(EntityType.EGG, Egg.class, CraftEgg::new, spawnData -> new ThrownEgg(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z()))); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java -index 9231511af4cba747594000364f0b8fceeeab4819..5a7d314ec0562e472f5dc45924a7b24841cff126 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java -@@ -18,6 +18,18 @@ public class CraftExperienceOrb extends CraftEntity implements ExperienceOrb { - this.getHandle().value = value; - } - -+ // Paper start -+ public java.util.UUID getTriggerEntityId() { -+ return getHandle().triggerEntityId; -+ } -+ public java.util.UUID getSourceEntityId() { -+ return getHandle().sourceEntityId; -+ } -+ public SpawnReason getSpawnReason() { -+ return getHandle().spawnReason; -+ } -+ // Paper end -+ - @Override - public net.minecraft.world.entity.ExperienceOrb getHandle() { - return (net.minecraft.world.entity.ExperienceOrb) this.entity; diff --git a/patches/server/0124-Cap-Entity-Collisions.patch b/patches/server/0124-Cap-Entity-Collisions.patch deleted file mode 100644 index a0754189559c..000000000000 --- a/patches/server/0124-Cap-Entity-Collisions.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 22 Jan 2017 18:07:56 -0500 -Subject: [PATCH] Cap Entity Collisions - -Limit a single entity to colliding a max of configurable times per tick. -This will alleviate issues where living entities are hoarded in 1x1 pens - -This is not tied to the maxEntityCramming rule. Cramming will still apply -just as it does in Vanilla, but entity pushing logic will be capped. - -You can set this to 0 to disable collisions. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index a914d2906ae7433164d7f439a0f2f0d781b14747..05cb13a5636f82d48bf0bd8b9d27abdd80a8df71 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -393,6 +393,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - public long activatedTick = Integer.MIN_VALUE; - public void inactiveTick() { } - // Spigot end -+ protected int numCollisions = 0; // Paper - Cap entity collisions - // Paper start - Entity origin API - @javax.annotation.Nullable - private org.bukkit.util.Vector origin; -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index ed8e0f5ca865dcde73a886e65f0fd1d7d4e7ac05..a799b64e9d655e7eba509e9930e623a88bbb0389 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3348,10 +3348,12 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - Iterator iterator1 = list.iterator(); -+ this.numCollisions = Math.max(0, this.numCollisions - this.level().paperConfig().collisions.maxEntityCollisions); // Paper - Cap entity collisions - -- while (iterator1.hasNext()) { -+ while (iterator1.hasNext() && this.numCollisions < this.level().paperConfig().collisions.maxEntityCollisions) { // Paper - Cap entity collisions - Entity entity1 = (Entity) iterator1.next(); -- -+ entity1.numCollisions++; // Paper - Cap entity collisions -+ this.numCollisions++; // Paper - Cap entity collisions - this.doPush(entity1); - } - } diff --git a/patches/server/0125-Remove-CraftScheduler-Async-Task-Debugger.patch b/patches/server/0124-Remove-CraftScheduler-Async-Task-Debugger.patch similarity index 100% rename from patches/server/0125-Remove-CraftScheduler-Async-Task-Debugger.patch rename to patches/server/0124-Remove-CraftScheduler-Async-Task-Debugger.patch diff --git a/patches/server/0125-Properly-handle-async-calls-to-restart-the-server.patch b/patches/server/0125-Properly-handle-async-calls-to-restart-the-server.patch new file mode 100644 index 000000000000..8096104d813e --- /dev/null +++ b/patches/server/0125-Properly-handle-async-calls-to-restart-the-server.patch @@ -0,0 +1,290 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Fri, 12 May 2017 23:34:11 -0500 +Subject: [PATCH] Properly handle async calls to restart the server + +The watchdog thread calls the server restart function asynchronously. Prior to +this change, it attempted to do several non-safe operations from the watchdog +thread, rather than the main. Specifically, because of a separate upstream change, +it causes player entities to be ticked asynchronously, among other things. + +This is dangerous. + +This patch moves the old handling into a synchronous variant, for calls from the +restart command, and adds separate handling for async calls, such as those from +the watchdog thread. + +When calling from the watchdog thread, we cannot assume the main thread is in a +tickable state; it may be completely deadlocked. In order to handle this, we mark +the server as stopping, in order to account for situations where the server should +complete a tick reasonbly soon, i.e. 99% of cases. + +Should the server not enter a state where it is stopping within 10 seconds, We +will assume that the server has in fact deadlocked and will proceed to force +kill the server. + +This modification does not force restart the server should we actually enter a +deadlocked state where the server is stopping, whereas this will in most cases +exit within a reasonable amount of time, to put a fixed limit on a process that +will have plugins and worlds saving to the disk has a high potential to result +in corruption/dataloss. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index fcaa31ccd6f6e6affaccf76403dbab26e6932571..1d8e17f4b862e71cc5ef8c5eabfb80dc427aec51 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -236,6 +236,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> levels; + private PlayerList playerList; + private volatile boolean running; ++ private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart + private boolean stopped; + private int tickCount; + private int ticksUntilAutosave; +@@ -919,7 +920,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && new File( split[0] ).isFile() ) ++ // Paper - extract method and cleanup ++ boolean isRestarting = addShutdownHook( restartScript ); ++ if ( isRestarting ) + { +- System.out.println( "Attempting to restart with " + restartScript ); ++ System.out.println( "Attempting to restart with " + SpigotConfig.restartScript ); ++ } else ++ { ++ System.out.println( "Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server." ); ++ } ++ // Stop the watchdog ++ WatchdogThread.doStop(); + +- // Disable Watchdog +- WatchdogThread.doStop(); ++ shutdownServer( isRestarting ); ++ // Paper end ++ } catch ( Exception ex ) ++ { ++ ex.printStackTrace(); ++ } ++ } + +- // Kick all players +- for ( ServerPlayer p : (List) MinecraftServer.getServer().getPlayerList().players ) +- { +- p.connection.disconnect(SpigotConfig.restartMessage); +- } +- // Give the socket a chance to send the packets +- try +- { +- Thread.sleep( 100 ); +- } catch ( InterruptedException ex ) +- { +- } +- // Close the socket so we can rebind with the new process +- MinecraftServer.getServer().getConnection().stop(); ++ // Paper start - sync copied from above with minor changes, async added ++ private static void shutdownServer(boolean isRestarting) ++ { ++ if ( MinecraftServer.getServer().isSameThread() ) ++ { ++ // Kick all players ++ for ( ServerPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) ) ++ { ++ p.connection.disconnect(SpigotConfig.restartMessage); ++ } ++ // Give the socket a chance to send the packets ++ try ++ { ++ Thread.sleep( 100 ); ++ } catch ( InterruptedException ex ) ++ { ++ } + +- // Give time for it to kick in +- try +- { +- Thread.sleep( 100 ); +- } catch ( InterruptedException ex ) +- { +- } ++ closeSocket(); + +- // Actually shutdown +- try +- { +- MinecraftServer.getServer().close(); +- } catch ( Throwable t ) +- { +- } ++ // Actually shutdown ++ try ++ { ++ MinecraftServer.getServer().close(); // calls stop() ++ } catch ( Throwable t ) ++ { ++ } ++ ++ // Actually stop the JVM ++ System.exit( 0 ); + +- // This will be done AFTER the server has completely halted +- Thread shutdownHook = new Thread() ++ } else ++ { ++ // Mark the server to shutdown at the end of the tick ++ MinecraftServer.getServer().safeShutdown( false, isRestarting ); ++ ++ // wait 10 seconds to see if we're actually going to try shutdown ++ try ++ { ++ Thread.sleep( 10000 ); ++ } ++ catch (InterruptedException ignored) ++ { ++ } ++ ++ // Check if we've actually hit a state where the server is going to safely shutdown ++ // if we have, let the server stop as usual ++ if (MinecraftServer.getServer().isStopped()) return; ++ ++ // If the server hasn't stopped by now, assume worse case and kill ++ closeSocket(); ++ System.exit( 0 ); ++ } ++ } ++ // Paper end ++ ++ // Paper - Split from moved code ++ private static void closeSocket() ++ { ++ // Close the socket so we can rebind with the new process ++ MinecraftServer.getServer().getConnection().stop(); ++ ++ // Give time for it to kick in ++ try ++ { ++ Thread.sleep( 100 ); ++ } catch ( InterruptedException ex ) ++ { ++ } ++ } ++ // Paper end ++ ++ // Paper start - copied from above and modified to return if the hook registered ++ private static boolean addShutdownHook(String restartScript) ++ { ++ String[] split = restartScript.split( " " ); ++ if ( split.length > 0 && new File( split[0] ).isFile() ) ++ { ++ Thread shutdownHook = new Thread() ++ { ++ @Override ++ public void run() + { +- @Override +- public void run() ++ try + { +- try ++ String os = System.getProperty( "os.name" ).toLowerCase(java.util.Locale.ENGLISH); ++ if ( os.contains( "win" ) ) + { +- String os = System.getProperty( "os.name" ).toLowerCase(java.util.Locale.ENGLISH); +- if ( os.contains( "win" ) ) +- { +- Runtime.getRuntime().exec( "cmd /c start " + restartScript ); +- } else +- { +- Runtime.getRuntime().exec( "sh " + restartScript ); +- } +- } catch ( Exception e ) ++ Runtime.getRuntime().exec( "cmd /c start " + restartScript ); ++ } else + { +- e.printStackTrace(); ++ Runtime.getRuntime().exec( "sh " + restartScript ); + } ++ } catch ( Exception e ) ++ { ++ e.printStackTrace(); + } +- }; +- +- shutdownHook.setDaemon( true ); +- Runtime.getRuntime().addShutdownHook( shutdownHook ); +- } else +- { +- System.out.println( "Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server." ); +- +- // Actually shutdown +- try +- { +- MinecraftServer.getServer().close(); +- } catch ( Throwable t ) +- { + } +- } +- System.exit( 0 ); +- } catch ( Exception ex ) ++ }; ++ ++ shutdownHook.setDaemon( true ); ++ Runtime.getRuntime().addShutdownHook( shutdownHook ); ++ return true; ++ } else + { +- ex.printStackTrace(); ++ return false; + } + } ++ // Paper end ++ + } diff --git a/patches/server/0126-Add-option-to-make-parrots-stay-on-shoulders-despite.patch b/patches/server/0126-Add-option-to-make-parrots-stay-on-shoulders-despite.patch new file mode 100644 index 000000000000..3a0dec28f722 --- /dev/null +++ b/patches/server/0126-Add-option-to-make-parrots-stay-on-shoulders-despite.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 16 May 2017 21:29:08 -0500 +Subject: [PATCH] Add option to make parrots stay on shoulders despite movement + +Makes parrots not fall off whenever the player changes height, or touches water, or gets hit by a passing leaf. +Instead, switches the behavior so that players have to sneak to make the birds leave. + +I suspect Mojang may switch to this behavior before full release. + +To be converted into a Paper-API event at some point in the future? + +== AT == +public net.minecraft.world.entity.player.Player removeEntitiesOnShoulder()V + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 6fec49a0833300ff0e4ef0f22d21480dfac9a2c7..12cfc3ed36d6eebd477dfd9058b2852e0a0d98eb 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2177,6 +2177,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + switch (packet.getAction()) { + case PRESS_SHIFT_KEY: + this.player.setShiftKeyDown(true); ++ ++ // Paper start - Add option to make parrots stay ++ if (this.player.level().paperConfig().entities.behavior.parrotsAreUnaffectedByPlayerMovement) { ++ this.player.removeEntitiesOnShoulder(); ++ } ++ // Paper end - Add option to make parrots stay ++ + break; + case RELEASE_SHIFT_KEY: + this.player.setShiftKeyDown(false); +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 10c2c9d7c7feb878319eb19cd1fb6401da3b9189..6ff0c2107fa0623d30d9159e3c67388faf597440 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -588,6 +588,7 @@ public abstract class Player extends LivingEntity { + this.playShoulderEntityAmbientSound(this.getShoulderEntityLeft()); + this.playShoulderEntityAmbientSound(this.getShoulderEntityRight()); + if (!this.level().isClientSide && (this.fallDistance > 0.5F || this.isInWater()) || this.abilities.flying || this.isSleeping() || this.isInPowderSnow) { ++ if (!this.level().paperConfig().entities.behavior.parrotsAreUnaffectedByPlayerMovement) // Paper - Add option to make parrots stay + this.removeEntitiesOnShoulder(); + } + diff --git a/patches/server/0126-Properly-handle-async-calls-to-restart-the-server.patch b/patches/server/0126-Properly-handle-async-calls-to-restart-the-server.patch deleted file mode 100644 index b9529845fa41..000000000000 --- a/patches/server/0126-Properly-handle-async-calls-to-restart-the-server.patch +++ /dev/null @@ -1,290 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Fri, 12 May 2017 23:34:11 -0500 -Subject: [PATCH] Properly handle async calls to restart the server - -The watchdog thread calls the server restart function asynchronously. Prior to -this change, it attempted to do several non-safe operations from the watchdog -thread, rather than the main. Specifically, because of a separate upstream change, -it causes player entities to be ticked asynchronously, among other things. - -This is dangerous. - -This patch moves the old handling into a synchronous variant, for calls from the -restart command, and adds separate handling for async calls, such as those from -the watchdog thread. - -When calling from the watchdog thread, we cannot assume the main thread is in a -tickable state; it may be completely deadlocked. In order to handle this, we mark -the server as stopping, in order to account for situations where the server should -complete a tick reasonbly soon, i.e. 99% of cases. - -Should the server not enter a state where it is stopping within 10 seconds, We -will assume that the server has in fact deadlocked and will proceed to force -kill the server. - -This modification does not force restart the server should we actually enter a -deadlocked state where the server is stopping, whereas this will in most cases -exit within a reasonable amount of time, to put a fixed limit on a process that -will have plugins and worlds saving to the disk has a high potential to result -in corruption/dataloss. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 1429dd8ca37e0b268304b92596fea316706e5c01..5ec728f6000753d517d943562efb55ad2541b01c 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -236,6 +236,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> levels; - private PlayerList playerList; - private volatile boolean running; -+ private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart - private boolean stopped; - private int tickCount; - private int ticksUntilAutosave; -@@ -919,7 +920,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && new File( split[0] ).isFile() ) -+ // Paper - extract method and cleanup -+ boolean isRestarting = addShutdownHook( restartScript ); -+ if ( isRestarting ) - { -- System.out.println( "Attempting to restart with " + restartScript ); -+ System.out.println( "Attempting to restart with " + SpigotConfig.restartScript ); -+ } else -+ { -+ System.out.println( "Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server." ); -+ } -+ // Stop the watchdog -+ WatchdogThread.doStop(); - -- // Disable Watchdog -- WatchdogThread.doStop(); -+ shutdownServer( isRestarting ); -+ // Paper end -+ } catch ( Exception ex ) -+ { -+ ex.printStackTrace(); -+ } -+ } - -- // Kick all players -- for ( ServerPlayer p : (List) MinecraftServer.getServer().getPlayerList().players ) -- { -- p.connection.disconnect(SpigotConfig.restartMessage); -- } -- // Give the socket a chance to send the packets -- try -- { -- Thread.sleep( 100 ); -- } catch ( InterruptedException ex ) -- { -- } -- // Close the socket so we can rebind with the new process -- MinecraftServer.getServer().getConnection().stop(); -+ // Paper start - sync copied from above with minor changes, async added -+ private static void shutdownServer(boolean isRestarting) -+ { -+ if ( MinecraftServer.getServer().isSameThread() ) -+ { -+ // Kick all players -+ for ( ServerPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) ) -+ { -+ p.connection.disconnect(SpigotConfig.restartMessage); -+ } -+ // Give the socket a chance to send the packets -+ try -+ { -+ Thread.sleep( 100 ); -+ } catch ( InterruptedException ex ) -+ { -+ } - -- // Give time for it to kick in -- try -- { -- Thread.sleep( 100 ); -- } catch ( InterruptedException ex ) -- { -- } -+ closeSocket(); - -- // Actually shutdown -- try -- { -- MinecraftServer.getServer().close(); -- } catch ( Throwable t ) -- { -- } -+ // Actually shutdown -+ try -+ { -+ MinecraftServer.getServer().close(); // calls stop() -+ } catch ( Throwable t ) -+ { -+ } -+ -+ // Actually stop the JVM -+ System.exit( 0 ); - -- // This will be done AFTER the server has completely halted -- Thread shutdownHook = new Thread() -+ } else -+ { -+ // Mark the server to shutdown at the end of the tick -+ MinecraftServer.getServer().safeShutdown( false, isRestarting ); -+ -+ // wait 10 seconds to see if we're actually going to try shutdown -+ try -+ { -+ Thread.sleep( 10000 ); -+ } -+ catch (InterruptedException ignored) -+ { -+ } -+ -+ // Check if we've actually hit a state where the server is going to safely shutdown -+ // if we have, let the server stop as usual -+ if (MinecraftServer.getServer().isStopped()) return; -+ -+ // If the server hasn't stopped by now, assume worse case and kill -+ closeSocket(); -+ System.exit( 0 ); -+ } -+ } -+ // Paper end -+ -+ // Paper - Split from moved code -+ private static void closeSocket() -+ { -+ // Close the socket so we can rebind with the new process -+ MinecraftServer.getServer().getConnection().stop(); -+ -+ // Give time for it to kick in -+ try -+ { -+ Thread.sleep( 100 ); -+ } catch ( InterruptedException ex ) -+ { -+ } -+ } -+ // Paper end -+ -+ // Paper start - copied from above and modified to return if the hook registered -+ private static boolean addShutdownHook(String restartScript) -+ { -+ String[] split = restartScript.split( " " ); -+ if ( split.length > 0 && new File( split[0] ).isFile() ) -+ { -+ Thread shutdownHook = new Thread() -+ { -+ @Override -+ public void run() - { -- @Override -- public void run() -+ try - { -- try -+ String os = System.getProperty( "os.name" ).toLowerCase(java.util.Locale.ENGLISH); -+ if ( os.contains( "win" ) ) - { -- String os = System.getProperty( "os.name" ).toLowerCase(java.util.Locale.ENGLISH); -- if ( os.contains( "win" ) ) -- { -- Runtime.getRuntime().exec( "cmd /c start " + restartScript ); -- } else -- { -- Runtime.getRuntime().exec( "sh " + restartScript ); -- } -- } catch ( Exception e ) -+ Runtime.getRuntime().exec( "cmd /c start " + restartScript ); -+ } else - { -- e.printStackTrace(); -+ Runtime.getRuntime().exec( "sh " + restartScript ); - } -+ } catch ( Exception e ) -+ { -+ e.printStackTrace(); - } -- }; -- -- shutdownHook.setDaemon( true ); -- Runtime.getRuntime().addShutdownHook( shutdownHook ); -- } else -- { -- System.out.println( "Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server." ); -- -- // Actually shutdown -- try -- { -- MinecraftServer.getServer().close(); -- } catch ( Throwable t ) -- { - } -- } -- System.exit( 0 ); -- } catch ( Exception ex ) -+ }; -+ -+ shutdownHook.setDaemon( true ); -+ Runtime.getRuntime().addShutdownHook( shutdownHook ); -+ return true; -+ } else - { -- ex.printStackTrace(); -+ return false; - } - } -+ // Paper end -+ - } diff --git a/patches/server/0127-Add-configuration-option-to-prevent-player-names-fro.patch b/patches/server/0127-Add-configuration-option-to-prevent-player-names-fro.patch new file mode 100644 index 000000000000..0c9e8ad80226 --- /dev/null +++ b/patches/server/0127-Add-configuration-option-to-prevent-player-names-fro.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kashike +Date: Fri, 9 Jun 2017 07:24:34 -0700 +Subject: [PATCH] Add configuration option to prevent player names from being + suggested + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 9978f3ffb3803b7f9278d49a688bc2d4bf740cf3..827579f59d34b61912a67b40624f0f41524185fd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2773,5 +2773,10 @@ public final class CraftServer implements Server { + commandMap.registerServerAliases(); + return true; + } ++ ++ @Override ++ public boolean suggestPlayerNamesWhenNullTabCompletions() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().commands.suggestPlayerNamesWhenNullTabCompletions; ++ } + // Paper end + } diff --git a/patches/server/0127-Add-option-to-make-parrots-stay-on-shoulders-despite.patch b/patches/server/0127-Add-option-to-make-parrots-stay-on-shoulders-despite.patch deleted file mode 100644 index 3bda195d941e..000000000000 --- a/patches/server/0127-Add-option-to-make-parrots-stay-on-shoulders-despite.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Tue, 16 May 2017 21:29:08 -0500 -Subject: [PATCH] Add option to make parrots stay on shoulders despite movement - -Makes parrots not fall off whenever the player changes height, or touches water, or gets hit by a passing leaf. -Instead, switches the behavior so that players have to sneak to make the birds leave. - -I suspect Mojang may switch to this behavior before full release. - -To be converted into a Paper-API event at some point in the future? - -== AT == -public net.minecraft.world.entity.player.Player removeEntitiesOnShoulder()V - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 63b2028bc880b39e679637228ba86c2cc470490f..2f34ff72f8932a8cac9af48003dfa505f19f07d0 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2177,6 +2177,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - switch (packet.getAction()) { - case PRESS_SHIFT_KEY: - this.player.setShiftKeyDown(true); -+ -+ // Paper start - Add option to make parrots stay -+ if (this.player.level().paperConfig().entities.behavior.parrotsAreUnaffectedByPlayerMovement) { -+ this.player.removeEntitiesOnShoulder(); -+ } -+ // Paper end - Add option to make parrots stay -+ - break; - case RELEASE_SHIFT_KEY: - this.player.setShiftKeyDown(false); -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 f8d3e195c094ff200c0a7bd8cd4829ef36d328da..a79e0b2d4ba706de0989ef68c87bb29fc1bf08ab 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -587,6 +587,7 @@ public abstract class Player extends LivingEntity { - this.playShoulderEntityAmbientSound(this.getShoulderEntityLeft()); - this.playShoulderEntityAmbientSound(this.getShoulderEntityRight()); - if (!this.level().isClientSide && (this.fallDistance > 0.5F || this.isInWater()) || this.abilities.flying || this.isSleeping() || this.isInPowderSnow) { -+ if (!this.level().paperConfig().entities.behavior.parrotsAreUnaffectedByPlayerMovement) // Paper - Add option to make parrots stay - this.removeEntitiesOnShoulder(); - } - diff --git a/patches/server/0128-Add-configuration-option-to-prevent-player-names-fro.patch b/patches/server/0128-Add-configuration-option-to-prevent-player-names-fro.patch deleted file mode 100644 index ffce53987c69..000000000000 --- a/patches/server/0128-Add-configuration-option-to-prevent-player-names-fro.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kashike -Date: Fri, 9 Jun 2017 07:24:34 -0700 -Subject: [PATCH] Add configuration option to prevent player names from being - suggested - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 1983650dbbb634472e81984960b857f1e87b26b8..fd8733b602e4711a3b57fb3c8c313a9035f15df0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2761,5 +2761,10 @@ public final class CraftServer implements Server { - commandMap.registerServerAliases(); - return true; - } -+ -+ @Override -+ public boolean suggestPlayerNamesWhenNullTabCompletions() { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().commands.suggestPlayerNamesWhenNullTabCompletions; -+ } - // Paper end - } diff --git a/patches/server/0128-Use-TerminalConsoleAppender-for-console-improvements.patch b/patches/server/0128-Use-TerminalConsoleAppender-for-console-improvements.patch new file mode 100644 index 000000000000..be1dff00d079 --- /dev/null +++ b/patches/server/0128-Use-TerminalConsoleAppender-for-console-improvements.patch @@ -0,0 +1,667 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Fri, 9 Jun 2017 19:03:43 +0200 +Subject: [PATCH] Use TerminalConsoleAppender for console improvements + +Rewrite console improvements (console colors, tab completion, +persistent input line, ...) using JLine 3.x and TerminalConsoleAppender. + +Also uses the new ANSIComponentSerializer to serialize components when +logging them via the ComponentLogger, or when sending messages to the +console, for hex color support. + +New features: + - Support console colors for Vanilla commands + - Add console colors for warnings and errors + - Server can now be turned off safely using CTRL + C. JLine catches + the signal and the implementation shuts down the server cleanly. + - Support console colors and persistent input line when running in + IntelliJ IDEA + +Other changes: + - Server starts 1-2 seconds faster thanks to optimizations in Log4j + configuration + +Co-Authored-By: Emilia Kond + +diff --git a/build.gradle.kts b/build.gradle.kts +index 65e9d5918d46b123fb4f8122344a7d3863aec758..7a3c96318f95fcd6cf6fd94415958382d1193ec6 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -6,9 +6,30 @@ plugins { + id("com.github.johnrengelman.shadow") + } + ++val log4jPlugins = sourceSets.create("log4jPlugins") ++configurations.named(log4jPlugins.compileClasspathConfigurationName) { ++ extendsFrom(configurations.compileClasspath.get()) ++} ++val alsoShade: Configuration by configurations.creating ++ + dependencies { + implementation(project(":paper-api")) +- implementation("jline:jline:2.12.1") ++ // Paper start ++ implementation("org.jline:jline-terminal-jansi:3.21.0") ++ implementation("net.minecrell:terminalconsoleappender:1.3.0") ++ implementation("net.kyori:adventure-text-serializer-ansi:4.14.0") // Keep in sync with adventureVersion from Paper-API build file ++ implementation("net.kyori:ansi:1.0.3") // Manually bump beyond above transitive dep ++ /* ++ Required to add the missing Log4j2Plugins.dat file from log4j-core ++ which has been removed by Mojang. Without it, log4j has to classload ++ all its classes to check if they are plugins. ++ Scanning takes about 1-2 seconds so adding this speeds up the server start. ++ */ ++ runtimeOnly("org.apache.logging.log4j:log4j-core:2.19.0") ++ log4jPlugins.annotationProcessorConfigurationName("org.apache.logging.log4j:log4j-core:2.19.0") // Paper - Needed to generate meta for our Log4j plugins ++ runtimeOnly(log4jPlugins.output) ++ alsoShade(log4jPlugins.output) ++ // Paper end + implementation("org.apache.logging.log4j:log4j-iostreams:2.19.0") // Paper - remove exclusion + implementation("org.ow2.asm:asm-commons:9.5") + implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files +@@ -78,7 +99,7 @@ relocation { + } + + tasks.shadowJar { +- configurations = listOf(project.configurations.vanillaServer.get()) ++ configurations = listOf(project.configurations.vanillaServer.get(), alsoShade) + archiveClassifier.set("mojang-mapped") + + for (relocation in relocation.relocations.get()) { +diff --git a/src/log4jPlugins/java/io/papermc/paper/console/StripANSIConverter.java b/src/log4jPlugins/java/io/papermc/paper/console/StripANSIConverter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..91547f6e6fe90006713beb2818da634304bdd236 +--- /dev/null ++++ b/src/log4jPlugins/java/io/papermc/paper/console/StripANSIConverter.java +@@ -0,0 +1,51 @@ ++package io.papermc.paper.console; ++ ++import org.apache.logging.log4j.core.LogEvent; ++import org.apache.logging.log4j.core.config.Configuration; ++import org.apache.logging.log4j.core.config.plugins.Plugin; ++import org.apache.logging.log4j.core.layout.PatternLayout; ++import org.apache.logging.log4j.core.pattern.ConverterKeys; ++import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; ++import org.apache.logging.log4j.core.pattern.PatternConverter; ++import org.apache.logging.log4j.core.pattern.PatternFormatter; ++import org.apache.logging.log4j.core.pattern.PatternParser; ++ ++import java.util.List; ++import java.util.regex.Pattern; ++ ++@Plugin(name = "stripAnsi", category = PatternConverter.CATEGORY) ++@ConverterKeys({"stripAnsi"}) ++public final class StripANSIConverter extends LogEventPatternConverter { ++ final private Pattern ANSI_PATTERN = Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); ++ ++ private final List formatters; ++ ++ private StripANSIConverter(List formatters) { ++ super("stripAnsi", null); ++ this.formatters = formatters; ++ } ++ ++ @Override ++ public void format(LogEvent event, StringBuilder toAppendTo) { ++ int start = toAppendTo.length(); ++ for (PatternFormatter formatter : formatters) { ++ formatter.format(event, toAppendTo); ++ } ++ String content = toAppendTo.substring(start); ++ content = ANSI_PATTERN.matcher(content).replaceAll(""); ++ ++ toAppendTo.setLength(start); ++ toAppendTo.append(content); ++ } ++ ++ public static StripANSIConverter newInstance(Configuration config, String[] options) { ++ if (options.length != 1) { ++ LOGGER.error("Incorrect number of options on stripAnsi. Expected exactly 1, received " + options.length); ++ return null; ++ } ++ ++ PatternParser parser = PatternLayout.createPatternParser(config); ++ List formatters = parser.parse(options[0]); ++ return new StripANSIConverter(formatters); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a4070b59e261f0f1ac4beec47b11492f4724bf27 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +@@ -0,0 +1,41 @@ ++package com.destroystokyo.paper.console; ++ ++import net.minecraft.server.dedicated.DedicatedServer; ++import net.minecrell.terminalconsole.SimpleTerminalConsole; ++import org.bukkit.craftbukkit.command.ConsoleCommandCompleter; ++import org.jline.reader.LineReader; ++import org.jline.reader.LineReaderBuilder; ++ ++public final class PaperConsole extends SimpleTerminalConsole { ++ ++ private final DedicatedServer server; ++ ++ public PaperConsole(DedicatedServer server) { ++ this.server = server; ++ } ++ ++ @Override ++ protected LineReader buildReader(LineReaderBuilder builder) { ++ return super.buildReader(builder ++ .appName("Paper") ++ .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) ++ .completer(new ConsoleCommandCompleter(this.server)) ++ ); ++ } ++ ++ @Override ++ protected boolean isRunning() { ++ return !this.server.isStopped() && this.server.isRunning(); ++ } ++ ++ @Override ++ protected void runCommand(String command) { ++ this.server.handleConsoleInput(command, this.server.createCommandSourceStack()); ++ } ++ ++ @Override ++ protected void shutdown() { ++ this.server.halt(false); ++ } ++ ++} +diff --git a/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8f07539a82f449ad217e316a7513a1708781fb63 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java +@@ -0,0 +1,26 @@ ++package com.destroystokyo.paper.console; ++ ++import net.kyori.adventure.audience.MessageType; ++import net.kyori.adventure.identity.Identity; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.logger.slf4j.ComponentLogger; ++import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; ++import org.apache.logging.log4j.LogManager; ++import org.bukkit.craftbukkit.command.CraftConsoleCommandSender; ++ ++public class TerminalConsoleCommandSender extends CraftConsoleCommandSender { ++ ++ private static final ComponentLogger LOGGER = ComponentLogger.logger(LogManager.getRootLogger().getName()); ++ ++ @Override ++ public void sendRawMessage(String message) { ++ final Component msg = LegacyComponentSerializer.legacySection().deserialize(message); ++ this.sendMessage(Identity.nil(), msg, MessageType.SYSTEM); ++ } ++ ++ @Override ++ public void sendMessage(Identity identity, Component message, MessageType type) { ++ LOGGER.info(message); ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java +index 2e757cd9b01ac7eba1e4723a6e21dcea9d062483..ca80cbe422d766b3d044a5b53ce40bb7f92558e4 100644 +--- a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java ++++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java +@@ -24,6 +24,7 @@ import net.kyori.adventure.text.TranslationArgument; + import net.kyori.adventure.text.flattener.ComponentFlattener; + import net.kyori.adventure.text.format.TextColor; + import net.kyori.adventure.text.serializer.ComponentSerializer; ++import net.kyori.adventure.text.serializer.ansi.ANSIComponentSerializer; + import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; + import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; + import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +@@ -111,6 +112,7 @@ public final class PaperAdventure { + public static final AttributeKey LOCALE_ATTRIBUTE = AttributeKey.valueOf("adventure:locale"); // init after FLATTENER because classloading triggered here might create a logger + @Deprecated + public static final PlainComponentSerializer PLAIN = PlainComponentSerializer.builder().flattener(FLATTENER).build(); ++ public static final ANSIComponentSerializer ANSI_SERIALIZER = ANSIComponentSerializer.builder().flattener(FLATTENER).build(); + static final Codec NBT_CODEC = new Codec() { + @Override + public @NotNull CompoundTag decode(final @NotNull String encoded) throws IOException { +diff --git a/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java b/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java +index 8323f135d6bf2e1f12525e05094ffa3f2420e7e1..a143ea1e58464a3122fbd8ccafe417bdb3c31c78 100644 +--- a/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java ++++ b/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java +@@ -1,9 +1,11 @@ + package io.papermc.paper.adventure.providers; + + import io.papermc.paper.adventure.PaperAdventure; ++import java.util.Locale; + import net.kyori.adventure.text.Component; + import net.kyori.adventure.text.logger.slf4j.ComponentLogger; + import net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider; ++import net.kyori.adventure.translation.GlobalTranslator; + import org.jetbrains.annotations.NotNull; + import org.slf4j.LoggerFactory; + +@@ -15,6 +17,6 @@ public class ComponentLoggerProviderImpl implements ComponentLoggerProvider { + } + + private String serialize(final Component message) { +- return PaperAdventure.asPlain(message, null); ++ return PaperAdventure.ANSI_SERIALIZER.serialize(GlobalTranslator.render(message, Locale.getDefault())); + } + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 1d8e17f4b862e71cc5ef8c5eabfb80dc427aec51..6587cd9ea257d0f6f1308ea8ca4d0245178b2870 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -155,7 +155,7 @@ import org.slf4j.Logger; + import com.mojang.serialization.Dynamic; + import com.mojang.serialization.Lifecycle; + import java.util.Random; +-import jline.console.ConsoleReader; ++// import jline.console.ConsoleReader; // Paper + import joptsimple.OptionSet; + import net.minecraft.nbt.NbtException; + import net.minecraft.nbt.ReportedNbtException; +@@ -284,7 +284,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); + public int autosavePeriod; +@@ -371,7 +370,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { // Trim to filter lines which are just spaces +- DedicatedServer.this.handleConsoleInput(s, DedicatedServer.this.createCommandSourceStack()); ++ DedicatedServer.this.issueCommand(s, DedicatedServer.this.getServerCommandListener()); + } + // CraftBukkit end + } +@@ -137,6 +140,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + DedicatedServer.LOGGER.error("Exception handling console input", ioexception); + } + ++ */ ++ // Paper end + } + }; + +@@ -148,6 +153,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler()); + ++ // Paper start - Not needed with TerminalConsoleAppender ++ final org.apache.logging.log4j.Logger logger = LogManager.getRootLogger(); ++ /* + final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()); + for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) { + if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) { +@@ -156,6 +164,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + + new org.bukkit.craftbukkit.util.TerminalConsoleWriterThread(System.out, this.reader).start(); ++ */ ++ // Paper end + + System.setOut(IoBuilder.forLogger(logger).setLevel(Level.INFO).buildPrintStream()); + System.setErr(IoBuilder.forLogger(logger).setLevel(Level.WARN).buildPrintStream()); +diff --git a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java +index 75083eeb9b413e6dd5375007360dce6857a08fff..d292fdb165436f0b9b46b32110f5e09ad0e517a1 100644 +--- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java ++++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java +@@ -166,7 +166,7 @@ public class MinecraftServerGui extends JComponent { + this.finalizers.forEach(Runnable::run); + } + +- private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})*)?[m|K]"); // CraftBukkit ++ private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper + public void print(JTextArea textArea, JScrollPane scrollPane, String message) { + if (!SwingUtilities.isEventDispatchThread()) { + SwingUtilities.invokeLater(() -> { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 40af2325afea3e4831a9d8795ce1932a6a5663bf..db4480778e4b917a073c61f29cd45663ed859597 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -160,8 +160,7 @@ public abstract class PlayerList { + + public PlayerList(MinecraftServer server, LayeredRegistryAccess registryManager, PlayerDataStorage saveHandler, int maxPlayers) { + this.cserver = server.server = new CraftServer((DedicatedServer) server, this); +- server.console = org.bukkit.craftbukkit.command.ColouredConsoleSender.getInstance(); +- server.reader.addCompleter(new org.bukkit.craftbukkit.command.ConsoleCommandCompleter(server.server)); ++ server.console = new com.destroystokyo.paper.console.TerminalConsoleCommandSender(); // Paper + // CraftBukkit end + + this.bans = new UserBanList(PlayerList.USERBANLIST_FILE); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 827579f59d34b61912a67b40624f0f41524185fd..f7d937c6a11e24afe767411428210f3c042a6f78 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -42,7 +42,7 @@ import java.util.logging.Level; + import java.util.logging.Logger; + import java.util.stream.Collectors; + import javax.imageio.ImageIO; +-import jline.console.ConsoleReader; ++// import jline.console.ConsoleReader; + import net.minecraft.advancements.AdvancementHolder; + import net.minecraft.commands.CommandSourceStack; + import net.minecraft.commands.Commands; +@@ -1341,9 +1341,13 @@ public final class CraftServer implements Server { + return this.logger; + } + ++ // Paper start - JLine update ++ /* + public ConsoleReader getReader() { + return this.console.reader; + } ++ */ ++ // Paper end + + @Override + public PluginCommand getPluginCommand(String name) { +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 4fb377d967d13ed920ea1246e84b3b94cda25be6..659b32d49016a23475f3bbda1548a78101b468ce 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -13,7 +13,6 @@ import java.util.logging.Logger; + import joptsimple.OptionParser; + import joptsimple.OptionSet; + import joptsimple.util.PathConverter; +-import org.fusesource.jansi.AnsiConsole; + + public class Main { + public static boolean useJline = true; +@@ -220,6 +219,8 @@ public class Main { + } + + try { ++ // Paper start - Handled by TerminalConsoleAppender ++ /* + // This trick bypasses Maven Shade's clever rewriting of our getProperty call when using String literals + String jline_UnsupportedTerminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 'U', 'n', 's', 'u', 'p', 'p', 'o', 'r', 't', 'e', 'd', 'T', 'e', 'r', 'm', 'i', 'n', 'a', 'l'}); + String jline_terminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 't', 'e', 'r', 'm', 'i', 'n', 'a', 'l'}); +@@ -237,9 +238,18 @@ public class Main { + // This ensures the terminal literal will always match the jline implementation + System.setProperty(jline.TerminalFactory.JLINE_TERMINAL, jline.UnsupportedTerminal.class.getName()); + } ++ */ ++ ++ if (options.has("nojline")) { ++ System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); ++ useJline = false; ++ } ++ // Paper end + + if (options.has("noconsole")) { + Main.useConsole = false; ++ useJline = false; // Paper ++ System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper + } + + if (Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { +@@ -268,6 +278,7 @@ public class Main { + } + // Paper end - Log Java and OS versioning to help with debugging plugin issues + ++ System.setProperty("library.jansi.version", "Paper"); // Paper - set meaningless jansi version to prevent git builds from crashing on Windows + System.out.println("Loading libraries, please wait..."); + net.minecraft.server.Main.main(options); + } catch (Throwable t) { +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java b/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java +index bcf1c36d07b79520a39643d3a01020a67b1c9ef2..217e7e3b9db04c7fc5f6518f39cc9d3488f9128d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java +@@ -5,15 +5,13 @@ import java.util.EnumMap; + import java.util.Map; + import java.util.regex.Matcher; + import java.util.regex.Pattern; +-import jline.Terminal; ++//import jline.Terminal; + import org.bukkit.Bukkit; + import org.bukkit.ChatColor; + import org.bukkit.command.ConsoleCommandSender; + import org.bukkit.craftbukkit.CraftServer; +-import org.fusesource.jansi.Ansi; +-import org.fusesource.jansi.Ansi.Attribute; + +-public class ColouredConsoleSender extends CraftConsoleCommandSender { ++public class ColouredConsoleSender /*extends CraftConsoleCommandSender */{/* // Paper - disable + private final Terminal terminal; + private final Map replacements = new EnumMap(ChatColor.class); + private final ChatColor[] colors = ChatColor.values(); +@@ -93,5 +91,5 @@ public class ColouredConsoleSender extends CraftConsoleCommandSender { + } else { + return new ColouredConsoleSender(); + } +- } ++ }*/ // Paper + } +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +index 0b4c62387c1093652ac15b64a8703249de4cf088..d24acf28f5ed023acc550bcf877e4b9800ec8c9f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +@@ -4,50 +4,73 @@ import java.util.Collections; + import java.util.List; + import java.util.concurrent.ExecutionException; + import java.util.logging.Level; +-import jline.console.completer.Completer; ++import net.minecraft.server.dedicated.DedicatedServer; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.util.Waitable; ++ ++// Paper start - JLine update ++import org.jline.reader.Candidate; ++import org.jline.reader.Completer; ++import org.jline.reader.LineReader; ++import org.jline.reader.ParsedLine; ++// Paper end + import org.bukkit.event.server.TabCompleteEvent; + + public class ConsoleCommandCompleter implements Completer { +- private final CraftServer server; ++ private final DedicatedServer server; // Paper - CraftServer -> DedicatedServer + +- public ConsoleCommandCompleter(CraftServer server) { ++ public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer + this.server = server; + } + ++ // Paper start - Change method signature for JLine update + @Override +- public int complete(final String buffer, final int cursor, final List candidates) { ++ public void complete(LineReader reader, ParsedLine line, List candidates) { ++ final CraftServer server = this.server.server; ++ final String buffer = line.line(); ++ // Paper end + Waitable> waitable = new Waitable>() { + @Override + protected List evaluate() { +- List offers = ConsoleCommandCompleter.this.server.getCommandMap().tabComplete(ConsoleCommandCompleter.this.server.getConsoleSender(), buffer); ++ List offers = server.getCommandMap().tabComplete(server.getConsoleSender(), buffer); // Paper - Remove "this." + +- TabCompleteEvent tabEvent = new TabCompleteEvent(ConsoleCommandCompleter.this.server.getConsoleSender(), buffer, (offers == null) ? Collections.EMPTY_LIST : offers); +- ConsoleCommandCompleter.this.server.getPluginManager().callEvent(tabEvent); ++ TabCompleteEvent tabEvent = new TabCompleteEvent(server.getConsoleSender(), buffer, (offers == null) ? Collections.EMPTY_LIST : offers); // Paper - Remove "this." ++ server.getPluginManager().callEvent(tabEvent); // Paper - Remove "this." + + return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions(); + } + }; +- this.server.getServer().processQueue.add(waitable); ++ server.getServer().processQueue.add(waitable); // Paper - Remove "this." + try { + List offers = waitable.get(); + if (offers == null) { +- return cursor; ++ return; // Paper - Method returns void ++ } ++ ++ // Paper start - JLine update ++ for (String completion : offers) { ++ if (completion.isEmpty()) { ++ continue; ++ } ++ ++ candidates.add(new Candidate(completion)); + } +- candidates.addAll(offers); ++ // Paper end + ++ // Paper start - JLine handles cursor now ++ /* + final int lastSpace = buffer.lastIndexOf(' '); + if (lastSpace == -1) { + return cursor - buffer.length(); + } else { + return cursor - (buffer.length() - lastSpace - 1); + } ++ */ ++ // Paper end + } catch (ExecutionException e) { +- this.server.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e); ++ server.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e); // Paper - Remove "this." + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } +- return cursor; + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +index 8390f5b5b957b5435efece26507a89756d0a7b3c..c6e8441e299f477ddb22c1ce2618710763978f1a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +@@ -16,7 +16,7 @@ public class ServerShutdownThread extends Thread { + this.server.close(); + } finally { + try { +- this.server.reader.getTerminal().restore(); ++ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender + } catch (Exception e) { + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java b/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java +index 1d8b279f3cbe6fde6bb1bfc4985c4133b0d4a95d..cdc52bbe5c6ae4688615a7732b10071f7f51718e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java +@@ -5,12 +5,12 @@ import java.io.IOException; + import java.io.OutputStream; + import java.util.logging.Level; + import java.util.logging.Logger; +-import jline.console.ConsoleReader; ++//import jline.console.ConsoleReader; + import org.bukkit.craftbukkit.Main; +-import org.fusesource.jansi.Ansi; +-import org.fusesource.jansi.Ansi.Erase; ++//import org.fusesource.jansi.Ansi; ++//import org.fusesource.jansi.Ansi.Erase; + +-public class TerminalConsoleWriterThread extends Thread { ++public class TerminalConsoleWriterThread /*extends Thread*/ {/* // Paper - disable + private final ConsoleReader reader; + private final OutputStream output; + +@@ -54,5 +54,5 @@ public class TerminalConsoleWriterThread extends Thread { + Logger.getLogger(TerminalConsoleWriterThread.class.getName()).log(Level.SEVERE, null, ex); + } + } +- } ++ }*/ + } +diff --git a/src/main/resources/log4j2.component.properties b/src/main/resources/log4j2.component.properties +new file mode 100644 +index 0000000000000000000000000000000000000000..0694b21465fb9e4164e71862ff24b62241b191f2 +--- /dev/null ++++ b/src/main/resources/log4j2.component.properties +@@ -0,0 +1 @@ ++log4j.skipJansi=true +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index 722ca84968cbbbdeffd09939abff0cccd0a84010..a994ec0f8621b1f267b40049306f63479c050e2f 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -1,17 +1,14 @@ + + + +- +- +- + + + +- +- +- ++ ++ ++ + +- ++ + + + +@@ -24,10 +21,9 @@ + + + +- + +- + ++ + + + diff --git a/patches/server/0129-Use-TerminalConsoleAppender-for-console-improvements.patch b/patches/server/0129-Use-TerminalConsoleAppender-for-console-improvements.patch deleted file mode 100644 index 3912c5458682..000000000000 --- a/patches/server/0129-Use-TerminalConsoleAppender-for-console-improvements.patch +++ /dev/null @@ -1,667 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Minecrell -Date: Fri, 9 Jun 2017 19:03:43 +0200 -Subject: [PATCH] Use TerminalConsoleAppender for console improvements - -Rewrite console improvements (console colors, tab completion, -persistent input line, ...) using JLine 3.x and TerminalConsoleAppender. - -Also uses the new ANSIComponentSerializer to serialize components when -logging them via the ComponentLogger, or when sending messages to the -console, for hex color support. - -New features: - - Support console colors for Vanilla commands - - Add console colors for warnings and errors - - Server can now be turned off safely using CTRL + C. JLine catches - the signal and the implementation shuts down the server cleanly. - - Support console colors and persistent input line when running in - IntelliJ IDEA - -Other changes: - - Server starts 1-2 seconds faster thanks to optimizations in Log4j - configuration - -Co-Authored-By: Emilia Kond - -diff --git a/build.gradle.kts b/build.gradle.kts -index 65e9d5918d46b123fb4f8122344a7d3863aec758..7a3c96318f95fcd6cf6fd94415958382d1193ec6 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -6,9 +6,30 @@ plugins { - id("com.github.johnrengelman.shadow") - } - -+val log4jPlugins = sourceSets.create("log4jPlugins") -+configurations.named(log4jPlugins.compileClasspathConfigurationName) { -+ extendsFrom(configurations.compileClasspath.get()) -+} -+val alsoShade: Configuration by configurations.creating -+ - dependencies { - implementation(project(":paper-api")) -- implementation("jline:jline:2.12.1") -+ // Paper start -+ implementation("org.jline:jline-terminal-jansi:3.21.0") -+ implementation("net.minecrell:terminalconsoleappender:1.3.0") -+ implementation("net.kyori:adventure-text-serializer-ansi:4.14.0") // Keep in sync with adventureVersion from Paper-API build file -+ implementation("net.kyori:ansi:1.0.3") // Manually bump beyond above transitive dep -+ /* -+ Required to add the missing Log4j2Plugins.dat file from log4j-core -+ which has been removed by Mojang. Without it, log4j has to classload -+ all its classes to check if they are plugins. -+ Scanning takes about 1-2 seconds so adding this speeds up the server start. -+ */ -+ runtimeOnly("org.apache.logging.log4j:log4j-core:2.19.0") -+ log4jPlugins.annotationProcessorConfigurationName("org.apache.logging.log4j:log4j-core:2.19.0") // Paper - Needed to generate meta for our Log4j plugins -+ runtimeOnly(log4jPlugins.output) -+ alsoShade(log4jPlugins.output) -+ // Paper end - implementation("org.apache.logging.log4j:log4j-iostreams:2.19.0") // Paper - remove exclusion - implementation("org.ow2.asm:asm-commons:9.5") - implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files -@@ -78,7 +99,7 @@ relocation { - } - - tasks.shadowJar { -- configurations = listOf(project.configurations.vanillaServer.get()) -+ configurations = listOf(project.configurations.vanillaServer.get(), alsoShade) - archiveClassifier.set("mojang-mapped") - - for (relocation in relocation.relocations.get()) { -diff --git a/src/log4jPlugins/java/io/papermc/paper/console/StripANSIConverter.java b/src/log4jPlugins/java/io/papermc/paper/console/StripANSIConverter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..91547f6e6fe90006713beb2818da634304bdd236 ---- /dev/null -+++ b/src/log4jPlugins/java/io/papermc/paper/console/StripANSIConverter.java -@@ -0,0 +1,51 @@ -+package io.papermc.paper.console; -+ -+import org.apache.logging.log4j.core.LogEvent; -+import org.apache.logging.log4j.core.config.Configuration; -+import org.apache.logging.log4j.core.config.plugins.Plugin; -+import org.apache.logging.log4j.core.layout.PatternLayout; -+import org.apache.logging.log4j.core.pattern.ConverterKeys; -+import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; -+import org.apache.logging.log4j.core.pattern.PatternConverter; -+import org.apache.logging.log4j.core.pattern.PatternFormatter; -+import org.apache.logging.log4j.core.pattern.PatternParser; -+ -+import java.util.List; -+import java.util.regex.Pattern; -+ -+@Plugin(name = "stripAnsi", category = PatternConverter.CATEGORY) -+@ConverterKeys({"stripAnsi"}) -+public final class StripANSIConverter extends LogEventPatternConverter { -+ final private Pattern ANSI_PATTERN = Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); -+ -+ private final List formatters; -+ -+ private StripANSIConverter(List formatters) { -+ super("stripAnsi", null); -+ this.formatters = formatters; -+ } -+ -+ @Override -+ public void format(LogEvent event, StringBuilder toAppendTo) { -+ int start = toAppendTo.length(); -+ for (PatternFormatter formatter : formatters) { -+ formatter.format(event, toAppendTo); -+ } -+ String content = toAppendTo.substring(start); -+ content = ANSI_PATTERN.matcher(content).replaceAll(""); -+ -+ toAppendTo.setLength(start); -+ toAppendTo.append(content); -+ } -+ -+ public static StripANSIConverter newInstance(Configuration config, String[] options) { -+ if (options.length != 1) { -+ LOGGER.error("Incorrect number of options on stripAnsi. Expected exactly 1, received " + options.length); -+ return null; -+ } -+ -+ PatternParser parser = PatternLayout.createPatternParser(config); -+ List formatters = parser.parse(options[0]); -+ return new StripANSIConverter(formatters); -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a4070b59e261f0f1ac4beec47b11492f4724bf27 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -@@ -0,0 +1,41 @@ -+package com.destroystokyo.paper.console; -+ -+import net.minecraft.server.dedicated.DedicatedServer; -+import net.minecrell.terminalconsole.SimpleTerminalConsole; -+import org.bukkit.craftbukkit.command.ConsoleCommandCompleter; -+import org.jline.reader.LineReader; -+import org.jline.reader.LineReaderBuilder; -+ -+public final class PaperConsole extends SimpleTerminalConsole { -+ -+ private final DedicatedServer server; -+ -+ public PaperConsole(DedicatedServer server) { -+ this.server = server; -+ } -+ -+ @Override -+ protected LineReader buildReader(LineReaderBuilder builder) { -+ return super.buildReader(builder -+ .appName("Paper") -+ .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) -+ .completer(new ConsoleCommandCompleter(this.server)) -+ ); -+ } -+ -+ @Override -+ protected boolean isRunning() { -+ return !this.server.isStopped() && this.server.isRunning(); -+ } -+ -+ @Override -+ protected void runCommand(String command) { -+ this.server.handleConsoleInput(command, this.server.createCommandSourceStack()); -+ } -+ -+ @Override -+ protected void shutdown() { -+ this.server.halt(false); -+ } -+ -+} -diff --git a/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8f07539a82f449ad217e316a7513a1708781fb63 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java -@@ -0,0 +1,26 @@ -+package com.destroystokyo.paper.console; -+ -+import net.kyori.adventure.audience.MessageType; -+import net.kyori.adventure.identity.Identity; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.logger.slf4j.ComponentLogger; -+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -+import org.apache.logging.log4j.LogManager; -+import org.bukkit.craftbukkit.command.CraftConsoleCommandSender; -+ -+public class TerminalConsoleCommandSender extends CraftConsoleCommandSender { -+ -+ private static final ComponentLogger LOGGER = ComponentLogger.logger(LogManager.getRootLogger().getName()); -+ -+ @Override -+ public void sendRawMessage(String message) { -+ final Component msg = LegacyComponentSerializer.legacySection().deserialize(message); -+ this.sendMessage(Identity.nil(), msg, MessageType.SYSTEM); -+ } -+ -+ @Override -+ public void sendMessage(Identity identity, Component message, MessageType type) { -+ LOGGER.info(message); -+ } -+ -+} -diff --git a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java -index 2e757cd9b01ac7eba1e4723a6e21dcea9d062483..ca80cbe422d766b3d044a5b53ce40bb7f92558e4 100644 ---- a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java -+++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java -@@ -24,6 +24,7 @@ import net.kyori.adventure.text.TranslationArgument; - import net.kyori.adventure.text.flattener.ComponentFlattener; - import net.kyori.adventure.text.format.TextColor; - import net.kyori.adventure.text.serializer.ComponentSerializer; -+import net.kyori.adventure.text.serializer.ansi.ANSIComponentSerializer; - import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; - import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; - import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -@@ -111,6 +112,7 @@ public final class PaperAdventure { - public static final AttributeKey LOCALE_ATTRIBUTE = AttributeKey.valueOf("adventure:locale"); // init after FLATTENER because classloading triggered here might create a logger - @Deprecated - public static final PlainComponentSerializer PLAIN = PlainComponentSerializer.builder().flattener(FLATTENER).build(); -+ public static final ANSIComponentSerializer ANSI_SERIALIZER = ANSIComponentSerializer.builder().flattener(FLATTENER).build(); - static final Codec NBT_CODEC = new Codec() { - @Override - public @NotNull CompoundTag decode(final @NotNull String encoded) throws IOException { -diff --git a/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java b/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java -index 8323f135d6bf2e1f12525e05094ffa3f2420e7e1..a143ea1e58464a3122fbd8ccafe417bdb3c31c78 100644 ---- a/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java -+++ b/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java -@@ -1,9 +1,11 @@ - package io.papermc.paper.adventure.providers; - - import io.papermc.paper.adventure.PaperAdventure; -+import java.util.Locale; - import net.kyori.adventure.text.Component; - import net.kyori.adventure.text.logger.slf4j.ComponentLogger; - import net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider; -+import net.kyori.adventure.translation.GlobalTranslator; - import org.jetbrains.annotations.NotNull; - import org.slf4j.LoggerFactory; - -@@ -15,6 +17,6 @@ public class ComponentLoggerProviderImpl implements ComponentLoggerProvider { - } - - private String serialize(final Component message) { -- return PaperAdventure.asPlain(message, null); -+ return PaperAdventure.ANSI_SERIALIZER.serialize(GlobalTranslator.render(message, Locale.getDefault())); - } - } -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 5ec728f6000753d517d943562efb55ad2541b01c..dad61777658cafaaae56928f3492075067149fd8 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -155,7 +155,7 @@ import org.slf4j.Logger; - import com.mojang.serialization.Dynamic; - import com.mojang.serialization.Lifecycle; - import java.util.Random; --import jline.console.ConsoleReader; -+// import jline.console.ConsoleReader; // Paper - import joptsimple.OptionSet; - import net.minecraft.nbt.NbtException; - import net.minecraft.nbt.ReportedNbtException; -@@ -284,7 +284,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); - public int autosavePeriod; -@@ -371,7 +370,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { // Trim to filter lines which are just spaces -- DedicatedServer.this.handleConsoleInput(s, DedicatedServer.this.createCommandSourceStack()); -+ DedicatedServer.this.issueCommand(s, DedicatedServer.this.getServerCommandListener()); - } - // CraftBukkit end - } -@@ -137,6 +140,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - DedicatedServer.LOGGER.error("Exception handling console input", ioexception); - } - -+ */ -+ // Paper end - } - }; - -@@ -148,6 +153,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - } - global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler()); - -+ // Paper start - Not needed with TerminalConsoleAppender -+ final org.apache.logging.log4j.Logger logger = LogManager.getRootLogger(); -+ /* - final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()); - for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) { - if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) { -@@ -156,6 +164,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - } - - new org.bukkit.craftbukkit.util.TerminalConsoleWriterThread(System.out, this.reader).start(); -+ */ -+ // Paper end - - System.setOut(IoBuilder.forLogger(logger).setLevel(Level.INFO).buildPrintStream()); - System.setErr(IoBuilder.forLogger(logger).setLevel(Level.WARN).buildPrintStream()); -diff --git a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java -index 75083eeb9b413e6dd5375007360dce6857a08fff..d292fdb165436f0b9b46b32110f5e09ad0e517a1 100644 ---- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java -+++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java -@@ -166,7 +166,7 @@ public class MinecraftServerGui extends JComponent { - this.finalizers.forEach(Runnable::run); - } - -- private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})*)?[m|K]"); // CraftBukkit -+ private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper - public void print(JTextArea textArea, JScrollPane scrollPane, String message) { - if (!SwingUtilities.isEventDispatchThread()) { - SwingUtilities.invokeLater(() -> { -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 40af2325afea3e4831a9d8795ce1932a6a5663bf..db4480778e4b917a073c61f29cd45663ed859597 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -160,8 +160,7 @@ public abstract class PlayerList { - - public PlayerList(MinecraftServer server, LayeredRegistryAccess registryManager, PlayerDataStorage saveHandler, int maxPlayers) { - this.cserver = server.server = new CraftServer((DedicatedServer) server, this); -- server.console = org.bukkit.craftbukkit.command.ColouredConsoleSender.getInstance(); -- server.reader.addCompleter(new org.bukkit.craftbukkit.command.ConsoleCommandCompleter(server.server)); -+ server.console = new com.destroystokyo.paper.console.TerminalConsoleCommandSender(); // Paper - // CraftBukkit end - - this.bans = new UserBanList(PlayerList.USERBANLIST_FILE); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 30ef05974f5645e0769cb1604290a4c8045501c8..883c053baea5968a978f1619ebb170647fe15eef 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -42,7 +42,7 @@ import java.util.logging.Level; - import java.util.logging.Logger; - import java.util.stream.Collectors; - import javax.imageio.ImageIO; --import jline.console.ConsoleReader; -+// import jline.console.ConsoleReader; - import net.minecraft.advancements.AdvancementHolder; - import net.minecraft.commands.CommandSourceStack; - import net.minecraft.commands.Commands; -@@ -1329,9 +1329,13 @@ public final class CraftServer implements Server { - return this.logger; - } - -+ // Paper start - JLine update -+ /* - public ConsoleReader getReader() { - return this.console.reader; - } -+ */ -+ // Paper end - - @Override - public PluginCommand getPluginCommand(String name) { -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 4fb377d967d13ed920ea1246e84b3b94cda25be6..659b32d49016a23475f3bbda1548a78101b468ce 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -13,7 +13,6 @@ import java.util.logging.Logger; - import joptsimple.OptionParser; - import joptsimple.OptionSet; - import joptsimple.util.PathConverter; --import org.fusesource.jansi.AnsiConsole; - - public class Main { - public static boolean useJline = true; -@@ -220,6 +219,8 @@ public class Main { - } - - try { -+ // Paper start - Handled by TerminalConsoleAppender -+ /* - // This trick bypasses Maven Shade's clever rewriting of our getProperty call when using String literals - String jline_UnsupportedTerminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 'U', 'n', 's', 'u', 'p', 'p', 'o', 'r', 't', 'e', 'd', 'T', 'e', 'r', 'm', 'i', 'n', 'a', 'l'}); - String jline_terminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 't', 'e', 'r', 'm', 'i', 'n', 'a', 'l'}); -@@ -237,9 +238,18 @@ public class Main { - // This ensures the terminal literal will always match the jline implementation - System.setProperty(jline.TerminalFactory.JLINE_TERMINAL, jline.UnsupportedTerminal.class.getName()); - } -+ */ -+ -+ if (options.has("nojline")) { -+ System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); -+ useJline = false; -+ } -+ // Paper end - - if (options.has("noconsole")) { - Main.useConsole = false; -+ useJline = false; // Paper -+ System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper - } - - if (Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { -@@ -268,6 +278,7 @@ public class Main { - } - // Paper end - Log Java and OS versioning to help with debugging plugin issues - -+ System.setProperty("library.jansi.version", "Paper"); // Paper - set meaningless jansi version to prevent git builds from crashing on Windows - System.out.println("Loading libraries, please wait..."); - net.minecraft.server.Main.main(options); - } catch (Throwable t) { -diff --git a/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java b/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java -index bcf1c36d07b79520a39643d3a01020a67b1c9ef2..217e7e3b9db04c7fc5f6518f39cc9d3488f9128d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java -@@ -5,15 +5,13 @@ import java.util.EnumMap; - import java.util.Map; - import java.util.regex.Matcher; - import java.util.regex.Pattern; --import jline.Terminal; -+//import jline.Terminal; - import org.bukkit.Bukkit; - import org.bukkit.ChatColor; - import org.bukkit.command.ConsoleCommandSender; - import org.bukkit.craftbukkit.CraftServer; --import org.fusesource.jansi.Ansi; --import org.fusesource.jansi.Ansi.Attribute; - --public class ColouredConsoleSender extends CraftConsoleCommandSender { -+public class ColouredConsoleSender /*extends CraftConsoleCommandSender */{/* // Paper - disable - private final Terminal terminal; - private final Map replacements = new EnumMap(ChatColor.class); - private final ChatColor[] colors = ChatColor.values(); -@@ -93,5 +91,5 @@ public class ColouredConsoleSender extends CraftConsoleCommandSender { - } else { - return new ColouredConsoleSender(); - } -- } -+ }*/ // Paper - } -diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -index 0b4c62387c1093652ac15b64a8703249de4cf088..d24acf28f5ed023acc550bcf877e4b9800ec8c9f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -@@ -4,50 +4,73 @@ import java.util.Collections; - import java.util.List; - import java.util.concurrent.ExecutionException; - import java.util.logging.Level; --import jline.console.completer.Completer; -+import net.minecraft.server.dedicated.DedicatedServer; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.craftbukkit.util.Waitable; -+ -+// Paper start - JLine update -+import org.jline.reader.Candidate; -+import org.jline.reader.Completer; -+import org.jline.reader.LineReader; -+import org.jline.reader.ParsedLine; -+// Paper end - import org.bukkit.event.server.TabCompleteEvent; - - public class ConsoleCommandCompleter implements Completer { -- private final CraftServer server; -+ private final DedicatedServer server; // Paper - CraftServer -> DedicatedServer - -- public ConsoleCommandCompleter(CraftServer server) { -+ public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer - this.server = server; - } - -+ // Paper start - Change method signature for JLine update - @Override -- public int complete(final String buffer, final int cursor, final List candidates) { -+ public void complete(LineReader reader, ParsedLine line, List candidates) { -+ final CraftServer server = this.server.server; -+ final String buffer = line.line(); -+ // Paper end - Waitable> waitable = new Waitable>() { - @Override - protected List evaluate() { -- List offers = ConsoleCommandCompleter.this.server.getCommandMap().tabComplete(ConsoleCommandCompleter.this.server.getConsoleSender(), buffer); -+ List offers = server.getCommandMap().tabComplete(server.getConsoleSender(), buffer); // Paper - Remove "this." - -- TabCompleteEvent tabEvent = new TabCompleteEvent(ConsoleCommandCompleter.this.server.getConsoleSender(), buffer, (offers == null) ? Collections.EMPTY_LIST : offers); -- ConsoleCommandCompleter.this.server.getPluginManager().callEvent(tabEvent); -+ TabCompleteEvent tabEvent = new TabCompleteEvent(server.getConsoleSender(), buffer, (offers == null) ? Collections.EMPTY_LIST : offers); // Paper - Remove "this." -+ server.getPluginManager().callEvent(tabEvent); // Paper - Remove "this." - - return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions(); - } - }; -- this.server.getServer().processQueue.add(waitable); -+ server.getServer().processQueue.add(waitable); // Paper - Remove "this." - try { - List offers = waitable.get(); - if (offers == null) { -- return cursor; -+ return; // Paper - Method returns void -+ } -+ -+ // Paper start - JLine update -+ for (String completion : offers) { -+ if (completion.isEmpty()) { -+ continue; -+ } -+ -+ candidates.add(new Candidate(completion)); - } -- candidates.addAll(offers); -+ // Paper end - -+ // Paper start - JLine handles cursor now -+ /* - final int lastSpace = buffer.lastIndexOf(' '); - if (lastSpace == -1) { - return cursor - buffer.length(); - } else { - return cursor - (buffer.length() - lastSpace - 1); - } -+ */ -+ // Paper end - } catch (ExecutionException e) { -- this.server.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e); -+ server.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e); // Paper - Remove "this." - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } -- return cursor; - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java -index 8390f5b5b957b5435efece26507a89756d0a7b3c..c6e8441e299f477ddb22c1ce2618710763978f1a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java -@@ -16,7 +16,7 @@ public class ServerShutdownThread extends Thread { - this.server.close(); - } finally { - try { -- this.server.reader.getTerminal().restore(); -+ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender - } catch (Exception e) { - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java b/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java -index 1d8b279f3cbe6fde6bb1bfc4985c4133b0d4a95d..cdc52bbe5c6ae4688615a7732b10071f7f51718e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java -@@ -5,12 +5,12 @@ import java.io.IOException; - import java.io.OutputStream; - import java.util.logging.Level; - import java.util.logging.Logger; --import jline.console.ConsoleReader; -+//import jline.console.ConsoleReader; - import org.bukkit.craftbukkit.Main; --import org.fusesource.jansi.Ansi; --import org.fusesource.jansi.Ansi.Erase; -+//import org.fusesource.jansi.Ansi; -+//import org.fusesource.jansi.Ansi.Erase; - --public class TerminalConsoleWriterThread extends Thread { -+public class TerminalConsoleWriterThread /*extends Thread*/ {/* // Paper - disable - private final ConsoleReader reader; - private final OutputStream output; - -@@ -54,5 +54,5 @@ public class TerminalConsoleWriterThread extends Thread { - Logger.getLogger(TerminalConsoleWriterThread.class.getName()).log(Level.SEVERE, null, ex); - } - } -- } -+ }*/ - } -diff --git a/src/main/resources/log4j2.component.properties b/src/main/resources/log4j2.component.properties -new file mode 100644 -index 0000000000000000000000000000000000000000..0694b21465fb9e4164e71862ff24b62241b191f2 ---- /dev/null -+++ b/src/main/resources/log4j2.component.properties -@@ -0,0 +1 @@ -+log4j.skipJansi=true -diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml -index 722ca84968cbbbdeffd09939abff0cccd0a84010..a994ec0f8621b1f267b40049306f63479c050e2f 100644 ---- a/src/main/resources/log4j2.xml -+++ b/src/main/resources/log4j2.xml -@@ -1,17 +1,14 @@ - - - -- -- -- - - - -- -- -- -+ -+ -+ - -- -+ - - - -@@ -24,10 +21,9 @@ - - - -- - -- - -+ - - - diff --git a/patches/server/0130-provide-a-configurable-option-to-disable-creeper-lin.patch b/patches/server/0129-provide-a-configurable-option-to-disable-creeper-lin.patch similarity index 100% rename from patches/server/0130-provide-a-configurable-option-to-disable-creeper-lin.patch rename to patches/server/0129-provide-a-configurable-option-to-disable-creeper-lin.patch diff --git a/patches/server/0130-Item-canEntityPickup.patch b/patches/server/0130-Item-canEntityPickup.patch new file mode 100644 index 000000000000..24f6e98a31b7 --- /dev/null +++ b/patches/server/0130-Item-canEntityPickup.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 5 May 2017 03:57:17 -0500 +Subject: [PATCH] Item#canEntityPickup + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index fa5d8a041858d17c785f033dd2aa3ab242069749..bb051d73a048b0a8ce245914f3564e39702b8452 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -670,6 +670,11 @@ public abstract class Mob extends LivingEntity implements Targeting { + ItemEntity entityitem = (ItemEntity) iterator.next(); + + if (!entityitem.isRemoved() && !entityitem.getItem().isEmpty() && !entityitem.hasPickUpDelay() && this.wantsToPickUp(entityitem.getItem())) { ++ // Paper start - Item#canEntityPickup ++ if (!entityitem.canMobPickup) { ++ continue; ++ } ++ // Paper end - Item#canEntityPickup + this.pickUpItem(entityitem); + } + } +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 5bb26ca5c81635d27ca59352d5184d8b4300e0b5..6c2e22f1b2b1ce2903c0ee1d2dbde96b40bd1624 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -54,6 +54,7 @@ public class ItemEntity extends Entity implements TraceableEntity { + public UUID target; + public final float bobOffs; + private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit ++ public boolean canMobPickup = true; // Paper - Item#canEntityPickup + + public ItemEntity(EntityType type, Level world) { + super(type, world); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +index fb3738f4c558796f41e3327dd41b8aec68007a8a..5620a0849fda49313c68edfd747fedd09641a3d5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +@@ -63,6 +63,18 @@ public class CraftItem extends CraftEntity implements Item { + } + } + ++ // Paper start ++ @Override ++ public boolean canMobPickup() { ++ return this.getHandle().canMobPickup; ++ } ++ ++ @Override ++ public void setCanMobPickup(boolean canMobPickup) { ++ this.getHandle().canMobPickup = canMobPickup; ++ } ++ // Paper end ++ + @Override + public void setOwner(UUID uuid) { + this.getHandle().setTarget(uuid); diff --git a/patches/server/0131-Item-canEntityPickup.patch b/patches/server/0131-Item-canEntityPickup.patch deleted file mode 100644 index 89bfcac35f7b..000000000000 --- a/patches/server/0131-Item-canEntityPickup.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Fri, 5 May 2017 03:57:17 -0500 -Subject: [PATCH] Item#canEntityPickup - - -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 3fcd93f6d5a7553b032b44e7e919838ad2120dc9..15ad425b9c091ee27965fe166f9021509199aa18 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -670,6 +670,11 @@ public abstract class Mob extends LivingEntity implements Targeting { - ItemEntity entityitem = (ItemEntity) iterator.next(); - - if (!entityitem.isRemoved() && !entityitem.getItem().isEmpty() && !entityitem.hasPickUpDelay() && this.wantsToPickUp(entityitem.getItem())) { -+ // Paper start - Item#canEntityPickup -+ if (!entityitem.canMobPickup) { -+ continue; -+ } -+ // Paper end - Item#canEntityPickup - this.pickUpItem(entityitem); - } - } -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 5bb26ca5c81635d27ca59352d5184d8b4300e0b5..6c2e22f1b2b1ce2903c0ee1d2dbde96b40bd1624 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -54,6 +54,7 @@ public class ItemEntity extends Entity implements TraceableEntity { - public UUID target; - public final float bobOffs; - private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit -+ public boolean canMobPickup = true; // Paper - Item#canEntityPickup - - public ItemEntity(EntityType type, Level world) { - super(type, world); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -index fb3738f4c558796f41e3327dd41b8aec68007a8a..5620a0849fda49313c68edfd747fedd09641a3d5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -@@ -63,6 +63,18 @@ public class CraftItem extends CraftEntity implements Item { - } - } - -+ // Paper start -+ @Override -+ public boolean canMobPickup() { -+ return this.getHandle().canMobPickup; -+ } -+ -+ @Override -+ public void setCanMobPickup(boolean canMobPickup) { -+ this.getHandle().canMobPickup = canMobPickup; -+ } -+ // Paper end -+ - @Override - public void setOwner(UUID uuid) { - this.getHandle().setTarget(uuid); diff --git a/patches/server/0132-PlayerPickupItemEvent-setFlyAtPlayer.patch b/patches/server/0131-PlayerPickupItemEvent-setFlyAtPlayer.patch similarity index 100% rename from patches/server/0132-PlayerPickupItemEvent-setFlyAtPlayer.patch rename to patches/server/0131-PlayerPickupItemEvent-setFlyAtPlayer.patch diff --git a/patches/server/0133-PlayerAttemptPickupItemEvent.patch b/patches/server/0132-PlayerAttemptPickupItemEvent.patch similarity index 100% rename from patches/server/0133-PlayerAttemptPickupItemEvent.patch rename to patches/server/0132-PlayerAttemptPickupItemEvent.patch diff --git a/patches/server/0134-Do-not-submit-profile-lookups-to-worldgen-threads.patch b/patches/server/0133-Do-not-submit-profile-lookups-to-worldgen-threads.patch similarity index 100% rename from patches/server/0134-Do-not-submit-profile-lookups-to-worldgen-threads.patch rename to patches/server/0133-Do-not-submit-profile-lookups-to-worldgen-threads.patch diff --git a/patches/server/0134-Basic-PlayerProfile-API.patch b/patches/server/0134-Basic-PlayerProfile-API.patch new file mode 100644 index 000000000000..84b49f7cf93d --- /dev/null +++ b/patches/server/0134-Basic-PlayerProfile-API.patch @@ -0,0 +1,756 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 15 Jan 2018 22:11:48 -0500 +Subject: [PATCH] Basic PlayerProfile API + +Establishes base extension of profile systems for future edits too + +== AT == +public org.bukkit.craftbukkit.profile.CraftProfileProperty +public org.bukkit.craftbukkit.profile.CraftPlayerTextures +public org.bukkit.craftbukkit.profile.CraftPlayerTextures copyFrom(Lorg/bukkit/profile/PlayerTextures;)V +public org.bukkit.craftbukkit.profile.CraftPlayerTextures rebuildPropertyIfDirty()V +public org.bukkit.craftbukkit.profile.CraftPlayerProfile toString(Lcom/mojang/authlib/properties/PropertyMap;)Ljava/lang/String; +public org.bukkit.craftbukkit.profile.CraftPlayerProfile getProperty(Ljava/lang/String;)Lcom/mojang/authlib/properties/Property; +public org.bukkit.craftbukkit.profile.CraftPlayerProfile setProperty(Ljava/lang/String;Lcom/mojang/authlib/properties/Property;)V + +diff --git a/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java +new file mode 100644 +index 0000000000000000000000000000000000000000..daa157eaa021d039f9a092bea0b78f7c1f746e3b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java +@@ -0,0 +1,409 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.yggdrasil.ProfileResult; ++import io.papermc.paper.configuration.GlobalConfiguration; ++import com.google.common.base.Charsets; ++import com.google.common.collect.Iterables; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.properties.Property; ++import com.mojang.authlib.properties.PropertyMap; ++import net.minecraft.Util; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.players.GameProfileCache; ++import org.apache.commons.lang3.StringUtils; ++import org.apache.commons.lang3.Validate; ++import org.bukkit.configuration.serialization.SerializableAs; ++import org.bukkit.craftbukkit.configuration.ConfigSerializationUtil; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.profile.CraftPlayerTextures; ++import org.bukkit.craftbukkit.profile.CraftProfileProperty; ++import org.bukkit.profile.PlayerTextures; ++import org.jetbrains.annotations.NotNull; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++import java.util.*; ++import java.util.concurrent.CompletableFuture; ++ ++@SerializableAs("PlayerProfile") ++public class CraftPlayerProfile implements PlayerProfile, SharedPlayerProfile { ++ ++ private GameProfile profile; ++ private final PropertySet properties = new PropertySet(); ++ ++ public CraftPlayerProfile(CraftPlayer player) { ++ this.profile = player.getHandle().getGameProfile(); ++ } ++ ++ public CraftPlayerProfile(UUID id, String name) { ++ this.profile = createAuthLibProfile(id, name); ++ } ++ ++ public CraftPlayerProfile(GameProfile profile) { ++ Validate.notNull(profile, "GameProfile cannot be null!"); ++ this.profile = profile; ++ } ++ ++ @Override ++ public boolean hasProperty(String property) { ++ return profile.getProperties().containsKey(property); ++ } ++ ++ @Override ++ public void setProperty(ProfileProperty property) { ++ String name = property.getName(); ++ PropertyMap properties = profile.getProperties(); ++ properties.removeAll(name); ++ properties.put(name, new Property(name, property.getValue(), property.getSignature())); ++ } ++ ++ @Override ++ public CraftPlayerTextures getTextures() { ++ return new CraftPlayerTextures(this); ++ } ++ ++ @Override ++ public void setTextures(@Nullable PlayerTextures textures) { ++ if (textures == null) { ++ this.removeProperty("textures"); ++ } else { ++ CraftPlayerTextures craftPlayerTextures = new CraftPlayerTextures(this); ++ craftPlayerTextures.copyFrom(textures); ++ craftPlayerTextures.rebuildPropertyIfDirty(); ++ } ++ } ++ ++ public GameProfile getGameProfile() { ++ return profile; ++ } ++ ++ @Nullable ++ @Override ++ public UUID getId() { ++ return profile.getId().equals(Util.NIL_UUID) ? null : profile.getId(); ++ } ++ ++ @Override ++ @Deprecated(forRemoval = true) ++ public UUID setId(@Nullable UUID uuid) { ++ final GameProfile previousProfile = this.profile; ++ final UUID previousId = this.getId(); ++ this.profile = createAuthLibProfile(uuid, previousProfile.getName()); ++ copyProfileProperties(previousProfile, this.profile); ++ return previousId; ++ } ++ ++ @Override ++ public UUID getUniqueId() { ++ return getId(); ++ } ++ ++ @Nullable ++ @Override ++ public String getName() { ++ return profile.getName(); ++ } ++ ++ @Override ++ @Deprecated(forRemoval = true) ++ public String setName(@Nullable String name) { ++ GameProfile prev = this.profile; ++ this.profile = createAuthLibProfile(prev.getId(), name); ++ copyProfileProperties(prev, this.profile); ++ return prev.getName(); ++ } ++ ++ @Nonnull ++ @Override ++ public Set getProperties() { ++ return properties; ++ } ++ ++ @Override ++ public void setProperties(Collection properties) { ++ properties.forEach(this::setProperty); ++ } ++ ++ @Override ++ public void clearProperties() { ++ profile.getProperties().clear(); ++ } ++ ++ @Override ++ public boolean removeProperty(String property) { ++ return !profile.getProperties().removeAll(property).isEmpty(); ++ } ++ ++ @Nullable ++ @Override ++ public Property getProperty(String property) { ++ return Iterables.getFirst(this.profile.getProperties().get(property), null); ++ } ++ ++ @Nullable ++ @Override ++ public void setProperty(@NotNull String propertyName, @Nullable Property property) { ++ PropertyMap properties = profile.getProperties(); ++ properties.removeAll(propertyName); ++ if (property != null) { ++ properties.put(propertyName, property); ++ } ++ } ++ ++ @Override ++ public @NotNull GameProfile buildGameProfile() { ++ GameProfile profile = new GameProfile(this.profile.getId(), this.profile.getName()); ++ profile.getProperties().putAll(this.profile.getProperties()); ++ return profile; ++ } ++ ++ @Override ++ public CraftPlayerProfile clone() { ++ CraftPlayerProfile clone = new CraftPlayerProfile(this.getId(), this.getName()); ++ clone.setProperties(getProperties()); ++ return clone; ++ } ++ ++ @Override ++ public boolean isComplete() { ++ return this.getId() != null && StringUtils.isNotBlank(this.getName()); ++ } ++ ++ @Override ++ public @NotNull CompletableFuture update() { ++ return CompletableFuture.supplyAsync(() -> { ++ final CraftPlayerProfile clone = clone(); ++ clone.complete(true); ++ return clone; ++ }, Util.PROFILE_EXECUTOR); ++ } ++ ++ @Override ++ public boolean completeFromCache() { ++ return completeFromCache(false, GlobalConfiguration.get().proxies.isProxyOnlineMode()); ++ } ++ ++ public boolean completeFromCache(boolean onlineMode) { ++ return completeFromCache(false, onlineMode); ++ } ++ ++ public boolean completeFromCache(boolean lookupUUID, boolean onlineMode) { ++ MinecraftServer server = MinecraftServer.getServer(); ++ String name = profile.getName(); ++ GameProfileCache userCache = server.getProfileCache(); ++ if (this.getId() == null) { ++ final GameProfile profile; ++ if (onlineMode) { ++ profile = lookupUUID ? userCache.get(name).orElse(null) : userCache.getProfileIfCached(name); ++ } else { ++ // Make an OfflinePlayer using an offline mode UUID since the name has no profile ++ profile = new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name); ++ } ++ if (profile != null) { ++ // if old has it, assume its newer, so overwrite, else use cached if it was set and ours wasn't ++ copyProfileProperties(this.profile, profile); ++ this.profile = profile; ++ } ++ } ++ ++ if ((profile.getName().isEmpty() || !hasTextures()) && this.getId() != null) { ++ Optional optProfile = userCache.get(this.profile.getId()); ++ if (optProfile.isPresent()) { ++ GameProfile profile = optProfile.get(); ++ if (this.profile.getName().isEmpty()) { ++ // if old has it, assume its newer, so overwrite, else use cached if it was set and ours wasn't ++ copyProfileProperties(this.profile, profile); ++ this.profile = profile; ++ } else { ++ copyProfileProperties(profile, this.profile); ++ } ++ } ++ } ++ return this.isComplete(); ++ } ++ ++ public boolean complete(boolean textures) { ++ return complete(textures, GlobalConfiguration.get().proxies.isProxyOnlineMode()); ++ } ++ public boolean complete(boolean textures, boolean onlineMode) { ++ MinecraftServer server = MinecraftServer.getServer(); ++ boolean isCompleteFromCache = this.completeFromCache(true, onlineMode); ++ if (onlineMode && (!isCompleteFromCache || textures && !hasTextures())) { ++ ProfileResult result = server.getSessionService().fetchProfile(this.getId(), true); ++ if (result != null && result.profile() != null) { ++ copyProfileProperties(result.profile(), this.profile, true); ++ } ++ if (this.isComplete()) { ++ server.getProfileCache().add(this.profile); ++ } ++ } ++ return this.isComplete() && (!onlineMode || !textures || hasTextures()); ++ } ++ ++ private static void copyProfileProperties(GameProfile source, GameProfile target) { ++ copyProfileProperties(source, target, false); ++ } ++ ++ private static void copyProfileProperties(GameProfile source, GameProfile target, boolean clearTarget) { ++ PropertyMap sourceProperties = source.getProperties(); ++ PropertyMap targetProperties = target.getProperties(); ++ if (clearTarget) targetProperties.clear(); ++ if (sourceProperties.isEmpty()) { ++ return; ++ } ++ ++ for (Property property : sourceProperties.values()) { ++ targetProperties.removeAll(property.name()); ++ targetProperties.put(property.name(), property); ++ } ++ } ++ ++ private static GameProfile createAuthLibProfile(UUID uniqueId, String name) { ++ return new GameProfile( ++ uniqueId != null ? uniqueId : Util.NIL_UUID, ++ name != null ? name : "" ++ ); ++ } ++ ++ private static ProfileProperty toBukkit(Property property) { ++ return new ProfileProperty(property.name(), property.value(), property.signature()); ++ } ++ ++ public static PlayerProfile asBukkitCopy(GameProfile gameProfile) { ++ CraftPlayerProfile profile = new CraftPlayerProfile(gameProfile.getId(), gameProfile.getName()); ++ copyProfileProperties(gameProfile, profile.profile); ++ return profile; ++ } ++ ++ public static PlayerProfile asBukkitMirror(GameProfile profile) { ++ return new CraftPlayerProfile(profile); ++ } ++ ++ public static Property asAuthlib(ProfileProperty property) { ++ return new Property(property.getName(), property.getValue(), property.getSignature()); ++ } ++ ++ public static GameProfile asAuthlibCopy(PlayerProfile profile) { ++ CraftPlayerProfile craft = ((CraftPlayerProfile) profile); ++ return asAuthlib(craft.clone()); ++ } ++ ++ public static GameProfile asAuthlib(PlayerProfile profile) { ++ CraftPlayerProfile craft = ((CraftPlayerProfile) profile); ++ return craft.getGameProfile(); ++ } ++ ++ @Override ++ public @NotNull Map serialize() { ++ Map map = new LinkedHashMap<>(); ++ if (this.getId() != null) { ++ map.put("uniqueId", this.getId().toString()); ++ } ++ if (!this.getName().isEmpty()) { ++ map.put("name", getName()); ++ } ++ if (!this.properties.isEmpty()) { ++ List propertiesData = new ArrayList<>(); ++ for (ProfileProperty property : properties) { ++ propertiesData.add(CraftProfileProperty.serialize(new Property(property.getName(), property.getValue(), property.getSignature()))); ++ } ++ map.put("properties", propertiesData); ++ } ++ return map; ++ } ++ ++ public static CraftPlayerProfile deserialize(Map map) { ++ UUID uniqueId = ConfigSerializationUtil.getUuid(map, "uniqueId", true); ++ String name = ConfigSerializationUtil.getString(map, "name", true); ++ ++ // This also validates the deserialized unique id and name (ensures that not both are null): ++ CraftPlayerProfile profile = new CraftPlayerProfile(uniqueId, name); ++ ++ if (map.containsKey("properties")) { ++ for (Object propertyData : (List) map.get("properties")) { ++ if (!(propertyData instanceof Map)) { ++ throw new IllegalArgumentException("Property data (" + propertyData + ") is not a valid Map"); ++ } ++ Property property = CraftProfileProperty.deserialize((Map) propertyData); ++ profile.profile.getProperties().put(property.name(), property); ++ } ++ } ++ ++ return profile; ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ if (this == obj) return true; ++ if (!(obj instanceof CraftPlayerProfile otherProfile)) return false; ++ return Objects.equals(this.profile, otherProfile.profile); ++ } ++ ++ @Override ++ public String toString() { ++ return "CraftPlayerProfile [uniqueId=" + getId() + ++ ", name=" + getName() + ++ ", properties=" + org.bukkit.craftbukkit.profile.CraftPlayerProfile.toString(this.profile.getProperties()) + ++ "]"; ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.profile.hashCode(); ++ } ++ ++ private class PropertySet extends AbstractSet { ++ ++ @Override ++ @Nonnull ++ public Iterator iterator() { ++ return new ProfilePropertyIterator(profile.getProperties().values().iterator()); ++ } ++ ++ @Override ++ public int size() { ++ return profile.getProperties().size(); ++ } ++ ++ @Override ++ public boolean add(ProfileProperty property) { ++ setProperty(property); ++ return true; ++ } ++ ++ @Override ++ public boolean addAll(Collection c) { ++ //noinspection unchecked ++ setProperties((Collection) c); ++ return true; ++ } ++ ++ @Override ++ public boolean contains(Object o) { ++ return o instanceof ProfileProperty && profile.getProperties().containsKey(((ProfileProperty) o).getName()); ++ } ++ ++ private class ProfilePropertyIterator implements Iterator { ++ private final Iterator iterator; ++ ++ ProfilePropertyIterator(Iterator iterator) { ++ this.iterator = iterator; ++ } ++ ++ @Override ++ public boolean hasNext() { ++ return iterator.hasNext(); ++ } ++ ++ @Override ++ public ProfileProperty next() { ++ return toBukkit(iterator.next()); ++ } ++ ++ @Override ++ public void remove() { ++ iterator.remove(); ++ } ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java +new file mode 100644 +index 0000000000000000000000000000000000000000..48e774677edf17d4a99ae9ed23d1b371dab41abb +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java +@@ -0,0 +1,30 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.Environment; ++import com.mojang.authlib.EnvironmentParser; ++import com.mojang.authlib.GameProfileRepository; ++import com.mojang.authlib.minecraft.MinecraftSessionService; ++import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; ++import com.mojang.authlib.yggdrasil.YggdrasilEnvironment; ++ ++import java.net.Proxy; ++ ++public class PaperAuthenticationService extends YggdrasilAuthenticationService { ++ ++ private final Environment environment; ++ ++ public PaperAuthenticationService(Proxy proxy) { ++ super(proxy); ++ this.environment = EnvironmentParser.getEnvironmentFromProperties().orElse(YggdrasilEnvironment.PROD.getEnvironment()); ++ } ++ ++ @Override ++ public MinecraftSessionService createMinecraftSessionService() { ++ return new PaperMinecraftSessionService(this.getServicesKeySet(), this.getProxy(), this.environment); ++ } ++ ++ @Override ++ public GameProfileRepository createProfileRepository() { ++ return new PaperGameProfileRepository(this.getProxy(), this.environment); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7b9e797b42c88b17d6a7c590a423f4e85d99a59d +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java +@@ -0,0 +1,17 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.Environment; ++import com.mojang.authlib.ProfileLookupCallback; ++import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository; ++import java.net.Proxy; ++ ++public class PaperGameProfileRepository extends YggdrasilGameProfileRepository { ++ public PaperGameProfileRepository(Proxy proxy, Environment environment) { ++ super(proxy, environment); ++ } ++ ++ @Override ++ public void findProfilesByNames(String[] names, ProfileLookupCallback callback) { ++ super.findProfilesByNames(names, callback); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java +new file mode 100644 +index 0000000000000000000000000000000000000000..985e6fc43a0946943847e0c283426242ef594a26 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java +@@ -0,0 +1,22 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.Environment; ++import com.mojang.authlib.yggdrasil.ProfileResult; ++import com.mojang.authlib.yggdrasil.ServicesKeySet; ++import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService; ++ ++import java.net.Proxy; ++import java.util.UUID; ++import org.jetbrains.annotations.Nullable; ++ ++public class PaperMinecraftSessionService extends YggdrasilMinecraftSessionService { ++ ++ protected PaperMinecraftSessionService(ServicesKeySet servicesKeySet, Proxy proxy, Environment environment) { ++ super(servicesKeySet, proxy, environment); ++ } ++ ++ @Override ++ public @Nullable ProfileResult fetchProfile(final UUID profileId, final boolean requireSecure) { ++ return super.fetchProfile(profileId, requireSecure); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7ac27392a8647ef7d0dc78efe78703e993885017 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java +@@ -0,0 +1,23 @@ ++package com.destroystokyo.paper.profile; ++ ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.properties.Property; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.UUID; ++ ++public interface SharedPlayerProfile { ++ ++ @Nullable UUID getUniqueId(); ++ ++ @Nullable String getName(); ++ ++ boolean removeProperty(@NotNull String property); ++ ++ @Nullable Property getProperty(@NotNull String propertyName); ++ ++ @Nullable void setProperty(@NotNull String propertyName, @Nullable Property property); ++ ++ @NotNull GameProfile buildGameProfile(); ++} +diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java +index 8ebef203d1e2584aed61bd61a93e231416eda749..36c0215a1ebc9372e5f355ecbe34fc1aaefd6903 100644 +--- a/src/main/java/io/papermc/paper/util/MCUtil.java ++++ b/src/main/java/io/papermc/paper/util/MCUtil.java +@@ -1,5 +1,7 @@ + package io.papermc.paper.util; + ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.destroystokyo.paper.profile.PlayerProfile; + import com.google.common.util.concurrent.ThreadFactoryBuilder; + import io.papermc.paper.math.BlockPosition; + import io.papermc.paper.math.FinePosition; +@@ -17,6 +19,7 @@ import net.minecraft.world.level.ClipContext; + import net.minecraft.world.level.Level; + import net.minecraft.world.phys.Vec3; + import org.apache.commons.lang.exception.ExceptionUtils; ++import com.mojang.authlib.GameProfile; + import org.bukkit.Location; + import org.bukkit.block.BlockFace; + import org.bukkit.craftbukkit.CraftWorld; +@@ -360,6 +363,10 @@ public final class MCUtil { + return run.get(); + } + ++ public static PlayerProfile toBukkit(GameProfile profile) { ++ return CraftPlayerProfile.asBukkitMirror(profile); ++ } ++ + /** + * Calculates distance between 2 entities + * @param e1 +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index ba58580f4c60205d1c7a7b7dfcdc22c4fafc9bc6..5374a6b70a9780cfe9f62207b290b9a3ab82c7e9 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -176,7 +176,7 @@ public class Main { + } + + File file = (File) optionset.valueOf("universe"); // CraftBukkit +- Services services = Services.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), file, optionset); // Paper - pass OptionSet to load paper config files ++ Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, optionset); // Paper - pass OptionSet to load paper config files; override authentication service + // CraftBukkit start + String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName); + LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath()); +diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java +index adb472c175cc6f6ced7075a37423d6c898fd5ccb..1ec0f3a7148c2f412421772f6e1dff0bb92a51bc 100644 +--- a/src/main/java/net/minecraft/server/players/GameProfileCache.java ++++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java +@@ -126,6 +126,17 @@ public class GameProfileCache { + return this.operationCount.incrementAndGet(); + } + ++ // Paper start ++ public @Nullable GameProfile getProfileIfCached(String name) { ++ GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT)); ++ if (entry == null) { ++ return null; ++ } ++ entry.setLastAccess(this.getNextOperation()); ++ return entry.getProfile(); ++ } ++ // Paper end ++ + public Optional get(String name) { + String s1 = name.toLowerCase(Locale.ROOT); + GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index f7d937c6a11e24afe767411428210f3c042a6f78..eb3bb76cf141991acd6a384bf461719de8392754 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -262,6 +262,9 @@ import org.yaml.snakeyaml.error.MarkedYAMLException; + + import net.md_5.bungee.api.chat.BaseComponent; // Spigot + ++import javax.annotation.Nullable; // Paper ++import javax.annotation.Nonnull; // Paper ++ + public final class CraftServer implements Server { + private final String serverName = "Paper"; // Paper + private final String serverVersion; +@@ -305,6 +308,7 @@ public final class CraftServer implements Server { + static { + ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); + ConfigurationSerialization.registerClass(CraftPlayerProfile.class); ++ ConfigurationSerialization.registerClass(com.destroystokyo.paper.profile.CraftPlayerProfile.class); // Paper + CraftItemFactory.instance(); + } + +@@ -2782,5 +2786,42 @@ public final class CraftServer implements Server { + public boolean suggestPlayerNamesWhenNullTabCompletions() { + return io.papermc.paper.configuration.GlobalConfiguration.get().commands.suggestPlayerNamesWhenNullTabCompletions; + } ++ ++ @Override ++ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) { ++ return createProfile(uuid, null); ++ } ++ ++ @Override ++ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull String name) { ++ return createProfile(null, name); ++ } ++ ++ @Override ++ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nullable UUID uuid, @Nullable String name) { ++ Player player = uuid != null ? Bukkit.getPlayer(uuid) : (name != null ? Bukkit.getPlayerExact(name) : null); ++ if (player != null) return new com.destroystokyo.paper.profile.CraftPlayerProfile((CraftPlayer) player); ++ ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name); ++ } ++ ++ @Override ++ public com.destroystokyo.paper.profile.PlayerProfile createProfileExact(@Nullable UUID uuid, @Nullable String name) { ++ Player player = uuid != null ? Bukkit.getPlayer(uuid) : (name != null ? Bukkit.getPlayerExact(name) : null); ++ if (player == null) { ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name); ++ } ++ ++ if (java.util.Objects.equals(uuid, player.getUniqueId()) && java.util.Objects.equals(name, player.getName())) { ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile((CraftPlayer) player); ++ } ++ ++ final com.mojang.authlib.GameProfile profile = new com.mojang.authlib.GameProfile( ++ uuid != null ? uuid : net.minecraft.Util.NIL_UUID, ++ name != null ? name : "" ++ ); ++ profile.getProperties().putAll(((CraftPlayer) player).getHandle().getGameProfile().getProperties()); ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile(profile); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java +index edd340c66ea8cec1c76ba29f1deab14c4784a7e5..6f779c6f4422c5b5dc22f66e3e702c714d0e052b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java ++++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java +@@ -28,7 +28,7 @@ import org.bukkit.profile.PlayerProfile; + import org.bukkit.profile.PlayerTextures; + + @SerializableAs("PlayerProfile") +-public final class CraftPlayerProfile implements PlayerProfile { ++public final class CraftPlayerProfile implements PlayerProfile, com.destroystokyo.paper.profile.SharedPlayerProfile { // Paper + + @Nonnull + public static GameProfile validateSkullProfile(@Nonnull GameProfile gameProfile) { +@@ -93,8 +93,10 @@ public final class CraftPlayerProfile implements PlayerProfile { + } + } + +- void removeProperty(String propertyName) { +- this.properties.removeAll(propertyName); ++ // Paper start - change return value for shared interface ++ public boolean removeProperty(String propertyName) { ++ return !this.properties.removeAll(propertyName).isEmpty(); ++ // Paper end + } + + void rebuildDirtyProperties() { +@@ -237,6 +239,7 @@ public final class CraftPlayerProfile implements PlayerProfile { + + @Override + public Map serialize() { ++ // Paper - diff on change + Map map = new LinkedHashMap<>(); + if (this.getUniqueId() != null) { + map.put("uniqueId", this.getUniqueId().toString()); +@@ -252,10 +255,12 @@ public final class CraftPlayerProfile implements PlayerProfile { + }); + map.put("properties", propertiesData); + } ++ // Paper - diff on change + return map; + } + + public static CraftPlayerProfile deserialize(Map map) { ++ // Paper - diff on change + UUID uniqueId = ConfigSerializationUtil.getUuid(map, "uniqueId", true); + String name = ConfigSerializationUtil.getString(map, "name", true); + +@@ -269,7 +274,7 @@ public final class CraftPlayerProfile implements PlayerProfile { + profile.properties.put(property.name(), property); + } + } +- ++ // Paper - diff on change + return profile; + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java +index 78e47fec27f4dd955632d03f7181682af0429961..0dce455fb47b3f5a2eb2b15a1cdbc4c6a54b7b69 100644 +--- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java ++++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java +@@ -48,7 +48,7 @@ public final class CraftPlayerTextures implements PlayerTextures { + } + } + +- private final CraftPlayerProfile profile; ++ private final com.destroystokyo.paper.profile.SharedPlayerProfile profile; // Paper + + // The textures data is loaded lazily: + private boolean loaded = false; +@@ -67,7 +67,7 @@ public final class CraftPlayerTextures implements PlayerTextures { + // GameProfiles (even if these modifications are later reverted). + private boolean dirty = false; + +- CraftPlayerTextures(@Nonnull CraftPlayerProfile profile) { ++ public CraftPlayerTextures(@Nonnull com.destroystokyo.paper.profile.SharedPlayerProfile profile) { // Paper + this.profile = profile; + } + diff --git a/patches/server/0135-Add-UnknownCommandEvent.patch b/patches/server/0135-Add-UnknownCommandEvent.patch new file mode 100644 index 000000000000..032fb0b09fca --- /dev/null +++ b/patches/server/0135-Add-UnknownCommandEvent.patch @@ -0,0 +1,128 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sweepyoface +Date: Sat, 17 Jun 2017 18:48:21 -0400 +Subject: [PATCH] Add UnknownCommandEvent + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java +index f6938c35ac6f6116084d3e7ec9cdc918f20b6f61..edf49f2d9921b4517fb98929d842f7a62c5549df 100644 +--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java ++++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java +@@ -334,8 +334,13 @@ public class CommandSourceStack implements ExecutionCommandSource dispatcher = new com.mojang.brigadier.CommandDispatcher(); ++ public final java.util.List> vanillaCommandNodes = new java.util.ArrayList<>(); // Paper - Add UnknownCommandEvent + + public Commands(Commands.CommandSelection environment, CommandBuildContext commandRegistryAccess) { + this(); // CraftBukkit +@@ -254,6 +255,7 @@ public class Commands { + if (environment.includeIntegrated) { + PublishCommand.register(this.dispatcher); + } ++ this.vanillaCommandNodes.addAll(this.dispatcher.getRoot().getChildren()); // Paper - Add UnknownCommandEvent + + // CraftBukkit start + } +@@ -328,7 +330,7 @@ public class Commands { + commandlistenerwrapper.getServer().getProfiler().push(() -> { + return "/" + s; + }); +- ContextChain contextchain = Commands.finishParsing(parseresults, s, commandlistenerwrapper, label); // CraftBukkit ++ ContextChain contextchain = this.finishParsing(parseresults, s, commandlistenerwrapper, label); // CraftBukkit // Paper - Add UnknownCommandEvent + + try { + if (contextchain != null) { +@@ -362,14 +364,23 @@ public class Commands { + } + + @Nullable +- private static ContextChain finishParsing(ParseResults parseresults, String s, CommandSourceStack commandlistenerwrapper, String label) { // CraftBukkit ++ private ContextChain finishParsing(ParseResults parseresults, String s, CommandSourceStack commandlistenerwrapper, String label) { // CraftBukkit // Paper - Add UnknownCommandEvent + try { + Commands.validateParseResults(parseresults); + return (ContextChain) ContextChain.tryFlatten(parseresults.getContext().build(s)).orElseThrow(() -> { + return CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parseresults.getReader()); + }); + } catch (CommandSyntaxException commandsyntaxexception) { +- commandlistenerwrapper.sendFailure(ComponentUtils.fromMessage(commandsyntaxexception.getRawMessage())); ++ // Paper start - Add UnknownCommandEvent ++ final net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text(); ++ if ((parseresults.getContext().getNodes().isEmpty() || !this.vanillaCommandNodes.contains(parseresults.getContext().getNodes().get(0).getNode()))) { ++ if (!org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty()) { ++ builder.append(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.unknownCommandMessage)); ++ } ++ } else { ++ // commandlistenerwrapper.sendFailure(ComponentUtils.fromMessage(commandsyntaxexception.getRawMessage())); ++ builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(commandsyntaxexception.getRawMessage())); ++ // Paper end - Add UnknownCommandEvent + if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) { + int i = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor()); + MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> { +@@ -388,7 +399,18 @@ public class Commands { + } + + ichatmutablecomponent.append((Component) Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC)); +- commandlistenerwrapper.sendFailure(ichatmutablecomponent); ++ // Paper start - Add UnknownCommandEvent ++ // commandlistenerwrapper.sendFailure(ichatmutablecomponent); ++ builder ++ .append(net.kyori.adventure.text.Component.newline()) ++ .append(io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); ++ } ++ } ++ org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(commandlistenerwrapper.getBukkitSender(), s, org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty() ? null : builder.build()); ++ org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event); ++ if (event.message() != null) { ++ commandlistenerwrapper.sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false); ++ // Paper end - Add UnknownCommandEvent + } + + return null; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index eb3bb76cf141991acd6a384bf461719de8392754..b6e6b4213e893fac64855cc4b4e6eeb4246050d1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -529,6 +529,7 @@ public final class CraftServer implements Server { + } + node = clone; + } ++ dispatcher.vanillaCommandNodes.add(node); // Paper + + dispatcher.getDispatcher().getRoot().addChild(node); + } else { +@@ -911,7 +912,13 @@ public final class CraftServer implements Server { + + // Spigot start + if (!org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty()) { +- sender.sendMessage(org.spigotmc.SpigotConfig.unknownCommandMessage); ++ // Paper start ++ org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(sender, commandLine, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.unknownCommandMessage)); ++ this.getPluginManager().callEvent(event); ++ if (event.message() != null) { ++ sender.sendMessage(event.message()); ++ } ++ // Paper end + } + // Spigot end + diff --git a/patches/server/0135-Basic-PlayerProfile-API.patch b/patches/server/0135-Basic-PlayerProfile-API.patch deleted file mode 100644 index 9f1c39496a67..000000000000 --- a/patches/server/0135-Basic-PlayerProfile-API.patch +++ /dev/null @@ -1,756 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Mon, 15 Jan 2018 22:11:48 -0500 -Subject: [PATCH] Basic PlayerProfile API - -Establishes base extension of profile systems for future edits too - -== AT == -public org.bukkit.craftbukkit.profile.CraftProfileProperty -public org.bukkit.craftbukkit.profile.CraftPlayerTextures -public org.bukkit.craftbukkit.profile.CraftPlayerTextures copyFrom(Lorg/bukkit/profile/PlayerTextures;)V -public org.bukkit.craftbukkit.profile.CraftPlayerTextures rebuildPropertyIfDirty()V -public org.bukkit.craftbukkit.profile.CraftPlayerProfile toString(Lcom/mojang/authlib/properties/PropertyMap;)Ljava/lang/String; -public org.bukkit.craftbukkit.profile.CraftPlayerProfile getProperty(Ljava/lang/String;)Lcom/mojang/authlib/properties/Property; -public org.bukkit.craftbukkit.profile.CraftPlayerProfile setProperty(Ljava/lang/String;Lcom/mojang/authlib/properties/Property;)V - -diff --git a/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java -new file mode 100644 -index 0000000000000000000000000000000000000000..daa157eaa021d039f9a092bea0b78f7c1f746e3b ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java -@@ -0,0 +1,409 @@ -+package com.destroystokyo.paper.profile; -+ -+import com.mojang.authlib.yggdrasil.ProfileResult; -+import io.papermc.paper.configuration.GlobalConfiguration; -+import com.google.common.base.Charsets; -+import com.google.common.collect.Iterables; -+import com.mojang.authlib.GameProfile; -+import com.mojang.authlib.properties.Property; -+import com.mojang.authlib.properties.PropertyMap; -+import net.minecraft.Util; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.players.GameProfileCache; -+import org.apache.commons.lang3.StringUtils; -+import org.apache.commons.lang3.Validate; -+import org.bukkit.configuration.serialization.SerializableAs; -+import org.bukkit.craftbukkit.configuration.ConfigSerializationUtil; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.craftbukkit.profile.CraftPlayerTextures; -+import org.bukkit.craftbukkit.profile.CraftProfileProperty; -+import org.bukkit.profile.PlayerTextures; -+import org.jetbrains.annotations.NotNull; -+ -+import javax.annotation.Nonnull; -+import javax.annotation.Nullable; -+import java.util.*; -+import java.util.concurrent.CompletableFuture; -+ -+@SerializableAs("PlayerProfile") -+public class CraftPlayerProfile implements PlayerProfile, SharedPlayerProfile { -+ -+ private GameProfile profile; -+ private final PropertySet properties = new PropertySet(); -+ -+ public CraftPlayerProfile(CraftPlayer player) { -+ this.profile = player.getHandle().getGameProfile(); -+ } -+ -+ public CraftPlayerProfile(UUID id, String name) { -+ this.profile = createAuthLibProfile(id, name); -+ } -+ -+ public CraftPlayerProfile(GameProfile profile) { -+ Validate.notNull(profile, "GameProfile cannot be null!"); -+ this.profile = profile; -+ } -+ -+ @Override -+ public boolean hasProperty(String property) { -+ return profile.getProperties().containsKey(property); -+ } -+ -+ @Override -+ public void setProperty(ProfileProperty property) { -+ String name = property.getName(); -+ PropertyMap properties = profile.getProperties(); -+ properties.removeAll(name); -+ properties.put(name, new Property(name, property.getValue(), property.getSignature())); -+ } -+ -+ @Override -+ public CraftPlayerTextures getTextures() { -+ return new CraftPlayerTextures(this); -+ } -+ -+ @Override -+ public void setTextures(@Nullable PlayerTextures textures) { -+ if (textures == null) { -+ this.removeProperty("textures"); -+ } else { -+ CraftPlayerTextures craftPlayerTextures = new CraftPlayerTextures(this); -+ craftPlayerTextures.copyFrom(textures); -+ craftPlayerTextures.rebuildPropertyIfDirty(); -+ } -+ } -+ -+ public GameProfile getGameProfile() { -+ return profile; -+ } -+ -+ @Nullable -+ @Override -+ public UUID getId() { -+ return profile.getId().equals(Util.NIL_UUID) ? null : profile.getId(); -+ } -+ -+ @Override -+ @Deprecated(forRemoval = true) -+ public UUID setId(@Nullable UUID uuid) { -+ final GameProfile previousProfile = this.profile; -+ final UUID previousId = this.getId(); -+ this.profile = createAuthLibProfile(uuid, previousProfile.getName()); -+ copyProfileProperties(previousProfile, this.profile); -+ return previousId; -+ } -+ -+ @Override -+ public UUID getUniqueId() { -+ return getId(); -+ } -+ -+ @Nullable -+ @Override -+ public String getName() { -+ return profile.getName(); -+ } -+ -+ @Override -+ @Deprecated(forRemoval = true) -+ public String setName(@Nullable String name) { -+ GameProfile prev = this.profile; -+ this.profile = createAuthLibProfile(prev.getId(), name); -+ copyProfileProperties(prev, this.profile); -+ return prev.getName(); -+ } -+ -+ @Nonnull -+ @Override -+ public Set getProperties() { -+ return properties; -+ } -+ -+ @Override -+ public void setProperties(Collection properties) { -+ properties.forEach(this::setProperty); -+ } -+ -+ @Override -+ public void clearProperties() { -+ profile.getProperties().clear(); -+ } -+ -+ @Override -+ public boolean removeProperty(String property) { -+ return !profile.getProperties().removeAll(property).isEmpty(); -+ } -+ -+ @Nullable -+ @Override -+ public Property getProperty(String property) { -+ return Iterables.getFirst(this.profile.getProperties().get(property), null); -+ } -+ -+ @Nullable -+ @Override -+ public void setProperty(@NotNull String propertyName, @Nullable Property property) { -+ PropertyMap properties = profile.getProperties(); -+ properties.removeAll(propertyName); -+ if (property != null) { -+ properties.put(propertyName, property); -+ } -+ } -+ -+ @Override -+ public @NotNull GameProfile buildGameProfile() { -+ GameProfile profile = new GameProfile(this.profile.getId(), this.profile.getName()); -+ profile.getProperties().putAll(this.profile.getProperties()); -+ return profile; -+ } -+ -+ @Override -+ public CraftPlayerProfile clone() { -+ CraftPlayerProfile clone = new CraftPlayerProfile(this.getId(), this.getName()); -+ clone.setProperties(getProperties()); -+ return clone; -+ } -+ -+ @Override -+ public boolean isComplete() { -+ return this.getId() != null && StringUtils.isNotBlank(this.getName()); -+ } -+ -+ @Override -+ public @NotNull CompletableFuture update() { -+ return CompletableFuture.supplyAsync(() -> { -+ final CraftPlayerProfile clone = clone(); -+ clone.complete(true); -+ return clone; -+ }, Util.PROFILE_EXECUTOR); -+ } -+ -+ @Override -+ public boolean completeFromCache() { -+ return completeFromCache(false, GlobalConfiguration.get().proxies.isProxyOnlineMode()); -+ } -+ -+ public boolean completeFromCache(boolean onlineMode) { -+ return completeFromCache(false, onlineMode); -+ } -+ -+ public boolean completeFromCache(boolean lookupUUID, boolean onlineMode) { -+ MinecraftServer server = MinecraftServer.getServer(); -+ String name = profile.getName(); -+ GameProfileCache userCache = server.getProfileCache(); -+ if (this.getId() == null) { -+ final GameProfile profile; -+ if (onlineMode) { -+ profile = lookupUUID ? userCache.get(name).orElse(null) : userCache.getProfileIfCached(name); -+ } else { -+ // Make an OfflinePlayer using an offline mode UUID since the name has no profile -+ profile = new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name); -+ } -+ if (profile != null) { -+ // if old has it, assume its newer, so overwrite, else use cached if it was set and ours wasn't -+ copyProfileProperties(this.profile, profile); -+ this.profile = profile; -+ } -+ } -+ -+ if ((profile.getName().isEmpty() || !hasTextures()) && this.getId() != null) { -+ Optional optProfile = userCache.get(this.profile.getId()); -+ if (optProfile.isPresent()) { -+ GameProfile profile = optProfile.get(); -+ if (this.profile.getName().isEmpty()) { -+ // if old has it, assume its newer, so overwrite, else use cached if it was set and ours wasn't -+ copyProfileProperties(this.profile, profile); -+ this.profile = profile; -+ } else { -+ copyProfileProperties(profile, this.profile); -+ } -+ } -+ } -+ return this.isComplete(); -+ } -+ -+ public boolean complete(boolean textures) { -+ return complete(textures, GlobalConfiguration.get().proxies.isProxyOnlineMode()); -+ } -+ public boolean complete(boolean textures, boolean onlineMode) { -+ MinecraftServer server = MinecraftServer.getServer(); -+ boolean isCompleteFromCache = this.completeFromCache(true, onlineMode); -+ if (onlineMode && (!isCompleteFromCache || textures && !hasTextures())) { -+ ProfileResult result = server.getSessionService().fetchProfile(this.getId(), true); -+ if (result != null && result.profile() != null) { -+ copyProfileProperties(result.profile(), this.profile, true); -+ } -+ if (this.isComplete()) { -+ server.getProfileCache().add(this.profile); -+ } -+ } -+ return this.isComplete() && (!onlineMode || !textures || hasTextures()); -+ } -+ -+ private static void copyProfileProperties(GameProfile source, GameProfile target) { -+ copyProfileProperties(source, target, false); -+ } -+ -+ private static void copyProfileProperties(GameProfile source, GameProfile target, boolean clearTarget) { -+ PropertyMap sourceProperties = source.getProperties(); -+ PropertyMap targetProperties = target.getProperties(); -+ if (clearTarget) targetProperties.clear(); -+ if (sourceProperties.isEmpty()) { -+ return; -+ } -+ -+ for (Property property : sourceProperties.values()) { -+ targetProperties.removeAll(property.name()); -+ targetProperties.put(property.name(), property); -+ } -+ } -+ -+ private static GameProfile createAuthLibProfile(UUID uniqueId, String name) { -+ return new GameProfile( -+ uniqueId != null ? uniqueId : Util.NIL_UUID, -+ name != null ? name : "" -+ ); -+ } -+ -+ private static ProfileProperty toBukkit(Property property) { -+ return new ProfileProperty(property.name(), property.value(), property.signature()); -+ } -+ -+ public static PlayerProfile asBukkitCopy(GameProfile gameProfile) { -+ CraftPlayerProfile profile = new CraftPlayerProfile(gameProfile.getId(), gameProfile.getName()); -+ copyProfileProperties(gameProfile, profile.profile); -+ return profile; -+ } -+ -+ public static PlayerProfile asBukkitMirror(GameProfile profile) { -+ return new CraftPlayerProfile(profile); -+ } -+ -+ public static Property asAuthlib(ProfileProperty property) { -+ return new Property(property.getName(), property.getValue(), property.getSignature()); -+ } -+ -+ public static GameProfile asAuthlibCopy(PlayerProfile profile) { -+ CraftPlayerProfile craft = ((CraftPlayerProfile) profile); -+ return asAuthlib(craft.clone()); -+ } -+ -+ public static GameProfile asAuthlib(PlayerProfile profile) { -+ CraftPlayerProfile craft = ((CraftPlayerProfile) profile); -+ return craft.getGameProfile(); -+ } -+ -+ @Override -+ public @NotNull Map serialize() { -+ Map map = new LinkedHashMap<>(); -+ if (this.getId() != null) { -+ map.put("uniqueId", this.getId().toString()); -+ } -+ if (!this.getName().isEmpty()) { -+ map.put("name", getName()); -+ } -+ if (!this.properties.isEmpty()) { -+ List propertiesData = new ArrayList<>(); -+ for (ProfileProperty property : properties) { -+ propertiesData.add(CraftProfileProperty.serialize(new Property(property.getName(), property.getValue(), property.getSignature()))); -+ } -+ map.put("properties", propertiesData); -+ } -+ return map; -+ } -+ -+ public static CraftPlayerProfile deserialize(Map map) { -+ UUID uniqueId = ConfigSerializationUtil.getUuid(map, "uniqueId", true); -+ String name = ConfigSerializationUtil.getString(map, "name", true); -+ -+ // This also validates the deserialized unique id and name (ensures that not both are null): -+ CraftPlayerProfile profile = new CraftPlayerProfile(uniqueId, name); -+ -+ if (map.containsKey("properties")) { -+ for (Object propertyData : (List) map.get("properties")) { -+ if (!(propertyData instanceof Map)) { -+ throw new IllegalArgumentException("Property data (" + propertyData + ") is not a valid Map"); -+ } -+ Property property = CraftProfileProperty.deserialize((Map) propertyData); -+ profile.profile.getProperties().put(property.name(), property); -+ } -+ } -+ -+ return profile; -+ } -+ -+ @Override -+ public boolean equals(Object obj) { -+ if (this == obj) return true; -+ if (!(obj instanceof CraftPlayerProfile otherProfile)) return false; -+ return Objects.equals(this.profile, otherProfile.profile); -+ } -+ -+ @Override -+ public String toString() { -+ return "CraftPlayerProfile [uniqueId=" + getId() + -+ ", name=" + getName() + -+ ", properties=" + org.bukkit.craftbukkit.profile.CraftPlayerProfile.toString(this.profile.getProperties()) + -+ "]"; -+ } -+ -+ @Override -+ public int hashCode() { -+ return this.profile.hashCode(); -+ } -+ -+ private class PropertySet extends AbstractSet { -+ -+ @Override -+ @Nonnull -+ public Iterator iterator() { -+ return new ProfilePropertyIterator(profile.getProperties().values().iterator()); -+ } -+ -+ @Override -+ public int size() { -+ return profile.getProperties().size(); -+ } -+ -+ @Override -+ public boolean add(ProfileProperty property) { -+ setProperty(property); -+ return true; -+ } -+ -+ @Override -+ public boolean addAll(Collection c) { -+ //noinspection unchecked -+ setProperties((Collection) c); -+ return true; -+ } -+ -+ @Override -+ public boolean contains(Object o) { -+ return o instanceof ProfileProperty && profile.getProperties().containsKey(((ProfileProperty) o).getName()); -+ } -+ -+ private class ProfilePropertyIterator implements Iterator { -+ private final Iterator iterator; -+ -+ ProfilePropertyIterator(Iterator iterator) { -+ this.iterator = iterator; -+ } -+ -+ @Override -+ public boolean hasNext() { -+ return iterator.hasNext(); -+ } -+ -+ @Override -+ public ProfileProperty next() { -+ return toBukkit(iterator.next()); -+ } -+ -+ @Override -+ public void remove() { -+ iterator.remove(); -+ } -+ } -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java -new file mode 100644 -index 0000000000000000000000000000000000000000..48e774677edf17d4a99ae9ed23d1b371dab41abb ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java -@@ -0,0 +1,30 @@ -+package com.destroystokyo.paper.profile; -+ -+import com.mojang.authlib.Environment; -+import com.mojang.authlib.EnvironmentParser; -+import com.mojang.authlib.GameProfileRepository; -+import com.mojang.authlib.minecraft.MinecraftSessionService; -+import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; -+import com.mojang.authlib.yggdrasil.YggdrasilEnvironment; -+ -+import java.net.Proxy; -+ -+public class PaperAuthenticationService extends YggdrasilAuthenticationService { -+ -+ private final Environment environment; -+ -+ public PaperAuthenticationService(Proxy proxy) { -+ super(proxy); -+ this.environment = EnvironmentParser.getEnvironmentFromProperties().orElse(YggdrasilEnvironment.PROD.getEnvironment()); -+ } -+ -+ @Override -+ public MinecraftSessionService createMinecraftSessionService() { -+ return new PaperMinecraftSessionService(this.getServicesKeySet(), this.getProxy(), this.environment); -+ } -+ -+ @Override -+ public GameProfileRepository createProfileRepository() { -+ return new PaperGameProfileRepository(this.getProxy(), this.environment); -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7b9e797b42c88b17d6a7c590a423f4e85d99a59d ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java -@@ -0,0 +1,17 @@ -+package com.destroystokyo.paper.profile; -+ -+import com.mojang.authlib.Environment; -+import com.mojang.authlib.ProfileLookupCallback; -+import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository; -+import java.net.Proxy; -+ -+public class PaperGameProfileRepository extends YggdrasilGameProfileRepository { -+ public PaperGameProfileRepository(Proxy proxy, Environment environment) { -+ super(proxy, environment); -+ } -+ -+ @Override -+ public void findProfilesByNames(String[] names, ProfileLookupCallback callback) { -+ super.findProfilesByNames(names, callback); -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java -new file mode 100644 -index 0000000000000000000000000000000000000000..985e6fc43a0946943847e0c283426242ef594a26 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java -@@ -0,0 +1,22 @@ -+package com.destroystokyo.paper.profile; -+ -+import com.mojang.authlib.Environment; -+import com.mojang.authlib.yggdrasil.ProfileResult; -+import com.mojang.authlib.yggdrasil.ServicesKeySet; -+import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService; -+ -+import java.net.Proxy; -+import java.util.UUID; -+import org.jetbrains.annotations.Nullable; -+ -+public class PaperMinecraftSessionService extends YggdrasilMinecraftSessionService { -+ -+ protected PaperMinecraftSessionService(ServicesKeySet servicesKeySet, Proxy proxy, Environment environment) { -+ super(servicesKeySet, proxy, environment); -+ } -+ -+ @Override -+ public @Nullable ProfileResult fetchProfile(final UUID profileId, final boolean requireSecure) { -+ return super.fetchProfile(profileId, requireSecure); -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7ac27392a8647ef7d0dc78efe78703e993885017 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/profile/SharedPlayerProfile.java -@@ -0,0 +1,23 @@ -+package com.destroystokyo.paper.profile; -+ -+import com.mojang.authlib.GameProfile; -+import com.mojang.authlib.properties.Property; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.UUID; -+ -+public interface SharedPlayerProfile { -+ -+ @Nullable UUID getUniqueId(); -+ -+ @Nullable String getName(); -+ -+ boolean removeProperty(@NotNull String property); -+ -+ @Nullable Property getProperty(@NotNull String propertyName); -+ -+ @Nullable void setProperty(@NotNull String propertyName, @Nullable Property property); -+ -+ @NotNull GameProfile buildGameProfile(); -+} -diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java -index 7ae65f05eb2219a63a108728e4137b245775d08b..b08190d604f7f95f771a4da115cc50c38c5f1691 100644 ---- a/src/main/java/io/papermc/paper/util/MCUtil.java -+++ b/src/main/java/io/papermc/paper/util/MCUtil.java -@@ -1,5 +1,7 @@ - package io.papermc.paper.util; - -+import com.destroystokyo.paper.profile.CraftPlayerProfile; -+import com.destroystokyo.paper.profile.PlayerProfile; - import com.google.common.util.concurrent.ThreadFactoryBuilder; - import io.papermc.paper.math.BlockPosition; - import io.papermc.paper.math.FinePosition; -@@ -17,6 +19,7 @@ import net.minecraft.world.level.ClipContext; - import net.minecraft.world.level.Level; - import net.minecraft.world.phys.Vec3; - import org.apache.commons.lang.exception.ExceptionUtils; -+import com.mojang.authlib.GameProfile; - import org.bukkit.Location; - import org.bukkit.block.BlockFace; - import org.bukkit.craftbukkit.CraftWorld; -@@ -360,6 +363,10 @@ public final class MCUtil { - return run.get(); - } - -+ public static PlayerProfile toBukkit(GameProfile profile) { -+ return CraftPlayerProfile.asBukkitMirror(profile); -+ } -+ - /** - * Calculates distance between 2 entities - * @param e1 -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index ba58580f4c60205d1c7a7b7dfcdc22c4fafc9bc6..5374a6b70a9780cfe9f62207b290b9a3ab82c7e9 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -176,7 +176,7 @@ public class Main { - } - - File file = (File) optionset.valueOf("universe"); // CraftBukkit -- Services services = Services.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), file, optionset); // Paper - pass OptionSet to load paper config files -+ Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, optionset); // Paper - pass OptionSet to load paper config files; override authentication service - // CraftBukkit start - String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName); - LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath()); -diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java -index adb472c175cc6f6ced7075a37423d6c898fd5ccb..1ec0f3a7148c2f412421772f6e1dff0bb92a51bc 100644 ---- a/src/main/java/net/minecraft/server/players/GameProfileCache.java -+++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java -@@ -126,6 +126,17 @@ public class GameProfileCache { - return this.operationCount.incrementAndGet(); - } - -+ // Paper start -+ public @Nullable GameProfile getProfileIfCached(String name) { -+ GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT)); -+ if (entry == null) { -+ return null; -+ } -+ entry.setLastAccess(this.getNextOperation()); -+ return entry.getProfile(); -+ } -+ // Paper end -+ - public Optional get(String name) { - String s1 = name.toLowerCase(Locale.ROOT); - GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index b9c23e03577724459b87bf9f0c5b93f593515b5a..11111b1015d362b35cbb6d35bee91c3c130e7cc7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -260,6 +260,9 @@ import org.yaml.snakeyaml.error.MarkedYAMLException; - - import net.md_5.bungee.api.chat.BaseComponent; // Spigot - -+import javax.annotation.Nullable; // Paper -+import javax.annotation.Nonnull; // Paper -+ - public final class CraftServer implements Server { - private final String serverName = "Paper"; // Paper - private final String serverVersion; -@@ -303,6 +306,7 @@ public final class CraftServer implements Server { - static { - ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); - ConfigurationSerialization.registerClass(CraftPlayerProfile.class); -+ ConfigurationSerialization.registerClass(com.destroystokyo.paper.profile.CraftPlayerProfile.class); // Paper - CraftItemFactory.instance(); - } - -@@ -2770,5 +2774,42 @@ public final class CraftServer implements Server { - public boolean suggestPlayerNamesWhenNullTabCompletions() { - return io.papermc.paper.configuration.GlobalConfiguration.get().commands.suggestPlayerNamesWhenNullTabCompletions; - } -+ -+ @Override -+ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) { -+ return createProfile(uuid, null); -+ } -+ -+ @Override -+ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull String name) { -+ return createProfile(null, name); -+ } -+ -+ @Override -+ public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nullable UUID uuid, @Nullable String name) { -+ Player player = uuid != null ? Bukkit.getPlayer(uuid) : (name != null ? Bukkit.getPlayerExact(name) : null); -+ if (player != null) return new com.destroystokyo.paper.profile.CraftPlayerProfile((CraftPlayer) player); -+ -+ return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name); -+ } -+ -+ @Override -+ public com.destroystokyo.paper.profile.PlayerProfile createProfileExact(@Nullable UUID uuid, @Nullable String name) { -+ Player player = uuid != null ? Bukkit.getPlayer(uuid) : (name != null ? Bukkit.getPlayerExact(name) : null); -+ if (player == null) { -+ return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name); -+ } -+ -+ if (java.util.Objects.equals(uuid, player.getUniqueId()) && java.util.Objects.equals(name, player.getName())) { -+ return new com.destroystokyo.paper.profile.CraftPlayerProfile((CraftPlayer) player); -+ } -+ -+ final com.mojang.authlib.GameProfile profile = new com.mojang.authlib.GameProfile( -+ uuid != null ? uuid : net.minecraft.Util.NIL_UUID, -+ name != null ? name : "" -+ ); -+ profile.getProperties().putAll(((CraftPlayer) player).getHandle().getGameProfile().getProperties()); -+ return new com.destroystokyo.paper.profile.CraftPlayerProfile(profile); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java -index edd340c66ea8cec1c76ba29f1deab14c4784a7e5..6f779c6f4422c5b5dc22f66e3e702c714d0e052b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java -+++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java -@@ -28,7 +28,7 @@ import org.bukkit.profile.PlayerProfile; - import org.bukkit.profile.PlayerTextures; - - @SerializableAs("PlayerProfile") --public final class CraftPlayerProfile implements PlayerProfile { -+public final class CraftPlayerProfile implements PlayerProfile, com.destroystokyo.paper.profile.SharedPlayerProfile { // Paper - - @Nonnull - public static GameProfile validateSkullProfile(@Nonnull GameProfile gameProfile) { -@@ -93,8 +93,10 @@ public final class CraftPlayerProfile implements PlayerProfile { - } - } - -- void removeProperty(String propertyName) { -- this.properties.removeAll(propertyName); -+ // Paper start - change return value for shared interface -+ public boolean removeProperty(String propertyName) { -+ return !this.properties.removeAll(propertyName).isEmpty(); -+ // Paper end - } - - void rebuildDirtyProperties() { -@@ -237,6 +239,7 @@ public final class CraftPlayerProfile implements PlayerProfile { - - @Override - public Map serialize() { -+ // Paper - diff on change - Map map = new LinkedHashMap<>(); - if (this.getUniqueId() != null) { - map.put("uniqueId", this.getUniqueId().toString()); -@@ -252,10 +255,12 @@ public final class CraftPlayerProfile implements PlayerProfile { - }); - map.put("properties", propertiesData); - } -+ // Paper - diff on change - return map; - } - - public static CraftPlayerProfile deserialize(Map map) { -+ // Paper - diff on change - UUID uniqueId = ConfigSerializationUtil.getUuid(map, "uniqueId", true); - String name = ConfigSerializationUtil.getString(map, "name", true); - -@@ -269,7 +274,7 @@ public final class CraftPlayerProfile implements PlayerProfile { - profile.properties.put(property.name(), property); - } - } -- -+ // Paper - diff on change - return profile; - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java -index 78e47fec27f4dd955632d03f7181682af0429961..0dce455fb47b3f5a2eb2b15a1cdbc4c6a54b7b69 100644 ---- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java -+++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerTextures.java -@@ -48,7 +48,7 @@ public final class CraftPlayerTextures implements PlayerTextures { - } - } - -- private final CraftPlayerProfile profile; -+ private final com.destroystokyo.paper.profile.SharedPlayerProfile profile; // Paper - - // The textures data is loaded lazily: - private boolean loaded = false; -@@ -67,7 +67,7 @@ public final class CraftPlayerTextures implements PlayerTextures { - // GameProfiles (even if these modifications are later reverted). - private boolean dirty = false; - -- CraftPlayerTextures(@Nonnull CraftPlayerProfile profile) { -+ public CraftPlayerTextures(@Nonnull com.destroystokyo.paper.profile.SharedPlayerProfile profile) { // Paper - this.profile = profile; - } - diff --git a/patches/server/0136-Add-UnknownCommandEvent.patch b/patches/server/0136-Add-UnknownCommandEvent.patch deleted file mode 100644 index c5c7d0af9553..000000000000 --- a/patches/server/0136-Add-UnknownCommandEvent.patch +++ /dev/null @@ -1,128 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Sweepyoface -Date: Sat, 17 Jun 2017 18:48:21 -0400 -Subject: [PATCH] Add UnknownCommandEvent - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java -index f6938c35ac6f6116084d3e7ec9cdc918f20b6f61..edf49f2d9921b4517fb98929d842f7a62c5549df 100644 ---- a/src/main/java/net/minecraft/commands/CommandSourceStack.java -+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java -@@ -334,8 +334,13 @@ public class CommandSourceStack implements ExecutionCommandSource dispatcher = new com.mojang.brigadier.CommandDispatcher(); -+ public final java.util.List> vanillaCommandNodes = new java.util.ArrayList<>(); // Paper - Add UnknownCommandEvent - - public Commands(Commands.CommandSelection environment, CommandBuildContext commandRegistryAccess) { - this(); // CraftBukkit -@@ -254,6 +255,7 @@ public class Commands { - if (environment.includeIntegrated) { - PublishCommand.register(this.dispatcher); - } -+ this.vanillaCommandNodes.addAll(this.dispatcher.getRoot().getChildren()); // Paper - Add UnknownCommandEvent - - // CraftBukkit start - } -@@ -328,7 +330,7 @@ public class Commands { - commandlistenerwrapper.getServer().getProfiler().push(() -> { - return "/" + s; - }); -- ContextChain contextchain = Commands.finishParsing(parseresults, s, commandlistenerwrapper, label); // CraftBukkit -+ ContextChain contextchain = this.finishParsing(parseresults, s, commandlistenerwrapper, label); // CraftBukkit // Paper - Add UnknownCommandEvent - - try { - if (contextchain != null) { -@@ -362,14 +364,23 @@ public class Commands { - } - - @Nullable -- private static ContextChain finishParsing(ParseResults parseresults, String s, CommandSourceStack commandlistenerwrapper, String label) { // CraftBukkit -+ private ContextChain finishParsing(ParseResults parseresults, String s, CommandSourceStack commandlistenerwrapper, String label) { // CraftBukkit // Paper - Add UnknownCommandEvent - try { - Commands.validateParseResults(parseresults); - return (ContextChain) ContextChain.tryFlatten(parseresults.getContext().build(s)).orElseThrow(() -> { - return CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parseresults.getReader()); - }); - } catch (CommandSyntaxException commandsyntaxexception) { -- commandlistenerwrapper.sendFailure(ComponentUtils.fromMessage(commandsyntaxexception.getRawMessage())); -+ // Paper start - Add UnknownCommandEvent -+ final net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text(); -+ if ((parseresults.getContext().getNodes().isEmpty() || !this.vanillaCommandNodes.contains(parseresults.getContext().getNodes().get(0).getNode()))) { -+ if (!org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty()) { -+ builder.append(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.unknownCommandMessage)); -+ } -+ } else { -+ // commandlistenerwrapper.sendFailure(ComponentUtils.fromMessage(commandsyntaxexception.getRawMessage())); -+ builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(commandsyntaxexception.getRawMessage())); -+ // Paper end - Add UnknownCommandEvent - if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) { - int i = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor()); - MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> { -@@ -388,7 +399,18 @@ public class Commands { - } - - ichatmutablecomponent.append((Component) Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC)); -- commandlistenerwrapper.sendFailure(ichatmutablecomponent); -+ // Paper start - Add UnknownCommandEvent -+ // commandlistenerwrapper.sendFailure(ichatmutablecomponent); -+ builder -+ .append(net.kyori.adventure.text.Component.newline()) -+ .append(io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); -+ } -+ } -+ org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(commandlistenerwrapper.getBukkitSender(), s, org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty() ? null : builder.build()); -+ org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event); -+ if (event.message() != null) { -+ commandlistenerwrapper.sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false); -+ // Paper end - Add UnknownCommandEvent - } - - return null; -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 11111b1015d362b35cbb6d35bee91c3c130e7cc7..46acd80641b2ecafd9f364d7ad9b477b6b2f0289 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -527,6 +527,7 @@ public final class CraftServer implements Server { - } - node = clone; - } -+ dispatcher.vanillaCommandNodes.add(node); // Paper - - dispatcher.getDispatcher().getRoot().addChild(node); - } else { -@@ -899,7 +900,13 @@ public final class CraftServer implements Server { - - // Spigot start - if (!org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty()) { -- sender.sendMessage(org.spigotmc.SpigotConfig.unknownCommandMessage); -+ // Paper start -+ org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(sender, commandLine, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.unknownCommandMessage)); -+ this.getPluginManager().callEvent(event); -+ if (event.message() != null) { -+ sender.sendMessage(event.message()); -+ } -+ // Paper end - } - // Spigot end - diff --git a/patches/server/0136-Shoulder-Entities-Release-API.patch b/patches/server/0136-Shoulder-Entities-Release-API.patch new file mode 100644 index 000000000000..4e178e4c6a82 --- /dev/null +++ b/patches/server/0136-Shoulder-Entities-Release-API.patch @@ -0,0 +1,97 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 17 Jun 2017 15:18:30 -0400 +Subject: [PATCH] Shoulder Entities Release API + + +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 6ff0c2107fa0623d30d9159e3c67388faf597440..cd5a4e91ee8da2a2a5d34ecc9a24394e131e79d0 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1948,20 +1948,45 @@ public abstract class Player extends LivingEntity { + + } + ++ // Paper start ++ public Entity releaseLeftShoulderEntity() { ++ Entity entity = this.respawnEntityOnShoulder0(this.getShoulderEntityLeft()); ++ if (entity != null) { ++ this.setShoulderEntityLeft(new CompoundTag()); ++ } ++ return entity; ++ } ++ ++ public Entity releaseRightShoulderEntity() { ++ Entity entity = this.respawnEntityOnShoulder0(this.getShoulderEntityRight()); ++ if (entity != null) { ++ this.setShoulderEntityRight(new CompoundTag()); ++ } ++ return entity; ++ } ++ // Paper - maintain old signature ++ + private boolean respawnEntityOnShoulder(CompoundTag nbttagcompound) { // CraftBukkit void->boolean +- if (!this.level().isClientSide && !nbttagcompound.isEmpty()) { ++ return this.respawnEntityOnShoulder0(nbttagcompound) != null; ++ } ++ ++ // Paper - return entity ++ private Entity respawnEntityOnShoulder0(CompoundTag nbttagcompound) { // CraftBukkit void->boolean ++ if (!this.level().isClientSide && nbttagcompound != null && !nbttagcompound.isEmpty()) { + return EntityType.create(nbttagcompound, this.level()).map((entity) -> { // CraftBukkit + if (entity instanceof TamableAnimal) { + ((TamableAnimal) entity).setOwnerUUID(this.uuid); + } + + entity.setPos(this.getX(), this.getY() + 0.699999988079071D, this.getZ()); +- return ((ServerLevel) this.level()).addWithUUID(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY); // CraftBukkit +- }).orElse(true); // CraftBukkit ++ boolean addedToWorld = ((ServerLevel) this.level()).addWithUUID(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY); // CraftBukkit ++ return addedToWorld ? entity : null; ++ }).orElse(null); // CraftBukkit // Paper - true -> null + } + +- return true; // CraftBukkit ++ return null; // Paper - return null + } ++ // Paper end + + @Override + public abstract boolean isSpectator(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 52222f15399f2e1828f93b0f69e34157e3abff23..e6b16e3db7a197c7af0c4fdb782749c3c3f37538 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -516,6 +516,32 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + this.getHandle().getCooldowns().addCooldown(CraftItemType.bukkitToMinecraft(material), ticks); + } + ++ // Paper start ++ @Override ++ public org.bukkit.entity.Entity releaseLeftShoulderEntity() { ++ if (!getHandle().getShoulderEntityLeft().isEmpty()) { ++ Entity entity = getHandle().releaseLeftShoulderEntity(); ++ if (entity != null) { ++ return entity.getBukkitEntity(); ++ } ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public org.bukkit.entity.Entity releaseRightShoulderEntity() { ++ if (!getHandle().getShoulderEntityRight().isEmpty()) { ++ Entity entity = getHandle().releaseRightShoulderEntity(); ++ if (entity != null) { ++ return entity.getBukkitEntity(); ++ } ++ } ++ ++ return null; ++ } ++ // Paper end ++ + @Override + public boolean discoverRecipe(NamespacedKey recipe) { + return this.discoverRecipes(Arrays.asList(recipe)) != 0; diff --git a/patches/server/0138-Profile-Lookup-Events.patch b/patches/server/0137-Profile-Lookup-Events.patch similarity index 100% rename from patches/server/0138-Profile-Lookup-Events.patch rename to patches/server/0137-Profile-Lookup-Events.patch diff --git a/patches/server/0137-Shoulder-Entities-Release-API.patch b/patches/server/0137-Shoulder-Entities-Release-API.patch deleted file mode 100644 index 1f43846ac35f..000000000000 --- a/patches/server/0137-Shoulder-Entities-Release-API.patch +++ /dev/null @@ -1,97 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 17 Jun 2017 15:18:30 -0400 -Subject: [PATCH] Shoulder Entities Release API - - -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 a79e0b2d4ba706de0989ef68c87bb29fc1bf08ab..2d3c2b2b98b75a0815771a3f9c3bf244091a50b7 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1947,20 +1947,45 @@ public abstract class Player extends LivingEntity { - - } - -+ // Paper start -+ public Entity releaseLeftShoulderEntity() { -+ Entity entity = this.respawnEntityOnShoulder0(this.getShoulderEntityLeft()); -+ if (entity != null) { -+ this.setShoulderEntityLeft(new CompoundTag()); -+ } -+ return entity; -+ } -+ -+ public Entity releaseRightShoulderEntity() { -+ Entity entity = this.respawnEntityOnShoulder0(this.getShoulderEntityRight()); -+ if (entity != null) { -+ this.setShoulderEntityRight(new CompoundTag()); -+ } -+ return entity; -+ } -+ // Paper - maintain old signature -+ - private boolean respawnEntityOnShoulder(CompoundTag nbttagcompound) { // CraftBukkit void->boolean -- if (!this.level().isClientSide && !nbttagcompound.isEmpty()) { -+ return this.respawnEntityOnShoulder0(nbttagcompound) != null; -+ } -+ -+ // Paper - return entity -+ private Entity respawnEntityOnShoulder0(CompoundTag nbttagcompound) { // CraftBukkit void->boolean -+ if (!this.level().isClientSide && nbttagcompound != null && !nbttagcompound.isEmpty()) { - return EntityType.create(nbttagcompound, this.level()).map((entity) -> { // CraftBukkit - if (entity instanceof TamableAnimal) { - ((TamableAnimal) entity).setOwnerUUID(this.uuid); - } - - entity.setPos(this.getX(), this.getY() + 0.699999988079071D, this.getZ()); -- return ((ServerLevel) this.level()).addWithUUID(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY); // CraftBukkit -- }).orElse(true); // CraftBukkit -+ boolean addedToWorld = ((ServerLevel) this.level()).addWithUUID(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY); // CraftBukkit -+ return addedToWorld ? entity : null; -+ }).orElse(null); // CraftBukkit // Paper - true -> null - } - -- return true; // CraftBukkit -+ return null; // Paper - return null - } -+ // Paper end - - @Override - public abstract boolean isSpectator(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 52222f15399f2e1828f93b0f69e34157e3abff23..e6b16e3db7a197c7af0c4fdb782749c3c3f37538 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -516,6 +516,32 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - this.getHandle().getCooldowns().addCooldown(CraftItemType.bukkitToMinecraft(material), ticks); - } - -+ // Paper start -+ @Override -+ public org.bukkit.entity.Entity releaseLeftShoulderEntity() { -+ if (!getHandle().getShoulderEntityLeft().isEmpty()) { -+ Entity entity = getHandle().releaseLeftShoulderEntity(); -+ if (entity != null) { -+ return entity.getBukkitEntity(); -+ } -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public org.bukkit.entity.Entity releaseRightShoulderEntity() { -+ if (!getHandle().getShoulderEntityRight().isEmpty()) { -+ Entity entity = getHandle().releaseRightShoulderEntity(); -+ if (entity != null) { -+ return entity.getBukkitEntity(); -+ } -+ } -+ -+ return null; -+ } -+ // Paper end -+ - @Override - public boolean discoverRecipe(NamespacedKey recipe) { - return this.discoverRecipes(Arrays.asList(recipe)) != 0; diff --git a/patches/server/0139-Block-player-logins-during-server-shutdown.patch b/patches/server/0138-Block-player-logins-during-server-shutdown.patch similarity index 100% rename from patches/server/0139-Block-player-logins-during-server-shutdown.patch rename to patches/server/0138-Block-player-logins-during-server-shutdown.patch diff --git a/patches/server/0139-Entity-fromMobSpawner.patch b/patches/server/0139-Entity-fromMobSpawner.patch new file mode 100644 index 000000000000..9b5e7349f689 --- /dev/null +++ b/patches/server/0139-Entity-fromMobSpawner.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 18 Jun 2017 18:17:05 -0500 +Subject: [PATCH] Entity#fromMobSpawner() + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 0a31747076225d1221dce554135cde704c76eec4..6fbde57320a58600f8c4b9ce598fa93bd2772e8b 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -394,6 +394,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + public void inactiveTick() { } + // Spigot end + protected int numCollisions = 0; // Paper - Cap entity collisions ++ public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one + // Paper start - Entity origin API + @javax.annotation.Nullable + private org.bukkit.util.Vector origin; +@@ -2149,6 +2150,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + nbttagcompound.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ())); + } ++ // Save entity's from mob spawner status ++ if (spawnedViaMobSpawner) { ++ nbttagcompound.putBoolean("Paper.FromMobSpawner", true); ++ } + // Paper end + return nbttagcompound; + } catch (Throwable throwable) { +@@ -2289,6 +2294,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + this.originWorld = originWorld; + origin = new org.bukkit.util.Vector(originTag.getDouble(0), originTag.getDouble(1), originTag.getDouble(2)); + } ++ ++ spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status + // Paper end + + } catch (Throwable throwable) { +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index a46293dafeb73f9206b92a2850df18a6a5f688b4..487e4211d6486d2b3052c931c27cee9729e7841b 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -167,6 +167,7 @@ public abstract class BaseSpawner { + // Spigot End + } + ++ entity.spawnedViaMobSpawner = true; // Paper + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) { + continue; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 74937603e7b8308fd314d650d9d966e8abd2c725..deb355b2188a49f818aaddad6c4ce60de94428e0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1012,5 +1012,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + //noinspection ConstantConditions + return originVector.toLocation(world); + } ++ ++ @Override ++ public boolean fromMobSpawner() { ++ return getHandle().spawnedViaMobSpawner; ++ } + // Paper end + } diff --git a/patches/server/0140-Entity-fromMobSpawner.patch b/patches/server/0140-Entity-fromMobSpawner.patch deleted file mode 100644 index 7fdfeefda9ef..000000000000 --- a/patches/server/0140-Entity-fromMobSpawner.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sun, 18 Jun 2017 18:17:05 -0500 -Subject: [PATCH] Entity#fromMobSpawner() - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 05cb13a5636f82d48bf0bd8b9d27abdd80a8df71..d96fc02a8b84dbcfc17496ca476a5dee8821b785 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -394,6 +394,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - public void inactiveTick() { } - // Spigot end - protected int numCollisions = 0; // Paper - Cap entity collisions -+ public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one - // Paper start - Entity origin API - @javax.annotation.Nullable - private org.bukkit.util.Vector origin; -@@ -2150,6 +2151,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - nbttagcompound.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ())); - } -+ // Save entity's from mob spawner status -+ if (spawnedViaMobSpawner) { -+ nbttagcompound.putBoolean("Paper.FromMobSpawner", true); -+ } - // Paper end - return nbttagcompound; - } catch (Throwable throwable) { -@@ -2290,6 +2295,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - this.originWorld = originWorld; - origin = new org.bukkit.util.Vector(originTag.getDouble(0), originTag.getDouble(1), originTag.getDouble(2)); - } -+ -+ spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status - // Paper end - - } catch (Throwable throwable) { -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index a46293dafeb73f9206b92a2850df18a6a5f688b4..487e4211d6486d2b3052c931c27cee9729e7841b 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -167,6 +167,7 @@ public abstract class BaseSpawner { - // Spigot End - } - -+ entity.spawnedViaMobSpawner = true; // Paper - // CraftBukkit start - if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) { - continue; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 74937603e7b8308fd314d650d9d966e8abd2c725..deb355b2188a49f818aaddad6c4ce60de94428e0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1012,5 +1012,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - //noinspection ConstantConditions - return originVector.toLocation(world); - } -+ -+ @Override -+ public boolean fromMobSpawner() { -+ return getHandle().spawnedViaMobSpawner; -+ } - // Paper end - } diff --git a/patches/server/0141-Improve-the-Saddle-API-for-Horses.patch b/patches/server/0140-Improve-the-Saddle-API-for-Horses.patch similarity index 100% rename from patches/server/0141-Improve-the-Saddle-API-for-Horses.patch rename to patches/server/0140-Improve-the-Saddle-API-for-Horses.patch diff --git a/patches/server/0141-ensureServerConversions-API.patch b/patches/server/0141-ensureServerConversions-API.patch new file mode 100644 index 000000000000..d448c021b944 --- /dev/null +++ b/patches/server/0141-ensureServerConversions-API.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 4 May 2016 22:43:12 -0400 +Subject: [PATCH] ensureServerConversions API + +This will take a Bukkit ItemStack and run it through any conversions a server process would perform on it, +to ensure it meets latest minecraft expectations. + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 70511628eefc28163d07f50f18d9cc55dd93d68b..9d34a7cd8361fd65d30537d4498c8e2a03d93bb1 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -230,7 +230,7 @@ public final class ItemStack { + + // Called to run this stack through the data converter to handle older storage methods and serialized items + public void convertStack(int version) { +- if (0 < version && version < CraftMagicNumbers.INSTANCE.getDataVersion()) { ++ if (0 < version && version < CraftMagicNumbers.INSTANCE.getDataVersion() && MinecraftServer.getServer() != null) { // Paper - skip conversion if the server doesn't exist (for tests) + CompoundTag savedStack = new CompoundTag(); + this.save(savedStack); + savedStack = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic(NbtOps.INSTANCE, savedStack), version, CraftMagicNumbers.INSTANCE.getDataVersion()).getValue(); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index 0875dfe89644f5f54d004488ed980092b31c6416..89bc6cb62123bc17d579b578c4d1d2e5f8c40837 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -505,4 +505,11 @@ public final class CraftItemFactory implements ItemFactory { + return io.papermc.paper.adventure.PaperAdventure.asAdventure(CraftItemStack.asNMSCopy(itemStack).getDisplayName()); + } + // Paper end - Adventure ++ ++ // Paper start - ensure server conversions API ++ @Override ++ public ItemStack ensureServerConversions(ItemStack item) { ++ return CraftItemStack.asCraftMirror(CraftItemStack.asNMSCopy(item)); ++ } ++ // Paper end - ensure server conversions API + } diff --git a/patches/server/0143-Implement-getI18NDisplayName.patch b/patches/server/0142-Implement-getI18NDisplayName.patch similarity index 100% rename from patches/server/0143-Implement-getI18NDisplayName.patch rename to patches/server/0142-Implement-getI18NDisplayName.patch diff --git a/patches/server/0142-ensureServerConversions-API.patch b/patches/server/0142-ensureServerConversions-API.patch deleted file mode 100644 index 976464eb5418..000000000000 --- a/patches/server/0142-ensureServerConversions-API.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 4 May 2016 22:43:12 -0400 -Subject: [PATCH] ensureServerConversions API - -This will take a Bukkit ItemStack and run it through any conversions a server process would perform on it, -to ensure it meets latest minecraft expectations. - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index b0ea04fc0bac640f7076100e44c16c03b86b2a0e..7f85121b6830ebf480c5ca7b42d3c835911de836 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -229,7 +229,7 @@ public final class ItemStack { - - // Called to run this stack through the data converter to handle older storage methods and serialized items - public void convertStack(int version) { -- if (0 < version && version < CraftMagicNumbers.INSTANCE.getDataVersion()) { -+ if (0 < version && version < CraftMagicNumbers.INSTANCE.getDataVersion() && MinecraftServer.getServer() != null) { // Paper - skip conversion if the server doesn't exist (for tests) - CompoundTag savedStack = new CompoundTag(); - this.save(savedStack); - savedStack = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic(NbtOps.INSTANCE, savedStack), version, CraftMagicNumbers.INSTANCE.getDataVersion()).getValue(); -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -index 0875dfe89644f5f54d004488ed980092b31c6416..89bc6cb62123bc17d579b578c4d1d2e5f8c40837 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -@@ -505,4 +505,11 @@ public final class CraftItemFactory implements ItemFactory { - return io.papermc.paper.adventure.PaperAdventure.asAdventure(CraftItemStack.asNMSCopy(itemStack).getDisplayName()); - } - // Paper end - Adventure -+ -+ // Paper start - ensure server conversions API -+ @Override -+ public ItemStack ensureServerConversions(ItemStack item) { -+ return CraftItemStack.asCraftMirror(CraftItemStack.asNMSCopy(item)); -+ } -+ // Paper end - ensure server conversions API - } diff --git a/patches/server/0144-ProfileWhitelistVerifyEvent.patch b/patches/server/0143-ProfileWhitelistVerifyEvent.patch similarity index 100% rename from patches/server/0144-ProfileWhitelistVerifyEvent.patch rename to patches/server/0143-ProfileWhitelistVerifyEvent.patch diff --git a/patches/server/0145-Fix-this-stupid-bullshit.patch b/patches/server/0144-Fix-this-stupid-bullshit.patch similarity index 100% rename from patches/server/0145-Fix-this-stupid-bullshit.patch rename to patches/server/0144-Fix-this-stupid-bullshit.patch diff --git a/patches/server/0145-LivingEntity-setKiller.patch b/patches/server/0145-LivingEntity-setKiller.patch new file mode 100644 index 000000000000..ece70c37de8f --- /dev/null +++ b/patches/server/0145-LivingEntity-setKiller.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 31 Jul 2017 01:49:48 -0500 +Subject: [PATCH] LivingEntity#setKiller + +== AT == +public net.minecraft.world.entity.LivingEntity lastHurtByPlayerTime + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 4636e1ebf11025a551d398a6594b1506748a8390..82773a05783b731e2f7bd00c8ec68090d15a7b66 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -390,6 +390,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return this.getHandle().lastHurtByPlayer == null ? null : (Player) this.getHandle().lastHurtByPlayer.getBukkitEntity(); + } + ++ // Paper start ++ @Override ++ public void setKiller(Player killer) { ++ net.minecraft.server.level.ServerPlayer entityPlayer = killer == null ? null : ((CraftPlayer) killer).getHandle(); ++ getHandle().lastHurtByPlayer = entityPlayer; ++ getHandle().lastHurtByMob = entityPlayer; ++ getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : 100; // 100 value taken from EntityLiving#damageEntity ++ } ++ // Paper end ++ + @Override + public boolean addPotionEffect(PotionEffect effect) { + return this.addPotionEffect(effect, false); diff --git a/patches/server/0146-LivingEntity-setKiller.patch b/patches/server/0146-LivingEntity-setKiller.patch deleted file mode 100644 index 1e88027161c5..000000000000 --- a/patches/server/0146-LivingEntity-setKiller.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Mon, 31 Jul 2017 01:49:48 -0500 -Subject: [PATCH] LivingEntity#setKiller - -== AT == -public net.minecraft.world.entity.LivingEntity lastHurtByPlayerTime - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index c5cc6beca76884fdf8eb7bacb619b4d5dedbd31a..f09064a6721481c202449ff12ba1d54385e24043 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -377,6 +377,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return this.getHandle().lastHurtByPlayer == null ? null : (Player) this.getHandle().lastHurtByPlayer.getBukkitEntity(); - } - -+ // Paper start -+ @Override -+ public void setKiller(Player killer) { -+ net.minecraft.server.level.ServerPlayer entityPlayer = killer == null ? null : ((CraftPlayer) killer).getHandle(); -+ getHandle().lastHurtByPlayer = entityPlayer; -+ getHandle().lastHurtByMob = entityPlayer; -+ getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : 100; // 100 value taken from EntityLiving#damageEntity -+ } -+ // Paper end -+ - @Override - public boolean addPotionEffect(PotionEffect effect) { - return this.addPotionEffect(effect, false); diff --git a/patches/server/0147-Ocelot-despawns-should-honor-nametags-and-leash.patch b/patches/server/0146-Ocelot-despawns-should-honor-nametags-and-leash.patch similarity index 100% rename from patches/server/0147-Ocelot-despawns-should-honor-nametags-and-leash.patch rename to patches/server/0146-Ocelot-despawns-should-honor-nametags-and-leash.patch diff --git a/patches/server/0148-Reset-spawner-timer-when-spawner-event-is-cancelled.patch b/patches/server/0147-Reset-spawner-timer-when-spawner-event-is-cancelled.patch similarity index 100% rename from patches/server/0148-Reset-spawner-timer-when-spawner-event-is-cancelled.patch rename to patches/server/0147-Reset-spawner-timer-when-spawner-event-is-cancelled.patch diff --git a/patches/server/0149-Allow-specifying-a-custom-authentication-servers-dow.patch b/patches/server/0148-Allow-specifying-a-custom-authentication-servers-dow.patch similarity index 100% rename from patches/server/0149-Allow-specifying-a-custom-authentication-servers-dow.patch rename to patches/server/0148-Allow-specifying-a-custom-authentication-servers-dow.patch diff --git a/patches/server/0150-Handle-plugin-prefixes-using-Log4J-configuration.patch b/patches/server/0149-Handle-plugin-prefixes-using-Log4J-configuration.patch similarity index 100% rename from patches/server/0150-Handle-plugin-prefixes-using-Log4J-configuration.patch rename to patches/server/0149-Handle-plugin-prefixes-using-Log4J-configuration.patch diff --git a/patches/server/0151-Improve-Log4J-Configuration-Plugin-Loggers.patch b/patches/server/0150-Improve-Log4J-Configuration-Plugin-Loggers.patch similarity index 100% rename from patches/server/0151-Improve-Log4J-Configuration-Plugin-Loggers.patch rename to patches/server/0150-Improve-Log4J-Configuration-Plugin-Loggers.patch diff --git a/patches/server/0151-Add-PlayerJumpEvent.patch b/patches/server/0151-Add-PlayerJumpEvent.patch new file mode 100644 index 000000000000..e24d0d58ee75 --- /dev/null +++ b/patches/server/0151-Add-PlayerJumpEvent.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Thu, 28 Sep 2017 17:21:44 -0400 +Subject: [PATCH] Add PlayerJumpEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 12cfc3ed36d6eebd477dfd9058b2852e0a0d98eb..3c4431123c39256fdf704111d350c1005e4d9ef9 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1218,7 +1218,34 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + boolean flag = d7 > 0.0D; + + if (this.player.onGround() && !packet.isOnGround() && flag) { +- this.player.jumpFromGround(); ++ // Paper start - Add PlayerJumpEvent ++ Player player = this.getCraftPlayer(); ++ Location from = new Location(player.getWorld(), lastPosX, lastPosY, lastPosZ, lastYaw, lastPitch); // Get the Players previous Event location. ++ Location to = player.getLocation().clone(); // Start off the To location as the Players current location. ++ ++ // If the packet contains movement information then we update the To location with the correct XYZ. ++ if (packet.hasPos) { ++ to.setX(packet.x); ++ to.setY(packet.y); ++ to.setZ(packet.z); ++ } ++ ++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch. ++ if (packet.hasRot) { ++ to.setYaw(packet.yRot); ++ to.setPitch(packet.xRot); ++ } ++ ++ com.destroystokyo.paper.event.player.PlayerJumpEvent event = new com.destroystokyo.paper.event.player.PlayerJumpEvent(player, from, to); ++ ++ if (event.callEvent()) { ++ this.player.jumpFromGround(); ++ } else { ++ from = event.getFrom(); ++ this.internalTeleport(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch(), Collections.emptySet()); ++ return; ++ } ++ // Paper end - Add PlayerJumpEvent + } + + boolean flag1 = this.player.verticalCollisionBelow; diff --git a/patches/server/0152-Add-PlayerJumpEvent.patch b/patches/server/0152-Add-PlayerJumpEvent.patch deleted file mode 100644 index d305d17f3581..000000000000 --- a/patches/server/0152-Add-PlayerJumpEvent.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Thu, 28 Sep 2017 17:21:44 -0400 -Subject: [PATCH] Add PlayerJumpEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 2f34ff72f8932a8cac9af48003dfa505f19f07d0..ba2628f22907b386fe5638220e5f10e39bc1f80b 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1218,7 +1218,34 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - boolean flag = d7 > 0.0D; - - if (this.player.onGround() && !packet.isOnGround() && flag) { -- this.player.jumpFromGround(); -+ // Paper start - Add PlayerJumpEvent -+ Player player = this.getCraftPlayer(); -+ Location from = new Location(player.getWorld(), lastPosX, lastPosY, lastPosZ, lastYaw, lastPitch); // Get the Players previous Event location. -+ Location to = player.getLocation().clone(); // Start off the To location as the Players current location. -+ -+ // If the packet contains movement information then we update the To location with the correct XYZ. -+ if (packet.hasPos) { -+ to.setX(packet.x); -+ to.setY(packet.y); -+ to.setZ(packet.z); -+ } -+ -+ // If the packet contains look information then we update the To location with the correct Yaw & Pitch. -+ if (packet.hasRot) { -+ to.setYaw(packet.yRot); -+ to.setPitch(packet.xRot); -+ } -+ -+ com.destroystokyo.paper.event.player.PlayerJumpEvent event = new com.destroystokyo.paper.event.player.PlayerJumpEvent(player, from, to); -+ -+ if (event.callEvent()) { -+ this.player.jumpFromGround(); -+ } else { -+ from = event.getFrom(); -+ this.internalTeleport(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch(), Collections.emptySet()); -+ return; -+ } -+ // Paper end - Add PlayerJumpEvent - } - - boolean flag1 = this.player.verticalCollisionBelow; diff --git a/patches/server/0153-handle-ServerboundKeepAlivePacket-async.patch b/patches/server/0152-handle-ServerboundKeepAlivePacket-async.patch similarity index 100% rename from patches/server/0153-handle-ServerboundKeepAlivePacket-async.patch rename to patches/server/0152-handle-ServerboundKeepAlivePacket-async.patch diff --git a/patches/server/0153-Expose-client-protocol-version-and-virtual-host.patch b/patches/server/0153-Expose-client-protocol-version-and-virtual-host.patch new file mode 100644 index 000000000000..349a9ccc59fa --- /dev/null +++ b/patches/server/0153-Expose-client-protocol-version-and-virtual-host.patch @@ -0,0 +1,116 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Tue, 10 Oct 2017 18:45:20 +0200 +Subject: [PATCH] Expose client protocol version and virtual host + + +diff --git a/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java b/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a5a7624f1f372a26b982836cd31cff15e2589e9b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java +@@ -0,0 +1,49 @@ ++package com.destroystokyo.paper.network; ++ ++import java.net.InetSocketAddress; ++ ++import javax.annotation.Nullable; ++import net.minecraft.network.Connection; ++ ++public class PaperNetworkClient implements NetworkClient { ++ ++ private final Connection networkManager; ++ ++ PaperNetworkClient(Connection networkManager) { ++ this.networkManager = networkManager; ++ } ++ ++ @Override ++ public InetSocketAddress getAddress() { ++ return (InetSocketAddress) this.networkManager.getRemoteAddress(); ++ } ++ ++ @Override ++ public int getProtocolVersion() { ++ return this.networkManager.protocolVersion; ++ } ++ ++ @Nullable ++ @Override ++ public InetSocketAddress getVirtualHost() { ++ return this.networkManager.virtualHost; ++ } ++ ++ public static InetSocketAddress prepareVirtualHost(String host, int port) { ++ int len = host.length(); ++ ++ // FML appends a marker to the host to recognize FML clients (\0FML\0) ++ int pos = host.indexOf('\0'); ++ if (pos >= 0) { ++ len = pos; ++ } ++ ++ // When clients connect with a SRV record, their host contains a trailing '.' ++ if (len > 0 && host.charAt(len - 1) == '.') { ++ len--; ++ } ++ ++ return InetSocketAddress.createUnresolved(host.substring(0, len), port); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 5b267514504497de3faa7ffa490a179200d9415c..faac9ba36d83f537fe62e177c15ae237059cc5cc 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -111,6 +111,10 @@ public class Connection extends SimpleChannelInboundHandler> { + @Nullable + BandwidthDebugMonitor bandwidthDebugMonitor; + public String hostname = ""; // CraftBukkit - add field ++ // Paper start - NetworkClient implementation ++ public int protocolVersion; ++ public java.net.InetSocketAddress virtualHost; ++ // Paper end + + // Paper start - add utility methods + public final net.minecraft.server.level.ServerPlayer getPlayer() { +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index ce51bde4db395ff30c7d75e3badd68d6395fa40f..3dfec4462d85f3223071b4e78465587db6185f0d 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -165,6 +165,10 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + throw new UnsupportedOperationException("Invalid intention " + packet.intention()); + } + ++ // Paper start - NetworkClient implementation ++ this.connection.protocolVersion = packet.protocolVersion(); ++ this.connection.virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(packet.hostName(), packet.port()); ++ // Paper end + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 3d3927503dd7f157b59853c570ace002b7e07c25..901a1c98024eb81d2fa3e7ca13add63864a9c5ef 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -253,6 +253,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + } + ++ // Paper start - Implement NetworkClient ++ @Override ++ public int getProtocolVersion() { ++ if (getHandle().connection == null) return -1; ++ return getHandle().connection.connection.protocolVersion; ++ } ++ ++ @Override ++ public InetSocketAddress getVirtualHost() { ++ if (getHandle().connection == null) return null; ++ return getHandle().connection.connection.virtualHost; ++ } ++ // Paper end ++ + @Override + public double getEyeHeight(boolean ignorePose) { + if (ignorePose) { diff --git a/patches/server/0154-Expose-client-protocol-version-and-virtual-host.patch b/patches/server/0154-Expose-client-protocol-version-and-virtual-host.patch deleted file mode 100644 index dd5398aac0b4..000000000000 --- a/patches/server/0154-Expose-client-protocol-version-and-virtual-host.patch +++ /dev/null @@ -1,116 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Minecrell -Date: Tue, 10 Oct 2017 18:45:20 +0200 -Subject: [PATCH] Expose client protocol version and virtual host - - -diff --git a/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java b/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a5a7624f1f372a26b982836cd31cff15e2589e9b ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/network/PaperNetworkClient.java -@@ -0,0 +1,49 @@ -+package com.destroystokyo.paper.network; -+ -+import java.net.InetSocketAddress; -+ -+import javax.annotation.Nullable; -+import net.minecraft.network.Connection; -+ -+public class PaperNetworkClient implements NetworkClient { -+ -+ private final Connection networkManager; -+ -+ PaperNetworkClient(Connection networkManager) { -+ this.networkManager = networkManager; -+ } -+ -+ @Override -+ public InetSocketAddress getAddress() { -+ return (InetSocketAddress) this.networkManager.getRemoteAddress(); -+ } -+ -+ @Override -+ public int getProtocolVersion() { -+ return this.networkManager.protocolVersion; -+ } -+ -+ @Nullable -+ @Override -+ public InetSocketAddress getVirtualHost() { -+ return this.networkManager.virtualHost; -+ } -+ -+ public static InetSocketAddress prepareVirtualHost(String host, int port) { -+ int len = host.length(); -+ -+ // FML appends a marker to the host to recognize FML clients (\0FML\0) -+ int pos = host.indexOf('\0'); -+ if (pos >= 0) { -+ len = pos; -+ } -+ -+ // When clients connect with a SRV record, their host contains a trailing '.' -+ if (len > 0 && host.charAt(len - 1) == '.') { -+ len--; -+ } -+ -+ return InetSocketAddress.createUnresolved(host.substring(0, len), port); -+ } -+ -+} -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 5b267514504497de3faa7ffa490a179200d9415c..faac9ba36d83f537fe62e177c15ae237059cc5cc 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -111,6 +111,10 @@ public class Connection extends SimpleChannelInboundHandler> { - @Nullable - BandwidthDebugMonitor bandwidthDebugMonitor; - public String hostname = ""; // CraftBukkit - add field -+ // Paper start - NetworkClient implementation -+ public int protocolVersion; -+ public java.net.InetSocketAddress virtualHost; -+ // Paper end - - // Paper start - add utility methods - public final net.minecraft.server.level.ServerPlayer getPlayer() { -diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -index ce51bde4db395ff30c7d75e3badd68d6395fa40f..3dfec4462d85f3223071b4e78465587db6185f0d 100644 ---- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -@@ -165,6 +165,10 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - throw new UnsupportedOperationException("Invalid intention " + packet.intention()); - } - -+ // Paper start - NetworkClient implementation -+ this.connection.protocolVersion = packet.protocolVersion(); -+ this.connection.virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(packet.hostName(), packet.port()); -+ // Paper end - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 53d33699b8389a46d27e897b1c24fb250ae025f8..c3eb39a30a70d7e289caba49749cf1f9c812be90 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -247,6 +247,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - } - -+ // Paper start - Implement NetworkClient -+ @Override -+ public int getProtocolVersion() { -+ if (getHandle().connection == null) return -1; -+ return getHandle().connection.connection.protocolVersion; -+ } -+ -+ @Override -+ public InetSocketAddress getVirtualHost() { -+ if (getHandle().connection == null) return null; -+ return getHandle().connection.connection.virtualHost; -+ } -+ // Paper end -+ - @Override - public double getEyeHeight(boolean ignorePose) { - if (ignorePose) { diff --git a/patches/server/0155-revert-serverside-behavior-of-keepalives.patch b/patches/server/0154-revert-serverside-behavior-of-keepalives.patch similarity index 100% rename from patches/server/0155-revert-serverside-behavior-of-keepalives.patch rename to patches/server/0154-revert-serverside-behavior-of-keepalives.patch diff --git a/patches/server/0155-Send-attack-SoundEffects-only-to-players-who-can-see.patch b/patches/server/0155-Send-attack-SoundEffects-only-to-players-who-can-see.patch new file mode 100644 index 000000000000..089df28228b0 --- /dev/null +++ b/patches/server/0155-Send-attack-SoundEffects-only-to-players-who-can-see.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Tue, 31 Oct 2017 03:26:18 +0100 +Subject: [PATCH] Send attack SoundEffects only to players who can see the + attacker + + +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 cd5a4e91ee8da2a2a5d34ecc9a24394e131e79d0..056b816c2959b44c24fca04e14e8d764b4a06b0f 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1226,7 +1226,7 @@ public abstract class Player extends LivingEntity { + int i = b0 + EnchantmentHelper.getKnockbackBonus(this); + + if (this.isSprinting() && flag) { +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + ++i; + flag1 = true; + } +@@ -1301,7 +1301,7 @@ public abstract class Player extends LivingEntity { + } + } + +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + this.sweepAttack(); + } + +@@ -1329,15 +1329,15 @@ public abstract class Player extends LivingEntity { + } + + if (flag2) { +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + this.crit(target); + } + + if (!flag2 && !flag3) { + if (flag) { +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + } else { +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + } + } + +@@ -1389,7 +1389,7 @@ public abstract class Player extends LivingEntity { + + this.causeFoodExhaustion(this.level().spigotConfig.combatExhaustion, EntityExhaustionEvent.ExhaustionReason.ATTACK); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value + } else { +- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F); ++ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + if (flag4) { + target.clearFire(); + } +@@ -1783,6 +1783,14 @@ public abstract class Player extends LivingEntity { + public int getXpNeededForNextLevel() { + return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); + } ++ // Paper start - send while respecting visibility ++ private static void sendSoundEffect(Player fromEntity, double x, double y, double z, SoundEvent soundEffect, SoundSource soundCategory, float volume, float pitch) { ++ fromEntity.level().playSound(fromEntity, x, y, z, soundEffect, soundCategory, volume, pitch); // This will not send the effect to the entity himself ++ if (fromEntity instanceof ServerPlayer) { ++ ((ServerPlayer) fromEntity).connection.send(new net.minecraft.network.protocol.game.ClientboundSoundPacket(net.minecraft.core.registries.BuiltInRegistries.SOUND_EVENT.wrapAsHolder(soundEffect), soundCategory, x, y, z, volume, pitch, fromEntity.random.nextLong())); ++ } ++ } ++ // Paper end - send while respecting visibility + + // CraftBukkit start + public void causeFoodExhaustion(float exhaustion) { diff --git a/patches/server/0156-Add-PlayerArmorChangeEvent.patch b/patches/server/0156-Add-PlayerArmorChangeEvent.patch new file mode 100644 index 000000000000..58c26b93425b --- /dev/null +++ b/patches/server/0156-Add-PlayerArmorChangeEvent.patch @@ -0,0 +1,104 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: pkt77 +Date: Fri, 10 Nov 2017 23:46:34 -0500 +Subject: [PATCH] Add PlayerArmorChangeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index ec862d3a70b9d9f71873a71c1f1d143bc95799ae..1203387260e5e2727ffb682882da85b8c89c1f4c 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3060,6 +3060,13 @@ public abstract class LivingEntity extends Entity implements Attackable { + ItemStack itemstack1 = this.getItemBySlot(enumitemslot); + + if (this.equipmentHasChanged(itemstack, itemstack1)) { ++ // Paper start - PlayerArmorChangeEvent ++ if (this instanceof ServerPlayer && enumitemslot.getType() == EquipmentSlot.Type.ARMOR) { ++ final org.bukkit.inventory.ItemStack oldItem = CraftItemStack.asBukkitCopy(itemstack); ++ final org.bukkit.inventory.ItemStack newItem = CraftItemStack.asBukkitCopy(itemstack1); ++ new com.destroystokyo.paper.event.player.PlayerArmorChangeEvent((Player) this.getBukkitEntity(), com.destroystokyo.paper.event.player.PlayerArmorChangeEvent.SlotType.valueOf(enumitemslot.name()), oldItem, newItem).callEvent(); ++ } ++ // Paper end - PlayerArmorChangeEvent + if (map == null) { + map = Maps.newEnumMap(EquipmentSlot.class); + } +diff --git a/src/test/java/io/papermc/paper/inventory/item/ArmorSlotTypeMaterialTest.java b/src/test/java/io/papermc/paper/inventory/item/ArmorSlotTypeMaterialTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a706dcbabfe31bf54a552965fa5feb8be34213bf +--- /dev/null ++++ b/src/test/java/io/papermc/paper/inventory/item/ArmorSlotTypeMaterialTest.java +@@ -0,0 +1,74 @@ ++package io.papermc.paper.inventory.item; ++ ++import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.stream.Stream; ++import net.minecraft.world.entity.EquipmentSlot; ++import net.minecraft.world.item.Equipable; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.item.ItemStack; ++import org.bukkit.Material; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++import static org.junit.jupiter.api.Assertions.assertNotNull; ++import static org.junit.jupiter.api.Assertions.assertTrue; ++ ++public class ArmorSlotTypeMaterialTest extends AbstractTestingBase { ++ ++ public static Stream slotTypeParams() { ++ final List parameters = new ArrayList<>(); ++ for (final PlayerArmorChangeEvent.SlotType slotType : PlayerArmorChangeEvent.SlotType.values()) { ++ for (final Material item : slotType.getTypes()) { ++ parameters.add(new Object[]{ slotType, item }); ++ } ++ } ++ return parameters.stream(); ++ } ++ ++ @ParameterizedTest(name = "{argumentsWithNames}") ++ @MethodSource("slotTypeParams") ++ public void testSlotType(PlayerArmorChangeEvent.SlotType slotType, Material item) { ++ final Item nmsItem = CraftMagicNumbers.getItem(item); ++ final Equipable equipable = Equipable.get(new ItemStack(nmsItem)); ++ assertNotNull(equipable, item + " isn't equipable"); ++ final EquipmentSlot slot = switch (slotType) { ++ case HEAD -> EquipmentSlot.HEAD; ++ case CHEST -> EquipmentSlot.CHEST; ++ case LEGS -> EquipmentSlot.LEGS; ++ case FEET -> EquipmentSlot.FEET; ++ }; ++ assertEquals(equipable.getEquipmentSlot(), slot, item + " isn't set to the right slot"); ++ } ++ ++ public static Stream equipableParams() { ++ final List parameters = new ArrayList<>(); ++ for (final Item item : net.minecraft.core.registries.BuiltInRegistries.ITEM) { ++ final Equipable equipable = Equipable.get(new ItemStack(item)); ++ if (equipable != null) { ++ parameters.add(new Object[]{equipable, item}); ++ } ++ } ++ return parameters.stream(); ++ } ++ ++ @ParameterizedTest(name = "{argumentsWithNames}") ++ @MethodSource("equipableParams") ++ public void testEquipable(Equipable equipable, Item item) { ++ final EquipmentSlot equipmentSlot = equipable.getEquipmentSlot(); ++ PlayerArmorChangeEvent.SlotType slotType = switch (equipmentSlot) { ++ case HEAD -> PlayerArmorChangeEvent.SlotType.HEAD; ++ case CHEST -> PlayerArmorChangeEvent.SlotType.CHEST; ++ case LEGS -> PlayerArmorChangeEvent.SlotType.LEGS; ++ case FEET -> PlayerArmorChangeEvent.SlotType.FEET; ++ default -> null; ++ }; ++ if (slotType != null) { ++ assertTrue(slotType.getTypes().contains(CraftMagicNumbers.getMaterial(item)), "SlotType " + slotType + " doesn't include " + item); ++ } ++ } ++} diff --git a/patches/server/0156-Send-attack-SoundEffects-only-to-players-who-can-see.patch b/patches/server/0156-Send-attack-SoundEffects-only-to-players-who-can-see.patch deleted file mode 100644 index 0239bdb21f50..000000000000 --- a/patches/server/0156-Send-attack-SoundEffects-only-to-players-who-can-see.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Brokkonaut -Date: Tue, 31 Oct 2017 03:26:18 +0100 -Subject: [PATCH] Send attack SoundEffects only to players who can see the - attacker - - -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 2d3c2b2b98b75a0815771a3f9c3bf244091a50b7..5fa0e0c5293eda8a368c1801a9b5255807bf078c 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1225,7 +1225,7 @@ public abstract class Player extends LivingEntity { - int i = b0 + EnchantmentHelper.getKnockbackBonus(this); - - if (this.isSprinting() && flag) { -- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F); -+ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_KNOCKBACK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility - ++i; - flag1 = true; - } -@@ -1300,7 +1300,7 @@ public abstract class Player extends LivingEntity { - } - } - -- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F); -+ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_SWEEP, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility - this.sweepAttack(); - } - -@@ -1328,15 +1328,15 @@ public abstract class Player extends LivingEntity { - } - - if (flag2) { -- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F); -+ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_CRIT, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility - this.crit(target); - } - - if (!flag2 && !flag3) { - if (flag) { -- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F); -+ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_STRONG, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility - } else { -- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F); -+ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_WEAK, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility - } - } - -@@ -1388,7 +1388,7 @@ public abstract class Player extends LivingEntity { - - this.causeFoodExhaustion(this.level().spigotConfig.combatExhaustion, EntityExhaustionEvent.ExhaustionReason.ATTACK); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value - } else { -- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F); -+ sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility - if (flag4) { - target.clearFire(); - } -@@ -1782,6 +1782,14 @@ public abstract class Player extends LivingEntity { - public int getXpNeededForNextLevel() { - return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); - } -+ // Paper start - send while respecting visibility -+ private static void sendSoundEffect(Player fromEntity, double x, double y, double z, SoundEvent soundEffect, SoundSource soundCategory, float volume, float pitch) { -+ fromEntity.level().playSound(fromEntity, x, y, z, soundEffect, soundCategory, volume, pitch); // This will not send the effect to the entity himself -+ if (fromEntity instanceof ServerPlayer) { -+ ((ServerPlayer) fromEntity).connection.send(new net.minecraft.network.protocol.game.ClientboundSoundPacket(net.minecraft.core.registries.BuiltInRegistries.SOUND_EVENT.wrapAsHolder(soundEffect), soundCategory, x, y, z, volume, pitch, fromEntity.random.nextLong())); -+ } -+ } -+ // Paper end - send while respecting visibility - - // CraftBukkit start - public void causeFoodExhaustion(float exhaustion) { diff --git a/patches/server/0157-Add-PlayerArmorChangeEvent.patch b/patches/server/0157-Add-PlayerArmorChangeEvent.patch deleted file mode 100644 index 3788e0b1e23c..000000000000 --- a/patches/server/0157-Add-PlayerArmorChangeEvent.patch +++ /dev/null @@ -1,104 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: pkt77 -Date: Fri, 10 Nov 2017 23:46:34 -0500 -Subject: [PATCH] Add PlayerArmorChangeEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index a799b64e9d655e7eba509e9930e623a88bbb0389..882bb83a552be415711c8df8118e91981fc63e46 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3047,6 +3047,13 @@ public abstract class LivingEntity extends Entity implements Attackable { - ItemStack itemstack1 = this.getItemBySlot(enumitemslot); - - if (this.equipmentHasChanged(itemstack, itemstack1)) { -+ // Paper start - PlayerArmorChangeEvent -+ if (this instanceof ServerPlayer && enumitemslot.getType() == EquipmentSlot.Type.ARMOR) { -+ final org.bukkit.inventory.ItemStack oldItem = CraftItemStack.asBukkitCopy(itemstack); -+ final org.bukkit.inventory.ItemStack newItem = CraftItemStack.asBukkitCopy(itemstack1); -+ new com.destroystokyo.paper.event.player.PlayerArmorChangeEvent((Player) this.getBukkitEntity(), com.destroystokyo.paper.event.player.PlayerArmorChangeEvent.SlotType.valueOf(enumitemslot.name()), oldItem, newItem).callEvent(); -+ } -+ // Paper end - PlayerArmorChangeEvent - if (map == null) { - map = Maps.newEnumMap(EquipmentSlot.class); - } -diff --git a/src/test/java/io/papermc/paper/inventory/item/ArmorSlotTypeMaterialTest.java b/src/test/java/io/papermc/paper/inventory/item/ArmorSlotTypeMaterialTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a706dcbabfe31bf54a552965fa5feb8be34213bf ---- /dev/null -+++ b/src/test/java/io/papermc/paper/inventory/item/ArmorSlotTypeMaterialTest.java -@@ -0,0 +1,74 @@ -+package io.papermc.paper.inventory.item; -+ -+import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.stream.Stream; -+import net.minecraft.world.entity.EquipmentSlot; -+import net.minecraft.world.item.Equipable; -+import net.minecraft.world.item.Item; -+import net.minecraft.world.item.ItemStack; -+import org.bukkit.Material; -+import org.bukkit.craftbukkit.util.CraftMagicNumbers; -+import org.bukkit.support.AbstractTestingBase; -+import org.junit.jupiter.params.ParameterizedTest; -+import org.junit.jupiter.params.provider.MethodSource; -+ -+import static org.junit.jupiter.api.Assertions.assertEquals; -+import static org.junit.jupiter.api.Assertions.assertNotNull; -+import static org.junit.jupiter.api.Assertions.assertTrue; -+ -+public class ArmorSlotTypeMaterialTest extends AbstractTestingBase { -+ -+ public static Stream slotTypeParams() { -+ final List parameters = new ArrayList<>(); -+ for (final PlayerArmorChangeEvent.SlotType slotType : PlayerArmorChangeEvent.SlotType.values()) { -+ for (final Material item : slotType.getTypes()) { -+ parameters.add(new Object[]{ slotType, item }); -+ } -+ } -+ return parameters.stream(); -+ } -+ -+ @ParameterizedTest(name = "{argumentsWithNames}") -+ @MethodSource("slotTypeParams") -+ public void testSlotType(PlayerArmorChangeEvent.SlotType slotType, Material item) { -+ final Item nmsItem = CraftMagicNumbers.getItem(item); -+ final Equipable equipable = Equipable.get(new ItemStack(nmsItem)); -+ assertNotNull(equipable, item + " isn't equipable"); -+ final EquipmentSlot slot = switch (slotType) { -+ case HEAD -> EquipmentSlot.HEAD; -+ case CHEST -> EquipmentSlot.CHEST; -+ case LEGS -> EquipmentSlot.LEGS; -+ case FEET -> EquipmentSlot.FEET; -+ }; -+ assertEquals(equipable.getEquipmentSlot(), slot, item + " isn't set to the right slot"); -+ } -+ -+ public static Stream equipableParams() { -+ final List parameters = new ArrayList<>(); -+ for (final Item item : net.minecraft.core.registries.BuiltInRegistries.ITEM) { -+ final Equipable equipable = Equipable.get(new ItemStack(item)); -+ if (equipable != null) { -+ parameters.add(new Object[]{equipable, item}); -+ } -+ } -+ return parameters.stream(); -+ } -+ -+ @ParameterizedTest(name = "{argumentsWithNames}") -+ @MethodSource("equipableParams") -+ public void testEquipable(Equipable equipable, Item item) { -+ final EquipmentSlot equipmentSlot = equipable.getEquipmentSlot(); -+ PlayerArmorChangeEvent.SlotType slotType = switch (equipmentSlot) { -+ case HEAD -> PlayerArmorChangeEvent.SlotType.HEAD; -+ case CHEST -> PlayerArmorChangeEvent.SlotType.CHEST; -+ case LEGS -> PlayerArmorChangeEvent.SlotType.LEGS; -+ case FEET -> PlayerArmorChangeEvent.SlotType.FEET; -+ default -> null; -+ }; -+ if (slotType != null) { -+ assertTrue(slotType.getTypes().contains(CraftMagicNumbers.getMaterial(item)), "SlotType " + slotType + " doesn't include " + item); -+ } -+ } -+} diff --git a/patches/server/0158-Prevent-logins-from-being-processed-when-the-player-.patch b/patches/server/0157-Prevent-logins-from-being-processed-when-the-player-.patch similarity index 100% rename from patches/server/0158-Prevent-logins-from-being-processed-when-the-player-.patch rename to patches/server/0157-Prevent-logins-from-being-processed-when-the-player-.patch diff --git a/patches/server/0159-Fix-MC-117075-Block-entity-unload-lag-spike.patch b/patches/server/0158-Fix-MC-117075-Block-entity-unload-lag-spike.patch similarity index 100% rename from patches/server/0159-Fix-MC-117075-Block-entity-unload-lag-spike.patch rename to patches/server/0158-Fix-MC-117075-Block-entity-unload-lag-spike.patch diff --git a/patches/server/0160-use-CB-BlockState-implementations-for-captured-block.patch b/patches/server/0159-use-CB-BlockState-implementations-for-captured-block.patch similarity index 100% rename from patches/server/0160-use-CB-BlockState-implementations-for-captured-block.patch rename to patches/server/0159-use-CB-BlockState-implementations-for-captured-block.patch diff --git a/patches/server/0161-API-to-get-a-BlockState-without-a-snapshot.patch b/patches/server/0160-API-to-get-a-BlockState-without-a-snapshot.patch similarity index 100% rename from patches/server/0161-API-to-get-a-BlockState-without-a-snapshot.patch rename to patches/server/0160-API-to-get-a-BlockState-without-a-snapshot.patch diff --git a/patches/server/0161-AsyncTabCompleteEvent.patch b/patches/server/0161-AsyncTabCompleteEvent.patch new file mode 100644 index 000000000000..665a68f81eb4 --- /dev/null +++ b/patches/server/0161-AsyncTabCompleteEvent.patch @@ -0,0 +1,176 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Sun, 26 Nov 2017 13:19:58 -0500 +Subject: [PATCH] AsyncTabCompleteEvent + +Let plugins be able to control tab completion of commands and chat async. + +This will be useful for frameworks like ACF so we can define async safe completion handlers, +and avoid going to main for tab completions. + +Especially useful if you need to query a database in order to obtain the results for tab +completion, such as offline players. + +Also adds isCommand and getLocation to the sync TabCompleteEvent + +Co-authored-by: Aikar + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 3c4431123c39256fdf704111d350c1005e4d9ef9..f60d754cb617df3c5ab8654eba66016c1cc04617 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -688,21 +688,58 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + } + ++ // Paper start - AsyncTabCompleteEvent ++ private static final java.util.concurrent.ExecutorService TAB_COMPLETE_EXECUTOR = java.util.concurrent.Executors.newFixedThreadPool(4, ++ new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Tab Complete Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); ++ // Paper end - AsyncTabCompleteEvent + @Override + public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { +- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async + // CraftBukkit start + if (this.chatSpamTickCount.addAndGet(1) > 500 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { + this.disconnect(Component.translatable("disconnect.spam")); + return; + } + // CraftBukkit end ++ // Paper start - AsyncTabCompleteEvent ++ TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet)); ++ } ++ ++ private void handleCustomCommandSuggestions0(final ServerboundCommandSuggestionPacket packet) { + StringReader stringreader = new StringReader(packet.getCommand()); + + if (stringreader.canRead() && stringreader.peek() == '/') { + stringreader.skip(); + } + ++ final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(this.getCraftPlayer(), packet.getCommand(), true, null); ++ event.callEvent(); ++ final List completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.completions(); ++ // If the event isn't handled, we can assume that we have no completions, and so we'll ask the server ++ if (!event.isHandled()) { ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ // This needs to be on main ++ this.server.scheduleOnMain(() -> this.sendServerSuggestions(packet, stringreader)); ++ } else if (!completions.isEmpty()) { ++ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringreader.getTotalLength()); ++ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder = builder0.createOffset(builder0.getInput().lastIndexOf(' ') + 1); ++ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { ++ final Integer intSuggestion = com.google.common.primitives.Ints.tryParse(completion.suggestion()); ++ if (intSuggestion != null) { ++ builder.suggest(intSuggestion, PaperAdventure.asVanilla(completion.tooltip())); ++ } else { ++ builder.suggest(completion.suggestion(), PaperAdventure.asVanilla(completion.tooltip())); ++ } ++ } ++ this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), builder.buildFuture().join())); ++ } ++ } ++ ++ private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringreader) { ++ // Paper end - AsyncTabCompleteEvent + ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); + + this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index b6e6b4213e893fac64855cc4b4e6eeb4246050d1..28a8b687958a1c1396a5a8b13a04fb371ac9f3ab 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2239,7 +2239,7 @@ public final class CraftServer implements Server { + offers = this.tabCompleteChat(player, message); + } + +- TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers); ++ TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers, message.startsWith("/") || forceCommand, pos != null ? io.papermc.paper.util.MCUtil.toLocation(((CraftWorld) player.getWorld()).getHandle(), BlockPos.containing(pos)) : null); // Paper - AsyncTabCompleteEvent + this.getPluginManager().callEvent(tabEvent); + + return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions(); +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +index d24acf28f5ed023acc550bcf877e4b9800ec8c9f..8f82041f0482df22a6a9ea38d50d56228131775d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +@@ -28,6 +28,61 @@ public class ConsoleCommandCompleter implements Completer { + public void complete(LineReader reader, ParsedLine line, List candidates) { + final CraftServer server = this.server.server; + final String buffer = line.line(); ++ // Async Tab Complete ++ final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event = ++ new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(server.getConsoleSender(), buffer, true, null); ++ event.callEvent(); ++ final List completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.completions(); ++ ++ if (event.isCancelled() || event.isHandled()) { ++ // Still fire sync event with the provided completions, if someone is listening ++ if (!event.isCancelled() && TabCompleteEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ List finalCompletions = new java.util.ArrayList<>(completions); ++ Waitable> syncCompletions = new Waitable>() { ++ @Override ++ protected List evaluate() { ++ org.bukkit.event.server.TabCompleteEvent syncEvent = new org.bukkit.event.server.TabCompleteEvent(server.getConsoleSender(), buffer, ++ finalCompletions.stream() ++ .map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::suggestion) ++ .collect(java.util.stream.Collectors.toList())); ++ return syncEvent.callEvent() ? syncEvent.getCompletions() : com.google.common.collect.ImmutableList.of(); ++ } ++ }; ++ server.getServer().processQueue.add(syncCompletions); ++ try { ++ final List legacyCompletions = syncCompletions.get(); ++ completions.removeIf(it -> !legacyCompletions.contains(it.suggestion())); // remove any suggestions that were removed ++ // add any new suggestions ++ for (final String completion : legacyCompletions) { ++ if (notNewSuggestion(completions, completion)) { ++ continue; ++ } ++ completions.add(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion(completion)); ++ } ++ } catch (InterruptedException | ExecutionException e1) { ++ e1.printStackTrace(); ++ } ++ } ++ ++ if (!completions.isEmpty()) { ++ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { ++ if (completion.suggestion().isEmpty()) { ++ continue; ++ } ++ candidates.add(new Candidate( ++ completion.suggestion(), ++ completion.suggestion(), ++ null, ++ io.papermc.paper.adventure.PaperAdventure.PLAIN.serializeOr(completion.tooltip(), null), ++ null, ++ null, ++ false ++ )); ++ } ++ } ++ return; ++ } ++ + // Paper end + Waitable> waitable = new Waitable>() { + @Override +@@ -73,4 +128,15 @@ public class ConsoleCommandCompleter implements Completer { + Thread.currentThread().interrupt(); + } + } ++ ++ // Paper start ++ private boolean notNewSuggestion(final List completions, final String completion) { ++ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion it : completions) { ++ if (it.suggestion().equals(completion)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ // Paper end + } diff --git a/patches/server/0162-AsyncTabCompleteEvent.patch b/patches/server/0162-AsyncTabCompleteEvent.patch deleted file mode 100644 index 721226052fbb..000000000000 --- a/patches/server/0162-AsyncTabCompleteEvent.patch +++ /dev/null @@ -1,176 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sun, 26 Nov 2017 13:19:58 -0500 -Subject: [PATCH] AsyncTabCompleteEvent - -Let plugins be able to control tab completion of commands and chat async. - -This will be useful for frameworks like ACF so we can define async safe completion handlers, -and avoid going to main for tab completions. - -Especially useful if you need to query a database in order to obtain the results for tab -completion, such as offline players. - -Also adds isCommand and getLocation to the sync TabCompleteEvent - -Co-authored-by: Aikar - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index a30eb2a9e201face352be5c6f74e690d8e5c0478..0497393afd8f60ac17148dde2a394b4b6795c7d8 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -688,21 +688,58 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - } - -+ // Paper start - AsyncTabCompleteEvent -+ private static final java.util.concurrent.ExecutorService TAB_COMPLETE_EXECUTOR = java.util.concurrent.Executors.newFixedThreadPool(4, -+ new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Tab Complete Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); -+ // Paper end - AsyncTabCompleteEvent - @Override - public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { -- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); -+ // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async - // CraftBukkit start - if (this.chatSpamTickCount.addAndGet(1) > 500 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { - this.disconnect(Component.translatable("disconnect.spam")); - return; - } - // CraftBukkit end -+ // Paper start - AsyncTabCompleteEvent -+ TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet)); -+ } -+ -+ private void handleCustomCommandSuggestions0(final ServerboundCommandSuggestionPacket packet) { - StringReader stringreader = new StringReader(packet.getCommand()); - - if (stringreader.canRead() && stringreader.peek() == '/') { - stringreader.skip(); - } - -+ final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(this.getCraftPlayer(), packet.getCommand(), true, null); -+ event.callEvent(); -+ final List completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.completions(); -+ // If the event isn't handled, we can assume that we have no completions, and so we'll ask the server -+ if (!event.isHandled()) { -+ if (event.isCancelled()) { -+ return; -+ } -+ -+ // This needs to be on main -+ this.server.scheduleOnMain(() -> this.sendServerSuggestions(packet, stringreader)); -+ } else if (!completions.isEmpty()) { -+ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringreader.getTotalLength()); -+ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder = builder0.createOffset(builder0.getInput().lastIndexOf(' ') + 1); -+ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { -+ final Integer intSuggestion = com.google.common.primitives.Ints.tryParse(completion.suggestion()); -+ if (intSuggestion != null) { -+ builder.suggest(intSuggestion, PaperAdventure.asVanilla(completion.tooltip())); -+ } else { -+ builder.suggest(completion.suggestion(), PaperAdventure.asVanilla(completion.tooltip())); -+ } -+ } -+ this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), builder.buildFuture().join())); -+ } -+ } -+ -+ private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringreader) { -+ // Paper end - AsyncTabCompleteEvent - ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); - - this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 46acd80641b2ecafd9f364d7ad9b477b6b2f0289..22e61d492c935688b6d1d63d13ba8313abd9e1b4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2227,7 +2227,7 @@ public final class CraftServer implements Server { - offers = this.tabCompleteChat(player, message); - } - -- TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers); -+ TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers, message.startsWith("/") || forceCommand, pos != null ? io.papermc.paper.util.MCUtil.toLocation(((CraftWorld) player.getWorld()).getHandle(), BlockPos.containing(pos)) : null); // Paper - AsyncTabCompleteEvent - this.getPluginManager().callEvent(tabEvent); - - return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions(); -diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -index d24acf28f5ed023acc550bcf877e4b9800ec8c9f..8f82041f0482df22a6a9ea38d50d56228131775d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -@@ -28,6 +28,61 @@ public class ConsoleCommandCompleter implements Completer { - public void complete(LineReader reader, ParsedLine line, List candidates) { - final CraftServer server = this.server.server; - final String buffer = line.line(); -+ // Async Tab Complete -+ final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event = -+ new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(server.getConsoleSender(), buffer, true, null); -+ event.callEvent(); -+ final List completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.completions(); -+ -+ if (event.isCancelled() || event.isHandled()) { -+ // Still fire sync event with the provided completions, if someone is listening -+ if (!event.isCancelled() && TabCompleteEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ List finalCompletions = new java.util.ArrayList<>(completions); -+ Waitable> syncCompletions = new Waitable>() { -+ @Override -+ protected List evaluate() { -+ org.bukkit.event.server.TabCompleteEvent syncEvent = new org.bukkit.event.server.TabCompleteEvent(server.getConsoleSender(), buffer, -+ finalCompletions.stream() -+ .map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::suggestion) -+ .collect(java.util.stream.Collectors.toList())); -+ return syncEvent.callEvent() ? syncEvent.getCompletions() : com.google.common.collect.ImmutableList.of(); -+ } -+ }; -+ server.getServer().processQueue.add(syncCompletions); -+ try { -+ final List legacyCompletions = syncCompletions.get(); -+ completions.removeIf(it -> !legacyCompletions.contains(it.suggestion())); // remove any suggestions that were removed -+ // add any new suggestions -+ for (final String completion : legacyCompletions) { -+ if (notNewSuggestion(completions, completion)) { -+ continue; -+ } -+ completions.add(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion(completion)); -+ } -+ } catch (InterruptedException | ExecutionException e1) { -+ e1.printStackTrace(); -+ } -+ } -+ -+ if (!completions.isEmpty()) { -+ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { -+ if (completion.suggestion().isEmpty()) { -+ continue; -+ } -+ candidates.add(new Candidate( -+ completion.suggestion(), -+ completion.suggestion(), -+ null, -+ io.papermc.paper.adventure.PaperAdventure.PLAIN.serializeOr(completion.tooltip(), null), -+ null, -+ null, -+ false -+ )); -+ } -+ } -+ return; -+ } -+ - // Paper end - Waitable> waitable = new Waitable>() { - @Override -@@ -73,4 +128,15 @@ public class ConsoleCommandCompleter implements Completer { - Thread.currentThread().interrupt(); - } - } -+ -+ // Paper start -+ private boolean notNewSuggestion(final List completions, final String completion) { -+ for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion it : completions) { -+ if (it.suggestion().equals(completion)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ // Paper end - } diff --git a/patches/server/0163-PlayerPickupExperienceEvent.patch b/patches/server/0162-PlayerPickupExperienceEvent.patch similarity index 100% rename from patches/server/0163-PlayerPickupExperienceEvent.patch rename to patches/server/0162-PlayerPickupExperienceEvent.patch diff --git a/patches/server/0163-Ability-to-apply-mending-to-XP-API.patch b/patches/server/0163-Ability-to-apply-mending-to-XP-API.patch new file mode 100644 index 000000000000..fb84ea101451 --- /dev/null +++ b/patches/server/0163-Ability-to-apply-mending-to-XP-API.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 20 Dec 2017 17:36:49 -0500 +Subject: [PATCH] Ability to apply mending to XP API + +This allows plugins that give players the ability to apply the experience +points to the Item Mending formula, which will repair an item instead +of giving the player experience points. + +Both an API To standalone mend, and apply mending logic to .giveExp has been added. + +== AT == +public net.minecraft.world.entity.ExperienceOrb durabilityToXp(I)I +public net.minecraft.world.entity.ExperienceOrb xpToDurability(I)I + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 901a1c98024eb81d2fa3e7ca13add63864a9c5ef..1444b633a2b49ce492a8a3c2a9c4befb8c7618fd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1546,7 +1546,37 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + @Override +- public void giveExp(int exp) { ++ // Paper start ++ public int applyMending(int amount) { ++ ServerPlayer handle = this.getHandle(); ++ // Logic copied from EntityExperienceOrb and remapped to unobfuscated methods/properties ++ final var stackEntry = net.minecraft.world.item.enchantment.EnchantmentHelper ++ .getRandomItemWith(net.minecraft.world.item.enchantment.Enchantments.MENDING, handle); ++ final net.minecraft.world.item.ItemStack itemstack = stackEntry != null ? stackEntry.getValue() : net.minecraft.world.item.ItemStack.EMPTY; ++ if (!itemstack.isEmpty() && itemstack.getItem().canBeDepleted()) { ++ net.minecraft.world.entity.ExperienceOrb orb = net.minecraft.world.entity.EntityType.EXPERIENCE_ORB.create(handle.level()); ++ orb.value = amount; ++ orb.spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM; ++ orb.setPosRaw(handle.getX(), handle.getY(), handle.getZ()); ++ ++ int i = Math.min(orb.xpToDurability(amount), itemstack.getDamageValue()); ++ org.bukkit.event.player.PlayerItemMendEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemMendEvent(handle, orb, itemstack, stackEntry.getKey(), i); ++ i = event.getRepairAmount(); ++ orb.discard(); ++ if (!event.isCancelled()) { ++ amount -= orb.durabilityToXp(i); ++ itemstack.setDamageValue(itemstack.getDamageValue() - i); ++ } ++ } ++ return amount; ++ } ++ ++ @Override ++ public void giveExp(int exp, boolean applyMending) { ++ if (applyMending) { ++ exp = this.applyMending(exp); ++ } ++ // Paper end + this.getHandle().giveExperiencePoints(exp); + } + diff --git a/patches/server/0164-Ability-to-apply-mending-to-XP-API.patch b/patches/server/0164-Ability-to-apply-mending-to-XP-API.patch deleted file mode 100644 index 3dac4b6da6f5..000000000000 --- a/patches/server/0164-Ability-to-apply-mending-to-XP-API.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 20 Dec 2017 17:36:49 -0500 -Subject: [PATCH] Ability to apply mending to XP API - -This allows plugins that give players the ability to apply the experience -points to the Item Mending formula, which will repair an item instead -of giving the player experience points. - -Both an API To standalone mend, and apply mending logic to .giveExp has been added. - -== AT == -public net.minecraft.world.entity.ExperienceOrb durabilityToXp(I)I -public net.minecraft.world.entity.ExperienceOrb xpToDurability(I)I - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index cfa861cc63ccd96ef9c71e65d6796567154afd5f..af58b8c5a9faf78bd4daace9bd52a012fa91e079 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1516,7 +1516,37 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - @Override -- public void giveExp(int exp) { -+ // Paper start -+ public int applyMending(int amount) { -+ ServerPlayer handle = this.getHandle(); -+ // Logic copied from EntityExperienceOrb and remapped to unobfuscated methods/properties -+ final var stackEntry = net.minecraft.world.item.enchantment.EnchantmentHelper -+ .getRandomItemWith(net.minecraft.world.item.enchantment.Enchantments.MENDING, handle); -+ final net.minecraft.world.item.ItemStack itemstack = stackEntry != null ? stackEntry.getValue() : net.minecraft.world.item.ItemStack.EMPTY; -+ if (!itemstack.isEmpty() && itemstack.getItem().canBeDepleted()) { -+ net.minecraft.world.entity.ExperienceOrb orb = net.minecraft.world.entity.EntityType.EXPERIENCE_ORB.create(handle.level()); -+ orb.value = amount; -+ orb.spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM; -+ orb.setPosRaw(handle.getX(), handle.getY(), handle.getZ()); -+ -+ int i = Math.min(orb.xpToDurability(amount), itemstack.getDamageValue()); -+ org.bukkit.event.player.PlayerItemMendEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemMendEvent(handle, orb, itemstack, stackEntry.getKey(), i); -+ i = event.getRepairAmount(); -+ orb.discard(); -+ if (!event.isCancelled()) { -+ amount -= orb.durabilityToXp(i); -+ itemstack.setDamageValue(itemstack.getDamageValue() - i); -+ } -+ } -+ return amount; -+ } -+ -+ @Override -+ public void giveExp(int exp, boolean applyMending) { -+ if (applyMending) { -+ exp = this.applyMending(exp); -+ } -+ // Paper end - this.getHandle().giveExperiencePoints(exp); - } - diff --git a/patches/server/0164-PlayerNaturallySpawnCreaturesEvent.patch b/patches/server/0164-PlayerNaturallySpawnCreaturesEvent.patch new file mode 100644 index 000000000000..86df19fb45c8 --- /dev/null +++ b/patches/server/0164-PlayerNaturallySpawnCreaturesEvent.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 14 Jan 2018 17:36:02 -0500 +Subject: [PATCH] PlayerNaturallySpawnCreaturesEvent + +This event can be used for when you want to exclude a certain player +from triggering monster spawns on a server. + +Also a highly more effecient way to blanket block spawns in a world + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 7245b93a4935aece23567fda0474104686485395..de328a93abcf23d3ff265557a7d8bad5be56287c 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1203,7 +1203,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + chunkRange = (chunkRange > this.level.spigotConfig.viewDistance) ? (byte) this.level.spigotConfig.viewDistance : chunkRange; + chunkRange = (chunkRange > 8) ? 8 : chunkRange; + +- double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; ++ final int finalChunkRange = chunkRange; // Paper for lambda below ++ //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event ++ double blockRange = 16384.0D; // Paper + // Spigot end + if (!this.distanceManager.hasPlayersNearby(chunkcoordintpair.toLong())) { + return false; +@@ -1218,6 +1220,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + entityplayer = (ServerPlayer) iterator.next(); ++ // Paper start - PlayerNaturallySpawnCreaturesEvent ++ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; ++ blockRange = 16384.0D; ++ if (reducedRange) { ++ event = entityplayer.playerNaturallySpawnedEvent; ++ if (event == null || event.isCancelled()) return false; ++ blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); ++ } ++ // Paper end - PlayerNaturallySpawnCreaturesEvent + } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot + + return true; +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index e0ae7274da59ff043cd423d282ed8db0382561d4..369e4bf5ff52cb774f1acaf760b8bd276a0745f5 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -568,6 +568,15 @@ public class ServerChunkCache extends ChunkSource { + boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit + + Util.shuffle(list, this.level.random); ++ // Paper start - PlayerNaturallySpawnCreaturesEvent ++ int chunkRange = level.spigotConfig.mobSpawnRange; ++ chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; ++ chunkRange = Math.min(chunkRange, 8); ++ for (ServerPlayer entityPlayer : this.level.players()) { ++ entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); ++ entityPlayer.playerNaturallySpawnedEvent.callEvent(); ++ } ++ // Paper end - PlayerNaturallySpawnCreaturesEvent + int l = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); + boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit + Iterator iterator1 = list.iterator(); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index f1f090b7d9ac21f6430756ddb02b734f7dda0c7c..19cd69fc911bb9b95257b1fdc19645bbdfe05de3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -265,6 +265,7 @@ public class ServerPlayer extends Player { + // CraftBukkit end + public boolean isRealPlayer; // Paper + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper ++ public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent + + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { + super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); diff --git a/patches/server/0166-Add-setPlayerProfile-API-for-Skulls.patch b/patches/server/0165-Add-setPlayerProfile-API-for-Skulls.patch similarity index 100% rename from patches/server/0166-Add-setPlayerProfile-API-for-Skulls.patch rename to patches/server/0165-Add-setPlayerProfile-API-for-Skulls.patch diff --git a/patches/server/0165-PlayerNaturallySpawnCreaturesEvent.patch b/patches/server/0165-PlayerNaturallySpawnCreaturesEvent.patch deleted file mode 100644 index 9caa2d8f5fe2..000000000000 --- a/patches/server/0165-PlayerNaturallySpawnCreaturesEvent.patch +++ /dev/null @@ -1,73 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 14 Jan 2018 17:36:02 -0500 -Subject: [PATCH] PlayerNaturallySpawnCreaturesEvent - -This event can be used for when you want to exclude a certain player -from triggering monster spawns on a server. - -Also a highly more effecient way to blanket block spawns in a world - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index c581ddf24f66a98798e8965adc848638edd889de..987f867def412552b0d7f6cb2cba50af520f1257 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1203,7 +1203,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - chunkRange = (chunkRange > this.level.spigotConfig.viewDistance) ? (byte) this.level.spigotConfig.viewDistance : chunkRange; - chunkRange = (chunkRange > 8) ? 8 : chunkRange; - -- double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; -+ final int finalChunkRange = chunkRange; // Paper for lambda below -+ //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event -+ double blockRange = 16384.0D; // Paper - // Spigot end - if (!this.distanceManager.hasPlayersNearby(chunkcoordintpair.toLong())) { - return false; -@@ -1218,6 +1220,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - entityplayer = (ServerPlayer) iterator.next(); -+ // Paper start - PlayerNaturallySpawnCreaturesEvent -+ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; -+ blockRange = 16384.0D; -+ if (reducedRange) { -+ event = entityplayer.playerNaturallySpawnedEvent; -+ if (event == null || event.isCancelled()) return false; -+ blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); -+ } -+ // Paper end - PlayerNaturallySpawnCreaturesEvent - } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot - - return true; -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index e0ae7274da59ff043cd423d282ed8db0382561d4..369e4bf5ff52cb774f1acaf760b8bd276a0745f5 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -568,6 +568,15 @@ public class ServerChunkCache extends ChunkSource { - boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit - - Util.shuffle(list, this.level.random); -+ // Paper start - PlayerNaturallySpawnCreaturesEvent -+ int chunkRange = level.spigotConfig.mobSpawnRange; -+ chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; -+ chunkRange = Math.min(chunkRange, 8); -+ for (ServerPlayer entityPlayer : this.level.players()) { -+ entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); -+ entityPlayer.playerNaturallySpawnedEvent.callEvent(); -+ } -+ // Paper end - PlayerNaturallySpawnCreaturesEvent - int l = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); - boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit - Iterator iterator1 = list.iterator(); -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 5197fc02a080c3f603030d5c4fa59e10f8c0e3b6..d51244c634d0209efb98be965ce7318480220b96 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -265,6 +265,7 @@ public class ServerPlayer extends Player { - // CraftBukkit end - public boolean isRealPlayer; // Paper - public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper -+ public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent - - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { - super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); diff --git a/patches/server/0167-PreCreatureSpawnEvent.patch b/patches/server/0166-PreCreatureSpawnEvent.patch similarity index 100% rename from patches/server/0167-PreCreatureSpawnEvent.patch rename to patches/server/0166-PreCreatureSpawnEvent.patch diff --git a/patches/server/0168-Fill-Profile-Property-Events.patch b/patches/server/0167-Fill-Profile-Property-Events.patch similarity index 100% rename from patches/server/0168-Fill-Profile-Property-Events.patch rename to patches/server/0167-Fill-Profile-Property-Events.patch diff --git a/patches/server/0169-Add-PlayerAdvancementCriterionGrantEvent.patch b/patches/server/0168-Add-PlayerAdvancementCriterionGrantEvent.patch similarity index 100% rename from patches/server/0169-Add-PlayerAdvancementCriterionGrantEvent.patch rename to patches/server/0168-Add-PlayerAdvancementCriterionGrantEvent.patch diff --git a/patches/server/0170-Add-ArmorStand-Item-Meta.patch b/patches/server/0169-Add-ArmorStand-Item-Meta.patch similarity index 100% rename from patches/server/0170-Add-ArmorStand-Item-Meta.patch rename to patches/server/0169-Add-ArmorStand-Item-Meta.patch diff --git a/patches/server/0171-Extend-Player-Interact-cancellation.patch b/patches/server/0170-Extend-Player-Interact-cancellation.patch similarity index 100% rename from patches/server/0171-Extend-Player-Interact-cancellation.patch rename to patches/server/0170-Extend-Player-Interact-cancellation.patch diff --git a/patches/server/0172-Tameable-getOwnerUniqueId-API.patch b/patches/server/0171-Tameable-getOwnerUniqueId-API.patch similarity index 100% rename from patches/server/0172-Tameable-getOwnerUniqueId-API.patch rename to patches/server/0171-Tameable-getOwnerUniqueId-API.patch diff --git a/patches/server/0172-Toggleable-player-crits.patch b/patches/server/0172-Toggleable-player-crits.patch new file mode 100644 index 000000000000..a43d2d76dd1d --- /dev/null +++ b/patches/server/0172-Toggleable-player-crits.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Sat, 10 Mar 2018 00:50:24 +0100 +Subject: [PATCH] Toggleable player crits + + +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 056b816c2959b44c24fca04e14e8d764b4a06b0f..e38f2e9a2ea616ebe5f167583fe339fc7244ccbf 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1233,6 +1233,7 @@ public abstract class Player extends LivingEntity { + + boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround() && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity; + ++ flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits + flag2 = flag2 && !this.isSprinting(); + if (flag2) { + f *= 1.5F; diff --git a/patches/server/0174-Disable-Explicit-Network-Manager-Flushing.patch b/patches/server/0173-Disable-Explicit-Network-Manager-Flushing.patch similarity index 100% rename from patches/server/0174-Disable-Explicit-Network-Manager-Flushing.patch rename to patches/server/0173-Disable-Explicit-Network-Manager-Flushing.patch diff --git a/patches/server/0173-Toggleable-player-crits.patch b/patches/server/0173-Toggleable-player-crits.patch deleted file mode 100644 index e7199ef60c62..000000000000 --- a/patches/server/0173-Toggleable-player-crits.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MiniDigger -Date: Sat, 10 Mar 2018 00:50:24 +0100 -Subject: [PATCH] Toggleable player crits - - -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 5fa0e0c5293eda8a368c1801a9b5255807bf078c..04d02c456d2e3562dcd122cb1951f8de3d808f7f 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1232,6 +1232,7 @@ public abstract class Player extends LivingEntity { - - boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround() && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity; - -+ flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits - flag2 = flag2 && !this.isSprinting(); - if (flag2) { - f *= 1.5F; diff --git a/patches/server/0175-Implement-extended-PaperServerListPingEvent.patch b/patches/server/0174-Implement-extended-PaperServerListPingEvent.patch similarity index 100% rename from patches/server/0175-Implement-extended-PaperServerListPingEvent.patch rename to patches/server/0174-Implement-extended-PaperServerListPingEvent.patch diff --git a/patches/server/0176-Add-more-fields-to-AsyncPreLoginEvent.patch b/patches/server/0175-Add-more-fields-to-AsyncPreLoginEvent.patch similarity index 100% rename from patches/server/0176-Add-more-fields-to-AsyncPreLoginEvent.patch rename to patches/server/0175-Add-more-fields-to-AsyncPreLoginEvent.patch diff --git a/patches/server/0176-Player.setPlayerProfile-API.patch b/patches/server/0176-Player.setPlayerProfile-API.patch new file mode 100644 index 000000000000..17827ed57f99 --- /dev/null +++ b/patches/server/0176-Player.setPlayerProfile-API.patch @@ -0,0 +1,239 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 18 Mar 2018 12:29:48 -0400 +Subject: [PATCH] Player.setPlayerProfile API + +This can be useful for changing name or skins after a player has logged in. + +== AT == +public-f net.minecraft.world.entity.player.Player gameProfile + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f60d754cb617df3c5ab8654eba66016c1cc04617..39af3e197502c1f262fbdd4e06dc0e3fc7537b77 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1467,7 +1467,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.internalTeleport(dest.getX(), dest.getY(), dest.getZ(), dest.getYaw(), dest.getPitch(), Collections.emptySet()); + } + +- private void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { ++ public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { // Paper + // CraftBukkit start + if (Float.isNaN(f)) { + f = 0; +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index b41aa61f80cd4ab8ddd19da397244392f9efde78..e745061f3e6a4541f44ba119106915cdb7023fc6 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -295,11 +295,11 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + + // Paper start - Add more fields to AsyncPlayerPreLoginEvent + final InetAddress rawAddress = ((InetSocketAddress) this.connection.channel.remoteAddress()).getAddress(); +- com.destroystokyo.paper.profile.PlayerProfile profile = org.bukkit.Bukkit.createProfile(uniqueId, playerName); ++ com.destroystokyo.paper.profile.PlayerProfile profile = com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(gameprofile); // Paper - setPlayerProfileAPI + AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, profile, this.connection.hostname); + server.getPluginManager().callEvent(asyncEvent); + profile = asyncEvent.getPlayerProfile(); +- profile.complete(); ++ profile.complete(true); // Paper - setPlayerProfileAPI + gameprofile = com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopy(profile); + playerName = gameprofile.getName(); + uniqueId = gameprofile.getId(); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 7552d1b9e6c286daaa6b094af0fdebc2b300272a..9f8a95c8f46a11f36ff16863922a91a8d81d0bb3 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -862,10 +862,16 @@ public abstract class PlayerList { + } + + public void sendPlayerPermissionLevel(ServerPlayer player) { ++ // Paper start - avoid recalculating permissions if possible ++ this.sendPlayerPermissionLevel(player, true); ++ } ++ ++ public void sendPlayerPermissionLevel(ServerPlayer player, boolean recalculatePermissions) { ++ // Paper end - avoid recalculating permissions if possible + GameProfile gameprofile = player.getGameProfile(); + int i = this.server.getProfilePermissions(gameprofile); + +- this.sendPlayerPermissionLevel(player, i); ++ this.sendPlayerPermissionLevel(player, i, recalculatePermissions); // Paper - avoid recalculating permissions if possible + } + + public void tick() { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +index 954825dcd011716dcd859aa285a8e3cdb6ff5464..34925d6448e0ef1d5bb4b24359f732b67aaa4230 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +@@ -82,8 +82,8 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + } + + @Override +- public PlayerProfile getPlayerProfile() { +- return new CraftPlayerProfile(this.profile); ++ public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() { // Paper ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile(this.profile); // Paper + } + + public Server getServer() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 1444b633a2b49ce492a8a3c2a9c4befb8c7618fd..f33aee99de2007701bd593917a63e05c7fa5e349 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -236,11 +236,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return this.server.getPlayer(this.getUniqueId()) != null; + } + +- @Override +- public PlayerProfile getPlayerProfile() { +- return new CraftPlayerProfile(this.getProfile()); +- } +- + @Override + public InetSocketAddress getAddress() { + if (this.getHandle().connection == null) return null; +@@ -1696,8 +1691,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + private void untrackAndHideEntity(org.bukkit.entity.Entity entity) { + // Remove this entity from the hidden player's EntityTrackerEntry +- ChunkMap tracker = ((ServerLevel) this.getHandle().level()).getChunkSource().chunkMap; ++ // Paper start + Entity other = ((CraftEntity) entity).getHandle(); ++ unregisterEntity(other); ++ ++ server.getPluginManager().callEvent(new PlayerHideEntityEvent(this, entity)); ++ } ++ private void unregisterEntity(Entity other) { ++ // Paper end ++ ChunkMap tracker = ((ServerLevel) this.getHandle().level()).getChunkSource().chunkMap; + ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId()); + if (entry != null) { + entry.removePlayer(this.getHandle()); +@@ -1710,8 +1712,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.getHandle().connection.send(new ClientboundPlayerInfoRemovePacket(List.of(otherPlayer.getUUID()))); + } + } +- +- this.server.getPluginManager().callEvent(new PlayerHideEntityEvent(this, entity)); + } + + void resetAndHideEntity(org.bukkit.entity.Entity entity) { +@@ -1776,12 +1776,25 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + private void trackAndShowEntity(org.bukkit.entity.Entity entity) { ++ // Paper start - uuid override ++ this.trackAndShowEntity(entity, null); ++ } ++ private void trackAndShowEntity(org.bukkit.entity.Entity entity, final @Nullable UUID uuidOverride) { ++ // Paper end + ChunkMap tracker = ((ServerLevel) this.getHandle().level()).getChunkSource().chunkMap; + Entity other = ((CraftEntity) entity).getHandle(); + + if (other instanceof ServerPlayer) { + ServerPlayer otherPlayer = (ServerPlayer) other; ++ // Paper start - uuid override ++ UUID original = null; ++ if (uuidOverride != null) { ++ original = otherPlayer.getUUID(); ++ otherPlayer.setUUID(uuidOverride); ++ } ++ // Paper end + this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer))); ++ if (original != null) otherPlayer.setUUID(original); // Paper - uuid override + } + + ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId()); +@@ -1791,6 +1804,39 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + this.server.getPluginManager().callEvent(new PlayerShowEntityEvent(this, entity)); + } ++ // Paper start ++ @Override ++ public void setPlayerProfile(com.destroystokyo.paper.profile.PlayerProfile profile) { ++ ServerPlayer self = this.getHandle(); ++ GameProfile gameProfile = com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopy(profile); ++ if (!self.sentListPacket) { ++ self.gameProfile = gameProfile; ++ return; ++ } ++ List players = this.server.getServer().getPlayerList().players; ++ // First unregister the player for all players with the OLD game profile ++ for (ServerPlayer player : players) { ++ CraftPlayer bukkitPlayer = player.getBukkitEntity(); ++ if (bukkitPlayer.canSee(this)) { ++ bukkitPlayer.unregisterEntity(self); ++ } ++ } ++ ++ // Set the game profile here, we should have unregistered the entity via iterating all player entities above. ++ self.gameProfile = gameProfile; ++ ++ // Re-register the game profile for all players ++ for (ServerPlayer player : players) { ++ CraftPlayer bukkitPlayer = player.getBukkitEntity(); ++ if (bukkitPlayer.canSee(this)) { ++ bukkitPlayer.trackAndShowEntity(self.getBukkitEntity(), gameProfile.getId()); ++ } ++ } ++ ++ // Refresh misc player things AFTER sending game profile ++ this.refreshPlayer(); ++ } ++ // Paper end + + void resetAndShowEntity(org.bukkit.entity.Entity entity) { + // SPIGOT-7312: Can't show/hide self +@@ -1802,6 +1848,34 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.trackAndShowEntity(entity); + } + } ++ // Paper start ++ public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() { ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile(this).clone(); ++ } ++ ++ private void refreshPlayer() { ++ ServerPlayer handle = this.getHandle(); ++ Location loc = this.getLocation(); ++ ++ ServerGamePacketListenerImpl connection = handle.connection; ++ ++ //Respawn the player then update their position and selected slot ++ ServerLevel worldserver = handle.serverLevel(); ++ connection.send(new net.minecraft.network.protocol.game.ClientboundRespawnPacket(handle.createCommonSpawnInfo(worldserver), net.minecraft.network.protocol.game.ClientboundRespawnPacket.KEEP_ALL_DATA)); ++ handle.onUpdateAbilities(); ++ connection.internalTeleport(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch(), java.util.Collections.emptySet()); ++ net.minecraft.server.players.PlayerList playerList = handle.server.getPlayerList(); ++ playerList.sendPlayerPermissionLevel(handle, false); ++ playerList.sendLevelInfo(handle, worldserver); ++ playerList.sendAllPlayerInfo(handle); ++ ++ // Resend their XP and effects because the respawn packet resets it ++ connection.send(new net.minecraft.network.protocol.game.ClientboundSetExperiencePacket(handle.experienceProgress, handle.totalExperience, handle.experienceLevel)); ++ for (net.minecraft.world.effect.MobEffectInstance mobEffect : handle.getActiveEffects()) { ++ connection.send(new net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket(handle.getId(), mobEffect)); ++ } ++ } ++ // Paper end + + public void onEntityRemove(Entity entity) { + this.invertedVisibilityEntities.remove(entity.getUUID()); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +index 457860fa9babe542347608a3fc11ad5d75e5d135..6a661bbae8bc35a4c3b4bb7e86dd77a7575fdd97 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +@@ -277,6 +277,13 @@ public class Commodore { + return; + } + ++ // Paper start - Rewrite plugins ++ if ((owner.equals("org/bukkit/OfflinePlayer") || owner.equals("org/bukkit/entity/Player")) && name.equals("getPlayerProfile") && desc.equals("()Lorg/bukkit/profile/PlayerProfile;")) { ++ super.visitMethodInsn(opcode, owner, name, "()Lcom/destroystokyo/paper/profile/PlayerProfile;", itf); ++ return; ++ } ++ // Paper end ++ + if (modern) { + if (owner.equals("org/bukkit/Material") || (instantiatedMethodType != null && instantiatedMethodType.getDescriptor().startsWith("(Lorg/bukkit/Material;)"))) { + switch (name) { diff --git a/patches/server/0177-Player.setPlayerProfile-API.patch b/patches/server/0177-Player.setPlayerProfile-API.patch deleted file mode 100644 index 6fa95377f971..000000000000 --- a/patches/server/0177-Player.setPlayerProfile-API.patch +++ /dev/null @@ -1,239 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 18 Mar 2018 12:29:48 -0400 -Subject: [PATCH] Player.setPlayerProfile API - -This can be useful for changing name or skins after a player has logged in. - -== AT == -public-f net.minecraft.world.entity.player.Player gameProfile - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0497393afd8f60ac17148dde2a394b4b6795c7d8..1da44846ad1ed2768fd7ebca4017c4bbc399ba68 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1467,7 +1467,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.internalTeleport(dest.getX(), dest.getY(), dest.getZ(), dest.getYaw(), dest.getPitch(), Collections.emptySet()); - } - -- private void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { -+ public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { // Paper - // CraftBukkit start - if (Float.isNaN(f)) { - f = 0; -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index b41aa61f80cd4ab8ddd19da397244392f9efde78..e745061f3e6a4541f44ba119106915cdb7023fc6 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -295,11 +295,11 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - - // Paper start - Add more fields to AsyncPlayerPreLoginEvent - final InetAddress rawAddress = ((InetSocketAddress) this.connection.channel.remoteAddress()).getAddress(); -- com.destroystokyo.paper.profile.PlayerProfile profile = org.bukkit.Bukkit.createProfile(uniqueId, playerName); -+ com.destroystokyo.paper.profile.PlayerProfile profile = com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(gameprofile); // Paper - setPlayerProfileAPI - AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, profile, this.connection.hostname); - server.getPluginManager().callEvent(asyncEvent); - profile = asyncEvent.getPlayerProfile(); -- profile.complete(); -+ profile.complete(true); // Paper - setPlayerProfileAPI - gameprofile = com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopy(profile); - playerName = gameprofile.getName(); - uniqueId = gameprofile.getId(); -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 7552d1b9e6c286daaa6b094af0fdebc2b300272a..9f8a95c8f46a11f36ff16863922a91a8d81d0bb3 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -862,10 +862,16 @@ public abstract class PlayerList { - } - - public void sendPlayerPermissionLevel(ServerPlayer player) { -+ // Paper start - avoid recalculating permissions if possible -+ this.sendPlayerPermissionLevel(player, true); -+ } -+ -+ public void sendPlayerPermissionLevel(ServerPlayer player, boolean recalculatePermissions) { -+ // Paper end - avoid recalculating permissions if possible - GameProfile gameprofile = player.getGameProfile(); - int i = this.server.getProfilePermissions(gameprofile); - -- this.sendPlayerPermissionLevel(player, i); -+ this.sendPlayerPermissionLevel(player, i, recalculatePermissions); // Paper - avoid recalculating permissions if possible - } - - public void tick() { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -index 954825dcd011716dcd859aa285a8e3cdb6ff5464..34925d6448e0ef1d5bb4b24359f732b67aaa4230 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -@@ -82,8 +82,8 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - } - - @Override -- public PlayerProfile getPlayerProfile() { -- return new CraftPlayerProfile(this.profile); -+ public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() { // Paper -+ return new com.destroystokyo.paper.profile.CraftPlayerProfile(this.profile); // Paper - } - - public Server getServer() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index af58b8c5a9faf78bd4daace9bd52a012fa91e079..d59c2bbb88a4e11136e2aa8fb30f15894560f13f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -230,11 +230,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - return this.server.getPlayer(this.getUniqueId()) != null; - } - -- @Override -- public PlayerProfile getPlayerProfile() { -- return new CraftPlayerProfile(this.getProfile()); -- } -- - @Override - public InetSocketAddress getAddress() { - if (this.getHandle().connection == null) return null; -@@ -1666,8 +1661,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - private void untrackAndHideEntity(org.bukkit.entity.Entity entity) { - // Remove this entity from the hidden player's EntityTrackerEntry -- ChunkMap tracker = ((ServerLevel) this.getHandle().level()).getChunkSource().chunkMap; -+ // Paper start - Entity other = ((CraftEntity) entity).getHandle(); -+ unregisterEntity(other); -+ -+ server.getPluginManager().callEvent(new PlayerHideEntityEvent(this, entity)); -+ } -+ private void unregisterEntity(Entity other) { -+ // Paper end -+ ChunkMap tracker = ((ServerLevel) this.getHandle().level()).getChunkSource().chunkMap; - ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId()); - if (entry != null) { - entry.removePlayer(this.getHandle()); -@@ -1680,8 +1682,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.getHandle().connection.send(new ClientboundPlayerInfoRemovePacket(List.of(otherPlayer.getUUID()))); - } - } -- -- this.server.getPluginManager().callEvent(new PlayerHideEntityEvent(this, entity)); - } - - void resetAndHideEntity(org.bukkit.entity.Entity entity) { -@@ -1746,12 +1746,25 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - private void trackAndShowEntity(org.bukkit.entity.Entity entity) { -+ // Paper start - uuid override -+ this.trackAndShowEntity(entity, null); -+ } -+ private void trackAndShowEntity(org.bukkit.entity.Entity entity, final @Nullable UUID uuidOverride) { -+ // Paper end - ChunkMap tracker = ((ServerLevel) this.getHandle().level()).getChunkSource().chunkMap; - Entity other = ((CraftEntity) entity).getHandle(); - - if (other instanceof ServerPlayer) { - ServerPlayer otherPlayer = (ServerPlayer) other; -+ // Paper start - uuid override -+ UUID original = null; -+ if (uuidOverride != null) { -+ original = otherPlayer.getUUID(); -+ otherPlayer.setUUID(uuidOverride); -+ } -+ // Paper end - this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer))); -+ if (original != null) otherPlayer.setUUID(original); // Paper - uuid override - } - - ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId()); -@@ -1761,6 +1774,39 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - this.server.getPluginManager().callEvent(new PlayerShowEntityEvent(this, entity)); - } -+ // Paper start -+ @Override -+ public void setPlayerProfile(com.destroystokyo.paper.profile.PlayerProfile profile) { -+ ServerPlayer self = this.getHandle(); -+ GameProfile gameProfile = com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopy(profile); -+ if (!self.sentListPacket) { -+ self.gameProfile = gameProfile; -+ return; -+ } -+ List players = this.server.getServer().getPlayerList().players; -+ // First unregister the player for all players with the OLD game profile -+ for (ServerPlayer player : players) { -+ CraftPlayer bukkitPlayer = player.getBukkitEntity(); -+ if (bukkitPlayer.canSee(this)) { -+ bukkitPlayer.unregisterEntity(self); -+ } -+ } -+ -+ // Set the game profile here, we should have unregistered the entity via iterating all player entities above. -+ self.gameProfile = gameProfile; -+ -+ // Re-register the game profile for all players -+ for (ServerPlayer player : players) { -+ CraftPlayer bukkitPlayer = player.getBukkitEntity(); -+ if (bukkitPlayer.canSee(this)) { -+ bukkitPlayer.trackAndShowEntity(self.getBukkitEntity(), gameProfile.getId()); -+ } -+ } -+ -+ // Refresh misc player things AFTER sending game profile -+ this.refreshPlayer(); -+ } -+ // Paper end - - void resetAndShowEntity(org.bukkit.entity.Entity entity) { - // SPIGOT-7312: Can't show/hide self -@@ -1772,6 +1818,34 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.trackAndShowEntity(entity); - } - } -+ // Paper start -+ public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() { -+ return new com.destroystokyo.paper.profile.CraftPlayerProfile(this).clone(); -+ } -+ -+ private void refreshPlayer() { -+ ServerPlayer handle = this.getHandle(); -+ Location loc = this.getLocation(); -+ -+ ServerGamePacketListenerImpl connection = handle.connection; -+ -+ //Respawn the player then update their position and selected slot -+ ServerLevel worldserver = handle.serverLevel(); -+ connection.send(new net.minecraft.network.protocol.game.ClientboundRespawnPacket(handle.createCommonSpawnInfo(worldserver), net.minecraft.network.protocol.game.ClientboundRespawnPacket.KEEP_ALL_DATA)); -+ handle.onUpdateAbilities(); -+ connection.internalTeleport(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch(), java.util.Collections.emptySet()); -+ net.minecraft.server.players.PlayerList playerList = handle.server.getPlayerList(); -+ playerList.sendPlayerPermissionLevel(handle, false); -+ playerList.sendLevelInfo(handle, worldserver); -+ playerList.sendAllPlayerInfo(handle); -+ -+ // Resend their XP and effects because the respawn packet resets it -+ connection.send(new net.minecraft.network.protocol.game.ClientboundSetExperiencePacket(handle.experienceProgress, handle.totalExperience, handle.experienceLevel)); -+ for (net.minecraft.world.effect.MobEffectInstance mobEffect : handle.getActiveEffects()) { -+ connection.send(new net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket(handle.getId(), mobEffect)); -+ } -+ } -+ // Paper end - - public void onEntityRemove(Entity entity) { - this.invertedVisibilityEntities.remove(entity.getUUID()); -diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java -index 457860fa9babe542347608a3fc11ad5d75e5d135..6a661bbae8bc35a4c3b4bb7e86dd77a7575fdd97 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java -@@ -277,6 +277,13 @@ public class Commodore { - return; - } - -+ // Paper start - Rewrite plugins -+ if ((owner.equals("org/bukkit/OfflinePlayer") || owner.equals("org/bukkit/entity/Player")) && name.equals("getPlayerProfile") && desc.equals("()Lorg/bukkit/profile/PlayerProfile;")) { -+ super.visitMethodInsn(opcode, owner, name, "()Lcom/destroystokyo/paper/profile/PlayerProfile;", itf); -+ return; -+ } -+ // Paper end -+ - if (modern) { - if (owner.equals("org/bukkit/Material") || (instantiatedMethodType != null && instantiatedMethodType.getDescriptor().startsWith("(Lorg/bukkit/Material;)"))) { - switch (name) { diff --git a/patches/server/0177-getPlayerUniqueId-API.patch b/patches/server/0177-getPlayerUniqueId-API.patch new file mode 100644 index 000000000000..701b20e981d3 --- /dev/null +++ b/patches/server/0177-getPlayerUniqueId-API.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 22 Mar 2018 01:40:24 -0400 +Subject: [PATCH] getPlayerUniqueId API + +Gets the unique ID of the player currently known as the specified player name +In Offline Mode, will return an Offline UUID + +This is a more performant way to obtain a UUID for a name than loading an OfflinePlayer + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 28a8b687958a1c1396a5a8b13a04fb371ac9f3ab..d25ee3ca530289c0deda45b01196ece98788e628 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1838,6 +1838,25 @@ public final class CraftServer implements Server { + return recipients.size(); + } + ++ // Paper start ++ @Nullable ++ public UUID getPlayerUniqueId(String name) { ++ Player player = Bukkit.getPlayerExact(name); ++ if (player != null) { ++ return player.getUniqueId(); ++ } ++ GameProfile profile; ++ // Only fetch an online UUID in online mode ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) { ++ profile = console.getProfileCache().get(name).orElse(null); ++ } else { ++ // Make an OfflinePlayer using an offline mode UUID since the name has no profile ++ profile = new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name); ++ } ++ return profile != null ? profile.getId() : null; ++ } ++ // Paper end ++ + @Override + @Deprecated + public OfflinePlayer getOfflinePlayer(String name) { diff --git a/patches/server/0179-Improved-Async-Task-Scheduler.patch b/patches/server/0178-Improved-Async-Task-Scheduler.patch similarity index 100% rename from patches/server/0179-Improved-Async-Task-Scheduler.patch rename to patches/server/0178-Improved-Async-Task-Scheduler.patch diff --git a/patches/server/0178-getPlayerUniqueId-API.patch b/patches/server/0178-getPlayerUniqueId-API.patch deleted file mode 100644 index d9dc0416d463..000000000000 --- a/patches/server/0178-getPlayerUniqueId-API.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 22 Mar 2018 01:40:24 -0400 -Subject: [PATCH] getPlayerUniqueId API - -Gets the unique ID of the player currently known as the specified player name -In Offline Mode, will return an Offline UUID - -This is a more performant way to obtain a UUID for a name than loading an OfflinePlayer - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index fb26379f600cc3e7e286d246f25e2bf718611e9d..d4c3d9a45c01d462513e5dbfa514afdde32c9ad7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1826,6 +1826,25 @@ public final class CraftServer implements Server { - return recipients.size(); - } - -+ // Paper start -+ @Nullable -+ public UUID getPlayerUniqueId(String name) { -+ Player player = Bukkit.getPlayerExact(name); -+ if (player != null) { -+ return player.getUniqueId(); -+ } -+ GameProfile profile; -+ // Only fetch an online UUID in online mode -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) { -+ profile = console.getProfileCache().get(name).orElse(null); -+ } else { -+ // Make an OfflinePlayer using an offline mode UUID since the name has no profile -+ profile = new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name); -+ } -+ return profile != null ? profile.getId() : null; -+ } -+ // Paper end -+ - @Override - @Deprecated - public OfflinePlayer getOfflinePlayer(String name) { diff --git a/patches/server/0180-Make-legacy-ping-handler-more-reliable.patch b/patches/server/0179-Make-legacy-ping-handler-more-reliable.patch similarity index 100% rename from patches/server/0180-Make-legacy-ping-handler-more-reliable.patch rename to patches/server/0179-Make-legacy-ping-handler-more-reliable.patch diff --git a/patches/server/0181-Call-PaperServerListPingEvent-for-legacy-pings.patch b/patches/server/0180-Call-PaperServerListPingEvent-for-legacy-pings.patch similarity index 100% rename from patches/server/0181-Call-PaperServerListPingEvent-for-legacy-pings.patch rename to patches/server/0180-Call-PaperServerListPingEvent-for-legacy-pings.patch diff --git a/patches/server/0181-Flag-to-disable-the-channel-limit.patch b/patches/server/0181-Flag-to-disable-the-channel-limit.patch new file mode 100644 index 000000000000..4c3389e676e5 --- /dev/null +++ b/patches/server/0181-Flag-to-disable-the-channel-limit.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 31 Mar 2018 17:04:26 +0100 +Subject: [PATCH] Flag to disable the channel limit + +In some enviroments, the channel limit set by spigot can cause issues, +e.g. servers which allow and support the usage of mod packs. + +provide an optional flag to disable this check, at your own risk. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index f33aee99de2007701bd593917a63e05c7fa5e349..a77c15a9c351c9cd5e8b4832016ec6de30483ed6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -196,6 +196,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + private CraftWorldBorder clientWorldBorder = null; + private BorderChangeListener clientWorldBorderListener = this.createWorldBorderListener(); + public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; // Paper - more resource pack API ++ private static final boolean DISABLE_CHANNEL_LIMIT = System.getProperty("paper.disableChannelLimit") != null; // Paper - add a flag to disable the channel limit + + public CraftPlayer(CraftServer server, ServerPlayer entity) { + super(server, entity); +@@ -2186,7 +2187,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + public void addChannel(String channel) { +- Preconditions.checkState(this.channels.size() < 128, "Cannot register channel '%s'. Too many channels registered!", channel); ++ Preconditions.checkState(DISABLE_CHANNEL_LIMIT || this.channels.size() < 128, "Cannot register channel '%s'. Too many channels registered!", channel); // Paper - flag to disable channel limit + channel = StandardMessenger.validateAndCorrectChannel(channel); + if (this.channels.add(channel)) { + this.server.getPluginManager().callEvent(new PlayerRegisterChannelEvent(this, channel)); diff --git a/patches/server/0183-Add-openSign-method-to-HumanEntity.patch b/patches/server/0182-Add-openSign-method-to-HumanEntity.patch similarity index 100% rename from patches/server/0183-Add-openSign-method-to-HumanEntity.patch rename to patches/server/0182-Add-openSign-method-to-HumanEntity.patch diff --git a/patches/server/0182-Flag-to-disable-the-channel-limit.patch b/patches/server/0182-Flag-to-disable-the-channel-limit.patch deleted file mode 100644 index dad19420a3ca..000000000000 --- a/patches/server/0182-Flag-to-disable-the-channel-limit.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sat, 31 Mar 2018 17:04:26 +0100 -Subject: [PATCH] Flag to disable the channel limit - -In some enviroments, the channel limit set by spigot can cause issues, -e.g. servers which allow and support the usage of mod packs. - -provide an optional flag to disable this check, at your own risk. - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index d59c2bbb88a4e11136e2aa8fb30f15894560f13f..4d0d2b1bc24a0d56724d7062bdbc3c7cf78e2b8a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -190,6 +190,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - private CraftWorldBorder clientWorldBorder = null; - private BorderChangeListener clientWorldBorderListener = this.createWorldBorderListener(); - public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; // Paper - more resource pack API -+ private static final boolean DISABLE_CHANNEL_LIMIT = System.getProperty("paper.disableChannelLimit") != null; // Paper - add a flag to disable the channel limit - - public CraftPlayer(CraftServer server, ServerPlayer entity) { - super(server, entity); -@@ -2135,7 +2136,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - public void addChannel(String channel) { -- Preconditions.checkState(this.channels.size() < 128, "Cannot register channel '%s'. Too many channels registered!", channel); -+ Preconditions.checkState(DISABLE_CHANNEL_LIMIT || this.channels.size() < 128, "Cannot register channel '%s'. Too many channels registered!", channel); // Paper - flag to disable channel limit - channel = StandardMessenger.validateAndCorrectChannel(channel); - if (this.channels.add(channel)) { - this.server.getPluginManager().callEvent(new PlayerRegisterChannelEvent(this, channel)); diff --git a/patches/server/0183-Configurable-sprint-interruption-on-attack.patch b/patches/server/0183-Configurable-sprint-interruption-on-attack.patch new file mode 100644 index 000000000000..dd26e680bc79 --- /dev/null +++ b/patches/server/0183-Configurable-sprint-interruption-on-attack.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Sat, 14 Apr 2018 20:20:46 +0200 +Subject: [PATCH] Configurable sprint interruption on attack + +If the sprint interruption is disabled players continue sprinting when they attack entities. + +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 e38f2e9a2ea616ebe5f167583fe339fc7244ccbf..cd85cc78cc25a2291c1202d53af82c7c00ac39d2 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1282,7 +1282,11 @@ public abstract class Player extends LivingEntity { + } + + this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); +- this.setSprinting(false); ++ // Paper start - Configurable sprint interruption on attack ++ if (!this.level().paperConfig().misc.disableSprintInterruptionOnAttack) { ++ this.setSprinting(false); ++ } ++ // Paper end - Configurable sprint interruption on attack + } + + if (flag3) { diff --git a/patches/server/0184-Configurable-sprint-interruption-on-attack.patch b/patches/server/0184-Configurable-sprint-interruption-on-attack.patch deleted file mode 100644 index 15d9d04e9977..000000000000 --- a/patches/server/0184-Configurable-sprint-interruption-on-attack.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Brokkonaut -Date: Sat, 14 Apr 2018 20:20:46 +0200 -Subject: [PATCH] Configurable sprint interruption on attack - -If the sprint interruption is disabled players continue sprinting when they attack entities. - -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 04d02c456d2e3562dcd122cb1951f8de3d808f7f..0c2c998a22a9328696bef75f48e1a5abd8589fa7 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1281,7 +1281,11 @@ public abstract class Player extends LivingEntity { - } - - this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); -- this.setSprinting(false); -+ // Paper start - Configurable sprint interruption on attack -+ if (!this.level().paperConfig().misc.disableSprintInterruptionOnAttack) { -+ this.setSprinting(false); -+ } -+ // Paper end - Configurable sprint interruption on attack - } - - if (flag3) { diff --git a/patches/server/0185-EndermanEscapeEvent.patch b/patches/server/0184-EndermanEscapeEvent.patch similarity index 100% rename from patches/server/0185-EndermanEscapeEvent.patch rename to patches/server/0184-EndermanEscapeEvent.patch diff --git a/patches/server/0186-Enderman.teleportRandomly.patch b/patches/server/0185-Enderman.teleportRandomly.patch similarity index 100% rename from patches/server/0186-Enderman.teleportRandomly.patch rename to patches/server/0185-Enderman.teleportRandomly.patch diff --git a/patches/server/0186-Block-Enderpearl-Travel-Exploit.patch b/patches/server/0186-Block-Enderpearl-Travel-Exploit.patch new file mode 100644 index 000000000000..ffdce6c52432 --- /dev/null +++ b/patches/server/0186-Block-Enderpearl-Travel-Exploit.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 30 Apr 2018 17:15:26 -0400 +Subject: [PATCH] Block Enderpearl Travel Exploit + +Players are able to use alt accounts and enderpearls to travel +long distances utilizing the pearls in unloaded chunks and loading +the chunk later when convenient. + +This disables that by not saving the thrower when the chunk is unloaded. + +This is mainly useful for survival servers that do not allow freeform teleporting. + +== AT == +public net.minecraft.world.entity.projectile.Projectile cachedOwner +public net.minecraft.world.entity.projectile.Projectile ownerUUID + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 5f0885c13116ad070dc076cca5a527b2a616c541..6cadfd678f1b2323a763ffd9220de7394620328b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2114,6 +2114,12 @@ public class ServerLevel extends Level implements WorldGenLevel { + + public void onTickingEnd(Entity entity) { + ServerLevel.this.entityTickList.remove(entity); ++ // Paper start - Reset pearls when they stop being ticked ++ if (paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) { ++ pearl.cachedOwner = null; ++ pearl.ownerUUID = null; ++ } ++ // Paper end - Reset pearls when they stop being ticked + } + + public void onTrackingStart(Entity entity) { +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 fbbb1fa3d5f4ace67fee96aa235cec3b39deb7b1..e432855ce79f69c0e91fa31e8f6c59a465b0d09e 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -101,6 +101,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { + if (nbt.hasUUID("Owner")) { + this.ownerUUID = nbt.getUUID("Owner"); + this.cachedOwner = null; ++ if (this instanceof ThrownEnderpearl && this.level() != null && this.level().paperConfig().fixes.disableUnloadedChunkEnderpearlExploit) { this.ownerUUID = null; } // Paper - Reset pearls when they stop being ticked; Don't store shooter name for pearls to block enderpearl travel exploit + } + + this.leftOwner = nbt.getBoolean("LeftOwner"); diff --git a/patches/server/0187-Block-Enderpearl-Travel-Exploit.patch b/patches/server/0187-Block-Enderpearl-Travel-Exploit.patch deleted file mode 100644 index 213dc36f8e15..000000000000 --- a/patches/server/0187-Block-Enderpearl-Travel-Exploit.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Mon, 30 Apr 2018 17:15:26 -0400 -Subject: [PATCH] Block Enderpearl Travel Exploit - -Players are able to use alt accounts and enderpearls to travel -long distances utilizing the pearls in unloaded chunks and loading -the chunk later when convenient. - -This disables that by not saving the thrower when the chunk is unloaded. - -This is mainly useful for survival servers that do not allow freeform teleporting. - -== AT == -public net.minecraft.world.entity.projectile.Projectile cachedOwner -public net.minecraft.world.entity.projectile.Projectile ownerUUID - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 54b3103d558adacba5f7a7b9fd230649623c7702..85e63061856a49c8e531eb0de8d1ca7b9805f424 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2114,6 +2114,12 @@ public class ServerLevel extends Level implements WorldGenLevel { - - public void onTickingEnd(Entity entity) { - ServerLevel.this.entityTickList.remove(entity); -+ // Paper start - Reset pearls when they stop being ticked -+ if (paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) { -+ pearl.cachedOwner = null; -+ pearl.ownerUUID = null; -+ } -+ // Paper end - Reset pearls when they stop being ticked - } - - public void onTrackingStart(Entity entity) { -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 fbbb1fa3d5f4ace67fee96aa235cec3b39deb7b1..e432855ce79f69c0e91fa31e8f6c59a465b0d09e 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -101,6 +101,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { - if (nbt.hasUUID("Owner")) { - this.ownerUUID = nbt.getUUID("Owner"); - this.cachedOwner = null; -+ if (this instanceof ThrownEnderpearl && this.level() != null && this.level().paperConfig().fixes.disableUnloadedChunkEnderpearlExploit) { this.ownerUUID = null; } // Paper - Reset pearls when they stop being ticked; Don't store shooter name for pearls to block enderpearl travel exploit - } - - this.leftOwner = nbt.getBoolean("LeftOwner"); diff --git a/patches/server/0187-Expand-World.spawnParticle-API-and-add-Builder.patch b/patches/server/0187-Expand-World.spawnParticle-API-and-add-Builder.patch new file mode 100644 index 000000000000..336526462f33 --- /dev/null +++ b/patches/server/0187-Expand-World.spawnParticle-API-and-add-Builder.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 15 Aug 2017 22:29:12 -0400 +Subject: [PATCH] Expand World.spawnParticle API and add Builder + +Adds ability to control who receives it and who is the source/sender (vanish API) +the standard API is to send the packet to everyone in the world, which is ineffecient. +Adds an option to control the force mode of the particle. + +This adds a new Builder API which is much friendlier to use. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 6cadfd678f1b2323a763ffd9220de7394620328b..180a9ade7bbc84d8c64b6c92583ba870464c23a5 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1503,12 +1503,17 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + + public int sendParticles(ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) { ++ // Paper start - Particle API ++ return sendParticles(players, sender, t0, d0, d1, d2, i, d3, d4, d5, d6, force); ++ } ++ public int sendParticles(List receivers, @Nullable ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) { ++ // Paper end - Particle API + ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(t0, force, d0, d1, d2, (float) d3, (float) d4, (float) d5, (float) d6, i); + // CraftBukkit end + int j = 0; + +- for (int k = 0; k < this.players.size(); ++k) { +- ServerPlayer entityplayer = (ServerPlayer) this.players.get(k); ++ for (Player entityhuman : receivers) { // Paper - Particle API ++ ServerPlayer entityplayer = (ServerPlayer) entityhuman; // Paper - Particle API + if (sender != null && !entityplayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit + + if (this.sendParticles(entityplayer, force, d0, d1, d2, packetplayoutworldparticles)) { // CraftBukkit +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 07aac886235e18a29420c494380e6b26bfa8f36e..012c0bf4f824543d475e97e0edb6c4fe88d046e5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1932,13 +1932,20 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { ++ // Paper start - Particle API ++ spawnParticle(particle, null, null, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, force); ++ } ++ @Override ++ public void spawnParticle(Particle particle, List receivers, Player sender, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { ++ // Paper end - Particle API + particle = CraftParticle.convertLegacy(particle); + data = CraftParticle.convertLegacy(data); + if (data != null) { + Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType()); + } + this.getHandle().sendParticles( +- null, // Sender ++ receivers == null ? getHandle().players() : receivers.stream().map(player -> ((CraftPlayer) player).getHandle()).collect(java.util.stream.Collectors.toList()), // Paper - Particle API ++ sender != null ? ((CraftPlayer) sender).getHandle() : null, // Sender // Paper - Particle API + CraftParticle.createParticleParam(particle, data), // Particle + x, y, z, // Position + count, // Count diff --git a/patches/server/0188-Expand-World.spawnParticle-API-and-add-Builder.patch b/patches/server/0188-Expand-World.spawnParticle-API-and-add-Builder.patch deleted file mode 100644 index 25289ea1b627..000000000000 --- a/patches/server/0188-Expand-World.spawnParticle-API-and-add-Builder.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 15 Aug 2017 22:29:12 -0400 -Subject: [PATCH] Expand World.spawnParticle API and add Builder - -Adds ability to control who receives it and who is the source/sender (vanish API) -the standard API is to send the packet to everyone in the world, which is ineffecient. -Adds an option to control the force mode of the particle. - -This adds a new Builder API which is much friendlier to use. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 85e63061856a49c8e531eb0de8d1ca7b9805f424..aa9d514f26fbe70edf3a8b7443fbaf577cf3a030 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1503,12 +1503,17 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - public int sendParticles(ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) { -+ // Paper start - Particle API -+ return sendParticles(players, sender, t0, d0, d1, d2, i, d3, d4, d5, d6, force); -+ } -+ public int sendParticles(List receivers, @Nullable ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) { -+ // Paper end - Particle API - ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(t0, force, d0, d1, d2, (float) d3, (float) d4, (float) d5, (float) d6, i); - // CraftBukkit end - int j = 0; - -- for (int k = 0; k < this.players.size(); ++k) { -- ServerPlayer entityplayer = (ServerPlayer) this.players.get(k); -+ for (Player entityhuman : receivers) { // Paper - Particle API -+ ServerPlayer entityplayer = (ServerPlayer) entityhuman; // Paper - Particle API - if (sender != null && !entityplayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit - - if (this.sendParticles(entityplayer, force, d0, d1, d2, packetplayoutworldparticles)) { // CraftBukkit -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 27f7b2cacd3113289b852c767e379d9769a9fa01..8fbc232ce409951d67eee1dbe34bc5bf4d069d01 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1919,13 +1919,20 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { -+ // Paper start - Particle API -+ spawnParticle(particle, null, null, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, force); -+ } -+ @Override -+ public void spawnParticle(Particle particle, List receivers, Player sender, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { -+ // Paper end - Particle API - particle = CraftParticle.convertLegacy(particle); - data = CraftParticle.convertLegacy(data); - if (data != null) { - Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType()); - } - this.getHandle().sendParticles( -- null, // Sender -+ receivers == null ? getHandle().players() : receivers.stream().map(player -> ((CraftPlayer) player).getHandle()).collect(java.util.stream.Collectors.toList()), // Paper - Particle API -+ sender != null ? ((CraftPlayer) sender).getHandle() : null, // Sender // Paper - Particle API - CraftParticle.createParticleParam(particle, data), // Particle - x, y, z, // Position - count, // Count diff --git a/patches/server/0189-Fix-exploit-that-allowed-colored-signs-to-be-created.patch b/patches/server/0188-Fix-exploit-that-allowed-colored-signs-to-be-created.patch similarity index 100% rename from patches/server/0189-Fix-exploit-that-allowed-colored-signs-to-be-created.patch rename to patches/server/0188-Fix-exploit-that-allowed-colored-signs-to-be-created.patch diff --git a/patches/server/0190-EndermanAttackPlayerEvent.patch b/patches/server/0189-EndermanAttackPlayerEvent.patch similarity index 100% rename from patches/server/0190-EndermanAttackPlayerEvent.patch rename to patches/server/0189-EndermanAttackPlayerEvent.patch diff --git a/patches/server/0191-WitchConsumePotionEvent.patch b/patches/server/0190-WitchConsumePotionEvent.patch similarity index 100% rename from patches/server/0191-WitchConsumePotionEvent.patch rename to patches/server/0190-WitchConsumePotionEvent.patch diff --git a/patches/server/0192-WitchThrowPotionEvent.patch b/patches/server/0191-WitchThrowPotionEvent.patch similarity index 100% rename from patches/server/0192-WitchThrowPotionEvent.patch rename to patches/server/0191-WitchThrowPotionEvent.patch diff --git a/patches/server/0192-WitchReadyPotionEvent.patch b/patches/server/0192-WitchReadyPotionEvent.patch new file mode 100644 index 000000000000..f558e9c1736a --- /dev/null +++ b/patches/server/0192-WitchReadyPotionEvent.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 5 Jun 2018 22:47:26 -0400 +Subject: [PATCH] WitchReadyPotionEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java +index 96a87db9f8976d3f1ff09beb9598db31fff72d5b..25a1edf64602a13c07779e58b167a8471019588e 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Witch.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java +@@ -160,7 +160,11 @@ public class Witch extends Raider implements RangedAttackMob { + } + + if (potionregistry != null) { +- this.setItemSlot(EquipmentSlot.MAINHAND, PotionUtils.setPotion(new ItemStack(Items.POTION), potionregistry)); ++ // Paper start ++ ItemStack potion = PotionUtils.setPotion(new ItemStack(Items.POTION), potionregistry); ++ potion = org.bukkit.craftbukkit.event.CraftEventFactory.handleWitchReadyPotionEvent(this, potion); ++ this.setItemSlot(EquipmentSlot.MAINHAND, potion); ++ // Paper end + this.usingTime = this.getMainHandItem().getUseDuration(); + this.setUsingItem(true); + if (!this.isSilent()) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index d461f56cbc64efba422d748c42ec106f84423833..e0234ea1d55e918072574fb5d4b6cc75fc5d46d6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1880,4 +1880,14 @@ public class CraftEventFactory { + ).callEvent(); + } + // Paper end - PlayerUseUnknownEntityEvent ++ ++ // Paper start - WitchReadyPotionEvent ++ public static ItemStack handleWitchReadyPotionEvent(net.minecraft.world.entity.monster.Witch witch, @Nullable ItemStack potion) { ++ com.destroystokyo.paper.event.entity.WitchReadyPotionEvent event = new com.destroystokyo.paper.event.entity.WitchReadyPotionEvent((org.bukkit.entity.Witch) witch.getBukkitEntity(), CraftItemStack.asCraftMirror(potion)); ++ if (!event.callEvent() || event.getPotion() == null) { ++ return ItemStack.EMPTY; ++ } ++ return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion()); ++ } ++ // Paper end - WitchReadyPotionEvent + } diff --git a/patches/server/0193-ItemStack-getMaxItemUseDuration.patch b/patches/server/0193-ItemStack-getMaxItemUseDuration.patch new file mode 100644 index 000000000000..83ab07155e1e --- /dev/null +++ b/patches/server/0193-ItemStack-getMaxItemUseDuration.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 5 Jun 2018 23:00:29 -0400 +Subject: [PATCH] ItemStack#getMaxItemUseDuration + +Allows you to determine how long it takes to use a usable/consumable item + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index c72a1a503f6e71228a1f82b37068ff7a83e983dc..d9a07829d5d0ebcb18b8e3f12622ed7795955d61 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -184,6 +184,13 @@ public final class CraftItemStack extends ItemStack { + return (this.handle == null) ? Material.AIR.getMaxStackSize() : this.handle.getItem().getMaxStackSize(); + } + ++ // Paper start ++ @Override ++ public int getMaxItemUseDuration() { ++ return handle == null ? 0 : handle.getUseDuration(); ++ } ++ // Paper end ++ + @Override + public void addUnsafeEnchantment(Enchantment ench, int level) { + Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); diff --git a/patches/server/0193-WitchReadyPotionEvent.patch b/patches/server/0193-WitchReadyPotionEvent.patch deleted file mode 100644 index eda5a5e73d67..000000000000 --- a/patches/server/0193-WitchReadyPotionEvent.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 5 Jun 2018 22:47:26 -0400 -Subject: [PATCH] WitchReadyPotionEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java -index 96a87db9f8976d3f1ff09beb9598db31fff72d5b..25a1edf64602a13c07779e58b167a8471019588e 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Witch.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java -@@ -160,7 +160,11 @@ public class Witch extends Raider implements RangedAttackMob { - } - - if (potionregistry != null) { -- this.setItemSlot(EquipmentSlot.MAINHAND, PotionUtils.setPotion(new ItemStack(Items.POTION), potionregistry)); -+ // Paper start -+ ItemStack potion = PotionUtils.setPotion(new ItemStack(Items.POTION), potionregistry); -+ potion = org.bukkit.craftbukkit.event.CraftEventFactory.handleWitchReadyPotionEvent(this, potion); -+ this.setItemSlot(EquipmentSlot.MAINHAND, potion); -+ // Paper end - this.usingTime = this.getMainHandItem().getUseDuration(); - this.setUsingItem(true); - if (!this.isSilent()) { -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 5bc9b40dd3bbb2e743205adec77cad402564dabf..6e37cb0200f56182030962d981694ab5b126be0d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1935,4 +1935,14 @@ public class CraftEventFactory { - ).callEvent(); - } - // Paper end - PlayerUseUnknownEntityEvent -+ -+ // Paper start - WitchReadyPotionEvent -+ public static ItemStack handleWitchReadyPotionEvent(net.minecraft.world.entity.monster.Witch witch, @Nullable ItemStack potion) { -+ com.destroystokyo.paper.event.entity.WitchReadyPotionEvent event = new com.destroystokyo.paper.event.entity.WitchReadyPotionEvent((org.bukkit.entity.Witch) witch.getBukkitEntity(), CraftItemStack.asCraftMirror(potion)); -+ if (!event.callEvent() || event.getPotion() == null) { -+ return ItemStack.EMPTY; -+ } -+ return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion()); -+ } -+ // Paper end - WitchReadyPotionEvent - } diff --git a/patches/server/0195-Add-EntityTeleportEndGatewayEvent.patch b/patches/server/0194-Add-EntityTeleportEndGatewayEvent.patch similarity index 100% rename from patches/server/0195-Add-EntityTeleportEndGatewayEvent.patch rename to patches/server/0194-Add-EntityTeleportEndGatewayEvent.patch diff --git a/patches/server/0194-ItemStack-getMaxItemUseDuration.patch b/patches/server/0194-ItemStack-getMaxItemUseDuration.patch deleted file mode 100644 index a37f9cf78d1f..000000000000 --- a/patches/server/0194-ItemStack-getMaxItemUseDuration.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 5 Jun 2018 23:00:29 -0400 -Subject: [PATCH] ItemStack#getMaxItemUseDuration - -Allows you to determine how long it takes to use a usable/consumable item - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index a05f01ca9a9bc88e414c8cf89c01c7e993e27dd2..07f011a968592ba5b38a0a2723189824ba5d0938 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -184,6 +184,13 @@ public final class CraftItemStack extends ItemStack { - return (this.handle == null) ? Material.AIR.getMaxStackSize() : this.handle.getItem().getMaxStackSize(); - } - -+ // Paper start -+ @Override -+ public int getMaxItemUseDuration() { -+ return handle == null ? 0 : handle.getUseDuration(); -+ } -+ // Paper end -+ - @Override - public void addUnsafeEnchantment(Enchantment ench, int level) { - Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); diff --git a/patches/server/0196-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch b/patches/server/0195-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch similarity index 100% rename from patches/server/0196-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch rename to patches/server/0195-Unset-Ignited-flag-on-cancel-of-Explosion-Event.patch diff --git a/patches/server/0197-Fix-CraftEntity-hashCode.patch b/patches/server/0196-Fix-CraftEntity-hashCode.patch similarity index 100% rename from patches/server/0197-Fix-CraftEntity-hashCode.patch rename to patches/server/0196-Fix-CraftEntity-hashCode.patch diff --git a/patches/server/0198-Configurable-LootPool-luck-formula.patch b/patches/server/0197-Configurable-LootPool-luck-formula.patch similarity index 100% rename from patches/server/0198-Configurable-LootPool-luck-formula.patch rename to patches/server/0197-Configurable-LootPool-luck-formula.patch diff --git a/patches/server/0198-Print-Error-details-when-failing-to-save-player-data.patch b/patches/server/0198-Print-Error-details-when-failing-to-save-player-data.patch new file mode 100644 index 000000000000..85805baf5330 --- /dev/null +++ b/patches/server/0198-Print-Error-details-when-failing-to-save-player-data.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 15 Jun 2018 20:37:03 -0400 +Subject: [PATCH] Print Error details when failing to save player data + + +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 2a167a0131d866b4368fc30849c17acdf0ab9af0..49d39980054bce470ddaceeb6ab7fab83bf8dc54 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -47,7 +47,7 @@ public class PlayerDataStorage { + + Util.safeReplaceFile(path2, path1, path3); + } catch (Exception exception) { +- PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getName().getString()); ++ PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getScoreboardName(), exception); // Paper - Print exception + } + + } diff --git a/patches/server/0199-Make-shield-blocking-delay-configurable.patch b/patches/server/0199-Make-shield-blocking-delay-configurable.patch new file mode 100644 index 000000000000..202e56a0ff34 --- /dev/null +++ b/patches/server/0199-Make-shield-blocking-delay-configurable.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 16 Jun 2018 01:18:16 -0500 +Subject: [PATCH] Make shield blocking delay configurable + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 1203387260e5e2727ffb682882da85b8c89c1f4c..001179978a81f2f7f1c56270b2a131dda7aaa881 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3820,12 +3820,24 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (this.isUsingItem() && !this.useItem.isEmpty()) { + Item item = this.useItem.getItem(); + +- return item.getUseAnimation(this.useItem) != UseAnim.BLOCK ? false : item.getUseDuration(this.useItem) - this.useItemRemaining >= 5; ++ return item.getUseAnimation(this.useItem) != UseAnim.BLOCK ? false : item.getUseDuration(this.useItem) - this.useItemRemaining >= getShieldBlockingDelay(); // Paper - Make shield blocking delay configurable + } else { + return false; + } + } + ++ // Paper start - Make shield blocking delay configurable ++ public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; ++ ++ public int getShieldBlockingDelay() { ++ return shieldBlockingDelay; ++ } ++ ++ public void setShieldBlockingDelay(int shieldBlockingDelay) { ++ this.shieldBlockingDelay = shieldBlockingDelay; ++ } ++ // Paper end - Make shield blocking delay configurable ++ + public boolean isSuppressingSlidingDownLadder() { + return this.isShiftKeyDown(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 82773a05783b731e2f7bd00c8ec68090d15a7b66..ca92ece290c6dd164b3ba2ba46289350ae6829e8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -839,5 +839,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void setArrowsStuck(final int arrows) { + this.getHandle().setArrowCount(arrows); + } ++ ++ @Override ++ public int getShieldBlockingDelay() { ++ return getHandle().getShieldBlockingDelay(); ++ } ++ ++ @Override ++ public void setShieldBlockingDelay(int delay) { ++ getHandle().setShieldBlockingDelay(delay); ++ } + // Paper end + } diff --git a/patches/server/0199-Print-Error-details-when-failing-to-save-player-data.patch b/patches/server/0199-Print-Error-details-when-failing-to-save-player-data.patch deleted file mode 100644 index ae45a4597484..000000000000 --- a/patches/server/0199-Print-Error-details-when-failing-to-save-player-data.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 15 Jun 2018 20:37:03 -0400 -Subject: [PATCH] Print Error details when failing to save player data - - -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 08f4cc47ec3aa4dd6980ba543219891a510b010b..63e187c65cb855031f286aad0d25ac4694f7a331 100644 ---- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -+++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -@@ -47,7 +47,7 @@ public class PlayerDataStorage { - - Util.safeReplaceFile(path2, path1, path3); - } catch (Exception exception) { -- PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getName().getString()); -+ PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getScoreboardName(), exception); // Paper - Print exception - } - - } diff --git a/patches/server/0201-Improve-EntityShootBowEvent.patch b/patches/server/0200-Improve-EntityShootBowEvent.patch similarity index 100% rename from patches/server/0201-Improve-EntityShootBowEvent.patch rename to patches/server/0200-Improve-EntityShootBowEvent.patch diff --git a/patches/server/0200-Make-shield-blocking-delay-configurable.patch b/patches/server/0200-Make-shield-blocking-delay-configurable.patch deleted file mode 100644 index 5c854fab7565..000000000000 --- a/patches/server/0200-Make-shield-blocking-delay-configurable.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 16 Jun 2018 01:18:16 -0500 -Subject: [PATCH] Make shield blocking delay configurable - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 882bb83a552be415711c8df8118e91981fc63e46..f01a0d7a19329aabbfa69be68c48a409d11aa352 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3807,12 +3807,24 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (this.isUsingItem() && !this.useItem.isEmpty()) { - Item item = this.useItem.getItem(); - -- return item.getUseAnimation(this.useItem) != UseAnim.BLOCK ? false : item.getUseDuration(this.useItem) - this.useItemRemaining >= 5; -+ return item.getUseAnimation(this.useItem) != UseAnim.BLOCK ? false : item.getUseDuration(this.useItem) - this.useItemRemaining >= getShieldBlockingDelay(); // Paper - Make shield blocking delay configurable - } else { - return false; - } - } - -+ // Paper start - Make shield blocking delay configurable -+ public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; -+ -+ public int getShieldBlockingDelay() { -+ return shieldBlockingDelay; -+ } -+ -+ public void setShieldBlockingDelay(int shieldBlockingDelay) { -+ this.shieldBlockingDelay = shieldBlockingDelay; -+ } -+ // Paper end - Make shield blocking delay configurable -+ - public boolean isSuppressingSlidingDownLadder() { - return this.isShiftKeyDown(); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index f09064a6721481c202449ff12ba1d54385e24043..30c6d32040decc56947d7e8a840036262e122137 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -826,5 +826,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - public void setArrowsStuck(final int arrows) { - this.getHandle().setArrowCount(arrows); - } -+ -+ @Override -+ public int getShieldBlockingDelay() { -+ return getHandle().getShieldBlockingDelay(); -+ } -+ -+ @Override -+ public void setShieldBlockingDelay(int delay) { -+ getHandle().setShieldBlockingDelay(delay); -+ } - // Paper end - } diff --git a/patches/server/0201-PlayerReadyArrowEvent.patch b/patches/server/0201-PlayerReadyArrowEvent.patch new file mode 100644 index 000000000000..75712ba31e6a --- /dev/null +++ b/patches/server/0201-PlayerReadyArrowEvent.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Mon, 18 Jun 2018 01:12:53 -0400 +Subject: [PATCH] PlayerReadyArrowEvent + +Called when a player is firing a bow and the server is choosing an arrow to use. +Plugins can skip selection of certain arrows and control which is used. + +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 cd85cc78cc25a2291c1202d53af82c7c00ac39d2..aad9add92eaff7205383033a0b03d072724bf428 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -2168,18 +2168,29 @@ public abstract class Player extends LivingEntity { + return ImmutableList.of(Pose.STANDING, Pose.CROUCHING, Pose.SWIMMING); + } + ++ // Paper start - PlayerReadyArrowEvent ++ protected boolean tryReadyArrow(ItemStack bow, ItemStack itemstack) { ++ return !(this instanceof ServerPlayer) || ++ new com.destroystokyo.paper.event.player.PlayerReadyArrowEvent( ++ ((ServerPlayer) this).getBukkitEntity(), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(bow), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack) ++ ).callEvent(); ++ } ++ // Paper end - PlayerReadyArrowEvent ++ + @Override + public ItemStack getProjectile(ItemStack stack) { + if (!(stack.getItem() instanceof ProjectileWeaponItem)) { + return ItemStack.EMPTY; + } else { +- Predicate predicate = ((ProjectileWeaponItem) stack.getItem()).getSupportedHeldProjectiles(); ++ Predicate predicate = ((ProjectileWeaponItem) stack.getItem()).getSupportedHeldProjectiles().and(item -> tryReadyArrow(stack, item)); // Paper - PlayerReadyArrowEvent + ItemStack itemstack1 = ProjectileWeaponItem.getHeldProjectile(this, predicate); + + if (!itemstack1.isEmpty()) { + return itemstack1; + } else { +- predicate = ((ProjectileWeaponItem) stack.getItem()).getAllSupportedProjectiles(); ++ predicate = ((ProjectileWeaponItem) stack.getItem()).getAllSupportedProjectiles().and(item -> tryReadyArrow(stack, item)); // Paper - PlayerReadyArrowEvent + + for (int i = 0; i < this.inventory.getContainerSize(); ++i) { + ItemStack itemstack2 = this.inventory.getItem(i); diff --git a/patches/server/0202-Add-EntityKnockbackByEntityEvent-and-EntityPushedByE.patch b/patches/server/0202-Add-EntityKnockbackByEntityEvent-and-EntityPushedByE.patch new file mode 100644 index 000000000000..3fb3823a0103 --- /dev/null +++ b/patches/server/0202-Add-EntityKnockbackByEntityEvent-and-EntityPushedByE.patch @@ -0,0 +1,166 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Mon, 18 Jun 2018 15:46:23 +0200 +Subject: [PATCH] Add EntityKnockbackByEntityEvent and + EntityPushedByEntityAttackEvent + +Co-authored-by: aerulion + +This event is called when an entity receives knockback by another entity. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 6fbde57320a58600f8c4b9ce598fa93bd2772e8b..cf907e0c1a89639639b6453032f8f6fe4e05a81c 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1856,8 +1856,22 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + public void push(double deltaX, double deltaY, double deltaZ) { +- this.setDeltaMovement(this.getDeltaMovement().add(deltaX, deltaY, deltaZ)); ++ // Paper start - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent ++ this.push(deltaX, deltaY, deltaZ, null); ++ } ++ ++ public void push(double deltaX, double deltaY, double deltaZ, @org.jetbrains.annotations.Nullable Entity pushingEntity) { ++ org.bukkit.util.Vector delta = new org.bukkit.util.Vector(deltaX, deltaY, deltaZ); ++ if (pushingEntity != null) { ++ io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent event = new io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent(getBukkitEntity(), pushingEntity.getBukkitEntity(), delta); ++ if (!event.callEvent()) { ++ return; ++ } ++ delta = event.getAcceleration(); ++ } ++ this.setDeltaMovement(this.getDeltaMovement().add(delta.getX(), delta.getY(), delta.getZ())); + this.hasImpulse = true; ++ // Paper end - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + } + + protected void markHurt() { +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 001179978a81f2f7f1c56270b2a131dda7aaa881..2900326a87d21c8a92edb303ed42fd11ea7f3010 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1561,7 +1561,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + protected void blockedByShield(LivingEntity target) { +- target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ(), null, EntityKnockbackEvent.KnockbackCause.SHIELD_BLOCK); // CraftBukkit ++ target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ(), this, EntityKnockbackEvent.KnockbackCause.SHIELD_BLOCK); // CraftBukkit // Paper - fix attacker + } + + private boolean checkTotemDeathProtection(DamageSource source) { +@@ -1836,8 +1836,22 @@ public abstract class LivingEntity extends Entity implements Attackable { + return; + } + ++ // Paper start - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent ++ final org.bukkit.util.Vector currentMovement = this.getBukkitEntity().getVelocity(); ++ org.bukkit.util.Vector resultingMovement = event.getFinalKnockback(); ++ final org.bukkit.util.Vector deltaMovement = resultingMovement.clone().subtract(currentMovement); ++ if (attacker != null) { ++ final com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent knockbackEvent = new com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent((org.bukkit.entity.LivingEntity) getBukkitEntity(), attacker.getBukkitEntity(), (float) event.getForce(), deltaMovement); ++ if (!knockbackEvent.callEvent()) { ++ return; ++ } ++ ++ // Back from delta to the absolute vector ++ resultingMovement = currentMovement.add(knockbackEvent.getAcceleration()); ++ } + this.hasImpulse = true; +- this.setDeltaMovement(event.getFinalKnockback().getX(), event.getFinalKnockback().getY(), event.getFinalKnockback().getZ()); ++ this.setDeltaMovement(resultingMovement.getX(), resultingMovement.getY(), resultingMovement.getZ()); ++ // Paper end - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + // CraftBukkit end + } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java b/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java +index e319a46a21a94314c5d496820b1ac4879dcf56b9..fcc0a7789c79b956f097bc6d34e0c37e0b90a2db 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java +@@ -77,7 +77,7 @@ public class RamTarget extends Behavior { + float f = 0.25F * (float)(i - j); + float g = Mth.clamp(entity.getSpeed() * 1.65F, 0.2F, 3.0F) + f; + float h = livingEntity.isDamageSourceBlocked(world.damageSources().mobAttack(entity)) ? 0.5F : 1.0F; +- livingEntity.knockback((double)(h * g) * this.getKnockbackForce.applyAsDouble(entity), this.ramDirection.x(), this.ramDirection.z()); ++ livingEntity.knockback((double)(h * g) * this.getKnockbackForce.applyAsDouble(entity), this.ramDirection.x(), this.ramDirection.z(), entity, org.bukkit.event.entity.EntityKnockbackEvent.KnockbackCause.ENTITY_ATTACK); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + this.finishRam(world, entity); + world.playSound((Player)null, entity, this.getImpactSound.apply(entity), SoundSource.NEUTRAL, 1.0F, 1.0F); + } else if (this.hasRammedHornBreakingBlock(world, entity)) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java b/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java +index 29cfd065f246bbd3d3c2a5bbd32c3f4813a02951..771d798fa3b367043129f41101c65f13f0b466fa 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java +@@ -68,7 +68,7 @@ public class SonicBoom extends Behavior { + target.hurt(world.damageSources().sonicBoom(entity), 10.0F); + double d = 0.5D * (1.0D - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE)); + double e = 2.5D * (1.0D - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE)); +- target.push(vec33.x() * e, vec33.y() * d, vec33.z() * e); ++ target.push(vec33.x() * e, vec33.y() * d, vec33.z() * e, entity); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + }); + } + } +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index f5ce6423b8146cc741023e15004fe9814a035da8..666c62d796cae9a70516680e26196f7e02c53918 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -458,7 +458,7 @@ public class EnderDragon extends Mob implements Enemy { + double d3 = entity.getZ() - d1; + double d4 = Math.max(d2 * d2 + d3 * d3, 0.1D); + +- entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D); ++ entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + if (!this.phaseManager.getCurrentPhase().isSitting() && ((LivingEntity) entity).getLastHurtByMobTimestamp() < entity.tickCount - 2) { + entity.hurt(this.damageSources().mobAttack(this), 5.0F); + this.doEnchantDamageEffects(this, entity); +diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +index 041f1650b853138e4286fe83a08d79d276054ce7..aba20a4352d8983b01ab5d329187588f68d3e405 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +@@ -265,7 +265,7 @@ public class Ravager extends Raider { + double d1 = entity.getZ() - this.getZ(); + double d2 = Math.max(d0 * d0 + d1 * d1, 0.001D); + +- entity.push(d0 / d2 * 4.0D, 0.2D, d1 / d2 * 4.0D); ++ entity.push(d0 / d2 * 4.0D, 0.2D, d1 / d2 * 4.0D, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java +index 81003ce3f05c6be6f52a92b86a4721235f4ce12a..cae7e3e85c3b911f50f8a06badf695c3df2847e5 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java ++++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java +@@ -40,7 +40,7 @@ public interface HoglinBase { + double j = f * (double)(attacker.level().random.nextFloat() * 0.5F + 0.2F); + Vec3 vec3 = (new Vec3(g, 0.0D, h)).normalize().scale(j).yRot(i); + double k = f * (double)attacker.level().random.nextFloat() * 0.5D; +- target.push(vec3.x, k, vec3.z); ++ target.push(vec3.x, k, vec3.z, attacker); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + target.hurtMarked = true; + } + } +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 aad9add92eaff7205383033a0b03d072724bf428..de537f4e89ccadefafb0f3e6075dba5f030f2654 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1278,7 +1278,7 @@ public abstract class Player extends LivingEntity { + if (target instanceof LivingEntity) { + ((LivingEntity) target).knockback((double) ((float) i * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, EntityKnockbackEvent.KnockbackCause.ENTITY_ATTACK); // CraftBukkit + } else { +- target.push((double) (-Mth.sin(this.getYRot() * 0.017453292F) * (float) i * 0.5F), 0.1D, (double) (Mth.cos(this.getYRot() * 0.017453292F) * (float) i * 0.5F)); ++ target.push((double) (-Mth.sin(this.getYRot() * 0.017453292F) * (float) i * 0.5F), 0.1D, (double) (Mth.cos(this.getYRot() * 0.017453292F) * (float) i * 0.5F), this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + } + + this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index 0f700442a7559fac5d27d1fb6b3716f3853a9897..f3861cea4eb6a39fa16936383f8dabc6689a58a9 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -409,7 +409,7 @@ public abstract class AbstractArrow extends Projectile { + Vec3 vec3d = this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D).normalize().scale((double) this.knockback * 0.6D * d0); + + if (vec3d.lengthSqr() > 0.0D) { +- entityliving.push(vec3d.x, 0.1D, vec3d.z); ++ entityliving.push(vec3d.x, 0.1D, vec3d.z, this); // Paper + } + } + diff --git a/patches/server/0202-PlayerReadyArrowEvent.patch b/patches/server/0202-PlayerReadyArrowEvent.patch deleted file mode 100644 index 230edc928163..000000000000 --- a/patches/server/0202-PlayerReadyArrowEvent.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Mon, 18 Jun 2018 01:12:53 -0400 -Subject: [PATCH] PlayerReadyArrowEvent - -Called when a player is firing a bow and the server is choosing an arrow to use. -Plugins can skip selection of certain arrows and control which is used. - -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 0c2c998a22a9328696bef75f48e1a5abd8589fa7..f28e9f06596cea6904b407fa87835f21d07817fa 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -2167,18 +2167,29 @@ public abstract class Player extends LivingEntity { - return ImmutableList.of(Pose.STANDING, Pose.CROUCHING, Pose.SWIMMING); - } - -+ // Paper start - PlayerReadyArrowEvent -+ protected boolean tryReadyArrow(ItemStack bow, ItemStack itemstack) { -+ return !(this instanceof ServerPlayer) || -+ new com.destroystokyo.paper.event.player.PlayerReadyArrowEvent( -+ ((ServerPlayer) this).getBukkitEntity(), -+ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(bow), -+ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack) -+ ).callEvent(); -+ } -+ // Paper end - PlayerReadyArrowEvent -+ - @Override - public ItemStack getProjectile(ItemStack stack) { - if (!(stack.getItem() instanceof ProjectileWeaponItem)) { - return ItemStack.EMPTY; - } else { -- Predicate predicate = ((ProjectileWeaponItem) stack.getItem()).getSupportedHeldProjectiles(); -+ Predicate predicate = ((ProjectileWeaponItem) stack.getItem()).getSupportedHeldProjectiles().and(item -> tryReadyArrow(stack, item)); // Paper - PlayerReadyArrowEvent - ItemStack itemstack1 = ProjectileWeaponItem.getHeldProjectile(this, predicate); - - if (!itemstack1.isEmpty()) { - return itemstack1; - } else { -- predicate = ((ProjectileWeaponItem) stack.getItem()).getAllSupportedProjectiles(); -+ predicate = ((ProjectileWeaponItem) stack.getItem()).getAllSupportedProjectiles().and(item -> tryReadyArrow(stack, item)); // Paper - PlayerReadyArrowEvent - - for (int i = 0; i < this.inventory.getContainerSize(); ++i) { - ItemStack itemstack2 = this.inventory.getItem(i); diff --git a/patches/server/0203-Add-EntityKnockbackByEntityEvent-and-EntityPushedByE.patch b/patches/server/0203-Add-EntityKnockbackByEntityEvent-and-EntityPushedByE.patch deleted file mode 100644 index d6134ef2d61a..000000000000 --- a/patches/server/0203-Add-EntityKnockbackByEntityEvent-and-EntityPushedByE.patch +++ /dev/null @@ -1,200 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Brokkonaut -Date: Mon, 18 Jun 2018 15:46:23 +0200 -Subject: [PATCH] Add EntityKnockbackByEntityEvent and - EntityPushedByEntityAttackEvent - -Co-authored-by: aerulion - -This event is called when an entity receives knockback by another entity. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index d96fc02a8b84dbcfc17496ca476a5dee8821b785..94c6914a7af6329e4bed8a8d470563b7901fb248 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1857,8 +1857,17 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - public void push(double deltaX, double deltaY, double deltaZ) { -- this.setDeltaMovement(this.getDeltaMovement().add(deltaX, deltaY, deltaZ)); -- this.hasImpulse = true; -+ // Paper start - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent -+ this.push(deltaX, deltaY, deltaZ, null); -+ } -+ -+ public void push(double deltaX, double deltaY, double deltaZ, @org.jetbrains.annotations.Nullable Entity pushingEntity) { -+ org.bukkit.util.Vector delta = new org.bukkit.util.Vector(deltaX, deltaY, deltaZ); -+ if (pushingEntity == null || new io.papermc.paper.event.entity.EntityPushedByEntityAttackEvent(getBukkitEntity(), pushingEntity.getBukkitEntity(), delta).callEvent()) { -+ this.setDeltaMovement(this.getDeltaMovement().add(delta.getX(), delta.getY(), delta.getZ())); -+ this.hasImpulse = true; -+ } -+ // Paper end - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - } - - protected void markHurt() { -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index f01a0d7a19329aabbfa69be68c48a409d11aa352..b58a13e33a0b98bbbdd3283fc3b90cde11ecd37f 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1512,7 +1512,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - d0 = (Math.random() - Math.random()) * 0.01D; - } - -- this.knockback(0.4000000059604645D, d0, d1); -+ this.knockback(0.4000000059604645D, d0, d1, entity1); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - if (!flag) { - this.indicateDamage(d0, d1); - } -@@ -1560,7 +1560,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - protected void blockedByShield(LivingEntity target) { -- target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ()); -+ target.knockback(0.5D, target.getX() - this.getX(), target.getZ() - this.getZ(), this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - } - - private boolean checkTotemDeathProtection(DamageSource source) { -@@ -1819,6 +1819,11 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public void knockback(double strength, double x, double z) { -+ // Paper start - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent -+ this.knockback(strength, x, z, null); -+ } -+ public void knockback(double strength, double x, double z, Entity knockingBackEntity) { -+ // Paper end - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - strength *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE); - if (strength > 0.0D) { - this.hasImpulse = true; -@@ -1826,6 +1831,15 @@ public abstract class LivingEntity extends Entity implements Attackable { - Vec3 vec3d1 = (new Vec3(x, 0.0D, z)).normalize().scale(strength); - - this.setDeltaMovement(vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + strength) : vec3d.y, vec3d.z / 2.0D - vec3d1.z); -+ // Paper start - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent -+ Vec3 currentMovement = this.getDeltaMovement(); -+ org.bukkit.util.Vector delta = new org.bukkit.util.Vector(currentMovement.x - vec3d.x, currentMovement.y - vec3d.y, currentMovement.z - vec3d.z); -+ // Restore old velocity to be able to access it in the event -+ this.setDeltaMovement(vec3d); -+ if (knockingBackEntity == null || new com.destroystokyo.paper.event.entity.EntityKnockbackByEntityEvent((org.bukkit.entity.LivingEntity) getBukkitEntity(), knockingBackEntity.getBukkitEntity(), (float) strength, delta).callEvent()) { -+ this.setDeltaMovement(vec3d.x + delta.getX(), vec3d.y + delta.getY(), vec3d.z + delta.getZ()); -+ } -+ // Paper end - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 15ad425b9c091ee27965fe166f9021509199aa18..4ae0f36276592e37aeb5f881b713efa76d086f8e 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1646,7 +1646,7 @@ public abstract class Mob extends LivingEntity implements Targeting { - - if (flag) { - if (f1 > 0.0F && target instanceof LivingEntity) { -- ((LivingEntity) target).knockback((double) (f1 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F))); -+ ((LivingEntity) target).knockback((double) (f1 * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); - } - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java b/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java -index e319a46a21a94314c5d496820b1ac4879dcf56b9..4f78e7fbb7a082069b44f7b28f0048e856e5c773 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/RamTarget.java -@@ -77,7 +77,7 @@ public class RamTarget extends Behavior { - float f = 0.25F * (float)(i - j); - float g = Mth.clamp(entity.getSpeed() * 1.65F, 0.2F, 3.0F) + f; - float h = livingEntity.isDamageSourceBlocked(world.damageSources().mobAttack(entity)) ? 0.5F : 1.0F; -- livingEntity.knockback((double)(h * g) * this.getKnockbackForce.applyAsDouble(entity), this.ramDirection.x(), this.ramDirection.z()); -+ livingEntity.knockback((double)(h * g) * this.getKnockbackForce.applyAsDouble(entity), this.ramDirection.x(), this.ramDirection.z(), entity); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - this.finishRam(world, entity); - world.playSound((Player)null, entity, this.getImpactSound.apply(entity), SoundSource.NEUTRAL, 1.0F, 1.0F); - } else if (this.hasRammedHornBreakingBlock(world, entity)) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java b/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java -index 29cfd065f246bbd3d3c2a5bbd32c3f4813a02951..771d798fa3b367043129f41101c65f13f0b466fa 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/warden/SonicBoom.java -@@ -68,7 +68,7 @@ public class SonicBoom extends Behavior { - target.hurt(world.damageSources().sonicBoom(entity), 10.0F); - double d = 0.5D * (1.0D - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE)); - double e = 2.5D * (1.0D - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE)); -- target.push(vec33.x() * e, vec33.y() * d, vec33.z() * e); -+ target.push(vec33.x() * e, vec33.y() * d, vec33.z() * e, entity); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - }); - } - } -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index f5ce6423b8146cc741023e15004fe9814a035da8..666c62d796cae9a70516680e26196f7e02c53918 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -458,7 +458,7 @@ public class EnderDragon extends Mob implements Enemy { - double d3 = entity.getZ() - d1; - double d4 = Math.max(d2 * d2 + d3 * d3, 0.1D); - -- entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D); -+ entity.push(d2 / d4 * 4.0D, 0.20000000298023224D, d3 / d4 * 4.0D, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - if (!this.phaseManager.getCurrentPhase().isSitting() && ((LivingEntity) entity).getLastHurtByMobTimestamp() < entity.tickCount - 2) { - entity.hurt(this.damageSources().mobAttack(this), 5.0F); - this.doEnchantDamageEffects(this, entity); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -index 041f1650b853138e4286fe83a08d79d276054ce7..aba20a4352d8983b01ab5d329187588f68d3e405 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -@@ -265,7 +265,7 @@ public class Ravager extends Raider { - double d1 = entity.getZ() - this.getZ(); - double d2 = Math.max(d0 * d0 + d1 * d1, 0.001D); - -- entity.push(d0 / d2 * 4.0D, 0.2D, d1 / d2 * 4.0D); -+ entity.push(d0 / d2 * 4.0D, 0.2D, d1 / d2 * 4.0D, this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java -index 81003ce3f05c6be6f52a92b86a4721235f4ce12a..cae7e3e85c3b911f50f8a06badf695c3df2847e5 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java -+++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/HoglinBase.java -@@ -40,7 +40,7 @@ public interface HoglinBase { - double j = f * (double)(attacker.level().random.nextFloat() * 0.5F + 0.2F); - Vec3 vec3 = (new Vec3(g, 0.0D, h)).normalize().scale(j).yRot(i); - double k = f * (double)attacker.level().random.nextFloat() * 0.5D; -- target.push(vec3.x, k, vec3.z); -+ target.push(vec3.x, k, vec3.z, attacker); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - target.hurtMarked = true; - } - } -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 f28e9f06596cea6904b407fa87835f21d07817fa..5f59c7436756645a74ea730b026deb25d1571c9c 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1275,9 +1275,9 @@ public abstract class Player extends LivingEntity { - if (flag5) { - if (i > 0) { - if (target instanceof LivingEntity) { -- ((LivingEntity) target).knockback((double) ((float) i * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F))); -+ ((LivingEntity) target).knockback((double) ((float) i * 0.5F), (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - } else { -- target.push((double) (-Mth.sin(this.getYRot() * 0.017453292F) * (float) i * 0.5F), 0.1D, (double) (Mth.cos(this.getYRot() * 0.017453292F) * (float) i * 0.5F)); -+ target.push((double) (-Mth.sin(this.getYRot() * 0.017453292F) * (float) i * 0.5F), 0.1D, (double) (Mth.cos(this.getYRot() * 0.017453292F) * (float) i * 0.5F), this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - } - - this.setDeltaMovement(this.getDeltaMovement().multiply(0.6D, 1.0D, 0.6D)); -@@ -1299,7 +1299,7 @@ public abstract class Player extends LivingEntity { - if (entityliving != this && entityliving != target && !this.isAlliedTo((Entity) entityliving) && (!(entityliving instanceof ArmorStand) || !((ArmorStand) entityliving).isMarker()) && this.distanceToSqr((Entity) entityliving) < 9.0D) { - // CraftBukkit start - Only apply knockback if the damage hits - if (entityliving.hurt(this.damageSources().playerAttack(this).sweep(), f4)) { -- entityliving.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F))); -+ entityliving.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - } - // CraftBukkit end - } -diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index 0f700442a7559fac5d27d1fb6b3716f3853a9897..f3861cea4eb6a39fa16936383f8dabc6689a58a9 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -409,7 +409,7 @@ public abstract class AbstractArrow extends Projectile { - Vec3 vec3d = this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D).normalize().scale((double) this.knockback * 0.6D * d0); - - if (vec3d.lengthSqr() > 0.0D) { -- entityliving.push(vec3d.x, 0.1D, vec3d.z); -+ entityliving.push(vec3d.x, 0.1D, vec3d.z, this); // Paper - } - } - diff --git a/patches/server/0203-Expand-Explosions-API.patch b/patches/server/0203-Expand-Explosions-API.patch new file mode 100644 index 000000000000..c181cd3402ff --- /dev/null +++ b/patches/server/0203-Expand-Explosions-API.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 20 Jun 2018 23:17:24 -0400 +Subject: [PATCH] Expand Explosions API + +Add Entity as a Source capability, and add more API choices, and on Location. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 012c0bf4f824543d475e97e0edb6c4fe88d046e5..b5bf92b668be5bdbd6af722929aee28eb1bf5ffa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -735,6 +735,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source) { + return !this.world.explode(source == null ? null : ((CraftEntity) source).getHandle(), x, y, z, power, setFire, breakBlocks ? net.minecraft.world.level.Level.ExplosionInteraction.MOB : net.minecraft.world.level.Level.ExplosionInteraction.NONE).wasCanceled; + } ++ // Paper start ++ @Override ++ public boolean createExplosion(Entity source, Location loc, float power, boolean setFire, boolean breakBlocks) { ++ return !world.explode(source != null ? ((org.bukkit.craftbukkit.entity.CraftEntity) source).getHandle() : null, loc.getX(), loc.getY(), loc.getZ(), power, setFire, breakBlocks ? net.minecraft.world.level.Level.ExplosionInteraction.MOB : net.minecraft.world.level.Level.ExplosionInteraction.NONE).wasCanceled; ++ } ++ // Paper end + + @Override + public boolean createExplosion(Location loc, float power) { diff --git a/patches/server/0204-Expand-Explosions-API.patch b/patches/server/0204-Expand-Explosions-API.patch deleted file mode 100644 index c91ea25247d9..000000000000 --- a/patches/server/0204-Expand-Explosions-API.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 20 Jun 2018 23:17:24 -0400 -Subject: [PATCH] Expand Explosions API - -Add Entity as a Source capability, and add more API choices, and on Location. - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 8fbc232ce409951d67eee1dbe34bc5bf4d069d01..4f631eced3403df0339d3d7b4a49b5c8f68408dd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -729,6 +729,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source) { - return !this.world.explode(source == null ? null : ((CraftEntity) source).getHandle(), x, y, z, power, setFire, breakBlocks ? net.minecraft.world.level.Level.ExplosionInteraction.MOB : net.minecraft.world.level.Level.ExplosionInteraction.NONE).wasCanceled; - } -+ // Paper start -+ @Override -+ public boolean createExplosion(Entity source, Location loc, float power, boolean setFire, boolean breakBlocks) { -+ return !world.explode(source != null ? ((org.bukkit.craftbukkit.entity.CraftEntity) source).getHandle() : null, loc.getX(), loc.getY(), loc.getZ(), power, setFire, breakBlocks ? net.minecraft.world.level.Level.ExplosionInteraction.MOB : net.minecraft.world.level.Level.ExplosionInteraction.NONE).wasCanceled; -+ } -+ // Paper end - - @Override - public boolean createExplosion(Location loc, float power) { diff --git a/patches/server/0204-LivingEntity-Hand-Raised-Item-Use-API.patch b/patches/server/0204-LivingEntity-Hand-Raised-Item-Use-API.patch new file mode 100644 index 000000000000..586af96db360 --- /dev/null +++ b/patches/server/0204-LivingEntity-Hand-Raised-Item-Use-API.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 29 Jun 2018 00:21:28 -0400 +Subject: [PATCH] LivingEntity Hand Raised/Item Use API + +How long an entity has raised hands to charge an attack or use an item + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index ca92ece290c6dd164b3ba2ba46289350ae6829e8..8ac3beab73904db7362caa3054994b214c997cf7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -849,5 +849,30 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void setShieldBlockingDelay(int delay) { + getHandle().setShieldBlockingDelay(delay); + } ++ ++ @Override ++ public ItemStack getActiveItem() { ++ return getHandle().getUseItem().asBukkitMirror(); ++ } ++ ++ @Override ++ public int getItemUseRemainingTime() { ++ return getHandle().getUseItemRemainingTicks(); ++ } ++ ++ @Override ++ public int getHandRaisedTime() { ++ return getHandle().getTicksUsingItem(); ++ } ++ ++ @Override ++ public boolean isHandRaised() { ++ return getHandle().isUsingItem(); ++ } ++ ++ @Override ++ public org.bukkit.inventory.EquipmentSlot getHandRaised() { ++ return getHandle().getUsedItemHand() == net.minecraft.world.InteractionHand.MAIN_HAND ? org.bukkit.inventory.EquipmentSlot.HAND : org.bukkit.inventory.EquipmentSlot.OFF_HAND; ++ } + // Paper end + } diff --git a/patches/server/0205-LivingEntity-Hand-Raised-Item-Use-API.patch b/patches/server/0205-LivingEntity-Hand-Raised-Item-Use-API.patch deleted file mode 100644 index 189a3bdc7b95..000000000000 --- a/patches/server/0205-LivingEntity-Hand-Raised-Item-Use-API.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 29 Jun 2018 00:21:28 -0400 -Subject: [PATCH] LivingEntity Hand Raised/Item Use API - -How long an entity has raised hands to charge an attack or use an item - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 30c6d32040decc56947d7e8a840036262e122137..0c561653f81dbc64cc78b4a27185aa3de1f9946b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -836,5 +836,30 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - public void setShieldBlockingDelay(int delay) { - getHandle().setShieldBlockingDelay(delay); - } -+ -+ @Override -+ public ItemStack getActiveItem() { -+ return getHandle().getUseItem().asBukkitMirror(); -+ } -+ -+ @Override -+ public int getItemUseRemainingTime() { -+ return getHandle().getUseItemRemainingTicks(); -+ } -+ -+ @Override -+ public int getHandRaisedTime() { -+ return getHandle().getTicksUsingItem(); -+ } -+ -+ @Override -+ public boolean isHandRaised() { -+ return getHandle().isUsingItem(); -+ } -+ -+ @Override -+ public org.bukkit.inventory.EquipmentSlot getHandRaised() { -+ return getHandle().getUsedItemHand() == net.minecraft.world.InteractionHand.MAIN_HAND ? org.bukkit.inventory.EquipmentSlot.HAND : org.bukkit.inventory.EquipmentSlot.OFF_HAND; -+ } - // Paper end - } diff --git a/patches/server/0206-RangedEntity-API.patch b/patches/server/0205-RangedEntity-API.patch similarity index 100% rename from patches/server/0206-RangedEntity-API.patch rename to patches/server/0205-RangedEntity-API.patch diff --git a/patches/server/0206-Add-config-to-disable-ender-dragon-legacy-check.patch b/patches/server/0206-Add-config-to-disable-ender-dragon-legacy-check.patch new file mode 100644 index 000000000000..8a9728d94f1f --- /dev/null +++ b/patches/server/0206-Add-config-to-disable-ender-dragon-legacy-check.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 22 Jun 2018 10:38:31 -0500 +Subject: [PATCH] Add config to disable ender dragon legacy check + + +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 38c2aed0785b3b5bcceba572a1a6f5fb0224964d..cad7a1b28c9d7a3e67dbf0865cbf232ebd39a8d9 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 +@@ -106,6 +106,10 @@ public class EndDragonFight { + this.ticksSinceLastPlayerScan = 21; + this.skipArenaLoadedCheck = false; + this.needsStateScanning = true; ++ // Paper start - Add config to disable ender dragon legacy check ++ this.needsStateScanning = world.paperConfig().entities.spawning.scanForLegacyEnderDragon; ++ if (!this.needsStateScanning) this.dragonKilled = true; ++ // Paper end - Add config to disable ender dragon legacy check + this.level = world; + this.origin = origin; + this.validPlayer = EntitySelector.ENTITY_STILL_ALIVE.and(EntitySelector.withinDistance((double) origin.getX(), (double) (128 + origin.getY()), (double) origin.getZ(), 192.0D)); diff --git a/patches/server/0207-Add-config-to-disable-ender-dragon-legacy-check.patch b/patches/server/0207-Add-config-to-disable-ender-dragon-legacy-check.patch deleted file mode 100644 index 719410ac4b02..000000000000 --- a/patches/server/0207-Add-config-to-disable-ender-dragon-legacy-check.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Fri, 22 Jun 2018 10:38:31 -0500 -Subject: [PATCH] Add config to disable ender dragon legacy check - - -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 f310098b0a59fa5341198bb8282b23dfa4a7fc3a..d6b6c83a6d660107956a28c16fde2260583722aa 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 -@@ -106,6 +106,10 @@ public class EndDragonFight { - this.ticksSinceLastPlayerScan = 21; - this.skipArenaLoadedCheck = false; - this.needsStateScanning = true; -+ // Paper start - Add config to disable ender dragon legacy check -+ this.needsStateScanning = world.paperConfig().entities.spawning.scanForLegacyEnderDragon; -+ if (!this.needsStateScanning) this.dragonKilled = true; -+ // Paper end - Add config to disable ender dragon legacy check - this.level = world; - this.origin = origin; - this.validPlayer = EntitySelector.ENTITY_STILL_ALIVE.and(EntitySelector.withinDistance((double) origin.getX(), (double) (128 + origin.getY()), (double) origin.getZ(), 192.0D)); diff --git a/patches/server/0207-Implement-World.getEntity-UUID-API.patch b/patches/server/0207-Implement-World.getEntity-UUID-API.patch new file mode 100644 index 000000000000..9f5b8131e3d5 --- /dev/null +++ b/patches/server/0207-Implement-World.getEntity-UUID-API.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Tue, 3 Jul 2018 16:08:14 +0200 +Subject: [PATCH] Implement World.getEntity(UUID) API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index b5bf92b668be5bdbd6af722929aee28eb1bf5ffa..3fe84fa65961dff58a416f7262c8b8ce218a3b49 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1071,6 +1071,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return list; + } + ++ // Paper start - getEntity by UUID API ++ @Override ++ public Entity getEntity(UUID uuid) { ++ Preconditions.checkArgument(uuid != null, "UUID cannot be null"); ++ net.minecraft.world.entity.Entity entity = world.getEntity(uuid); ++ return entity == null ? null : entity.getBukkitEntity(); ++ } ++ // Paper end ++ + @Override + public void save() { + org.spigotmc.AsyncCatcher.catchOp("world save"); // Spigot diff --git a/patches/server/0208-Implement-World.getEntity-UUID-API.patch b/patches/server/0208-Implement-World.getEntity-UUID-API.patch deleted file mode 100644 index 5f8874e80e15..000000000000 --- a/patches/server/0208-Implement-World.getEntity-UUID-API.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Brokkonaut -Date: Tue, 3 Jul 2018 16:08:14 +0200 -Subject: [PATCH] Implement World.getEntity(UUID) API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 4f631eced3403df0339d3d7b4a49b5c8f68408dd..845e16bec244a59a89d790363ddd5fe1d7bfdbf7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1058,6 +1058,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return list; - } - -+ // Paper start - getEntity by UUID API -+ @Override -+ public Entity getEntity(UUID uuid) { -+ Preconditions.checkArgument(uuid != null, "UUID cannot be null"); -+ net.minecraft.world.entity.Entity entity = world.getEntity(uuid); -+ return entity == null ? null : entity.getBukkitEntity(); -+ } -+ // Paper end -+ - @Override - public void save() { - org.spigotmc.AsyncCatcher.catchOp("world save"); // Spigot diff --git a/patches/server/0208-InventoryCloseEvent-Reason-API.patch b/patches/server/0208-InventoryCloseEvent-Reason-API.patch new file mode 100644 index 000000000000..053274315609 --- /dev/null +++ b/patches/server/0208-InventoryCloseEvent-Reason-API.patch @@ -0,0 +1,212 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 3 Jul 2018 21:56:23 -0400 +Subject: [PATCH] InventoryCloseEvent Reason API + +Allows you to determine why an inventory was closed, enabling plugin developers +to "confirm" things based on if it was player triggered close or not. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 180a9ade7bbc84d8c64b6c92583ba870464c23a5..a6749080a192a591dd4e52f56b691df8d9cfcd98 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1235,7 +1235,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) { + if (tileentity instanceof net.minecraft.world.Container) { + for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { +- h.closeInventory(); ++ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason + } + } + } +@@ -2204,7 +2204,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + // Spigot Start + if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message + for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) { +- h.closeInventory(); ++ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason + } + } + // Spigot End +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 19cd69fc911bb9b95257b1fdc19645bbdfe05de3..38a3b264ba72631c27203a178ac0bbdd36e10a10 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -648,7 +648,7 @@ public class ServerPlayer extends Player { + } + // Paper end - Configurable container update tick rate + if (!this.level().isClientSide && !this.containerMenu.stillValid(this)) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason + this.containerMenu = this.inventoryMenu; + } + +@@ -841,7 +841,7 @@ public class ServerPlayer extends Player { + + // SPIGOT-943 - only call if they have an inventory open + if (this.containerMenu != this.inventoryMenu) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DEATH); // Paper - Inventory close reason + } + + net.kyori.adventure.text.Component deathMessage = event.deathMessage() != null ? event.deathMessage() : net.kyori.adventure.text.Component.empty(); // Paper - Adventure +@@ -1465,7 +1465,7 @@ public class ServerPlayer extends Player { + } + // CraftBukkit end + if (this.containerMenu != this.inventoryMenu) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason + } + + // this.nextContainerCounter(); // CraftBukkit - moved up +@@ -1493,7 +1493,13 @@ public class ServerPlayer extends Player { + + @Override + public void closeContainer() { +- CraftEventFactory.handleInventoryCloseEvent(this); // CraftBukkit ++ // Paper start - Inventory close reason ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); ++ } ++ @Override ++ public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit ++ // Paper end - Inventory close reason + this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); + this.doCloseContainer(); + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 39af3e197502c1f262fbdd4e06dc0e3fc7537b77..f9e961bc76f0ae1d7737f3840dfc667567f969d0 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2526,10 +2526,15 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + @Override + public void handleContainerClose(ServerboundContainerClosePacket packet) { ++ // Paper start - Inventory close reason ++ this.handleContainerClose(packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason.PLAYER); ++ } ++ public void handleContainerClose(ServerboundContainerClosePacket packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // Paper end - Inventory close reason + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + + if (this.player.isImmobile()) return; // CraftBukkit +- CraftEventFactory.handleInventoryCloseEvent(this.player); // CraftBukkit ++ CraftEventFactory.handleInventoryCloseEvent(this.player, reason); // CraftBukkit // Paper + + this.player.doCloseContainer(); + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 9f8a95c8f46a11f36ff16863922a91a8d81d0bb3..e68a0c86f73325189a67186b058062509663f6e7 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -518,7 +518,7 @@ public abstract class PlayerList { + // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it + // See SPIGOT-5799, SPIGOT-6145 + if (entityplayer.containerMenu != entityplayer.inventoryMenu) { +- entityplayer.closeContainer(); ++ entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), 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()))); // Paper - Adventure +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 de537f4e89ccadefafb0f3e6075dba5f030f2654..c794fa6290be9904f3e97e74be9959f243c00f58 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -264,7 +264,7 @@ public abstract class Player extends LivingEntity { + this.updateIsUnderwater(); + super.tick(); + if (!this.level().isClientSide && this.containerMenu != null && !this.containerMenu.stillValid(this)) { +- this.closeContainer(); ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason + this.containerMenu = this.inventoryMenu; + } + +@@ -496,6 +496,13 @@ public abstract class Player extends LivingEntity { + + } + ++ // Paper start - Inventory close reason; unused code, but to keep signatures aligned ++ public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ closeContainer(); ++ this.containerMenu = this.inventoryMenu; ++ } ++ // Paper end - Inventory close reason ++ + public void closeContainer() { + this.containerMenu = this.inventoryMenu; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 092d2bfab7e8fd9840e853f09adc85311ab29046..af6f8d11f0384c353332f5c5bb0967e518deccbe 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -376,7 +376,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + if (((ServerPlayer) this.getHandle()).connection == null) return; + if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { + // fire INVENTORY_CLOSE if one already open +- ((ServerPlayer) this.getHandle()).connection.handleContainerClose(new ServerboundContainerClosePacket(this.getHandle().containerMenu.containerId)); ++ ((ServerPlayer) this.getHandle()).connection.handleContainerClose(new ServerboundContainerClosePacket(this.getHandle().containerMenu.containerId), org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason + } + ServerPlayer player = (ServerPlayer) this.getHandle(); + AbstractContainerMenu container; +@@ -446,8 +446,14 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + + @Override + public void closeInventory() { +- this.getHandle().closeContainer(); ++ // Paper start - Inventory close reason ++ this.getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.PLUGIN); + } ++ @Override ++ public void closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ getHandle().closeContainer(reason); ++ } ++ // Paper end - Inventory close reason + + @Override + public boolean isBlocking() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index a77c15a9c351c9cd5e8b4832016ec6de30483ed6..ddf089d73a05792d99a96bb449717d82755f5cd9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1182,7 +1182,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + // Close any foreign inventory + if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { +- this.getHandle().closeContainer(); ++ this.getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); // Paper - Inventory close reason + } + + // Check if the fromWorld and toWorld are the same. +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index e0234ea1d55e918072574fb5d4b6cc75fc5d46d6..9e2007967d093d7a72e980f578657e4bcb9badae 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1262,7 +1262,7 @@ public class CraftEventFactory { + + public static AbstractContainerMenu callInventoryOpenEvent(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { + if (player.containerMenu != player.inventoryMenu) { // fire INVENTORY_CLOSE if one already open +- player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId)); ++ player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId), InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason + } + + CraftServer server = player.level().getCraftServer(); +@@ -1449,8 +1449,18 @@ public class CraftEventFactory { + return event; + } + ++ // Paper start ++ /** ++ * Incase plugins hooked into this or Spigot adds a new inventory close event. Prefer to pass a reason ++ * @param human ++ */ ++ @Deprecated + public static void handleInventoryCloseEvent(net.minecraft.world.entity.player.Player human) { +- InventoryCloseEvent event = new InventoryCloseEvent(human.containerMenu.getBukkitView()); ++ handleInventoryCloseEvent(human, org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); ++ } ++ public static void handleInventoryCloseEvent(net.minecraft.world.entity.player.Player human, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // Paper end ++ InventoryCloseEvent event = new InventoryCloseEvent(human.containerMenu.getBukkitView(), reason); // Paper + human.level().getCraftServer().getPluginManager().callEvent(event); + human.containerMenu.transferTo(human.inventoryMenu, human.getBukkitEntity()); + } diff --git a/patches/server/0209-InventoryCloseEvent-Reason-API.patch b/patches/server/0209-InventoryCloseEvent-Reason-API.patch deleted file mode 100644 index e7a3e3b0ff50..000000000000 --- a/patches/server/0209-InventoryCloseEvent-Reason-API.patch +++ /dev/null @@ -1,212 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 3 Jul 2018 21:56:23 -0400 -Subject: [PATCH] InventoryCloseEvent Reason API - -Allows you to determine why an inventory was closed, enabling plugin developers -to "confirm" things based on if it was player triggered close or not. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 180a9ade7bbc84d8c64b6c92583ba870464c23a5..a6749080a192a591dd4e52f56b691df8d9cfcd98 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1235,7 +1235,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) { - if (tileentity instanceof net.minecraft.world.Container) { - for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { -- h.closeInventory(); -+ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason - } - } - } -@@ -2204,7 +2204,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - // Spigot Start - if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message - for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) { -- h.closeInventory(); -+ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason - } - } - // Spigot End -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index d51244c634d0209efb98be965ce7318480220b96..33108a55c1cc305d44238d0862755af874f19395 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -648,7 +648,7 @@ public class ServerPlayer extends Player { - } - // Paper end - Configurable container update tick rate - if (!this.level().isClientSide && !this.containerMenu.stillValid(this)) { -- this.closeContainer(); -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason - this.containerMenu = this.inventoryMenu; - } - -@@ -841,7 +841,7 @@ public class ServerPlayer extends Player { - - // SPIGOT-943 - only call if they have an inventory open - if (this.containerMenu != this.inventoryMenu) { -- this.closeContainer(); -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DEATH); // Paper - Inventory close reason - } - - net.kyori.adventure.text.Component deathMessage = event.deathMessage() != null ? event.deathMessage() : net.kyori.adventure.text.Component.empty(); // Paper - Adventure -@@ -1465,7 +1465,7 @@ public class ServerPlayer extends Player { - } - // CraftBukkit end - if (this.containerMenu != this.inventoryMenu) { -- this.closeContainer(); -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason - } - - // this.nextContainerCounter(); // CraftBukkit - moved up -@@ -1493,7 +1493,13 @@ public class ServerPlayer extends Player { - - @Override - public void closeContainer() { -- CraftEventFactory.handleInventoryCloseEvent(this); // CraftBukkit -+ // Paper start - Inventory close reason -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); -+ } -+ @Override -+ public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit -+ // Paper end - Inventory close reason - this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); - this.doCloseContainer(); - } -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 1da44846ad1ed2768fd7ebca4017c4bbc399ba68..6bd81598a45fd0b428740f7be674adfe0c26aa05 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2526,10 +2526,15 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - @Override - public void handleContainerClose(ServerboundContainerClosePacket packet) { -+ // Paper start - Inventory close reason -+ this.handleContainerClose(packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason.PLAYER); -+ } -+ public void handleContainerClose(ServerboundContainerClosePacket packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ // Paper end - Inventory close reason - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - - if (this.player.isImmobile()) return; // CraftBukkit -- CraftEventFactory.handleInventoryCloseEvent(this.player); // CraftBukkit -+ CraftEventFactory.handleInventoryCloseEvent(this.player, reason); // CraftBukkit // Paper - - this.player.doCloseContainer(); - } -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 9f8a95c8f46a11f36ff16863922a91a8d81d0bb3..e68a0c86f73325189a67186b058062509663f6e7 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -518,7 +518,7 @@ public abstract class PlayerList { - // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it - // See SPIGOT-5799, SPIGOT-6145 - if (entityplayer.containerMenu != entityplayer.inventoryMenu) { -- entityplayer.closeContainer(); -+ entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason - } - - PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), 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()))); // Paper - Adventure -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 5f59c7436756645a74ea730b026deb25d1571c9c..fb4cc32f2840098a13981ec4328e7eb6fe4f714b 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -263,7 +263,7 @@ public abstract class Player extends LivingEntity { - this.updateIsUnderwater(); - super.tick(); - if (!this.level().isClientSide && this.containerMenu != null && !this.containerMenu.stillValid(this)) { -- this.closeContainer(); -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason - this.containerMenu = this.inventoryMenu; - } - -@@ -495,6 +495,13 @@ public abstract class Player extends LivingEntity { - - } - -+ // Paper start - Inventory close reason; unused code, but to keep signatures aligned -+ public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ closeContainer(); -+ this.containerMenu = this.inventoryMenu; -+ } -+ // Paper end - Inventory close reason -+ - public void closeContainer() { - this.containerMenu = this.inventoryMenu; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 092d2bfab7e8fd9840e853f09adc85311ab29046..af6f8d11f0384c353332f5c5bb0967e518deccbe 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -376,7 +376,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - if (((ServerPlayer) this.getHandle()).connection == null) return; - if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { - // fire INVENTORY_CLOSE if one already open -- ((ServerPlayer) this.getHandle()).connection.handleContainerClose(new ServerboundContainerClosePacket(this.getHandle().containerMenu.containerId)); -+ ((ServerPlayer) this.getHandle()).connection.handleContainerClose(new ServerboundContainerClosePacket(this.getHandle().containerMenu.containerId), org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason - } - ServerPlayer player = (ServerPlayer) this.getHandle(); - AbstractContainerMenu container; -@@ -446,8 +446,14 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - - @Override - public void closeInventory() { -- this.getHandle().closeContainer(); -+ // Paper start - Inventory close reason -+ this.getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.PLUGIN); - } -+ @Override -+ public void closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ getHandle().closeContainer(reason); -+ } -+ // Paper end - Inventory close reason - - @Override - public boolean isBlocking() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 4d0d2b1bc24a0d56724d7062bdbc3c7cf78e2b8a..047be06ad290e6e066689fe3b95902a3459304b3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1152,7 +1152,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - // Close any foreign inventory - if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { -- this.getHandle().closeContainer(); -+ this.getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); // Paper - Inventory close reason - } - - // Check if the fromWorld and toWorld are the same. -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 83efc53d933e6de7ee414e89226d3a0cb54e3cf3..de7971320df8cb1c00c6a836a5c3c953a10f9a69 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1331,7 +1331,7 @@ public class CraftEventFactory { - - public static AbstractContainerMenu callInventoryOpenEvent(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { - if (player.containerMenu != player.inventoryMenu) { // fire INVENTORY_CLOSE if one already open -- player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId)); -+ player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId), InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason - } - - CraftServer server = player.level().getCraftServer(); -@@ -1518,8 +1518,18 @@ public class CraftEventFactory { - return event; - } - -+ // Paper start -+ /** -+ * Incase plugins hooked into this or Spigot adds a new inventory close event. Prefer to pass a reason -+ * @param human -+ */ -+ @Deprecated - public static void handleInventoryCloseEvent(net.minecraft.world.entity.player.Player human) { -- InventoryCloseEvent event = new InventoryCloseEvent(human.containerMenu.getBukkitView()); -+ handleInventoryCloseEvent(human, org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); -+ } -+ public static void handleInventoryCloseEvent(net.minecraft.world.entity.player.Player human, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ // Paper end -+ InventoryCloseEvent event = new InventoryCloseEvent(human.containerMenu.getBukkitView(), reason); // Paper - human.level().getCraftServer().getPluginManager().callEvent(event); - human.containerMenu.transferTo(human.inventoryMenu, human.getBukkitEntity()); - } diff --git a/patches/server/0210-Vex-get-setSummoner-API.patch b/patches/server/0209-Vex-get-setSummoner-API.patch similarity index 100% rename from patches/server/0210-Vex-get-setSummoner-API.patch rename to patches/server/0209-Vex-get-setSummoner-API.patch diff --git a/patches/server/0210-Refresh-player-inventory-when-cancelling-PlayerInter.patch b/patches/server/0210-Refresh-player-inventory-when-cancelling-PlayerInter.patch new file mode 100644 index 000000000000..20ce8db8a725 --- /dev/null +++ b/patches/server/0210-Refresh-player-inventory-when-cancelling-PlayerInter.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Fri, 13 Jul 2018 14:54:43 +0200 +Subject: [PATCH] Refresh player inventory when cancelling + PlayerInteractEntityEvent + +When interacting with entities with an item, the client will assume +the interaction is successful, and update the held item on the +client. However, if the interaction is cancelled on the server side, +the client will still mistakenly remove/replace the item in hand. + +Examples for this are milking cows with a bucket or dyeing sheep. +The bucket is replaced with milk and the dye removed from inventory. + +Refresh the player inventory when PlayerInteractEntityEvent is +cancelled to avoid this problem. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f9e961bc76f0ae1d7737f3840dfc667567f969d0..3d6355f50d04035f62fa9eddee076d0a157c89a6 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2417,6 +2417,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + if (event.isCancelled()) { ++ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); // Paper - Refresh player inventory + return; + } + // CraftBukkit end diff --git a/patches/server/0211-Refresh-player-inventory-when-cancelling-PlayerInter.patch b/patches/server/0211-Refresh-player-inventory-when-cancelling-PlayerInter.patch deleted file mode 100644 index 6a911ce3321c..000000000000 --- a/patches/server/0211-Refresh-player-inventory-when-cancelling-PlayerInter.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Minecrell -Date: Fri, 13 Jul 2018 14:54:43 +0200 -Subject: [PATCH] Refresh player inventory when cancelling - PlayerInteractEntityEvent - -When interacting with entities with an item, the client will assume -the interaction is successful, and update the held item on the -client. However, if the interaction is cancelled on the server side, -the client will still mistakenly remove/replace the item in hand. - -Examples for this are milking cows with a bucket or dyeing sheep. -The bucket is replaced with milk and the dye removed from inventory. - -Refresh the player inventory when PlayerInteractEntityEvent is -cancelled to avoid this problem. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 6bd81598a45fd0b428740f7be674adfe0c26aa05..60443fecfc6956e9863d8cf3cf74be447d48ba34 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2417,6 +2417,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - if (event.isCancelled()) { -+ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); // Paper - Refresh player inventory - return; - } - // CraftBukkit end diff --git a/patches/server/0212-Use-AsyncAppender-to-keep-logging-IO-off-main-thread.patch b/patches/server/0211-Use-AsyncAppender-to-keep-logging-IO-off-main-thread.patch similarity index 100% rename from patches/server/0212-Use-AsyncAppender-to-keep-logging-IO-off-main-thread.patch rename to patches/server/0211-Use-AsyncAppender-to-keep-logging-IO-off-main-thread.patch diff --git a/patches/server/0212-add-more-information-to-Entity.toString.patch b/patches/server/0212-add-more-information-to-Entity.toString.patch new file mode 100644 index 000000000000..5c2362281872 --- /dev/null +++ b/patches/server/0212-add-more-information-to-Entity.toString.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 19 Jul 2018 01:13:28 -0400 +Subject: [PATCH] add more information to Entity.toString() + +UUID, ticks lived, valid, dead + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index cf907e0c1a89639639b6453032f8f6fe4e05a81c..dadf26ee5b6be851473429dbef703037f0c0d9fe 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3153,7 +3153,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + public String toString() { + String s = this.level() == null ? "~NULL~" : this.level().toString(); + +- return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ(), this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ()); ++ return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid, this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid); // Paper - add more info + } + + public boolean isInvulnerableTo(DamageSource damageSource) { diff --git a/patches/server/0214-EnderDragon-Events.patch b/patches/server/0213-EnderDragon-Events.patch similarity index 100% rename from patches/server/0214-EnderDragon-Events.patch rename to patches/server/0213-EnderDragon-Events.patch diff --git a/patches/server/0213-add-more-information-to-Entity.toString.patch b/patches/server/0213-add-more-information-to-Entity.toString.patch deleted file mode 100644 index dfdc05060964..000000000000 --- a/patches/server/0213-add-more-information-to-Entity.toString.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 19 Jul 2018 01:13:28 -0400 -Subject: [PATCH] add more information to Entity.toString() - -UUID, ticks lived, valid, dead - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 94c6914a7af6329e4bed8a8d470563b7901fb248..7abb4ba10029ad8df5ed520d1d3deb9d2819ca8d 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3150,7 +3150,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - public String toString() { - String s = this.level() == null ? "~NULL~" : this.level().toString(); - -- return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ(), this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, l='%s', x=%.2f, y=%.2f, z=%.2f]", this.getClass().getSimpleName(), this.getName().getString(), this.id, s, this.getX(), this.getY(), this.getZ()); -+ return this.removalReason != null ? String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b, removed=%s]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid, this.removalReason) : String.format(Locale.ROOT, "%s['%s'/%d, uuid='%s', l='%s', x=%.2f, y=%.2f, z=%.2f, cpos=%s, tl=%d, v=%b]", this.getClass().getSimpleName(), this.getName().getString(), this.id, this.uuid, s, this.getX(), this.getY(), this.getZ(), this.chunkPosition(), this.tickCount, this.valid); // Paper - add more info - } - - public boolean isInvulnerableTo(DamageSource damageSource) { diff --git a/patches/server/0215-PlayerElytraBoostEvent.patch b/patches/server/0214-PlayerElytraBoostEvent.patch similarity index 100% rename from patches/server/0215-PlayerElytraBoostEvent.patch rename to patches/server/0214-PlayerElytraBoostEvent.patch diff --git a/patches/server/0216-PlayerLaunchProjectileEvent.patch b/patches/server/0215-PlayerLaunchProjectileEvent.patch similarity index 100% rename from patches/server/0216-PlayerLaunchProjectileEvent.patch rename to patches/server/0215-PlayerLaunchProjectileEvent.patch diff --git a/patches/server/0217-Improve-BlockPosition-inlining.patch b/patches/server/0216-Improve-BlockPosition-inlining.patch similarity index 100% rename from patches/server/0217-Improve-BlockPosition-inlining.patch rename to patches/server/0216-Improve-BlockPosition-inlining.patch diff --git a/patches/server/0218-Option-to-prevent-armor-stands-from-doing-entity-loo.patch b/patches/server/0217-Option-to-prevent-armor-stands-from-doing-entity-loo.patch similarity index 100% rename from patches/server/0218-Option-to-prevent-armor-stands-from-doing-entity-loo.patch rename to patches/server/0217-Option-to-prevent-armor-stands-from-doing-entity-loo.patch diff --git a/patches/server/0218-Vanished-players-don-t-have-rights.patch b/patches/server/0218-Vanished-players-don-t-have-rights.patch new file mode 100644 index 000000000000..1b1fe50a9604 --- /dev/null +++ b/patches/server/0218-Vanished-players-don-t-have-rights.patch @@ -0,0 +1,109 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hugo Manrique +Date: Mon, 23 Jul 2018 14:22:26 +0200 +Subject: [PATCH] Vanished players don't have rights + + +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 e432855ce79f69c0e91fa31e8f6c59a465b0d09e..4ebc38d0666d01c67c2728355fbbef296a0672e3 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -237,6 +237,15 @@ public abstract class Projectile extends Entity implements TraceableEntity { + } else { + Entity entity1 = this.getOwner(); + ++ // Paper start - Cancel hit for vanished players ++ if (entity1 instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer) { ++ org.bukkit.entity.Player collided = (org.bukkit.entity.Player) entity.getBukkitEntity(); ++ org.bukkit.entity.Player shooter = (org.bukkit.entity.Player) entity1.getBukkitEntity(); ++ if (!shooter.canSee(collided)) { ++ return false; ++ } ++ } ++ // Paper end - Cancel hit for vanished players + return entity1 == null || this.leftOwner || !entity1.isPassengerOfSameVehicle(entity); + } + } +diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java +index a6638e626600e4304a973497a39e3fac52203b16..c19069f65a9ecbc9000ea4333417a2df4ace1007 100644 +--- a/src/main/java/net/minecraft/world/item/BlockItem.java ++++ b/src/main/java/net/minecraft/world/item/BlockItem.java +@@ -197,7 +197,8 @@ public class BlockItem extends Item { + Player entityhuman = context.getPlayer(); + CollisionContext voxelshapecollision = entityhuman == null ? CollisionContext.empty() : CollisionContext.of(entityhuman); + // CraftBukkit start - store default return +- boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && context.getLevel().isUnobstructed(state, context.getClickedPos(), voxelshapecollision); ++ Level world = context.getLevel(); // Paper - Cancel hit for vanished players ++ boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && world.checkEntityCollision(state, entityhuman, voxelshapecollision, context.getClickedPos(), true); // Paper - Cancel hit for vanished players + org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null; + + BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(context.getLevel(), context.getClickedPos()), player, CraftBlockData.fromData(state), defaultReturn); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 37c50c9da4249a92811aaa5cef5743ccefdcb4a8..181201ecd6c617cc37ba097a667bd96ae3b1823e 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -271,6 +271,45 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); + } + ++ // Paper start - Cancel hit for vanished players ++ // ret true if no collision ++ public final boolean checkEntityCollision(BlockState data, Entity source, net.minecraft.world.phys.shapes.CollisionContext voxelshapedcollision, ++ BlockPos position, boolean checkCanSee) { ++ // Copied from IWorldReader#a(IBlockData, BlockPosition, VoxelShapeCollision) & EntityAccess#a(Entity, VoxelShape) ++ net.minecraft.world.phys.shapes.VoxelShape voxelshape = data.getCollisionShape(this, position, voxelshapedcollision); ++ if (voxelshape.isEmpty()) { ++ return true; ++ } ++ ++ voxelshape = voxelshape.move((double) position.getX(), (double) position.getY(), (double) position.getZ()); ++ if (voxelshape.isEmpty()) { ++ return true; ++ } ++ ++ List entities = this.getEntities(null, voxelshape.bounds()); ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ Entity entity = entities.get(i); ++ ++ if (checkCanSee && source instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer ++ && !((net.minecraft.server.level.ServerPlayer) source).getBukkitEntity().canSee(((net.minecraft.server.level.ServerPlayer) entity).getBukkitEntity())) { ++ continue; ++ } ++ ++ // !entity1.dead && entity1.i && (entity == null || !entity1.x(entity)); ++ // elide the last check since vanilla calls with entity = null ++ // only we care about the source for the canSee check ++ if (entity.isRemoved() || !entity.blocksBuilding) { ++ continue; ++ } ++ ++ if (net.minecraft.world.phys.shapes.Shapes.joinIsNotEmpty(voxelshape, net.minecraft.world.phys.shapes.Shapes.create(entity.getBoundingBox()), net.minecraft.world.phys.shapes.BooleanOp.AND)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ // Paper end - Cancel hit for vanished players + @Override + public boolean isClientSide() { + return this.isClientSide; +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 9e2007967d093d7a72e980f578657e4bcb9badae..9d95dba058dd26db42d9e32b90399a78211bcd79 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1299,6 +1299,14 @@ public class CraftEventFactory { + Projectile projectile = (Projectile) entity.getBukkitEntity(); + org.bukkit.entity.Entity collided = position.getEntity().getBukkitEntity(); + com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = new com.destroystokyo.paper.event.entity.ProjectileCollideEvent(projectile, collided); ++ ++ if (projectile.getShooter() instanceof Player && collided instanceof Player) { ++ if (!((Player) projectile.getShooter()).canSee((Player) collided)) { ++ event.setCancelled(true); ++ return event; ++ } ++ } ++ + Bukkit.getPluginManager().callEvent(event); + return event; + } diff --git a/patches/server/0220-Allow-disabling-armor-stand-ticking.patch b/patches/server/0219-Allow-disabling-armor-stand-ticking.patch similarity index 100% rename from patches/server/0220-Allow-disabling-armor-stand-ticking.patch rename to patches/server/0219-Allow-disabling-armor-stand-ticking.patch diff --git a/patches/server/0219-Vanished-players-don-t-have-rights.patch b/patches/server/0219-Vanished-players-don-t-have-rights.patch deleted file mode 100644 index 952717da68d4..000000000000 --- a/patches/server/0219-Vanished-players-don-t-have-rights.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Hugo Manrique -Date: Mon, 23 Jul 2018 14:22:26 +0200 -Subject: [PATCH] Vanished players don't have rights - - -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 e432855ce79f69c0e91fa31e8f6c59a465b0d09e..4ebc38d0666d01c67c2728355fbbef296a0672e3 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -237,6 +237,15 @@ public abstract class Projectile extends Entity implements TraceableEntity { - } else { - Entity entity1 = this.getOwner(); - -+ // Paper start - Cancel hit for vanished players -+ if (entity1 instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer) { -+ org.bukkit.entity.Player collided = (org.bukkit.entity.Player) entity.getBukkitEntity(); -+ org.bukkit.entity.Player shooter = (org.bukkit.entity.Player) entity1.getBukkitEntity(); -+ if (!shooter.canSee(collided)) { -+ return false; -+ } -+ } -+ // Paper end - Cancel hit for vanished players - return entity1 == null || this.leftOwner || !entity1.isPassengerOfSameVehicle(entity); - } - } -diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java -index a6638e626600e4304a973497a39e3fac52203b16..c19069f65a9ecbc9000ea4333417a2df4ace1007 100644 ---- a/src/main/java/net/minecraft/world/item/BlockItem.java -+++ b/src/main/java/net/minecraft/world/item/BlockItem.java -@@ -197,7 +197,8 @@ public class BlockItem extends Item { - Player entityhuman = context.getPlayer(); - CollisionContext voxelshapecollision = entityhuman == null ? CollisionContext.empty() : CollisionContext.of(entityhuman); - // CraftBukkit start - store default return -- boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && context.getLevel().isUnobstructed(state, context.getClickedPos(), voxelshapecollision); -+ Level world = context.getLevel(); // Paper - Cancel hit for vanished players -+ boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && world.checkEntityCollision(state, entityhuman, voxelshapecollision, context.getClickedPos(), true); // Paper - Cancel hit for vanished players - org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null; - - BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(context.getLevel(), context.getClickedPos()), player, CraftBlockData.fromData(state), defaultReturn); -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 37c50c9da4249a92811aaa5cef5743ccefdcb4a8..181201ecd6c617cc37ba097a667bd96ae3b1823e 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -271,6 +271,45 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); - } - -+ // Paper start - Cancel hit for vanished players -+ // ret true if no collision -+ public final boolean checkEntityCollision(BlockState data, Entity source, net.minecraft.world.phys.shapes.CollisionContext voxelshapedcollision, -+ BlockPos position, boolean checkCanSee) { -+ // Copied from IWorldReader#a(IBlockData, BlockPosition, VoxelShapeCollision) & EntityAccess#a(Entity, VoxelShape) -+ net.minecraft.world.phys.shapes.VoxelShape voxelshape = data.getCollisionShape(this, position, voxelshapedcollision); -+ if (voxelshape.isEmpty()) { -+ return true; -+ } -+ -+ voxelshape = voxelshape.move((double) position.getX(), (double) position.getY(), (double) position.getZ()); -+ if (voxelshape.isEmpty()) { -+ return true; -+ } -+ -+ List entities = this.getEntities(null, voxelshape.bounds()); -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ Entity entity = entities.get(i); -+ -+ if (checkCanSee && source instanceof net.minecraft.server.level.ServerPlayer && entity instanceof net.minecraft.server.level.ServerPlayer -+ && !((net.minecraft.server.level.ServerPlayer) source).getBukkitEntity().canSee(((net.minecraft.server.level.ServerPlayer) entity).getBukkitEntity())) { -+ continue; -+ } -+ -+ // !entity1.dead && entity1.i && (entity == null || !entity1.x(entity)); -+ // elide the last check since vanilla calls with entity = null -+ // only we care about the source for the canSee check -+ if (entity.isRemoved() || !entity.blocksBuilding) { -+ continue; -+ } -+ -+ if (net.minecraft.world.phys.shapes.Shapes.joinIsNotEmpty(voxelshape, net.minecraft.world.phys.shapes.Shapes.create(entity.getBoundingBox()), net.minecraft.world.phys.shapes.BooleanOp.AND)) { -+ return false; -+ } -+ } -+ -+ return true; -+ } -+ // Paper end - Cancel hit for vanished players - @Override - public boolean isClientSide() { - return this.isClientSide; -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index de7971320df8cb1c00c6a836a5c3c953a10f9a69..3ee2b011689b9d2df03947c1e04e3042b8e8c853 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1368,6 +1368,14 @@ public class CraftEventFactory { - Projectile projectile = (Projectile) entity.getBukkitEntity(); - org.bukkit.entity.Entity collided = position.getEntity().getBukkitEntity(); - com.destroystokyo.paper.event.entity.ProjectileCollideEvent event = new com.destroystokyo.paper.event.entity.ProjectileCollideEvent(projectile, collided); -+ -+ if (projectile.getShooter() instanceof Player && collided instanceof Player) { -+ if (!((Player) projectile.getShooter()).canSee((Player) collided)) { -+ event.setCancelled(true); -+ return event; -+ } -+ } -+ - Bukkit.getPluginManager().callEvent(event); - return event; - } diff --git a/patches/server/0221-SkeletonHorse-Additions.patch b/patches/server/0220-SkeletonHorse-Additions.patch similarity index 100% rename from patches/server/0221-SkeletonHorse-Additions.patch rename to patches/server/0220-SkeletonHorse-Additions.patch diff --git a/patches/server/0222-Don-t-call-getItemMeta-on-hasItemMeta.patch b/patches/server/0221-Don-t-call-getItemMeta-on-hasItemMeta.patch similarity index 100% rename from patches/server/0222-Don-t-call-getItemMeta-on-hasItemMeta.patch rename to patches/server/0221-Don-t-call-getItemMeta-on-hasItemMeta.patch diff --git a/patches/server/0223-Expand-ArmorStand-API.patch b/patches/server/0222-Expand-ArmorStand-API.patch similarity index 100% rename from patches/server/0223-Expand-ArmorStand-API.patch rename to patches/server/0222-Expand-ArmorStand-API.patch diff --git a/patches/server/0224-AnvilDamageEvent.patch b/patches/server/0223-AnvilDamageEvent.patch similarity index 100% rename from patches/server/0224-AnvilDamageEvent.patch rename to patches/server/0223-AnvilDamageEvent.patch diff --git a/patches/server/0225-Add-TNTPrimeEvent.patch b/patches/server/0224-Add-TNTPrimeEvent.patch similarity index 100% rename from patches/server/0225-Add-TNTPrimeEvent.patch rename to patches/server/0224-Add-TNTPrimeEvent.patch diff --git a/patches/server/0225-Break-up-and-make-tab-spam-limits-configurable.patch b/patches/server/0225-Break-up-and-make-tab-spam-limits-configurable.patch new file mode 100644 index 000000000000..48a5038a8894 --- /dev/null +++ b/patches/server/0225-Break-up-and-make-tab-spam-limits-configurable.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 29 Jul 2018 05:02:15 +0100 +Subject: [PATCH] Break up and make tab spam limits configurable + +Due to the changes in 1.13, clients will send a tab completion request +for all bukkit commands in order to factor in the lack of support for +brigadier and provide backwards support in the API. + +Craftbukkit, however; has moved the chat spam limiter to also interact +with the tab completion request, which while good for avoiding abuse, +causes 1.13 clients to easilly be kicked from a server in bukkit due +to this. Removing the spam limit could cause issues for servers, however, +there is no way for servers to manipulate this without blindly cancelling +kick events, which only causes additional complications. This also causes +issues in that the tab spam limit and chat share the same field but different +limits, meaning that a player having typed a long command may be kicked from +the server. + +Splitting the field up and making it configurable allows for server owners +to take the burden of this into their own hand without having to rely on +plugins doing unsafe things. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 3d6355f50d04035f62fa9eddee076d0a157c89a6..acf350e18b9e4de77b43a01afe40a72ef040d92e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -256,6 +256,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + private int ackBlockChangesUpTo = -1; + // CraftBukkit start - multithreaded fields + private final AtomicInteger chatSpamTickCount = new AtomicInteger(); ++ private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits + // CraftBukkit end + private int dropSpamTickCount; + private double firstGoodX; +@@ -373,6 +374,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.keepConnectionAlive(); + // CraftBukkit start + for (int spam; (spam = this.chatSpamTickCount.get()) > 0 && !this.chatSpamTickCount.compareAndSet(spam, spam - 1); ) ; ++ if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - configurable tab spam limits + /* Use thread-safe field access instead + if (this.chatSpamTickCount > 0) { + --this.chatSpamTickCount; +@@ -696,7 +698,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { + // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async + // CraftBukkit start +- if (this.chatSpamTickCount.addAndGet(1) > 500 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { ++ if (this.chatSpamTickCount.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper - configurable tab spam limits + this.disconnect(Component.translatable("disconnect.spam")); + return; + } diff --git a/patches/server/0226-Break-up-and-make-tab-spam-limits-configurable.patch b/patches/server/0226-Break-up-and-make-tab-spam-limits-configurable.patch deleted file mode 100644 index fffc05d6091f..000000000000 --- a/patches/server/0226-Break-up-and-make-tab-spam-limits-configurable.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sun, 29 Jul 2018 05:02:15 +0100 -Subject: [PATCH] Break up and make tab spam limits configurable - -Due to the changes in 1.13, clients will send a tab completion request -for all bukkit commands in order to factor in the lack of support for -brigadier and provide backwards support in the API. - -Craftbukkit, however; has moved the chat spam limiter to also interact -with the tab completion request, which while good for avoiding abuse, -causes 1.13 clients to easilly be kicked from a server in bukkit due -to this. Removing the spam limit could cause issues for servers, however, -there is no way for servers to manipulate this without blindly cancelling -kick events, which only causes additional complications. This also causes -issues in that the tab spam limit and chat share the same field but different -limits, meaning that a player having typed a long command may be kicked from -the server. - -Splitting the field up and making it configurable allows for server owners -to take the burden of this into their own hand without having to rely on -plugins doing unsafe things. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 60443fecfc6956e9863d8cf3cf74be447d48ba34..4787d49964ddb93558faa271ad27f5bbc17742f2 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -256,6 +256,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private int ackBlockChangesUpTo = -1; - // CraftBukkit start - multithreaded fields - private final AtomicInteger chatSpamTickCount = new AtomicInteger(); -+ private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits - // CraftBukkit end - private int dropSpamTickCount; - private double firstGoodX; -@@ -373,6 +374,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.keepConnectionAlive(); - // CraftBukkit start - for (int spam; (spam = this.chatSpamTickCount.get()) > 0 && !this.chatSpamTickCount.compareAndSet(spam, spam - 1); ) ; -+ if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - configurable tab spam limits - /* Use thread-safe field access instead - if (this.chatSpamTickCount > 0) { - --this.chatSpamTickCount; -@@ -696,7 +698,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { - // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async - // CraftBukkit start -- if (this.chatSpamTickCount.addAndGet(1) > 500 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { -+ if (this.chatSpamTickCount.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper - configurable tab spam limits - this.disconnect(Component.translatable("disconnect.spam")); - return; - } diff --git a/patches/server/0227-Fix-NBT-type-issues.patch b/patches/server/0226-Fix-NBT-type-issues.patch similarity index 100% rename from patches/server/0227-Fix-NBT-type-issues.patch rename to patches/server/0226-Fix-NBT-type-issues.patch diff --git a/patches/server/0228-Remove-unnecessary-itemmeta-handling.patch b/patches/server/0227-Remove-unnecessary-itemmeta-handling.patch similarity index 100% rename from patches/server/0228-Remove-unnecessary-itemmeta-handling.patch rename to patches/server/0227-Remove-unnecessary-itemmeta-handling.patch diff --git a/patches/server/0228-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch b/patches/server/0228-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch new file mode 100644 index 000000000000..004d23918bc8 --- /dev/null +++ b/patches/server/0228-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 21 Jul 2018 08:25:40 -0400 +Subject: [PATCH] Add Debug Entities option to debug dupe uuid issues + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index de328a93abcf23d3ff265557a7d8bad5be56287c..9f8ee9d9b5fec9ca9c5e8462ed1aef742fa628ac 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1425,6 +1425,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } else { + ChunkMap.TrackedEntity playerchunkmap_entitytracker = new ChunkMap.TrackedEntity(entity, i, j, entitytypes.trackDeltas()); + ++ entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker + this.entityMap.put(entity.getId(), playerchunkmap_entitytracker); + playerchunkmap_entitytracker.updatePlayers(this.level.players()); + if (entity instanceof ServerPlayer) { +@@ -1467,7 +1468,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (playerchunkmap_entitytracker1 != null) { + playerchunkmap_entitytracker1.broadcastRemoved(); + } +- ++ entity.tracker = null; // Paper - We're no longer tracked + } + + protected void tick() { +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index a6749080a192a591dd4e52f56b691df8d9cfcd98..e1518465e4ffc473a75a57e4d9b3ad399a022589 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1197,6 +1197,12 @@ public class ServerLevel extends Level implements WorldGenLevel { + // CraftBukkit start + private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { + org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ // Paper start - extra debug info ++ if (entity.valid) { ++ MinecraftServer.LOGGER.error("Attempted Double World add on {}", entity, new Throwable()); ++ return true; ++ } ++ // Paper end - extra debug info + if (entity.isRemoved()) { + // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit + return false; +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 803802380883cb198b4a40c08d1540cacaca8c2f..5a837807037b2bf8c3cd5f7fd5965c26cbc79e1b 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -241,6 +241,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper + private CraftEntity bukkitEntity; + ++ public @org.jetbrains.annotations.Nullable net.minecraft.server.level.ChunkMap.TrackedEntity tracker; // Paper + public CraftEntity getBukkitEntity() { + if (this.bukkitEntity == null) { + this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this); +diff --git a/src/main/java/net/minecraft/world/level/entity/EntityLookup.java b/src/main/java/net/minecraft/world/level/entity/EntityLookup.java +index 21a2800db22f287b9c6a8290326fdf3b94ae94b1..2e561ac90a8c91ea13cfc18d09f8e0abbcff9385 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntityLookup.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntityLookup.java +@@ -34,6 +34,14 @@ public class EntityLookup { + UUID uUID = entity.getUUID(); + if (this.byUuid.containsKey(uUID)) { + LOGGER.warn("Duplicate entity UUID {}: {}", uUID, entity); ++ // Paper start - extra debug info ++ if (entity instanceof net.minecraft.world.entity.Entity) { ++ final T old = this.byUuid.get(entity.getUUID()); ++ if (old instanceof net.minecraft.world.entity.Entity oldCast && oldCast.getId() != entity.getId() && oldCast.valid) { ++ LOGGER.error("Overwrote an existing entity {} with {}", oldCast, entity); ++ } ++ } ++ // Paper end - extra debug info + } else { + this.byUuid.put(uUID, entity); + this.byId.put(entity.getId(), entity); diff --git a/patches/server/0229-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch b/patches/server/0229-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch deleted file mode 100644 index f9d49cc31be1..000000000000 --- a/patches/server/0229-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 21 Jul 2018 08:25:40 -0400 -Subject: [PATCH] Add Debug Entities option to debug dupe uuid issues - - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 987f867def412552b0d7f6cb2cba50af520f1257..1a80f48a425a81af6acd917f67d33a80746f46c3 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1425,6 +1425,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } else { - ChunkMap.TrackedEntity playerchunkmap_entitytracker = new ChunkMap.TrackedEntity(entity, i, j, entitytypes.trackDeltas()); - -+ entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker - this.entityMap.put(entity.getId(), playerchunkmap_entitytracker); - playerchunkmap_entitytracker.updatePlayers(this.level.players()); - if (entity instanceof ServerPlayer) { -@@ -1467,7 +1468,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - if (playerchunkmap_entitytracker1 != null) { - playerchunkmap_entitytracker1.broadcastRemoved(); - } -- -+ entity.tracker = null; // Paper - We're no longer tracked - } - - protected void tick() { -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index a6749080a192a591dd4e52f56b691df8d9cfcd98..e1518465e4ffc473a75a57e4d9b3ad399a022589 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1197,6 +1197,12 @@ public class ServerLevel extends Level implements WorldGenLevel { - // CraftBukkit start - private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { - org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot -+ // Paper start - extra debug info -+ if (entity.valid) { -+ MinecraftServer.LOGGER.error("Attempted Double World add on {}", entity, new Throwable()); -+ return true; -+ } -+ // Paper end - extra debug info - if (entity.isRemoved()) { - // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit - return false; -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 3ff8c6a4aa867e2ceed8c355550c9c20db6e812e..92284eff12eb28034e33563bb3644deeb0fa81ac 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -241,6 +241,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper - private CraftEntity bukkitEntity; - -+ public @org.jetbrains.annotations.Nullable net.minecraft.server.level.ChunkMap.TrackedEntity tracker; // Paper - public CraftEntity getBukkitEntity() { - if (this.bukkitEntity == null) { - this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this); -diff --git a/src/main/java/net/minecraft/world/level/entity/EntityLookup.java b/src/main/java/net/minecraft/world/level/entity/EntityLookup.java -index 21a2800db22f287b9c6a8290326fdf3b94ae94b1..2e561ac90a8c91ea13cfc18d09f8e0abbcff9385 100644 ---- a/src/main/java/net/minecraft/world/level/entity/EntityLookup.java -+++ b/src/main/java/net/minecraft/world/level/entity/EntityLookup.java -@@ -34,6 +34,14 @@ public class EntityLookup { - UUID uUID = entity.getUUID(); - if (this.byUuid.containsKey(uUID)) { - LOGGER.warn("Duplicate entity UUID {}: {}", uUID, entity); -+ // Paper start - extra debug info -+ if (entity instanceof net.minecraft.world.entity.Entity) { -+ final T old = this.byUuid.get(entity.getUUID()); -+ if (old instanceof net.minecraft.world.entity.Entity oldCast && oldCast.getId() != entity.getId() && oldCast.valid) { -+ LOGGER.error("Overwrote an existing entity {} with {}", oldCast, entity); -+ } -+ } -+ // Paper end - extra debug info - } else { - this.byUuid.put(uUID, entity); - this.byId.put(entity.getId(), entity); diff --git a/patches/server/0229-Add-Early-Warning-Feature-to-WatchDog.patch b/patches/server/0229-Add-Early-Warning-Feature-to-WatchDog.patch new file mode 100644 index 000000000000..4fee792554b6 --- /dev/null +++ b/patches/server/0229-Add-Early-Warning-Feature-to-WatchDog.patch @@ -0,0 +1,159 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: miclebrick +Date: Wed, 8 Aug 2018 15:30:52 -0400 +Subject: [PATCH] Add Early Warning Feature to WatchDog + +Detect when the server has been hung for a long duration, and start printing +thread dumps at an interval until the point of crash. + +This will help diagnose what was going on in that time before the crash. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 0f3589ae1c5577c612b93289fc42cdb977486b6b..0b704d5d39263b66f7846a9c4a116ac4d96383cf 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1092,6 +1092,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && WatchdogThread.monotonicMillis() > this.lastTick + this.timeoutTime && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable ++ // Paper start ++ Logger log = Bukkit.getServer().getLogger(); ++ long currentTime = WatchdogThread.monotonicMillis(); ++ if ( this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable + { +- Logger log = Bukkit.getServer().getLogger(); ++ boolean isLongTimeout = currentTime > lastTick + timeoutTime; ++ // Don't spam early warning dumps ++ if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue; ++ if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... ++ lastEarlyWarning = currentTime; ++ if (isLongTimeout) { ++ // Paper end + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper + log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" ); +@@ -92,29 +107,45 @@ public class WatchdogThread extends Thread + } + } + // Paper end ++ } else ++ { ++ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); ++ log.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump"); ++ } ++ // Paper end - Different message for short timeout + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper + WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // ++ // Paper start - Only print full dump on long timeouts ++ if ( isLongTimeout ) ++ { + log.log( Level.SEVERE, "Entire Thread Dump:" ); + ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads( true, true ); + for ( ThreadInfo thread : threads ) + { + WatchdogThread.dumpThread( thread, log ); + } ++ } else { ++ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---"); ++ } ++ + log.log( Level.SEVERE, "------------------------------" ); + ++ if ( isLongTimeout ) ++ { + if ( this.restart && !MinecraftServer.getServer().hasStopped() ) + { + RestartCommand.restart(); + } + break; ++ } // Paper end + } + + try + { +- sleep( 10000 ); ++ sleep( 1000 ); // Paper - Reduce check time to every second instead of every ten seconds, more consistent and allows for short timeout + } catch ( InterruptedException ex ) + { + this.interrupt(); diff --git a/patches/server/0230-Add-Early-Warning-Feature-to-WatchDog.patch b/patches/server/0230-Add-Early-Warning-Feature-to-WatchDog.patch deleted file mode 100644 index 61218d8b3c2c..000000000000 --- a/patches/server/0230-Add-Early-Warning-Feature-to-WatchDog.patch +++ /dev/null @@ -1,159 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: miclebrick -Date: Wed, 8 Aug 2018 15:30:52 -0400 -Subject: [PATCH] Add Early Warning Feature to WatchDog - -Detect when the server has been hung for a long duration, and start printing -thread dumps at an interval until the point of crash. - -This will help diagnose what was going on in that time before the crash. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index eb305e3e4e6574637cd20f99ca979be8acd09e9b..e1d8eae931f6eb7ddfca1d6dadcb28fec08ccede 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1092,6 +1092,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && WatchdogThread.monotonicMillis() > this.lastTick + this.timeoutTime && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable -+ // Paper start -+ Logger log = Bukkit.getServer().getLogger(); -+ long currentTime = WatchdogThread.monotonicMillis(); -+ if ( this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable - { -- Logger log = Bukkit.getServer().getLogger(); -+ boolean isLongTimeout = currentTime > lastTick + timeoutTime; -+ // Don't spam early warning dumps -+ if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue; -+ if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... -+ lastEarlyWarning = currentTime; -+ if (isLongTimeout) { -+ // Paper end - log.log( Level.SEVERE, "------------------------------" ); - log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper - log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" ); -@@ -92,29 +107,45 @@ public class WatchdogThread extends Thread - } - } - // Paper end -+ } else -+ { -+ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); -+ log.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump"); -+ } -+ // Paper end - Different message for short timeout - log.log( Level.SEVERE, "------------------------------" ); - log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper - WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); - log.log( Level.SEVERE, "------------------------------" ); - // -+ // Paper start - Only print full dump on long timeouts -+ if ( isLongTimeout ) -+ { - log.log( Level.SEVERE, "Entire Thread Dump:" ); - ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads( true, true ); - for ( ThreadInfo thread : threads ) - { - WatchdogThread.dumpThread( thread, log ); - } -+ } else { -+ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---"); -+ } -+ - log.log( Level.SEVERE, "------------------------------" ); - -+ if ( isLongTimeout ) -+ { - if ( this.restart && !MinecraftServer.getServer().hasStopped() ) - { - RestartCommand.restart(); - } - break; -+ } // Paper end - } - - try - { -- sleep( 10000 ); -+ sleep( 1000 ); // Paper - Reduce check time to every second instead of every ten seconds, more consistent and allows for short timeout - } catch ( InterruptedException ex ) - { - this.interrupt(); diff --git a/patches/server/0231-Use-ConcurrentHashMap-in-JsonList.patch b/patches/server/0230-Use-ConcurrentHashMap-in-JsonList.patch similarity index 100% rename from patches/server/0231-Use-ConcurrentHashMap-in-JsonList.patch rename to patches/server/0230-Use-ConcurrentHashMap-in-JsonList.patch diff --git a/patches/server/0231-Use-a-Queue-for-Queueing-Commands.patch b/patches/server/0231-Use-a-Queue-for-Queueing-Commands.patch new file mode 100644 index 000000000000..24d08d52b36f --- /dev/null +++ b/patches/server/0231-Use-a-Queue-for-Queueing-Commands.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 12 Aug 2018 02:33:39 -0400 +Subject: [PATCH] Use a Queue for Queueing Commands + +Lists are bad as Queues mmmkay. + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 6db918db1984d298c57eb93f7cf38aa26f01a04c..78775feb965d6eb98a1ff655ae44b9f0399ef9aa 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -69,7 +69,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + static final Logger LOGGER = LogUtils.getLogger(); + private static final int CONVERSION_RETRY_DELAY_MS = 5000; + private static final int CONVERSION_RETRIES = 2; +- private final List consoleInput = Collections.synchronizedList(Lists.newArrayList()); ++ private final java.util.Queue serverCommandQueue = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - Perf: use a proper queue + @Nullable + private QueryThreadGs4 queryThreadGs4; + // private final RemoteControlCommandListener rconConsoleSource; // CraftBukkit - remove field +@@ -412,13 +412,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + + public void handleConsoleInput(String command, CommandSourceStack commandSource) { +- this.consoleInput.add(new ConsoleInput(command, commandSource)); ++ this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); // Paper - Perf: use proper queue + } + + public void handleConsoleInputs() { + MinecraftTimings.serverCommandTimer.startTiming(); // Spigot +- while (!this.consoleInput.isEmpty()) { +- ConsoleInput servercommand = (ConsoleInput) this.consoleInput.remove(0); ++ // Paper start - Perf: use proper queue ++ ConsoleInput servercommand; ++ while ((servercommand = this.serverCommandQueue.poll()) != null) { ++ // Paper end - Perf: use proper queue + + // CraftBukkit start - ServerCommand for preprocessing + ServerCommandEvent event = new ServerCommandEvent(this.console, servercommand.msg); diff --git a/patches/server/0232-Ability-to-get-block-entities-from-a-chunk-without-s.patch b/patches/server/0232-Ability-to-get-block-entities-from-a-chunk-without-s.patch new file mode 100644 index 000000000000..bc4f825d2a2b --- /dev/null +++ b/patches/server/0232-Ability-to-get-block-entities-from-a-chunk-without-s.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 15 Aug 2018 01:16:34 -0400 +Subject: [PATCH] Ability to get block entities from a chunk without snapshots + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index 70999e95116d50de1a7fecdd91bbad0bac2bf1d8..bfde7573acb6d84accfd3f7fee877bbfb3b0852f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -173,6 +173,13 @@ public class CraftChunk implements Chunk { + + @Override + public BlockState[] getTileEntities() { ++ // Paper start ++ return getTileEntities(true); ++ } ++ ++ @Override ++ public BlockState[] getTileEntities(boolean useSnapshot) { ++ // Paper end + if (!this.isLoaded()) { + this.getWorld().getChunkAt(this.x, this.z); // Transient load for this tick + } +@@ -182,7 +189,29 @@ public class CraftChunk implements Chunk { + BlockState[] entities = new BlockState[chunk.blockEntities.size()]; + + for (BlockPos position : chunk.blockEntities.keySet()) { +- entities[index++] = this.worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(); ++ // Paper start ++ entities[index++] = this.worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(useSnapshot); ++ } ++ ++ return entities; ++ } ++ ++ @Override ++ public Collection getTileEntities(Predicate blockPredicate, boolean useSnapshot) { ++ Preconditions.checkNotNull(blockPredicate, "blockPredicate"); ++ if (!this.isLoaded()) { ++ this.getWorld().getChunkAt(this.x, this.z); // Transient load for this tick ++ } ++ ChunkAccess chunk = this.getHandle(ChunkStatus.FULL); ++ ++ java.util.List entities = new java.util.ArrayList<>(); ++ ++ for (BlockPos position : chunk.blockEntities.keySet()) { ++ Block block = this.worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()); ++ if (blockPredicate.test(block)) { ++ entities.add(block.getState(useSnapshot)); ++ } ++ // Paper end + } + + return entities; diff --git a/patches/server/0232-Use-a-Queue-for-Queueing-Commands.patch b/patches/server/0232-Use-a-Queue-for-Queueing-Commands.patch deleted file mode 100644 index 2d825430b697..000000000000 --- a/patches/server/0232-Use-a-Queue-for-Queueing-Commands.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 12 Aug 2018 02:33:39 -0400 -Subject: [PATCH] Use a Queue for Queueing Commands - -Lists are bad as Queues mmmkay. - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 333647f4328c29afffdc2b0de5abeec731959c59..47b12535d2cb146155044cc20b14bb5a432f83d5 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -69,7 +69,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - static final Logger LOGGER = LogUtils.getLogger(); - private static final int CONVERSION_RETRY_DELAY_MS = 5000; - private static final int CONVERSION_RETRIES = 2; -- private final List consoleInput = Collections.synchronizedList(Lists.newArrayList()); -+ private final java.util.Queue serverCommandQueue = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - Perf: use a proper queue - @Nullable - private QueryThreadGs4 queryThreadGs4; - // private final RemoteControlCommandListener rconConsoleSource; // CraftBukkit - remove field -@@ -412,13 +412,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - } - - public void handleConsoleInput(String command, CommandSourceStack commandSource) { -- this.consoleInput.add(new ConsoleInput(command, commandSource)); -+ this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); // Paper - Perf: use proper queue - } - - public void handleConsoleInputs() { - MinecraftTimings.serverCommandTimer.startTiming(); // Spigot -- while (!this.consoleInput.isEmpty()) { -- ConsoleInput servercommand = (ConsoleInput) this.consoleInput.remove(0); -+ // Paper start - Perf: use proper queue -+ ConsoleInput servercommand; -+ while ((servercommand = this.serverCommandQueue.poll()) != null) { -+ // Paper end - Perf: use proper queue - - // CraftBukkit start - ServerCommand for preprocessing - ServerCommandEvent event = new ServerCommandEvent(this.console, servercommand.msg); diff --git a/patches/server/0233-Ability-to-get-block-entities-from-a-chunk-without-s.patch b/patches/server/0233-Ability-to-get-block-entities-from-a-chunk-without-s.patch deleted file mode 100644 index cebc69de1bd2..000000000000 --- a/patches/server/0233-Ability-to-get-block-entities-from-a-chunk-without-s.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 15 Aug 2018 01:16:34 -0400 -Subject: [PATCH] Ability to get block entities from a chunk without snapshots - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index 491416754e1c5e8c2b345b57f45289906c7932ba..e38643853220cabeac6c30912a9ae4bc1c018d5f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -171,6 +171,13 @@ public class CraftChunk implements Chunk { - - @Override - public BlockState[] getTileEntities() { -+ // Paper start -+ return getTileEntities(true); -+ } -+ -+ @Override -+ public BlockState[] getTileEntities(boolean useSnapshot) { -+ // Paper end - if (!this.isLoaded()) { - this.getWorld().getChunkAt(this.x, this.z); // Transient load for this tick - } -@@ -180,7 +187,29 @@ public class CraftChunk implements Chunk { - BlockState[] entities = new BlockState[chunk.blockEntities.size()]; - - for (BlockPos position : chunk.blockEntities.keySet()) { -- entities[index++] = this.worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(); -+ // Paper start -+ entities[index++] = this.worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()).getState(useSnapshot); -+ } -+ -+ return entities; -+ } -+ -+ @Override -+ public Collection getTileEntities(Predicate blockPredicate, boolean useSnapshot) { -+ Preconditions.checkNotNull(blockPredicate, "blockPredicate"); -+ if (!this.isLoaded()) { -+ this.getWorld().getChunkAt(this.x, this.z); // Transient load for this tick -+ } -+ ChunkAccess chunk = this.getHandle(ChunkStatus.FULL); -+ -+ java.util.List entities = new java.util.ArrayList<>(); -+ -+ for (BlockPos position : chunk.blockEntities.keySet()) { -+ Block block = this.worldServer.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()); -+ if (blockPredicate.test(block)) { -+ entities.add(block.getState(useSnapshot)); -+ } -+ // Paper end - } - - return entities; diff --git a/patches/server/0234-Optimize-BlockPosition-helper-methods.patch b/patches/server/0233-Optimize-BlockPosition-helper-methods.patch similarity index 100% rename from patches/server/0234-Optimize-BlockPosition-helper-methods.patch rename to patches/server/0233-Optimize-BlockPosition-helper-methods.patch diff --git a/patches/server/0235-Restore-vanilla-default-mob-spawn-range-and-water-an.patch b/patches/server/0234-Restore-vanilla-default-mob-spawn-range-and-water-an.patch similarity index 100% rename from patches/server/0235-Restore-vanilla-default-mob-spawn-range-and-water-an.patch rename to patches/server/0234-Restore-vanilla-default-mob-spawn-range-and-water-an.patch diff --git a/patches/server/0236-Slime-Pathfinder-Events.patch b/patches/server/0235-Slime-Pathfinder-Events.patch similarity index 100% rename from patches/server/0236-Slime-Pathfinder-Events.patch rename to patches/server/0235-Slime-Pathfinder-Events.patch diff --git a/patches/server/0237-Configurable-speed-for-water-flowing-over-lava.patch b/patches/server/0236-Configurable-speed-for-water-flowing-over-lava.patch similarity index 100% rename from patches/server/0237-Configurable-speed-for-water-flowing-over-lava.patch rename to patches/server/0236-Configurable-speed-for-water-flowing-over-lava.patch diff --git a/patches/server/0237-Optimize-CraftBlockData-Creation.patch b/patches/server/0237-Optimize-CraftBlockData-Creation.patch new file mode 100644 index 000000000000..24ea11022cd3 --- /dev/null +++ b/patches/server/0237-Optimize-CraftBlockData-Creation.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: miclebrick +Date: Thu, 23 Aug 2018 11:45:32 -0400 +Subject: [PATCH] Optimize CraftBlockData Creation + +Avoids a hashmap lookup by cacheing a reference to the CraftBlockData +and cloning it when one is needed. + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index b6d3f9f9520e410526cfeabcdeb9720dbe30e4bf..6f60622f2dad5f82fb24505612e7e3a32722ab93 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -875,6 +875,14 @@ public abstract class BlockBehaviour implements FeatureElement { + this.instrument = blockbase_info.instrument; + this.replaceable = blockbase_info.replaceable; + } ++ // Paper start - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time ++ private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; ++ ++ public org.bukkit.craftbukkit.block.data.CraftBlockData createCraftBlockData() { ++ if (cachedCraftBlockData == null) cachedCraftBlockData = org.bukkit.craftbukkit.block.data.CraftBlockData.createData(asState()); ++ return (org.bukkit.craftbukkit.block.data.CraftBlockData) cachedCraftBlockData.clone(); ++ } ++ // Paper end - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time + + private boolean calculateSolid() { + if (((Block) this.owner).properties.forceSolidOn) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +index 85ea3d059efed939c56b612f3c8e2064a28c39e6..fce3fec5e1ff164b0596fdd6b3d7b5b0277253ef 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -570,7 +570,17 @@ public class CraftBlockData implements BlockData { + return craft; + } + ++ // Paper start - optimize creating BlockData to not need a map lookup ++ static { ++ // Initialize cached data for all IBlockData instances after registration ++ Block.BLOCK_STATE_REGISTRY.iterator().forEachRemaining(net.minecraft.world.level.block.state.BlockState::createCraftBlockData); ++ } + public static CraftBlockData fromData(net.minecraft.world.level.block.state.BlockState data) { ++ return data.createCraftBlockData(); ++ } ++ ++ public static CraftBlockData createData(net.minecraft.world.level.block.state.BlockState data) { ++ // Paper end + return CraftBlockData.MAP.getOrDefault(data.getBlock().getClass(), CraftBlockData::new).apply(data); + } + diff --git a/patches/server/0238-Optimize-CraftBlockData-Creation.patch b/patches/server/0238-Optimize-CraftBlockData-Creation.patch deleted file mode 100644 index 363405cffd28..000000000000 --- a/patches/server/0238-Optimize-CraftBlockData-Creation.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: miclebrick -Date: Thu, 23 Aug 2018 11:45:32 -0400 -Subject: [PATCH] Optimize CraftBlockData Creation - -Avoids a hashmap lookup by cacheing a reference to the CraftBlockData -and cloning it when one is needed. - -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index b6d3f9f9520e410526cfeabcdeb9720dbe30e4bf..6f60622f2dad5f82fb24505612e7e3a32722ab93 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -875,6 +875,14 @@ public abstract class BlockBehaviour implements FeatureElement { - this.instrument = blockbase_info.instrument; - this.replaceable = blockbase_info.replaceable; - } -+ // Paper start - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time -+ private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; -+ -+ public org.bukkit.craftbukkit.block.data.CraftBlockData createCraftBlockData() { -+ if (cachedCraftBlockData == null) cachedCraftBlockData = org.bukkit.craftbukkit.block.data.CraftBlockData.createData(asState()); -+ return (org.bukkit.craftbukkit.block.data.CraftBlockData) cachedCraftBlockData.clone(); -+ } -+ // Paper end - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time - - private boolean calculateSolid() { - if (((Block) this.owner).properties.forceSolidOn) { -diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -index 09b95423e66da487f48ee594ba682a4c92e347b5..85ee8a3d6db6610104f8a10d77d7cad5dc9b667e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -@@ -570,7 +570,17 @@ public class CraftBlockData implements BlockData { - return craft; - } - -+ // Paper start - optimize creating BlockData to not need a map lookup -+ static { -+ // Initialize cached data for all IBlockData instances after registration -+ Block.BLOCK_STATE_REGISTRY.iterator().forEachRemaining(net.minecraft.world.level.block.state.BlockState::createCraftBlockData); -+ } - public static CraftBlockData fromData(net.minecraft.world.level.block.state.BlockState data) { -+ return data.createCraftBlockData(); -+ } -+ -+ public static CraftBlockData createData(net.minecraft.world.level.block.state.BlockState data) { -+ // Paper end - return CraftBlockData.MAP.getOrDefault(data.getBlock().getClass(), CraftBlockData::new).apply(data); - } - diff --git a/patches/server/0239-Optimize-MappedRegistry.patch b/patches/server/0238-Optimize-MappedRegistry.patch similarity index 100% rename from patches/server/0239-Optimize-MappedRegistry.patch rename to patches/server/0238-Optimize-MappedRegistry.patch diff --git a/patches/server/0240-Add-PhantomPreSpawnEvent.patch b/patches/server/0239-Add-PhantomPreSpawnEvent.patch similarity index 100% rename from patches/server/0240-Add-PhantomPreSpawnEvent.patch rename to patches/server/0239-Add-PhantomPreSpawnEvent.patch diff --git a/patches/server/0241-Add-More-Creeper-API.patch b/patches/server/0240-Add-More-Creeper-API.patch similarity index 100% rename from patches/server/0241-Add-More-Creeper-API.patch rename to patches/server/0240-Add-More-Creeper-API.patch diff --git a/patches/server/0242-Inventory-removeItemAnySlot.patch b/patches/server/0241-Inventory-removeItemAnySlot.patch similarity index 100% rename from patches/server/0242-Inventory-removeItemAnySlot.patch rename to patches/server/0241-Inventory-removeItemAnySlot.patch diff --git a/patches/server/0242-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch b/patches/server/0242-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch new file mode 100644 index 000000000000..5588062e0976 --- /dev/null +++ b/patches/server/0242-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 2 Sep 2018 19:34:33 -0700 +Subject: [PATCH] Make CraftWorld#loadChunk(int, int, false) load unconverted + chunks + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 3fe84fa65961dff58a416f7262c8b8ce218a3b49..358fe6e35ab7555bbd6ae075bcec5249e09dede1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -406,7 +406,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public boolean loadChunk(int x, int z, boolean generate) { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot +- ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); ++ ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper + + // If generate = false, but the chunk already exists, we will get this back. + if (chunk instanceof ImposterProtoChunk) { diff --git a/patches/server/0243-Add-ray-tracing-methods-to-LivingEntity.patch b/patches/server/0243-Add-ray-tracing-methods-to-LivingEntity.patch new file mode 100644 index 000000000000..7cb609e3e708 --- /dev/null +++ b/patches/server/0243-Add-ray-tracing-methods-to-LivingEntity.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Mon, 3 Sep 2018 18:20:03 -0500 +Subject: [PATCH] Add ray tracing methods to LivingEntity + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 2900326a87d21c8a92edb303ed42fd11ea7f3010..b9d202cb06672f0791792ac676761c404ffb02bc 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3841,6 +3841,19 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + // Paper start - Make shield blocking delay configurable ++ public HitResult getRayTrace(int maxDistance, ClipContext.Fluid fluidCollisionOption) { ++ if (maxDistance < 1 || maxDistance > 120) { ++ throw new IllegalArgumentException("maxDistance must be between 1-120"); ++ } ++ ++ Vec3 start = new Vec3(getX(), getY() + getEyeHeight(), getZ()); ++ org.bukkit.util.Vector dir = getBukkitEntity().getLocation().getDirection().multiply(maxDistance); ++ Vec3 end = new Vec3(start.x + dir.getX(), start.y + dir.getY(), start.z + dir.getZ()); ++ ClipContext raytrace = new ClipContext(start, end, ClipContext.Block.OUTLINE, fluidCollisionOption, this); ++ ++ return this.level().clip(raytrace); ++ } ++ + public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; + + public int getShieldBlockingDelay() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 8ac3beab73904db7362caa3054994b214c997cf7..1510b4d8fc08f4455b38ad4edb6bdf91ad317c96 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -201,6 +201,33 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return blocks.get(0); + } + ++ // Paper start ++ @Override ++ public Block getTargetBlock(int maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode) { ++ return this.getTargetBlockExact(maxDistance, fluidMode.bukkit); ++ } ++ ++ @Override ++ public org.bukkit.block.BlockFace getTargetBlockFace(int maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode) { ++ return this.getTargetBlockFace(maxDistance, fluidMode.bukkit); ++ } ++ ++ @Override ++ public org.bukkit.block.BlockFace getTargetBlockFace(int maxDistance, org.bukkit.FluidCollisionMode fluidMode) { ++ RayTraceResult result = this.rayTraceBlocks(maxDistance, fluidMode); ++ return result != null ? result.getHitBlockFace() : null; ++ } ++ ++ @Override ++ public com.destroystokyo.paper.block.TargetBlockInfo getTargetBlockInfo(int maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode) { ++ RayTraceResult result = this.rayTraceBlocks(maxDistance, fluidMode.bukkit); ++ if (result != null && result.getHitBlock() != null && result.getHitBlockFace() != null) { ++ return new com.destroystokyo.paper.block.TargetBlockInfo(result.getHitBlock(), result.getHitBlockFace()); ++ } ++ return null; ++ } ++ // Paper end ++ + @Override + public List getLastTwoTargetBlocks(Set transparent, int maxDistance) { + return this.getLineOfSight(transparent, maxDistance, 2); diff --git a/patches/server/0243-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch b/patches/server/0243-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch deleted file mode 100644 index b249c98d2ab1..000000000000 --- a/patches/server/0243-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 2 Sep 2018 19:34:33 -0700 -Subject: [PATCH] Make CraftWorld#loadChunk(int, int, false) load unconverted - chunks - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 845e16bec244a59a89d790363ddd5fe1d7bfdbf7..e0cb72d826168f3c22c5be4c2c8e6d760edafb97 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -400,7 +400,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @Override - public boolean loadChunk(int x, int z, boolean generate) { - org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot -- ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); -+ ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper - - // If generate = false, but the chunk already exists, we will get this back. - if (chunk instanceof ImposterProtoChunk) { diff --git a/patches/server/0244-Add-ray-tracing-methods-to-LivingEntity.patch b/patches/server/0244-Add-ray-tracing-methods-to-LivingEntity.patch deleted file mode 100644 index a8fc76396c09..000000000000 --- a/patches/server/0244-Add-ray-tracing-methods-to-LivingEntity.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Mon, 3 Sep 2018 18:20:03 -0500 -Subject: [PATCH] Add ray tracing methods to LivingEntity - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index b58a13e33a0b98bbbdd3283fc3b90cde11ecd37f..9e0555b1e98bba6a9305c3996c69347e47d73204 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3828,6 +3828,19 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - // Paper start - Make shield blocking delay configurable -+ public HitResult getRayTrace(int maxDistance, ClipContext.Fluid fluidCollisionOption) { -+ if (maxDistance < 1 || maxDistance > 120) { -+ throw new IllegalArgumentException("maxDistance must be between 1-120"); -+ } -+ -+ Vec3 start = new Vec3(getX(), getY() + getEyeHeight(), getZ()); -+ org.bukkit.util.Vector dir = getBukkitEntity().getLocation().getDirection().multiply(maxDistance); -+ Vec3 end = new Vec3(start.x + dir.getX(), start.y + dir.getY(), start.z + dir.getZ()); -+ ClipContext raytrace = new ClipContext(start, end, ClipContext.Block.OUTLINE, fluidCollisionOption, this); -+ -+ return this.level().clip(raytrace); -+ } -+ - public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; - - public int getShieldBlockingDelay() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index a4af5db4d2d45429a46830e4af0ba45c8c5008be..7ac9515c0cca4e6a1197a42d5ff1dff04d183bd5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -200,6 +200,33 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return blocks.get(0); - } - -+ // Paper start -+ @Override -+ public Block getTargetBlock(int maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode) { -+ return this.getTargetBlockExact(maxDistance, fluidMode.bukkit); -+ } -+ -+ @Override -+ public org.bukkit.block.BlockFace getTargetBlockFace(int maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode) { -+ return this.getTargetBlockFace(maxDistance, fluidMode.bukkit); -+ } -+ -+ @Override -+ public org.bukkit.block.BlockFace getTargetBlockFace(int maxDistance, org.bukkit.FluidCollisionMode fluidMode) { -+ RayTraceResult result = this.rayTraceBlocks(maxDistance, fluidMode); -+ return result != null ? result.getHitBlockFace() : null; -+ } -+ -+ @Override -+ public com.destroystokyo.paper.block.TargetBlockInfo getTargetBlockInfo(int maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode) { -+ RayTraceResult result = this.rayTraceBlocks(maxDistance, fluidMode.bukkit); -+ if (result != null && result.getHitBlock() != null && result.getHitBlockFace() != null) { -+ return new com.destroystokyo.paper.block.TargetBlockInfo(result.getHitBlock(), result.getHitBlockFace()); -+ } -+ return null; -+ } -+ // Paper end -+ - @Override - public List getLastTwoTargetBlocks(Set transparent, int maxDistance) { - return this.getLineOfSight(transparent, maxDistance, 2); diff --git a/patches/server/0244-Expose-attack-cooldown-methods-for-Player.patch b/patches/server/0244-Expose-attack-cooldown-methods-for-Player.patch new file mode 100644 index 000000000000..2b4d1222f5d1 --- /dev/null +++ b/patches/server/0244-Expose-attack-cooldown-methods-for-Player.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 4 Sep 2018 15:02:00 -0500 +Subject: [PATCH] Expose attack cooldown methods for Player + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index ddf089d73a05792d99a96bb449717d82755f5cd9..27d29187b5471fa9d1588b7e8e35539be8a861a0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2897,6 +2897,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + return this.adventure$pointers; + } ++ ++ @Override ++ public float getCooldownPeriod() { ++ return getHandle().getCurrentItemAttackStrengthDelay(); ++ } ++ ++ @Override ++ public float getCooledAttackStrength(float adjustTicks) { ++ return getHandle().getAttackStrengthScale(adjustTicks); ++ } ++ ++ @Override ++ public void resetCooldown() { ++ getHandle().resetAttackStrengthTicker(); ++ } + // Paper end + // Spigot start + private final Player.Spigot spigot = new Player.Spigot() diff --git a/patches/server/0245-Expose-attack-cooldown-methods-for-Player.patch b/patches/server/0245-Expose-attack-cooldown-methods-for-Player.patch deleted file mode 100644 index fca7875ee111..000000000000 --- a/patches/server/0245-Expose-attack-cooldown-methods-for-Player.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Tue, 4 Sep 2018 15:02:00 -0500 -Subject: [PATCH] Expose attack cooldown methods for Player - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 047be06ad290e6e066689fe3b95902a3459304b3..b98f26b6958cb3f874a4c0d85e2c10ec517cbbff 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2846,6 +2846,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - return this.adventure$pointers; - } -+ -+ @Override -+ public float getCooldownPeriod() { -+ return getHandle().getCurrentItemAttackStrengthDelay(); -+ } -+ -+ @Override -+ public float getCooledAttackStrength(float adjustTicks) { -+ return getHandle().getAttackStrengthScale(adjustTicks); -+ } -+ -+ @Override -+ public void resetCooldown() { -+ getHandle().resetAttackStrengthTicker(); -+ } - // Paper end - // Spigot start - private final Player.Spigot spigot = new Player.Spigot() diff --git a/patches/server/0245-Improve-death-events.patch b/patches/server/0245-Improve-death-events.patch new file mode 100644 index 000000000000..076aeeb78515 --- /dev/null +++ b/patches/server/0245-Improve-death-events.patch @@ -0,0 +1,525 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Tue, 21 Aug 2018 01:39:35 +0100 +Subject: [PATCH] Improve death events + +This adds the ability to cancel the death events and to modify the sound +an entity makes when dying. (In cases were no sound should it will be +called with shouldPlaySound set to false allowing unsilencing of silent +entities) + +It makes handling of entity deaths a lot nicer as you no longer need +to listen on the damage event and calculate if the entity dies yourself +to cancel the death which has the benefit of also receiving the dropped +items and experience which is otherwise only properly possible by using +internal code. + +== AT == +public net.minecraft.world.entity.LivingEntity getDeathSound()Lnet/minecraft/sounds/SoundEvent; +public net.minecraft.world.entity.LivingEntity getSoundVolume()F + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 38a3b264ba72631c27203a178ac0bbdd36e10a10..80cedd5221d3c0dc475c631113e8782507067cb0 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -248,6 +248,10 @@ public class ServerPlayer extends Player { + private int containerCounter; + public boolean wonGame; + private int containerUpdateDelay; // Paper - Configurable container update tick rate ++ // Paper start - cancellable death event ++ public boolean queueHealthUpdatePacket; ++ public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; ++ // Paper end - cancellable death event + + // CraftBukkit start + public String displayName; +@@ -810,7 +814,7 @@ public class ServerPlayer extends Player { + + @Override + public void die(DamageSource damageSource) { +- this.gameEvent(GameEvent.ENTITY_DIE); ++ // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check + boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); + // CraftBukkit start - fire PlayerDeathEvent + if (this.isRemoved()) { +@@ -838,6 +842,16 @@ public class ServerPlayer extends Player { + String deathmessage = defaultMessage.getString(); + this.keepLevel = keepInventory; // SPIGOT-2222: pre-set keepLevel + org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, loot, PaperAdventure.asAdventure(defaultMessage), keepInventory); // Paper - Adventure ++ // Paper start - cancellable death event ++ if (event.isCancelled()) { ++ // make compatible with plugins that might have already set the health in an event listener ++ if (this.getHealth() <= 0) { ++ this.setHealth((float) event.getReviveHealth()); ++ } ++ return; ++ } ++ this.gameEvent(GameEvent.ENTITY_DIE); // moved from the top of this method ++ // Paper end + + // SPIGOT-943 - only call if they have an inventory open + if (this.containerMenu != this.inventoryMenu) { +@@ -986,8 +1000,17 @@ public class ServerPlayer extends Player { + } + } + } +- +- return super.hurt(source, amount); ++ // Paper start - cancellable death events ++ //return super.hurt(source, amount); ++ this.queueHealthUpdatePacket = true; ++ boolean damaged = super.hurt(source, amount); ++ this.queueHealthUpdatePacket = false; ++ if (this.queuedHealthUpdatePacket != null) { ++ this.connection.send(this.queuedHealthUpdatePacket); ++ this.queuedHealthUpdatePacket = null; ++ } ++ return damaged; ++ // Paper end + } + } + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index b9d202cb06672f0791792ac676761c404ffb02bc..78f601ff13b160c0661ba0b60365403f9eb7fffb 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -259,6 +259,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + public Set collidableExemptions = new HashSet<>(); + public boolean bukkitPickUpLoot; + public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper ++ public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event + + @Override + public float getBukkitYaw() { +@@ -1522,13 +1523,12 @@ public abstract class LivingEntity extends Entity implements Attackable { + + if (this.isDeadOrDying()) { + if (!this.checkTotemDeathProtection(source)) { +- SoundEvent soundeffect = this.getDeathSound(); +- +- if (flag1 && soundeffect != null) { +- this.playSound(soundeffect, this.getSoundVolume(), this.getVoicePitch()); +- } ++ // Paper start - moved into CraftEventFactory event caller for cancellable death event ++ this.silentDeath = !flag1; // mark entity as dying silently ++ // Paper end + + this.die(source); ++ this.silentDeath = false; // Paper - cancellable death event - reset to default + } + } else if (flag1) { + this.playHurtSound(source); +@@ -1680,6 +1680,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + Entity entity = damageSource.getEntity(); + LivingEntity entityliving = this.getKillCredit(); + ++ /* // Paper - move down to make death event cancellable - this is the awardKillScore below + if (this.deathScore >= 0 && entityliving != null) { + entityliving.awardKillScore(this, this.deathScore, damageSource); + } +@@ -1691,24 +1692,59 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (!this.level().isClientSide && this.hasCustomName()) { + if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot + } ++ */ // Paper - move down to make death event cancellable - this is the awardKillScore below + + this.dead = true; +- this.getCombatTracker().recheckStatus(); ++ // Paper - moved into if below + Level world = this.level(); + + if (world instanceof ServerLevel) { + ServerLevel worldserver = (ServerLevel) world; ++ // Paper - move below into if for onKill + +- if (entity == null || entity.killedEntity(worldserver, this)) { ++ // Paper start ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(damageSource); ++ if (deathEvent == null || !deathEvent.isCancelled()) { ++ if (this.deathScore >= 0 && entityliving != null) { ++ entityliving.awardKillScore(this, this.deathScore, damageSource); ++ } ++ // Paper start - clear equipment if event is not cancelled ++ if (this instanceof Mob) { ++ for (EquipmentSlot slot : this.clearedEquipmentSlots) { ++ this.setItemSlot(slot, ItemStack.EMPTY); ++ } ++ this.clearedEquipmentSlots.clear(); ++ } ++ // Paper end ++ ++ if (this.isSleeping()) { ++ this.stopSleeping(); ++ } ++ ++ if (!this.level().isClientSide && this.hasCustomName()) { ++ if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot ++ } ++ ++ this.getCombatTracker().recheckStatus(); ++ if (entity != null) { ++ entity.killedEntity((ServerLevel) this.level(), this); ++ } + this.gameEvent(GameEvent.ENTITY_DIE); +- this.dropAllDeathLoot(damageSource); +- this.createWitherRose(entityliving); ++ } else { ++ this.dead = false; ++ this.setHealth((float) deathEvent.getReviveHealth()); + } + +- this.level().broadcastEntityEvent(this, (byte) 3); ++ // Paper end ++ this.createWitherRose(entityliving); + } + ++ // Paper start ++ if (this.dead) { // Paper ++ this.level().broadcastEntityEvent(this, (byte) 3); + this.setPose(Pose.DYING); ++ } ++ // Paper end + } + } + +@@ -1716,7 +1752,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (!this.level().isClientSide) { + boolean flag = false; + +- if (adversary instanceof WitherBoss) { ++ if (this.dead && adversary instanceof WitherBoss) { // Paper + if (this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + BlockPos blockposition = this.blockPosition(); + BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState(); +@@ -1745,7 +1781,11 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + } + +- protected void dropAllDeathLoot(DamageSource source) { ++ // Paper start ++ protected boolean clearEquipmentSlots = true; ++ protected Set clearedEquipmentSlots = new java.util.HashSet<>(); ++ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource source) { ++ // Paper end + Entity entity = source.getEntity(); + int i; + +@@ -1760,18 +1800,27 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.dropEquipment(); // CraftBukkit - from below + if (this.shouldDropLoot() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { + this.dropFromLootTable(source, flag); ++ // Paper start ++ final boolean prev = this.clearEquipmentSlots; ++ this.clearEquipmentSlots = false; ++ this.clearedEquipmentSlots.clear(); ++ // Paper end + this.dropCustomDeathLoot(source, i, flag); ++ this.clearEquipmentSlots = prev; // Paper + } + // CraftBukkit start - Call death event +- CraftEventFactory.callEntityDeathEvent(this, this.drops); ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops); // Paper ++ this.postDeathDropItems(deathEvent); // Paper + this.drops = new ArrayList<>(); + // CraftBukkit end + + // this.dropInventory();// CraftBukkit - moved up + this.dropExperience(); ++ return deathEvent; // Paper + } + + protected void dropEquipment() {} ++ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {} // Paper - method for post death logic that cannot be ran before the event is potentially cancelled + + // CraftBukkit start + public int getExpReward() { +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index bb051d73a048b0a8ce245914f3564e39702b8452..645fb2ec7d969068eb10d59d43a512c74cca5a58 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1057,6 +1057,12 @@ public abstract class Mob extends LivingEntity implements Targeting { + + } + ++ // Paper start ++ protected boolean shouldSkipLoot(EquipmentSlot slot) { // method to avoid to fallback into the global mob loot logic (i.e fox) ++ return false; ++ } ++ // Paper end ++ + @Override + protected void dropCustomDeathLoot(DamageSource source, int lootingMultiplier, boolean allowDrops) { + super.dropCustomDeathLoot(source, lootingMultiplier, allowDrops); +@@ -1065,6 +1071,7 @@ public abstract class Mob extends LivingEntity implements Targeting { + + for (int k = 0; k < j; ++k) { + EquipmentSlot enumitemslot = aenumitemslot[k]; ++ if (this.shouldSkipLoot(enumitemslot)) continue; // Paper + ItemStack itemstack = this.getItemBySlot(enumitemslot); + float f = this.getEquipmentDropChance(enumitemslot); + boolean flag1 = f > 1.0F; +@@ -1075,7 +1082,13 @@ public abstract class Mob extends LivingEntity implements Targeting { + } + + this.spawnAtLocation(itemstack); ++ if (this.clearEquipmentSlots) { // Paper + this.setItemSlot(enumitemslot, ItemStack.EMPTY); ++ // Paper start ++ } else { ++ this.clearedEquipmentSlots.add(enumitemslot); ++ } ++ // Paper end + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java +index 8670d8b2a08e96df787a91f36c48df8b345080dc..950e4b476a03fb5c26351a3b4d1578975a0b0ea8 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java +@@ -711,16 +711,38 @@ public class Fox extends Animal implements VariantHolder { + return this.getTrustedUUIDs().contains(uuid); + } + ++ // Paper start - handle the bitten item separately like vanilla + @Override +- protected void dropAllDeathLoot(DamageSource source) { ++ protected boolean shouldSkipLoot(EquipmentSlot slot) { ++ return slot == EquipmentSlot.MAINHAND; ++ } ++ // Paper end ++ ++ @Override ++ // Paper start - Cancellable death event ++ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource source) { + ItemStack itemstack = this.getItemBySlot(EquipmentSlot.MAINHAND); + +- if (!itemstack.isEmpty()) { ++ boolean releaseMouth = false; ++ if (!itemstack.isEmpty() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Fix MC-153010 + this.spawnAtLocation(itemstack); ++ releaseMouth = true; ++ } ++ ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = super.dropAllDeathLoot(source); ++ ++ // Below is code to drop ++ ++ if (deathEvent == null || deathEvent.isCancelled()) { ++ return deathEvent; ++ } ++ ++ if (releaseMouth) { ++ // Paper end - Cancellable death event + this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); + } + +- super.dropAllDeathLoot(source); ++ return deathEvent; // Paper - Cancellable death event + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java +index bb399f775a5530a01f59332848c8ab9b8eceb2b5..14edfe103e61024b569f33de0b6608f39e749319 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java +@@ -70,11 +70,19 @@ public abstract class AbstractChestedHorse extends AbstractHorse { + this.spawnAtLocation(Blocks.CHEST); + } + +- this.setChest(false); ++ //this.setCarryingChest(false); // Paper - moved to post death logic + } + + } + ++ // Paper start ++ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) { ++ if (this.hasChest() && (event == null || !event.isCancelled())) { ++ this.setChest(false); ++ } ++ } ++ // Paper end ++ + @Override + public void addAdditionalSaveData(CompoundTag nbt) { + super.addAdditionalSaveData(nbt); +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index ecce8036e58a9ed1408e110e75980bf77c18779a..ddd512e1d7608ec051fb5adf6ec2c6bbb93f5a9d 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -492,8 +492,10 @@ public class ArmorStand extends LivingEntity { + } + // CraftBukkit end + if (source.is(DamageTypeTags.IS_EXPLOSION)) { +- this.brokenByAnything(source); +- this.kill(); ++ // Paper start - avoid duplicate event call ++ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(source); ++ if (!event.isCancelled()) this.kill(false); ++ // Paper end + return false; + } else if (source.is(DamageTypeTags.IGNITES_ARMOR_STANDS)) { + if (this.isOnFire()) { +@@ -536,9 +538,9 @@ public class ArmorStand extends LivingEntity { + this.gameEvent(GameEvent.ENTITY_DAMAGE, source.getEntity()); + this.lastHit = i; + } else { +- this.brokenByPlayer(source); ++ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByPlayer(source); // Paper + this.showBreakingParticles(); +- this.discard(); // CraftBukkit - SPIGOT-4890: remain as this.discard() since above damagesource method will call death event ++ if (!event.isCancelled()) this.kill(false); // Paper - we still need to kill to follow vanilla logic (emit the game event etc...) + } + + return true; +@@ -590,8 +592,10 @@ public class ArmorStand extends LivingEntity { + + f1 -= amount; + if (f1 <= 0.5F) { +- this.brokenByAnything(damageSource); +- this.kill(); ++ // Paper start - avoid duplicate event call ++ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(damageSource); ++ if (!event.isCancelled()) this.kill(false); ++ // Paper end + } else { + this.setHealth(f1); + this.gameEvent(GameEvent.ENTITY_DAMAGE, damageSource.getEntity()); +@@ -599,7 +603,7 @@ public class ArmorStand extends LivingEntity { + + } + +- private void brokenByPlayer(DamageSource damageSource) { ++ private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(DamageSource damageSource) { // Paper + ItemStack itemstack = new ItemStack(Items.ARMOR_STAND); + + if (this.hasCustomName()) { +@@ -607,10 +611,10 @@ public class ArmorStand extends LivingEntity { + } + + this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops +- this.brokenByAnything(damageSource); ++ return this.brokenByAnything(damageSource); // Paper + } + +- private void brokenByAnything(DamageSource damageSource) { ++ private org.bukkit.event.entity.EntityDeathEvent brokenByAnything(DamageSource damageSource) { // Paper + this.playBrokenSound(); + // this.dropAllDeathLoot(damagesource); // CraftBukkit - moved down + +@@ -632,7 +636,7 @@ public class ArmorStand extends LivingEntity { + this.armorItems.set(i, ItemStack.EMPTY); + } + } +- this.dropAllDeathLoot(damageSource); // CraftBukkit - moved from above ++ return this.dropAllDeathLoot(damageSource); // CraftBukkit - moved from above // Paper + + } + +@@ -759,7 +763,16 @@ public class ArmorStand extends LivingEntity { + + @Override + public void kill() { +- org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, this.drops); // CraftBukkit - call event ++ // Paper start ++ kill(true); ++ } ++ ++ public void kill(boolean callEvent) { ++ if (callEvent) { ++ // Paper end ++ org.bukkit.event.entity.EntityDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, this.drops); // CraftBukkit - call event // Paper - make cancellable ++ if (event.isCancelled()) return; // Paper - make cancellable ++ } // Paper + this.remove(Entity.RemovalReason.KILLED); + this.gameEvent(GameEvent.ENTITY_DIE); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 27d29187b5471fa9d1588b7e8e35539be8a861a0..8cda792c1700bbc511e364e28fd7ee6893fe8db2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2434,7 +2434,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + @Override + public void sendHealthUpdate() { + FoodData foodData = this.getHandle().getFoodData(); +- this.sendHealthUpdate(this.getScaledHealth(), foodData.getFoodLevel(), foodData.getSaturationLevel()); ++ // Paper start - cancellable death event ++ ClientboundSetHealthPacket packet = new ClientboundSetHealthPacket(this.getScaledHealth(), foodData.getFoodLevel(), foodData.getSaturationLevel()); ++ if (this.getHandle().queueHealthUpdatePacket) { ++ this.getHandle().queuedHealthUpdatePacket = packet; ++ } else { ++ this.getHandle().connection.send(packet); ++ } ++ // Paper end + } + + public void injectScaledMaxHealth(Collection collection, boolean force) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 9d95dba058dd26db42d9e32b90399a78211bcd79..a08d1250cd5a5cc70e3796ac56a0cfaee6fe2b83 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -890,9 +890,16 @@ public class CraftEventFactory { + public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops) { + CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); + EntityDeathEvent event = new EntityDeathEvent(entity, drops, victim.getExpReward()); ++ populateFields(victim, event); // Paper - make cancellable + CraftWorld world = (CraftWorld) entity.getWorld(); + Bukkit.getServer().getPluginManager().callEvent(event); + ++ // Paper start - make cancellable ++ if (event.isCancelled()) { ++ return event; ++ } ++ playDeathSound(victim, event); ++ // Paper end + victim.expToDrop = event.getDroppedExp(); + + for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { +@@ -909,8 +916,15 @@ public class CraftEventFactory { + PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage); + event.setKeepInventory(keepInventory); + event.setKeepLevel(victim.keepLevel); // SPIGOT-2222: pre-set keepLevel ++ populateFields(victim, event); // Paper - make cancellable + org.bukkit.World world = entity.getWorld(); + Bukkit.getServer().getPluginManager().callEvent(event); ++ // Paper start - make cancellable ++ if (event.isCancelled()) { ++ return event; ++ } ++ playDeathSound(victim, event); ++ // Paper end + + victim.keepLevel = event.getKeepLevel(); + victim.newLevel = event.getNewLevel(); +@@ -927,6 +941,31 @@ public class CraftEventFactory { + return event; + } + ++ // Paper start - helper methods for making death event cancellable ++ // Add information to death event ++ private static void populateFields(net.minecraft.world.entity.LivingEntity victim, EntityDeathEvent event) { ++ event.setReviveHealth(event.getEntity().getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH).getValue()); ++ event.setShouldPlayDeathSound(!victim.silentDeath && !victim.isSilent()); ++ net.minecraft.sounds.SoundEvent soundEffect = victim.getDeathSound(); ++ event.setDeathSound(soundEffect != null ? org.bukkit.craftbukkit.CraftSound.minecraftToBukkit(soundEffect) : null); ++ event.setDeathSoundCategory(org.bukkit.SoundCategory.valueOf(victim.getSoundSource().name())); ++ event.setDeathSoundVolume(victim.getSoundVolume()); ++ event.setDeathSoundPitch(victim.getVoicePitch()); ++ } ++ ++ // Play death sound manually ++ private static void playDeathSound(net.minecraft.world.entity.LivingEntity victim, EntityDeathEvent event) { ++ if (event.shouldPlayDeathSound() && event.getDeathSound() != null && event.getDeathSoundCategory() != null) { ++ net.minecraft.world.entity.player.Player source = victim instanceof net.minecraft.world.entity.player.Player ? (net.minecraft.world.entity.player.Player) victim : null; ++ double x = event.getEntity().getLocation().getX(); ++ double y = event.getEntity().getLocation().getY(); ++ double z = event.getEntity().getLocation().getZ(); ++ net.minecraft.sounds.SoundEvent soundEffect = org.bukkit.craftbukkit.CraftSound.bukkitToMinecraft(event.getDeathSound()); ++ net.minecraft.sounds.SoundSource soundCategory = net.minecraft.sounds.SoundSource.valueOf(event.getDeathSoundCategory().name()); ++ victim.level().playSound(source, x, y, z, soundEffect, soundCategory, event.getDeathSoundVolume(), event.getDeathSoundPitch()); ++ } ++ } ++ // Paper end + /** + * Server methods + */ diff --git a/patches/server/0246-Allow-chests-to-be-placed-with-NBT-data.patch b/patches/server/0246-Allow-chests-to-be-placed-with-NBT-data.patch new file mode 100644 index 000000000000..ecee8a86b0e1 --- /dev/null +++ b/patches/server/0246-Allow-chests-to-be-placed-with-NBT-data.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 8 Sep 2018 18:43:31 -0500 +Subject: [PATCH] Allow chests to be placed with NBT data + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 9d34a7cd8361fd65d30537d4498c8e2a03d93bb1..0a3fec9b82a4d744f9046aebe30f80bb6e56c500 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -409,6 +409,7 @@ public final class ItemStack { + enuminteractionresult = InteractionResult.FAIL; // cancel placement + // PAIL: Remove this when MC-99075 fixed + placeEvent.getPlayer().updateInventory(); ++ world.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot + // revert back all captured blocks + world.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710 + for (BlockState blockstate : blocks) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java +index d4f5af759bbb6208432ad7b5002af5455dc7958c..9b1243d96e0694c62fc9e82e9be540bce0d2b3ad 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java +@@ -237,7 +237,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement + // CraftBukkit start + @Override + public boolean onlyOpCanSetNbt() { +- return true; ++ return false; // Paper - Allow chests to be placed with NBT data + } + // CraftBukkit end + } diff --git a/patches/server/0246-Improve-death-events.patch b/patches/server/0246-Improve-death-events.patch deleted file mode 100644 index c73e594c2b69..000000000000 --- a/patches/server/0246-Improve-death-events.patch +++ /dev/null @@ -1,525 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Tue, 21 Aug 2018 01:39:35 +0100 -Subject: [PATCH] Improve death events - -This adds the ability to cancel the death events and to modify the sound -an entity makes when dying. (In cases were no sound should it will be -called with shouldPlaySound set to false allowing unsilencing of silent -entities) - -It makes handling of entity deaths a lot nicer as you no longer need -to listen on the damage event and calculate if the entity dies yourself -to cancel the death which has the benefit of also receiving the dropped -items and experience which is otherwise only properly possible by using -internal code. - -== AT == -public net.minecraft.world.entity.LivingEntity getDeathSound()Lnet/minecraft/sounds/SoundEvent; -public net.minecraft.world.entity.LivingEntity getSoundVolume()F - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 33108a55c1cc305d44238d0862755af874f19395..1042b19c906e40d4a4e9ef76d69228149e84e3c5 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -248,6 +248,10 @@ public class ServerPlayer extends Player { - private int containerCounter; - public boolean wonGame; - private int containerUpdateDelay; // Paper - Configurable container update tick rate -+ // Paper start - cancellable death event -+ public boolean queueHealthUpdatePacket; -+ public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; -+ // Paper end - cancellable death event - - // CraftBukkit start - public String displayName; -@@ -810,7 +814,7 @@ public class ServerPlayer extends Player { - - @Override - public void die(DamageSource damageSource) { -- this.gameEvent(GameEvent.ENTITY_DIE); -+ // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check - boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); - // CraftBukkit start - fire PlayerDeathEvent - if (this.isRemoved()) { -@@ -838,6 +842,16 @@ public class ServerPlayer extends Player { - String deathmessage = defaultMessage.getString(); - this.keepLevel = keepInventory; // SPIGOT-2222: pre-set keepLevel - org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, loot, PaperAdventure.asAdventure(defaultMessage), keepInventory); // Paper - Adventure -+ // Paper start - cancellable death event -+ if (event.isCancelled()) { -+ // make compatible with plugins that might have already set the health in an event listener -+ if (this.getHealth() <= 0) { -+ this.setHealth((float) event.getReviveHealth()); -+ } -+ return; -+ } -+ this.gameEvent(GameEvent.ENTITY_DIE); // moved from the top of this method -+ // Paper end - - // SPIGOT-943 - only call if they have an inventory open - if (this.containerMenu != this.inventoryMenu) { -@@ -986,8 +1000,17 @@ public class ServerPlayer extends Player { - } - } - } -- -- return super.hurt(source, amount); -+ // Paper start - cancellable death events -+ //return super.hurt(source, amount); -+ this.queueHealthUpdatePacket = true; -+ boolean damaged = super.hurt(source, amount); -+ this.queueHealthUpdatePacket = false; -+ if (this.queuedHealthUpdatePacket != null) { -+ this.connection.send(this.queuedHealthUpdatePacket); -+ this.queuedHealthUpdatePacket = null; -+ } -+ return damaged; -+ // Paper end - } - } - } -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 9e0555b1e98bba6a9305c3996c69347e47d73204..bd70046b4fef307b6b8b626a65f0b4a366ab7158 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -258,6 +258,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - public Set collidableExemptions = new HashSet<>(); - public boolean bukkitPickUpLoot; - public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper -+ public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event - - @Override - public float getBukkitYaw() { -@@ -1521,13 +1522,12 @@ public abstract class LivingEntity extends Entity implements Attackable { - - if (this.isDeadOrDying()) { - if (!this.checkTotemDeathProtection(source)) { -- SoundEvent soundeffect = this.getDeathSound(); -- -- if (flag1 && soundeffect != null) { -- this.playSound(soundeffect, this.getSoundVolume(), this.getVoicePitch()); -- } -+ // Paper start - moved into CraftEventFactory event caller for cancellable death event -+ this.silentDeath = !flag1; // mark entity as dying silently -+ // Paper end - - this.die(source); -+ this.silentDeath = false; // Paper - cancellable death event - reset to default - } - } else if (flag1) { - this.playHurtSound(source); -@@ -1679,6 +1679,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - Entity entity = damageSource.getEntity(); - LivingEntity entityliving = this.getKillCredit(); - -+ /* // Paper - move down to make death event cancellable - this is the awardKillScore below - if (this.deathScore >= 0 && entityliving != null) { - entityliving.awardKillScore(this, this.deathScore, damageSource); - } -@@ -1690,24 +1691,59 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (!this.level().isClientSide && this.hasCustomName()) { - if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot - } -+ */ // Paper - move down to make death event cancellable - this is the awardKillScore below - - this.dead = true; -- this.getCombatTracker().recheckStatus(); -+ // Paper - moved into if below - Level world = this.level(); - - if (world instanceof ServerLevel) { - ServerLevel worldserver = (ServerLevel) world; -+ // Paper - move below into if for onKill - -- if (entity == null || entity.killedEntity(worldserver, this)) { -+ // Paper start -+ org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(damageSource); -+ if (deathEvent == null || !deathEvent.isCancelled()) { -+ if (this.deathScore >= 0 && entityliving != null) { -+ entityliving.awardKillScore(this, this.deathScore, damageSource); -+ } -+ // Paper start - clear equipment if event is not cancelled -+ if (this instanceof Mob) { -+ for (EquipmentSlot slot : this.clearedEquipmentSlots) { -+ this.setItemSlot(slot, ItemStack.EMPTY); -+ } -+ this.clearedEquipmentSlots.clear(); -+ } -+ // Paper end -+ -+ if (this.isSleeping()) { -+ this.stopSleeping(); -+ } -+ -+ if (!this.level().isClientSide && this.hasCustomName()) { -+ if (org.spigotmc.SpigotConfig.logNamedDeaths) LivingEntity.LOGGER.info("Named entity {} died: {}", this, this.getCombatTracker().getDeathMessage().getString()); // Spigot -+ } -+ -+ this.getCombatTracker().recheckStatus(); -+ if (entity != null) { -+ entity.killedEntity((ServerLevel) this.level(), this); -+ } - this.gameEvent(GameEvent.ENTITY_DIE); -- this.dropAllDeathLoot(damageSource); -- this.createWitherRose(entityliving); -+ } else { -+ this.dead = false; -+ this.setHealth((float) deathEvent.getReviveHealth()); - } - -- this.level().broadcastEntityEvent(this, (byte) 3); -+ // Paper end -+ this.createWitherRose(entityliving); - } - -+ // Paper start -+ if (this.dead) { // Paper -+ this.level().broadcastEntityEvent(this, (byte) 3); - this.setPose(Pose.DYING); -+ } -+ // Paper end - } - } - -@@ -1715,7 +1751,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (!this.level().isClientSide) { - boolean flag = false; - -- if (adversary instanceof WitherBoss) { -+ if (this.dead && adversary instanceof WitherBoss) { // Paper - if (this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { - BlockPos blockposition = this.blockPosition(); - BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState(); -@@ -1744,7 +1780,11 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - } - -- protected void dropAllDeathLoot(DamageSource source) { -+ // Paper start -+ protected boolean clearEquipmentSlots = true; -+ protected Set clearedEquipmentSlots = new java.util.HashSet<>(); -+ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource source) { -+ // Paper end - Entity entity = source.getEntity(); - int i; - -@@ -1759,18 +1799,27 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.dropEquipment(); // CraftBukkit - from below - if (this.shouldDropLoot() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { - this.dropFromLootTable(source, flag); -+ // Paper start -+ final boolean prev = this.clearEquipmentSlots; -+ this.clearEquipmentSlots = false; -+ this.clearedEquipmentSlots.clear(); -+ // Paper end - this.dropCustomDeathLoot(source, i, flag); -+ this.clearEquipmentSlots = prev; // Paper - } - // CraftBukkit start - Call death event -- CraftEventFactory.callEntityDeathEvent(this, this.drops); -+ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops); // Paper -+ this.postDeathDropItems(deathEvent); // Paper - this.drops = new ArrayList<>(); - // CraftBukkit end - - // this.dropInventory();// CraftBukkit - moved up - this.dropExperience(); -+ return deathEvent; // Paper - } - - protected void dropEquipment() {} -+ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) {} // Paper - method for post death logic that cannot be ran before the event is potentially cancelled - - // CraftBukkit start - public int getExpReward() { -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 4ae0f36276592e37aeb5f881b713efa76d086f8e..236b10b86cdf6e0a723d8c9f199dde9cc983198e 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1057,6 +1057,12 @@ public abstract class Mob extends LivingEntity implements Targeting { - - } - -+ // Paper start -+ protected boolean shouldSkipLoot(EquipmentSlot slot) { // method to avoid to fallback into the global mob loot logic (i.e fox) -+ return false; -+ } -+ // Paper end -+ - @Override - protected void dropCustomDeathLoot(DamageSource source, int lootingMultiplier, boolean allowDrops) { - super.dropCustomDeathLoot(source, lootingMultiplier, allowDrops); -@@ -1065,6 +1071,7 @@ public abstract class Mob extends LivingEntity implements Targeting { - - for (int k = 0; k < j; ++k) { - EquipmentSlot enumitemslot = aenumitemslot[k]; -+ if (this.shouldSkipLoot(enumitemslot)) continue; // Paper - ItemStack itemstack = this.getItemBySlot(enumitemslot); - float f = this.getEquipmentDropChance(enumitemslot); - boolean flag1 = f > 1.0F; -@@ -1075,7 +1082,13 @@ public abstract class Mob extends LivingEntity implements Targeting { - } - - this.spawnAtLocation(itemstack); -+ if (this.clearEquipmentSlots) { // Paper - this.setItemSlot(enumitemslot, ItemStack.EMPTY); -+ // Paper start -+ } else { -+ this.clearedEquipmentSlots.add(enumitemslot); -+ } -+ // Paper end - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java -index 8670d8b2a08e96df787a91f36c48df8b345080dc..950e4b476a03fb5c26351a3b4d1578975a0b0ea8 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java -@@ -711,16 +711,38 @@ public class Fox extends Animal implements VariantHolder { - return this.getTrustedUUIDs().contains(uuid); - } - -+ // Paper start - handle the bitten item separately like vanilla - @Override -- protected void dropAllDeathLoot(DamageSource source) { -+ protected boolean shouldSkipLoot(EquipmentSlot slot) { -+ return slot == EquipmentSlot.MAINHAND; -+ } -+ // Paper end -+ -+ @Override -+ // Paper start - Cancellable death event -+ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource source) { - ItemStack itemstack = this.getItemBySlot(EquipmentSlot.MAINHAND); - -- if (!itemstack.isEmpty()) { -+ boolean releaseMouth = false; -+ if (!itemstack.isEmpty() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Fix MC-153010 - this.spawnAtLocation(itemstack); -+ releaseMouth = true; -+ } -+ -+ org.bukkit.event.entity.EntityDeathEvent deathEvent = super.dropAllDeathLoot(source); -+ -+ // Below is code to drop -+ -+ if (deathEvent == null || deathEvent.isCancelled()) { -+ return deathEvent; -+ } -+ -+ if (releaseMouth) { -+ // Paper end - Cancellable death event - this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); - } - -- super.dropAllDeathLoot(source); -+ return deathEvent; // Paper - Cancellable death event - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java -index bb399f775a5530a01f59332848c8ab9b8eceb2b5..14edfe103e61024b569f33de0b6608f39e749319 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractChestedHorse.java -@@ -70,11 +70,19 @@ public abstract class AbstractChestedHorse extends AbstractHorse { - this.spawnAtLocation(Blocks.CHEST); - } - -- this.setChest(false); -+ //this.setCarryingChest(false); // Paper - moved to post death logic - } - - } - -+ // Paper start -+ protected void postDeathDropItems(org.bukkit.event.entity.EntityDeathEvent event) { -+ if (this.hasChest() && (event == null || !event.isCancelled())) { -+ this.setChest(false); -+ } -+ } -+ // Paper end -+ - @Override - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -index ecce8036e58a9ed1408e110e75980bf77c18779a..ddd512e1d7608ec051fb5adf6ec2c6bbb93f5a9d 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -492,8 +492,10 @@ public class ArmorStand extends LivingEntity { - } - // CraftBukkit end - if (source.is(DamageTypeTags.IS_EXPLOSION)) { -- this.brokenByAnything(source); -- this.kill(); -+ // Paper start - avoid duplicate event call -+ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(source); -+ if (!event.isCancelled()) this.kill(false); -+ // Paper end - return false; - } else if (source.is(DamageTypeTags.IGNITES_ARMOR_STANDS)) { - if (this.isOnFire()) { -@@ -536,9 +538,9 @@ public class ArmorStand extends LivingEntity { - this.gameEvent(GameEvent.ENTITY_DAMAGE, source.getEntity()); - this.lastHit = i; - } else { -- this.brokenByPlayer(source); -+ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByPlayer(source); // Paper - this.showBreakingParticles(); -- this.discard(); // CraftBukkit - SPIGOT-4890: remain as this.discard() since above damagesource method will call death event -+ if (!event.isCancelled()) this.kill(false); // Paper - we still need to kill to follow vanilla logic (emit the game event etc...) - } - - return true; -@@ -590,8 +592,10 @@ public class ArmorStand extends LivingEntity { - - f1 -= amount; - if (f1 <= 0.5F) { -- this.brokenByAnything(damageSource); -- this.kill(); -+ // Paper start - avoid duplicate event call -+ org.bukkit.event.entity.EntityDeathEvent event = this.brokenByAnything(damageSource); -+ if (!event.isCancelled()) this.kill(false); -+ // Paper end - } else { - this.setHealth(f1); - this.gameEvent(GameEvent.ENTITY_DAMAGE, damageSource.getEntity()); -@@ -599,7 +603,7 @@ public class ArmorStand extends LivingEntity { - - } - -- private void brokenByPlayer(DamageSource damageSource) { -+ private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(DamageSource damageSource) { // Paper - ItemStack itemstack = new ItemStack(Items.ARMOR_STAND); - - if (this.hasCustomName()) { -@@ -607,10 +611,10 @@ public class ArmorStand extends LivingEntity { - } - - this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops -- this.brokenByAnything(damageSource); -+ return this.brokenByAnything(damageSource); // Paper - } - -- private void brokenByAnything(DamageSource damageSource) { -+ private org.bukkit.event.entity.EntityDeathEvent brokenByAnything(DamageSource damageSource) { // Paper - this.playBrokenSound(); - // this.dropAllDeathLoot(damagesource); // CraftBukkit - moved down - -@@ -632,7 +636,7 @@ public class ArmorStand extends LivingEntity { - this.armorItems.set(i, ItemStack.EMPTY); - } - } -- this.dropAllDeathLoot(damageSource); // CraftBukkit - moved from above -+ return this.dropAllDeathLoot(damageSource); // CraftBukkit - moved from above // Paper - - } - -@@ -759,7 +763,16 @@ public class ArmorStand extends LivingEntity { - - @Override - public void kill() { -- org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, this.drops); // CraftBukkit - call event -+ // Paper start -+ kill(true); -+ } -+ -+ public void kill(boolean callEvent) { -+ if (callEvent) { -+ // Paper end -+ org.bukkit.event.entity.EntityDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, this.drops); // CraftBukkit - call event // Paper - make cancellable -+ if (event.isCancelled()) return; // Paper - make cancellable -+ } // Paper - this.remove(Entity.RemovalReason.KILLED); - this.gameEvent(GameEvent.ENTITY_DIE); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index b98f26b6958cb3f874a4c0d85e2c10ec517cbbff..adcba9111415a20a4a55bb182be84c9f6d3755d7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2383,7 +2383,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - @Override - public void sendHealthUpdate() { - FoodData foodData = this.getHandle().getFoodData(); -- this.sendHealthUpdate(this.getScaledHealth(), foodData.getFoodLevel(), foodData.getSaturationLevel()); -+ // Paper start - cancellable death event -+ ClientboundSetHealthPacket packet = new ClientboundSetHealthPacket(this.getScaledHealth(), foodData.getFoodLevel(), foodData.getSaturationLevel()); -+ if (this.getHandle().queueHealthUpdatePacket) { -+ this.getHandle().queuedHealthUpdatePacket = packet; -+ } else { -+ this.getHandle().connection.send(packet); -+ } -+ // Paper end - } - - public void injectScaledMaxHealth(Collection collection, boolean force) { -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 3ee2b011689b9d2df03947c1e04e3042b8e8c853..131fa4d389c3ddad31651f298ccd807847462f38 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -890,9 +890,16 @@ public class CraftEventFactory { - public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops) { - CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); - EntityDeathEvent event = new EntityDeathEvent(entity, drops, victim.getExpReward()); -+ populateFields(victim, event); // Paper - make cancellable - CraftWorld world = (CraftWorld) entity.getWorld(); - Bukkit.getServer().getPluginManager().callEvent(event); - -+ // Paper start - make cancellable -+ if (event.isCancelled()) { -+ return event; -+ } -+ playDeathSound(victim, event); -+ // Paper end - victim.expToDrop = event.getDroppedExp(); - - for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { -@@ -909,8 +916,15 @@ public class CraftEventFactory { - PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage); - event.setKeepInventory(keepInventory); - event.setKeepLevel(victim.keepLevel); // SPIGOT-2222: pre-set keepLevel -+ populateFields(victim, event); // Paper - make cancellable - org.bukkit.World world = entity.getWorld(); - Bukkit.getServer().getPluginManager().callEvent(event); -+ // Paper start - make cancellable -+ if (event.isCancelled()) { -+ return event; -+ } -+ playDeathSound(victim, event); -+ // Paper end - - victim.keepLevel = event.getKeepLevel(); - victim.newLevel = event.getNewLevel(); -@@ -927,6 +941,31 @@ public class CraftEventFactory { - return event; - } - -+ // Paper start - helper methods for making death event cancellable -+ // Add information to death event -+ private static void populateFields(net.minecraft.world.entity.LivingEntity victim, EntityDeathEvent event) { -+ event.setReviveHealth(event.getEntity().getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH).getValue()); -+ event.setShouldPlayDeathSound(!victim.silentDeath && !victim.isSilent()); -+ net.minecraft.sounds.SoundEvent soundEffect = victim.getDeathSound(); -+ event.setDeathSound(soundEffect != null ? org.bukkit.craftbukkit.CraftSound.minecraftToBukkit(soundEffect) : null); -+ event.setDeathSoundCategory(org.bukkit.SoundCategory.valueOf(victim.getSoundSource().name())); -+ event.setDeathSoundVolume(victim.getSoundVolume()); -+ event.setDeathSoundPitch(victim.getVoicePitch()); -+ } -+ -+ // Play death sound manually -+ private static void playDeathSound(net.minecraft.world.entity.LivingEntity victim, EntityDeathEvent event) { -+ if (event.shouldPlayDeathSound() && event.getDeathSound() != null && event.getDeathSoundCategory() != null) { -+ net.minecraft.world.entity.player.Player source = victim instanceof net.minecraft.world.entity.player.Player ? (net.minecraft.world.entity.player.Player) victim : null; -+ double x = event.getEntity().getLocation().getX(); -+ double y = event.getEntity().getLocation().getY(); -+ double z = event.getEntity().getLocation().getZ(); -+ net.minecraft.sounds.SoundEvent soundEffect = org.bukkit.craftbukkit.CraftSound.bukkitToMinecraft(event.getDeathSound()); -+ net.minecraft.sounds.SoundSource soundCategory = net.minecraft.sounds.SoundSource.valueOf(event.getDeathSoundCategory().name()); -+ victim.level().playSound(source, x, y, z, soundEffect, soundCategory, event.getDeathSoundVolume(), event.getDeathSoundPitch()); -+ } -+ } -+ // Paper end - /** - * Server methods - */ diff --git a/patches/server/0247-Allow-chests-to-be-placed-with-NBT-data.patch b/patches/server/0247-Allow-chests-to-be-placed-with-NBT-data.patch deleted file mode 100644 index 13419c163628..000000000000 --- a/patches/server/0247-Allow-chests-to-be-placed-with-NBT-data.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 8 Sep 2018 18:43:31 -0500 -Subject: [PATCH] Allow chests to be placed with NBT data - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 7f85121b6830ebf480c5ca7b42d3c835911de836..9b9504ca32d8cc7c037e0a96f2d8aa03d5c5495d 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -407,6 +407,7 @@ public final class ItemStack { - enuminteractionresult = InteractionResult.FAIL; // cancel placement - // PAIL: Remove this when MC-99075 fixed - placeEvent.getPlayer().updateInventory(); -+ world.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot - // revert back all captured blocks - world.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710 - for (BlockState blockstate : blocks) { -diff --git a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java -index d4f5af759bbb6208432ad7b5002af5455dc7958c..9b1243d96e0694c62fc9e82e9be540bce0d2b3ad 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java -@@ -237,7 +237,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement - // CraftBukkit start - @Override - public boolean onlyOpCanSetNbt() { -- return true; -+ return false; // Paper - Allow chests to be placed with NBT data - } - // CraftBukkit end - } diff --git a/patches/server/0248-Mob-Pathfinding-API.patch b/patches/server/0247-Mob-Pathfinding-API.patch similarity index 100% rename from patches/server/0248-Mob-Pathfinding-API.patch rename to patches/server/0247-Mob-Pathfinding-API.patch diff --git a/patches/server/0249-Add-API-for-CanPlaceOn-and-CanDestroy-NBT-values.patch b/patches/server/0248-Add-API-for-CanPlaceOn-and-CanDestroy-NBT-values.patch similarity index 100% rename from patches/server/0249-Add-API-for-CanPlaceOn-and-CanDestroy-NBT-values.patch rename to patches/server/0248-Add-API-for-CanPlaceOn-and-CanDestroy-NBT-values.patch diff --git a/patches/server/0250-Prevent-various-interactions-from-causing-chunk-load.patch b/patches/server/0249-Prevent-various-interactions-from-causing-chunk-load.patch similarity index 100% rename from patches/server/0250-Prevent-various-interactions-from-causing-chunk-load.patch rename to patches/server/0249-Prevent-various-interactions-from-causing-chunk-load.patch diff --git a/patches/server/0250-Prevent-mob-spawning-from-loading-generating-chunks.patch b/patches/server/0250-Prevent-mob-spawning-from-loading-generating-chunks.patch new file mode 100644 index 000000000000..406112a4c1b8 --- /dev/null +++ b/patches/server/0250-Prevent-mob-spawning-from-loading-generating-chunks.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 12 Sep 2018 21:12:57 -0400 +Subject: [PATCH] Prevent mob spawning from loading/generating chunks + +also prevents if out of world border bounds + +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index 352d230b365c512a4b9831265d3a57825f80fbfd..d5ada3301429e7fec0d157d7a33d4937e0f82fbb 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -170,9 +170,9 @@ public final class NaturalSpawner { + StructureManager structuremanager = world.structureManager(); + ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); + int i = pos.getY(); +- BlockState iblockdata = chunk.getBlockState(pos); ++ BlockState iblockdata = world.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn + +- if (!iblockdata.isRedstoneConductor(chunk, pos)) { ++ if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); + int j = 0; + int k = 0; +@@ -201,7 +201,7 @@ public final class NaturalSpawner { + if (entityhuman != null) { + double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); + +- if (NaturalSpawner.isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2)) { ++ if (world.isLoadedAndInBounds(blockposition_mutableblockposition) && NaturalSpawner.isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2)) { // Paper - don't load chunks for mob spawn + if (biomesettingsmobs_c == null) { + Optional optional = NaturalSpawner.getRandomSpawnMobAt(world, structuremanager, chunkgenerator, group, world.random, blockposition_mutableblockposition); + diff --git a/patches/server/0252-Implement-furnace-cook-speed-multiplier-API.patch b/patches/server/0251-Implement-furnace-cook-speed-multiplier-API.patch similarity index 100% rename from patches/server/0252-Implement-furnace-cook-speed-multiplier-API.patch rename to patches/server/0251-Implement-furnace-cook-speed-multiplier-API.patch diff --git a/patches/server/0251-Prevent-mob-spawning-from-loading-generating-chunks.patch b/patches/server/0251-Prevent-mob-spawning-from-loading-generating-chunks.patch deleted file mode 100644 index 5933a5395ee2..000000000000 --- a/patches/server/0251-Prevent-mob-spawning-from-loading-generating-chunks.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 12 Sep 2018 21:12:57 -0400 -Subject: [PATCH] Prevent mob spawning from loading/generating chunks - -also prevents if out of world border bounds - -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 286592be7aeb183d1a9ee439c250f2acf932f0bf..aec4897a647206da20666f4d54cdc5d1b516bfc2 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -170,9 +170,9 @@ public final class NaturalSpawner { - StructureManager structuremanager = world.structureManager(); - ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); - int i = pos.getY(); -- BlockState iblockdata = chunk.getBlockState(pos); -+ BlockState iblockdata = world.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn - -- if (!iblockdata.isRedstoneConductor(chunk, pos)) { -+ if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn - BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); - int j = 0; - int k = 0; -@@ -201,7 +201,7 @@ public final class NaturalSpawner { - if (entityhuman != null) { - double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); - -- if (NaturalSpawner.isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2)) { -+ if (world.isLoadedAndInBounds(blockposition_mutableblockposition) && NaturalSpawner.isRightDistanceToPlayerAndSpawnPoint(world, chunk, blockposition_mutableblockposition, d2)) { // Paper - don't load chunks for mob spawn - if (biomesettingsmobs_c == null) { - Optional optional = NaturalSpawner.getRandomSpawnMobAt(world, structuremanager, chunkgenerator, group, world.random, blockposition_mutableblockposition); - diff --git a/patches/server/0253-Honor-EntityAgeable.ageLock.patch b/patches/server/0252-Honor-EntityAgeable.ageLock.patch similarity index 100% rename from patches/server/0253-Honor-EntityAgeable.ageLock.patch rename to patches/server/0252-Honor-EntityAgeable.ageLock.patch diff --git a/patches/server/0254-Configurable-connection-throttle-kick-message.patch b/patches/server/0253-Configurable-connection-throttle-kick-message.patch similarity index 100% rename from patches/server/0254-Configurable-connection-throttle-kick-message.patch rename to patches/server/0253-Configurable-connection-throttle-kick-message.patch diff --git a/patches/server/0255-Prevent-chunk-loading-from-Fluid-Flowing.patch b/patches/server/0254-Prevent-chunk-loading-from-Fluid-Flowing.patch similarity index 100% rename from patches/server/0255-Prevent-chunk-loading-from-Fluid-Flowing.patch rename to patches/server/0254-Prevent-chunk-loading-from-Fluid-Flowing.patch diff --git a/patches/server/0256-Hook-into-CB-plugin-rewrites.patch b/patches/server/0255-Hook-into-CB-plugin-rewrites.patch similarity index 100% rename from patches/server/0256-Hook-into-CB-plugin-rewrites.patch rename to patches/server/0255-Hook-into-CB-plugin-rewrites.patch diff --git a/patches/server/0257-PreSpawnerSpawnEvent.patch b/patches/server/0256-PreSpawnerSpawnEvent.patch similarity index 100% rename from patches/server/0257-PreSpawnerSpawnEvent.patch rename to patches/server/0256-PreSpawnerSpawnEvent.patch diff --git a/patches/server/0257-Add-LivingEntity-getTargetEntity.patch b/patches/server/0257-Add-LivingEntity-getTargetEntity.patch new file mode 100644 index 000000000000..fb454512de5b --- /dev/null +++ b/patches/server/0257-Add-LivingEntity-getTargetEntity.patch @@ -0,0 +1,108 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 22 Sep 2018 00:33:08 -0500 +Subject: [PATCH] Add LivingEntity#getTargetEntity + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 78f601ff13b160c0661ba0b60365403f9eb7fffb..792473529c8afbabe2e501ab223eacaac08fcb96 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -115,6 +115,7 @@ import net.minecraft.world.level.storage.loot.LootTable; + import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; + import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + import net.minecraft.world.phys.AABB; ++import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.scores.PlayerTeam; +@@ -3903,6 +3904,38 @@ public abstract class LivingEntity extends Entity implements Attackable { + return this.level().clip(raytrace); + } + ++ public @Nullable EntityHitResult getTargetEntity(int maxDistance) { ++ if (maxDistance < 1 || maxDistance > 120) { ++ throw new IllegalArgumentException("maxDistance must be between 1-120"); ++ } ++ ++ Vec3 start = this.getEyePosition(1.0F); ++ Vec3 direction = this.getLookAngle(); ++ Vec3 end = start.add(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance); ++ ++ List entityList = this.level().getEntities(this, getBoundingBox().expandTowards(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance).inflate(1.0D, 1.0D, 1.0D), EntitySelector.NO_SPECTATORS.and(Entity::isPickable)); ++ ++ double distance = 0.0D; ++ EntityHitResult result = null; ++ ++ for (Entity entity : entityList) { ++ final double inflationAmount = (double) entity.getPickRadius(); ++ AABB aabb = entity.getBoundingBox().inflate(inflationAmount, inflationAmount, inflationAmount); ++ Optional rayTraceResult = aabb.clip(start, end); ++ ++ if (rayTraceResult.isPresent()) { ++ Vec3 rayTrace = rayTraceResult.get(); ++ double distanceTo = start.distanceToSqr(rayTrace); ++ if (distanceTo < distance || distance == 0.0D) { ++ result = new EntityHitResult(entity, rayTrace); ++ distance = distanceTo; ++ } ++ } ++ } ++ ++ return result; ++ } ++ + public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; + + public int getShieldBlockingDelay() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 1510b4d8fc08f4455b38ad4edb6bdf91ad317c96..ea9a6446ed2dbddedbc615df93c9856a0a1f1a2e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1,5 +1,6 @@ + package org.bukkit.craftbukkit.entity; + ++import com.destroystokyo.paper.entity.TargetEntityInfo; + import com.google.common.base.Preconditions; + import com.google.common.collect.Sets; + import java.util.ArrayList; +@@ -226,6 +227,39 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } + return null; + } ++ ++ public Entity getTargetEntity(int maxDistance, boolean ignoreBlocks) { ++ net.minecraft.world.phys.EntityHitResult rayTrace = rayTraceEntity(maxDistance, ignoreBlocks); ++ return rayTrace == null ? null : rayTrace.getEntity().getBukkitEntity(); ++ } ++ ++ public TargetEntityInfo getTargetEntityInfo(int maxDistance, boolean ignoreBlocks) { ++ net.minecraft.world.phys.EntityHitResult rayTrace = rayTraceEntity(maxDistance, ignoreBlocks); ++ return rayTrace == null ? null : new TargetEntityInfo(rayTrace.getEntity().getBukkitEntity(), new org.bukkit.util.Vector(rayTrace.getLocation().x, rayTrace.getLocation().y, rayTrace.getLocation().z)); ++ } ++ ++ @Override ++ public RayTraceResult rayTraceEntities(int maxDistance, boolean ignoreBlocks) { ++ net.minecraft.world.phys.EntityHitResult rayTrace = this.rayTraceEntity(maxDistance, ignoreBlocks); ++ return rayTrace == null ? null : new org.bukkit.util.RayTraceResult(org.bukkit.craftbukkit.util.CraftVector.toBukkit(rayTrace.getLocation()), rayTrace.getEntity().getBukkitEntity()); ++ } ++ ++ public net.minecraft.world.phys.EntityHitResult rayTraceEntity(int maxDistance, boolean ignoreBlocks) { ++ net.minecraft.world.phys.EntityHitResult rayTrace = getHandle().getTargetEntity(maxDistance); ++ if (rayTrace == null) { ++ return null; ++ } ++ if (!ignoreBlocks) { ++ net.minecraft.world.phys.HitResult rayTraceBlocks = getHandle().getRayTrace(maxDistance, net.minecraft.world.level.ClipContext.Fluid.NONE); ++ if (rayTraceBlocks != null) { ++ net.minecraft.world.phys.Vec3 eye = getHandle().getEyePosition(1.0F); ++ if (eye.distanceToSqr(rayTraceBlocks.getLocation()) <= eye.distanceToSqr(rayTrace.getLocation())) { ++ return null; ++ } ++ } ++ } ++ return rayTrace; ++ } + // Paper end + + @Override diff --git a/patches/server/0258-Add-LivingEntity-getTargetEntity.patch b/patches/server/0258-Add-LivingEntity-getTargetEntity.patch deleted file mode 100644 index fa447f577dc3..000000000000 --- a/patches/server/0258-Add-LivingEntity-getTargetEntity.patch +++ /dev/null @@ -1,108 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 22 Sep 2018 00:33:08 -0500 -Subject: [PATCH] Add LivingEntity#getTargetEntity - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index bd70046b4fef307b6b8b626a65f0b4a366ab7158..1f89fe5ee232089df6b63277d6e2938da840f5c0 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -115,6 +115,7 @@ import net.minecraft.world.level.storage.loot.LootTable; - import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; - import net.minecraft.world.level.storage.loot.parameters.LootContextParams; - import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.EntityHitResult; - import net.minecraft.world.phys.HitResult; - import net.minecraft.world.phys.Vec3; - import net.minecraft.world.scores.PlayerTeam; -@@ -3890,6 +3891,38 @@ public abstract class LivingEntity extends Entity implements Attackable { - return this.level().clip(raytrace); - } - -+ public @Nullable EntityHitResult getTargetEntity(int maxDistance) { -+ if (maxDistance < 1 || maxDistance > 120) { -+ throw new IllegalArgumentException("maxDistance must be between 1-120"); -+ } -+ -+ Vec3 start = this.getEyePosition(1.0F); -+ Vec3 direction = this.getLookAngle(); -+ Vec3 end = start.add(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance); -+ -+ List entityList = this.level().getEntities(this, getBoundingBox().expandTowards(direction.x * maxDistance, direction.y * maxDistance, direction.z * maxDistance).inflate(1.0D, 1.0D, 1.0D), EntitySelector.NO_SPECTATORS.and(Entity::isPickable)); -+ -+ double distance = 0.0D; -+ EntityHitResult result = null; -+ -+ for (Entity entity : entityList) { -+ final double inflationAmount = (double) entity.getPickRadius(); -+ AABB aabb = entity.getBoundingBox().inflate(inflationAmount, inflationAmount, inflationAmount); -+ Optional rayTraceResult = aabb.clip(start, end); -+ -+ if (rayTraceResult.isPresent()) { -+ Vec3 rayTrace = rayTraceResult.get(); -+ double distanceTo = start.distanceToSqr(rayTrace); -+ if (distanceTo < distance || distance == 0.0D) { -+ result = new EntityHitResult(entity, rayTrace); -+ distance = distanceTo; -+ } -+ } -+ } -+ -+ return result; -+ } -+ - public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; - - public int getShieldBlockingDelay() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 7ac9515c0cca4e6a1197a42d5ff1dff04d183bd5..fc7b54892af62e8b6e1cc93f2937e05bbfd01c8c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -1,5 +1,6 @@ - package org.bukkit.craftbukkit.entity; - -+import com.destroystokyo.paper.entity.TargetEntityInfo; - import com.google.common.base.Preconditions; - import com.google.common.collect.Sets; - import java.util.ArrayList; -@@ -225,6 +226,39 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } - return null; - } -+ -+ public Entity getTargetEntity(int maxDistance, boolean ignoreBlocks) { -+ net.minecraft.world.phys.EntityHitResult rayTrace = rayTraceEntity(maxDistance, ignoreBlocks); -+ return rayTrace == null ? null : rayTrace.getEntity().getBukkitEntity(); -+ } -+ -+ public TargetEntityInfo getTargetEntityInfo(int maxDistance, boolean ignoreBlocks) { -+ net.minecraft.world.phys.EntityHitResult rayTrace = rayTraceEntity(maxDistance, ignoreBlocks); -+ return rayTrace == null ? null : new TargetEntityInfo(rayTrace.getEntity().getBukkitEntity(), new org.bukkit.util.Vector(rayTrace.getLocation().x, rayTrace.getLocation().y, rayTrace.getLocation().z)); -+ } -+ -+ @Override -+ public RayTraceResult rayTraceEntities(int maxDistance, boolean ignoreBlocks) { -+ net.minecraft.world.phys.EntityHitResult rayTrace = this.rayTraceEntity(maxDistance, ignoreBlocks); -+ return rayTrace == null ? null : new org.bukkit.util.RayTraceResult(org.bukkit.craftbukkit.util.CraftVector.toBukkit(rayTrace.getLocation()), rayTrace.getEntity().getBukkitEntity()); -+ } -+ -+ public net.minecraft.world.phys.EntityHitResult rayTraceEntity(int maxDistance, boolean ignoreBlocks) { -+ net.minecraft.world.phys.EntityHitResult rayTrace = getHandle().getTargetEntity(maxDistance); -+ if (rayTrace == null) { -+ return null; -+ } -+ if (!ignoreBlocks) { -+ net.minecraft.world.phys.HitResult rayTraceBlocks = getHandle().getRayTrace(maxDistance, net.minecraft.world.level.ClipContext.Fluid.NONE); -+ if (rayTraceBlocks != null) { -+ net.minecraft.world.phys.Vec3 eye = getHandle().getEyePosition(1.0F); -+ if (eye.distanceToSqr(rayTraceBlocks.getLocation()) <= eye.distanceToSqr(rayTrace.getLocation())) { -+ return null; -+ } -+ } -+ } -+ return rayTrace; -+ } - // Paper end - - @Override diff --git a/patches/server/0258-Add-sun-related-API.patch b/patches/server/0258-Add-sun-related-API.patch new file mode 100644 index 000000000000..9725c39278f4 --- /dev/null +++ b/patches/server/0258-Add-sun-related-API.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sun, 7 Oct 2018 00:54:21 -0500 +Subject: [PATCH] Add sun related API + +== AT == +public net.minecraft.world.entity.Mob isSunBurnTick()Z + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 358fe6e35ab7555bbd6ae075bcec5249e09dede1..0cded703702e7271eec909e470fbfded17eb791f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -711,6 +711,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + } + ++ // Paper start ++ @Override ++ public boolean isDayTime() { ++ return getHandle().isDay(); ++ } ++ // Paper end ++ + @Override + public long getGameTime() { + return this.world.levelData.getGameTime(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index 018884ced888fcd03d2fb17b3620f8e6125e67da..0ad9885f939bcb50026d50ed78b970a44772ceb8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -93,4 +93,11 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + public long getSeed() { + return this.getHandle().lootTableSeed; + } ++ ++ // Paper start ++ @Override ++ public boolean isInDaylight() { ++ return getHandle().isSunBurnTick(); ++ } ++ // Paper end + } diff --git a/patches/server/0259-Add-sun-related-API.patch b/patches/server/0259-Add-sun-related-API.patch deleted file mode 100644 index 31139b29c173..000000000000 --- a/patches/server/0259-Add-sun-related-API.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sun, 7 Oct 2018 00:54:21 -0500 -Subject: [PATCH] Add sun related API - -== AT == -public net.minecraft.world.entity.Mob isSunBurnTick()Z - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index e0cb72d826168f3c22c5be4c2c8e6d760edafb97..400dbd72c576590ccdbab9c52a3960acb457102f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -705,6 +705,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { - } - } - -+ // Paper start -+ @Override -+ public boolean isDayTime() { -+ return getHandle().isDay(); -+ } -+ // Paper end -+ - @Override - public long getGameTime() { - return this.world.levelData.getGameTime(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -index 018884ced888fcd03d2fb17b3620f8e6125e67da..0ad9885f939bcb50026d50ed78b970a44772ceb8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -@@ -93,4 +93,11 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { - public long getSeed() { - return this.getHandle().lootTableSeed; - } -+ -+ // Paper start -+ @Override -+ public boolean isInDaylight() { -+ return getHandle().isSunBurnTick(); -+ } -+ // Paper end - } diff --git a/patches/server/0260-Catch-JsonParseException-in-entity-and-block-entity-.patch b/patches/server/0259-Catch-JsonParseException-in-entity-and-block-entity-.patch similarity index 100% rename from patches/server/0260-Catch-JsonParseException-in-entity-and-block-entity-.patch rename to patches/server/0259-Catch-JsonParseException-in-entity-and-block-entity-.patch diff --git a/patches/server/0260-Turtle-API.patch b/patches/server/0260-Turtle-API.patch new file mode 100644 index 000000000000..c72425f93acb --- /dev/null +++ b/patches/server/0260-Turtle-API.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 29 Sep 2018 16:08:23 -0500 +Subject: [PATCH] Turtle API + +== AT == +public net.minecraft.world.entity.animal.Turtle getHomePos()Lnet/minecraft/core/BlockPos; +public net.minecraft.world.entity.animal.Turtle setHasEgg(Z)V +public net.minecraft.world.entity.animal.Turtle isGoingHome()Z +public net.minecraft.world.entity.animal.Turtle setGoingHome(Z)V +public net.minecraft.world.entity.animal.Turtle isTravelling()Z +public net.minecraft.world.entity.animal.Turtle setTravelling(Z)V + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +index 11322066522a3268063bad7267ef4dd4f06d983e..cfc0cee09dfd522409bb5853fc96528bd0137475 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +@@ -489,14 +489,17 @@ public class Turtle extends Animal { + + if (!this.turtle.isInWater() && this.isReachedTarget()) { + if (this.turtle.layEggCounter < 1) { +- this.turtle.setLayingEgg(true); ++ this.turtle.setLayingEgg(new com.destroystokyo.paper.event.entity.TurtleStartDiggingEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos)).callEvent()); // Paper - Turtle API + } else if (this.turtle.layEggCounter > this.adjustedTickDelay(200)) { + Level world = this.turtle.level(); + +- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1))) { // CraftBukkit ++ // Paper start - Turtle API ++ int eggCount = this.turtle.random.nextInt(4) + 1; ++ com.destroystokyo.paper.event.entity.TurtleLayEggEvent layEggEvent = new com.destroystokyo.paper.event.entity.TurtleLayEggEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos.above()), eggCount); ++ if (layEggEvent.callEvent() && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()))) { + world.playSound((Player) null, blockposition, SoundEvents.TURTLE_LAY_EGG, SoundSource.BLOCKS, 0.3F, 0.9F + world.random.nextFloat() * 0.2F); + BlockPos blockposition1 = this.blockPos.above(); +- BlockState iblockdata = (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1); ++ BlockState iblockdata = (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()); // Paper + + world.setBlock(blockposition1, iblockdata, 3); + world.gameEvent(GameEvent.BLOCK_PLACE, blockposition1, GameEvent.Context.of(this.turtle, iblockdata)); +@@ -566,7 +569,7 @@ public class Turtle extends Animal { + + @Override + public boolean canUse() { +- return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(reducedTickDelay(700)) != 0 ? false : !this.turtle.getHomePos().closerToCenterThan(this.turtle.position(), 64.0D))); ++ return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(reducedTickDelay(700)) != 0 ? false : !this.turtle.getHomePos().closerToCenterThan(this.turtle.position(), 64.0D))) && new com.destroystokyo.paper.event.entity.TurtleGoHomeEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity()).callEvent(); // Paper - Turtle API + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java +index fac0317ff945db991e761ac8973f0d3d41ade26b..d44e6f4bb682d18c1497eee9fb2802f2bda6e840 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java +@@ -28,4 +28,31 @@ public class CraftTurtle extends CraftAnimals implements Turtle { + public boolean isLayingEgg() { + return this.getHandle().isLayingEgg(); + } ++ ++ // Paper start ++ @Override ++ public org.bukkit.Location getHome() { ++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), this.getHandle().getHomePos()); ++ } ++ ++ @Override ++ public void setHome(org.bukkit.Location location) { ++ this.getHandle().setHomePos(io.papermc.paper.util.MCUtil.toBlockPosition(location)); ++ } ++ ++ @Override ++ public boolean isGoingHome() { ++ return this.getHandle().isGoingHome(); ++ } ++ ++ @Override ++ public boolean isDigging() { ++ return this.getHandle().isLayingEgg(); ++ } ++ ++ @Override ++ public void setHasEgg(boolean hasEgg) { ++ this.getHandle().setHasEgg(hasEgg); ++ } ++ // Paper end + } diff --git a/patches/server/0261-Call-player-spectator-target-events-and-improve-impl.patch b/patches/server/0261-Call-player-spectator-target-events-and-improve-impl.patch new file mode 100644 index 000000000000..4dccbd3f6327 --- /dev/null +++ b/patches/server/0261-Call-player-spectator-target-events-and-improve-impl.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Caleb Bassham +Date: Fri, 28 Sep 2018 02:32:19 -0500 +Subject: [PATCH] Call player spectator target events and improve + implementation + +Use a proper teleport for teleporting to entities in different +worlds. + +Implementation improvements authored by Spottedleaf +Validate that the target entity is valid and deny spectate +requests from frozen players. + +Also, make sure the entity is spawned to the client before +sending the camera packet. If the entity isn't spawned clientside +when it receives the camera packet, then the client will not +spectate the target entity. + +Co-authored-by: Spottedleaf + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 80cedd5221d3c0dc475c631113e8782507067cb0..3a991a6ef6a8c3150bdc557219424cf13f1dc0cc 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2041,6 +2041,21 @@ public class ServerPlayer extends Player { + + this.camera = (Entity) (entity == null ? this : entity); + if (entity1 != this.camera) { ++ // Paper start - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity ++ if (this.camera == this) { ++ com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent playerStopSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity()); ++ if (!playerStopSpectatingEntityEvent.callEvent()) { ++ this.camera = entity1; // rollback camera entity again ++ return; ++ } ++ } else { ++ com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent playerStartSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity(), entity.getBukkitEntity()); ++ if (!playerStartSpectatingEntityEvent.callEvent()) { ++ this.camera = entity1; // rollback camera entity again ++ return; ++ } ++ } ++ // Paper end - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity + Level world = this.camera.level(); + + if (world instanceof ServerLevel) { diff --git a/patches/server/0261-Turtle-API.patch b/patches/server/0261-Turtle-API.patch deleted file mode 100644 index cfcc79e8a827..000000000000 --- a/patches/server/0261-Turtle-API.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 29 Sep 2018 16:08:23 -0500 -Subject: [PATCH] Turtle API - -== AT == -public net.minecraft.world.entity.animal.Turtle getHomePos()Lnet/minecraft/core/BlockPos; -public net.minecraft.world.entity.animal.Turtle setHasEgg(Z)V -public net.minecraft.world.entity.animal.Turtle isGoingHome()Z -public net.minecraft.world.entity.animal.Turtle setGoingHome(Z)V -public net.minecraft.world.entity.animal.Turtle isTravelling()Z -public net.minecraft.world.entity.animal.Turtle setTravelling(Z)V - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 0dab0da65788720e56a568918de458ab7195ef5c..6bbcdd34fb89ea5774e825de8f9a588552716fc2 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -491,14 +491,17 @@ public class Turtle extends Animal { - - if (!this.turtle.isInWater() && this.isReachedTarget()) { - if (this.turtle.layEggCounter < 1) { -- this.turtle.setLayingEgg(true); -+ this.turtle.setLayingEgg(new com.destroystokyo.paper.event.entity.TurtleStartDiggingEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos)).callEvent()); // Paper - Turtle API - } else if (this.turtle.layEggCounter > this.adjustedTickDelay(200)) { - Level world = this.turtle.level(); - -- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1))) { // CraftBukkit -+ // Paper start - Turtle API -+ int eggCount = this.turtle.random.nextInt(4) + 1; -+ com.destroystokyo.paper.event.entity.TurtleLayEggEvent layEggEvent = new com.destroystokyo.paper.event.entity.TurtleLayEggEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(this.turtle.level(), this.blockPos.above()), eggCount); -+ if (layEggEvent.callEvent() && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()))) { - world.playSound((Player) null, blockposition, SoundEvents.TURTLE_LAY_EGG, SoundSource.BLOCKS, 0.3F, 0.9F + world.random.nextFloat() * 0.2F); - BlockPos blockposition1 = this.blockPos.above(); -- BlockState iblockdata = (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1); -+ BlockState iblockdata = (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, layEggEvent.getEggCount()); // Paper - - world.setBlock(blockposition1, iblockdata, 3); - world.gameEvent(GameEvent.BLOCK_PLACE, blockposition1, GameEvent.Context.of(this.turtle, iblockdata)); -@@ -568,7 +571,7 @@ public class Turtle extends Animal { - - @Override - public boolean canUse() { -- return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(reducedTickDelay(700)) != 0 ? false : !this.turtle.getHomePos().closerToCenterThan(this.turtle.position(), 64.0D))); -+ return this.turtle.isBaby() ? false : (this.turtle.hasEgg() ? true : (this.turtle.getRandom().nextInt(reducedTickDelay(700)) != 0 ? false : !this.turtle.getHomePos().closerToCenterThan(this.turtle.position(), 64.0D))) && new com.destroystokyo.paper.event.entity.TurtleGoHomeEvent((org.bukkit.entity.Turtle) this.turtle.getBukkitEntity()).callEvent(); // Paper - Turtle API - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java -index fac0317ff945db991e761ac8973f0d3d41ade26b..d44e6f4bb682d18c1497eee9fb2802f2bda6e840 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java -@@ -28,4 +28,31 @@ public class CraftTurtle extends CraftAnimals implements Turtle { - public boolean isLayingEgg() { - return this.getHandle().isLayingEgg(); - } -+ -+ // Paper start -+ @Override -+ public org.bukkit.Location getHome() { -+ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), this.getHandle().getHomePos()); -+ } -+ -+ @Override -+ public void setHome(org.bukkit.Location location) { -+ this.getHandle().setHomePos(io.papermc.paper.util.MCUtil.toBlockPosition(location)); -+ } -+ -+ @Override -+ public boolean isGoingHome() { -+ return this.getHandle().isGoingHome(); -+ } -+ -+ @Override -+ public boolean isDigging() { -+ return this.getHandle().isLayingEgg(); -+ } -+ -+ @Override -+ public void setHasEgg(boolean hasEgg) { -+ this.getHandle().setHasEgg(hasEgg); -+ } -+ // Paper end - } diff --git a/patches/server/0263-Add-more-Witch-API.patch b/patches/server/0262-Add-more-Witch-API.patch similarity index 100% rename from patches/server/0263-Add-more-Witch-API.patch rename to patches/server/0262-Add-more-Witch-API.patch diff --git a/patches/server/0262-Call-player-spectator-target-events-and-improve-impl.patch b/patches/server/0262-Call-player-spectator-target-events-and-improve-impl.patch deleted file mode 100644 index f9512688bf5d..000000000000 --- a/patches/server/0262-Call-player-spectator-target-events-and-improve-impl.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Caleb Bassham -Date: Fri, 28 Sep 2018 02:32:19 -0500 -Subject: [PATCH] Call player spectator target events and improve - implementation - -Use a proper teleport for teleporting to entities in different -worlds. - -Implementation improvements authored by Spottedleaf -Validate that the target entity is valid and deny spectate -requests from frozen players. - -Also, make sure the entity is spawned to the client before -sending the camera packet. If the entity isn't spawned clientside -when it receives the camera packet, then the client will not -spectate the target entity. - -Co-authored-by: Spottedleaf - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 1042b19c906e40d4a4e9ef76d69228149e84e3c5..8432081cb287dbdbc7e89572e91fa634437b9809 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2041,6 +2041,21 @@ public class ServerPlayer extends Player { - - this.camera = (Entity) (entity == null ? this : entity); - if (entity1 != this.camera) { -+ // Paper start - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity -+ if (this.camera == this) { -+ com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent playerStopSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity()); -+ if (!playerStopSpectatingEntityEvent.callEvent()) { -+ this.camera = entity1; // rollback camera entity again -+ return; -+ } -+ } else { -+ com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent playerStartSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity(), entity.getBukkitEntity()); -+ if (!playerStartSpectatingEntityEvent.callEvent()) { -+ this.camera = entity1; // rollback camera entity again -+ return; -+ } -+ } -+ // Paper end - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity - Level world = this.camera.level(); - - if (world instanceof ServerLevel) { diff --git a/patches/server/0264-Check-Drowned-for-Villager-Aggression-Config.patch b/patches/server/0263-Check-Drowned-for-Villager-Aggression-Config.patch similarity index 100% rename from patches/server/0264-Check-Drowned-for-Villager-Aggression-Config.patch rename to patches/server/0263-Check-Drowned-for-Villager-Aggression-Config.patch diff --git a/patches/server/0264-Add-option-to-prevent-players-from-moving-into-unloa.patch b/patches/server/0264-Add-option-to-prevent-players-from-moving-into-unloa.patch new file mode 100644 index 000000000000..403e73e6eef2 --- /dev/null +++ b/patches/server/0264-Add-option-to-prevent-players-from-moving-into-unloa.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Gabriele C +Date: Mon, 22 Oct 2018 17:34:10 +0200 +Subject: [PATCH] Add option to prevent players from moving into unloaded + chunks #1551 + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index acf350e18b9e4de77b43a01afe40a72ef040d92e..c827a2b9a64bba61ffdc8ee5ee35460b332a8556 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -474,9 +474,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + double d0 = entity.getX(); + double d1 = entity.getY(); + double d2 = entity.getZ(); +- double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); +- double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); +- double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); ++ double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); final double toX = d3; // Paper - OBFHELPER ++ double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); final double toY = d4; // Paper - OBFHELPER ++ double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); final double toZ = d5; // Paper - OBFHELPER + float f = Mth.wrapDegrees(packet.getYRot()); + float f1 = Mth.wrapDegrees(packet.getXRot()); + double d6 = d3 - this.vehicleFirstGoodX; +@@ -510,6 +510,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + speed *= 2f; // TODO: Get the speed of the vehicle instead of the player + ++ // Paper start - Prevent moving into unloaded chunks ++ if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && ( ++ !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position()))) || ++ !worldserver.areChunksLoadedForMove(entity.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(entity.position()))) ++ )) { ++ this.connection.send(new ClientboundMoveVehiclePacket(entity)); ++ return; ++ } ++ // Paper end - Prevent moving into unloaded chunks ++ + if (d10 - d9 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { + // CraftBukkit end + ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", new Object[]{entity.getName().getString(), this.player.getName().getString(), d6, d7, d8}); +@@ -1178,9 +1188,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + } else { + this.awaitingTeleportTime = this.tickCount; +- double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); +- double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); +- double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); ++ double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); final double toX = d0; // Paper - OBFHELPER ++ double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); final double toY = d1; // Paper - OBFHELPER ++ double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); final double toZ = d2; // Paper - OBFHELPER + float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); + float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); + +@@ -1236,6 +1246,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } else { + speed = this.player.getAbilities().walkingSpeed * 10f; + } ++ // Paper start - Prevent moving into unloaded chunks ++ if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position())))) { ++ this.internalTeleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot(), Collections.emptySet()); ++ return; ++ } ++ // Paper end - Prevent moving into unloaded chunks + + if (!this.player.isChangingDimension() && (!this.player.level().getGameRules().getBoolean(GameRules.RULE_DISABLE_ELYTRA_MOVEMENT_CHECK) || !this.player.isFallFlying())) { + float f2 = this.player.isFallFlying() ? 300.0F : 100.0F; diff --git a/patches/server/0265-Add-option-to-prevent-players-from-moving-into-unloa.patch b/patches/server/0265-Add-option-to-prevent-players-from-moving-into-unloa.patch deleted file mode 100644 index 6edb55ba5a6b..000000000000 --- a/patches/server/0265-Add-option-to-prevent-players-from-moving-into-unloa.patch +++ /dev/null @@ -1,67 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Gabriele C -Date: Mon, 22 Oct 2018 17:34:10 +0200 -Subject: [PATCH] Add option to prevent players from moving into unloaded - chunks #1551 - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 4787d49964ddb93558faa271ad27f5bbc17742f2..0c5ddfc6c12aaab10369ff96fcac1152965befcf 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -474,9 +474,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - double d0 = entity.getX(); - double d1 = entity.getY(); - double d2 = entity.getZ(); -- double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); -- double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); -- double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); -+ double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); final double toX = d3; // Paper - OBFHELPER -+ double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); final double toY = d4; // Paper - OBFHELPER -+ double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); final double toZ = d5; // Paper - OBFHELPER - float f = Mth.wrapDegrees(packet.getYRot()); - float f1 = Mth.wrapDegrees(packet.getXRot()); - double d6 = d3 - this.vehicleFirstGoodX; -@@ -510,6 +510,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - speed *= 2f; // TODO: Get the speed of the vehicle instead of the player - -+ // Paper start - Prevent moving into unloaded chunks -+ if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && ( -+ !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position()))) || -+ !worldserver.areChunksLoadedForMove(entity.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(entity.position()))) -+ )) { -+ this.connection.send(new ClientboundMoveVehiclePacket(entity)); -+ return; -+ } -+ // Paper end - Prevent moving into unloaded chunks -+ - if (d10 - d9 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { - // CraftBukkit end - ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", new Object[]{entity.getName().getString(), this.player.getName().getString(), d6, d7, d8}); -@@ -1178,9 +1188,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - } else { - this.awaitingTeleportTime = this.tickCount; -- double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); -- double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); -- double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); -+ double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); final double toX = d0; // Paper - OBFHELPER -+ double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); final double toY = d1; // Paper - OBFHELPER -+ double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); final double toZ = d2; // Paper - OBFHELPER - float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); - float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); - -@@ -1236,6 +1246,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } else { - speed = this.player.getAbilities().walkingSpeed * 10f; - } -+ // Paper start - Prevent moving into unloaded chunks -+ if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position())))) { -+ this.internalTeleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot(), Collections.emptySet()); -+ return; -+ } -+ // Paper end - Prevent moving into unloaded chunks - - if (!this.player.isChangingDimension() && (!this.player.level().getGameRules().getBoolean(GameRules.RULE_DISABLE_ELYTRA_MOVEMENT_CHECK) || !this.player.isFallFlying())) { - float f2 = this.player.isFallFlying() ? 300.0F : 100.0F; diff --git a/patches/server/0265-Reset-players-airTicks-on-respawn.patch b/patches/server/0265-Reset-players-airTicks-on-respawn.patch new file mode 100644 index 000000000000..d2e0da64ac53 --- /dev/null +++ b/patches/server/0265-Reset-players-airTicks-on-respawn.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: GreenMeanie +Date: Sat, 20 Oct 2018 22:34:02 -0400 +Subject: [PATCH] Reset players airTicks on respawn + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 3a991a6ef6a8c3150bdc557219424cf13f1dc0cc..0627d51d41d492e07b230e9c398158d656493848 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2516,6 +2516,7 @@ public class ServerPlayer extends Player { + + this.setHealth(this.getMaxHealth()); + this.stopUsingItem(); // CraftBukkit - SPIGOT-6682: Clear active item on reset ++ this.setAirSupply(this.getMaxAirSupply()); // Paper - Reset players airTicks on respawn + this.setRemainingFireTicks(0); + this.fallDistance = 0; + this.foodData = new FoodData(this); diff --git a/patches/server/0267-Don-t-sleep-after-profile-lookups-if-not-needed.patch b/patches/server/0266-Don-t-sleep-after-profile-lookups-if-not-needed.patch similarity index 100% rename from patches/server/0267-Don-t-sleep-after-profile-lookups-if-not-needed.patch rename to patches/server/0266-Don-t-sleep-after-profile-lookups-if-not-needed.patch diff --git a/patches/server/0266-Reset-players-airTicks-on-respawn.patch b/patches/server/0266-Reset-players-airTicks-on-respawn.patch deleted file mode 100644 index c4670e370f4f..000000000000 --- a/patches/server/0266-Reset-players-airTicks-on-respawn.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: GreenMeanie -Date: Sat, 20 Oct 2018 22:34:02 -0400 -Subject: [PATCH] Reset players airTicks on respawn - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 8432081cb287dbdbc7e89572e91fa634437b9809..c201325314b27c7658d6c16ddc7311afa79ccb4e 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2516,6 +2516,7 @@ public class ServerPlayer extends Player { - - this.setHealth(this.getMaxHealth()); - this.stopUsingItem(); // CraftBukkit - SPIGOT-6682: Clear active item on reset -+ this.setAirSupply(this.getMaxAirSupply()); // Paper - Reset players airTicks on respawn - this.setRemainingFireTicks(0); - this.fallDistance = 0; - this.foodData = new FoodData(this); diff --git a/patches/server/0267-Improve-Server-Thread-Pool-and-Thread-Priorities.patch b/patches/server/0267-Improve-Server-Thread-Pool-and-Thread-Priorities.patch new file mode 100644 index 000000000000..c37045a97f0f --- /dev/null +++ b/patches/server/0267-Improve-Server-Thread-Pool-and-Thread-Priorities.patch @@ -0,0 +1,105 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 23 Oct 2018 23:14:38 -0400 +Subject: [PATCH] Improve Server Thread Pool and Thread Priorities + +Use a simple executor since Fork join is a much more complex pool +type and we are not using its capabilities. + +Set thread priorities so main thread has above normal priority over +server threads + +Allow usage of a single thread executor by not using ForkJoin so single core CPU's +and reduce worldgen thread worker count for low core count CPUs. + +== AT == +public net.minecraft.Util onThreadException(Ljava/lang/Thread;Ljava/lang/Throwable;)V + +Co-authored-by: Spottedleaf + +diff --git a/src/main/java/io/papermc/paper/util/ServerWorkerThread.java b/src/main/java/io/papermc/paper/util/ServerWorkerThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b60f59cf5cc8eb84a6055b7861857dece7f2501b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/ServerWorkerThread.java +@@ -0,0 +1,14 @@ ++package io.papermc.paper.util; ++ ++import java.util.concurrent.atomic.AtomicInteger; ++import net.minecraft.Util; ++ ++public class ServerWorkerThread extends Thread { ++ private static final AtomicInteger threadId = new AtomicInteger(1); ++ public ServerWorkerThread(Runnable target, String poolName, int prioritityModifier) { ++ super(target, "Worker-" + poolName + "-" + threadId.getAndIncrement()); ++ setPriority(Thread.NORM_PRIORITY+prioritityModifier); // Deprioritize over main ++ this.setDaemon(true); ++ this.setUncaughtExceptionHandler(Util::onThreadException); ++ } ++} +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index aa52b271bd556a29f774fde375b713d0d187521b..765e4bc00a0526aab6e263dae7233f63f7f31498 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -86,7 +86,7 @@ public class Util { + private static final int DEFAULT_MAX_THREADS = 255; + private static final int DEFAULT_SAFE_FILE_OPERATION_RETRIES = 10; + private static final String MAX_THREADS_SYSTEM_PROPERTY = "max.bg.threads"; +- private static final ExecutorService BACKGROUND_EXECUTOR = makeExecutor("Main"); ++ private static final ExecutorService BACKGROUND_EXECUTOR = makeExecutor("Main", -1); // Paper - Perf: add priority + private static final ExecutorService IO_POOL = makeIoExecutor("IO-Worker-", false); + private static final ExecutorService DOWNLOAD_POOL = makeIoExecutor("Download-", true); + // Paper start - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread +@@ -152,15 +152,27 @@ public class Util { + return FILENAME_DATE_TIME_FORMATTER.format(ZonedDateTime.now()); + } + +- private static ExecutorService makeExecutor(String name) { +- int i = Mth.clamp(Runtime.getRuntime().availableProcessors() - 1, 1, getMaxThreads()); ++ private static ExecutorService makeExecutor(String s, int priorityModifier) { // Paper - Perf: add priority ++ // Paper start - Perf: use simpler thread pool that allows 1 thread and reduce worldgen thread worker count for low core count CPUs ++ int cpus = Runtime.getRuntime().availableProcessors() / 2; ++ int i; ++ if (cpus <= 4) { ++ i = cpus <= 2 ? 1 : 2; ++ } else if (cpus <= 8) { ++ // [5, 8] ++ i = Math.max(3, cpus - 2); ++ } else { ++ i = cpus * 2 / 3; ++ } ++ i = Math.min(8, i); ++ i = Integer.getInteger("Paper.WorkerThreadCount", i); + ExecutorService executorService; + if (i <= 0) { + executorService = MoreExecutors.newDirectExecutorService(); + } else { +- AtomicInteger atomicInteger = new AtomicInteger(1); +- executorService = new ForkJoinPool(i, (pool) -> { +- ForkJoinWorkerThread forkJoinWorkerThread = new ForkJoinWorkerThread(pool) { ++ executorService = new java.util.concurrent.ThreadPoolExecutor(i, i,0L, TimeUnit.MILLISECONDS, new java.util.concurrent.LinkedBlockingQueue<>(), target -> new io.papermc.paper.util.ServerWorkerThread(target, s, priorityModifier)); ++ } ++ /* + @Override + protected void onTermination(Throwable throwable) { + if (throwable != null) { +@@ -176,6 +188,7 @@ public class Util { + return forkJoinWorkerThread; + }, Util::onThreadException, true); + } ++ }*/ // Paper end - Perf: use simpler thread pool + + return executorService; + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 0b704d5d39263b66f7846a9c4a116ac4d96383cf..114f40c63c617f4df9e6c9bf2d50707e289f2f20 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -312,6 +312,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + MinecraftServer.LOGGER.error("Uncaught exception in server thread", throwable); + }); ++ thread.setPriority(Thread.NORM_PRIORITY+2); // Paper - Perf: Boost priority + if (Runtime.getRuntime().availableProcessors() > 4) { + thread.setPriority(8); + } diff --git a/patches/server/0268-Improve-Server-Thread-Pool-and-Thread-Priorities.patch b/patches/server/0268-Improve-Server-Thread-Pool-and-Thread-Priorities.patch deleted file mode 100644 index a69c32f79256..000000000000 --- a/patches/server/0268-Improve-Server-Thread-Pool-and-Thread-Priorities.patch +++ /dev/null @@ -1,105 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 23 Oct 2018 23:14:38 -0400 -Subject: [PATCH] Improve Server Thread Pool and Thread Priorities - -Use a simple executor since Fork join is a much more complex pool -type and we are not using its capabilities. - -Set thread priorities so main thread has above normal priority over -server threads - -Allow usage of a single thread executor by not using ForkJoin so single core CPU's -and reduce worldgen thread worker count for low core count CPUs. - -== AT == -public net.minecraft.Util onThreadException(Ljava/lang/Thread;Ljava/lang/Throwable;)V - -Co-authored-by: Spottedleaf - -diff --git a/src/main/java/io/papermc/paper/util/ServerWorkerThread.java b/src/main/java/io/papermc/paper/util/ServerWorkerThread.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b60f59cf5cc8eb84a6055b7861857dece7f2501b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/ServerWorkerThread.java -@@ -0,0 +1,14 @@ -+package io.papermc.paper.util; -+ -+import java.util.concurrent.atomic.AtomicInteger; -+import net.minecraft.Util; -+ -+public class ServerWorkerThread extends Thread { -+ private static final AtomicInteger threadId = new AtomicInteger(1); -+ public ServerWorkerThread(Runnable target, String poolName, int prioritityModifier) { -+ super(target, "Worker-" + poolName + "-" + threadId.getAndIncrement()); -+ setPriority(Thread.NORM_PRIORITY+prioritityModifier); // Deprioritize over main -+ this.setDaemon(true); -+ this.setUncaughtExceptionHandler(Util::onThreadException); -+ } -+} -diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java -index aa52b271bd556a29f774fde375b713d0d187521b..765e4bc00a0526aab6e263dae7233f63f7f31498 100644 ---- a/src/main/java/net/minecraft/Util.java -+++ b/src/main/java/net/minecraft/Util.java -@@ -86,7 +86,7 @@ public class Util { - private static final int DEFAULT_MAX_THREADS = 255; - private static final int DEFAULT_SAFE_FILE_OPERATION_RETRIES = 10; - private static final String MAX_THREADS_SYSTEM_PROPERTY = "max.bg.threads"; -- private static final ExecutorService BACKGROUND_EXECUTOR = makeExecutor("Main"); -+ private static final ExecutorService BACKGROUND_EXECUTOR = makeExecutor("Main", -1); // Paper - Perf: add priority - private static final ExecutorService IO_POOL = makeIoExecutor("IO-Worker-", false); - private static final ExecutorService DOWNLOAD_POOL = makeIoExecutor("Download-", true); - // Paper start - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread -@@ -152,15 +152,27 @@ public class Util { - return FILENAME_DATE_TIME_FORMATTER.format(ZonedDateTime.now()); - } - -- private static ExecutorService makeExecutor(String name) { -- int i = Mth.clamp(Runtime.getRuntime().availableProcessors() - 1, 1, getMaxThreads()); -+ private static ExecutorService makeExecutor(String s, int priorityModifier) { // Paper - Perf: add priority -+ // Paper start - Perf: use simpler thread pool that allows 1 thread and reduce worldgen thread worker count for low core count CPUs -+ int cpus = Runtime.getRuntime().availableProcessors() / 2; -+ int i; -+ if (cpus <= 4) { -+ i = cpus <= 2 ? 1 : 2; -+ } else if (cpus <= 8) { -+ // [5, 8] -+ i = Math.max(3, cpus - 2); -+ } else { -+ i = cpus * 2 / 3; -+ } -+ i = Math.min(8, i); -+ i = Integer.getInteger("Paper.WorkerThreadCount", i); - ExecutorService executorService; - if (i <= 0) { - executorService = MoreExecutors.newDirectExecutorService(); - } else { -- AtomicInteger atomicInteger = new AtomicInteger(1); -- executorService = new ForkJoinPool(i, (pool) -> { -- ForkJoinWorkerThread forkJoinWorkerThread = new ForkJoinWorkerThread(pool) { -+ executorService = new java.util.concurrent.ThreadPoolExecutor(i, i,0L, TimeUnit.MILLISECONDS, new java.util.concurrent.LinkedBlockingQueue<>(), target -> new io.papermc.paper.util.ServerWorkerThread(target, s, priorityModifier)); -+ } -+ /* - @Override - protected void onTermination(Throwable throwable) { - if (throwable != null) { -@@ -176,6 +188,7 @@ public class Util { - return forkJoinWorkerThread; - }, Util::onThreadException, true); - } -+ }*/ // Paper end - Perf: use simpler thread pool - - return executorService; - } -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index e1d8eae931f6eb7ddfca1d6dadcb28fec08ccede..be757de5b307bd8233c20f907f5d5d112761c362 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -312,6 +312,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { - MinecraftServer.LOGGER.error("Uncaught exception in server thread", throwable); - }); -+ thread.setPriority(Thread.NORM_PRIORITY+2); // Paper - Perf: Boost priority - if (Runtime.getRuntime().availableProcessors() > 4) { - thread.setPriority(8); - } diff --git a/patches/server/0268-Optimize-World-Time-Updates.patch b/patches/server/0268-Optimize-World-Time-Updates.patch new file mode 100644 index 000000000000..1587037e1a9a --- /dev/null +++ b/patches/server/0268-Optimize-World-Time-Updates.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 2 Nov 2018 23:11:51 -0400 +Subject: [PATCH] Optimize World Time Updates + +Splits time updates into incremental updates as well as does +the updates per world, so that we can re-use the same packet +object for every player unless they have per-player time enabled. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 114f40c63c617f4df9e6c9bf2d50707e289f2f20..2950d4b158f7ac2658b84315045a27b89ba27fc3 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1478,12 +1478,24 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Fri, 2 Nov 2018 23:11:51 -0400 -Subject: [PATCH] Optimize World Time Updates - -Splits time updates into incremental updates as well as does -the updates per world, so that we can re-use the same packet -object for every player unless they have per-player time enabled. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index be757de5b307bd8233c20f907f5d5d112761c362..fe0fde11e6d6bbe77a739c582a936c378b81a79c 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1478,12 +1478,24 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sun, 11 Nov 2018 21:01:09 +0000 +Subject: [PATCH] Don't allow digging into unloaded chunks + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index a8d33286832786031ac57e6ce27d5181e1a3d9b1..e8b12b27e5ec74afb940f575e5ce78e5905d55f4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -119,8 +119,8 @@ public class ServerPlayerGameMode { + BlockState iblockdata; + + if (this.hasDelayedDestroy) { +- iblockdata = this.level.getBlockState(this.delayedDestroyPos); +- if (iblockdata.isAir()) { ++ iblockdata = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks ++ if (iblockdata == null || iblockdata.isAir()) { // Paper - Don't allow digging into unloaded chunks + this.hasDelayedDestroy = false; + } else { + float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart); +@@ -131,7 +131,13 @@ public class ServerPlayerGameMode { + } + } + } else if (this.isDestroyingBlock) { +- iblockdata = this.level.getBlockState(this.destroyPos); ++ // Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead ++ iblockdata = this.level.getBlockStateIfLoaded(this.destroyPos); ++ if (iblockdata == null) { ++ this.isDestroyingBlock = false; ++ return; ++ } ++ // Paper end - Don't allow digging into unloaded chunks + if (iblockdata.isAir()) { + this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); + this.lastSentState = -1; +@@ -160,6 +166,7 @@ public class ServerPlayerGameMode { + + public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { + if (this.player.getEyePosition().distanceToSqr(Vec3.atCenterOf(pos)) > ServerGamePacketListenerImpl.MAX_INTERACTION_DISTANCE) { ++ if (true) return; // Paper - Don't allow digging into unloaded chunks; Don't notify if unreasonably far away + this.debugLogging(pos, false, sequence, "too far"); + } else if (pos.getY() >= worldHeight) { + this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); +@@ -299,10 +306,12 @@ public class ServerPlayerGameMode { + this.debugLogging(pos, true, sequence, "stopped destroying"); + } else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) { + this.isDestroyingBlock = false; +- if (!Objects.equals(this.destroyPos, pos)) { ++ if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) { // Paper + ServerPlayerGameMode.LOGGER.debug("Mismatch in destroy block pos: {} {}", this.destroyPos, pos); // CraftBukkit - SPIGOT-5457 sent by client when interact event cancelled +- this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); +- this.debugLogging(pos, true, sequence, "aborted mismatched destroying"); ++ BlockState type = this.level.getBlockStateIfLoaded(this.destroyPos); // Paper - don't load unloaded chunks for stale records here ++ if (type != null) this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); ++ if (type != null) this.debugLogging(pos, true, sequence, "aborted mismatched destroying"); ++ this.destroyPos = BlockPos.ZERO; // Paper + } + + this.level.destroyBlockProgress(this.player.getId(), pos, -1); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index c827a2b9a64bba61ffdc8ee5ee35460b332a8556..f64157a1fc3d629b9ef3c3d7b8fcf8f2cd960fe0 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1590,6 +1590,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + case START_DESTROY_BLOCK: + case ABORT_DESTROY_BLOCK: + case STOP_DESTROY_BLOCK: ++ // Paper start - Don't allow digging into unloaded chunks ++ if (this.player.level().getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) { ++ this.player.connection.ackBlockChangesUpTo(packet.getSequence()); ++ return; ++ } ++ // Paper end - Don't allow digging into unloaded chunks + this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level().getMaxBuildHeight(), packet.getSequence()); + this.player.connection.ackBlockChangesUpTo(packet.getSequence()); + return; diff --git a/patches/server/0272-Don-t-allow-digging-into-unloaded-chunks.patch b/patches/server/0272-Don-t-allow-digging-into-unloaded-chunks.patch deleted file mode 100644 index 48c08aa4bdfb..000000000000 --- a/patches/server/0272-Don-t-allow-digging-into-unloaded-chunks.patch +++ /dev/null @@ -1,77 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sun, 11 Nov 2018 21:01:09 +0000 -Subject: [PATCH] Don't allow digging into unloaded chunks - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index a8d33286832786031ac57e6ce27d5181e1a3d9b1..e8b12b27e5ec74afb940f575e5ce78e5905d55f4 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -119,8 +119,8 @@ public class ServerPlayerGameMode { - BlockState iblockdata; - - if (this.hasDelayedDestroy) { -- iblockdata = this.level.getBlockState(this.delayedDestroyPos); -- if (iblockdata.isAir()) { -+ iblockdata = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks -+ if (iblockdata == null || iblockdata.isAir()) { // Paper - Don't allow digging into unloaded chunks - this.hasDelayedDestroy = false; - } else { - float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart); -@@ -131,7 +131,13 @@ public class ServerPlayerGameMode { - } - } - } else if (this.isDestroyingBlock) { -- iblockdata = this.level.getBlockState(this.destroyPos); -+ // Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead -+ iblockdata = this.level.getBlockStateIfLoaded(this.destroyPos); -+ if (iblockdata == null) { -+ this.isDestroyingBlock = false; -+ return; -+ } -+ // Paper end - Don't allow digging into unloaded chunks - if (iblockdata.isAir()) { - this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); - this.lastSentState = -1; -@@ -160,6 +166,7 @@ public class ServerPlayerGameMode { - - public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { - if (this.player.getEyePosition().distanceToSqr(Vec3.atCenterOf(pos)) > ServerGamePacketListenerImpl.MAX_INTERACTION_DISTANCE) { -+ if (true) return; // Paper - Don't allow digging into unloaded chunks; Don't notify if unreasonably far away - this.debugLogging(pos, false, sequence, "too far"); - } else if (pos.getY() >= worldHeight) { - this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); -@@ -299,10 +306,12 @@ public class ServerPlayerGameMode { - this.debugLogging(pos, true, sequence, "stopped destroying"); - } else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) { - this.isDestroyingBlock = false; -- if (!Objects.equals(this.destroyPos, pos)) { -+ if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) { // Paper - ServerPlayerGameMode.LOGGER.debug("Mismatch in destroy block pos: {} {}", this.destroyPos, pos); // CraftBukkit - SPIGOT-5457 sent by client when interact event cancelled -- this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); -- this.debugLogging(pos, true, sequence, "aborted mismatched destroying"); -+ BlockState type = this.level.getBlockStateIfLoaded(this.destroyPos); // Paper - don't load unloaded chunks for stale records here -+ if (type != null) this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); -+ if (type != null) this.debugLogging(pos, true, sequence, "aborted mismatched destroying"); -+ this.destroyPos = BlockPos.ZERO; // Paper - } - - this.level.destroyBlockProgress(this.player.getId(), pos, -1); -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0c5ddfc6c12aaab10369ff96fcac1152965befcf..7a63be0cfda49395c19d563bd298bf48888fd84c 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1590,6 +1590,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - case START_DESTROY_BLOCK: - case ABORT_DESTROY_BLOCK: - case STOP_DESTROY_BLOCK: -+ // Paper start - Don't allow digging into unloaded chunks -+ if (this.player.level().getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) { -+ this.player.connection.ackBlockChangesUpTo(packet.getSequence()); -+ return; -+ } -+ // Paper end - Don't allow digging into unloaded chunks - this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level().getMaxBuildHeight(), packet.getSequence()); - this.player.connection.ackBlockChangesUpTo(packet.getSequence()); - return; diff --git a/patches/server/0272-Make-the-default-permission-message-configurable.patch b/patches/server/0272-Make-the-default-permission-message-configurable.patch new file mode 100644 index 000000000000..0f8d6463054d --- /dev/null +++ b/patches/server/0272-Make-the-default-permission-message-configurable.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 18 Nov 2018 19:49:56 +0000 +Subject: [PATCH] Make the default permission message configurable + + +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 6d06b772ffb9d47d6a717462a4b2b494544e80ae..69ffd6ea2ce7c6d4f211c6081fcea79afd7eac6c 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -73,7 +73,7 @@ public final class PaperCommand extends Command { + if (sender.hasPermission(BASE_PERM + permission) || sender.hasPermission("bukkit.command.paper")) { + return true; + } +- sender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED)); ++ sender.sendMessage(Bukkit.permissionMessage()); + return false; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index abdff07588cb06f8ec4f3f2172f7dce92fa8a979..43c69ec522a2c91d47b5cea05aaf86f979c5fcb2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2815,6 +2815,16 @@ public final class CraftServer implements Server { + return io.papermc.paper.configuration.GlobalConfiguration.get().commands.suggestPlayerNamesWhenNullTabCompletions; + } + ++ @Override ++ public String getPermissionMessage() { ++ return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().serialize(io.papermc.paper.configuration.GlobalConfiguration.get().messages.noPermission); ++ } ++ ++ @Override ++ public net.kyori.adventure.text.Component permissionMessage() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().messages.noPermission; ++ } ++ + @Override + public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) { + return createProfile(uuid, null); diff --git a/patches/server/0274-Handle-Large-Packets-disconnecting-client.patch b/patches/server/0273-Handle-Large-Packets-disconnecting-client.patch similarity index 100% rename from patches/server/0274-Handle-Large-Packets-disconnecting-client.patch rename to patches/server/0273-Handle-Large-Packets-disconnecting-client.patch diff --git a/patches/server/0273-Make-the-default-permission-message-configurable.patch b/patches/server/0273-Make-the-default-permission-message-configurable.patch deleted file mode 100644 index 1631a9ec3896..000000000000 --- a/patches/server/0273-Make-the-default-permission-message-configurable.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sun, 18 Nov 2018 19:49:56 +0000 -Subject: [PATCH] Make the default permission message configurable - - -diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index 6d06b772ffb9d47d6a717462a4b2b494544e80ae..69ffd6ea2ce7c6d4f211c6081fcea79afd7eac6c 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -73,7 +73,7 @@ public final class PaperCommand extends Command { - if (sender.hasPermission(BASE_PERM + permission) || sender.hasPermission("bukkit.command.paper")) { - return true; - } -- sender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED)); -+ sender.sendMessage(Bukkit.permissionMessage()); - return false; - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index b942d5efdde98c3367833fc1863ee196a1920d3b..41c9c8957e81c492891fda79f68262ba402dddee 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2803,6 +2803,16 @@ public final class CraftServer implements Server { - return io.papermc.paper.configuration.GlobalConfiguration.get().commands.suggestPlayerNamesWhenNullTabCompletions; - } - -+ @Override -+ public String getPermissionMessage() { -+ return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().serialize(io.papermc.paper.configuration.GlobalConfiguration.get().messages.noPermission); -+ } -+ -+ @Override -+ public net.kyori.adventure.text.Component permissionMessage() { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().messages.noPermission; -+ } -+ - @Override - public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) { - return createProfile(uuid, null); diff --git a/patches/server/0274-force-entity-dismount-during-teleportation.patch b/patches/server/0274-force-entity-dismount-during-teleportation.patch new file mode 100644 index 000000000000..ff8e4534d967 --- /dev/null +++ b/patches/server/0274-force-entity-dismount-during-teleportation.patch @@ -0,0 +1,113 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 15 Nov 2018 13:38:37 +0000 +Subject: [PATCH] force entity dismount during teleportation + +Entities must be dismounted before teleportation in order to avoid +multiple issues in the server with regards to teleportation, shamefully, +too many plugins rely on the events firing, which means that not firing +these events caues more issues than it solves; + +In order to counteract this, Entity dismount/exit vehicle events have +been modified to supress cancellation (and has a method to allow plugins +to check if this has been set), noting that cancellation will be silently +surpressed given that plugins are not expecting this event to not be cancellable. + +This is a far from ideal scenario, however: given the current state of this +event and other alternatives causing issues elsewhere, I believe that +this is going to be the best soultion all around. + +Improvements/suggestions welcome! + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c8c781aac68beb72e9efd671fcbb3fecc53d686d..4f37305c032255a33d641da66333ae7cf2fdf668 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2557,11 +2557,16 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + public void removeVehicle() { ++ // Paper start - Force entity dismount during teleportation ++ stopRiding(false); ++ } ++ public void stopRiding(boolean suppressCancellation) { ++ // Paper end - Force entity dismount during teleportation + if (this.vehicle != null) { + Entity entity = this.vehicle; + + this.vehicle = null; +- if (!entity.removePassenger(this)) this.vehicle = entity; // CraftBukkit ++ if (!entity.removePassenger(this, suppressCancellation)) this.vehicle = entity; // CraftBukkit // Paper - Force entity dismount during teleportation + } + + } +@@ -2592,7 +2597,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + } + +- protected boolean removePassenger(Entity entity) { // CraftBukkit ++ // Paper start - Force entity dismount during teleportation ++ protected boolean removePassenger(Entity entity) { return removePassenger(entity, false);} ++ protected boolean removePassenger(Entity entity, boolean suppressCancellation) { // CraftBukkit ++ // Paper end - Force entity dismount during teleportation + if (entity.getVehicle() == this) { + throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)"); + } else { +@@ -2602,7 +2610,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + if (this.getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) { + VehicleExitEvent event = new VehicleExitEvent( + (Vehicle) this.getBukkitEntity(), +- (LivingEntity) entity.getBukkitEntity() ++ (LivingEntity) entity.getBukkitEntity(), !suppressCancellation // Paper - Force entity dismount during teleportation + ); + // Suppress during worldgen + if (this.valid) { +@@ -2615,7 +2623,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + } + +- EntityDismountEvent event = new EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity()); ++ EntityDismountEvent event = new EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper - Force entity dismount during teleportation + // Suppress during worldgen + if (this.valid) { + Bukkit.getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 792473529c8afbabe2e501ab223eacaac08fcb96..7fb9880caa964df65402153b87a953200b930e24 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3484,9 +3484,15 @@ public abstract class LivingEntity extends Entity implements Attackable { + + @Override + public void stopRiding() { ++ // Paper start - Force entity dismount during teleportation ++ stopRiding(false); ++ } ++ @Override ++ public void stopRiding(boolean suppressCancellation) { ++ // Paper end - Force entity dismount during teleportation + Entity entity = this.getVehicle(); + +- super.stopRiding(); ++ super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation + if (entity != null && entity != this.getVehicle() && !this.level().isClientSide) { + this.dismountVehicle(entity); + } +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 c794fa6290be9904f3e97e74be9959f243c00f58..6ab6f520f2ccb60646660cb2990c5b91a0e91909 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1140,7 +1140,13 @@ public abstract class Player extends LivingEntity { + + @Override + public void removeVehicle() { +- super.removeVehicle(); ++ // Paper start - Force entity dismount during teleportation ++ stopRiding(false); ++ } ++ @Override ++ public void stopRiding(boolean suppressCancellation) { ++ super.stopRiding(suppressCancellation); ++ // Paper end - Force entity dismount during teleportation + this.boardingCooldown = 0; + } + diff --git a/patches/server/0276-Add-more-Zombie-API.patch b/patches/server/0275-Add-more-Zombie-API.patch similarity index 100% rename from patches/server/0276-Add-more-Zombie-API.patch rename to patches/server/0275-Add-more-Zombie-API.patch diff --git a/patches/server/0275-force-entity-dismount-during-teleportation.patch b/patches/server/0275-force-entity-dismount-during-teleportation.patch deleted file mode 100644 index e2199d7f7e86..000000000000 --- a/patches/server/0275-force-entity-dismount-during-teleportation.patch +++ /dev/null @@ -1,113 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Thu, 15 Nov 2018 13:38:37 +0000 -Subject: [PATCH] force entity dismount during teleportation - -Entities must be dismounted before teleportation in order to avoid -multiple issues in the server with regards to teleportation, shamefully, -too many plugins rely on the events firing, which means that not firing -these events caues more issues than it solves; - -In order to counteract this, Entity dismount/exit vehicle events have -been modified to supress cancellation (and has a method to allow plugins -to check if this has been set), noting that cancellation will be silently -surpressed given that plugins are not expecting this event to not be cancellable. - -This is a far from ideal scenario, however: given the current state of this -event and other alternatives causing issues elsewhere, I believe that -this is going to be the best soultion all around. - -Improvements/suggestions welcome! - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 227df91e28d11cad778b5190432c6eb5fb0312e5..5baf85f179c096a9e2be170d7def110d02952dae 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2553,11 +2553,16 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - public void removeVehicle() { -+ // Paper start - Force entity dismount during teleportation -+ stopRiding(false); -+ } -+ public void stopRiding(boolean suppressCancellation) { -+ // Paper end - Force entity dismount during teleportation - if (this.vehicle != null) { - Entity entity = this.vehicle; - - this.vehicle = null; -- if (!entity.removePassenger(this)) this.vehicle = entity; // CraftBukkit -+ if (!entity.removePassenger(this, suppressCancellation)) this.vehicle = entity; // CraftBukkit // Paper - Force entity dismount during teleportation - } - - } -@@ -2588,7 +2593,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - } - -- protected boolean removePassenger(Entity entity) { // CraftBukkit -+ // Paper start - Force entity dismount during teleportation -+ protected boolean removePassenger(Entity entity) { return removePassenger(entity, false);} -+ protected boolean removePassenger(Entity entity, boolean suppressCancellation) { // CraftBukkit -+ // Paper end - Force entity dismount during teleportation - if (entity.getVehicle() == this) { - throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)"); - } else { -@@ -2598,7 +2606,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - if (this.getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) { - VehicleExitEvent event = new VehicleExitEvent( - (Vehicle) this.getBukkitEntity(), -- (LivingEntity) entity.getBukkitEntity() -+ (LivingEntity) entity.getBukkitEntity(), !suppressCancellation // Paper - Force entity dismount during teleportation - ); - // Suppress during worldgen - if (this.valid) { -@@ -2611,7 +2619,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - } - -- EntityDismountEvent event = new EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity()); -+ EntityDismountEvent event = new EntityDismountEvent(entity.getBukkitEntity(), this.getBukkitEntity(), !suppressCancellation); // Paper - Force entity dismount during teleportation - // Suppress during worldgen - if (this.valid) { - Bukkit.getPluginManager().callEvent(event); -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 966db97da239e3712d382d18c2a0c6c111b60ab2..98a26f97749d883f4ca04da27199f499211f0f33 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3471,9 +3471,15 @@ public abstract class LivingEntity extends Entity implements Attackable { - - @Override - public void stopRiding() { -+ // Paper start - Force entity dismount during teleportation -+ stopRiding(false); -+ } -+ @Override -+ public void stopRiding(boolean suppressCancellation) { -+ // Paper end - Force entity dismount during teleportation - Entity entity = this.getVehicle(); - -- super.stopRiding(); -+ super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation - if (entity != null && entity != this.getVehicle() && !this.level().isClientSide) { - this.dismountVehicle(entity); - } -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 fb4cc32f2840098a13981ec4328e7eb6fe4f714b..80fba3abe6f971da951cf5b613ac54364d641a81 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1139,7 +1139,13 @@ public abstract class Player extends LivingEntity { - - @Override - public void removeVehicle() { -- super.removeVehicle(); -+ // Paper start - Force entity dismount during teleportation -+ stopRiding(false); -+ } -+ @Override -+ public void stopRiding(boolean suppressCancellation) { -+ super.stopRiding(suppressCancellation); -+ // Paper end - Force entity dismount during teleportation - this.boardingCooldown = 0; - } - diff --git a/patches/server/0276-Book-Size-Limits.patch b/patches/server/0276-Book-Size-Limits.patch new file mode 100644 index 000000000000..bc6794b68a29 --- /dev/null +++ b/patches/server/0276-Book-Size-Limits.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 16 Nov 2018 23:08:50 -0500 +Subject: [PATCH] Book Size Limits + +Puts some limits on the size of books. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f64157a1fc3d629b9ef3c3d7b8fcf8f2cd960fe0..aa4178c00391ca00f2ca148a914eacb161b1860e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1024,6 +1024,45 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + @Override + public void handleEditBook(ServerboundEditBookPacket packet) { ++ // Paper start - Book size limits ++ if (!this.cserver.isPrimaryThread()) { ++ List pageList = packet.getPages(); ++ long byteTotal = 0; ++ int maxBookPageSize = io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.pageMax; ++ double multiplier = Math.max(0.3D, Math.min(1D, io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.totalMultiplier)); ++ long byteAllowed = maxBookPageSize; ++ for (String testString : pageList) { ++ int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; ++ if (byteLength > 256 * 4) { ++ ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!"); ++ server.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ return; ++ } ++ byteTotal += byteLength; ++ int length = testString.length(); ++ int multibytes = 0; ++ if (byteLength != length) { ++ for (char c : testString.toCharArray()) { ++ if (c > 127) { ++ multibytes++; ++ } ++ } ++ } ++ byteAllowed += (maxBookPageSize * Math.min(1, Math.max(0.1D, (double) length / 255D))) * multiplier; ++ ++ if (multibytes > 1) { ++ // penalize MB ++ byteAllowed -= multibytes; ++ } ++ } ++ ++ if (byteTotal > byteAllowed) { ++ ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); ++ server.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ return; ++ } ++ } ++ // Paper end - Book size limits + // CraftBukkit start + if (this.lastBookTick + 20 > MinecraftServer.currentTick) { + this.disconnect("Book edited too quickly!"); diff --git a/patches/server/0278-Add-PlayerConnectionCloseEvent.patch b/patches/server/0277-Add-PlayerConnectionCloseEvent.patch similarity index 100% rename from patches/server/0278-Add-PlayerConnectionCloseEvent.patch rename to patches/server/0277-Add-PlayerConnectionCloseEvent.patch diff --git a/patches/server/0277-Book-Size-Limits.patch b/patches/server/0277-Book-Size-Limits.patch deleted file mode 100644 index d43a591560dd..000000000000 --- a/patches/server/0277-Book-Size-Limits.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 16 Nov 2018 23:08:50 -0500 -Subject: [PATCH] Book Size Limits - -Puts some limits on the size of books. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 7a63be0cfda49395c19d563bd298bf48888fd84c..2a49c02ad4baccc1fe3b0b69da0e584b7efec3ee 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1024,6 +1024,45 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - @Override - public void handleEditBook(ServerboundEditBookPacket packet) { -+ // Paper start - Book size limits -+ if (!this.cserver.isPrimaryThread()) { -+ List pageList = packet.getPages(); -+ long byteTotal = 0; -+ int maxBookPageSize = io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.pageMax; -+ double multiplier = Math.max(0.3D, Math.min(1D, io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.totalMultiplier)); -+ long byteAllowed = maxBookPageSize; -+ for (String testString : pageList) { -+ int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; -+ if (byteLength > 256 * 4) { -+ ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!"); -+ server.scheduleOnMain(() -> this.disconnect("Book too large!")); -+ return; -+ } -+ byteTotal += byteLength; -+ int length = testString.length(); -+ int multibytes = 0; -+ if (byteLength != length) { -+ for (char c : testString.toCharArray()) { -+ if (c > 127) { -+ multibytes++; -+ } -+ } -+ } -+ byteAllowed += (maxBookPageSize * Math.min(1, Math.max(0.1D, (double) length / 255D))) * multiplier; -+ -+ if (multibytes > 1) { -+ // penalize MB -+ byteAllowed -= multibytes; -+ } -+ } -+ -+ if (byteTotal > byteAllowed) { -+ ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); -+ server.scheduleOnMain(() -> this.disconnect("Book too large!")); -+ return; -+ } -+ } -+ // Paper end - Book size limits - // CraftBukkit start - if (this.lastBookTick + 20 > MinecraftServer.currentTick) { - this.disconnect("Book edited too quickly!"); diff --git a/patches/server/0278-Replace-OfflinePlayer-getLastPlayed.patch b/patches/server/0278-Replace-OfflinePlayer-getLastPlayed.patch new file mode 100644 index 000000000000..3b0afeb2bac5 --- /dev/null +++ b/patches/server/0278-Replace-OfflinePlayer-getLastPlayed.patch @@ -0,0 +1,164 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Wed, 2 Jan 2019 00:35:43 -0600 +Subject: [PATCH] Replace OfflinePlayer#getLastPlayed + +Currently OfflinePlayer#getLastPlayed could more accurately be described +as "OfflinePlayer#getLastTimeTheirDataWasSaved". + +The API doc says it should return the last time the server "witnessed" +the player, whilst also saying it should return the last time they +logged in. The current implementation does neither. + +Given this interesting contradiction in the API documentation and the +current defacto implementation, I've elected to deprecate (with no +intent to remove) and replace it with two new methods, clearly named and +documented as to their purpose. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 0627d51d41d492e07b230e9c398158d656493848..f500440ba1bcd36af3b3bc6470c108ec3f546cc4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -248,6 +248,7 @@ public class ServerPlayer extends Player { + private int containerCounter; + public boolean wonGame; + private int containerUpdateDelay; // Paper - Configurable container update tick rate ++ public long loginTime; // Paper - Replace OfflinePlayer#getLastPlayed + // Paper start - cancellable death event + public boolean queueHealthUpdatePacket; + public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 455f14d758551229d15d703990bb5225fff37700..e2136ae52074f922a87ac30f5e34cb8035c70475 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -179,6 +179,7 @@ public abstract class PlayerList { + + public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { + player.isRealPlayer = true; // Paper ++ player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed + GameProfile gameprofile = player.getGameProfile(); + GameProfileCache usercache = this.server.getProfileCache(); + String s; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +index 34925d6448e0ef1d5bb4b24359f732b67aaa4230..0c1b5f625a351905e082b2c2a63bfd737101527e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +@@ -262,6 +262,61 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + return this.getData() != null; + } + ++ // Paper start ++ @Override ++ public long getLastLogin() { ++ Player player = getPlayer(); ++ if (player != null) return player.getLastLogin(); ++ ++ CompoundTag data = getPaperData(); ++ ++ if (data != null) { ++ if (data.contains("LastLogin")) { ++ return data.getLong("LastLogin"); ++ } else { ++ // if the player file cannot provide accurate data, this is probably the closest we can approximate ++ File file = getDataFile(); ++ return file.lastModified(); ++ } ++ } else { ++ return 0; ++ } ++ } ++ ++ @Override ++ public long getLastSeen() { ++ Player player = getPlayer(); ++ if (player != null) return player.getLastSeen(); ++ ++ CompoundTag data = getPaperData(); ++ ++ if (data != null) { ++ if (data.contains("LastSeen")) { ++ return data.getLong("LastSeen"); ++ } else { ++ // if the player file cannot provide accurate data, this is probably the closest we can approximate ++ File file = getDataFile(); ++ return file.lastModified(); ++ } ++ } else { ++ return 0; ++ } ++ } ++ ++ private CompoundTag getPaperData() { ++ CompoundTag result = getData(); ++ ++ if (result != null) { ++ if (!result.contains("Paper")) { ++ result.put("Paper", new CompoundTag()); ++ } ++ result = result.getCompound("Paper"); ++ } ++ ++ return result; ++ } ++ // Paper end ++ + @Override + public Location getLastDeathLocation() { + if (this.getData().contains("LastDeathLocation", 10)) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 8cda792c1700bbc511e364e28fd7ee6893fe8db2..f309b16670d1814b7c4a1f8464e60af512b86da3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -197,6 +197,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + private BorderChangeListener clientWorldBorderListener = this.createWorldBorderListener(); + public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; // Paper - more resource pack API + private static final boolean DISABLE_CHANNEL_LIMIT = System.getProperty("paper.disableChannelLimit") != null; // Paper - add a flag to disable the channel limit ++ private long lastSaveTime; // Paper - getLastPlayed replacement API + + public CraftPlayer(CraftServer server, ServerPlayer entity) { + super(server, entity); +@@ -1956,6 +1957,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.firstPlayed = firstPlayed; + } + ++ // Paper start - getLastPlayed replacement API ++ @Override ++ public long getLastLogin() { ++ return this.getHandle().loginTime; ++ } ++ ++ @Override ++ public long getLastSeen() { ++ return this.isOnline() ? System.currentTimeMillis() : this.lastSaveTime; ++ } ++ // Paper end - getLastPlayed replacement API ++ + public void readExtraData(CompoundTag nbttagcompound) { + this.hasPlayedBefore = true; + if (nbttagcompound.contains("bukkit")) { +@@ -1978,6 +1991,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + public void setExtraData(CompoundTag nbttagcompound) { ++ this.lastSaveTime = System.currentTimeMillis(); // Paper ++ + if (!nbttagcompound.contains("bukkit")) { + nbttagcompound.put("bukkit", new CompoundTag()); + } +@@ -1992,6 +2007,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + data.putLong("firstPlayed", this.getFirstPlayed()); + data.putLong("lastPlayed", System.currentTimeMillis()); + data.putString("lastKnownName", handle.getScoreboardName()); ++ ++ // Paper start - persist for use in offline save data ++ if (!nbttagcompound.contains("Paper")) { ++ nbttagcompound.put("Paper", new CompoundTag()); ++ } ++ ++ CompoundTag paper = nbttagcompound.getCompound("Paper"); ++ paper.putLong("LastLogin", handle.loginTime); ++ paper.putLong("LastSeen", System.currentTimeMillis()); ++ // Paper end + } + + @Override diff --git a/patches/server/0279-Replace-OfflinePlayer-getLastPlayed.patch b/patches/server/0279-Replace-OfflinePlayer-getLastPlayed.patch deleted file mode 100644 index 2acfb65bcc76..000000000000 --- a/patches/server/0279-Replace-OfflinePlayer-getLastPlayed.patch +++ /dev/null @@ -1,164 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Wed, 2 Jan 2019 00:35:43 -0600 -Subject: [PATCH] Replace OfflinePlayer#getLastPlayed - -Currently OfflinePlayer#getLastPlayed could more accurately be described -as "OfflinePlayer#getLastTimeTheirDataWasSaved". - -The API doc says it should return the last time the server "witnessed" -the player, whilst also saying it should return the last time they -logged in. The current implementation does neither. - -Given this interesting contradiction in the API documentation and the -current defacto implementation, I've elected to deprecate (with no -intent to remove) and replace it with two new methods, clearly named and -documented as to their purpose. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index c201325314b27c7658d6c16ddc7311afa79ccb4e..f21d6a14fa7f7ea1581ba02bc3ca06e589281a58 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -248,6 +248,7 @@ public class ServerPlayer extends Player { - private int containerCounter; - public boolean wonGame; - private int containerUpdateDelay; // Paper - Configurable container update tick rate -+ public long loginTime; // Paper - Replace OfflinePlayer#getLastPlayed - // Paper start - cancellable death event - public boolean queueHealthUpdatePacket; - public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 455f14d758551229d15d703990bb5225fff37700..e2136ae52074f922a87ac30f5e34cb8035c70475 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -179,6 +179,7 @@ public abstract class PlayerList { - - public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { - player.isRealPlayer = true; // Paper -+ player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed - GameProfile gameprofile = player.getGameProfile(); - GameProfileCache usercache = this.server.getProfileCache(); - String s; -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -index 34925d6448e0ef1d5bb4b24359f732b67aaa4230..0c1b5f625a351905e082b2c2a63bfd737101527e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -@@ -262,6 +262,61 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - return this.getData() != null; - } - -+ // Paper start -+ @Override -+ public long getLastLogin() { -+ Player player = getPlayer(); -+ if (player != null) return player.getLastLogin(); -+ -+ CompoundTag data = getPaperData(); -+ -+ if (data != null) { -+ if (data.contains("LastLogin")) { -+ return data.getLong("LastLogin"); -+ } else { -+ // if the player file cannot provide accurate data, this is probably the closest we can approximate -+ File file = getDataFile(); -+ return file.lastModified(); -+ } -+ } else { -+ return 0; -+ } -+ } -+ -+ @Override -+ public long getLastSeen() { -+ Player player = getPlayer(); -+ if (player != null) return player.getLastSeen(); -+ -+ CompoundTag data = getPaperData(); -+ -+ if (data != null) { -+ if (data.contains("LastSeen")) { -+ return data.getLong("LastSeen"); -+ } else { -+ // if the player file cannot provide accurate data, this is probably the closest we can approximate -+ File file = getDataFile(); -+ return file.lastModified(); -+ } -+ } else { -+ return 0; -+ } -+ } -+ -+ private CompoundTag getPaperData() { -+ CompoundTag result = getData(); -+ -+ if (result != null) { -+ if (!result.contains("Paper")) { -+ result.put("Paper", new CompoundTag()); -+ } -+ result = result.getCompound("Paper"); -+ } -+ -+ return result; -+ } -+ // Paper end -+ - @Override - public Location getLastDeathLocation() { - if (this.getData().contains("LastDeathLocation", 10)) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index adcba9111415a20a4a55bb182be84c9f6d3755d7..95eb6060eb5661fa88bfdbd1c4d8bbcd78c2d983 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -191,6 +191,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - private BorderChangeListener clientWorldBorderListener = this.createWorldBorderListener(); - public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; // Paper - more resource pack API - private static final boolean DISABLE_CHANNEL_LIMIT = System.getProperty("paper.disableChannelLimit") != null; // Paper - add a flag to disable the channel limit -+ private long lastSaveTime; // Paper - getLastPlayed replacement API - - public CraftPlayer(CraftServer server, ServerPlayer entity) { - super(server, entity); -@@ -1926,6 +1927,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.firstPlayed = firstPlayed; - } - -+ // Paper start - getLastPlayed replacement API -+ @Override -+ public long getLastLogin() { -+ return this.getHandle().loginTime; -+ } -+ -+ @Override -+ public long getLastSeen() { -+ return this.isOnline() ? System.currentTimeMillis() : this.lastSaveTime; -+ } -+ // Paper end - getLastPlayed replacement API -+ - public void readExtraData(CompoundTag nbttagcompound) { - this.hasPlayedBefore = true; - if (nbttagcompound.contains("bukkit")) { -@@ -1948,6 +1961,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - public void setExtraData(CompoundTag nbttagcompound) { -+ this.lastSaveTime = System.currentTimeMillis(); // Paper -+ - if (!nbttagcompound.contains("bukkit")) { - nbttagcompound.put("bukkit", new CompoundTag()); - } -@@ -1962,6 +1977,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - data.putLong("firstPlayed", this.getFirstPlayed()); - data.putLong("lastPlayed", System.currentTimeMillis()); - data.putString("lastKnownName", handle.getScoreboardName()); -+ -+ // Paper start - persist for use in offline save data -+ if (!nbttagcompound.contains("Paper")) { -+ nbttagcompound.put("Paper", new CompoundTag()); -+ } -+ -+ CompoundTag paper = nbttagcompound.getCompound("Paper"); -+ paper.putLong("LastLogin", handle.loginTime); -+ paper.putLong("LastSeen", System.currentTimeMillis()); -+ // Paper end - } - - @Override diff --git a/patches/server/0279-Workaround-for-vehicle-tracking-issue-on-disconnect.patch b/patches/server/0279-Workaround-for-vehicle-tracking-issue-on-disconnect.patch new file mode 100644 index 000000000000..47dac04a5a38 --- /dev/null +++ b/patches/server/0279-Workaround-for-vehicle-tracking-issue-on-disconnect.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: connorhartley +Date: Mon, 7 Jan 2019 14:43:48 -0600 +Subject: [PATCH] Workaround for vehicle tracking issue on disconnect + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index f500440ba1bcd36af3b3bc6470c108ec3f546cc4..390e36f3557040f2ddc2c9158410a535cc178aa3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1705,6 +1705,13 @@ public class ServerPlayer extends Player { + public void disconnect() { + this.disconnected = true; + this.ejectPassengers(); ++ ++ // Paper start - Workaround vehicle not tracking the passenger disconnection dismount ++ if (this.isPassenger() && this.getVehicle() instanceof ServerPlayer) { ++ this.stopRiding(); ++ } ++ // Paper end - Workaround vehicle not tracking the passenger disconnection dismount ++ + if (this.isSleeping()) { + this.stopSleepInBed(true, false); + } diff --git a/patches/server/0280-Dont-block-Player-remove-if-the-handle-is-a-custom-p.patch b/patches/server/0280-Dont-block-Player-remove-if-the-handle-is-a-custom-p.patch new file mode 100644 index 000000000000..a0f7ab0755d9 --- /dev/null +++ b/patches/server/0280-Dont-block-Player-remove-if-the-handle-is-a-custom-p.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Mon, 4 Feb 2019 23:33:24 -0500 +Subject: [PATCH] Dont block Player#remove if the handle is a custom player + +Upstream throws UOE if you try to call remove on a Player. +We just add a check to ensure that the CraftPlayer's handle +is a ServerPlayer + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index f309b16670d1814b7c4a1f8464e60af512b86da3..c5ea6d424c8009c8afd791e58a75174291696d05 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -211,8 +211,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void remove() { ++ if (this.getHandle().getClass().equals(ServerPlayer.class)) { // special case for NMS plugins inheriting + // Will lead to an inconsistent player state if we remove the player as any other entity. + throw new UnsupportedOperationException(String.format("Cannot remove player %s, use Player#kickPlayer(String) instead.", this.getName())); ++ } else { ++ super.remove(); ++ } + } + + @Override diff --git a/patches/server/0280-Workaround-for-vehicle-tracking-issue-on-disconnect.patch b/patches/server/0280-Workaround-for-vehicle-tracking-issue-on-disconnect.patch deleted file mode 100644 index 634426aafccf..000000000000 --- a/patches/server/0280-Workaround-for-vehicle-tracking-issue-on-disconnect.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: connorhartley -Date: Mon, 7 Jan 2019 14:43:48 -0600 -Subject: [PATCH] Workaround for vehicle tracking issue on disconnect - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index f21d6a14fa7f7ea1581ba02bc3ca06e589281a58..6c68797a5a0b8453dde5aa8f1a875a032c1c56e3 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1705,6 +1705,13 @@ public class ServerPlayer extends Player { - public void disconnect() { - this.disconnected = true; - this.ejectPassengers(); -+ -+ // Paper start - Workaround vehicle not tracking the passenger disconnection dismount -+ if (this.isPassenger() && this.getVehicle() instanceof ServerPlayer) { -+ this.stopRiding(); -+ } -+ // Paper end - Workaround vehicle not tracking the passenger disconnection dismount -+ - if (this.isSleeping()) { - this.stopSleepInBed(true, false); - } diff --git a/patches/server/0282-BlockDestroyEvent.patch b/patches/server/0281-BlockDestroyEvent.patch similarity index 100% rename from patches/server/0282-BlockDestroyEvent.patch rename to patches/server/0281-BlockDestroyEvent.patch diff --git a/patches/server/0281-Dont-block-Player-remove-if-the-handle-is-a-custom-p.patch b/patches/server/0281-Dont-block-Player-remove-if-the-handle-is-a-custom-p.patch deleted file mode 100644 index e08566577d02..000000000000 --- a/patches/server/0281-Dont-block-Player-remove-if-the-handle-is-a-custom-p.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Mon, 4 Feb 2019 23:33:24 -0500 -Subject: [PATCH] Dont block Player#remove if the handle is a custom player - -Upstream throws UOE if you try to call remove on a Player. -We just add a check to ensure that the CraftPlayer's handle -is a ServerPlayer - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 24b08196d160feaa7b4409151fcd98cece19892e..1c8e4e3c53acc1c3043cb015cbe504f4cfb7a35c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -205,8 +205,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void remove() { -+ if (this.getHandle().getClass().equals(ServerPlayer.class)) { // special case for NMS plugins inheriting - // Will lead to an inconsistent player state if we remove the player as any other entity. - throw new UnsupportedOperationException(String.format("Cannot remove player %s, use Player#kickPlayer(String) instead.", this.getName())); -+ } else { -+ super.remove(); -+ } - } - - @Override diff --git a/patches/server/0282-Async-command-map-building.patch b/patches/server/0282-Async-command-map-building.patch new file mode 100644 index 000000000000..16d1e494fcd6 --- /dev/null +++ b/patches/server/0282-Async-command-map-building.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Callahan +Date: Wed, 8 Apr 2020 02:42:14 -0500 +Subject: [PATCH] Async command map building + +This adds a custom pool inorder to make sure that they are closed +without much though, as it doesn't matter if the client is not sent +commands if the server is restarting. Using the default async pool caused issues to arise +due to the shutdown logic generally being much later. + +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index 15a5059994371da4850adcf726034a715b80efba..af7cb518a32a4d550eae833fdd5bb17fd4058717 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -457,6 +457,24 @@ public class Commands { + if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) return; // Spigot + // CraftBukkit start + // Register Vanilla commands into builtRoot as before ++ // Paper start - Perf: Async command map building ++ COMMAND_SENDING_POOL.execute(() -> { ++ this.sendAsync(player); ++ }); ++ } ++ ++ public static final java.util.concurrent.ThreadPoolExecutor COMMAND_SENDING_POOL = new java.util.concurrent.ThreadPoolExecutor( ++ 0, 2, 60L, java.util.concurrent.TimeUnit.SECONDS, ++ new java.util.concurrent.LinkedBlockingQueue<>(), ++ new com.google.common.util.concurrent.ThreadFactoryBuilder() ++ .setNameFormat("Paper Async Command Builder Thread Pool - %1$d") ++ .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)) ++ .build(), ++ new java.util.concurrent.ThreadPoolExecutor.DiscardPolicy() ++ ); ++ ++ private void sendAsync(ServerPlayer player) { ++ // Paper end - Perf: Async command map building + Map, CommandNode> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues + RootCommandNode vanillaRoot = new RootCommandNode(); + +@@ -474,7 +492,14 @@ public class Commands { + for (CommandNode node : rootcommandnode.getChildren()) { + bukkit.add(node.getName()); + } ++ // Paper start - Perf: Async command map building ++ net.minecraft.server.MinecraftServer.getServer().execute(() -> { ++ runSync(player, bukkit, rootcommandnode); ++ }); ++ } + ++ private void runSync(ServerPlayer player, Collection bukkit, RootCommandNode rootcommandnode) { ++ // Paper end - Perf: Async command map building + PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit)); + event.getPlayer().getServer().getPluginManager().callEvent(event); + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 2950d4b158f7ac2658b84315045a27b89ba27fc3..0edd0181d7379e648155231689f1715076edcd1e 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -916,6 +916,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Wed, 8 Apr 2020 02:42:14 -0500 -Subject: [PATCH] Async command map building - -This adds a custom pool inorder to make sure that they are closed -without much though, as it doesn't matter if the client is not sent -commands if the server is restarting. Using the default async pool caused issues to arise -due to the shutdown logic generally being much later. - -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 15a5059994371da4850adcf726034a715b80efba..af7cb518a32a4d550eae833fdd5bb17fd4058717 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -457,6 +457,24 @@ public class Commands { - if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) return; // Spigot - // CraftBukkit start - // Register Vanilla commands into builtRoot as before -+ // Paper start - Perf: Async command map building -+ COMMAND_SENDING_POOL.execute(() -> { -+ this.sendAsync(player); -+ }); -+ } -+ -+ public static final java.util.concurrent.ThreadPoolExecutor COMMAND_SENDING_POOL = new java.util.concurrent.ThreadPoolExecutor( -+ 0, 2, 60L, java.util.concurrent.TimeUnit.SECONDS, -+ new java.util.concurrent.LinkedBlockingQueue<>(), -+ new com.google.common.util.concurrent.ThreadFactoryBuilder() -+ .setNameFormat("Paper Async Command Builder Thread Pool - %1$d") -+ .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)) -+ .build(), -+ new java.util.concurrent.ThreadPoolExecutor.DiscardPolicy() -+ ); -+ -+ private void sendAsync(ServerPlayer player) { -+ // Paper end - Perf: Async command map building - Map, CommandNode> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues - RootCommandNode vanillaRoot = new RootCommandNode(); - -@@ -474,7 +492,14 @@ public class Commands { - for (CommandNode node : rootcommandnode.getChildren()) { - bukkit.add(node.getName()); - } -+ // Paper start - Perf: Async command map building -+ net.minecraft.server.MinecraftServer.getServer().execute(() -> { -+ runSync(player, bukkit, rootcommandnode); -+ }); -+ } - -+ private void runSync(ServerPlayer player, Collection bukkit, RootCommandNode rootcommandnode) { -+ // Paper end - Perf: Async command map building - PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit)); - event.getPlayer().getServer().getPluginManager().callEvent(event); - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index fe0fde11e6d6bbe77a739c582a936c378b81a79c..e5ef4cba08031f0e35f127fa661b071d7872e7db 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -916,6 +916,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sun, 19 Apr 2020 18:15:29 -0400 +Subject: [PATCH] Brigadier Mojang API + +Adds AsyncPlayerSendCommandsEvent + - Allows modifying on a per command basis what command data they see. + +Adds CommandRegisteredEvent + - Allows manipulating the CommandNode to add more children/metadata for the client + +diff --git a/build.gradle.kts b/build.gradle.kts +index 30edfbf16e7bed29b3261b51d9e4f3124beef026..eaaf9a9779f57ee048245899750bf7a1599b716f 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -14,6 +14,7 @@ val alsoShade: Configuration by configurations.creating + + dependencies { + implementation(project(":paper-api")) ++ implementation(project(":paper-mojangapi")) + // Paper start + implementation("org.jline:jline-terminal-jansi:3.21.0") + implementation("net.minecrell:terminalconsoleappender:1.3.0") +diff --git a/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java b/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java +index 3370731ee064d2693b972a0765c13dd4fd69f66a..09d486a05179b9d878e1c33725b4e614c3544da9 100644 +--- a/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java ++++ b/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java +@@ -5,7 +5,7 @@ package com.mojang.brigadier.exceptions; + + import com.mojang.brigadier.Message; + +-public class CommandSyntaxException extends Exception { ++public class CommandSyntaxException extends Exception implements net.kyori.adventure.util.ComponentMessageThrowable { // Paper - Brigadier API + public static final int CONTEXT_AMOUNT = 10; + public static boolean ENABLE_COMMAND_STACK_TRACES = true; + public static BuiltInExceptionProvider BUILT_IN_EXCEPTIONS = new BuiltInExceptions(); +@@ -73,4 +73,11 @@ public class CommandSyntaxException extends Exception { + public int getCursor() { + return cursor; + } ++ ++ // Paper start - Brigadier API ++ @Override ++ public @org.jetbrains.annotations.Nullable net.kyori.adventure.text.Component componentMessage() { ++ return io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(this.message); ++ } ++ // Paper end - Brigadier API + } +diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +index da6250df1c5f3385b683cffde47754bca4606f5e..d8142624f9f3a5909e7cc5665f1629a1a67dd302 100644 +--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java ++++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +@@ -34,6 +34,7 @@ public abstract class CommandNode implements Comparable> { + private final RedirectModifier modifier; + private final boolean forks; + private Command command; ++ public LiteralCommandNode clientNode; // Paper - Brigadier API + // CraftBukkit start + public void removeCommand(String name) { + this.children.remove(name); +diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java +index edf49f2d9921b4517fb98929d842f7a62c5549df..0f98345f8adc6e9bf7fb2dc9ce4eba59a33ded61 100644 +--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java ++++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java +@@ -45,7 +45,7 @@ import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; + import com.mojang.brigadier.tree.CommandNode; // CraftBukkit + +-public class CommandSourceStack implements ExecutionCommandSource, SharedSuggestionProvider { ++public class CommandSourceStack implements ExecutionCommandSource, SharedSuggestionProvider, com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource { // Paper - Brigadier API + + public static final SimpleCommandExceptionType ERROR_NOT_PLAYER = new SimpleCommandExceptionType(Component.translatable("permissions.requires.player")); + public static final SimpleCommandExceptionType ERROR_NOT_ENTITY = new SimpleCommandExceptionType(Component.translatable("permissions.requires.entity")); +@@ -170,6 +170,26 @@ public class CommandSourceStack implements ExecutionCommandSource(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper - Brigadier API + net.minecraft.server.MinecraftServer.getServer().execute(() -> { + runSync(player, bukkit, rootcommandnode); + }); +@@ -500,6 +501,7 @@ public class Commands { + + private void runSync(ServerPlayer player, Collection bukkit, RootCommandNode rootcommandnode) { + // Paper end - Perf: Async command map building ++ new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper - Brigadier API + PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit)); + event.getPlayer().getServer().getPluginManager().callEvent(event); + +@@ -518,6 +520,11 @@ public class Commands { + + while (iterator.hasNext()) { + CommandNode commandnode2 = (CommandNode) iterator.next(); ++ // Paper start - Brigadier API ++ if (commandnode2.clientNode != null) { ++ commandnode2 = commandnode2.clientNode; ++ } ++ // Paper end - Brigadier API + if ( !org.spigotmc.SpigotConfig.sendNamespaced && commandnode2.getName().contains( ":" ) ) continue; // Spigot + + if (commandnode2.canUse(source)) { +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index aa4178c00391ca00f2ca148a914eacb161b1860e..7c5b550845035bf896358b7aad2101ab0b48b3fe 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -746,7 +746,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + builder.suggest(completion.suggestion(), PaperAdventure.asVanilla(completion.tooltip())); + } + } +- this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), builder.buildFuture().join())); ++ // Paper start - Brigadier API ++ com.mojang.brigadier.suggestion.Suggestions suggestions = builder.buildFuture().join(); ++ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); ++ suggestEvent.setCancelled(suggestions.isEmpty()); ++ if (suggestEvent.callEvent()) { ++ this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestEvent.getSuggestions())); ++ } ++ // Paper end - Brigadier API + } + } + +@@ -755,8 +762,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); + + this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { +- if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [] from showing for plugins with nothing more to offer +- this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions)); ++ // Paper start - Brigadier API ++ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); ++ suggestEvent.setCancelled(suggestions.isEmpty()); ++ if (suggestEvent.callEvent()) { ++ this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestEvent.getSuggestions())); ++ } ++ // Paper end - Brigadier API + }); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java +index 83d81b9371902b0302d13e53b31c15fac4e67966..d113e54a30db16e2ad955170df6030d15de530d6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java +@@ -20,7 +20,7 @@ import org.bukkit.command.CommandException; + import org.bukkit.command.CommandSender; + import org.bukkit.craftbukkit.CraftServer; + +-public class BukkitCommandWrapper implements com.mojang.brigadier.Command, Predicate, SuggestionProvider { ++public class BukkitCommandWrapper implements com.mojang.brigadier.Command, Predicate, SuggestionProvider, com.destroystokyo.paper.brigadier.BukkitBrigadierCommand { // Paper + + private final CraftServer server; + private final Command command; +@@ -31,10 +31,24 @@ public class BukkitCommandWrapper implements com.mojang.brigadier.Command register(CommandDispatcher dispatcher, String label) { +- return dispatcher.register( +- LiteralArgumentBuilder.literal(label).requires(this).executes(this) +- .then(RequiredArgumentBuilder.argument("args", StringArgumentType.greedyString()).suggests(this).executes(this)) +- ); ++ // Paper start - Expose Brigadier to Paper-MojangAPI ++ com.mojang.brigadier.tree.RootCommandNode root = dispatcher.getRoot(); ++ LiteralCommandNode literal = LiteralArgumentBuilder.literal(label).requires(this).executes(this).build(); ++ LiteralCommandNode defaultNode = literal; ++ com.mojang.brigadier.tree.ArgumentCommandNode defaultArgs = RequiredArgumentBuilder.argument("args", StringArgumentType.greedyString()).suggests(this).executes(this).build(); ++ literal.addChild(defaultArgs); ++ com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent event = new com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent<>(label, this, this.command, root, literal, defaultArgs); ++ if (!event.callEvent()) { ++ return null; ++ } ++ literal = event.getLiteral(); ++ if (event.isRawCommand()) { ++ defaultNode.clientNode = literal; ++ literal = defaultNode; ++ } ++ root.addChild(literal); ++ return literal; ++ // Paper end + } + + @Override diff --git a/patches/server/0284-Brigadier-Mojang-API.patch b/patches/server/0284-Brigadier-Mojang-API.patch deleted file mode 100644 index 3d9b5b23ccd3..000000000000 --- a/patches/server/0284-Brigadier-Mojang-API.patch +++ /dev/null @@ -1,210 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 19 Apr 2020 18:15:29 -0400 -Subject: [PATCH] Brigadier Mojang API - -Adds AsyncPlayerSendCommandsEvent - - Allows modifying on a per command basis what command data they see. - -Adds CommandRegisteredEvent - - Allows manipulating the CommandNode to add more children/metadata for the client - -diff --git a/build.gradle.kts b/build.gradle.kts -index 30edfbf16e7bed29b3261b51d9e4f3124beef026..eaaf9a9779f57ee048245899750bf7a1599b716f 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -14,6 +14,7 @@ val alsoShade: Configuration by configurations.creating - - dependencies { - implementation(project(":paper-api")) -+ implementation(project(":paper-mojangapi")) - // Paper start - implementation("org.jline:jline-terminal-jansi:3.21.0") - implementation("net.minecrell:terminalconsoleappender:1.3.0") -diff --git a/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java b/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java -index 3370731ee064d2693b972a0765c13dd4fd69f66a..09d486a05179b9d878e1c33725b4e614c3544da9 100644 ---- a/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java -+++ b/src/main/java/com/mojang/brigadier/exceptions/CommandSyntaxException.java -@@ -5,7 +5,7 @@ package com.mojang.brigadier.exceptions; - - import com.mojang.brigadier.Message; - --public class CommandSyntaxException extends Exception { -+public class CommandSyntaxException extends Exception implements net.kyori.adventure.util.ComponentMessageThrowable { // Paper - Brigadier API - public static final int CONTEXT_AMOUNT = 10; - public static boolean ENABLE_COMMAND_STACK_TRACES = true; - public static BuiltInExceptionProvider BUILT_IN_EXCEPTIONS = new BuiltInExceptions(); -@@ -73,4 +73,11 @@ public class CommandSyntaxException extends Exception { - public int getCursor() { - return cursor; - } -+ -+ // Paper start - Brigadier API -+ @Override -+ public @org.jetbrains.annotations.Nullable net.kyori.adventure.text.Component componentMessage() { -+ return io.papermc.paper.brigadier.PaperBrigadier.componentFromMessage(this.message); -+ } -+ // Paper end - Brigadier API - } -diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java -index da6250df1c5f3385b683cffde47754bca4606f5e..d8142624f9f3a5909e7cc5665f1629a1a67dd302 100644 ---- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java -+++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java -@@ -34,6 +34,7 @@ public abstract class CommandNode implements Comparable> { - private final RedirectModifier modifier; - private final boolean forks; - private Command command; -+ public LiteralCommandNode clientNode; // Paper - Brigadier API - // CraftBukkit start - public void removeCommand(String name) { - this.children.remove(name); -diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java -index edf49f2d9921b4517fb98929d842f7a62c5549df..0f98345f8adc6e9bf7fb2dc9ce4eba59a33ded61 100644 ---- a/src/main/java/net/minecraft/commands/CommandSourceStack.java -+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java -@@ -45,7 +45,7 @@ import net.minecraft.world.phys.Vec2; - import net.minecraft.world.phys.Vec3; - import com.mojang.brigadier.tree.CommandNode; // CraftBukkit - --public class CommandSourceStack implements ExecutionCommandSource, SharedSuggestionProvider { -+public class CommandSourceStack implements ExecutionCommandSource, SharedSuggestionProvider, com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource { // Paper - Brigadier API - - public static final SimpleCommandExceptionType ERROR_NOT_PLAYER = new SimpleCommandExceptionType(Component.translatable("permissions.requires.player")); - public static final SimpleCommandExceptionType ERROR_NOT_ENTITY = new SimpleCommandExceptionType(Component.translatable("permissions.requires.entity")); -@@ -170,6 +170,26 @@ public class CommandSourceStack implements ExecutionCommandSource(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper - Brigadier API - net.minecraft.server.MinecraftServer.getServer().execute(() -> { - runSync(player, bukkit, rootcommandnode); - }); -@@ -500,6 +501,7 @@ public class Commands { - - private void runSync(ServerPlayer player, Collection bukkit, RootCommandNode rootcommandnode) { - // Paper end - Perf: Async command map building -+ new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper - Brigadier API - PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit)); - event.getPlayer().getServer().getPluginManager().callEvent(event); - -@@ -518,6 +520,11 @@ public class Commands { - - while (iterator.hasNext()) { - CommandNode commandnode2 = (CommandNode) iterator.next(); -+ // Paper start - Brigadier API -+ if (commandnode2.clientNode != null) { -+ commandnode2 = commandnode2.clientNode; -+ } -+ // Paper end - Brigadier API - if ( !org.spigotmc.SpigotConfig.sendNamespaced && commandnode2.getName().contains( ":" ) ) continue; // Spigot - - if (commandnode2.canUse(source)) { -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 2a49c02ad4baccc1fe3b0b69da0e584b7efec3ee..eb62f5f1a0dc0f7cd49d54af7f4899e94afc95ee 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -746,7 +746,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - builder.suggest(completion.suggestion(), PaperAdventure.asVanilla(completion.tooltip())); - } - } -- this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), builder.buildFuture().join())); -+ // Paper start - Brigadier API -+ com.mojang.brigadier.suggestion.Suggestions suggestions = builder.buildFuture().join(); -+ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); -+ suggestEvent.setCancelled(suggestions.isEmpty()); -+ if (suggestEvent.callEvent()) { -+ this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestEvent.getSuggestions())); -+ } -+ // Paper end - Brigadier API - } - } - -@@ -755,8 +762,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); - - this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { -- if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [] from showing for plugins with nothing more to offer -- this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions)); -+ // Paper start - Brigadier API -+ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); -+ suggestEvent.setCancelled(suggestions.isEmpty()); -+ if (suggestEvent.callEvent()) { -+ this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestEvent.getSuggestions())); -+ } -+ // Paper end - Brigadier API - }); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java -index 83d81b9371902b0302d13e53b31c15fac4e67966..d113e54a30db16e2ad955170df6030d15de530d6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java -@@ -20,7 +20,7 @@ import org.bukkit.command.CommandException; - import org.bukkit.command.CommandSender; - import org.bukkit.craftbukkit.CraftServer; - --public class BukkitCommandWrapper implements com.mojang.brigadier.Command, Predicate, SuggestionProvider { -+public class BukkitCommandWrapper implements com.mojang.brigadier.Command, Predicate, SuggestionProvider, com.destroystokyo.paper.brigadier.BukkitBrigadierCommand { // Paper - - private final CraftServer server; - private final Command command; -@@ -31,10 +31,24 @@ public class BukkitCommandWrapper implements com.mojang.brigadier.Command register(CommandDispatcher dispatcher, String label) { -- return dispatcher.register( -- LiteralArgumentBuilder.literal(label).requires(this).executes(this) -- .then(RequiredArgumentBuilder.argument("args", StringArgumentType.greedyString()).suggests(this).executes(this)) -- ); -+ // Paper start - Expose Brigadier to Paper-MojangAPI -+ com.mojang.brigadier.tree.RootCommandNode root = dispatcher.getRoot(); -+ LiteralCommandNode literal = LiteralArgumentBuilder.literal(label).requires(this).executes(this).build(); -+ LiteralCommandNode defaultNode = literal; -+ com.mojang.brigadier.tree.ArgumentCommandNode defaultArgs = RequiredArgumentBuilder.argument("args", StringArgumentType.greedyString()).suggests(this).executes(this).build(); -+ literal.addChild(defaultArgs); -+ com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent event = new com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent<>(label, this, this.command, root, literal, defaultArgs); -+ if (!event.callEvent()) { -+ return null; -+ } -+ literal = event.getLiteral(); -+ if (event.isRawCommand()) { -+ defaultNode.clientNode = literal; -+ literal = defaultNode; -+ } -+ root.addChild(literal); -+ return literal; -+ // Paper end - } - - @Override diff --git a/patches/server/0285-Improve-exact-choice-recipe-ingredients.patch b/patches/server/0284-Improve-exact-choice-recipe-ingredients.patch similarity index 100% rename from patches/server/0285-Improve-exact-choice-recipe-ingredients.patch rename to patches/server/0284-Improve-exact-choice-recipe-ingredients.patch diff --git a/patches/server/0285-Limit-Client-Sign-length-more.patch b/patches/server/0285-Limit-Client-Sign-length-more.patch new file mode 100644 index 000000000000..b9e1d4e5034b --- /dev/null +++ b/patches/server/0285-Limit-Client-Sign-length-more.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 27 Feb 2019 22:18:40 -0500 +Subject: [PATCH] Limit Client Sign length more + +modified clients can send more data from the client +to the server and it would get stored on the sign as sent. + +Mojang has a limit of 384 which is much higher than reasonable. + +the client can barely render around 16 characters as-is, but formatting +codes can get it to be more than 16 actual length. + +Set a limit of 80 which should give an average of 16 characters 2 +sets of legacy formatting codes which should be plenty for all uses. + +This does not strip any existing data from the NBT as plugins +may use this for storing data out of the rendered area. + +it only impacts data sent from the client. + +Set -DPaper.maxSignLength=XX to change limit or -1 to disable + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 7c5b550845035bf896358b7aad2101ab0b48b3fe..89da9267c4c1a884e5483c423ccc68ed1d79259b 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -290,6 +290,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); + private final FutureChain chatMessageChain; + private boolean waitingForSwitchToConfig; ++ private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - Limit client sign length + + public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie clientData) { + super(server, connection, clientData, player); // CraftBukkit +@@ -3055,7 +3056,19 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + @Override + public void handleSignUpdate(ServerboundSignUpdatePacket packet) { +- List list = (List) Stream.of(packet.getLines()).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); ++ // Paper start - Limit client sign length ++ String[] lines = packet.getLines(); ++ for (int i = 0; i < lines.length; ++i) { ++ if (MAX_SIGN_LINE_LENGTH > 0 && lines[i].length() > MAX_SIGN_LINE_LENGTH) { ++ // This handles multibyte characters as 1 ++ int offset = lines[i].codePoints().limit(MAX_SIGN_LINE_LENGTH).map(Character::charCount).sum(); ++ if (offset < lines[i].length()) { ++ lines[i] = lines[i].substring(0, offset); // this will break any filtering, but filtering is NYI as of 1.17 ++ } ++ } ++ } ++ List list = (List) Stream.of(lines).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); ++ // Paper end - Limit client sign length + + this.filterTextPacket(list).thenAcceptAsync((list1) -> { + this.updateSignText(packet, list1); diff --git a/patches/server/0287-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch b/patches/server/0286-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch similarity index 100% rename from patches/server/0287-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch rename to patches/server/0286-Call-WhitelistToggleEvent-when-whitelist-is-toggled.patch diff --git a/patches/server/0286-Limit-Client-Sign-length-more.patch b/patches/server/0286-Limit-Client-Sign-length-more.patch deleted file mode 100644 index 8132f66a3bc1..000000000000 --- a/patches/server/0286-Limit-Client-Sign-length-more.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 27 Feb 2019 22:18:40 -0500 -Subject: [PATCH] Limit Client Sign length more - -modified clients can send more data from the client -to the server and it would get stored on the sign as sent. - -Mojang has a limit of 384 which is much higher than reasonable. - -the client can barely render around 16 characters as-is, but formatting -codes can get it to be more than 16 actual length. - -Set a limit of 80 which should give an average of 16 characters 2 -sets of legacy formatting codes which should be plenty for all uses. - -This does not strip any existing data from the NBT as plugins -may use this for storing data out of the rendered area. - -it only impacts data sent from the client. - -Set -DPaper.maxSignLength=XX to change limit or -1 to disable - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index eb62f5f1a0dc0f7cd49d54af7f4899e94afc95ee..79c7bce31d9c4e05afcb5cdbf06c4f6113d78ca3 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -290,6 +290,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); - private final FutureChain chatMessageChain; - private boolean waitingForSwitchToConfig; -+ private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - Limit client sign length - - public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie clientData) { - super(server, connection, clientData, player); // CraftBukkit -@@ -3055,7 +3056,19 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - @Override - public void handleSignUpdate(ServerboundSignUpdatePacket packet) { -- List list = (List) Stream.of(packet.getLines()).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); -+ // Paper start - Limit client sign length -+ String[] lines = packet.getLines(); -+ for (int i = 0; i < lines.length; ++i) { -+ if (MAX_SIGN_LINE_LENGTH > 0 && lines[i].length() > MAX_SIGN_LINE_LENGTH) { -+ // This handles multibyte characters as 1 -+ int offset = lines[i].codePoints().limit(MAX_SIGN_LINE_LENGTH).map(Character::charCount).sum(); -+ if (offset < lines[i].length()) { -+ lines[i] = lines[i].substring(0, offset); // this will break any filtering, but filtering is NYI as of 1.17 -+ } -+ } -+ } -+ List list = (List) Stream.of(lines).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); -+ // Paper end - Limit client sign length - - this.filterTextPacket(list).thenAcceptAsync((list1) -> { - this.updateSignText(packet, list1); diff --git a/patches/server/0287-Entity-getEntitySpawnReason.patch b/patches/server/0287-Entity-getEntitySpawnReason.patch new file mode 100644 index 000000000000..b07fe3361eb1 --- /dev/null +++ b/patches/server/0287-Entity-getEntitySpawnReason.patch @@ -0,0 +1,139 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 24 Mar 2019 00:24:52 -0400 +Subject: [PATCH] Entity#getEntitySpawnReason + +Allows you to return the SpawnReason for why an Entity Spawned + +Pre existing entities will return NATURAL if it was a non +persistenting Living Entity, SPAWNER for spawners, +or DEFAULT since data was not stored. + +diff --git a/src/main/java/net/minecraft/server/commands/SummonCommand.java b/src/main/java/net/minecraft/server/commands/SummonCommand.java +index 2eddeb8d5239bbfeefbf4d3bd363f1ad083299b6..e2b44b8ddb8afc6e1f7dddadb434c2268f284809 100644 +--- a/src/main/java/net/minecraft/server/commands/SummonCommand.java ++++ b/src/main/java/net/minecraft/server/commands/SummonCommand.java +@@ -57,6 +57,7 @@ public class SummonCommand { + ServerLevel worldserver = source.getLevel(); + Entity entity = EntityType.loadEntityRecursive(nbttagcompound1, worldserver, (entity1) -> { + entity1.moveTo(pos.x, pos.y, pos.z, entity1.getYRot(), entity1.getXRot()); ++ entity1.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND; // Paper - Entity#getEntitySpawnReason + return entity1; + }); + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index e1518465e4ffc473a75a57e4d9b3ad399a022589..4e7e3935a0040f48a1dc8f32e3da6d5a3ba154cb 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1203,6 +1203,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + return true; + } + // Paper end - extra debug info ++ if (entity.spawnReason == null) entity.spawnReason = spawnReason; // Paper - Entity#getEntitySpawnReason + if (entity.isRemoved()) { + // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit + return false; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index c137899597bf94b75b1ff80fae910db72f791bb8..6555d1e199f49c693270fb7068bc4c495038f77b 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -223,6 +223,11 @@ public abstract class PlayerList { + worldserver1 = worldserver; + } + ++ // Paper start - Entity#getEntitySpawnReason ++ if (nbttagcompound == null) { ++ player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login ++ } ++ // Paper end - Entity#getEntitySpawnReason + player.setServerLevel(worldserver1); + String s1 = connection.getLoggableAddress(this.server.logIPs()); + +@@ -354,7 +359,7 @@ public abstract class PlayerList { + // CraftBukkit start + ServerLevel finalWorldServer = worldserver1; + Entity entity = EntityType.loadEntityRecursive(nbttagcompound1.getCompound("Entity"), finalWorldServer, (entity1) -> { +- return !finalWorldServer.addWithUUID(entity1) ? null : entity1; ++ return !finalWorldServer.addWithUUID(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity1; // Paper - Entity#getEntitySpawnReason + // CraftBukkit end + }); + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 4f37305c032255a33d641da66333ae7cf2fdf668..07613c1a337e4aa82daa83157f44056d90fc5c24 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -237,6 +237,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + } + // Paper end - Share random for entities to make them more random ++ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason + + public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper + private CraftEntity bukkitEntity; +@@ -2311,6 +2312,26 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status ++ if (nbt.contains("Paper.SpawnReason")) { ++ String spawnReasonName = nbt.getString("Paper.SpawnReason"); ++ try { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.valueOf(spawnReasonName); ++ } catch (Exception ignored) { ++ LOGGER.error("Unknown SpawnReason " + spawnReasonName + " for " + this); ++ } ++ } ++ if (spawnReason == null) { ++ if (spawnedViaMobSpawner) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; ++ } else if (this instanceof Mob && (this instanceof net.minecraft.world.entity.animal.Animal || this instanceof net.minecraft.world.entity.animal.AbstractFish) && !((Mob) this).removeWhenFarAway(0.0)) { ++ if (!nbt.getBoolean("PersistenceRequired")) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL; ++ } ++ } ++ } ++ if (spawnReason == null) { ++ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; ++ } + // Paper end + + } catch (Throwable throwable) { +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index cae8c508972d771ad96228ace8a7e6cbc34d5489..3184f73ce799cc5202d2129be736e2fed9a3b8e3 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -182,6 +182,7 @@ public abstract class BaseSpawner { + } + + entity.spawnedViaMobSpawner = true; // Paper ++ entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; // Paper - Entity#getEntitySpawnReason + flag = true; // Paper + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java +index faad948f089575e4988d989790cc1dd13f8a79cd..e143f42e71ac774d49b75e6d85591aa1189ee210 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java +@@ -186,7 +186,7 @@ public class SculkShriekerBlockEntity extends BlockEntity implements GameEventLi + } + + private boolean trySummonWarden(ServerLevel world) { +- return this.warningLevel < 4 ? false : SpawnUtil.trySpawnMob(EntityType.WARDEN, MobSpawnType.TRIGGERED, world, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER).isPresent(); ++ return this.warningLevel < 4 ? false : SpawnUtil.trySpawnMob(EntityType.WARDEN, MobSpawnType.TRIGGERED, world, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL, null).isPresent(); // Paper - Entity#getEntitySpawnReason + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 388c9db9d130071122e9f80749fb2eef05455408..3da7fbec0fb55fb590fc9ff4bd0f984a5cac9fba 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1018,5 +1018,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public boolean fromMobSpawner() { + return getHandle().spawnedViaMobSpawner; + } ++ ++ @Override ++ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason getEntitySpawnReason() { ++ return getHandle().spawnReason; ++ } + // Paper end + } diff --git a/patches/server/0288-Entity-getEntitySpawnReason.patch b/patches/server/0288-Entity-getEntitySpawnReason.patch deleted file mode 100644 index a1b35465710e..000000000000 --- a/patches/server/0288-Entity-getEntitySpawnReason.patch +++ /dev/null @@ -1,139 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 24 Mar 2019 00:24:52 -0400 -Subject: [PATCH] Entity#getEntitySpawnReason - -Allows you to return the SpawnReason for why an Entity Spawned - -Pre existing entities will return NATURAL if it was a non -persistenting Living Entity, SPAWNER for spawners, -or DEFAULT since data was not stored. - -diff --git a/src/main/java/net/minecraft/server/commands/SummonCommand.java b/src/main/java/net/minecraft/server/commands/SummonCommand.java -index 2eddeb8d5239bbfeefbf4d3bd363f1ad083299b6..e2b44b8ddb8afc6e1f7dddadb434c2268f284809 100644 ---- a/src/main/java/net/minecraft/server/commands/SummonCommand.java -+++ b/src/main/java/net/minecraft/server/commands/SummonCommand.java -@@ -57,6 +57,7 @@ public class SummonCommand { - ServerLevel worldserver = source.getLevel(); - Entity entity = EntityType.loadEntityRecursive(nbttagcompound1, worldserver, (entity1) -> { - entity1.moveTo(pos.x, pos.y, pos.z, entity1.getYRot(), entity1.getXRot()); -+ entity1.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND; // Paper - Entity#getEntitySpawnReason - return entity1; - }); - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index e1518465e4ffc473a75a57e4d9b3ad399a022589..4e7e3935a0040f48a1dc8f32e3da6d5a3ba154cb 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1203,6 +1203,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - return true; - } - // Paper end - extra debug info -+ if (entity.spawnReason == null) entity.spawnReason = spawnReason; // Paper - Entity#getEntitySpawnReason - if (entity.isRemoved()) { - // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit - return false; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index c137899597bf94b75b1ff80fae910db72f791bb8..6555d1e199f49c693270fb7068bc4c495038f77b 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -223,6 +223,11 @@ public abstract class PlayerList { - worldserver1 = worldserver; - } - -+ // Paper start - Entity#getEntitySpawnReason -+ if (nbttagcompound == null) { -+ player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login -+ } -+ // Paper end - Entity#getEntitySpawnReason - player.setServerLevel(worldserver1); - String s1 = connection.getLoggableAddress(this.server.logIPs()); - -@@ -354,7 +359,7 @@ public abstract class PlayerList { - // CraftBukkit start - ServerLevel finalWorldServer = worldserver1; - Entity entity = EntityType.loadEntityRecursive(nbttagcompound1.getCompound("Entity"), finalWorldServer, (entity1) -> { -- return !finalWorldServer.addWithUUID(entity1) ? null : entity1; -+ return !finalWorldServer.addWithUUID(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity1; // Paper - Entity#getEntitySpawnReason - // CraftBukkit end - }); - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 5baf85f179c096a9e2be170d7def110d02952dae..c601bd02a75e10af7ad7040b3a4d974c585bbaaf 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -237,6 +237,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - } - // Paper end - Share random for entities to make them more random -+ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason - - public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper - private CraftEntity bukkitEntity; -@@ -2307,6 +2308,26 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status -+ if (nbt.contains("Paper.SpawnReason")) { -+ String spawnReasonName = nbt.getString("Paper.SpawnReason"); -+ try { -+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.valueOf(spawnReasonName); -+ } catch (Exception ignored) { -+ LOGGER.error("Unknown SpawnReason " + spawnReasonName + " for " + this); -+ } -+ } -+ if (spawnReason == null) { -+ if (spawnedViaMobSpawner) { -+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; -+ } else if (this instanceof Mob && (this instanceof net.minecraft.world.entity.animal.Animal || this instanceof net.minecraft.world.entity.animal.AbstractFish) && !((Mob) this).removeWhenFarAway(0.0)) { -+ if (!nbt.getBoolean("PersistenceRequired")) { -+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL; -+ } -+ } -+ } -+ if (spawnReason == null) { -+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; -+ } - // Paper end - - } catch (Throwable throwable) { -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index cae8c508972d771ad96228ace8a7e6cbc34d5489..3184f73ce799cc5202d2129be736e2fed9a3b8e3 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -182,6 +182,7 @@ public abstract class BaseSpawner { - } - - entity.spawnedViaMobSpawner = true; // Paper -+ entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; // Paper - Entity#getEntitySpawnReason - flag = true; // Paper - // CraftBukkit start - if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) { -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java -index faad948f089575e4988d989790cc1dd13f8a79cd..e143f42e71ac774d49b75e6d85591aa1189ee210 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java -@@ -186,7 +186,7 @@ public class SculkShriekerBlockEntity extends BlockEntity implements GameEventLi - } - - private boolean trySummonWarden(ServerLevel world) { -- return this.warningLevel < 4 ? false : SpawnUtil.trySpawnMob(EntityType.WARDEN, MobSpawnType.TRIGGERED, world, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER).isPresent(); -+ return this.warningLevel < 4 ? false : SpawnUtil.trySpawnMob(EntityType.WARDEN, MobSpawnType.TRIGGERED, world, this.getBlockPos(), 20, 5, 6, SpawnUtil.Strategy.ON_TOP_OF_COLLIDER, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL, null).isPresent(); // Paper - Entity#getEntitySpawnReason - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 388c9db9d130071122e9f80749fb2eef05455408..3da7fbec0fb55fb590fc9ff4bd0f984a5cac9fba 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1018,5 +1018,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - public boolean fromMobSpawner() { - return getHandle().spawnedViaMobSpawner; - } -+ -+ @Override -+ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason getEntitySpawnReason() { -+ return getHandle().spawnReason; -+ } - // Paper end - } diff --git a/patches/server/0289-Fire-event-on-GS4-query.patch b/patches/server/0288-Fire-event-on-GS4-query.patch similarity index 100% rename from patches/server/0289-Fire-event-on-GS4-query.patch rename to patches/server/0288-Fire-event-on-GS4-query.patch diff --git a/patches/server/0290-Add-PlayerPostRespawnEvent.patch b/patches/server/0289-Add-PlayerPostRespawnEvent.patch similarity index 100% rename from patches/server/0290-Add-PlayerPostRespawnEvent.patch rename to patches/server/0289-Add-PlayerPostRespawnEvent.patch diff --git a/patches/server/0291-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch b/patches/server/0290-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch similarity index 100% rename from patches/server/0291-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch rename to patches/server/0290-don-t-go-below-0-for-pickupDelay-breaks-picking-up-i.patch diff --git a/patches/server/0291-Server-Tick-Events.patch b/patches/server/0291-Server-Tick-Events.patch new file mode 100644 index 000000000000..51ca906ea22a --- /dev/null +++ b/patches/server/0291-Server-Tick-Events.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 27 Mar 2019 22:48:45 -0400 +Subject: [PATCH] Server Tick Events + +Fires event at start and end of a server tick + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 0edd0181d7379e648155231689f1715076edcd1e..ddbe9061b518711ed4bb2e30a90817293fd7fb7d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1357,6 +1357,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Wed, 27 Mar 2019 23:01:33 -0400 +Subject: [PATCH] PlayerDeathEvent#getItemsToKeep + +Exposes a mutable array on items a player should keep on death + +Example Usage: https://gist.github.com/aikar/5bb202de6057a051a950ce1f29feb0b4 + +== AT == +public net.minecraft.world.entity.player.Inventory compartments + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 390e36f3557040f2ddc2c9158410a535cc178aa3..71372fad81d34bb0a4035d3a69d656db3b45d9ce 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -813,6 +813,46 @@ public class ServerPlayer extends Player { + }); + } + ++ // Paper start - PlayerDeathEvent#getItemsToKeep ++ private static void processKeep(org.bukkit.event.entity.PlayerDeathEvent event, NonNullList inv) { ++ List itemsToKeep = event.getItemsToKeep(); ++ if (inv == null) { ++ // remainder of items left in toKeep - plugin added stuff on death that wasn't in the initial loot? ++ if (!itemsToKeep.isEmpty()) { ++ for (org.bukkit.inventory.ItemStack itemStack : itemsToKeep) { ++ event.getEntity().getInventory().addItem(itemStack); ++ } ++ } ++ ++ return; ++ } ++ ++ for (int i = 0; i < inv.size(); ++i) { ++ ItemStack item = inv.get(i); ++ if (EnchantmentHelper.hasVanishingCurse(item) || itemsToKeep.isEmpty() || item.isEmpty()) { ++ inv.set(i, ItemStack.EMPTY); ++ continue; ++ } ++ ++ final org.bukkit.inventory.ItemStack bukkitStack = item.getBukkitStack(); ++ boolean keep = false; ++ final Iterator iterator = itemsToKeep.iterator(); ++ while (iterator.hasNext()) { ++ final org.bukkit.inventory.ItemStack itemStack = iterator.next(); ++ if (bukkitStack.equals(itemStack)) { ++ iterator.remove(); ++ keep = true; ++ break; ++ } ++ } ++ ++ if (!keep) { ++ inv.set(i, ItemStack.EMPTY); ++ } ++ } ++ } ++ // Paper end - PlayerDeathEvent#getItemsToKeep ++ + @Override + public void die(DamageSource damageSource) { + // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check +@@ -897,7 +937,12 @@ public class ServerPlayer extends Player { + this.dropExperience(); + // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. + if (!event.getKeepInventory()) { +- this.getInventory().clearContent(); ++ // Paper start - PlayerDeathEvent#getItemsToKeep ++ for (NonNullList inv : this.getInventory().compartments) { ++ processKeep(event, inv); ++ } ++ processKeep(event, null); ++ // Paper end - PlayerDeathEvent#getItemsToKeep + } + + this.setCamera(this); // Remove spectated target diff --git a/patches/server/0292-Server-Tick-Events.patch b/patches/server/0292-Server-Tick-Events.patch deleted file mode 100644 index 97aeee8265ac..000000000000 --- a/patches/server/0292-Server-Tick-Events.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 27 Mar 2019 22:48:45 -0400 -Subject: [PATCH] Server Tick Events - -Fires event at start and end of a server tick - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index e5ef4cba08031f0e35f127fa661b071d7872e7db..1f23b277d5c1be48a3cfe7b6c9248a0b2bd95e4b 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1357,6 +1357,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Wed, 27 Mar 2019 23:01:33 -0400 -Subject: [PATCH] PlayerDeathEvent#getItemsToKeep - -Exposes a mutable array on items a player should keep on death - -Example Usage: https://gist.github.com/aikar/5bb202de6057a051a950ce1f29feb0b4 - -== AT == -public net.minecraft.world.entity.player.Inventory compartments - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 6c68797a5a0b8453dde5aa8f1a875a032c1c56e3..a003df2d8d6be620d4ccfe34dd6a8a1fc9225daf 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -813,6 +813,46 @@ public class ServerPlayer extends Player { - }); - } - -+ // Paper start - PlayerDeathEvent#getItemsToKeep -+ private static void processKeep(org.bukkit.event.entity.PlayerDeathEvent event, NonNullList inv) { -+ List itemsToKeep = event.getItemsToKeep(); -+ if (inv == null) { -+ // remainder of items left in toKeep - plugin added stuff on death that wasn't in the initial loot? -+ if (!itemsToKeep.isEmpty()) { -+ for (org.bukkit.inventory.ItemStack itemStack : itemsToKeep) { -+ event.getEntity().getInventory().addItem(itemStack); -+ } -+ } -+ -+ return; -+ } -+ -+ for (int i = 0; i < inv.size(); ++i) { -+ ItemStack item = inv.get(i); -+ if (EnchantmentHelper.hasVanishingCurse(item) || itemsToKeep.isEmpty() || item.isEmpty()) { -+ inv.set(i, ItemStack.EMPTY); -+ continue; -+ } -+ -+ final org.bukkit.inventory.ItemStack bukkitStack = item.getBukkitStack(); -+ boolean keep = false; -+ final Iterator iterator = itemsToKeep.iterator(); -+ while (iterator.hasNext()) { -+ final org.bukkit.inventory.ItemStack itemStack = iterator.next(); -+ if (bukkitStack.equals(itemStack)) { -+ iterator.remove(); -+ keep = true; -+ break; -+ } -+ } -+ -+ if (!keep) { -+ inv.set(i, ItemStack.EMPTY); -+ } -+ } -+ } -+ // Paper end - PlayerDeathEvent#getItemsToKeep -+ - @Override - public void die(DamageSource damageSource) { - // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check -@@ -897,7 +937,12 @@ public class ServerPlayer extends Player { - this.dropExperience(); - // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. - if (!event.getKeepInventory()) { -- this.getInventory().clearContent(); -+ // Paper start - PlayerDeathEvent#getItemsToKeep -+ for (NonNullList inv : this.getInventory().compartments) { -+ processKeep(event, inv); -+ } -+ processKeep(event, null); -+ // Paper end - PlayerDeathEvent#getItemsToKeep - } - - this.setCamera(this); // Remove spectated target diff --git a/patches/server/0294-Add-Heightmap-API.patch b/patches/server/0294-Add-Heightmap-API.patch new file mode 100644 index 000000000000..80731d3922cf --- /dev/null +++ b/patches/server/0294-Add-Heightmap-API.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 1 Jan 2019 02:22:01 -0800 +Subject: [PATCH] Add Heightmap API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 0cded703702e7271eec909e470fbfded17eb791f..83530c6823710e387f453898fc0d81392cd2fcd8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -228,6 +228,29 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return CraftBlock.at(this.world, new BlockPos(x, y, z)); + } + ++ // Paper start - Implement heightmap api ++ @Override ++ public int getHighestBlockYAt(final int x, final int z, final com.destroystokyo.paper.HeightmapType heightmap) throws UnsupportedOperationException { ++ this.getChunkAt(x >> 4, z >> 4); // heightmap will ret 0 on unloaded areas ++ ++ switch (heightmap) { ++ case LIGHT_BLOCKING: ++ throw new UnsupportedOperationException(); // TODO ++ //return this.world.getHighestBlockY(HeightMap.Type.LIGHT_BLOCKING, x, z); ++ case ANY: ++ return this.world.getHeight(net.minecraft.world.level.levelgen.Heightmap.Types.WORLD_SURFACE, x, z); ++ case SOLID: ++ return this.world.getHeight(net.minecraft.world.level.levelgen.Heightmap.Types.OCEAN_FLOOR, x, z); ++ case SOLID_OR_LIQUID: ++ return this.world.getHeight(net.minecraft.world.level.levelgen.Heightmap.Types.MOTION_BLOCKING, x, z); ++ case SOLID_OR_LIQUID_NO_LEAVES: ++ return this.world.getHeight(net.minecraft.world.level.levelgen.Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z); ++ default: ++ throw new UnsupportedOperationException(); ++ } ++ } ++ // Paper end ++ + @Override + public Location getSpawnLocation() { + BlockPos spawn = this.world.getSharedSpawnPos(); diff --git a/patches/server/0295-Add-Heightmap-API.patch b/patches/server/0295-Add-Heightmap-API.patch deleted file mode 100644 index e011e0d19e9a..000000000000 --- a/patches/server/0295-Add-Heightmap-API.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 1 Jan 2019 02:22:01 -0800 -Subject: [PATCH] Add Heightmap API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 400dbd72c576590ccdbab9c52a3960acb457102f..44b65087ec6f0cf9e29c4f4de94ddf7b6a7a7836 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -222,6 +222,29 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return CraftBlock.at(this.world, new BlockPos(x, y, z)); - } - -+ // Paper start - Implement heightmap api -+ @Override -+ public int getHighestBlockYAt(final int x, final int z, final com.destroystokyo.paper.HeightmapType heightmap) throws UnsupportedOperationException { -+ this.getChunkAt(x >> 4, z >> 4); // heightmap will ret 0 on unloaded areas -+ -+ switch (heightmap) { -+ case LIGHT_BLOCKING: -+ throw new UnsupportedOperationException(); // TODO -+ //return this.world.getHighestBlockY(HeightMap.Type.LIGHT_BLOCKING, x, z); -+ case ANY: -+ return this.world.getHeight(net.minecraft.world.level.levelgen.Heightmap.Types.WORLD_SURFACE, x, z); -+ case SOLID: -+ return this.world.getHeight(net.minecraft.world.level.levelgen.Heightmap.Types.OCEAN_FLOOR, x, z); -+ case SOLID_OR_LIQUID: -+ return this.world.getHeight(net.minecraft.world.level.levelgen.Heightmap.Types.MOTION_BLOCKING, x, z); -+ case SOLID_OR_LIQUID_NO_LEAVES: -+ return this.world.getHeight(net.minecraft.world.level.levelgen.Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z); -+ default: -+ throw new UnsupportedOperationException(); -+ } -+ } -+ // Paper end -+ - @Override - public Location getSpawnLocation() { - BlockPos spawn = this.world.getSharedSpawnPos(); diff --git a/patches/server/0296-Mob-Spawner-API-Enhancements.patch b/patches/server/0295-Mob-Spawner-API-Enhancements.patch similarity index 100% rename from patches/server/0296-Mob-Spawner-API-Enhancements.patch rename to patches/server/0295-Mob-Spawner-API-Enhancements.patch diff --git a/patches/server/0297-Fix-CB-call-to-changed-postToMainThread-method.patch b/patches/server/0296-Fix-CB-call-to-changed-postToMainThread-method.patch similarity index 100% rename from patches/server/0297-Fix-CB-call-to-changed-postToMainThread-method.patch rename to patches/server/0296-Fix-CB-call-to-changed-postToMainThread-method.patch diff --git a/patches/server/0298-Fix-sounds-when-item-frames-are-modified-MC-123450.patch b/patches/server/0297-Fix-sounds-when-item-frames-are-modified-MC-123450.patch similarity index 100% rename from patches/server/0298-Fix-sounds-when-item-frames-are-modified-MC-123450.patch rename to patches/server/0297-Fix-sounds-when-item-frames-are-modified-MC-123450.patch diff --git a/patches/server/0299-Implement-CraftBlockSoundGroup.patch b/patches/server/0298-Implement-CraftBlockSoundGroup.patch similarity index 100% rename from patches/server/0299-Implement-CraftBlockSoundGroup.patch rename to patches/server/0298-Implement-CraftBlockSoundGroup.patch diff --git a/patches/server/0299-Configurable-Keep-Spawn-Loaded-range-per-world.patch b/patches/server/0299-Configurable-Keep-Spawn-Loaded-range-per-world.patch new file mode 100644 index 000000000000..1bd19d7c9ff6 --- /dev/null +++ b/patches/server/0299-Configurable-Keep-Spawn-Loaded-range-per-world.patch @@ -0,0 +1,220 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 13 Sep 2014 23:14:43 -0400 +Subject: [PATCH] Configurable Keep Spawn Loaded range per world + +This lets you disable it for some worlds and lower it for others. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index ddbe9061b518711ed4bb2e30a90817293fd7fb7d..7263a7f8b01c6afdaf67cfd6cc67f6a45c0aee08 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -768,30 +768,33 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Sat, 13 Sep 2014 23:14:43 -0400 -Subject: [PATCH] Configurable Keep Spawn Loaded range per world - -This lets you disable it for some worlds and lower it for others. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 1f23b277d5c1be48a3cfe7b6c9248a0b2bd95e4b..cecf238af5fd695baa623d7d09323a60b41512a5 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -768,30 +768,33 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sat, 20 Apr 2019 19:47:34 -0500 +Subject: [PATCH] Expose the internal current tick + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 43c69ec522a2c91d47b5cea05aaf86f979c5fcb2..fd038c105d12c76c9e2645ad146e77b4a9cc1079 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2861,5 +2861,10 @@ public final class CraftServer implements Server { + profile.getProperties().putAll(((CraftPlayer) player).getHandle().getGameProfile().getProperties()); + return new com.destroystokyo.paper.profile.CraftPlayerProfile(profile); + } ++ ++ @Override ++ public int getCurrentTick() { ++ return net.minecraft.server.MinecraftServer.currentTick; ++ } + // Paper end + } diff --git a/patches/server/0301-Expose-the-internal-current-tick.patch b/patches/server/0301-Expose-the-internal-current-tick.patch deleted file mode 100644 index 2ddd0c9da4f0..000000000000 --- a/patches/server/0301-Expose-the-internal-current-tick.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 20 Apr 2019 19:47:34 -0500 -Subject: [PATCH] Expose the internal current tick - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 41c9c8957e81c492891fda79f68262ba402dddee..7e765fcdf4cb302c8eae2b4b6a86adfc038505ca 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2849,5 +2849,10 @@ public final class CraftServer implements Server { - profile.getProperties().putAll(((CraftPlayer) player).getHandle().getGameProfile().getProperties()); - return new com.destroystokyo.paper.profile.CraftPlayerProfile(profile); - } -+ -+ @Override -+ public int getCurrentTick() { -+ return net.minecraft.server.MinecraftServer.currentTick; -+ } - // Paper end - } diff --git a/patches/server/0302-Show-blockstate-location-if-we-failed-to-read-it.patch b/patches/server/0301-Show-blockstate-location-if-we-failed-to-read-it.patch similarity index 100% rename from patches/server/0302-Show-blockstate-location-if-we-failed-to-read-it.patch rename to patches/server/0301-Show-blockstate-location-if-we-failed-to-read-it.patch diff --git a/patches/server/0302-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch b/patches/server/0302-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch new file mode 100644 index 000000000000..a310ad9f1aea --- /dev/null +++ b/patches/server/0302-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 24 Mar 2019 01:01:32 -0400 +Subject: [PATCH] Only count Natural Spawned mobs towards natural spawn mob + limit + +This resolves the super common complaint about mobs not spawning. + +This was ultimately a flaw in the vanilla count algorithim that allows +spawners and other misc mobs to count against the mob limit, which are +not bounded, and can prevent the entire world from spawning new. + +I believe Bukkits changes around persistence may of actually made it +worse than vanilla. + +This should fully solve all of the issues around it so that only natural +influences natural spawns. + +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index d5ada3301429e7fec0d157d7a33d4937e0f82fbb..5247782edc426107fb4b3ade5d92f148c0b6e681 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -88,6 +88,13 @@ public final class NaturalSpawner { + MobCategory enumcreaturetype = entity.getType().getCategory(); + + if (enumcreaturetype != MobCategory.MISC) { ++ // Paper start - Only count natural spawns ++ if (!entity.level().paperConfig().entities.spawning.countAllMobsForSpawning && ++ !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL || ++ entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) { ++ continue; ++ } ++ // Paper end - Only count natural spawns + BlockPos blockposition = entity.blockPosition(); + + chunkSource.query(ChunkPos.asLong(blockposition), (chunk) -> { diff --git a/patches/server/0304-Configurable-projectile-relative-velocity.patch b/patches/server/0303-Configurable-projectile-relative-velocity.patch similarity index 100% rename from patches/server/0304-Configurable-projectile-relative-velocity.patch rename to patches/server/0303-Configurable-projectile-relative-velocity.patch diff --git a/patches/server/0303-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch b/patches/server/0303-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch deleted file mode 100644 index 4ba6c07c1831..000000000000 --- a/patches/server/0303-Only-count-Natural-Spawned-mobs-towards-natural-spaw.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 24 Mar 2019 01:01:32 -0400 -Subject: [PATCH] Only count Natural Spawned mobs towards natural spawn mob - limit - -This resolves the super common complaint about mobs not spawning. - -This was ultimately a flaw in the vanilla count algorithim that allows -spawners and other misc mobs to count against the mob limit, which are -not bounded, and can prevent the entire world from spawning new. - -I believe Bukkits changes around persistence may of actually made it -worse than vanilla. - -This should fully solve all of the issues around it so that only natural -influences natural spawns. - -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index aec4897a647206da20666f4d54cdc5d1b516bfc2..4ad3a4403f497f4b437209a9e63445f0d29b09f1 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -88,6 +88,13 @@ public final class NaturalSpawner { - MobCategory enumcreaturetype = entity.getType().getCategory(); - - if (enumcreaturetype != MobCategory.MISC) { -+ // Paper start - Only count natural spawns -+ if (!entity.level().paperConfig().entities.spawning.countAllMobsForSpawning && -+ !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL || -+ entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) { -+ continue; -+ } -+ // Paper end - Only count natural spawns - BlockPos blockposition = entity.blockPosition(); - - chunkSource.query(ChunkPos.asLong(blockposition), (chunk) -> { diff --git a/patches/server/0305-offset-item-frame-ticking.patch b/patches/server/0304-offset-item-frame-ticking.patch similarity index 100% rename from patches/server/0305-offset-item-frame-ticking.patch rename to patches/server/0304-offset-item-frame-ticking.patch diff --git a/patches/server/0305-Prevent-consuming-the-wrong-itemstack.patch b/patches/server/0305-Prevent-consuming-the-wrong-itemstack.patch new file mode 100644 index 000000000000..c1bcfd2f72f4 --- /dev/null +++ b/patches/server/0305-Prevent-consuming-the-wrong-itemstack.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Mon, 19 Aug 2019 19:42:35 +0500 +Subject: [PATCH] Prevent consuming the wrong itemstack + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 7fb9880caa964df65402153b87a953200b930e24..a2b13486bdcd4d50631053c0611ad86d628b7f1f 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3722,9 +3722,14 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + public void startUsingItem(InteractionHand hand) { ++ // Paper start - Prevent consuming the wrong itemstack ++ this.startUsingItem(hand, false); ++ } ++ public void startUsingItem(InteractionHand hand, boolean forceUpdate) { ++ // Paper end - Prevent consuming the wrong itemstack + ItemStack itemstack = this.getItemInHand(hand); + +- if (!itemstack.isEmpty() && !this.isUsingItem()) { ++ if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack + this.useItem = itemstack; + this.useItemRemaining = itemstack.getUseDuration(); + if (!this.level().isClientSide) { +@@ -3804,6 +3809,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.releaseUsingItem(); + } else { + if (!this.useItem.isEmpty() && this.isUsingItem()) { ++ this.startUsingItem(this.getUsedItemHand(), true); // Paper - Prevent consuming the wrong itemstack + this.triggerItemUseEffects(this.useItem, 16); + // CraftBukkit start - fire PlayerItemConsumeEvent + ItemStack itemstack; +@@ -3838,8 +3844,8 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + this.stopUsingItem(); +- // Paper start - if the replacement is anything but the default, update the client inventory +- if (this instanceof ServerPlayer && !com.google.common.base.Objects.equal(defaultReplacement, itemstack)) { ++ // Paper start ++ if (this instanceof ServerPlayer) { + ((ServerPlayer) this).getBukkitEntity().updateInventory(); + } + // Paper end diff --git a/patches/server/0307-Dont-send-unnecessary-sign-update.patch b/patches/server/0306-Dont-send-unnecessary-sign-update.patch similarity index 100% rename from patches/server/0307-Dont-send-unnecessary-sign-update.patch rename to patches/server/0306-Dont-send-unnecessary-sign-update.patch diff --git a/patches/server/0306-Prevent-consuming-the-wrong-itemstack.patch b/patches/server/0306-Prevent-consuming-the-wrong-itemstack.patch deleted file mode 100644 index 26f30425d29c..000000000000 --- a/patches/server/0306-Prevent-consuming-the-wrong-itemstack.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Mon, 19 Aug 2019 19:42:35 +0500 -Subject: [PATCH] Prevent consuming the wrong itemstack - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 98a26f97749d883f4ca04da27199f499211f0f33..e06aa636bf4e18ca2810e0626f427839cd2bce88 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3709,9 +3709,14 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public void startUsingItem(InteractionHand hand) { -+ // Paper start - Prevent consuming the wrong itemstack -+ this.startUsingItem(hand, false); -+ } -+ public void startUsingItem(InteractionHand hand, boolean forceUpdate) { -+ // Paper end - Prevent consuming the wrong itemstack - ItemStack itemstack = this.getItemInHand(hand); - -- if (!itemstack.isEmpty() && !this.isUsingItem()) { -+ if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack - this.useItem = itemstack; - this.useItemRemaining = itemstack.getUseDuration(); - if (!this.level().isClientSide) { -@@ -3791,6 +3796,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.releaseUsingItem(); - } else { - if (!this.useItem.isEmpty() && this.isUsingItem()) { -+ this.startUsingItem(this.getUsedItemHand(), true); // Paper - Prevent consuming the wrong itemstack - this.triggerItemUseEffects(this.useItem, 16); - // CraftBukkit start - fire PlayerItemConsumeEvent - ItemStack itemstack; -@@ -3825,8 +3831,8 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - this.stopUsingItem(); -- // Paper start - if the replacement is anything but the default, update the client inventory -- if (this instanceof ServerPlayer && !com.google.common.base.Objects.equal(defaultReplacement, itemstack)) { -+ // Paper start -+ if (this instanceof ServerPlayer) { - ((ServerPlayer) this).getBukkitEntity().updateInventory(); - } - // Paper end diff --git a/patches/server/0308-Add-option-to-disable-pillager-patrols.patch b/patches/server/0307-Add-option-to-disable-pillager-patrols.patch similarity index 100% rename from patches/server/0308-Add-option-to-disable-pillager-patrols.patch rename to patches/server/0307-Add-option-to-disable-pillager-patrols.patch diff --git a/patches/server/0309-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch b/patches/server/0308-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch similarity index 100% rename from patches/server/0309-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch rename to patches/server/0308-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch diff --git a/patches/server/0310-MC-145656-Fix-Follow-Range-Initial-Target.patch b/patches/server/0309-MC-145656-Fix-Follow-Range-Initial-Target.patch similarity index 100% rename from patches/server/0310-MC-145656-Fix-Follow-Range-Initial-Target.patch rename to patches/server/0309-MC-145656-Fix-Follow-Range-Initial-Target.patch diff --git a/patches/server/0310-Duplicate-UUID-Resolve-Option.patch b/patches/server/0310-Duplicate-UUID-Resolve-Option.patch new file mode 100644 index 000000000000..e2ec1d638a84 --- /dev/null +++ b/patches/server/0310-Duplicate-UUID-Resolve-Option.patch @@ -0,0 +1,92 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 21 Jul 2018 14:27:34 -0400 +Subject: [PATCH] Duplicate UUID Resolve Option + +Due to a bug in https://github.com/PaperMC/Paper/commit/2e29af3df05ec0a383f48be549d1c03200756d24 +which was added all the way back in March of 2016, it was unknown (potentially not at the time) +that an entity might actually change the seed of the random object. + +At some point, EntitySquid did start setting the seed. Due to this shared random, this caused +every entity to use a Random object with a predictable seed. + +This has caused entities to potentially generate with the same UUID.... + +Over the years, servers have had entities disappear, but no sign of trouble +because CraftBukkit removed the log lines indicating that something was wrong. + +We have fixed the root issue causing duplicate UUID's, however we now have chunk +files full of entities that have the same UUID as another entity! + +When these chunks load, the 2nd entity will not be added to the world correctly. + +If that chunk loads in a different order in the future, then it will reverse and the +missing one is now the one added to the world and not the other. This results in very +inconsistent entity behavior. + +This change allows you to recover any duplicate entity by generating a new UUID for it. +This also lets you delete them instead if you don't want to risk having new entities added to +the world that you previously did not see. + +But for those who are ok with leaving this inconsistent behavior, you may use WARN or NOTHING options. + +It is recommended you regenerate the entities, as these were legit entities, and deserve your love. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 9f8ee9d9b5fec9ca9c5e8462ed1aef742fa628ac..1b102f4c800dc6aa4e977c8ae8c9aada5d94e194 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -880,6 +880,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + entity.discard(); + needsRemoval = true; + } ++ checkDupeUUID(world, entity); // Paper - duplicate uuid resolving + return !needsRemoval; + })); + // CraftBukkit end +@@ -930,6 +931,45 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + }); + } + ++ // Paper start - duplicate uuid resolving ++ // rets true if to prevent the entity from being added ++ public static boolean checkDupeUUID(ServerLevel level, Entity entity) { ++ io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode mode = level.paperConfig().entities.spawning.duplicateUuid.mode; ++ if (mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.WARN ++ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.DELETE ++ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN) { ++ return false; ++ } ++ Entity other = level.getEntity(entity.getUUID()); ++ ++ if (other == null || other == entity) { ++ return false; ++ } ++ ++ if (mode == io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN && other != null && !other.isRemoved() ++ && Objects.equals(other.getEncodeId(), entity.getEncodeId()) ++ && entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < level.paperConfig().entities.spawning.duplicateUuid.safeRegenDeleteRange ++ ) { ++ entity.discard(); ++ return true; ++ } ++ if (!other.isRemoved()) { ++ switch (mode) { ++ case SAFE_REGEN: { ++ entity.setUUID(java.util.UUID.randomUUID()); ++ break; ++ } ++ case DELETE: { ++ entity.discard(); ++ return true; ++ } ++ default: ++ break; ++ } ++ } ++ return false; ++ } ++ // Paper end - duplicate uuid resolving + public CompletableFuture> prepareTickingChunk(ChunkHolder holder) { + CompletableFuture, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkRangeFuture(holder, 1, (i) -> { + return ChunkStatus.FULL; diff --git a/patches/server/0311-Duplicate-UUID-Resolve-Option.patch b/patches/server/0311-Duplicate-UUID-Resolve-Option.patch deleted file mode 100644 index 657d186095cb..000000000000 --- a/patches/server/0311-Duplicate-UUID-Resolve-Option.patch +++ /dev/null @@ -1,92 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 21 Jul 2018 14:27:34 -0400 -Subject: [PATCH] Duplicate UUID Resolve Option - -Due to a bug in https://github.com/PaperMC/Paper/commit/2e29af3df05ec0a383f48be549d1c03200756d24 -which was added all the way back in March of 2016, it was unknown (potentially not at the time) -that an entity might actually change the seed of the random object. - -At some point, EntitySquid did start setting the seed. Due to this shared random, this caused -every entity to use a Random object with a predictable seed. - -This has caused entities to potentially generate with the same UUID.... - -Over the years, servers have had entities disappear, but no sign of trouble -because CraftBukkit removed the log lines indicating that something was wrong. - -We have fixed the root issue causing duplicate UUID's, however we now have chunk -files full of entities that have the same UUID as another entity! - -When these chunks load, the 2nd entity will not be added to the world correctly. - -If that chunk loads in a different order in the future, then it will reverse and the -missing one is now the one added to the world and not the other. This results in very -inconsistent entity behavior. - -This change allows you to recover any duplicate entity by generating a new UUID for it. -This also lets you delete them instead if you don't want to risk having new entities added to -the world that you previously did not see. - -But for those who are ok with leaving this inconsistent behavior, you may use WARN or NOTHING options. - -It is recommended you regenerate the entities, as these were legit entities, and deserve your love. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 1a80f48a425a81af6acd917f67d33a80746f46c3..4357c2800aaceb2d6a9d3c5246faf796d7732f42 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -880,6 +880,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - entity.discard(); - needsRemoval = true; - } -+ checkDupeUUID(world, entity); // Paper - duplicate uuid resolving - return !needsRemoval; - })); - // CraftBukkit end -@@ -930,6 +931,45 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - }); - } - -+ // Paper start - duplicate uuid resolving -+ // rets true if to prevent the entity from being added -+ public static boolean checkDupeUUID(ServerLevel level, Entity entity) { -+ io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode mode = level.paperConfig().entities.spawning.duplicateUuid.mode; -+ if (mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.WARN -+ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.DELETE -+ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN) { -+ return false; -+ } -+ Entity other = level.getEntity(entity.getUUID()); -+ -+ if (other == null || other == entity) { -+ return false; -+ } -+ -+ if (mode == io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN && other != null && !other.isRemoved() -+ && Objects.equals(other.getEncodeId(), entity.getEncodeId()) -+ && entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < level.paperConfig().entities.spawning.duplicateUuid.safeRegenDeleteRange -+ ) { -+ entity.discard(); -+ return true; -+ } -+ if (!other.isRemoved()) { -+ switch (mode) { -+ case SAFE_REGEN: { -+ entity.setUUID(java.util.UUID.randomUUID()); -+ break; -+ } -+ case DELETE: { -+ entity.discard(); -+ return true; -+ } -+ default: -+ break; -+ } -+ } -+ return false; -+ } -+ // Paper end - duplicate uuid resolving - public CompletableFuture> prepareTickingChunk(ChunkHolder holder) { - CompletableFuture, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkRangeFuture(holder, 1, (i) -> { - return ChunkStatus.FULL; diff --git a/patches/server/0311-PlayerDeathEvent-shouldDropExperience.patch b/patches/server/0311-PlayerDeathEvent-shouldDropExperience.patch new file mode 100644 index 000000000000..8ce26f265aa2 --- /dev/null +++ b/patches/server/0311-PlayerDeathEvent-shouldDropExperience.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 24 Dec 2019 00:35:42 +0000 +Subject: [PATCH] PlayerDeathEvent#shouldDropExperience + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 71372fad81d34bb0a4035d3a69d656db3b45d9ce..8678efd1b0ad2e6dbf49ed47d0dc959027762b40 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -934,7 +934,7 @@ public class ServerPlayer extends Player { + this.tellNeutralMobsThatIDied(); + } + // SPIGOT-5478 must be called manually now +- this.dropExperience(); ++ if (event.shouldDropExperience()) this.dropExperience(); // Paper - tie to event + // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. + if (!event.getKeepInventory()) { + // Paper start - PlayerDeathEvent#getItemsToKeep diff --git a/patches/server/0312-PlayerDeathEvent-shouldDropExperience.patch b/patches/server/0312-PlayerDeathEvent-shouldDropExperience.patch deleted file mode 100644 index f11ba0a3abc8..000000000000 --- a/patches/server/0312-PlayerDeathEvent-shouldDropExperience.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Tue, 24 Dec 2019 00:35:42 +0000 -Subject: [PATCH] PlayerDeathEvent#shouldDropExperience - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index a003df2d8d6be620d4ccfe34dd6a8a1fc9225daf..d4d66bd96912592de499ef7e626f13de71714c93 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -934,7 +934,7 @@ public class ServerPlayer extends Player { - this.tellNeutralMobsThatIDied(); - } - // SPIGOT-5478 must be called manually now -- this.dropExperience(); -+ if (event.shouldDropExperience()) this.dropExperience(); // Paper - tie to event - // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. - if (!event.getKeepInventory()) { - // Paper start - PlayerDeathEvent#getItemsToKeep diff --git a/patches/server/0313-Prevent-bees-loading-chunks-checking-hive-position.patch b/patches/server/0312-Prevent-bees-loading-chunks-checking-hive-position.patch similarity index 100% rename from patches/server/0313-Prevent-bees-loading-chunks-checking-hive-position.patch rename to patches/server/0312-Prevent-bees-loading-chunks-checking-hive-position.patch diff --git a/patches/server/0314-Don-t-load-Chunks-from-Hoppers-and-other-things.patch b/patches/server/0313-Don-t-load-Chunks-from-Hoppers-and-other-things.patch similarity index 100% rename from patches/server/0314-Don-t-load-Chunks-from-Hoppers-and-other-things.patch rename to patches/server/0313-Don-t-load-Chunks-from-Hoppers-and-other-things.patch diff --git a/patches/server/0314-Optimise-EntityGetter-getPlayerByUUID.patch b/patches/server/0314-Optimise-EntityGetter-getPlayerByUUID.patch new file mode 100644 index 000000000000..3066fc8b6819 --- /dev/null +++ b/patches/server/0314-Optimise-EntityGetter-getPlayerByUUID.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 11 Jan 2020 21:50:56 -0800 +Subject: [PATCH] Optimise EntityGetter#getPlayerByUUID + +Use the PlayerList map instead of iterating over all players + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 514b3af4f8ac04744ae44cad7c01086a3f821d5f..3f70bab49d58ec36f6153a7dae577d22c90d2918 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -324,6 +324,15 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + // Paper end + ++ // Paper start - optimise getPlayerByUUID ++ @Nullable ++ @Override ++ public Player getPlayerByUUID(UUID uuid) { ++ final Player player = this.getServer().getPlayerList().getPlayer(uuid); ++ return player != null && player.level() == this ? player : null; ++ } ++ // Paper end - optimise getPlayerByUUID ++ + // Add env and gen to constructor, IWorldDataServer -> WorldDataServer + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { + // IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error diff --git a/patches/server/0316-Fix-items-not-falling-correctly.patch b/patches/server/0315-Fix-items-not-falling-correctly.patch similarity index 100% rename from patches/server/0316-Fix-items-not-falling-correctly.patch rename to patches/server/0315-Fix-items-not-falling-correctly.patch diff --git a/patches/server/0315-Optimise-EntityGetter-getPlayerByUUID.patch b/patches/server/0315-Optimise-EntityGetter-getPlayerByUUID.patch deleted file mode 100644 index 2f5e7ce829bc..000000000000 --- a/patches/server/0315-Optimise-EntityGetter-getPlayerByUUID.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 11 Jan 2020 21:50:56 -0800 -Subject: [PATCH] Optimise EntityGetter#getPlayerByUUID - -Use the PlayerList map instead of iterating over all players - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index bd41daf86f2a825a55ca685f4ea43a3df108b64a..989cd53db579408f89c3425d808e0a611194e896 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -324,6 +324,15 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - // Paper end - -+ // Paper start - optimise getPlayerByUUID -+ @Nullable -+ @Override -+ public Player getPlayerByUUID(UUID uuid) { -+ final Player player = this.getServer().getPlayerList().getPlayer(uuid); -+ return player != null && player.level() == this ? player : null; -+ } -+ // Paper end - optimise getPlayerByUUID -+ - // Add env and gen to constructor, IWorldDataServer -> WorldDataServer - public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { - // IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error diff --git a/patches/server/0316-Optimize-call-to-getFluid-for-explosions.patch b/patches/server/0316-Optimize-call-to-getFluid-for-explosions.patch new file mode 100644 index 000000000000..6afa3465ace1 --- /dev/null +++ b/patches/server/0316-Optimize-call-to-getFluid-for-explosions.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BrodyBeckwith +Date: Tue, 14 Jan 2020 17:49:03 -0500 +Subject: [PATCH] Optimize call to getFluid for explosions + + +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 00cfa26783ce0772c75166266ead258a415097bc..b03b4d366cae39081a7b70524e8615c986d76362 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -191,7 +191,7 @@ public class Explosion { + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { + BlockPos blockposition = BlockPos.containing(d4, d5, d6); + BlockState iblockdata = this.level.getBlockState(blockposition); +- FluidState fluid = this.level.getFluidState(blockposition); ++ FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions + + if (!this.level.isInWorldBounds(blockposition)) { + break; diff --git a/patches/server/0318-Fix-last-firework-in-stack-not-having-effects-when-d.patch b/patches/server/0317-Fix-last-firework-in-stack-not-having-effects-when-d.patch similarity index 100% rename from patches/server/0318-Fix-last-firework-in-stack-not-having-effects-when-d.patch rename to patches/server/0317-Fix-last-firework-in-stack-not-having-effects-when-d.patch diff --git a/patches/server/0317-Optimize-call-to-getFluid-for-explosions.patch b/patches/server/0317-Optimize-call-to-getFluid-for-explosions.patch deleted file mode 100644 index b55296324660..000000000000 --- a/patches/server/0317-Optimize-call-to-getFluid-for-explosions.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BrodyBeckwith -Date: Tue, 14 Jan 2020 17:49:03 -0500 -Subject: [PATCH] Optimize call to getFluid for explosions - - -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index 219f3abc6d3280077b53cfff97db7e724133f5a1..653036ca797ed4e87f0cc15898d55ede2ed96206 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -191,7 +191,7 @@ public class Explosion { - for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { - BlockPos blockposition = BlockPos.containing(d4, d5, d6); - BlockState iblockdata = this.level.getBlockState(blockposition); -- FluidState fluid = this.level.getFluidState(blockposition); -+ FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions - - if (!this.level.isInWorldBounds(blockposition)) { - break; diff --git a/patches/server/0318-Guard-against-serializing-mismatching-chunk-coordina.patch b/patches/server/0318-Guard-against-serializing-mismatching-chunk-coordina.patch new file mode 100644 index 000000000000..4a4b6f7faf4b --- /dev/null +++ b/patches/server/0318-Guard-against-serializing-mismatching-chunk-coordina.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 27 Dec 2019 09:42:26 -0800 +Subject: [PATCH] Guard against serializing mismatching chunk coordinate + +Should help if something dumb happens + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index ba7a816bd9dd4aec79e2560f0968374dbb28442c..cbc8e95c8f890f0c0eb717d4d2ae3f427dc260d8 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -86,8 +86,20 @@ public class ChunkSerializer { + + public ChunkSerializer() {} + ++ // Paper start - guard against serializing mismatching coordinates ++ // TODO Note: This needs to be re-checked each update ++ public static ChunkPos getChunkCoordinate(final CompoundTag chunkData) { ++ final int dataVersion = ChunkStorage.getVersion(chunkData); ++ if (dataVersion < 2842) { // Level tag is removed after this version ++ final CompoundTag levelData = chunkData.getCompound("Level"); ++ return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos")); ++ } else { ++ return new ChunkPos(chunkData.getInt("xPos"), chunkData.getInt("zPos")); ++ } ++ } ++ // Paper end - guard against serializing mismatching coordinates + public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) { +- ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); ++ ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate + + if (!Objects.equals(chunkPos, chunkcoordintpair1)) { + ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", new Object[]{chunkPos, chunkPos, chunkcoordintpair1}); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index 25623dcd44edc475c5dce2756bf99fc18e142b63..eaf978d15618b80d23c443acbd42db926d570d01 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -157,6 +157,13 @@ public class ChunkStorage implements AutoCloseable { + } + + public void write(ChunkPos chunkPos, CompoundTag nbt) { ++ // Paper start - guard against serializing mismatching coordinates ++ if (nbt != null && !chunkPos.equals(ChunkSerializer.getChunkCoordinate(nbt))) { ++ final String world = (this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap) this).level.getWorld().getName() : null; ++ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkPos ++ + " but compound says coordinate is " + ChunkSerializer.getChunkCoordinate(nbt) + (world == null ? " for an unknown world" : (" for world: " + world))); ++ } ++ // Paper end - guard against serializing mismatching coordinates + this.worker.store(chunkPos, nbt); + if (this.legacyStructureHandler != null) { + this.legacyStructureHandler.removeIndex(chunkPos.toLong()); diff --git a/patches/server/0320-Alternative-item-despawn-rate.patch b/patches/server/0319-Alternative-item-despawn-rate.patch similarity index 100% rename from patches/server/0320-Alternative-item-despawn-rate.patch rename to patches/server/0319-Alternative-item-despawn-rate.patch diff --git a/patches/server/0319-Guard-against-serializing-mismatching-chunk-coordina.patch b/patches/server/0319-Guard-against-serializing-mismatching-chunk-coordina.patch deleted file mode 100644 index 4ecb84bc71bb..000000000000 --- a/patches/server/0319-Guard-against-serializing-mismatching-chunk-coordina.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 27 Dec 2019 09:42:26 -0800 -Subject: [PATCH] Guard against serializing mismatching chunk coordinate - -Should help if something dumb happens - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index cc1d7626a82881c4410d65c6a33dadae7ab07172..5ef782ef14a9a880cb3db433bbee2d4a70d33718 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -86,8 +86,20 @@ public class ChunkSerializer { - - public ChunkSerializer() {} - -+ // Paper start - guard against serializing mismatching coordinates -+ // TODO Note: This needs to be re-checked each update -+ public static ChunkPos getChunkCoordinate(final CompoundTag chunkData) { -+ final int dataVersion = ChunkStorage.getVersion(chunkData); -+ if (dataVersion < 2842) { // Level tag is removed after this version -+ final CompoundTag levelData = chunkData.getCompound("Level"); -+ return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos")); -+ } else { -+ return new ChunkPos(chunkData.getInt("xPos"), chunkData.getInt("zPos")); -+ } -+ } -+ // Paper end - guard against serializing mismatching coordinates - public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) { -- ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); -+ ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate - - if (!Objects.equals(chunkPos, chunkcoordintpair1)) { - ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", new Object[]{chunkPos, chunkPos, chunkcoordintpair1}); -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -index 25623dcd44edc475c5dce2756bf99fc18e142b63..eaf978d15618b80d23c443acbd42db926d570d01 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -157,6 +157,13 @@ public class ChunkStorage implements AutoCloseable { - } - - public void write(ChunkPos chunkPos, CompoundTag nbt) { -+ // Paper start - guard against serializing mismatching coordinates -+ if (nbt != null && !chunkPos.equals(ChunkSerializer.getChunkCoordinate(nbt))) { -+ final String world = (this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap) this).level.getWorld().getName() : null; -+ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkPos -+ + " but compound says coordinate is " + ChunkSerializer.getChunkCoordinate(nbt) + (world == null ? " for an unknown world" : (" for world: " + world))); -+ } -+ // Paper end - guard against serializing mismatching coordinates - this.worker.store(chunkPos, nbt); - if (this.legacyStructureHandler != null) { - this.legacyStructureHandler.removeIndex(chunkPos.toLong()); diff --git a/patches/server/0320-Tracking-Range-Improvements.patch b/patches/server/0320-Tracking-Range-Improvements.patch new file mode 100644 index 000000000000..6370a7e08a65 --- /dev/null +++ b/patches/server/0320-Tracking-Range-Improvements.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Sat, 21 Dec 2019 15:22:09 -0500 +Subject: [PATCH] Tracking Range Improvements + +Sets tracking range of watermobs to animals instead of misc and simplifies code + +Also ignores Enderdragon, defaulting it to Mojang's setting + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 1b102f4c800dc6aa4e977c8ae8c9aada5d94e194..eff31fdd159dc7844bd3be3b769625a5704c2f17 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1761,6 +1761,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); + int j = entity.getType().clientTrackingRange() * 16; ++ j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper + + if (j > i) { + i = j; +diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java +index 73f9563551632a5369ba55e8fe9211afc325e869..bb06f89a29f30144e7e2113e088a503db006a83c 100644 +--- a/src/main/java/org/spigotmc/TrackingRange.java ++++ b/src/main/java/org/spigotmc/TrackingRange.java +@@ -7,7 +7,6 @@ import net.minecraft.world.entity.ExperienceOrb; + import net.minecraft.world.entity.decoration.ItemFrame; + import net.minecraft.world.entity.decoration.Painting; + import net.minecraft.world.entity.item.ItemEntity; +-import net.minecraft.world.entity.monster.Ghast; + + public class TrackingRange + { +@@ -30,22 +29,21 @@ public class TrackingRange + if ( entity instanceof ServerPlayer ) + { + return config.playerTrackingRange; +- } else if ( entity.activationType == ActivationRange.ActivationType.MONSTER || entity.activationType == ActivationRange.ActivationType.RAIDER ) +- { +- return config.monsterTrackingRange; +- } else if ( entity instanceof Ghast ) +- { +- if ( config.monsterTrackingRange > config.monsterActivationRange ) +- { ++ // Paper start - Simplify and set water mobs to animal tracking range ++ } ++ switch (entity.activationType) { ++ case RAIDER: ++ case MONSTER: ++ case FLYING_MONSTER: + return config.monsterTrackingRange; +- } else +- { +- return config.monsterActivationRange; +- } +- } else if ( entity.activationType == ActivationRange.ActivationType.ANIMAL ) +- { +- return config.animalTrackingRange; +- } else if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) ++ case WATER: ++ case VILLAGER: ++ case ANIMAL: ++ return config.animalTrackingRange; ++ case MISC: ++ } ++ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) ++ // Paper end + { + return config.miscTrackingRange; + } else if ( entity instanceof Display ) +@@ -53,6 +51,7 @@ public class TrackingRange + return config.displayTrackingRange; + } else + { ++ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return ((net.minecraft.server.level.ServerLevel)(entity.getCommandSenderWorld())).getChunkSource().chunkMap.serverViewDistance; // Paper - enderdragon is exempt + return config.otherTrackingRange; + } + } diff --git a/patches/server/0321-Fix-items-vanishing-through-end-portal.patch b/patches/server/0321-Fix-items-vanishing-through-end-portal.patch new file mode 100644 index 000000000000..5c6d4febc3a5 --- /dev/null +++ b/patches/server/0321-Fix-items-vanishing-through-end-portal.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AJMFactsheets +Date: Wed, 22 Jan 2020 19:52:28 -0600 +Subject: [PATCH] Fix items vanishing through end portal + +If the Paper configuration option "keep-spawn-loaded" is set to false, +items entering the overworld from the end will spawn at Y = 0. + +This is due to logic in the getHighestBlockYAt method in World.java +only searching the heightmap if the chunk is loaded. + +Quickly loading the exact world spawn chunk before searching the +heightmap resolves the issue without having to load all spawn chunks. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 07613c1a337e4aa82daa83157f44056d90fc5c24..ee095e846697e316f5cb1506ee6f14519cbf51b0 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3347,6 +3347,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + if (flag1) { + blockposition1 = ServerLevel.END_SPAWN_POINT; + } else { ++ destination.getChunkAt(destination.getSharedSpawnPos()); // Paper - Ensure spawn chunk is always loaded before calculating Y coordinate + blockposition1 = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, destination.getSharedSpawnPos()); + } + // CraftBukkit start diff --git a/patches/server/0321-Tracking-Range-Improvements.patch b/patches/server/0321-Tracking-Range-Improvements.patch deleted file mode 100644 index 63b16cfc5026..000000000000 --- a/patches/server/0321-Tracking-Range-Improvements.patch +++ /dev/null @@ -1,78 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Sat, 21 Dec 2019 15:22:09 -0500 -Subject: [PATCH] Tracking Range Improvements - -Sets tracking range of watermobs to animals instead of misc and simplifies code - -Also ignores Enderdragon, defaulting it to Mojang's setting - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index bc481cf547504140a921ec20d7c21d2931b1e7ab..51cbdaead57a527bac5eeb7bc130f31667fcf450 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1761,6 +1761,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - while (iterator.hasNext()) { - Entity entity = (Entity) iterator.next(); - int j = entity.getType().clientTrackingRange() * 16; -+ j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper - - if (j > i) { - i = j; -diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java -index 73f9563551632a5369ba55e8fe9211afc325e869..bb06f89a29f30144e7e2113e088a503db006a83c 100644 ---- a/src/main/java/org/spigotmc/TrackingRange.java -+++ b/src/main/java/org/spigotmc/TrackingRange.java -@@ -7,7 +7,6 @@ import net.minecraft.world.entity.ExperienceOrb; - import net.minecraft.world.entity.decoration.ItemFrame; - import net.minecraft.world.entity.decoration.Painting; - import net.minecraft.world.entity.item.ItemEntity; --import net.minecraft.world.entity.monster.Ghast; - - public class TrackingRange - { -@@ -30,22 +29,21 @@ public class TrackingRange - if ( entity instanceof ServerPlayer ) - { - return config.playerTrackingRange; -- } else if ( entity.activationType == ActivationRange.ActivationType.MONSTER || entity.activationType == ActivationRange.ActivationType.RAIDER ) -- { -- return config.monsterTrackingRange; -- } else if ( entity instanceof Ghast ) -- { -- if ( config.monsterTrackingRange > config.monsterActivationRange ) -- { -+ // Paper start - Simplify and set water mobs to animal tracking range -+ } -+ switch (entity.activationType) { -+ case RAIDER: -+ case MONSTER: -+ case FLYING_MONSTER: - return config.monsterTrackingRange; -- } else -- { -- return config.monsterActivationRange; -- } -- } else if ( entity.activationType == ActivationRange.ActivationType.ANIMAL ) -- { -- return config.animalTrackingRange; -- } else if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) -+ case WATER: -+ case VILLAGER: -+ case ANIMAL: -+ return config.animalTrackingRange; -+ case MISC: -+ } -+ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) -+ // Paper end - { - return config.miscTrackingRange; - } else if ( entity instanceof Display ) -@@ -53,6 +51,7 @@ public class TrackingRange - return config.displayTrackingRange; - } else - { -+ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return ((net.minecraft.server.level.ServerLevel)(entity.getCommandSenderWorld())).getChunkSource().chunkMap.serverViewDistance; // Paper - enderdragon is exempt - return config.otherTrackingRange; - } - } diff --git a/patches/server/0323-Bees-get-gravity-in-void.-Fixes-MC-167279.patch b/patches/server/0322-Bees-get-gravity-in-void.-Fixes-MC-167279.patch similarity index 100% rename from patches/server/0323-Bees-get-gravity-in-void.-Fixes-MC-167279.patch rename to patches/server/0322-Bees-get-gravity-in-void.-Fixes-MC-167279.patch diff --git a/patches/server/0322-Fix-items-vanishing-through-end-portal.patch b/patches/server/0322-Fix-items-vanishing-through-end-portal.patch deleted file mode 100644 index ba6886e945be..000000000000 --- a/patches/server/0322-Fix-items-vanishing-through-end-portal.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: AJMFactsheets -Date: Wed, 22 Jan 2020 19:52:28 -0600 -Subject: [PATCH] Fix items vanishing through end portal - -If the Paper configuration option "keep-spawn-loaded" is set to false, -items entering the overworld from the end will spawn at Y = 0. - -This is due to logic in the getHighestBlockYAt method in World.java -only searching the heightmap if the chunk is loaded. - -Quickly loading the exact world spawn chunk before searching the -heightmap resolves the issue without having to load all spawn chunks. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index c601bd02a75e10af7ad7040b3a4d974c585bbaaf..2aea992adeae1d51f224e76d104b031f46447a5d 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3344,6 +3344,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - if (flag1) { - blockposition1 = ServerLevel.END_SPAWN_POINT; - } else { -+ destination.getChunkAt(destination.getSharedSpawnPos()); // Paper - Ensure spawn chunk is always loaded before calculating Y coordinate - blockposition1 = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, destination.getSharedSpawnPos()); - } - // CraftBukkit start diff --git a/patches/server/0324-Improve-Block-breakNaturally-API.patch b/patches/server/0323-Improve-Block-breakNaturally-API.patch similarity index 100% rename from patches/server/0324-Improve-Block-breakNaturally-API.patch rename to patches/server/0323-Improve-Block-breakNaturally-API.patch diff --git a/patches/server/0325-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/patches/server/0324-Optimise-getChunkAt-calls-for-loaded-chunks.patch similarity index 100% rename from patches/server/0325-Optimise-getChunkAt-calls-for-loaded-chunks.patch rename to patches/server/0324-Optimise-getChunkAt-calls-for-loaded-chunks.patch diff --git a/patches/server/0326-Add-debug-for-sync-chunk-loads.patch b/patches/server/0325-Add-debug-for-sync-chunk-loads.patch similarity index 100% rename from patches/server/0326-Add-debug-for-sync-chunk-loads.patch rename to patches/server/0325-Add-debug-for-sync-chunk-loads.patch diff --git a/patches/server/0327-Improve-java-version-check.patch b/patches/server/0326-Improve-java-version-check.patch similarity index 100% rename from patches/server/0327-Improve-java-version-check.patch rename to patches/server/0326-Improve-java-version-check.patch diff --git a/patches/server/0328-Add-ThrownEggHatchEvent.patch b/patches/server/0327-Add-ThrownEggHatchEvent.patch similarity index 100% rename from patches/server/0328-Add-ThrownEggHatchEvent.patch rename to patches/server/0327-Add-ThrownEggHatchEvent.patch diff --git a/patches/server/0328-Entity-Jump-API.patch b/patches/server/0328-Entity-Jump-API.patch new file mode 100644 index 000000000000..ffc89eac238d --- /dev/null +++ b/patches/server/0328-Entity-Jump-API.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Sat, 8 Feb 2020 23:26:11 -0600 +Subject: [PATCH] Entity Jump API + +== AT == +public net.minecraft.world.entity.LivingEntity jumping + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index a2b13486bdcd4d50631053c0611ad86d628b7f1f..c68eba06077c6f6894f2cc2947f51d8d1c86ef94 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3296,8 +3296,10 @@ public abstract class LivingEntity extends Entity implements Attackable { + } else if (this.isInLava() && (!this.onGround() || d3 > d4)) { + this.jumpInLiquid(FluidTags.LAVA); + } else if ((this.onGround() || flag && d3 <= d4) && this.noJumpDelay == 0) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API + this.jumpFromGround(); + this.noJumpDelay = 10; ++ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop + } + } else { + this.noJumpDelay = 0; +diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java +index 3d947293d33407e5d24f8fba738ab872e1d890b6..48df7de02e0765bfe62ae1b8a4c0029743430221 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Panda.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java +@@ -527,7 +527,9 @@ public class Panda extends Animal { + Panda entitypanda = (Panda) iterator.next(); + + if (!entitypanda.isBaby() && entitypanda.onGround() && !entitypanda.isInWater() && entitypanda.canPerformAction()) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API + entitypanda.jumpFromGround(); ++ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +index aba20a4352d8983b01ab5d329187588f68d3e405..aac60e85cd6dba7d87f4a1663c2c62952521d112 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +@@ -167,7 +167,9 @@ public class Ravager extends Raider { + } + + if (!flag && this.onGround()) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API + this.jumpFromGround(); ++ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index ea9a6446ed2dbddedbc615df93c9856a0a1f1a2e..fd18531ff94daa6dc2994a69e1e647078a5a664c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -935,5 +935,19 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public org.bukkit.inventory.EquipmentSlot getHandRaised() { + return getHandle().getUsedItemHand() == net.minecraft.world.InteractionHand.MAIN_HAND ? org.bukkit.inventory.EquipmentSlot.HAND : org.bukkit.inventory.EquipmentSlot.OFF_HAND; + } ++ ++ @Override ++ public boolean isJumping() { ++ return getHandle().jumping; ++ } ++ ++ @Override ++ public void setJumping(boolean jumping) { ++ getHandle().setJumping(jumping); ++ if (jumping && getHandle() instanceof Mob) { ++ // this is needed to actually make a mob jump ++ ((Mob) getHandle()).getJumpControl().jump(); ++ } ++ } + // Paper end + } diff --git a/patches/server/0329-Add-option-to-nerf-pigmen-from-nether-portals.patch b/patches/server/0329-Add-option-to-nerf-pigmen-from-nether-portals.patch new file mode 100644 index 000000000000..14213f70e590 --- /dev/null +++ b/patches/server/0329-Add-option-to-nerf-pigmen-from-nether-portals.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 7 Feb 2020 14:36:56 -0600 +Subject: [PATCH] Add option to nerf pigmen from nether portals + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index ee095e846697e316f5cb1506ee6f14519cbf51b0..fa0f57779251ea785dfa4fe299c1505e46aa1446 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -396,6 +396,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + public void inactiveTick() { } + // Spigot end + protected int numCollisions = 0; // Paper - Cap entity collisions ++ public boolean fromNetherPortal; // Paper - Add option to nerf pigmen from nether portals + public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one + // Paper start - Entity origin API + @javax.annotation.Nullable +@@ -2170,6 +2171,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + if (spawnedViaMobSpawner) { + nbttagcompound.putBoolean("Paper.FromMobSpawner", true); + } ++ if (fromNetherPortal) { ++ nbttagcompound.putBoolean("Paper.FromNetherPortal", true); ++ } + // Paper end + return nbttagcompound; + } catch (Throwable throwable) { +@@ -2312,6 +2316,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status ++ fromNetherPortal = nbt.getBoolean("Paper.FromNetherPortal"); + if (nbt.contains("Paper.SpawnReason")) { + String spawnReasonName = nbt.getString("Paper.SpawnReason"); + try { +diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +index c9ff8a3fd8d65033ce5a476e8ceaf9d1b8e2d887..2a8f97d97ae7f268da920b5e3b9719743fa9a8e0 100644 +--- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +@@ -71,6 +71,8 @@ public class NetherPortalBlock extends Block { + + if (entity != null) { + entity.setPortalCooldown(); ++ entity.fromNetherPortal = true; // Paper - Add option to nerf pigmen from nether portals ++ if (world.paperConfig().entities.behavior.nerfPigmenFromNetherPortals) ((net.minecraft.world.entity.Mob) entity).aware = false; // Paper - Add option to nerf pigmen from nether portals + } + } + } diff --git a/patches/server/0329-Entity-Jump-API.patch b/patches/server/0329-Entity-Jump-API.patch deleted file mode 100644 index 9b166d8d572c..000000000000 --- a/patches/server/0329-Entity-Jump-API.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 8 Feb 2020 23:26:11 -0600 -Subject: [PATCH] Entity Jump API - -== AT == -public net.minecraft.world.entity.LivingEntity jumping - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index e06aa636bf4e18ca2810e0626f427839cd2bce88..bc51c6d8b39907d89a4f65cd7a8266df1fceab8c 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3283,8 +3283,10 @@ public abstract class LivingEntity extends Entity implements Attackable { - } else if (this.isInLava() && (!this.onGround() || d3 > d4)) { - this.jumpInLiquid(FluidTags.LAVA); - } else if ((this.onGround() || flag && d3 <= d4) && this.noJumpDelay == 0) { -+ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API - this.jumpFromGround(); - this.noJumpDelay = 10; -+ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop - } - } else { - this.noJumpDelay = 0; -diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java -index 3d947293d33407e5d24f8fba738ab872e1d890b6..48df7de02e0765bfe62ae1b8a4c0029743430221 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java -@@ -527,7 +527,9 @@ public class Panda extends Animal { - Panda entitypanda = (Panda) iterator.next(); - - if (!entitypanda.isBaby() && entitypanda.onGround() && !entitypanda.isInWater() && entitypanda.canPerformAction()) { -+ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API - entitypanda.jumpFromGround(); -+ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -index aba20a4352d8983b01ab5d329187588f68d3e405..aac60e85cd6dba7d87f4a1663c2c62952521d112 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -@@ -167,7 +167,9 @@ public class Ravager extends Raider { - } - - if (!flag && this.onGround()) { -+ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - Entity Jump API - this.jumpFromGround(); -+ } else { this.setJumping(false); } // Paper - Entity Jump API; setJumping(false) stops a potential loop - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index dbfeeacbb503aad5897c92c902d16fdbea7b793f..d5135ac97b2d4af7391a58799497c649cfcf8041 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -922,5 +922,19 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - public org.bukkit.inventory.EquipmentSlot getHandRaised() { - return getHandle().getUsedItemHand() == net.minecraft.world.InteractionHand.MAIN_HAND ? org.bukkit.inventory.EquipmentSlot.HAND : org.bukkit.inventory.EquipmentSlot.OFF_HAND; - } -+ -+ @Override -+ public boolean isJumping() { -+ return getHandle().jumping; -+ } -+ -+ @Override -+ public void setJumping(boolean jumping) { -+ getHandle().setJumping(jumping); -+ if (jumping && getHandle() instanceof Mob) { -+ // this is needed to actually make a mob jump -+ ((Mob) getHandle()).getJumpControl().jump(); -+ } -+ } - // Paper end - } diff --git a/patches/server/0330-Add-option-to-nerf-pigmen-from-nether-portals.patch b/patches/server/0330-Add-option-to-nerf-pigmen-from-nether-portals.patch deleted file mode 100644 index 42b9ca56c675..000000000000 --- a/patches/server/0330-Add-option-to-nerf-pigmen-from-nether-portals.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Fri, 7 Feb 2020 14:36:56 -0600 -Subject: [PATCH] Add option to nerf pigmen from nether portals - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 2aea992adeae1d51f224e76d104b031f46447a5d..99f9e96509800e2246e729400a1ac6de522d456a 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -396,6 +396,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - public void inactiveTick() { } - // Spigot end - protected int numCollisions = 0; // Paper - Cap entity collisions -+ public boolean fromNetherPortal; // Paper - Add option to nerf pigmen from nether portals - public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one - // Paper start - Entity origin API - @javax.annotation.Nullable -@@ -2166,6 +2167,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - if (spawnedViaMobSpawner) { - nbttagcompound.putBoolean("Paper.FromMobSpawner", true); - } -+ if (fromNetherPortal) { -+ nbttagcompound.putBoolean("Paper.FromNetherPortal", true); -+ } - // Paper end - return nbttagcompound; - } catch (Throwable throwable) { -@@ -2308,6 +2312,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status -+ fromNetherPortal = nbt.getBoolean("Paper.FromNetherPortal"); - if (nbt.contains("Paper.SpawnReason")) { - String spawnReasonName = nbt.getString("Paper.SpawnReason"); - try { -diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -index c9ff8a3fd8d65033ce5a476e8ceaf9d1b8e2d887..2a8f97d97ae7f268da920b5e3b9719743fa9a8e0 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -@@ -71,6 +71,8 @@ public class NetherPortalBlock extends Block { - - if (entity != null) { - entity.setPortalCooldown(); -+ entity.fromNetherPortal = true; // Paper - Add option to nerf pigmen from nether portals -+ if (world.paperConfig().entities.behavior.nerfPigmenFromNetherPortals) ((net.minecraft.world.entity.Mob) entity).aware = false; // Paper - Add option to nerf pigmen from nether portals - } - } - } diff --git a/patches/server/0331-Make-the-GUI-graph-fancier.patch b/patches/server/0330-Make-the-GUI-graph-fancier.patch similarity index 100% rename from patches/server/0331-Make-the-GUI-graph-fancier.patch rename to patches/server/0330-Make-the-GUI-graph-fancier.patch diff --git a/patches/server/0331-add-hand-to-BlockMultiPlaceEvent.patch b/patches/server/0331-add-hand-to-BlockMultiPlaceEvent.patch new file mode 100644 index 000000000000..c787ae5d9cfa --- /dev/null +++ b/patches/server/0331-add-hand-to-BlockMultiPlaceEvent.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Sun, 1 Mar 2020 22:43:24 +0100 +Subject: [PATCH] add hand to BlockMultiPlaceEvent + + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index a08d1250cd5a5cc70e3796ac56a0cfaee6fe2b83..2b342f81ae8bfefe2a240351f28fcafc40609a75 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -400,13 +400,18 @@ public class CraftEventFactory { + } + + org.bukkit.inventory.ItemStack item; ++ // Paper start - add hand to BlockMultiPlaceEvent ++ EquipmentSlot equipmentSlot; + if (hand == InteractionHand.MAIN_HAND) { + item = player.getInventory().getItemInMainHand(); ++ equipmentSlot = EquipmentSlot.HAND; + } else { + item = player.getInventory().getItemInOffHand(); ++ equipmentSlot = EquipmentSlot.OFF_HAND; + } + +- BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild); ++ BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild, equipmentSlot); ++ // Paper end + craftServer.getPluginManager().callEvent(event); + + return event; diff --git a/patches/server/0333-Validate-tripwire-hook-placement-before-update.patch b/patches/server/0332-Validate-tripwire-hook-placement-before-update.patch similarity index 100% rename from patches/server/0333-Validate-tripwire-hook-placement-before-update.patch rename to patches/server/0332-Validate-tripwire-hook-placement-before-update.patch diff --git a/patches/server/0332-add-hand-to-BlockMultiPlaceEvent.patch b/patches/server/0332-add-hand-to-BlockMultiPlaceEvent.patch deleted file mode 100644 index e9fd65e7c7ac..000000000000 --- a/patches/server/0332-add-hand-to-BlockMultiPlaceEvent.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Trigary -Date: Sun, 1 Mar 2020 22:43:24 +0100 -Subject: [PATCH] add hand to BlockMultiPlaceEvent - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 3bd92edf2f4e7a63a07bd2bbae002f99484a9069..7e7427b9b5d44f1a5205011fa427f084bd34165c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -400,13 +400,18 @@ public class CraftEventFactory { - } - - org.bukkit.inventory.ItemStack item; -+ // Paper start - add hand to BlockMultiPlaceEvent -+ EquipmentSlot equipmentSlot; - if (hand == InteractionHand.MAIN_HAND) { - item = player.getInventory().getItemInMainHand(); -+ equipmentSlot = EquipmentSlot.HAND; - } else { - item = player.getInventory().getItemInOffHand(); -+ equipmentSlot = EquipmentSlot.OFF_HAND; - } - -- BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild); -+ BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild, equipmentSlot); -+ // Paper end - craftServer.getPluginManager().callEvent(event); - - return event; diff --git a/patches/server/0334-Add-option-to-allow-iron-golems-to-spawn-in-air.patch b/patches/server/0333-Add-option-to-allow-iron-golems-to-spawn-in-air.patch similarity index 100% rename from patches/server/0334-Add-option-to-allow-iron-golems-to-spawn-in-air.patch rename to patches/server/0333-Add-option-to-allow-iron-golems-to-spawn-in-air.patch diff --git a/patches/server/0335-Configurable-chance-of-villager-zombie-infection.patch b/patches/server/0334-Configurable-chance-of-villager-zombie-infection.patch similarity index 100% rename from patches/server/0335-Configurable-chance-of-villager-zombie-infection.patch rename to patches/server/0334-Configurable-chance-of-villager-zombie-infection.patch diff --git a/patches/server/0336-Optimise-Chunk-getFluid.patch b/patches/server/0335-Optimise-Chunk-getFluid.patch similarity index 100% rename from patches/server/0336-Optimise-Chunk-getFluid.patch rename to patches/server/0335-Optimise-Chunk-getFluid.patch diff --git a/patches/server/0337-Set-spigots-verbose-world-setting-to-false-by-def.patch b/patches/server/0336-Set-spigots-verbose-world-setting-to-false-by-def.patch similarity index 100% rename from patches/server/0337-Set-spigots-verbose-world-setting-to-false-by-def.patch rename to patches/server/0336-Set-spigots-verbose-world-setting-to-false-by-def.patch diff --git a/patches/server/0337-Add-tick-times-API-and-mspt-command.patch b/patches/server/0337-Add-tick-times-API-and-mspt-command.patch new file mode 100644 index 000000000000..a38e26cb7369 --- /dev/null +++ b/patches/server/0337-Add-tick-times-API-and-mspt-command.patch @@ -0,0 +1,206 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 5 Apr 2020 22:23:14 -0500 +Subject: [PATCH] Add tick times API and /mspt command + + +diff --git a/src/main/java/io/papermc/paper/command/MSPTCommand.java b/src/main/java/io/papermc/paper/command/MSPTCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8b5293b0c696ef21d0101493ffa41b60bf0bc86b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/MSPTCommand.java +@@ -0,0 +1,102 @@ ++package io.papermc.paper.command; ++ ++import net.kyori.adventure.text.Component; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Location; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++ ++import java.text.DecimalFormat; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collections; ++import java.util.List; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GOLD; ++import static net.kyori.adventure.text.format.NamedTextColor.GRAY; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; ++ ++@DefaultQualifier(NonNull.class) ++public final class MSPTCommand extends Command { ++ private static final DecimalFormat DF = new DecimalFormat("########0.0"); ++ private static final Component SLASH = text("/"); ++ ++ public MSPTCommand(final String name) { ++ super(name); ++ this.description = "View server tick times"; ++ this.usageMessage = "/mspt"; ++ this.setPermission("bukkit.command.mspt"); ++ } ++ ++ @Override ++ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public boolean execute(CommandSender sender, String commandLabel, String[] args) { ++ if (!testPermission(sender)) return true; ++ ++ MinecraftServer server = MinecraftServer.getServer(); ++ ++ List times = new ArrayList<>(); ++ times.addAll(eval(server.tickTimes5s.getTimes())); ++ times.addAll(eval(server.tickTimes10s.getTimes())); ++ times.addAll(eval(server.tickTimes60s.getTimes())); ++ ++ sender.sendMessage(text().content("Server tick times ").color(GOLD) ++ .append(text().color(YELLOW) ++ .append( ++ text("("), ++ text("avg", GRAY), ++ text("/"), ++ text("min", GRAY), ++ text("/"), ++ text("max", GRAY), ++ text(")") ++ ) ++ ).append( ++ text(" from last 5s"), ++ text(",", GRAY), ++ text(" 10s"), ++ text(",", GRAY), ++ text(" 1m"), ++ text(":", YELLOW) ++ ) ++ ); ++ sender.sendMessage(text().content("â—´ ").color(GOLD) ++ .append(text().color(GRAY) ++ .append( ++ times.get(0), SLASH, times.get(1), SLASH, times.get(2), text(", ", YELLOW), ++ times.get(3), SLASH, times.get(4), SLASH, times.get(5), text(", ", YELLOW), ++ times.get(6), SLASH, times.get(7), SLASH, times.get(8) ++ ) ++ ) ++ ); ++ return true; ++ } ++ ++ private static List eval(long[] times) { ++ long min = Integer.MAX_VALUE; ++ long max = 0L; ++ long total = 0L; ++ for (long value : times) { ++ if (value > 0L && value < min) min = value; ++ if (value > max) max = value; ++ total += value; ++ } ++ double avgD = ((double) total / (double) times.length) * 1.0E-6D; ++ double minD = ((double) min) * 1.0E-6D; ++ double maxD = ((double) max) * 1.0E-6D; ++ return Arrays.asList(getColor(avgD), getColor(minD), getColor(maxD)); ++ } ++ ++ private static Component getColor(double avg) { ++ return text(DF.format(avg), avg >= 50 ? RED : avg >= 40 ? YELLOW : GREEN); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java +index 72f2e81b9905a0d57ed8e2a88578f62d5235c456..7b58b2d6297800c2dcdbf7539e5ab8e7703f39f1 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommands.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommands.java +@@ -18,6 +18,7 @@ public final class PaperCommands { + static { + COMMANDS.put("paper", new PaperCommand("paper")); + COMMANDS.put("callback", new CallbackCommand("callback")); ++ COMMANDS.put("mspt", new MSPTCommand("mspt")); + } + + public static void registerCommands(final MinecraftServer server) { +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 7263a7f8b01c6afdaf67cfd6cc67f6a45c0aee08..e5ecca5ae9e37e4d4d6d2adfd5f487a869ab775d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -252,6 +252,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Sun, 5 Apr 2020 22:23:14 -0500 -Subject: [PATCH] Add tick times API and /mspt command - - -diff --git a/src/main/java/io/papermc/paper/command/MSPTCommand.java b/src/main/java/io/papermc/paper/command/MSPTCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8b5293b0c696ef21d0101493ffa41b60bf0bc86b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/MSPTCommand.java -@@ -0,0 +1,102 @@ -+package io.papermc.paper.command; -+ -+import net.kyori.adventure.text.Component; -+import net.minecraft.server.MinecraftServer; -+import org.bukkit.Location; -+import org.bukkit.command.Command; -+import org.bukkit.command.CommandSender; -+ -+import java.text.DecimalFormat; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Collections; -+import java.util.List; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.GOLD; -+import static net.kyori.adventure.text.format.NamedTextColor.GRAY; -+import static net.kyori.adventure.text.format.NamedTextColor.GREEN; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; -+ -+@DefaultQualifier(NonNull.class) -+public final class MSPTCommand extends Command { -+ private static final DecimalFormat DF = new DecimalFormat("########0.0"); -+ private static final Component SLASH = text("/"); -+ -+ public MSPTCommand(final String name) { -+ super(name); -+ this.description = "View server tick times"; -+ this.usageMessage = "/mspt"; -+ this.setPermission("bukkit.command.mspt"); -+ } -+ -+ @Override -+ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { -+ return Collections.emptyList(); -+ } -+ -+ @Override -+ public boolean execute(CommandSender sender, String commandLabel, String[] args) { -+ if (!testPermission(sender)) return true; -+ -+ MinecraftServer server = MinecraftServer.getServer(); -+ -+ List times = new ArrayList<>(); -+ times.addAll(eval(server.tickTimes5s.getTimes())); -+ times.addAll(eval(server.tickTimes10s.getTimes())); -+ times.addAll(eval(server.tickTimes60s.getTimes())); -+ -+ sender.sendMessage(text().content("Server tick times ").color(GOLD) -+ .append(text().color(YELLOW) -+ .append( -+ text("("), -+ text("avg", GRAY), -+ text("/"), -+ text("min", GRAY), -+ text("/"), -+ text("max", GRAY), -+ text(")") -+ ) -+ ).append( -+ text(" from last 5s"), -+ text(",", GRAY), -+ text(" 10s"), -+ text(",", GRAY), -+ text(" 1m"), -+ text(":", YELLOW) -+ ) -+ ); -+ sender.sendMessage(text().content("â—´ ").color(GOLD) -+ .append(text().color(GRAY) -+ .append( -+ times.get(0), SLASH, times.get(1), SLASH, times.get(2), text(", ", YELLOW), -+ times.get(3), SLASH, times.get(4), SLASH, times.get(5), text(", ", YELLOW), -+ times.get(6), SLASH, times.get(7), SLASH, times.get(8) -+ ) -+ ) -+ ); -+ return true; -+ } -+ -+ private static List eval(long[] times) { -+ long min = Integer.MAX_VALUE; -+ long max = 0L; -+ long total = 0L; -+ for (long value : times) { -+ if (value > 0L && value < min) min = value; -+ if (value > max) max = value; -+ total += value; -+ } -+ double avgD = ((double) total / (double) times.length) * 1.0E-6D; -+ double minD = ((double) min) * 1.0E-6D; -+ double maxD = ((double) max) * 1.0E-6D; -+ return Arrays.asList(getColor(avgD), getColor(minD), getColor(maxD)); -+ } -+ -+ private static Component getColor(double avg) { -+ return text(DF.format(avg), avg >= 50 ? RED : avg >= 40 ? YELLOW : GREEN); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java -index 72f2e81b9905a0d57ed8e2a88578f62d5235c456..7b58b2d6297800c2dcdbf7539e5ab8e7703f39f1 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommands.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommands.java -@@ -18,6 +18,7 @@ public final class PaperCommands { - static { - COMMANDS.put("paper", new PaperCommand("paper")); - COMMANDS.put("callback", new CallbackCommand("callback")); -+ COMMANDS.put("mspt", new MSPTCommand("mspt")); - } - - public static void registerCommands(final MinecraftServer server) { -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index cecf238af5fd695baa623d7d09323a60b41512a5..698601fc2c6e5d19a990bd3dcf0bc52e4c6efaea 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -252,6 +252,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Fri, 10 Apr 2020 21:24:12 -0400 +Subject: [PATCH] Expose MinecraftServer#isRunning + +This allows for plugins to detect if the server is actually turning off in onDisable rather than just plugins reloading. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 58cf5568b97e907d840099198f3c62b9d780bd0c..ae424a5cbf8868aea9e11ffd565665c50aeb780e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2876,5 +2876,10 @@ public final class CraftServer implements Server { + public int getCurrentTick() { + return net.minecraft.server.MinecraftServer.currentTick; + } ++ ++ @Override ++ public boolean isStopping() { ++ return net.minecraft.server.MinecraftServer.getServer().hasStopped(); ++ } + // Paper end + } diff --git a/patches/server/0339-Add-Raw-Byte-ItemStack-Serialization.patch b/patches/server/0339-Add-Raw-Byte-ItemStack-Serialization.patch new file mode 100644 index 000000000000..59a539cd1497 --- /dev/null +++ b/patches/server/0339-Add-Raw-Byte-ItemStack-Serialization.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Thu, 30 Apr 2020 16:56:54 +0200 +Subject: [PATCH] Add Raw Byte ItemStack Serialization + +Serializes using NBT which is safer for server data migrations than bukkits format. + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index d11773ed44936f8836f2f8e582d5e9573af5b439..ef4b15e0ae11a54ec49e40f1d694dae58ae95e03 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -473,6 +473,53 @@ public final class CraftMagicNumbers implements UnsafeValues { + public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { + return new com.destroystokyo.paper.PaperVersionFetcher(); + } ++ ++ @Override ++ public byte[] serializeItem(ItemStack item) { ++ Preconditions.checkNotNull(item, "null cannot be serialized"); ++ Preconditions.checkArgument(item.getType() != Material.AIR, "air cannot be serialized"); ++ ++ return serializeNbtToBytes((item instanceof CraftItemStack ? ((CraftItemStack) item).handle : CraftItemStack.asNMSCopy(item)).save(new CompoundTag())); ++ } ++ ++ @Override ++ public ItemStack deserializeItem(byte[] data) { ++ Preconditions.checkNotNull(data, "null cannot be deserialized"); ++ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); ++ ++ CompoundTag compound = deserializeNbtFromBytes(data); ++ final int dataVersion = compound.getInt("DataVersion"); ++ compound = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue(); ++ return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of(compound)); ++ } ++ ++ private byte[] serializeNbtToBytes(CompoundTag compound) { ++ compound.putInt("DataVersion", getDataVersion()); ++ java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); ++ try { ++ net.minecraft.nbt.NbtIo.writeCompressed( ++ compound, ++ outputStream ++ ); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ return outputStream.toByteArray(); ++ } ++ ++ private CompoundTag deserializeNbtFromBytes(byte[] data) { ++ CompoundTag compound; ++ try { ++ compound = net.minecraft.nbt.NbtIo.readCompressed( ++ new java.io.ByteArrayInputStream(data), net.minecraft.nbt.NbtAccounter.unlimitedHeap() ++ ); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ int dataVersion = compound.getInt("DataVersion"); ++ Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); ++ return compound; ++ } + // Paper end + + /** diff --git a/patches/server/0339-Expose-MinecraftServer-isRunning.patch b/patches/server/0339-Expose-MinecraftServer-isRunning.patch deleted file mode 100644 index cef293bc57c5..000000000000 --- a/patches/server/0339-Expose-MinecraftServer-isRunning.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Fri, 10 Apr 2020 21:24:12 -0400 -Subject: [PATCH] Expose MinecraftServer#isRunning - -This allows for plugins to detect if the server is actually turning off in onDisable rather than just plugins reloading. - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 04788b844ddd7f33216f05642850edbe58021697..8d65ed46f3ab22b18bf7d54368d744d9084dad1b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2864,5 +2864,10 @@ public final class CraftServer implements Server { - public int getCurrentTick() { - return net.minecraft.server.MinecraftServer.currentTick; - } -+ -+ @Override -+ public boolean isStopping() { -+ return net.minecraft.server.MinecraftServer.getServer().hasStopped(); -+ } - // Paper end - } diff --git a/patches/server/0340-Add-Raw-Byte-ItemStack-Serialization.patch b/patches/server/0340-Add-Raw-Byte-ItemStack-Serialization.patch deleted file mode 100644 index d4d1597749e3..000000000000 --- a/patches/server/0340-Add-Raw-Byte-ItemStack-Serialization.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Thu, 30 Apr 2020 16:56:54 +0200 -Subject: [PATCH] Add Raw Byte ItemStack Serialization - -Serializes using NBT which is safer for server data migrations than bukkits format. - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index e31ead0d99203a018757cb2e765b5d28dd373eef..41fb303191783ad9e531331dc8468f95139432b9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -458,6 +458,53 @@ public final class CraftMagicNumbers implements UnsafeValues { - public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { - return new com.destroystokyo.paper.PaperVersionFetcher(); - } -+ -+ @Override -+ public byte[] serializeItem(ItemStack item) { -+ Preconditions.checkNotNull(item, "null cannot be serialized"); -+ Preconditions.checkArgument(item.getType() != Material.AIR, "air cannot be serialized"); -+ -+ return serializeNbtToBytes((item instanceof CraftItemStack ? ((CraftItemStack) item).handle : CraftItemStack.asNMSCopy(item)).save(new CompoundTag())); -+ } -+ -+ @Override -+ public ItemStack deserializeItem(byte[] data) { -+ Preconditions.checkNotNull(data, "null cannot be deserialized"); -+ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); -+ -+ CompoundTag compound = deserializeNbtFromBytes(data); -+ final int dataVersion = compound.getInt("DataVersion"); -+ compound = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue(); -+ return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of(compound)); -+ } -+ -+ private byte[] serializeNbtToBytes(CompoundTag compound) { -+ compound.putInt("DataVersion", getDataVersion()); -+ java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); -+ try { -+ net.minecraft.nbt.NbtIo.writeCompressed( -+ compound, -+ outputStream -+ ); -+ } catch (IOException ex) { -+ throw new RuntimeException(ex); -+ } -+ return outputStream.toByteArray(); -+ } -+ -+ private CompoundTag deserializeNbtFromBytes(byte[] data) { -+ CompoundTag compound; -+ try { -+ compound = net.minecraft.nbt.NbtIo.readCompressed( -+ new java.io.ByteArrayInputStream(data), net.minecraft.nbt.NbtAccounter.unlimitedHeap() -+ ); -+ } catch (IOException ex) { -+ throw new RuntimeException(ex); -+ } -+ int dataVersion = compound.getInt("DataVersion"); -+ Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); -+ return compound; -+ } - // Paper end - - /** diff --git a/patches/server/0340-Pillager-patrol-spawn-settings-and-per-player-option.patch b/patches/server/0340-Pillager-patrol-spawn-settings-and-per-player-option.patch new file mode 100644 index 000000000000..bcdbf3789261 --- /dev/null +++ b/patches/server/0340-Pillager-patrol-spawn-settings-and-per-player-option.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sat, 1 Feb 2020 16:50:39 +0100 +Subject: [PATCH] Pillager patrol spawn settings and per player options + +This adds config options for defining the spawn chance, spawn delay and +spawn start day as well as toggles for handling the spawn delay and +start day per player. (Based on the time played statistic) +When not per player it will use the Vanilla mechanic of one delay per +world and the world age for the start day. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 8678efd1b0ad2e6dbf49ed47d0dc959027762b40..7649367da5ff97815b710f4902b793a0f6b0e6fa 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -249,6 +249,7 @@ public class ServerPlayer extends Player { + public boolean wonGame; + private int containerUpdateDelay; // Paper - Configurable container update tick rate + public long loginTime; // Paper - Replace OfflinePlayer#getLastPlayed ++ public int patrolSpawnDelay; // Paper - Pillager patrol spawn settings and per player options + // Paper start - cancellable death event + public boolean queueHealthUpdatePacket; + public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; +diff --git a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java +index c7aea059de151cf8ae6e660785e176586bc23ff3..1c4dafdfb3898b5efe06d66792b1b0841ec5724d 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java +@@ -25,7 +25,7 @@ public class PatrolSpawner implements CustomSpawner { + + @Override + public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { +- if (world.paperConfig().entities.behavior.pillagerPatrols.disable) return 0; // Paper - Add option to disable pillager patrols ++ if (world.paperConfig().entities.behavior.pillagerPatrols.disable || world.paperConfig().entities.behavior.pillagerPatrols.spawnChance == 0) return 0; // Paper - Add option to disable pillager patrols & Pillager patrol spawn settings and per player options + if (!spawnMonsters) { + return 0; + } else if (!world.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) { +@@ -33,23 +33,51 @@ public class PatrolSpawner implements CustomSpawner { + } else { + RandomSource randomsource = world.random; + +- --this.nextTick; +- if (this.nextTick > 0) { ++ // Paper start - Pillager patrol spawn settings and per player options ++ // Random player selection moved up for per player spawning and configuration ++ int j = world.players().size(); ++ if (j < 1) { + return 0; ++ } ++ ++ net.minecraft.server.level.ServerPlayer entityhuman = world.players().get(randomsource.nextInt(j)); ++ if (entityhuman.isSpectator()) { ++ return 0; ++ } ++ ++ int patrolSpawnDelay; ++ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { ++ --entityhuman.patrolSpawnDelay; ++ patrolSpawnDelay = entityhuman.patrolSpawnDelay; + } else { +- this.nextTick += 12000 + randomsource.nextInt(1200); +- long i = world.getDayTime() / 24000L; ++ this.nextTick--; ++ patrolSpawnDelay = this.nextTick; ++ } ++ ++ if (patrolSpawnDelay > 0) { ++ return 0; ++ } else { ++ long days; ++ if (world.paperConfig().entities.behavior.pillagerPatrols.start.perPlayer) { ++ days = entityhuman.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.PLAY_TIME)) / 24000L; // PLAY_ONE_MINUTE is actually counting in ticks, a misnomer by Mojang ++ } else { ++ days = world.getDayTime() / 24000L; ++ } ++ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { ++ entityhuman.patrolSpawnDelay += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); ++ } else { ++ this.nextTick += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); ++ } + +- if (i >= 5L && world.isDay()) { +- if (randomsource.nextInt(5) != 0) { ++ if (days >= world.paperConfig().entities.behavior.pillagerPatrols.start.day && world.isDay()) { ++ if (randomsource.nextDouble() >= world.paperConfig().entities.behavior.pillagerPatrols.spawnChance) { ++ // Paper end - Pillager patrol spawn settings and per player options + return 0; + } else { +- int j = world.players().size(); + + if (j < 1) { + return 0; + } else { +- Player entityhuman = (Player) world.players().get(randomsource.nextInt(j)); + + if (entityhuman.isSpectator()) { + return 0; diff --git a/patches/server/0341-Pillager-patrol-spawn-settings-and-per-player-option.patch b/patches/server/0341-Pillager-patrol-spawn-settings-and-per-player-option.patch deleted file mode 100644 index 08c93f3fecb0..000000000000 --- a/patches/server/0341-Pillager-patrol-spawn-settings-and-per-player-option.patch +++ /dev/null @@ -1,96 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Sat, 1 Feb 2020 16:50:39 +0100 -Subject: [PATCH] Pillager patrol spawn settings and per player options - -This adds config options for defining the spawn chance, spawn delay and -spawn start day as well as toggles for handling the spawn delay and -start day per player. (Based on the time played statistic) -When not per player it will use the Vanilla mechanic of one delay per -world and the world age for the start day. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index d4d66bd96912592de499ef7e626f13de71714c93..b7111a76bac5f8d1bfa69a99b7c6bebf053c59bf 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -249,6 +249,7 @@ public class ServerPlayer extends Player { - public boolean wonGame; - private int containerUpdateDelay; // Paper - Configurable container update tick rate - public long loginTime; // Paper - Replace OfflinePlayer#getLastPlayed -+ public int patrolSpawnDelay; // Paper - Pillager patrol spawn settings and per player options - // Paper start - cancellable death event - public boolean queueHealthUpdatePacket; - public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; -diff --git a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java -index c7aea059de151cf8ae6e660785e176586bc23ff3..1c4dafdfb3898b5efe06d66792b1b0841ec5724d 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java -@@ -25,7 +25,7 @@ public class PatrolSpawner implements CustomSpawner { - - @Override - public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { -- if (world.paperConfig().entities.behavior.pillagerPatrols.disable) return 0; // Paper - Add option to disable pillager patrols -+ if (world.paperConfig().entities.behavior.pillagerPatrols.disable || world.paperConfig().entities.behavior.pillagerPatrols.spawnChance == 0) return 0; // Paper - Add option to disable pillager patrols & Pillager patrol spawn settings and per player options - if (!spawnMonsters) { - return 0; - } else if (!world.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) { -@@ -33,23 +33,51 @@ public class PatrolSpawner implements CustomSpawner { - } else { - RandomSource randomsource = world.random; - -- --this.nextTick; -- if (this.nextTick > 0) { -+ // Paper start - Pillager patrol spawn settings and per player options -+ // Random player selection moved up for per player spawning and configuration -+ int j = world.players().size(); -+ if (j < 1) { - return 0; -+ } -+ -+ net.minecraft.server.level.ServerPlayer entityhuman = world.players().get(randomsource.nextInt(j)); -+ if (entityhuman.isSpectator()) { -+ return 0; -+ } -+ -+ int patrolSpawnDelay; -+ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { -+ --entityhuman.patrolSpawnDelay; -+ patrolSpawnDelay = entityhuman.patrolSpawnDelay; - } else { -- this.nextTick += 12000 + randomsource.nextInt(1200); -- long i = world.getDayTime() / 24000L; -+ this.nextTick--; -+ patrolSpawnDelay = this.nextTick; -+ } -+ -+ if (patrolSpawnDelay > 0) { -+ return 0; -+ } else { -+ long days; -+ if (world.paperConfig().entities.behavior.pillagerPatrols.start.perPlayer) { -+ days = entityhuman.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.PLAY_TIME)) / 24000L; // PLAY_ONE_MINUTE is actually counting in ticks, a misnomer by Mojang -+ } else { -+ days = world.getDayTime() / 24000L; -+ } -+ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { -+ entityhuman.patrolSpawnDelay += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); -+ } else { -+ this.nextTick += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); -+ } - -- if (i >= 5L && world.isDay()) { -- if (randomsource.nextInt(5) != 0) { -+ if (days >= world.paperConfig().entities.behavior.pillagerPatrols.start.day && world.isDay()) { -+ if (randomsource.nextDouble() >= world.paperConfig().entities.behavior.pillagerPatrols.spawnChance) { -+ // Paper end - Pillager patrol spawn settings and per player options - return 0; - } else { -- int j = world.players().size(); - - if (j < 1) { - return 0; - } else { -- Player entityhuman = (Player) world.players().get(randomsource.nextInt(j)); - - if (entityhuman.isSpectator()) { - return 0; diff --git a/patches/server/0341-Remote-Connections-shouldn-t-hold-up-shutdown.patch b/patches/server/0341-Remote-Connections-shouldn-t-hold-up-shutdown.patch new file mode 100644 index 000000000000..6eb90f4536fd --- /dev/null +++ b/patches/server/0341-Remote-Connections-shouldn-t-hold-up-shutdown.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 31 Mar 2020 03:50:42 -0400 +Subject: [PATCH] Remote Connections shouldn't hold up shutdown + +Bugs in the connection logic appears to leave stale connections even, preventing shutdown + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 78775feb965d6eb98a1ff655ae44b9f0399ef9aa..28fe088d97bd5fbfcc29dcc7d2a657d54578b2be 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -390,11 +390,11 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + + if (this.rconThread != null) { +- this.rconThread.stop(); ++ this.rconThread.stopNonBlocking(); // Paper - don't wait for remote connections + } + + if (this.queryThreadGs4 != null) { +- this.queryThreadGs4.stop(); ++ // this.remoteStatusListener.stop(); // Paper - don't wait for remote connections + } + + System.exit(0); // CraftBukkit +diff --git a/src/main/java/net/minecraft/server/rcon/thread/RconThread.java b/src/main/java/net/minecraft/server/rcon/thread/RconThread.java +index 3bf60f640aa9fa4cabd2b3e5d3931e8467b9df24..8a493fa049dec741294098e3fee540a60f2accab 100644 +--- a/src/main/java/net/minecraft/server/rcon/thread/RconThread.java ++++ b/src/main/java/net/minecraft/server/rcon/thread/RconThread.java +@@ -107,6 +107,14 @@ public class RconThread extends GenericThread { + + this.clients.clear(); + } ++ // Paper start - don't wait for remote connections ++ public void stopNonBlocking() { ++ this.running = false; ++ for (RconClient client : this.clients) { ++ client.running = false; ++ } ++ } ++ // Paper end - don't wait for remote connections + + private void closeSocket(ServerSocket socket) { + LOGGER.debug("closeSocket: {}", (Object)socket); diff --git a/patches/server/0343-Do-not-allow-bees-to-load-chunks-for-beehives.patch b/patches/server/0342-Do-not-allow-bees-to-load-chunks-for-beehives.patch similarity index 100% rename from patches/server/0343-Do-not-allow-bees-to-load-chunks-for-beehives.patch rename to patches/server/0342-Do-not-allow-bees-to-load-chunks-for-beehives.patch diff --git a/patches/server/0342-Remote-Connections-shouldn-t-hold-up-shutdown.patch b/patches/server/0342-Remote-Connections-shouldn-t-hold-up-shutdown.patch deleted file mode 100644 index 5a220197f7e6..000000000000 --- a/patches/server/0342-Remote-Connections-shouldn-t-hold-up-shutdown.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 31 Mar 2020 03:50:42 -0400 -Subject: [PATCH] Remote Connections shouldn't hold up shutdown - -Bugs in the connection logic appears to leave stale connections even, preventing shutdown - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 47b12535d2cb146155044cc20b14bb5a432f83d5..567e642232cc698995def33d7cbe1f679ea7a871 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -390,11 +390,11 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - } - - if (this.rconThread != null) { -- this.rconThread.stop(); -+ this.rconThread.stopNonBlocking(); // Paper - don't wait for remote connections - } - - if (this.queryThreadGs4 != null) { -- this.queryThreadGs4.stop(); -+ // this.remoteStatusListener.stop(); // Paper - don't wait for remote connections - } - - System.exit(0); // CraftBukkit -diff --git a/src/main/java/net/minecraft/server/rcon/thread/RconThread.java b/src/main/java/net/minecraft/server/rcon/thread/RconThread.java -index 3bf60f640aa9fa4cabd2b3e5d3931e8467b9df24..8a493fa049dec741294098e3fee540a60f2accab 100644 ---- a/src/main/java/net/minecraft/server/rcon/thread/RconThread.java -+++ b/src/main/java/net/minecraft/server/rcon/thread/RconThread.java -@@ -107,6 +107,14 @@ public class RconThread extends GenericThread { - - this.clients.clear(); - } -+ // Paper start - don't wait for remote connections -+ public void stopNonBlocking() { -+ this.running = false; -+ for (RconClient client : this.clients) { -+ client.running = false; -+ } -+ } -+ // Paper end - don't wait for remote connections - - private void closeSocket(ServerSocket socket) { - LOGGER.debug("closeSocket: {}", (Object)socket); diff --git a/patches/server/0343-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch b/patches/server/0343-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch new file mode 100644 index 000000000000..a0fdf0378d62 --- /dev/null +++ b/patches/server/0343-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 01:42:39 -0400 +Subject: [PATCH] Prevent Double PlayerChunkMap adds crashing server + +Suspected case would be around the technique used in .stopRiding +Stack will identify any causer of this and warn instead of crashing. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index eff31fdd159dc7844bd3be3b769625a5704c2f17..2c954f8a91b9f50ce69eda475b22d4159b87d277 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1452,6 +1452,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + public void addEntity(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot ++ // Paper start - ignore and warn about illegal addEntity calls instead of crashing server ++ if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) { ++ LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName() ++ + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable()); ++ return; ++ } ++ // Paper end - ignore and warn about illegal addEntity calls instead of crashing server + if (!(entity instanceof EnderDragonPart)) { + EntityType entitytypes = entity.getType(); + int i = entitytypes.clientTrackingRange() * 16; +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 14958ce7c7cb1a55da6b6dd6c32a32c9346e866b..d2da284aa7284c5205e656c48262061980893be6 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2224,7 +2224,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + + public void onTrackingStart(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot +- ServerLevel.this.getChunkSource().addEntity(entity); ++ // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true + if (entity instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entity; + +@@ -2259,6 +2259,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + entity.updateDynamicGameEventListener(DynamicGameEventListener::add); + entity.inWorld = true; // CraftBukkit - Mark entity as in world + entity.valid = true; // CraftBukkit ++ ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server + // Paper start - Entity origin API + if (entity.getOriginVector() == null) { + entity.setOrigin(entity.getBukkitEntity().getLocation()); diff --git a/patches/server/0344-Don-t-tick-dead-players.patch b/patches/server/0344-Don-t-tick-dead-players.patch new file mode 100644 index 000000000000..7338d09f933e --- /dev/null +++ b/patches/server/0344-Don-t-tick-dead-players.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 17:16:48 -0400 +Subject: [PATCH] Don't tick dead players + +Causes sync chunk loads and who knows what all else. +This is safe because Spectators are skipped in unloaded chunks too in vanilla. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 7649367da5ff97815b710f4902b793a0f6b0e6fa..e695b5ee0b9ce5dad81a7f894ef1e7fd868f11eb 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -684,7 +684,7 @@ public class ServerPlayer extends Player { + + public void doTick() { + try { +- if (!this.isSpectator() || !this.touchingUnloadedChunk()) { ++ if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn) + super.tick(); + } + diff --git a/patches/server/0344-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch b/patches/server/0344-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch deleted file mode 100644 index 64a0e8c90de0..000000000000 --- a/patches/server/0344-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 2 Apr 2020 01:42:39 -0400 -Subject: [PATCH] Prevent Double PlayerChunkMap adds crashing server - -Suspected case would be around the technique used in .stopRiding -Stack will identify any causer of this and warn instead of crashing. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 5be7788ad5c9fb8158c70b7e5eb11b82e0fbeafe..1543bdec9bfa48bba65d03b04a0986698aa00bba 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1452,6 +1452,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - public void addEntity(Entity entity) { - org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot -+ // Paper start - ignore and warn about illegal addEntity calls instead of crashing server -+ if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) { -+ LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName() -+ + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable()); -+ return; -+ } -+ // Paper end - ignore and warn about illegal addEntity calls instead of crashing server - if (!(entity instanceof EnderDragonPart)) { - EntityType entitytypes = entity.getType(); - int i = entitytypes.clientTrackingRange() * 16; -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 14958ce7c7cb1a55da6b6dd6c32a32c9346e866b..d2da284aa7284c5205e656c48262061980893be6 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2224,7 +2224,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - - public void onTrackingStart(Entity entity) { - org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot -- ServerLevel.this.getChunkSource().addEntity(entity); -+ // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true - if (entity instanceof ServerPlayer) { - ServerPlayer entityplayer = (ServerPlayer) entity; - -@@ -2259,6 +2259,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - entity.updateDynamicGameEventListener(DynamicGameEventListener::add); - entity.inWorld = true; // CraftBukkit - Mark entity as in world - entity.valid = true; // CraftBukkit -+ ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server - // Paper start - Entity origin API - if (entity.getOriginVector() == null) { - entity.setOrigin(entity.getBukkitEntity().getLocation()); diff --git a/patches/server/0345-Dead-Player-s-shouldn-t-be-able-to-move.patch b/patches/server/0345-Dead-Player-s-shouldn-t-be-able-to-move.patch new file mode 100644 index 000000000000..8f01e2761de8 --- /dev/null +++ b/patches/server/0345-Dead-Player-s-shouldn-t-be-able-to-move.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 19:31:16 -0400 +Subject: [PATCH] Dead Player's shouldn't be able to move + +This fixes a lot of game state issues where packets were delayed for processing +due to 1.15's new queue but processed while dead. + +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 6ab6f520f2ccb60646660cb2990c5b91a0e91909..d00d6cf28212ed72f49953a198caa447aefc138c 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1152,7 +1152,7 @@ public abstract class Player extends LivingEntity { + + @Override + protected boolean isImmobile() { +- return super.isImmobile() || this.isSleeping(); ++ return super.isImmobile() || this.isSleeping() || this.isRemoved() || !valid; // Paper - player's who are dead or not in a world shouldn't move... + } + + @Override diff --git a/patches/server/0345-Don-t-tick-dead-players.patch b/patches/server/0345-Don-t-tick-dead-players.patch deleted file mode 100644 index fb1381bc7601..000000000000 --- a/patches/server/0345-Don-t-tick-dead-players.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 2 Apr 2020 17:16:48 -0400 -Subject: [PATCH] Don't tick dead players - -Causes sync chunk loads and who knows what all else. -This is safe because Spectators are skipped in unloaded chunks too in vanilla. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index b7111a76bac5f8d1bfa69a99b7c6bebf053c59bf..2cfe7e0e48ee8dcfb4bbcb2a8f6454d01d31d817 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -684,7 +684,7 @@ public class ServerPlayer extends Player { - - public void doTick() { - try { -- if (!this.isSpectator() || !this.touchingUnloadedChunk()) { -+ if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn) - super.tick(); - } - diff --git a/patches/server/0346-Dead-Player-s-shouldn-t-be-able-to-move.patch b/patches/server/0346-Dead-Player-s-shouldn-t-be-able-to-move.patch deleted file mode 100644 index 2bebf672b4b7..000000000000 --- a/patches/server/0346-Dead-Player-s-shouldn-t-be-able-to-move.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 2 Apr 2020 19:31:16 -0400 -Subject: [PATCH] Dead Player's shouldn't be able to move - -This fixes a lot of game state issues where packets were delayed for processing -due to 1.15's new queue but processed while dead. - -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 80fba3abe6f971da951cf5b613ac54364d641a81..bae09577905084f3e3d845b9cd3eaea9f46899d1 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1151,7 +1151,7 @@ public abstract class Player extends LivingEntity { - - @Override - protected boolean isImmobile() { -- return super.isImmobile() || this.isSleeping(); -+ return super.isImmobile() || this.isSleeping() || this.isRemoved() || !valid; // Paper - player's who are dead or not in a world shouldn't move... - } - - @Override diff --git a/patches/server/0346-Don-t-move-existing-players-to-world-spawn.patch b/patches/server/0346-Don-t-move-existing-players-to-world-spawn.patch new file mode 100644 index 000000000000..6d3a5af0c415 --- /dev/null +++ b/patches/server/0346-Don-t-move-existing-players-to-world-spawn.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 9 Apr 2020 21:20:33 -0400 +Subject: [PATCH] Don't move existing players to world spawn + +This can cause a nasty server lag the spawn chunks are not kept loaded +or they aren't finished loading yet, or if the world spawn radius is +larger than the keep loaded range. + +By skipping this, we avoid potential for a large spike on server start. + +== AT == +public net.minecraft.server.level.ServerPlayer fudgeSpawnLocation(Lnet/minecraft/server/level/ServerLevel;)V + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index e695b5ee0b9ce5dad81a7f894ef1e7fd868f11eb..b6698a65d778cea6b7dc9b8ebfceec2425c732b7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -337,7 +337,7 @@ public class ServerPlayer extends Player { + this.stats = server.getPlayerList().getPlayerStats(this); + this.advancements = server.getPlayerList().getPlayerAdvancements(this); + this.setMaxUpStep(1.0F); +- this.fudgeSpawnLocation(world); ++ // this.fudgeSpawnLocation(world); // Paper - Don't move existing players to world spawn + this.updateOptions(clientOptions); + + this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper +@@ -571,7 +571,7 @@ public class ServerPlayer extends Player { + position = Vec3.atCenterOf(world.getSharedSpawnPos()); + } + this.setLevel(world); +- this.setPos(position); ++ this.setPosRaw(position.x(), position.y(), position.z()); // Paper - don't register to chunks yet + } + this.gameMode.setLevel((ServerLevel) world); + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 60e50ce92414515c22f4d71e517504a7377c7e64..4530ba80f7d3983cf4ed9908eb1109c58aa425f2 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -226,6 +226,7 @@ public abstract class PlayerList { + // Paper start - Entity#getEntitySpawnReason + if (nbttagcompound == null) { + player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login ++ player.fudgeSpawnLocation(worldserver1); // Paper - Don't move existing players to world spawn + } + // Paper end - Entity#getEntitySpawnReason + player.setServerLevel(worldserver1); diff --git a/patches/server/0347-Don-t-move-existing-players-to-world-spawn.patch b/patches/server/0347-Don-t-move-existing-players-to-world-spawn.patch deleted file mode 100644 index 144c65f282c6..000000000000 --- a/patches/server/0347-Don-t-move-existing-players-to-world-spawn.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 9 Apr 2020 21:20:33 -0400 -Subject: [PATCH] Don't move existing players to world spawn - -This can cause a nasty server lag the spawn chunks are not kept loaded -or they aren't finished loading yet, or if the world spawn radius is -larger than the keep loaded range. - -By skipping this, we avoid potential for a large spike on server start. - -== AT == -public net.minecraft.server.level.ServerPlayer fudgeSpawnLocation(Lnet/minecraft/server/level/ServerLevel;)V - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 2cfe7e0e48ee8dcfb4bbcb2a8f6454d01d31d817..07b5a783ed9fec073f232f00c41f9e051f243efe 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -337,7 +337,7 @@ public class ServerPlayer extends Player { - this.stats = server.getPlayerList().getPlayerStats(this); - this.advancements = server.getPlayerList().getPlayerAdvancements(this); - this.setMaxUpStep(1.0F); -- this.fudgeSpawnLocation(world); -+ // this.fudgeSpawnLocation(world); // Paper - Don't move existing players to world spawn - this.updateOptions(clientOptions); - - this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper -@@ -571,7 +571,7 @@ public class ServerPlayer extends Player { - position = Vec3.atCenterOf(world.getSharedSpawnPos()); - } - this.setLevel(world); -- this.setPos(position); -+ this.setPosRaw(position.x(), position.y(), position.z()); // Paper - don't register to chunks yet - } - this.gameMode.setLevel((ServerLevel) world); - } -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 60e50ce92414515c22f4d71e517504a7377c7e64..4530ba80f7d3983cf4ed9908eb1109c58aa425f2 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -226,6 +226,7 @@ public abstract class PlayerList { - // Paper start - Entity#getEntitySpawnReason - if (nbttagcompound == null) { - player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login -+ player.fudgeSpawnLocation(worldserver1); // Paper - Don't move existing players to world spawn - } - // Paper end - Entity#getEntitySpawnReason - player.setServerLevel(worldserver1); diff --git a/patches/server/0348-Optimize-Pathfinding.patch b/patches/server/0347-Optimize-Pathfinding.patch similarity index 100% rename from patches/server/0348-Optimize-Pathfinding.patch rename to patches/server/0347-Optimize-Pathfinding.patch diff --git a/patches/server/0349-Reduce-Either-Optional-allocation.patch b/patches/server/0348-Reduce-Either-Optional-allocation.patch similarity index 100% rename from patches/server/0349-Reduce-Either-Optional-allocation.patch rename to patches/server/0348-Reduce-Either-Optional-allocation.patch diff --git a/patches/server/0350-Reduce-memory-footprint-of-CompoundTag.patch b/patches/server/0349-Reduce-memory-footprint-of-CompoundTag.patch similarity index 100% rename from patches/server/0350-Reduce-memory-footprint-of-CompoundTag.patch rename to patches/server/0349-Reduce-memory-footprint-of-CompoundTag.patch diff --git a/patches/server/0350-Prevent-opening-inventories-when-frozen.patch b/patches/server/0350-Prevent-opening-inventories-when-frozen.patch new file mode 100644 index 000000000000..b05b32474849 --- /dev/null +++ b/patches/server/0350-Prevent-opening-inventories-when-frozen.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Mon, 13 Apr 2020 07:31:44 +0100 +Subject: [PATCH] Prevent opening inventories when frozen + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index b6698a65d778cea6b7dc9b8ebfceec2425c732b7..347747282c77f5d1dde907b99cc2c8029675dc34 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -653,7 +653,7 @@ public class ServerPlayer extends Player { + containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate; + } + // Paper end - Configurable container update tick rate +- if (!this.level().isClientSide && !this.containerMenu.stillValid(this)) { ++ if (!this.level().isClientSide && this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - Prevent opening inventories when frozen + this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason + this.containerMenu = this.inventoryMenu; + } +@@ -1508,7 +1508,7 @@ public class ServerPlayer extends Player { + } else { + // CraftBukkit start + this.containerMenu = container; +- this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); ++ if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); // Paper - Prevent opening inventories when frozen + // CraftBukkit end + this.initMenu(container); + return OptionalInt.of(this.containerCounter); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index af6f8d11f0384c353332f5c5bb0967e518deccbe..7afbecd29f0a729e32c5cd50cb03d5404ba03d75 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -325,7 +325,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(container.getBukkitView().getTitle()); // Paper + + //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment +- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper ++ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen + player.containerMenu = container; + player.initMenu(container); + } +@@ -399,7 +399,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper + if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(inventory.getTitle()); // Paper + //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment +- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper ++ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen + player.containerMenu = container; + player.initMenu(container); + } diff --git a/patches/server/0351-Don-t-run-entity-collision-code-if-not-needed.patch b/patches/server/0351-Don-t-run-entity-collision-code-if-not-needed.patch new file mode 100644 index 000000000000..9f5acbc6c11b --- /dev/null +++ b/patches/server/0351-Don-t-run-entity-collision-code-if-not-needed.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 15 Apr 2020 17:56:07 -0700 +Subject: [PATCH] Don't run entity collision code if not needed + +Will not run if: +Max entity cramming is disabled and the max collisions per entity is less than or equal to 0. +Entity#isPushable() returns false, meaning all entities will not be able to collide with this +entity anyways. +The entity's current team collision rule causes them to NEVER collide. + +Co-authored-by: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index c68eba06077c6f6894f2cc2947f51d8d1c86ef94..6112571e2e0bc9fe66d68fd095d395168c817822 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3411,10 +3411,24 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (this.level().isClientSide()) { + this.level().getEntities(EntityTypeTest.forClass(net.minecraft.world.entity.player.Player.class), this.getBoundingBox(), EntitySelector.pushableBy(this)).forEach(this::doPush); + } else { ++ // Paper start - don't run getEntities if we're not going to use its result ++ if (!this.isPushable()) { ++ return; ++ } ++ net.minecraft.world.scores.Team team = this.getTeam(); ++ if (team != null && team.getCollisionRule() == net.minecraft.world.scores.Team.CollisionRule.NEVER) { ++ return; ++ } ++ ++ int i = this.level().getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); ++ if (i <= 0 && this.level().paperConfig().collisions.maxEntityCollisions <= 0) { ++ return; ++ } ++ // Paper end - don't run getEntities if we're not going to use its result + List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this)); + + if (!list.isEmpty()) { +- int i = this.level().getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); ++ // Paper - don't run getEntities if we're not going to use its result; moved up + + if (i > 0 && list.size() > i - 1 && this.random.nextInt(4) == 0) { + int j = 0; diff --git a/patches/server/0351-Prevent-opening-inventories-when-frozen.patch b/patches/server/0351-Prevent-opening-inventories-when-frozen.patch deleted file mode 100644 index 036d805389a7..000000000000 --- a/patches/server/0351-Prevent-opening-inventories-when-frozen.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Mon, 13 Apr 2020 07:31:44 +0100 -Subject: [PATCH] Prevent opening inventories when frozen - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 07b5a783ed9fec073f232f00c41f9e051f243efe..f6d266c5b279a22dee273cda109866b627e706da 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -653,7 +653,7 @@ public class ServerPlayer extends Player { - containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate; - } - // Paper end - Configurable container update tick rate -- if (!this.level().isClientSide && !this.containerMenu.stillValid(this)) { -+ if (!this.level().isClientSide && this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - Prevent opening inventories when frozen - this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason - this.containerMenu = this.inventoryMenu; - } -@@ -1508,7 +1508,7 @@ public class ServerPlayer extends Player { - } else { - // CraftBukkit start - this.containerMenu = container; -- this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); -+ if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); // Paper - Prevent opening inventories when frozen - // CraftBukkit end - this.initMenu(container); - return OptionalInt.of(this.containerCounter); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index af6f8d11f0384c353332f5c5bb0967e518deccbe..7afbecd29f0a729e32c5cd50cb03d5404ba03d75 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -325,7 +325,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(container.getBukkitView().getTitle()); // Paper - - //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment -- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper -+ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen - player.containerMenu = container; - player.initMenu(container); - } -@@ -399,7 +399,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper - if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(inventory.getTitle()); // Paper - //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment -- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper -+ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen - player.containerMenu = container; - player.initMenu(container); - } diff --git a/patches/server/0352-Don-t-run-entity-collision-code-if-not-needed.patch b/patches/server/0352-Don-t-run-entity-collision-code-if-not-needed.patch deleted file mode 100644 index 6b4ad7f09912..000000000000 --- a/patches/server/0352-Don-t-run-entity-collision-code-if-not-needed.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 15 Apr 2020 17:56:07 -0700 -Subject: [PATCH] Don't run entity collision code if not needed - -Will not run if: -Max entity cramming is disabled and the max collisions per entity is less than or equal to 0. -Entity#isPushable() returns false, meaning all entities will not be able to collide with this -entity anyways. -The entity's current team collision rule causes them to NEVER collide. - -Co-authored-by: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index bc51c6d8b39907d89a4f65cd7a8266df1fceab8c..1d75c7d7232dedaf6451c5a1e5bf224174d21041 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3398,10 +3398,24 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (this.level().isClientSide()) { - this.level().getEntities(EntityTypeTest.forClass(net.minecraft.world.entity.player.Player.class), this.getBoundingBox(), EntitySelector.pushableBy(this)).forEach(this::doPush); - } else { -+ // Paper start - don't run getEntities if we're not going to use its result -+ if (!this.isPushable()) { -+ return; -+ } -+ net.minecraft.world.scores.Team team = this.getTeam(); -+ if (team != null && team.getCollisionRule() == net.minecraft.world.scores.Team.CollisionRule.NEVER) { -+ return; -+ } -+ -+ int i = this.level().getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); -+ if (i <= 0 && this.level().paperConfig().collisions.maxEntityCollisions <= 0) { -+ return; -+ } -+ // Paper end - don't run getEntities if we're not going to use its result - List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this)); - - if (!list.isEmpty()) { -- int i = this.level().getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); -+ // Paper - don't run getEntities if we're not going to use its result; moved up - - if (i > 0 && list.size() > i - 1 && this.random.nextInt(4) == 0) { - int j = 0; diff --git a/patches/server/0352-Implement-Player-Client-Options-API.patch b/patches/server/0352-Implement-Player-Client-Options-API.patch new file mode 100644 index 000000000000..2a651ebbc32c --- /dev/null +++ b/patches/server/0352-Implement-Player-Client-Options-API.patch @@ -0,0 +1,194 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Mon, 20 Jan 2020 21:38:15 +0100 +Subject: [PATCH] Implement Player Client Options API + +== AT == +public net.minecraft.world.entity.player.Player DATA_PLAYER_MODE_CUSTOMISATION + +diff --git a/src/main/java/com/destroystokyo/paper/PaperSkinParts.java b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b6f4400df3d8ec7e06a996de54f8cabba57885e1 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java +@@ -0,0 +1,74 @@ ++package com.destroystokyo.paper; ++ ++import com.google.common.base.Objects; ++ ++import java.util.StringJoiner; ++ ++public class PaperSkinParts implements SkinParts { ++ ++ private final int raw; ++ ++ public PaperSkinParts(int raw) { ++ this.raw = raw; ++ } ++ ++ public boolean hasCapeEnabled() { ++ return (raw & 1) == 1; ++ } ++ ++ public boolean hasJacketEnabled() { ++ return (raw >> 1 & 1) == 1; ++ } ++ ++ public boolean hasLeftSleeveEnabled() { ++ return (raw >> 2 & 1) == 1; ++ } ++ ++ public boolean hasRightSleeveEnabled() { ++ return (raw >> 3 & 1) == 1; ++ } ++ ++ public boolean hasLeftPantsEnabled() { ++ return (raw >> 4 & 1) == 1; ++ } ++ ++ public boolean hasRightPantsEnabled() { ++ return (raw >> 5 & 1) == 1; ++ } ++ ++ public boolean hasHatsEnabled() { ++ return (raw >> 6 & 1) == 1; ++ } ++ ++ @Override ++ public int getRaw() { ++ return raw; ++ } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ PaperSkinParts that = (PaperSkinParts) o; ++ return raw == that.raw; ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hashCode(raw); ++ } ++ ++ @Override ++ public String toString() { ++ return new StringJoiner(", ", PaperSkinParts.class.getSimpleName() + "[", "]") ++ .add("raw=" + raw) ++ .add("cape=" + hasCapeEnabled()) ++ .add("jacket=" + hasJacketEnabled()) ++ .add("leftSleeve=" + hasLeftSleeveEnabled()) ++ .add("rightSleeve=" + hasRightSleeveEnabled()) ++ .add("leftPants=" + hasLeftPantsEnabled()) ++ .add("rightPants=" + hasRightPantsEnabled()) ++ .add("hats=" + hasHatsEnabled()) ++ .toString(); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 347747282c77f5d1dde907b99cc2c8029675dc34..eb0a0a9faacf3c7d879b435b637a9c8203319aa6 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -338,7 +338,7 @@ public class ServerPlayer extends Player { + this.advancements = server.getPlayerList().getPlayerAdvancements(this); + this.setMaxUpStep(1.0F); + // this.fudgeSpawnLocation(world); // Paper - Don't move existing players to world spawn +- this.updateOptions(clientOptions); ++ this.updateOptionsNoEvents(clientOptions); // Paper - don't call options events on login + + this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper + +@@ -2004,7 +2004,23 @@ public class ServerPlayer extends Player { + } + } + ++ // Paper start - Client option API ++ private java.util.Map, ?> getClientOptionMap(String locale, int viewDistance, com.destroystokyo.paper.ClientOption.ChatVisibility chatVisibility, boolean chatColors, com.destroystokyo.paper.PaperSkinParts skinParts, org.bukkit.inventory.MainHand mainHand, boolean allowsServerListing, boolean textFilteringEnabled) { ++ java.util.Map, Object> map = new java.util.HashMap<>(); ++ map.put(com.destroystokyo.paper.ClientOption.LOCALE, locale); ++ map.put(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE, viewDistance); ++ map.put(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY, chatVisibility); ++ map.put(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED, chatColors); ++ map.put(com.destroystokyo.paper.ClientOption.SKIN_PARTS, skinParts); ++ map.put(com.destroystokyo.paper.ClientOption.MAIN_HAND, mainHand); ++ map.put(com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS, allowsServerListing); ++ map.put(com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED, textFilteringEnabled); ++ return map; ++ } ++ // Paper end ++ + public void updateOptions(ClientInformation clientOptions) { ++ new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(getBukkitEntity(), getClientOptionMap(clientOptions.language(), clientOptions.viewDistance(), com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(clientOptions.chatVisibility().name()), clientOptions.chatColors(), new com.destroystokyo.paper.PaperSkinParts(clientOptions.modelCustomisation()), clientOptions.mainHand() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT, clientOptions.allowsListing(), clientOptions.textFilteringEnabled())).callEvent(); // Paper - settings event + // CraftBukkit start + if (this.getMainArm() != clientOptions.mainHand()) { + PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), this.getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); +@@ -2016,6 +2032,11 @@ public class ServerPlayer extends Player { + this.server.server.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerLocaleChangeEvent(this.getBukkitEntity(), this.language, clientOptions.language())); // Paper + } + // CraftBukkit end ++ // Paper start - don't call options events on login ++ updateOptionsNoEvents(clientOptions); ++ } ++ public void updateOptionsNoEvents(ClientInformation clientOptions) { ++ // Paper end + this.language = clientOptions.language(); + this.adventure$locale = java.util.Objects.requireNonNullElse(net.kyori.adventure.translation.Translator.parseLocale(this.language), java.util.Locale.US); // Paper + this.requestedViewDistance = clientOptions.viewDistance(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index c5ea6d424c8009c8afd791e58a75174291696d05..d6bddec130167af3d72555535045568ee941bb88 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -573,6 +573,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message); + } + } ++ ++ @Override ++ public T getClientOption(com.destroystokyo.paper.ClientOption type) { ++ if (com.destroystokyo.paper.ClientOption.SKIN_PARTS == type) { ++ return type.getType().cast(new com.destroystokyo.paper.PaperSkinParts(getHandle().getEntityData().get(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION))); ++ } else if (com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED == type) { ++ return type.getType().cast(getHandle().canChatInColor()); ++ } else if (com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY == type) { ++ return type.getType().cast(getHandle().getChatVisibility() == null ? com.destroystokyo.paper.ClientOption.ChatVisibility.UNKNOWN : com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(getHandle().getChatVisibility().name())); ++ } else if (com.destroystokyo.paper.ClientOption.LOCALE == type) { ++ return type.getType().cast(getLocale()); ++ } else if (com.destroystokyo.paper.ClientOption.MAIN_HAND == type) { ++ return type.getType().cast(getMainHand()); ++ } else if (com.destroystokyo.paper.ClientOption.VIEW_DISTANCE == type) { ++ return type.getType().cast(getClientViewDistance()); ++ } else if (com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS == type) { ++ return type.getType().cast(getHandle().allowsListing()); ++ } else if (com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED == type) { ++ return type.getType().cast(getHandle().isTextFilteringEnabled()); ++ } ++ throw new RuntimeException("Unknown settings type"); ++ } + // Paper end + + @Override +diff --git a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7f8b6462d2a1bbd39a870d2543bebc135f7eb45b +--- /dev/null ++++ b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java +@@ -0,0 +1,18 @@ ++package io.papermc.paper.world; ++ ++import com.destroystokyo.paper.ClientOption; ++import net.minecraft.world.entity.player.ChatVisiblity; ++import org.bukkit.Difficulty; ++import org.junit.jupiter.api.Assertions; ++import org.junit.jupiter.api.Test; ++ ++public class TranslationKeyTest { ++ ++ @Test ++ public void testChatVisibilityKeys() { ++ for (ClientOption.ChatVisibility chatVisibility : ClientOption.ChatVisibility.values()) { ++ if (chatVisibility == ClientOption.ChatVisibility.UNKNOWN) continue; ++ Assertions.assertEquals(ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey(), chatVisibility + "'s translation key doesn't match"); ++ } ++ } ++} diff --git a/patches/server/0353-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch b/patches/server/0353-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch new file mode 100644 index 000000000000..7fee0acf0f63 --- /dev/null +++ b/patches/server/0353-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 18 Apr 2020 15:59:41 -0400 +Subject: [PATCH] Don't crash if player is attempted to be removed from + untracked chunk. + +I suspect it deals with teleporting as it uses players current x/y/z + +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 76005b3c48bfa323a77781c20c63708eeaa66b2b..4e1618462840a1378dbe6492696c97544815edf2 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -285,8 +285,8 @@ public abstract class DistanceManager { + ObjectSet objectset = (ObjectSet) this.playersPerChunk.get(i); + if (objectset == null) return; // CraftBukkit - SPIGOT-6208 + +- objectset.remove(player); +- if (objectset.isEmpty()) { ++ if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully ++ if (objectset == null || objectset.isEmpty()) { // Paper + this.playersPerChunk.remove(i); + this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); + this.playerTicketManager.update(i, Integer.MAX_VALUE, false); diff --git a/patches/server/0353-Implement-Player-Client-Options-API.patch b/patches/server/0353-Implement-Player-Client-Options-API.patch deleted file mode 100644 index e86b98caa5f4..000000000000 --- a/patches/server/0353-Implement-Player-Client-Options-API.patch +++ /dev/null @@ -1,194 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MiniDigger -Date: Mon, 20 Jan 2020 21:38:15 +0100 -Subject: [PATCH] Implement Player Client Options API - -== AT == -public net.minecraft.world.entity.player.Player DATA_PLAYER_MODE_CUSTOMISATION - -diff --git a/src/main/java/com/destroystokyo/paper/PaperSkinParts.java b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b6f4400df3d8ec7e06a996de54f8cabba57885e1 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java -@@ -0,0 +1,74 @@ -+package com.destroystokyo.paper; -+ -+import com.google.common.base.Objects; -+ -+import java.util.StringJoiner; -+ -+public class PaperSkinParts implements SkinParts { -+ -+ private final int raw; -+ -+ public PaperSkinParts(int raw) { -+ this.raw = raw; -+ } -+ -+ public boolean hasCapeEnabled() { -+ return (raw & 1) == 1; -+ } -+ -+ public boolean hasJacketEnabled() { -+ return (raw >> 1 & 1) == 1; -+ } -+ -+ public boolean hasLeftSleeveEnabled() { -+ return (raw >> 2 & 1) == 1; -+ } -+ -+ public boolean hasRightSleeveEnabled() { -+ return (raw >> 3 & 1) == 1; -+ } -+ -+ public boolean hasLeftPantsEnabled() { -+ return (raw >> 4 & 1) == 1; -+ } -+ -+ public boolean hasRightPantsEnabled() { -+ return (raw >> 5 & 1) == 1; -+ } -+ -+ public boolean hasHatsEnabled() { -+ return (raw >> 6 & 1) == 1; -+ } -+ -+ @Override -+ public int getRaw() { -+ return raw; -+ } -+ -+ @Override -+ public boolean equals(Object o) { -+ if (this == o) return true; -+ if (o == null || getClass() != o.getClass()) return false; -+ PaperSkinParts that = (PaperSkinParts) o; -+ return raw == that.raw; -+ } -+ -+ @Override -+ public int hashCode() { -+ return Objects.hashCode(raw); -+ } -+ -+ @Override -+ public String toString() { -+ return new StringJoiner(", ", PaperSkinParts.class.getSimpleName() + "[", "]") -+ .add("raw=" + raw) -+ .add("cape=" + hasCapeEnabled()) -+ .add("jacket=" + hasJacketEnabled()) -+ .add("leftSleeve=" + hasLeftSleeveEnabled()) -+ .add("rightSleeve=" + hasRightSleeveEnabled()) -+ .add("leftPants=" + hasLeftPantsEnabled()) -+ .add("rightPants=" + hasRightPantsEnabled()) -+ .add("hats=" + hasHatsEnabled()) -+ .toString(); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 347747282c77f5d1dde907b99cc2c8029675dc34..eb0a0a9faacf3c7d879b435b637a9c8203319aa6 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -338,7 +338,7 @@ public class ServerPlayer extends Player { - this.advancements = server.getPlayerList().getPlayerAdvancements(this); - this.setMaxUpStep(1.0F); - // this.fudgeSpawnLocation(world); // Paper - Don't move existing players to world spawn -- this.updateOptions(clientOptions); -+ this.updateOptionsNoEvents(clientOptions); // Paper - don't call options events on login - - this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper - -@@ -2004,7 +2004,23 @@ public class ServerPlayer extends Player { - } - } - -+ // Paper start - Client option API -+ private java.util.Map, ?> getClientOptionMap(String locale, int viewDistance, com.destroystokyo.paper.ClientOption.ChatVisibility chatVisibility, boolean chatColors, com.destroystokyo.paper.PaperSkinParts skinParts, org.bukkit.inventory.MainHand mainHand, boolean allowsServerListing, boolean textFilteringEnabled) { -+ java.util.Map, Object> map = new java.util.HashMap<>(); -+ map.put(com.destroystokyo.paper.ClientOption.LOCALE, locale); -+ map.put(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE, viewDistance); -+ map.put(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY, chatVisibility); -+ map.put(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED, chatColors); -+ map.put(com.destroystokyo.paper.ClientOption.SKIN_PARTS, skinParts); -+ map.put(com.destroystokyo.paper.ClientOption.MAIN_HAND, mainHand); -+ map.put(com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS, allowsServerListing); -+ map.put(com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED, textFilteringEnabled); -+ return map; -+ } -+ // Paper end -+ - public void updateOptions(ClientInformation clientOptions) { -+ new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(getBukkitEntity(), getClientOptionMap(clientOptions.language(), clientOptions.viewDistance(), com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(clientOptions.chatVisibility().name()), clientOptions.chatColors(), new com.destroystokyo.paper.PaperSkinParts(clientOptions.modelCustomisation()), clientOptions.mainHand() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT, clientOptions.allowsListing(), clientOptions.textFilteringEnabled())).callEvent(); // Paper - settings event - // CraftBukkit start - if (this.getMainArm() != clientOptions.mainHand()) { - PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), this.getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); -@@ -2016,6 +2032,11 @@ public class ServerPlayer extends Player { - this.server.server.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerLocaleChangeEvent(this.getBukkitEntity(), this.language, clientOptions.language())); // Paper - } - // CraftBukkit end -+ // Paper start - don't call options events on login -+ updateOptionsNoEvents(clientOptions); -+ } -+ public void updateOptionsNoEvents(ClientInformation clientOptions) { -+ // Paper end - this.language = clientOptions.language(); - this.adventure$locale = java.util.Objects.requireNonNullElse(net.kyori.adventure.translation.Translator.parseLocale(this.language), java.util.Locale.US); // Paper - this.requestedViewDistance = clientOptions.viewDistance(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 473017107d80aa1467cd2f9400e8d48e39fab7e2..dcea3f827a79de3581adff51f34220a1d656e8e9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -567,6 +567,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message); - } - } -+ -+ @Override -+ public T getClientOption(com.destroystokyo.paper.ClientOption type) { -+ if (com.destroystokyo.paper.ClientOption.SKIN_PARTS == type) { -+ return type.getType().cast(new com.destroystokyo.paper.PaperSkinParts(getHandle().getEntityData().get(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION))); -+ } else if (com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED == type) { -+ return type.getType().cast(getHandle().canChatInColor()); -+ } else if (com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY == type) { -+ return type.getType().cast(getHandle().getChatVisibility() == null ? com.destroystokyo.paper.ClientOption.ChatVisibility.UNKNOWN : com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(getHandle().getChatVisibility().name())); -+ } else if (com.destroystokyo.paper.ClientOption.LOCALE == type) { -+ return type.getType().cast(getLocale()); -+ } else if (com.destroystokyo.paper.ClientOption.MAIN_HAND == type) { -+ return type.getType().cast(getMainHand()); -+ } else if (com.destroystokyo.paper.ClientOption.VIEW_DISTANCE == type) { -+ return type.getType().cast(getClientViewDistance()); -+ } else if (com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS == type) { -+ return type.getType().cast(getHandle().allowsListing()); -+ } else if (com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED == type) { -+ return type.getType().cast(getHandle().isTextFilteringEnabled()); -+ } -+ throw new RuntimeException("Unknown settings type"); -+ } - // Paper end - - @Override -diff --git a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7f8b6462d2a1bbd39a870d2543bebc135f7eb45b ---- /dev/null -+++ b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java -@@ -0,0 +1,18 @@ -+package io.papermc.paper.world; -+ -+import com.destroystokyo.paper.ClientOption; -+import net.minecraft.world.entity.player.ChatVisiblity; -+import org.bukkit.Difficulty; -+import org.junit.jupiter.api.Assertions; -+import org.junit.jupiter.api.Test; -+ -+public class TranslationKeyTest { -+ -+ @Test -+ public void testChatVisibilityKeys() { -+ for (ClientOption.ChatVisibility chatVisibility : ClientOption.ChatVisibility.values()) { -+ if (chatVisibility == ClientOption.ChatVisibility.UNKNOWN) continue; -+ Assertions.assertEquals(ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey(), chatVisibility + "'s translation key doesn't match"); -+ } -+ } -+} diff --git a/patches/server/0354-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch b/patches/server/0354-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch deleted file mode 100644 index 3f7dfaad06f2..000000000000 --- a/patches/server/0354-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 18 Apr 2020 15:59:41 -0400 -Subject: [PATCH] Don't crash if player is attempted to be removed from - untracked chunk. - -I suspect it deals with teleporting as it uses players current x/y/z - -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index 0bd05d7a8f2a388c28ddcf3f07db5b0648dddeea..1a9e6ed379c5fccfd82f4718c7fd2794978f63b4 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -285,8 +285,8 @@ public abstract class DistanceManager { - ObjectSet objectset = (ObjectSet) this.playersPerChunk.get(i); - if (objectset == null) return; // CraftBukkit - SPIGOT-6208 - -- objectset.remove(player); -- if (objectset.isEmpty()) { -+ if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully -+ if (objectset == null || objectset.isEmpty()) { // Paper - this.playersPerChunk.remove(i); - this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); - this.playerTicketManager.update(i, Integer.MAX_VALUE, false); diff --git a/patches/server/0354-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch b/patches/server/0354-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch new file mode 100644 index 000000000000..a42a520a66c6 --- /dev/null +++ b/patches/server/0354-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch @@ -0,0 +1,105 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 19 Apr 2020 00:05:46 -0400 +Subject: [PATCH] Fire PlayerJoinEvent when Player is actually ready + +For years, plugin developers have had to delay many things they do +inside of the PlayerJoinEvent by 1 tick to make it actually work. + +This all boiled down to 1 reason why: The event fired before the +player was fully ready and joined to the world! + +Additionally, if that player logged out on a vehicle, the event +fired before the vehicle was even loaded, so that plugins had no +access to the vehicle during this event either. + +This change finally fixes this issue, fully preparing the player +into the world as a fully ready entity, vehicle included. + +There should be no plugins that break because of this change, but might +improve consistency with other plugins instead. + +For example, if 2 plugins listens to this event, and the first one +teleported the player in the event, then the 2nd plugin actually +would be getting a valid player! + +This was very non deterministic. This change will ensure every plugin +receives a deterministic result, and should no longer require 1 tick +delays anymore. + +== AT == +public net.minecraft.server.level.ChunkMap addEntity(Lnet/minecraft/world/entity/Entity;)V + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 2c954f8a91b9f50ce69eda475b22d4159b87d277..5ef08156aa2e93e42eed586a4014c6208ddb20c1 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1459,6 +1459,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return; + } + // Paper end - ignore and warn about illegal addEntity calls instead of crashing server ++ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delay adding to tracker until after list packets + if (!(entity instanceof EnderDragonPart)) { + EntityType entitytypes = entity.getType(); + int i = entitytypes.clientTrackingRange() * 16; +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index eb0a0a9faacf3c7d879b435b637a9c8203319aa6..e5653695a3fbcd260ce44ca37291406a1033a3fa 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -267,6 +267,7 @@ public class ServerPlayer extends Player { + public double maxHealthCache; + public boolean joining = true; + public boolean sentListPacket = false; ++ public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready + public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent + // CraftBukkit end + public boolean isRealPlayer; // Paper +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 4530ba80f7d3983cf4ed9908eb1109c58aa425f2..16d43da0144eb3f77f639568a82e3c9d1bb7e260 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -295,6 +295,12 @@ public abstract class PlayerList { + this.playersByUUID.put(player.getUUID(), player); + // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer))); // CraftBukkit - replaced with loop below + ++ // Paper start - Fire PlayerJoinEvent when Player is actually ready; correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks ++ player.supressTrackerForLogin = true; ++ worldserver1.addNewPlayer(player); ++ this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below worldserver.addPlayerJoin(entityplayer); ++ mountSavedVehicle(player, worldserver1, nbttagcompound); ++ // Paper end - Fire PlayerJoinEvent when Player is actually ready + // CraftBukkit start + CraftPlayer bukkitPlayer = player.getBukkitEntity(); + +@@ -333,6 +339,8 @@ public abstract class PlayerList { + player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer1))); + } + player.sentListPacket = true; ++ player.supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready ++ ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now + // CraftBukkit end + + player.getEntityData().refresh(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn +@@ -355,6 +363,11 @@ public abstract class PlayerList { + playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect)); + } + ++ // Paper start - Fire PlayerJoinEvent when Player is actually ready; move vehicle into method so it can be called above - short circuit around that code ++ onPlayerJoinFinish(player, worldserver1, s1); ++ } ++ private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, CompoundTag nbttagcompound) { ++ // Paper end - Fire PlayerJoinEvent when Player is actually ready + if (nbttagcompound != null && nbttagcompound.contains("RootVehicle", 10)) { + CompoundTag nbttagcompound1 = nbttagcompound.getCompound("RootVehicle"); + // CraftBukkit start +@@ -403,6 +416,10 @@ public abstract class PlayerList { + } + } + ++ // Paper start - Fire PlayerJoinEvent when Player is actually ready ++ } ++ public void onPlayerJoinFinish(ServerPlayer player, ServerLevel worldserver1, String s1) { ++ // Paper end - Fire PlayerJoinEvent when Player is actually ready + player.initInventoryMenu(); + // CraftBukkit - Moved from above, added world + // Paper start - Configurable player collision; Add to collideRule team if needed diff --git a/patches/server/0355-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch b/patches/server/0355-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch deleted file mode 100644 index 05099c700e6b..000000000000 --- a/patches/server/0355-Fire-PlayerJoinEvent-when-Player-is-actually-ready.patch +++ /dev/null @@ -1,105 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 19 Apr 2020 00:05:46 -0400 -Subject: [PATCH] Fire PlayerJoinEvent when Player is actually ready - -For years, plugin developers have had to delay many things they do -inside of the PlayerJoinEvent by 1 tick to make it actually work. - -This all boiled down to 1 reason why: The event fired before the -player was fully ready and joined to the world! - -Additionally, if that player logged out on a vehicle, the event -fired before the vehicle was even loaded, so that plugins had no -access to the vehicle during this event either. - -This change finally fixes this issue, fully preparing the player -into the world as a fully ready entity, vehicle included. - -There should be no plugins that break because of this change, but might -improve consistency with other plugins instead. - -For example, if 2 plugins listens to this event, and the first one -teleported the player in the event, then the 2nd plugin actually -would be getting a valid player! - -This was very non deterministic. This change will ensure every plugin -receives a deterministic result, and should no longer require 1 tick -delays anymore. - -== AT == -public net.minecraft.server.level.ChunkMap addEntity(Lnet/minecraft/world/entity/Entity;)V - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 1543bdec9bfa48bba65d03b04a0986698aa00bba..7f61b2945e5174f89936041c334d4cb2e5cdb130 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1459,6 +1459,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - return; - } - // Paper end - ignore and warn about illegal addEntity calls instead of crashing server -+ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delay adding to tracker until after list packets - if (!(entity instanceof EnderDragonPart)) { - EntityType entitytypes = entity.getType(); - int i = entitytypes.clientTrackingRange() * 16; -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 6e3a791c4623ee904c3348ad7be5ede4b1657a12..e3413273076b697d560c927ea0e12f34722a79c1 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -267,6 +267,7 @@ public class ServerPlayer extends Player { - public double maxHealthCache; - public boolean joining = true; - public boolean sentListPacket = false; -+ public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready - public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent - // CraftBukkit end - public boolean isRealPlayer; // Paper -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 4530ba80f7d3983cf4ed9908eb1109c58aa425f2..16d43da0144eb3f77f639568a82e3c9d1bb7e260 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -295,6 +295,12 @@ public abstract class PlayerList { - this.playersByUUID.put(player.getUUID(), player); - // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer))); // CraftBukkit - replaced with loop below - -+ // Paper start - Fire PlayerJoinEvent when Player is actually ready; correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks -+ player.supressTrackerForLogin = true; -+ worldserver1.addNewPlayer(player); -+ this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below worldserver.addPlayerJoin(entityplayer); -+ mountSavedVehicle(player, worldserver1, nbttagcompound); -+ // Paper end - Fire PlayerJoinEvent when Player is actually ready - // CraftBukkit start - CraftPlayer bukkitPlayer = player.getBukkitEntity(); - -@@ -333,6 +339,8 @@ public abstract class PlayerList { - player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer1))); - } - player.sentListPacket = true; -+ player.supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready -+ ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now - // CraftBukkit end - - player.getEntityData().refresh(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn -@@ -355,6 +363,11 @@ public abstract class PlayerList { - playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect)); - } - -+ // Paper start - Fire PlayerJoinEvent when Player is actually ready; move vehicle into method so it can be called above - short circuit around that code -+ onPlayerJoinFinish(player, worldserver1, s1); -+ } -+ private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, CompoundTag nbttagcompound) { -+ // Paper end - Fire PlayerJoinEvent when Player is actually ready - if (nbttagcompound != null && nbttagcompound.contains("RootVehicle", 10)) { - CompoundTag nbttagcompound1 = nbttagcompound.getCompound("RootVehicle"); - // CraftBukkit start -@@ -403,6 +416,10 @@ public abstract class PlayerList { - } - } - -+ // Paper start - Fire PlayerJoinEvent when Player is actually ready -+ } -+ public void onPlayerJoinFinish(ServerPlayer player, ServerLevel worldserver1, String s1) { -+ // Paper end - Fire PlayerJoinEvent when Player is actually ready - player.initInventoryMenu(); - // CraftBukkit - Moved from above, added world - // Paper start - Configurable player collision; Add to collideRule team if needed diff --git a/patches/server/0355-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch b/patches/server/0355-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch new file mode 100644 index 000000000000..b8cdeec36258 --- /dev/null +++ b/patches/server/0355-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch @@ -0,0 +1,122 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: 2277 <38501234+2277@users.noreply.github.com> +Date: Tue, 31 Mar 2020 10:33:55 +0100 +Subject: [PATCH] Move player to spawn point if spawn in unloaded world + +If the playerdata contains an invalid world (missing, unloaded, invalid, +etc.), spawn the player at the spawn point of the main world. + +Co-authored-by: Wyatt Childers +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 16d43da0144eb3f77f639568a82e3c9d1bb7e260..54aba3118157f72491cb8c3d5fb5c63750ea6878 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -194,7 +194,7 @@ public abstract class PlayerList { + } + + CompoundTag nbttagcompound = this.load(player); +- ResourceKey resourcekey; ++ ResourceKey resourcekey = null; // Paper + // CraftBukkit start - Better rename detection + if (nbttagcompound != null && nbttagcompound.contains("bukkit")) { + CompoundTag bukkit = nbttagcompound.getCompound("bukkit"); +@@ -202,15 +202,42 @@ public abstract class PlayerList { + } + // CraftBukkit end + ++ // Paper start - move logic in Entity to here, to use bukkit supplied world UUID & reset to main world spawn if no valid world is found ++ boolean invalidPlayerWorld = false; ++ bukkitData: if (nbttagcompound != null) { ++ // The main way for bukkit worlds to store the world is the world UUID despite mojang adding custom worlds ++ final org.bukkit.World bWorld; ++ if (nbttagcompound.contains("WorldUUIDMost") && nbttagcompound.contains("WorldUUIDLeast")) { ++ bWorld = org.bukkit.Bukkit.getServer().getWorld(new UUID(nbttagcompound.getLong("WorldUUIDMost"), nbttagcompound.getLong("WorldUUIDLeast"))); ++ } else if (nbttagcompound.contains("world", net.minecraft.nbt.Tag.TAG_STRING)) { // Paper - legacy bukkit world name ++ bWorld = org.bukkit.Bukkit.getServer().getWorld(nbttagcompound.getString("world")); ++ } else { ++ break bukkitData; // if neither of the bukkit data points exist, proceed to the vanilla migration section ++ } ++ if (bWorld != null) { ++ resourcekey = ((CraftWorld) bWorld).getHandle().dimension(); ++ } else { ++ resourcekey = Level.OVERWORLD; ++ invalidPlayerWorld = true; ++ } ++ } ++ if (resourcekey == null) { // only run the vanilla logic if we haven't found a world from the bukkit data ++ // Below is the vanilla way of getting the dimension, this is for migration from vanilla servers ++ // Paper end + if (nbttagcompound != null) { + DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension"))); // CraftBukkit - decompile error + Logger logger = PlayerList.LOGGER; + + Objects.requireNonNull(logger); +- resourcekey = (ResourceKey) dataresult.resultOrPartial(logger::error).orElse(player.serverLevel().dimension()); // CraftBukkit - SPIGOT-7507: If no dimension, fall back to existing dimension loaded from "WorldUUID", which in turn defaults to World.OVERWORLD ++ // Paper start - reset to main world spawn if no valid world is found ++ final Optional> result = dataresult.resultOrPartial(logger::error); ++ invalidPlayerWorld = result.isEmpty(); ++ resourcekey = result.orElse(Level.OVERWORLD); ++ // Paper end + } else { +- resourcekey = player.serverLevel().dimension(); // CraftBukkit - SPIGOT-7507: If no dimension, fall back to existing dimension loaded from "WorldUUID", which in turn defaults to World.OVERWORLD ++ resourcekey = Level.OVERWORLD; // Paper - revert to vanilla default main world, this isn't an "invalid world" since no player data existed + } ++ } // Paper + + ResourceKey resourcekey1 = resourcekey; + ServerLevel worldserver = this.server.getLevel(resourcekey1); +@@ -219,6 +246,7 @@ public abstract class PlayerList { + if (worldserver == null) { + PlayerList.LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourcekey1); + worldserver1 = this.server.overworld(); ++ invalidPlayerWorld = true; // Paper - reset to main world if no world with parsed value is found + } else { + worldserver1 = worldserver; + } +@@ -226,6 +254,10 @@ public abstract class PlayerList { + // Paper start - Entity#getEntitySpawnReason + if (nbttagcompound == null) { + player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login ++ // Paper start - reset to main world spawn if first spawn or invalid world ++ } ++ if (nbttagcompound == null || invalidPlayerWorld) { ++ // Paper end - reset to main world spawn if first spawn or invalid world + player.fudgeSpawnLocation(worldserver1); // Paper - Don't move existing players to world spawn + } + // Paper end - Entity#getEntitySpawnReason +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index fa0f57779251ea785dfa4fe299c1505e46aa1446..828ffad0902d28c0dc86995f5f7270c54cd9d32c 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2273,27 +2273,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + // CraftBukkit end + +- // CraftBukkit start - Reset world +- if (this instanceof ServerPlayer) { +- Server server = Bukkit.getServer(); +- org.bukkit.World bworld = null; +- +- // TODO: Remove World related checks, replaced with WorldUID +- String worldName = nbt.getString("world"); +- +- if (nbt.contains("WorldUUIDMost") && nbt.contains("WorldUUIDLeast")) { +- UUID uid = new UUID(nbt.getLong("WorldUUIDMost"), nbt.getLong("WorldUUIDLeast")); +- bworld = server.getWorld(uid); +- } else { +- bworld = server.getWorld(worldName); +- } +- +- if (bworld == null) { +- bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getLevel(Level.OVERWORLD).getWorld(); +- } +- +- ((ServerPlayer) this).setLevel(bworld == null ? null : ((CraftWorld) bworld).getHandle()); +- } ++ // CraftBukkit start ++ // Paper - move world parsing/loading to PlayerList#placeNewPlayer + this.getBukkitEntity().readBukkitValues(nbt); + if (nbt.contains("Bukkit.invisible")) { + boolean bukkitInvisible = nbt.getBoolean("Bukkit.invisible"); diff --git a/patches/server/0356-Add-PlayerAttackEntityCooldownResetEvent.patch b/patches/server/0356-Add-PlayerAttackEntityCooldownResetEvent.patch new file mode 100644 index 000000000000..98ecc333d38d --- /dev/null +++ b/patches/server/0356-Add-PlayerAttackEntityCooldownResetEvent.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: nossr50 +Date: Thu, 26 Mar 2020 19:44:50 -0700 +Subject: [PATCH] Add PlayerAttackEntityCooldownResetEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 6112571e2e0bc9fe66d68fd095d395168c817822..8fff7d4c98e198bba0b4076807adc67476fcaf4b 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2203,7 +2203,16 @@ public abstract class LivingEntity extends Entity implements Attackable { + + EntityDamageEvent event = CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, hardHat, blocking, armor, resistance, magic, absorption); + if (damagesource.getEntity() instanceof net.minecraft.world.entity.player.Player) { +- ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired ++ // Paper start - PlayerAttackEntityCooldownResetEvent ++ if (damagesource.getEntity() instanceof ServerPlayer) { ++ ServerPlayer player = (ServerPlayer) damagesource.getEntity(); ++ if (new com.destroystokyo.paper.event.player.PlayerAttackEntityCooldownResetEvent(player.getBukkitEntity(), this.getBukkitEntity(), player.getAttackStrengthScale(0F)).callEvent()) { ++ player.resetAttackStrengthTicker(); ++ } ++ } else { ++ ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); ++ } ++ // Paper end - PlayerAttackEntityCooldownResetEvent + } + if (event.isCancelled()) { + return false; diff --git a/patches/server/0356-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch b/patches/server/0356-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch deleted file mode 100644 index 872f63a32384..000000000000 --- a/patches/server/0356-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch +++ /dev/null @@ -1,122 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: 2277 <38501234+2277@users.noreply.github.com> -Date: Tue, 31 Mar 2020 10:33:55 +0100 -Subject: [PATCH] Move player to spawn point if spawn in unloaded world - -If the playerdata contains an invalid world (missing, unloaded, invalid, -etc.), spawn the player at the spawn point of the main world. - -Co-authored-by: Wyatt Childers -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 16d43da0144eb3f77f639568a82e3c9d1bb7e260..54aba3118157f72491cb8c3d5fb5c63750ea6878 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -194,7 +194,7 @@ public abstract class PlayerList { - } - - CompoundTag nbttagcompound = this.load(player); -- ResourceKey resourcekey; -+ ResourceKey resourcekey = null; // Paper - // CraftBukkit start - Better rename detection - if (nbttagcompound != null && nbttagcompound.contains("bukkit")) { - CompoundTag bukkit = nbttagcompound.getCompound("bukkit"); -@@ -202,15 +202,42 @@ public abstract class PlayerList { - } - // CraftBukkit end - -+ // Paper start - move logic in Entity to here, to use bukkit supplied world UUID & reset to main world spawn if no valid world is found -+ boolean invalidPlayerWorld = false; -+ bukkitData: if (nbttagcompound != null) { -+ // The main way for bukkit worlds to store the world is the world UUID despite mojang adding custom worlds -+ final org.bukkit.World bWorld; -+ if (nbttagcompound.contains("WorldUUIDMost") && nbttagcompound.contains("WorldUUIDLeast")) { -+ bWorld = org.bukkit.Bukkit.getServer().getWorld(new UUID(nbttagcompound.getLong("WorldUUIDMost"), nbttagcompound.getLong("WorldUUIDLeast"))); -+ } else if (nbttagcompound.contains("world", net.minecraft.nbt.Tag.TAG_STRING)) { // Paper - legacy bukkit world name -+ bWorld = org.bukkit.Bukkit.getServer().getWorld(nbttagcompound.getString("world")); -+ } else { -+ break bukkitData; // if neither of the bukkit data points exist, proceed to the vanilla migration section -+ } -+ if (bWorld != null) { -+ resourcekey = ((CraftWorld) bWorld).getHandle().dimension(); -+ } else { -+ resourcekey = Level.OVERWORLD; -+ invalidPlayerWorld = true; -+ } -+ } -+ if (resourcekey == null) { // only run the vanilla logic if we haven't found a world from the bukkit data -+ // Below is the vanilla way of getting the dimension, this is for migration from vanilla servers -+ // Paper end - if (nbttagcompound != null) { - DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension"))); // CraftBukkit - decompile error - Logger logger = PlayerList.LOGGER; - - Objects.requireNonNull(logger); -- resourcekey = (ResourceKey) dataresult.resultOrPartial(logger::error).orElse(player.serverLevel().dimension()); // CraftBukkit - SPIGOT-7507: If no dimension, fall back to existing dimension loaded from "WorldUUID", which in turn defaults to World.OVERWORLD -+ // Paper start - reset to main world spawn if no valid world is found -+ final Optional> result = dataresult.resultOrPartial(logger::error); -+ invalidPlayerWorld = result.isEmpty(); -+ resourcekey = result.orElse(Level.OVERWORLD); -+ // Paper end - } else { -- resourcekey = player.serverLevel().dimension(); // CraftBukkit - SPIGOT-7507: If no dimension, fall back to existing dimension loaded from "WorldUUID", which in turn defaults to World.OVERWORLD -+ resourcekey = Level.OVERWORLD; // Paper - revert to vanilla default main world, this isn't an "invalid world" since no player data existed - } -+ } // Paper - - ResourceKey resourcekey1 = resourcekey; - ServerLevel worldserver = this.server.getLevel(resourcekey1); -@@ -219,6 +246,7 @@ public abstract class PlayerList { - if (worldserver == null) { - PlayerList.LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourcekey1); - worldserver1 = this.server.overworld(); -+ invalidPlayerWorld = true; // Paper - reset to main world if no world with parsed value is found - } else { - worldserver1 = worldserver; - } -@@ -226,6 +254,10 @@ public abstract class PlayerList { - // Paper start - Entity#getEntitySpawnReason - if (nbttagcompound == null) { - player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login -+ // Paper start - reset to main world spawn if first spawn or invalid world -+ } -+ if (nbttagcompound == null || invalidPlayerWorld) { -+ // Paper end - reset to main world spawn if first spawn or invalid world - player.fudgeSpawnLocation(worldserver1); // Paper - Don't move existing players to world spawn - } - // Paper end - Entity#getEntitySpawnReason -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 99f9e96509800e2246e729400a1ac6de522d456a..5bb40d4031ad7539dd98a1f4bf9bc7e3a2c6d978 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2269,27 +2269,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - // CraftBukkit end - -- // CraftBukkit start - Reset world -- if (this instanceof ServerPlayer) { -- Server server = Bukkit.getServer(); -- org.bukkit.World bworld = null; -- -- // TODO: Remove World related checks, replaced with WorldUID -- String worldName = nbt.getString("world"); -- -- if (nbt.contains("WorldUUIDMost") && nbt.contains("WorldUUIDLeast")) { -- UUID uid = new UUID(nbt.getLong("WorldUUIDMost"), nbt.getLong("WorldUUIDLeast")); -- bworld = server.getWorld(uid); -- } else { -- bworld = server.getWorld(worldName); -- } -- -- if (bworld == null) { -- bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getLevel(Level.OVERWORLD).getWorld(); -- } -- -- ((ServerPlayer) this).setLevel(bworld == null ? null : ((CraftWorld) bworld).getHandle()); -- } -+ // CraftBukkit start -+ // Paper - move world parsing/loading to PlayerList#placeNewPlayer - this.getBukkitEntity().readBukkitValues(nbt); - if (nbt.contains("Bukkit.invisible")) { - boolean bukkitInvisible = nbt.getBoolean("Bukkit.invisible"); diff --git a/patches/server/0357-Add-PlayerAttackEntityCooldownResetEvent.patch b/patches/server/0357-Add-PlayerAttackEntityCooldownResetEvent.patch deleted file mode 100644 index d4b6bd254abc..000000000000 --- a/patches/server/0357-Add-PlayerAttackEntityCooldownResetEvent.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: nossr50 -Date: Thu, 26 Mar 2020 19:44:50 -0700 -Subject: [PATCH] Add PlayerAttackEntityCooldownResetEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 1d75c7d7232dedaf6451c5a1e5bf224174d21041..72ce49ead51a846013c887b39b80abb7d141cb0f 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -2190,7 +2190,16 @@ public abstract class LivingEntity extends Entity implements Attackable { - - EntityDamageEvent event = CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, hardHat, blocking, armor, resistance, magic, absorption); - if (damagesource.getEntity() instanceof net.minecraft.world.entity.player.Player) { -- ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired -+ // Paper start - PlayerAttackEntityCooldownResetEvent -+ if (damagesource.getEntity() instanceof ServerPlayer) { -+ ServerPlayer player = (ServerPlayer) damagesource.getEntity(); -+ if (new com.destroystokyo.paper.event.player.PlayerAttackEntityCooldownResetEvent(player.getBukkitEntity(), this.getBukkitEntity(), player.getAttackStrengthScale(0F)).callEvent()) { -+ player.resetAttackStrengthTicker(); -+ } -+ } else { -+ ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); -+ } -+ // Paper end - PlayerAttackEntityCooldownResetEvent - } - if (event.isCancelled()) { - return false; diff --git a/patches/server/0357-Don-t-fire-BlockFade-on-worldgen-threads.patch b/patches/server/0357-Don-t-fire-BlockFade-on-worldgen-threads.patch new file mode 100644 index 000000000000..e5fdaf2d8fa3 --- /dev/null +++ b/patches/server/0357-Don-t-fire-BlockFade-on-worldgen-threads.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 23 Apr 2020 01:36:39 -0400 +Subject: [PATCH] Don't fire BlockFade on worldgen threads + + +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 da9cd2da60186b94f3b8a259c13b20f20e50fccb..65b2873ca8032a64a4968b7587637644df1aeca5 100644 +--- a/src/main/java/net/minecraft/world/level/block/FireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java +@@ -108,6 +108,7 @@ public class FireBlock extends BaseFireBlock { + @Override + public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { + // CraftBukkit start ++ if (!(world instanceof ServerLevel)) return this.canSurvive(state, world, pos) ? (BlockState) this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState(); // Paper - don't fire events in world generation + if (!this.canSurvive(state, world, pos)) { + // Suppress during worldgen + if (!(world instanceof Level)) { +@@ -123,7 +124,7 @@ public class FireBlock extends BaseFireBlock { + return blockState.getHandle(); + } + } +- return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); ++ return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); // Paper - don't fire events in world generation; diff on change, see "don't fire events in world generation" + // CraftBukkit end + } + diff --git a/patches/server/0359-Add-phantom-creative-and-insomniac-controls.patch b/patches/server/0358-Add-phantom-creative-and-insomniac-controls.patch similarity index 100% rename from patches/server/0359-Add-phantom-creative-and-insomniac-controls.patch rename to patches/server/0358-Add-phantom-creative-and-insomniac-controls.patch diff --git a/patches/server/0358-Don-t-fire-BlockFade-on-worldgen-threads.patch b/patches/server/0358-Don-t-fire-BlockFade-on-worldgen-threads.patch deleted file mode 100644 index 19015797ecdb..000000000000 --- a/patches/server/0358-Don-t-fire-BlockFade-on-worldgen-threads.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 23 Apr 2020 01:36:39 -0400 -Subject: [PATCH] Don't fire BlockFade on worldgen threads - - -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 c214a8a72c3fa27eaadc458b10a610e0de7937ec..5e5d0e5b41b5ed90a5f0109b231d468bbc566ae7 100644 ---- a/src/main/java/net/minecraft/world/level/block/FireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java -@@ -108,6 +108,7 @@ public class FireBlock extends BaseFireBlock { - @Override - public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - // CraftBukkit start -+ if (!(world instanceof ServerLevel)) return this.canSurvive(state, world, pos) ? (BlockState) this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState(); // Paper - don't fire events in world generation - if (!this.canSurvive(state, world, pos)) { - // Suppress during worldgen - if (!(world instanceof Level)) { -@@ -123,7 +124,7 @@ public class FireBlock extends BaseFireBlock { - return blockState.getHandle(); - } - } -- return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); -+ return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); // Paper - don't fire events in world generation; diff on change, see "don't fire events in world generation" - // CraftBukkit end - } - diff --git a/patches/server/0359-Fix-item-duplication-and-teleport-issues.patch b/patches/server/0359-Fix-item-duplication-and-teleport-issues.patch new file mode 100644 index 000000000000..9929b9285671 --- /dev/null +++ b/patches/server/0359-Fix-item-duplication-and-teleport-issues.patch @@ -0,0 +1,167 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 25 Apr 2020 06:46:35 -0400 +Subject: [PATCH] Fix item duplication and teleport issues + +This notably fixes the newest "Donkey Dupe", but also fixes a lot +of dupe bugs in general around nether portals and entity world transfer + +We also fix item duplication generically by anytime we clone an item +to drop it on the ground, destroy the source item. + +This avoid an itemstack ever existing twice in the world state pre +clean up stage. + +So even if something NEW comes up, it would be impossible to drop the +same item twice because the source was destroyed. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 828ffad0902d28c0dc86995f5f7270c54cd9d32c..7fc411c91b722d1f3494ef8eb37eeed7bbf10475 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2403,11 +2403,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } else { + // CraftBukkit start - Capture drops for death event + if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) { +- ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack)); ++ ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack)); // Paper - mirror so we can destroy it later + return null; + } + // CraftBukkit end +- ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY() + (double) yOffset, this.getZ(), stack); ++ ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original ++ stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe + + entityitem.setDefaultPickUpDelay(); + // CraftBukkit start +@@ -3206,6 +3207,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + @Nullable + public Entity teleportTo(ServerLevel worldserver, Vec3 location) { + // CraftBukkit end ++ // Paper start - Fix item duplication and teleport issues ++ if (!this.isAlive() || !this.valid) { ++ LOGGER.warn("Illegal Entity Teleport " + this + " to " + worldserver + ":" + location, new Throwable()); ++ return null; ++ } ++ // Paper end - Fix item duplication and teleport issues + if (this.level() instanceof ServerLevel && !this.isRemoved()) { + this.level().getProfiler().push("changeDimension"); + // CraftBukkit start +@@ -3232,6 +3239,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + // CraftBukkit end + + this.level().getProfiler().popPush("reloading"); ++ // Paper start - Fix item duplication and teleport issues ++ if (this instanceof Mob) { ++ ((Mob) this).dropLeash(true, true); // Paper drop lead ++ } ++ // Paper end - Fix item duplication and teleport issues + Entity entity = this.getType().create(worldserver); + + if (entity != null) { +@@ -3249,10 +3261,6 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + // CraftBukkit start - Forward the CraftEntity to the new entity + this.getBukkitEntity().setHandle(entity); + entity.bukkitEntity = this.getBukkitEntity(); +- +- if (this instanceof Mob) { +- ((Mob) this).dropLeash(true, false); // Unleash to prevent duping of leads. +- } + // CraftBukkit end + } + +@@ -3371,7 +3379,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + public boolean canChangeDimensions() { +- return !this.isPassenger() && !this.isVehicle(); ++ return !this.isPassenger() && !this.isVehicle() && isAlive() && valid; // Paper - Fix item duplication and teleport issues + } + + public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) { +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 8fff7d4c98e198bba0b4076807adc67476fcaf4b..035faf890c02ebd5bdbb430dc473e7a1bc7b9fd1 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1706,9 +1706,9 @@ public abstract class LivingEntity extends Entity implements Attackable { + // Paper start + org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(damageSource); + if (deathEvent == null || !deathEvent.isCancelled()) { +- if (this.deathScore >= 0 && entityliving != null) { +- entityliving.awardKillScore(this, this.deathScore, damageSource); +- } ++ // if (this.deathScore >= 0 && entityliving != null) { // Paper - Fix item duplication and teleport issues; moved to be run earlier in #dropAllDeathLoot before destroying the drop items in CraftEventFactory#callEntityDeathEvent ++ // entityliving.awardKillScore(this, this.deathScore, damageSource); ++ // } + // Paper start - clear equipment if event is not cancelled + if (this instanceof Mob) { + for (EquipmentSlot slot : this.clearedEquipmentSlots) { +@@ -1809,8 +1809,13 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.dropCustomDeathLoot(source, i, flag); + this.clearEquipmentSlots = prev; // Paper + } +- // CraftBukkit start - Call death event +- org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops); // Paper ++ // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops, () -> { ++ final LivingEntity entityliving = this.getKillCredit(); ++ if (this.deathScore >= 0 && entityliving != null) { ++ entityliving.awardKillScore(this, this.deathScore, source); ++ } ++ }); // Paper end + this.postDeathDropItems(deathEvent); // Paper + this.drops = new ArrayList<>(); + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index ddd512e1d7608ec051fb5adf6ec2c6bbb93f5a9d..4e3cf19b83410f3bcacd953a600bf0bed6e45450 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -624,7 +624,7 @@ public class ArmorStand extends LivingEntity { + for (i = 0; i < this.handItems.size(); ++i) { + itemstack = (ItemStack) this.handItems.get(i); + if (!itemstack.isEmpty()) { +- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops ++ this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe + this.handItems.set(i, ItemStack.EMPTY); + } + } +@@ -632,7 +632,7 @@ public class ArmorStand extends LivingEntity { + for (i = 0; i < this.armorItems.size(); ++i) { + itemstack = (ItemStack) this.armorItems.get(i); + if (!itemstack.isEmpty()) { +- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops ++ this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe + this.armorItems.set(i, ItemStack.EMPTY); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 2b342f81ae8bfefe2a240351f28fcafc40609a75..c9fbc54d7ba10da4f4c376e029b64fb0249171a3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -893,6 +893,11 @@ public class CraftEventFactory { + } + + public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops) { ++ // Paper start ++ return CraftEventFactory.callEntityDeathEvent(victim, drops, com.google.common.util.concurrent.Runnables.doNothing()); ++ } ++ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops, Runnable lootCheck) { ++ // Paper end + CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); + EntityDeathEvent event = new EntityDeathEvent(entity, drops, victim.getExpReward()); + populateFields(victim, event); // Paper - make cancellable +@@ -906,11 +911,13 @@ public class CraftEventFactory { + playDeathSound(victim, event); + // Paper end + victim.expToDrop = event.getDroppedExp(); ++ lootCheck.run(); // Paper - advancement triggers before destroying items + + for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { + if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; + +- world.dropItem(entity.getLocation(), stack); ++ world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS ++ if (stack instanceof CraftItemStack) stack.setAmount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe, but don't nuke bukkit stacks of manually added items + } + + return event; diff --git a/patches/server/0360-Fix-item-duplication-and-teleport-issues.patch b/patches/server/0360-Fix-item-duplication-and-teleport-issues.patch deleted file mode 100644 index f54692897caa..000000000000 --- a/patches/server/0360-Fix-item-duplication-and-teleport-issues.patch +++ /dev/null @@ -1,167 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 25 Apr 2020 06:46:35 -0400 -Subject: [PATCH] Fix item duplication and teleport issues - -This notably fixes the newest "Donkey Dupe", but also fixes a lot -of dupe bugs in general around nether portals and entity world transfer - -We also fix item duplication generically by anytime we clone an item -to drop it on the ground, destroy the source item. - -This avoid an itemstack ever existing twice in the world state pre -clean up stage. - -So even if something NEW comes up, it would be impossible to drop the -same item twice because the source was destroyed. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 5bb40d4031ad7539dd98a1f4bf9bc7e3a2c6d978..47094050283625e3b494f5ab6955a2f9c736388d 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2399,11 +2399,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } else { - // CraftBukkit start - Capture drops for death event - if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) { -- ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack)); -+ ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack)); // Paper - mirror so we can destroy it later - return null; - } - // CraftBukkit end -- ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY() + (double) yOffset, this.getZ(), stack); -+ ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original -+ stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe - - entityitem.setDefaultPickUpDelay(); - // CraftBukkit start -@@ -3203,6 +3204,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - @Nullable - public Entity teleportTo(ServerLevel worldserver, Vec3 location) { - // CraftBukkit end -+ // Paper start - Fix item duplication and teleport issues -+ if (!this.isAlive() || !this.valid) { -+ LOGGER.warn("Illegal Entity Teleport " + this + " to " + worldserver + ":" + location, new Throwable()); -+ return null; -+ } -+ // Paper end - Fix item duplication and teleport issues - if (this.level() instanceof ServerLevel && !this.isRemoved()) { - this.level().getProfiler().push("changeDimension"); - // CraftBukkit start -@@ -3229,6 +3236,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - // CraftBukkit end - - this.level().getProfiler().popPush("reloading"); -+ // Paper start - Fix item duplication and teleport issues -+ if (this instanceof Mob) { -+ ((Mob) this).dropLeash(true, true); // Paper drop lead -+ } -+ // Paper end - Fix item duplication and teleport issues - Entity entity = this.getType().create(worldserver); - - if (entity != null) { -@@ -3246,10 +3258,6 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - // CraftBukkit start - Forward the CraftEntity to the new entity - this.getBukkitEntity().setHandle(entity); - entity.bukkitEntity = this.getBukkitEntity(); -- -- if (this instanceof Mob) { -- ((Mob) this).dropLeash(true, false); // Unleash to prevent duping of leads. -- } - // CraftBukkit end - } - -@@ -3368,7 +3376,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - public boolean canChangeDimensions() { -- return !this.isPassenger() && !this.isVehicle(); -+ return !this.isPassenger() && !this.isVehicle() && isAlive() && valid; // Paper - Fix item duplication and teleport issues - } - - public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) { -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 72ce49ead51a846013c887b39b80abb7d141cb0f..b7bf58ddbc02989777c5c8dd58f6dd34acf57507 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1705,9 +1705,9 @@ public abstract class LivingEntity extends Entity implements Attackable { - // Paper start - org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(damageSource); - if (deathEvent == null || !deathEvent.isCancelled()) { -- if (this.deathScore >= 0 && entityliving != null) { -- entityliving.awardKillScore(this, this.deathScore, damageSource); -- } -+ // if (this.deathScore >= 0 && entityliving != null) { // Paper - Fix item duplication and teleport issues; moved to be run earlier in #dropAllDeathLoot before destroying the drop items in CraftEventFactory#callEntityDeathEvent -+ // entityliving.awardKillScore(this, this.deathScore, damageSource); -+ // } - // Paper start - clear equipment if event is not cancelled - if (this instanceof Mob) { - for (EquipmentSlot slot : this.clearedEquipmentSlots) { -@@ -1808,8 +1808,13 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.dropCustomDeathLoot(source, i, flag); - this.clearEquipmentSlots = prev; // Paper - } -- // CraftBukkit start - Call death event -- org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops); // Paper -+ // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment -+ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops, () -> { -+ final LivingEntity entityliving = this.getKillCredit(); -+ if (this.deathScore >= 0 && entityliving != null) { -+ entityliving.awardKillScore(this, this.deathScore, source); -+ } -+ }); // Paper end - this.postDeathDropItems(deathEvent); // Paper - this.drops = new ArrayList<>(); - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -index ddd512e1d7608ec051fb5adf6ec2c6bbb93f5a9d..4e3cf19b83410f3bcacd953a600bf0bed6e45450 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -624,7 +624,7 @@ public class ArmorStand extends LivingEntity { - for (i = 0; i < this.handItems.size(); ++i) { - itemstack = (ItemStack) this.handItems.get(i); - if (!itemstack.isEmpty()) { -- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops -+ this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe - this.handItems.set(i, ItemStack.EMPTY); - } - } -@@ -632,7 +632,7 @@ public class ArmorStand extends LivingEntity { - for (i = 0; i < this.armorItems.size(); ++i) { - itemstack = (ItemStack) this.armorItems.get(i); - if (!itemstack.isEmpty()) { -- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops -+ this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe - this.armorItems.set(i, ItemStack.EMPTY); - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 7e7427b9b5d44f1a5205011fa427f084bd34165c..77e1ff2725e015b76f6919f3f51d5d56a1e484a0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -893,6 +893,11 @@ public class CraftEventFactory { - } - - public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops) { -+ // Paper start -+ return CraftEventFactory.callEntityDeathEvent(victim, drops, com.google.common.util.concurrent.Runnables.doNothing()); -+ } -+ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops, Runnable lootCheck) { -+ // Paper end - CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); - EntityDeathEvent event = new EntityDeathEvent(entity, drops, victim.getExpReward()); - populateFields(victim, event); // Paper - make cancellable -@@ -906,11 +911,13 @@ public class CraftEventFactory { - playDeathSound(victim, event); - // Paper end - victim.expToDrop = event.getDroppedExp(); -+ lootCheck.run(); // Paper - advancement triggers before destroying items - - for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { - if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; - -- world.dropItem(entity.getLocation(), stack); -+ world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS -+ if (stack instanceof CraftItemStack) stack.setAmount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe, but don't nuke bukkit stacks of manually added items - } - - return event; diff --git a/patches/server/0361-Villager-Restocks-API.patch b/patches/server/0360-Villager-Restocks-API.patch similarity index 100% rename from patches/server/0361-Villager-Restocks-API.patch rename to patches/server/0360-Villager-Restocks-API.patch diff --git a/patches/server/0361-Validate-PickItem-Packet-and-kick-for-invalid.patch b/patches/server/0361-Validate-PickItem-Packet-and-kick-for-invalid.patch new file mode 100644 index 000000000000..daa58577919d --- /dev/null +++ b/patches/server/0361-Validate-PickItem-Packet-and-kick-for-invalid.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 2 May 2020 03:09:46 -0400 +Subject: [PATCH] Validate PickItem Packet and kick for invalid + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 89da9267c4c1a884e5483c423ccc68ed1d79259b..83cd7c4157b49f2cc86a44f5040f33585ced4270 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -867,7 +867,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + @Override + public void handlePickItem(ServerboundPickItemPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); +- this.player.getInventory().pickSlot(packet.getSlot()); ++ // Paper start - validate pick item position ++ if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.getInventory().items.size())) { ++ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); ++ this.disconnect("Invalid hotbar selection (Hacking?)"); ++ return; ++ } ++ this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed ++ // Paper end - validate pick item position + this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, this.player.getInventory().selected, this.player.getInventory().getItem(this.player.getInventory().selected))); + this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, packet.getSlot(), this.player.getInventory().getItem(packet.getSlot()))); + this.player.connection.send(new ClientboundSetCarriedItemPacket(this.player.getInventory().selected)); diff --git a/patches/server/0362-Expose-game-version.patch b/patches/server/0362-Expose-game-version.patch new file mode 100644 index 000000000000..3cbf0b5c30f3 --- /dev/null +++ b/patches/server/0362-Expose-game-version.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Fri, 1 May 2020 17:39:26 +0300 +Subject: [PATCH] Expose game version + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index ae424a5cbf8868aea9e11ffd565665c50aeb780e..356f737e4b224ed7d9692dcaf2a91617058e23ed 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -579,6 +579,13 @@ public final class CraftServer implements Server { + return this.bukkitVersion; + } + ++ // Paper start - expose game version ++ @Override ++ public String getMinecraftVersion() { ++ return console.getServerVersion(); ++ } ++ // Paper end ++ + @Override + public List getOnlinePlayers() { + return this.playerView; diff --git a/patches/server/0362-Validate-PickItem-Packet-and-kick-for-invalid.patch b/patches/server/0362-Validate-PickItem-Packet-and-kick-for-invalid.patch deleted file mode 100644 index 9d54c6260612..000000000000 --- a/patches/server/0362-Validate-PickItem-Packet-and-kick-for-invalid.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 2 May 2020 03:09:46 -0400 -Subject: [PATCH] Validate PickItem Packet and kick for invalid - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 79c7bce31d9c4e05afcb5cdbf06c4f6113d78ca3..36eb268d4aa4a8e7acb4498d27f2f7b75a519e2b 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -867,7 +867,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - @Override - public void handlePickItem(ServerboundPickItemPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); -- this.player.getInventory().pickSlot(packet.getSlot()); -+ // Paper start - validate pick item position -+ if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.getInventory().items.size())) { -+ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); -+ this.disconnect("Invalid hotbar selection (Hacking?)"); -+ return; -+ } -+ this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed -+ // Paper end - validate pick item position - this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, this.player.getInventory().selected, this.player.getInventory().getItem(this.player.getInventory().selected))); - this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, packet.getSlot(), this.player.getInventory().getItem(packet.getSlot()))); - this.player.connection.send(new ClientboundSetCarriedItemPacket(this.player.getInventory().selected)); diff --git a/patches/server/0363-Expose-game-version.patch b/patches/server/0363-Expose-game-version.patch deleted file mode 100644 index 1c3104600cf3..000000000000 --- a/patches/server/0363-Expose-game-version.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mark Vainomaa -Date: Fri, 1 May 2020 17:39:26 +0300 -Subject: [PATCH] Expose game version - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 69e25f23322704d054bef119f3581eee08cff55f..32833942d0383671a03463d07ed6e40171db53c6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -577,6 +577,13 @@ public final class CraftServer implements Server { - return this.bukkitVersion; - } - -+ // Paper start - expose game version -+ @Override -+ public String getMinecraftVersion() { -+ return console.getServerVersion(); -+ } -+ // Paper end -+ - @Override - public List getOnlinePlayers() { - return this.playerView; diff --git a/patches/server/0364-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch b/patches/server/0363-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch similarity index 100% rename from patches/server/0364-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch rename to patches/server/0363-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch diff --git a/patches/server/0364-misc-debugging-dumps.patch b/patches/server/0364-misc-debugging-dumps.patch new file mode 100644 index 000000000000..5495434652f7 --- /dev/null +++ b/patches/server/0364-misc-debugging-dumps.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 18 Feb 2021 20:23:28 +0000 +Subject: [PATCH] misc debugging dumps + + +diff --git a/src/main/java/io/papermc/paper/util/TraceUtil.java b/src/main/java/io/papermc/paper/util/TraceUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2d5494d2813b773e60ddba6790b750a9a08f21f8 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/TraceUtil.java +@@ -0,0 +1,18 @@ ++package io.papermc.paper.util; ++ ++import org.bukkit.Bukkit; ++ ++public final class TraceUtil { ++ ++ public static void dumpTraceForThread(Thread thread, String reason) { ++ Bukkit.getLogger().warning(thread.getName() + ": " + reason); ++ StackTraceElement[] trace = thread.getStackTrace(); ++ for (StackTraceElement traceElement : trace) { ++ Bukkit.getLogger().warning("\tat " + traceElement); ++ } ++ } ++ ++ public static void dumpTraceForThread(String reason) { ++ new Throwable(reason).printStackTrace(); ++ } ++} +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index 40ff3090fb17fb0f01a9b52639fb783ea57ce6b6..d39743d9626eb01c942194387dafb1106f13601b 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -341,7 +341,7 @@ public class Commands { + } catch (Exception exception) { + MutableComponent ichatmutablecomponent = Component.literal(exception.getMessage() == null ? exception.getClass().getName() : exception.getMessage()); + +- if (Commands.LOGGER.isDebugEnabled()) { ++ if (commandlistenerwrapper.getServer().isDebugging() || Commands.LOGGER.isDebugEnabled()) { // Paper - Debugging + Commands.LOGGER.error("Command exception: /{}", s, exception); + StackTraceElement[] astacktraceelement = exception.getStackTrace(); + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index e5ecca5ae9e37e4d4d6d2adfd5f487a869ab775d..390bc01c19d5fed7ed455ddf9823697469e0d7fe 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -904,6 +904,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Tue, 3 Mar 2020 05:26:40 +0000 +Subject: [PATCH] Prevent teleporting dead entities + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 83cd7c4157b49f2cc86a44f5040f33585ced4270..874a1998b6aaff9f4c7818481298ad51e1adc525 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1545,6 +1545,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { // Paper ++ // Paper start - Prevent teleporting dead entities ++ if (player.isRemoved()) { ++ LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); ++ if (server.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Attempt to teleport removed player"); ++ return; ++ } ++ // Paper end - Prevent teleporting dead entities + // CraftBukkit start + if (Float.isNaN(f)) { + f = 0; diff --git a/patches/server/0365-misc-debugging-dumps.patch b/patches/server/0365-misc-debugging-dumps.patch deleted file mode 100644 index ae8fbfc251a2..000000000000 --- a/patches/server/0365-misc-debugging-dumps.patch +++ /dev/null @@ -1,100 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Thu, 18 Feb 2021 20:23:28 +0000 -Subject: [PATCH] misc debugging dumps - - -diff --git a/src/main/java/io/papermc/paper/util/TraceUtil.java b/src/main/java/io/papermc/paper/util/TraceUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2d5494d2813b773e60ddba6790b750a9a08f21f8 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/TraceUtil.java -@@ -0,0 +1,18 @@ -+package io.papermc.paper.util; -+ -+import org.bukkit.Bukkit; -+ -+public final class TraceUtil { -+ -+ public static void dumpTraceForThread(Thread thread, String reason) { -+ Bukkit.getLogger().warning(thread.getName() + ": " + reason); -+ StackTraceElement[] trace = thread.getStackTrace(); -+ for (StackTraceElement traceElement : trace) { -+ Bukkit.getLogger().warning("\tat " + traceElement); -+ } -+ } -+ -+ public static void dumpTraceForThread(String reason) { -+ new Throwable(reason).printStackTrace(); -+ } -+} -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 40ff3090fb17fb0f01a9b52639fb783ea57ce6b6..d39743d9626eb01c942194387dafb1106f13601b 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -341,7 +341,7 @@ public class Commands { - } catch (Exception exception) { - MutableComponent ichatmutablecomponent = Component.literal(exception.getMessage() == null ? exception.getClass().getName() : exception.getMessage()); - -- if (Commands.LOGGER.isDebugEnabled()) { -+ if (commandlistenerwrapper.getServer().isDebugging() || Commands.LOGGER.isDebugEnabled()) { // Paper - Debugging - Commands.LOGGER.error("Command exception: /{}", s, exception); - StackTraceElement[] astacktraceelement = exception.getStackTrace(); - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 698601fc2c6e5d19a990bd3dcf0bc52e4c6efaea..4170f834c63807440c9bcda76cdcf93807de0eb1 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -904,6 +904,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sun, 20 Jun 2021 18:19:09 -0700 +Subject: [PATCH] Deobfuscate stacktraces in log messages, crash reports, and + etc. + + +diff --git a/build.gradle.kts b/build.gradle.kts +index eaaf9a9779f57ee048245899750bf7a1599b716f..450f7c03bdcc109938ba9b66328bdbb2c96c03c9 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -35,6 +35,7 @@ dependencies { + implementation("org.ow2.asm:asm-commons:9.5") + implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files + implementation("commons-lang:commons-lang:2.6") ++ implementation("net.fabricmc:mapping-io:0.5.0") // Paper - needed to read mappings for stacktrace deobfuscation + runtimeOnly("org.xerial:sqlite-jdbc:3.42.0.1") + runtimeOnly("com.mysql:mysql-connector-j:8.2.0") + runtimeOnly("com.lmax:disruptor:3.4.4") // Paper +@@ -124,6 +125,18 @@ tasks.check { + } + // Paper end + ++// Paper start - include reobf mappings in jar for stacktrace deobfuscation ++val includeMappings = tasks.register("includeMappings") { ++ inputJar.set(tasks.fixJarForReobf.flatMap { it.outputJar }) ++ mappings.set(tasks.reobfJar.flatMap { it.mappingsFile }) ++ mappingsDest.set("META-INF/mappings/reobf.tiny") ++} ++ ++tasks.reobfJar { ++ inputJar.set(includeMappings.flatMap { it.outputJar }) ++} ++// Paper end - include reobf mappings in jar for stacktrace deobfuscation ++ + tasks.test { + exclude("org/bukkit/craftbukkit/inventory/ItemStack*Test.class") + useJUnitPlatform() +diff --git a/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java b/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..66b6011ee3684695b2ab9292961c80bf2a420ee9 +--- /dev/null ++++ b/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java +@@ -0,0 +1,66 @@ ++package io.papermc.paper.logging; ++ ++import java.lang.invoke.MethodHandle; ++import java.lang.invoke.MethodHandles; ++import java.lang.invoke.VarHandle; ++import org.apache.logging.log4j.core.Core; ++import org.apache.logging.log4j.core.LogEvent; ++import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; ++import org.apache.logging.log4j.core.config.plugins.Plugin; ++import org.apache.logging.log4j.core.config.plugins.PluginFactory; ++import org.apache.logging.log4j.core.impl.Log4jLogEvent; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++@Plugin( ++ name = "StacktraceDeobfuscatingRewritePolicy", ++ category = Core.CATEGORY_NAME, ++ elementType = "rewritePolicy", ++ printObject = true ++) ++public final class StacktraceDeobfuscatingRewritePolicy implements RewritePolicy { ++ private static final MethodHandle DEOBFUSCATE_THROWABLE; ++ ++ static { ++ try { ++ final Class cls = Class.forName("io.papermc.paper.util.StacktraceDeobfuscator"); ++ final MethodHandles.Lookup lookup = MethodHandles.lookup(); ++ final VarHandle instanceHandle = lookup.findStaticVarHandle(cls, "INSTANCE", cls); ++ final Object deobfuscator = instanceHandle.get(); ++ DEOBFUSCATE_THROWABLE = lookup ++ .unreflect(cls.getDeclaredMethod("deobfuscateThrowable", Throwable.class)) ++ .bindTo(deobfuscator); ++ } catch (final ReflectiveOperationException ex) { ++ throw new IllegalStateException(ex); ++ } ++ } ++ ++ private StacktraceDeobfuscatingRewritePolicy() { ++ } ++ ++ @Override ++ public @NonNull LogEvent rewrite(final @NonNull LogEvent rewrite) { ++ final Throwable thrown = rewrite.getThrown(); ++ if (thrown != null) { ++ deobfuscateThrowable(thrown); ++ return new Log4jLogEvent.Builder(rewrite) ++ .setThrownProxy(null) ++ .build(); ++ } ++ return rewrite; ++ } ++ ++ private static void deobfuscateThrowable(final Throwable thrown) { ++ try { ++ DEOBFUSCATE_THROWABLE.invoke(thrown); ++ } catch (final Error e) { ++ throw e; ++ } catch (final Throwable e) { ++ throw new RuntimeException(e); ++ } ++ } ++ ++ @PluginFactory ++ public static @NonNull StacktraceDeobfuscatingRewritePolicy createPolicy() { ++ return new StacktraceDeobfuscatingRewritePolicy(); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +index 404a8fd128043527d23f22ee26f7c8c739f09089..9f24003fffee14592e5ef22e75ec9826428438e6 100644 +--- a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java ++++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +@@ -91,7 +91,7 @@ public class SyncLoadFinder { + + final JsonArray traces = new JsonArray(); + +- for (StackTraceElement element : pair.getFirst().stacktrace) { ++ for (StackTraceElement element : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(pair.getFirst().stacktrace)) { + traces.add(String.valueOf(element)); + } + +diff --git a/src/main/java/io/papermc/paper/util/ObfHelper.java b/src/main/java/io/papermc/paper/util/ObfHelper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e8ff684d8bd994c64ff34f20e1e0601b678244c1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/ObfHelper.java +@@ -0,0 +1,147 @@ ++package io.papermc.paper.util; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.InputStreamReader; ++import java.nio.charset.StandardCharsets; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Objects; ++import java.util.Set; ++import java.util.function.Function; ++import java.util.stream.Collectors; ++import net.fabricmc.mappingio.MappingReader; ++import net.fabricmc.mappingio.format.MappingFormat; ++import net.fabricmc.mappingio.tree.MappingTree; ++import net.fabricmc.mappingio.tree.MemoryMappingTree; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public enum ObfHelper { ++ INSTANCE; ++ ++ public static final String MOJANG_PLUS_YARN_NAMESPACE = "mojang+yarn"; ++ public static final String SPIGOT_NAMESPACE = "spigot"; ++ ++ private final @Nullable Map mappingsByObfName; ++ private final @Nullable Map mappingsByMojangName; ++ ++ ObfHelper() { ++ final @Nullable Set maps = loadMappingsIfPresent(); ++ if (maps != null) { ++ this.mappingsByObfName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::obfName, map -> map)); ++ this.mappingsByMojangName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::mojangName, map -> map)); ++ } else { ++ this.mappingsByObfName = null; ++ this.mappingsByMojangName = null; ++ } ++ } ++ ++ public @Nullable Map mappingsByObfName() { ++ return this.mappingsByObfName; ++ } ++ ++ public @Nullable Map mappingsByMojangName() { ++ return this.mappingsByMojangName; ++ } ++ ++ /** ++ * Attempts to get the obf name for a given class by its Mojang name. Will ++ * return the input string if mappings are not present. ++ * ++ * @param fullyQualifiedMojangName fully qualified class name (dotted) ++ * @return mapped or original fully qualified (dotted) class name ++ */ ++ public String reobfClassName(final String fullyQualifiedMojangName) { ++ if (this.mappingsByMojangName == null) { ++ return fullyQualifiedMojangName; ++ } ++ ++ final ClassMapping map = this.mappingsByMojangName.get(fullyQualifiedMojangName); ++ if (map == null) { ++ return fullyQualifiedMojangName; ++ } ++ ++ return map.obfName(); ++ } ++ ++ /** ++ * Attempts to get the Mojang name for a given class by its obf name. Will ++ * return the input string if mappings are not present. ++ * ++ * @param fullyQualifiedObfName fully qualified class name (dotted) ++ * @return mapped or original fully qualified (dotted) class name ++ */ ++ public String deobfClassName(final String fullyQualifiedObfName) { ++ if (this.mappingsByObfName == null) { ++ return fullyQualifiedObfName; ++ } ++ ++ final ClassMapping map = this.mappingsByObfName.get(fullyQualifiedObfName); ++ if (map == null) { ++ return fullyQualifiedObfName; ++ } ++ ++ return map.mojangName(); ++ } ++ ++ private static @Nullable Set loadMappingsIfPresent() { ++ try (final @Nullable InputStream mappingsInputStream = ObfHelper.class.getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny")) { ++ if (mappingsInputStream == null) { ++ return null; ++ } ++ final MemoryMappingTree tree = new MemoryMappingTree(); ++ MappingReader.read(new InputStreamReader(mappingsInputStream, StandardCharsets.UTF_8), MappingFormat.TINY_2_FILE, tree); ++ final Set classes = new HashSet<>(); ++ ++ final StringPool pool = new StringPool(); ++ for (final MappingTree.ClassMapping cls : tree.getClasses()) { ++ final Map methods = new HashMap<>(); ++ ++ for (final MappingTree.MethodMapping methodMapping : cls.getMethods()) { ++ methods.put( ++ pool.string(methodKey( ++ Objects.requireNonNull(methodMapping.getName(SPIGOT_NAMESPACE)), ++ Objects.requireNonNull(methodMapping.getDesc(SPIGOT_NAMESPACE)) ++ )), ++ pool.string(Objects.requireNonNull(methodMapping.getName(MOJANG_PLUS_YARN_NAMESPACE))) ++ ); ++ } ++ ++ final ClassMapping map = new ClassMapping( ++ Objects.requireNonNull(cls.getName(SPIGOT_NAMESPACE)).replace('/', '.'), ++ Objects.requireNonNull(cls.getName(MOJANG_PLUS_YARN_NAMESPACE)).replace('/', '.'), ++ Map.copyOf(methods) ++ ); ++ classes.add(map); ++ } ++ ++ return Set.copyOf(classes); ++ } catch (final IOException ex) { ++ System.err.println("Failed to load mappings for stacktrace deobfuscation."); ++ ex.printStackTrace(); ++ return null; ++ } ++ } ++ ++ public static String methodKey(final String obfName, final String obfDescriptor) { ++ return obfName + obfDescriptor; ++ } ++ ++ private static final class StringPool { ++ private final Map pool = new HashMap<>(); ++ ++ public String string(final String string) { ++ return this.pool.computeIfAbsent(string, Function.identity()); ++ } ++ } ++ ++ public record ClassMapping( ++ String obfName, ++ String mojangName, ++ Map methodsByObf ++ ) {} ++} +diff --git a/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java +new file mode 100644 +index 0000000000000000000000000000000000000000..eb910d4abf91488fa7cf1f5d47e0ee916c47f512 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java +@@ -0,0 +1,163 @@ ++package io.papermc.paper.util; ++ ++import io.papermc.paper.configuration.GlobalConfiguration; ++import it.unimi.dsi.fastutil.ints.IntArrayList; ++import it.unimi.dsi.fastutil.ints.IntList; ++import java.io.IOException; ++import java.io.InputStream; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.LinkedHashMap; ++import java.util.Map; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.objectweb.asm.ClassReader; ++import org.objectweb.asm.ClassVisitor; ++import org.objectweb.asm.Label; ++import org.objectweb.asm.MethodVisitor; ++import org.objectweb.asm.Opcodes; ++ ++@DefaultQualifier(NonNull.class) ++public enum StacktraceDeobfuscator { ++ INSTANCE; ++ ++ private final Map, Map> lineMapCache = Collections.synchronizedMap(new LinkedHashMap<>(128, 0.75f, true) { ++ @Override ++ protected boolean removeEldestEntry(final Map.Entry, Map> eldest) { ++ return this.size() > 127; ++ } ++ }); ++ ++ public void deobfuscateThrowable(final Throwable throwable) { ++ if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true ++ return; ++ } ++ ++ throwable.setStackTrace(this.deobfuscateStacktrace(throwable.getStackTrace())); ++ final Throwable cause = throwable.getCause(); ++ if (cause != null) { ++ this.deobfuscateThrowable(cause); ++ } ++ for (final Throwable suppressed : throwable.getSuppressed()) { ++ this.deobfuscateThrowable(suppressed); ++ } ++ } ++ ++ public StackTraceElement[] deobfuscateStacktrace(final StackTraceElement[] traceElements) { ++ if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true ++ return traceElements; ++ } ++ ++ final @Nullable Map mappings = ObfHelper.INSTANCE.mappingsByObfName(); ++ if (mappings == null || traceElements.length == 0) { ++ return traceElements; ++ } ++ final StackTraceElement[] result = new StackTraceElement[traceElements.length]; ++ for (int i = 0; i < traceElements.length; i++) { ++ final StackTraceElement element = traceElements[i]; ++ ++ final String className = element.getClassName(); ++ final String methodName = element.getMethodName(); ++ ++ final ObfHelper.ClassMapping classMapping = mappings.get(className); ++ if (classMapping == null) { ++ result[i] = element; ++ continue; ++ } ++ ++ final Class clazz; ++ try { ++ clazz = Class.forName(className); ++ } catch (final ClassNotFoundException ex) { ++ throw new RuntimeException(ex); ++ } ++ final @Nullable String methodKey = this.determineMethodForLine(clazz, element.getLineNumber()); ++ final @Nullable String mappedMethodName = methodKey == null ? null : classMapping.methodsByObf().get(methodKey); ++ ++ result[i] = new StackTraceElement( ++ element.getClassLoaderName(), ++ element.getModuleName(), ++ element.getModuleVersion(), ++ classMapping.mojangName(), ++ mappedMethodName != null ? mappedMethodName : methodName, ++ sourceFileName(classMapping.mojangName()), ++ element.getLineNumber() ++ ); ++ } ++ return result; ++ } ++ ++ private @Nullable String determineMethodForLine(final Class clazz, final int lineNumber) { ++ final Map lineMap = this.lineMapCache.computeIfAbsent(clazz, StacktraceDeobfuscator::buildLineMap); ++ for (final var entry : lineMap.entrySet()) { ++ final String methodKey = entry.getKey(); ++ final IntList lines = entry.getValue(); ++ for (int i = 0, linesSize = lines.size(); i < linesSize; i++) { ++ final int num = lines.getInt(i); ++ if (num == lineNumber) { ++ return methodKey; ++ } ++ } ++ } ++ return null; ++ } ++ ++ private static String sourceFileName(final String fullClassName) { ++ final int dot = fullClassName.lastIndexOf('.'); ++ final String className = dot == -1 ++ ? fullClassName ++ : fullClassName.substring(dot + 1); ++ final String rootClassName = className.split("\\$")[0]; ++ return rootClassName + ".java"; ++ } ++ ++ private static Map buildLineMap(final Class key) { ++ final Map lineMap = new HashMap<>(); ++ final class LineCollectingMethodVisitor extends MethodVisitor { ++ private final IntList lines = new IntArrayList(); ++ private final String name; ++ private final String descriptor; ++ ++ LineCollectingMethodVisitor(String name, String descriptor) { ++ super(Opcodes.ASM9); ++ this.name = name; ++ this.descriptor = descriptor; ++ } ++ ++ @Override ++ public void visitLineNumber(int line, Label start) { ++ super.visitLineNumber(line, start); ++ this.lines.add(line); ++ } ++ ++ @Override ++ public void visitEnd() { ++ super.visitEnd(); ++ lineMap.put(ObfHelper.methodKey(this.name, this.descriptor), this.lines); ++ } ++ } ++ final ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) { ++ @Override ++ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { ++ return new LineCollectingMethodVisitor(name, descriptor); ++ } ++ }; ++ try { ++ final @Nullable InputStream inputStream = StacktraceDeobfuscator.class.getClassLoader() ++ .getResourceAsStream(key.getName().replace('.', '/') + ".class"); ++ if (inputStream == null) { ++ throw new IllegalStateException("Could not find class file: " + key.getName()); ++ } ++ final byte[] classData; ++ try (inputStream) { ++ classData = inputStream.readAllBytes(); ++ } ++ final ClassReader reader = new ClassReader(classData); ++ reader.accept(classVisitor, 0); ++ } catch (final IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ return lineMap; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/util/TraceUtil.java b/src/main/java/io/papermc/paper/util/TraceUtil.java +index 2d5494d2813b773e60ddba6790b750a9a08f21f8..0b210bdf7c1f5962afbd44195af6f84f625635e3 100644 +--- a/src/main/java/io/papermc/paper/util/TraceUtil.java ++++ b/src/main/java/io/papermc/paper/util/TraceUtil.java +@@ -6,13 +6,20 @@ public final class TraceUtil { + + public static void dumpTraceForThread(Thread thread, String reason) { + Bukkit.getLogger().warning(thread.getName() + ": " + reason); +- StackTraceElement[] trace = thread.getStackTrace(); ++ StackTraceElement[] trace = StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()); + for (StackTraceElement traceElement : trace) { + Bukkit.getLogger().warning("\tat " + traceElement); + } + } + + public static void dumpTraceForThread(String reason) { +- new Throwable(reason).printStackTrace(); ++ final Throwable throwable = new Throwable(reason); ++ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(throwable); ++ throwable.printStackTrace(); ++ } ++ ++ public static void printStackTrace(Throwable thr) { ++ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(thr); ++ thr.printStackTrace(); + } + } +diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java +index a9a0248b1bd1ac454064e977b61f9b7d80962ff8..6f2452de76e8f5fcc1367066e0e753740764eb98 100644 +--- a/src/main/java/net/minecraft/CrashReport.java ++++ b/src/main/java/net/minecraft/CrashReport.java +@@ -34,6 +34,7 @@ public class CrashReport { + private final SystemReport systemReport = new SystemReport(); + + public CrashReport(String message, Throwable cause) { ++ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper + this.title = message; + this.exception = cause; + this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit +diff --git a/src/main/java/net/minecraft/CrashReportCategory.java b/src/main/java/net/minecraft/CrashReportCategory.java +index 52eb3176437113f9a0ff85d10ce5c2415e1b5570..b54ddd0ba0b001fbcb1838a838ca4890df936f1b 100644 +--- a/src/main/java/net/minecraft/CrashReportCategory.java ++++ b/src/main/java/net/minecraft/CrashReportCategory.java +@@ -104,6 +104,7 @@ public class CrashReportCategory { + } else { + this.stackTrace = new StackTraceElement[stackTraceElements.length - 3 - ignoredCallCount]; + System.arraycopy(stackTraceElements, 3 + ignoredCallCount, this.stackTrace, 0, this.stackTrace.length); ++ this.stackTrace = io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(this.stackTrace); // Paper + return this.stackTrace.length; + } + } +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 7648b889bc488197f32545e8c3671a54102c01ec..44e62675a2d612a8d727d9ce6db5fb85d1a0bcc8 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -75,13 +75,13 @@ public class Connection extends SimpleChannelInboundHandler> { + public static final AttributeKey> ATTRIBUTE_SERVERBOUND_PROTOCOL = AttributeKey.valueOf("serverbound_protocol"); + public static final AttributeKey> ATTRIBUTE_CLIENTBOUND_PROTOCOL = AttributeKey.valueOf("clientbound_protocol"); + public static final Supplier NETWORK_WORKER_GROUP = Suppliers.memoize(() -> { +- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build()); ++ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + public static final Supplier NETWORK_EPOLL_WORKER_GROUP = Suppliers.memoize(() -> { +- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build()); ++ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + public static final Supplier LOCAL_WORKER_GROUP = Suppliers.memoize(() -> { +- return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build()); ++ return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + private final PacketFlow receiving; + private final Queue> pendingActions = Queues.newConcurrentLinkedQueue(); +@@ -207,7 +207,7 @@ public class Connection extends SimpleChannelInboundHandler> { + + } + } +- if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) throwable.printStackTrace(); // Spigot ++ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) io.papermc.paper.util.TraceUtil.printStackTrace(throwable); // Spigot // Paper + } + + protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) { +diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java +index 61f05f34ca33837c643f2915e753ec3935a38314..85b8be8ffac0fb40e9cae0528271ed41473811c8 100644 +--- a/src/main/java/net/minecraft/network/PacketEncoder.java ++++ b/src/main/java/net/minecraft/network/PacketEncoder.java +@@ -47,7 +47,14 @@ public class PacketEncoder extends MessageToByteEncoder> { + + JvmProfiler.INSTANCE.onPacketSent(codecData.protocol(), i, channelHandlerContext.channel().remoteAddress(), k); + } catch (Throwable var13) { +- LOGGER.error("Packet encoding of packet ID {} threw (skippable? {})", i, packet.isSkippable(), var13); // Paper - Give proper error message ++ // Paper start - Give proper error message ++ String packetName = io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(packet.getClass().getName()); ++ if (packetName.contains(".")) { ++ packetName = packetName.substring(packetName.lastIndexOf(".") + 1); ++ } ++ ++ LOGGER.error("Packet encoding of packet {} (ID: {}) threw (skippable? {})", packetName, i, packet.isSkippable(), var13); ++ // Paper end + if (packet.isSkippable()) { + throw new SkipPacketException(var13); + } +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 28fe088d97bd5fbfcc29dcc7d2a657d54578b2be..c41c53ee3b1a8b5c2c41fc9846f557eeb4d10f9b 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -194,6 +194,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings")); + org.spigotmc.SpigotConfig.registerCommands(); + // Spigot end ++ io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc. + // Paper start - initialize global and world-defaults configuration + this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess()); + this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); +diff --git a/src/main/java/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +index d130f843975236018df4fa2ccc3ca6aaca7a06b8..76f31845fe50200d09e5ab6a6c08da00444414ad 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +@@ -133,7 +133,7 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis + ServerConfigurationPacketListenerImpl.LOGGER.error("Couldn't place player in world", exception); + // Paper start - Debugging + if (MinecraftServer.getServer().isDebugging()) { +- exception.printStackTrace(); ++ io.papermc.paper.util.TraceUtil.printStackTrace(exception); + } + // Paper end - Debugging + this.connection.send(new ClientboundDisconnectPacket(ServerConfigurationPacketListenerImpl.DISCONNECT_REASON_INVALID_DATA)); +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index 187b2cf175ba5cea94158d29b53993dc5a7c5b94..3b6bafb242d2623c15f26acdacd036478c7dc214 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -52,10 +52,10 @@ public class ServerConnectionListener { + + private static final Logger LOGGER = LogUtils.getLogger(); + public static final Supplier SERVER_EVENT_GROUP = Suppliers.memoize(() -> { +- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build()); ++ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + public static final Supplier SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize(() -> { +- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build()); ++ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + final MinecraftServer server; + public volatile boolean running; +diff --git a/src/main/java/net/minecraft/server/players/OldUsersConverter.java b/src/main/java/net/minecraft/server/players/OldUsersConverter.java +index 86c88e81e275d52576122a5083b419e64cb011fc..45d4638d568ea2aee805aa1b0542533019e5870d 100644 +--- a/src/main/java/net/minecraft/server/players/OldUsersConverter.java ++++ b/src/main/java/net/minecraft/server/players/OldUsersConverter.java +@@ -357,7 +357,7 @@ public class OldUsersConverter { + try { + root = NbtIo.readCompressed(new java.io.FileInputStream(file5), NbtAccounter.unlimitedHeap()); + } catch (Exception exception) { +- exception.printStackTrace(); ++ io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Paper + com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent + } + +@@ -371,7 +371,7 @@ public class OldUsersConverter { + try { + NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2)); + } catch (Exception exception) { +- exception.printStackTrace(); ++ io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Paper + com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent + } + } +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 5403fc4fa2ed2526d2e67c230a46dd2a75e017be..af757309cb46af6df07872f7596b66df6d6f18d7 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -592,7 +592,7 @@ public class LevelChunk extends ChunkAccess { + + " (" + getBlockState(blockposition) + ") where there was no entity tile!\n" + + "Chunk coordinates: " + (this.chunkPos.x * 16) + "," + (this.chunkPos.z * 16) + + "\nWorld: " + level.getLevel().dimension().location()); +- e.printStackTrace(); ++ io.papermc.paper.util.TraceUtil.printStackTrace(e); + ServerInternalException.reportInternalException(e); + // Paper end - ServerExceptionEvent + // CraftBukkit end +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +index 3c1992e212a6d6f1db4d5b807b38d71913619fc0..9c1aff17aabd062640e3f451a2ef8c50a7c62f10 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +@@ -40,9 +40,9 @@ public class CraftAsyncScheduler extends CraftScheduler { + + private final ThreadPoolExecutor executor = new ThreadPoolExecutor( + 4, Integer.MAX_VALUE,30L, TimeUnit.SECONDS, new SynchronousQueue<>(), +- new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); ++ new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper + private final Executor management = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() +- .setNameFormat("Craft Async Scheduler Management Thread").build()); ++ .setNameFormat("Craft Async Scheduler Management Thread").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper + private final List temp = new ArrayList<>(); + + CraftAsyncScheduler() { +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 017c2eadbc5dfec155c21b5d8a80f0b1e380398e..b1ac7338fa632611ea8332044b09070f78f8f5f1 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -102,7 +102,7 @@ public class WatchdogThread extends Thread + log.log(Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity"); + log.log(Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated"); + log.log(Level.SEVERE, org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getMessage()); +- for (StackTraceElement stack : org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace()) { ++ for (StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace())) { // Paper + log.log( Level.SEVERE, "\t\t" + stack ); + } + } +@@ -172,7 +172,7 @@ public class WatchdogThread extends Thread + } + log.log( Level.SEVERE, "\tStack:" ); + // +- for ( StackTraceElement stack : thread.getStackTrace() ) ++ for ( StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()) ) // Paper + { + log.log( Level.SEVERE, "\t\t" + stack ); + } +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index ea4e2161c0bd43884055cc6b8d70b2139f70e720..4e2ca9162450c1f54b7ab95a63c1bad8efe81a06 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -30,10 +30,14 @@ + + + ++ ++ ++ ++ + + + +- ++ + + + diff --git a/patches/server/0366-Prevent-teleporting-dead-entities.patch b/patches/server/0366-Prevent-teleporting-dead-entities.patch deleted file mode 100644 index 67228f41f020..000000000000 --- a/patches/server/0366-Prevent-teleporting-dead-entities.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Tue, 3 Mar 2020 05:26:40 +0000 -Subject: [PATCH] Prevent teleporting dead entities - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 36eb268d4aa4a8e7acb4498d27f2f7b75a519e2b..2e239328e31318e873973f86422d7aa469ee61e1 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1545,6 +1545,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { // Paper -+ // Paper start - Prevent teleporting dead entities -+ if (player.isRemoved()) { -+ LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); -+ if (server.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Attempt to teleport removed player"); -+ return; -+ } -+ // Paper end - Prevent teleporting dead entities - // CraftBukkit start - if (Float.isNaN(f)) { - f = 0; diff --git a/patches/server/0367-Deobfuscate-stacktraces-in-log-messages-crash-report.patch b/patches/server/0367-Deobfuscate-stacktraces-in-log-messages-crash-report.patch deleted file mode 100644 index 29fda00c16a1..000000000000 --- a/patches/server/0367-Deobfuscate-stacktraces-in-log-messages-crash-report.patch +++ /dev/null @@ -1,681 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sun, 20 Jun 2021 18:19:09 -0700 -Subject: [PATCH] Deobfuscate stacktraces in log messages, crash reports, and - etc. - - -diff --git a/build.gradle.kts b/build.gradle.kts -index eaaf9a9779f57ee048245899750bf7a1599b716f..450f7c03bdcc109938ba9b66328bdbb2c96c03c9 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -35,6 +35,7 @@ dependencies { - implementation("org.ow2.asm:asm-commons:9.5") - implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files - implementation("commons-lang:commons-lang:2.6") -+ implementation("net.fabricmc:mapping-io:0.5.0") // Paper - needed to read mappings for stacktrace deobfuscation - runtimeOnly("org.xerial:sqlite-jdbc:3.42.0.1") - runtimeOnly("com.mysql:mysql-connector-j:8.2.0") - runtimeOnly("com.lmax:disruptor:3.4.4") // Paper -@@ -124,6 +125,18 @@ tasks.check { - } - // Paper end - -+// Paper start - include reobf mappings in jar for stacktrace deobfuscation -+val includeMappings = tasks.register("includeMappings") { -+ inputJar.set(tasks.fixJarForReobf.flatMap { it.outputJar }) -+ mappings.set(tasks.reobfJar.flatMap { it.mappingsFile }) -+ mappingsDest.set("META-INF/mappings/reobf.tiny") -+} -+ -+tasks.reobfJar { -+ inputJar.set(includeMappings.flatMap { it.outputJar }) -+} -+// Paper end - include reobf mappings in jar for stacktrace deobfuscation -+ - tasks.test { - exclude("org/bukkit/craftbukkit/inventory/ItemStack*Test.class") - useJUnitPlatform() -diff --git a/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java b/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java -new file mode 100644 -index 0000000000000000000000000000000000000000..66b6011ee3684695b2ab9292961c80bf2a420ee9 ---- /dev/null -+++ b/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java -@@ -0,0 +1,66 @@ -+package io.papermc.paper.logging; -+ -+import java.lang.invoke.MethodHandle; -+import java.lang.invoke.MethodHandles; -+import java.lang.invoke.VarHandle; -+import org.apache.logging.log4j.core.Core; -+import org.apache.logging.log4j.core.LogEvent; -+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; -+import org.apache.logging.log4j.core.config.plugins.Plugin; -+import org.apache.logging.log4j.core.config.plugins.PluginFactory; -+import org.apache.logging.log4j.core.impl.Log4jLogEvent; -+import org.checkerframework.checker.nullness.qual.NonNull; -+ -+@Plugin( -+ name = "StacktraceDeobfuscatingRewritePolicy", -+ category = Core.CATEGORY_NAME, -+ elementType = "rewritePolicy", -+ printObject = true -+) -+public final class StacktraceDeobfuscatingRewritePolicy implements RewritePolicy { -+ private static final MethodHandle DEOBFUSCATE_THROWABLE; -+ -+ static { -+ try { -+ final Class cls = Class.forName("io.papermc.paper.util.StacktraceDeobfuscator"); -+ final MethodHandles.Lookup lookup = MethodHandles.lookup(); -+ final VarHandle instanceHandle = lookup.findStaticVarHandle(cls, "INSTANCE", cls); -+ final Object deobfuscator = instanceHandle.get(); -+ DEOBFUSCATE_THROWABLE = lookup -+ .unreflect(cls.getDeclaredMethod("deobfuscateThrowable", Throwable.class)) -+ .bindTo(deobfuscator); -+ } catch (final ReflectiveOperationException ex) { -+ throw new IllegalStateException(ex); -+ } -+ } -+ -+ private StacktraceDeobfuscatingRewritePolicy() { -+ } -+ -+ @Override -+ public @NonNull LogEvent rewrite(final @NonNull LogEvent rewrite) { -+ final Throwable thrown = rewrite.getThrown(); -+ if (thrown != null) { -+ deobfuscateThrowable(thrown); -+ return new Log4jLogEvent.Builder(rewrite) -+ .setThrownProxy(null) -+ .build(); -+ } -+ return rewrite; -+ } -+ -+ private static void deobfuscateThrowable(final Throwable thrown) { -+ try { -+ DEOBFUSCATE_THROWABLE.invoke(thrown); -+ } catch (final Error e) { -+ throw e; -+ } catch (final Throwable e) { -+ throw new RuntimeException(e); -+ } -+ } -+ -+ @PluginFactory -+ public static @NonNull StacktraceDeobfuscatingRewritePolicy createPolicy() { -+ return new StacktraceDeobfuscatingRewritePolicy(); -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java -index 404a8fd128043527d23f22ee26f7c8c739f09089..9f24003fffee14592e5ef22e75ec9826428438e6 100644 ---- a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java -+++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java -@@ -91,7 +91,7 @@ public class SyncLoadFinder { - - final JsonArray traces = new JsonArray(); - -- for (StackTraceElement element : pair.getFirst().stacktrace) { -+ for (StackTraceElement element : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(pair.getFirst().stacktrace)) { - traces.add(String.valueOf(element)); - } - -diff --git a/src/main/java/io/papermc/paper/util/ObfHelper.java b/src/main/java/io/papermc/paper/util/ObfHelper.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e8ff684d8bd994c64ff34f20e1e0601b678244c1 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/ObfHelper.java -@@ -0,0 +1,147 @@ -+package io.papermc.paper.util; -+ -+import java.io.IOException; -+import java.io.InputStream; -+import java.io.InputStreamReader; -+import java.nio.charset.StandardCharsets; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Objects; -+import java.util.Set; -+import java.util.function.Function; -+import java.util.stream.Collectors; -+import net.fabricmc.mappingio.MappingReader; -+import net.fabricmc.mappingio.format.MappingFormat; -+import net.fabricmc.mappingio.tree.MappingTree; -+import net.fabricmc.mappingio.tree.MemoryMappingTree; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public enum ObfHelper { -+ INSTANCE; -+ -+ public static final String MOJANG_PLUS_YARN_NAMESPACE = "mojang+yarn"; -+ public static final String SPIGOT_NAMESPACE = "spigot"; -+ -+ private final @Nullable Map mappingsByObfName; -+ private final @Nullable Map mappingsByMojangName; -+ -+ ObfHelper() { -+ final @Nullable Set maps = loadMappingsIfPresent(); -+ if (maps != null) { -+ this.mappingsByObfName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::obfName, map -> map)); -+ this.mappingsByMojangName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::mojangName, map -> map)); -+ } else { -+ this.mappingsByObfName = null; -+ this.mappingsByMojangName = null; -+ } -+ } -+ -+ public @Nullable Map mappingsByObfName() { -+ return this.mappingsByObfName; -+ } -+ -+ public @Nullable Map mappingsByMojangName() { -+ return this.mappingsByMojangName; -+ } -+ -+ /** -+ * Attempts to get the obf name for a given class by its Mojang name. Will -+ * return the input string if mappings are not present. -+ * -+ * @param fullyQualifiedMojangName fully qualified class name (dotted) -+ * @return mapped or original fully qualified (dotted) class name -+ */ -+ public String reobfClassName(final String fullyQualifiedMojangName) { -+ if (this.mappingsByMojangName == null) { -+ return fullyQualifiedMojangName; -+ } -+ -+ final ClassMapping map = this.mappingsByMojangName.get(fullyQualifiedMojangName); -+ if (map == null) { -+ return fullyQualifiedMojangName; -+ } -+ -+ return map.obfName(); -+ } -+ -+ /** -+ * Attempts to get the Mojang name for a given class by its obf name. Will -+ * return the input string if mappings are not present. -+ * -+ * @param fullyQualifiedObfName fully qualified class name (dotted) -+ * @return mapped or original fully qualified (dotted) class name -+ */ -+ public String deobfClassName(final String fullyQualifiedObfName) { -+ if (this.mappingsByObfName == null) { -+ return fullyQualifiedObfName; -+ } -+ -+ final ClassMapping map = this.mappingsByObfName.get(fullyQualifiedObfName); -+ if (map == null) { -+ return fullyQualifiedObfName; -+ } -+ -+ return map.mojangName(); -+ } -+ -+ private static @Nullable Set loadMappingsIfPresent() { -+ try (final @Nullable InputStream mappingsInputStream = ObfHelper.class.getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny")) { -+ if (mappingsInputStream == null) { -+ return null; -+ } -+ final MemoryMappingTree tree = new MemoryMappingTree(); -+ MappingReader.read(new InputStreamReader(mappingsInputStream, StandardCharsets.UTF_8), MappingFormat.TINY_2_FILE, tree); -+ final Set classes = new HashSet<>(); -+ -+ final StringPool pool = new StringPool(); -+ for (final MappingTree.ClassMapping cls : tree.getClasses()) { -+ final Map methods = new HashMap<>(); -+ -+ for (final MappingTree.MethodMapping methodMapping : cls.getMethods()) { -+ methods.put( -+ pool.string(methodKey( -+ Objects.requireNonNull(methodMapping.getName(SPIGOT_NAMESPACE)), -+ Objects.requireNonNull(methodMapping.getDesc(SPIGOT_NAMESPACE)) -+ )), -+ pool.string(Objects.requireNonNull(methodMapping.getName(MOJANG_PLUS_YARN_NAMESPACE))) -+ ); -+ } -+ -+ final ClassMapping map = new ClassMapping( -+ Objects.requireNonNull(cls.getName(SPIGOT_NAMESPACE)).replace('/', '.'), -+ Objects.requireNonNull(cls.getName(MOJANG_PLUS_YARN_NAMESPACE)).replace('/', '.'), -+ Map.copyOf(methods) -+ ); -+ classes.add(map); -+ } -+ -+ return Set.copyOf(classes); -+ } catch (final IOException ex) { -+ System.err.println("Failed to load mappings for stacktrace deobfuscation."); -+ ex.printStackTrace(); -+ return null; -+ } -+ } -+ -+ public static String methodKey(final String obfName, final String obfDescriptor) { -+ return obfName + obfDescriptor; -+ } -+ -+ private static final class StringPool { -+ private final Map pool = new HashMap<>(); -+ -+ public String string(final String string) { -+ return this.pool.computeIfAbsent(string, Function.identity()); -+ } -+ } -+ -+ public record ClassMapping( -+ String obfName, -+ String mojangName, -+ Map methodsByObf -+ ) {} -+} -diff --git a/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java -new file mode 100644 -index 0000000000000000000000000000000000000000..eb910d4abf91488fa7cf1f5d47e0ee916c47f512 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java -@@ -0,0 +1,163 @@ -+package io.papermc.paper.util; -+ -+import io.papermc.paper.configuration.GlobalConfiguration; -+import it.unimi.dsi.fastutil.ints.IntArrayList; -+import it.unimi.dsi.fastutil.ints.IntList; -+import java.io.IOException; -+import java.io.InputStream; -+import java.util.Collections; -+import java.util.HashMap; -+import java.util.LinkedHashMap; -+import java.util.Map; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+import org.objectweb.asm.ClassReader; -+import org.objectweb.asm.ClassVisitor; -+import org.objectweb.asm.Label; -+import org.objectweb.asm.MethodVisitor; -+import org.objectweb.asm.Opcodes; -+ -+@DefaultQualifier(NonNull.class) -+public enum StacktraceDeobfuscator { -+ INSTANCE; -+ -+ private final Map, Map> lineMapCache = Collections.synchronizedMap(new LinkedHashMap<>(128, 0.75f, true) { -+ @Override -+ protected boolean removeEldestEntry(final Map.Entry, Map> eldest) { -+ return this.size() > 127; -+ } -+ }); -+ -+ public void deobfuscateThrowable(final Throwable throwable) { -+ if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true -+ return; -+ } -+ -+ throwable.setStackTrace(this.deobfuscateStacktrace(throwable.getStackTrace())); -+ final Throwable cause = throwable.getCause(); -+ if (cause != null) { -+ this.deobfuscateThrowable(cause); -+ } -+ for (final Throwable suppressed : throwable.getSuppressed()) { -+ this.deobfuscateThrowable(suppressed); -+ } -+ } -+ -+ public StackTraceElement[] deobfuscateStacktrace(final StackTraceElement[] traceElements) { -+ if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true -+ return traceElements; -+ } -+ -+ final @Nullable Map mappings = ObfHelper.INSTANCE.mappingsByObfName(); -+ if (mappings == null || traceElements.length == 0) { -+ return traceElements; -+ } -+ final StackTraceElement[] result = new StackTraceElement[traceElements.length]; -+ for (int i = 0; i < traceElements.length; i++) { -+ final StackTraceElement element = traceElements[i]; -+ -+ final String className = element.getClassName(); -+ final String methodName = element.getMethodName(); -+ -+ final ObfHelper.ClassMapping classMapping = mappings.get(className); -+ if (classMapping == null) { -+ result[i] = element; -+ continue; -+ } -+ -+ final Class clazz; -+ try { -+ clazz = Class.forName(className); -+ } catch (final ClassNotFoundException ex) { -+ throw new RuntimeException(ex); -+ } -+ final @Nullable String methodKey = this.determineMethodForLine(clazz, element.getLineNumber()); -+ final @Nullable String mappedMethodName = methodKey == null ? null : classMapping.methodsByObf().get(methodKey); -+ -+ result[i] = new StackTraceElement( -+ element.getClassLoaderName(), -+ element.getModuleName(), -+ element.getModuleVersion(), -+ classMapping.mojangName(), -+ mappedMethodName != null ? mappedMethodName : methodName, -+ sourceFileName(classMapping.mojangName()), -+ element.getLineNumber() -+ ); -+ } -+ return result; -+ } -+ -+ private @Nullable String determineMethodForLine(final Class clazz, final int lineNumber) { -+ final Map lineMap = this.lineMapCache.computeIfAbsent(clazz, StacktraceDeobfuscator::buildLineMap); -+ for (final var entry : lineMap.entrySet()) { -+ final String methodKey = entry.getKey(); -+ final IntList lines = entry.getValue(); -+ for (int i = 0, linesSize = lines.size(); i < linesSize; i++) { -+ final int num = lines.getInt(i); -+ if (num == lineNumber) { -+ return methodKey; -+ } -+ } -+ } -+ return null; -+ } -+ -+ private static String sourceFileName(final String fullClassName) { -+ final int dot = fullClassName.lastIndexOf('.'); -+ final String className = dot == -1 -+ ? fullClassName -+ : fullClassName.substring(dot + 1); -+ final String rootClassName = className.split("\\$")[0]; -+ return rootClassName + ".java"; -+ } -+ -+ private static Map buildLineMap(final Class key) { -+ final Map lineMap = new HashMap<>(); -+ final class LineCollectingMethodVisitor extends MethodVisitor { -+ private final IntList lines = new IntArrayList(); -+ private final String name; -+ private final String descriptor; -+ -+ LineCollectingMethodVisitor(String name, String descriptor) { -+ super(Opcodes.ASM9); -+ this.name = name; -+ this.descriptor = descriptor; -+ } -+ -+ @Override -+ public void visitLineNumber(int line, Label start) { -+ super.visitLineNumber(line, start); -+ this.lines.add(line); -+ } -+ -+ @Override -+ public void visitEnd() { -+ super.visitEnd(); -+ lineMap.put(ObfHelper.methodKey(this.name, this.descriptor), this.lines); -+ } -+ } -+ final ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) { -+ @Override -+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { -+ return new LineCollectingMethodVisitor(name, descriptor); -+ } -+ }; -+ try { -+ final @Nullable InputStream inputStream = StacktraceDeobfuscator.class.getClassLoader() -+ .getResourceAsStream(key.getName().replace('.', '/') + ".class"); -+ if (inputStream == null) { -+ throw new IllegalStateException("Could not find class file: " + key.getName()); -+ } -+ final byte[] classData; -+ try (inputStream) { -+ classData = inputStream.readAllBytes(); -+ } -+ final ClassReader reader = new ClassReader(classData); -+ reader.accept(classVisitor, 0); -+ } catch (final IOException ex) { -+ throw new RuntimeException(ex); -+ } -+ return lineMap; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/util/TraceUtil.java b/src/main/java/io/papermc/paper/util/TraceUtil.java -index 2d5494d2813b773e60ddba6790b750a9a08f21f8..0b210bdf7c1f5962afbd44195af6f84f625635e3 100644 ---- a/src/main/java/io/papermc/paper/util/TraceUtil.java -+++ b/src/main/java/io/papermc/paper/util/TraceUtil.java -@@ -6,13 +6,20 @@ public final class TraceUtil { - - public static void dumpTraceForThread(Thread thread, String reason) { - Bukkit.getLogger().warning(thread.getName() + ": " + reason); -- StackTraceElement[] trace = thread.getStackTrace(); -+ StackTraceElement[] trace = StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()); - for (StackTraceElement traceElement : trace) { - Bukkit.getLogger().warning("\tat " + traceElement); - } - } - - public static void dumpTraceForThread(String reason) { -- new Throwable(reason).printStackTrace(); -+ final Throwable throwable = new Throwable(reason); -+ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(throwable); -+ throwable.printStackTrace(); -+ } -+ -+ public static void printStackTrace(Throwable thr) { -+ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(thr); -+ thr.printStackTrace(); - } - } -diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java -index a9a0248b1bd1ac454064e977b61f9b7d80962ff8..6f2452de76e8f5fcc1367066e0e753740764eb98 100644 ---- a/src/main/java/net/minecraft/CrashReport.java -+++ b/src/main/java/net/minecraft/CrashReport.java -@@ -34,6 +34,7 @@ public class CrashReport { - private final SystemReport systemReport = new SystemReport(); - - public CrashReport(String message, Throwable cause) { -+ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper - this.title = message; - this.exception = cause; - this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit -diff --git a/src/main/java/net/minecraft/CrashReportCategory.java b/src/main/java/net/minecraft/CrashReportCategory.java -index 52eb3176437113f9a0ff85d10ce5c2415e1b5570..b54ddd0ba0b001fbcb1838a838ca4890df936f1b 100644 ---- a/src/main/java/net/minecraft/CrashReportCategory.java -+++ b/src/main/java/net/minecraft/CrashReportCategory.java -@@ -104,6 +104,7 @@ public class CrashReportCategory { - } else { - this.stackTrace = new StackTraceElement[stackTraceElements.length - 3 - ignoredCallCount]; - System.arraycopy(stackTraceElements, 3 + ignoredCallCount, this.stackTrace, 0, this.stackTrace.length); -+ this.stackTrace = io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(this.stackTrace); // Paper - return this.stackTrace.length; - } - } -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 7648b889bc488197f32545e8c3671a54102c01ec..44e62675a2d612a8d727d9ce6db5fb85d1a0bcc8 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -75,13 +75,13 @@ public class Connection extends SimpleChannelInboundHandler> { - public static final AttributeKey> ATTRIBUTE_SERVERBOUND_PROTOCOL = AttributeKey.valueOf("serverbound_protocol"); - public static final AttributeKey> ATTRIBUTE_CLIENTBOUND_PROTOCOL = AttributeKey.valueOf("clientbound_protocol"); - public static final Supplier NETWORK_WORKER_GROUP = Suppliers.memoize(() -> { -- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build()); -+ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper - }); - public static final Supplier NETWORK_EPOLL_WORKER_GROUP = Suppliers.memoize(() -> { -- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build()); -+ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper - }); - public static final Supplier LOCAL_WORKER_GROUP = Suppliers.memoize(() -> { -- return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build()); -+ return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper - }); - private final PacketFlow receiving; - private final Queue> pendingActions = Queues.newConcurrentLinkedQueue(); -@@ -207,7 +207,7 @@ public class Connection extends SimpleChannelInboundHandler> { - - } - } -- if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) throwable.printStackTrace(); // Spigot -+ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) io.papermc.paper.util.TraceUtil.printStackTrace(throwable); // Spigot // Paper - } - - protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) { -diff --git a/src/main/java/net/minecraft/network/PacketEncoder.java b/src/main/java/net/minecraft/network/PacketEncoder.java -index 61f05f34ca33837c643f2915e753ec3935a38314..85b8be8ffac0fb40e9cae0528271ed41473811c8 100644 ---- a/src/main/java/net/minecraft/network/PacketEncoder.java -+++ b/src/main/java/net/minecraft/network/PacketEncoder.java -@@ -47,7 +47,14 @@ public class PacketEncoder extends MessageToByteEncoder> { - - JvmProfiler.INSTANCE.onPacketSent(codecData.protocol(), i, channelHandlerContext.channel().remoteAddress(), k); - } catch (Throwable var13) { -- LOGGER.error("Packet encoding of packet ID {} threw (skippable? {})", i, packet.isSkippable(), var13); // Paper - Give proper error message -+ // Paper start - Give proper error message -+ String packetName = io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(packet.getClass().getName()); -+ if (packetName.contains(".")) { -+ packetName = packetName.substring(packetName.lastIndexOf(".") + 1); -+ } -+ -+ LOGGER.error("Packet encoding of packet {} (ID: {}) threw (skippable? {})", packetName, i, packet.isSkippable(), var13); -+ // Paper end - if (packet.isSkippable()) { - throw new SkipPacketException(var13); - } -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 567e642232cc698995def33d7cbe1f679ea7a871..f269441cce34a0b5fb4da4764caeb22ff27cfb00 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -194,6 +194,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings")); - org.spigotmc.SpigotConfig.registerCommands(); - // Spigot end -+ io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc. - // Paper start - initialize global and world-defaults configuration - this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess()); - this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); -diff --git a/src/main/java/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java -index d130f843975236018df4fa2ccc3ca6aaca7a06b8..76f31845fe50200d09e5ab6a6c08da00444414ad 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java -@@ -133,7 +133,7 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis - ServerConfigurationPacketListenerImpl.LOGGER.error("Couldn't place player in world", exception); - // Paper start - Debugging - if (MinecraftServer.getServer().isDebugging()) { -- exception.printStackTrace(); -+ io.papermc.paper.util.TraceUtil.printStackTrace(exception); - } - // Paper end - Debugging - this.connection.send(new ClientboundDisconnectPacket(ServerConfigurationPacketListenerImpl.DISCONNECT_REASON_INVALID_DATA)); -diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -index 187b2cf175ba5cea94158d29b53993dc5a7c5b94..3b6bafb242d2623c15f26acdacd036478c7dc214 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -@@ -52,10 +52,10 @@ public class ServerConnectionListener { - - private static final Logger LOGGER = LogUtils.getLogger(); - public static final Supplier SERVER_EVENT_GROUP = Suppliers.memoize(() -> { -- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build()); -+ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper - }); - public static final Supplier SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize(() -> { -- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build()); -+ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper - }); - final MinecraftServer server; - public volatile boolean running; -diff --git a/src/main/java/net/minecraft/server/players/OldUsersConverter.java b/src/main/java/net/minecraft/server/players/OldUsersConverter.java -index 86c88e81e275d52576122a5083b419e64cb011fc..45d4638d568ea2aee805aa1b0542533019e5870d 100644 ---- a/src/main/java/net/minecraft/server/players/OldUsersConverter.java -+++ b/src/main/java/net/minecraft/server/players/OldUsersConverter.java -@@ -357,7 +357,7 @@ public class OldUsersConverter { - try { - root = NbtIo.readCompressed(new java.io.FileInputStream(file5), NbtAccounter.unlimitedHeap()); - } catch (Exception exception) { -- exception.printStackTrace(); -+ io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Paper - com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent - } - -@@ -371,7 +371,7 @@ public class OldUsersConverter { - try { - NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2)); - } catch (Exception exception) { -- exception.printStackTrace(); -+ io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Paper - com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); // Paper - ServerExceptionEvent - } - } -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 5403fc4fa2ed2526d2e67c230a46dd2a75e017be..af757309cb46af6df07872f7596b66df6d6f18d7 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -592,7 +592,7 @@ public class LevelChunk extends ChunkAccess { - + " (" + getBlockState(blockposition) + ") where there was no entity tile!\n" + - "Chunk coordinates: " + (this.chunkPos.x * 16) + "," + (this.chunkPos.z * 16) + - "\nWorld: " + level.getLevel().dimension().location()); -- e.printStackTrace(); -+ io.papermc.paper.util.TraceUtil.printStackTrace(e); - ServerInternalException.reportInternalException(e); - // Paper end - ServerExceptionEvent - // CraftBukkit end -diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java -index 3c1992e212a6d6f1db4d5b807b38d71913619fc0..9c1aff17aabd062640e3f451a2ef8c50a7c62f10 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java -@@ -40,9 +40,9 @@ public class CraftAsyncScheduler extends CraftScheduler { - - private final ThreadPoolExecutor executor = new ThreadPoolExecutor( - 4, Integer.MAX_VALUE,30L, TimeUnit.SECONDS, new SynchronousQueue<>(), -- new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); -+ new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper - private final Executor management = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() -- .setNameFormat("Craft Async Scheduler Management Thread").build()); -+ .setNameFormat("Craft Async Scheduler Management Thread").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper - private final List temp = new ArrayList<>(); - - CraftAsyncScheduler() { -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index 017c2eadbc5dfec155c21b5d8a80f0b1e380398e..b1ac7338fa632611ea8332044b09070f78f8f5f1 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -102,7 +102,7 @@ public class WatchdogThread extends Thread - log.log(Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity"); - log.log(Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated"); - log.log(Level.SEVERE, org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getMessage()); -- for (StackTraceElement stack : org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace()) { -+ for (StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace())) { // Paper - log.log( Level.SEVERE, "\t\t" + stack ); - } - } -@@ -172,7 +172,7 @@ public class WatchdogThread extends Thread - } - log.log( Level.SEVERE, "\tStack:" ); - // -- for ( StackTraceElement stack : thread.getStackTrace() ) -+ for ( StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()) ) // Paper - { - log.log( Level.SEVERE, "\t\t" + stack ); - } -diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml -index ea4e2161c0bd43884055cc6b8d70b2139f70e720..4e2ca9162450c1f54b7ab95a63c1bad8efe81a06 100644 ---- a/src/main/resources/log4j2.xml -+++ b/src/main/resources/log4j2.xml -@@ -30,10 +30,14 @@ - - - -+ -+ -+ -+ - - - -- -+ - - - diff --git a/patches/server/0367-Implement-Mob-Goal-API.patch b/patches/server/0367-Implement-Mob-Goal-API.patch new file mode 100644 index 000000000000..267fb7795517 --- /dev/null +++ b/patches/server/0367-Implement-Mob-Goal-API.patch @@ -0,0 +1,785 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Fri, 3 Jan 2020 16:26:19 +0100 +Subject: [PATCH] Implement Mob Goal API + + +diff --git a/build.gradle.kts b/build.gradle.kts +index 450f7c03bdcc109938ba9b66328bdbb2c96c03c9..c6241f858209ed662d8720217d143340916024e9 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -44,6 +44,7 @@ dependencies { + runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") + runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") + ++ testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test + testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") + testImplementation("org.hamcrest:hamcrest:2.2") + testImplementation("org.mockito:mockito-core:5.5.0") +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8117578ced94aa6bf01871f6526a388385c4adf2 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +@@ -0,0 +1,376 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import com.destroystokyo.paper.entity.RangedEntity; ++import com.destroystokyo.paper.util.set.OptimizedSmallEnumSet; ++import com.google.common.collect.BiMap; ++import com.google.common.collect.HashBiMap; ++import io.papermc.paper.util.ObfHelper; ++import java.lang.reflect.Constructor; ++import java.util.EnumSet; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++import net.minecraft.world.entity.FlyingMob; ++import net.minecraft.world.entity.PathfinderMob; ++import net.minecraft.world.entity.TamableAnimal; ++import net.minecraft.world.entity.ai.goal.Goal; ++import net.minecraft.world.entity.ambient.AmbientCreature; ++import net.minecraft.world.entity.animal.AbstractFish; ++import net.minecraft.world.entity.animal.AbstractGolem; ++import net.minecraft.world.entity.animal.AbstractSchoolingFish; ++import net.minecraft.world.entity.animal.Animal; ++import net.minecraft.world.entity.animal.Pufferfish; ++import net.minecraft.world.entity.animal.ShoulderRidingEntity; ++import net.minecraft.world.entity.animal.SnowGolem; ++import net.minecraft.world.entity.animal.WaterAnimal; ++import net.minecraft.world.entity.animal.camel.Camel; ++import net.minecraft.world.entity.animal.horse.AbstractChestedHorse; ++import net.minecraft.world.entity.boss.wither.WitherBoss; ++import net.minecraft.world.entity.monster.AbstractIllager; ++import net.minecraft.world.entity.monster.EnderMan; ++import net.minecraft.world.entity.monster.PatrollingMonster; ++import net.minecraft.world.entity.monster.RangedAttackMob; ++import net.minecraft.world.entity.monster.SpellcasterIllager; ++import net.minecraft.world.entity.monster.ZombifiedPiglin; ++import net.minecraft.world.entity.monster.breeze.Breeze; ++import net.minecraft.world.entity.monster.piglin.AbstractPiglin; ++import org.bukkit.NamespacedKey; ++import org.bukkit.entity.AbstractHorse; ++import org.bukkit.entity.AbstractSkeleton; ++import org.bukkit.entity.AbstractVillager; ++import org.bukkit.entity.Ageable; ++import org.bukkit.entity.Ambient; ++import org.bukkit.entity.Animals; ++import org.bukkit.entity.Bat; ++import org.bukkit.entity.Bee; ++import org.bukkit.entity.Blaze; ++import org.bukkit.entity.Cat; ++import org.bukkit.entity.CaveSpider; ++import org.bukkit.entity.ChestedHorse; ++import org.bukkit.entity.Chicken; ++import org.bukkit.entity.Cod; ++import org.bukkit.entity.Cow; ++import org.bukkit.entity.Creature; ++import org.bukkit.entity.Creeper; ++import org.bukkit.entity.Dolphin; ++import org.bukkit.entity.Donkey; ++import org.bukkit.entity.Drowned; ++import org.bukkit.entity.ElderGuardian; ++import org.bukkit.entity.EnderDragon; ++import org.bukkit.entity.Enderman; ++import org.bukkit.entity.Endermite; ++import org.bukkit.entity.Evoker; ++import org.bukkit.entity.Fish; ++import org.bukkit.entity.Flying; ++import org.bukkit.entity.Fox; ++import org.bukkit.entity.Ghast; ++import org.bukkit.entity.Giant; ++import org.bukkit.entity.Golem; ++import org.bukkit.entity.Guardian; ++import org.bukkit.entity.Hoglin; ++import org.bukkit.entity.Horse; ++import org.bukkit.entity.Husk; ++import org.bukkit.entity.Illager; ++import org.bukkit.entity.Illusioner; ++import org.bukkit.entity.IronGolem; ++import org.bukkit.entity.Llama; ++import org.bukkit.entity.MagmaCube; ++import org.bukkit.entity.Mob; ++import org.bukkit.entity.Monster; ++import org.bukkit.entity.Mule; ++import org.bukkit.entity.MushroomCow; ++import org.bukkit.entity.Ocelot; ++import org.bukkit.entity.Panda; ++import org.bukkit.entity.Parrot; ++import org.bukkit.entity.Phantom; ++import org.bukkit.entity.Pig; ++import org.bukkit.entity.PigZombie; ++import org.bukkit.entity.Piglin; ++import org.bukkit.entity.PiglinAbstract; ++import org.bukkit.entity.PiglinBrute; ++import org.bukkit.entity.Pillager; ++import org.bukkit.entity.PolarBear; ++import org.bukkit.entity.PufferFish; ++import org.bukkit.entity.Rabbit; ++import org.bukkit.entity.Raider; ++import org.bukkit.entity.Ravager; ++import org.bukkit.entity.Salmon; ++import org.bukkit.entity.Sheep; ++import org.bukkit.entity.Shulker; ++import org.bukkit.entity.Silverfish; ++import org.bukkit.entity.Skeleton; ++import org.bukkit.entity.SkeletonHorse; ++import org.bukkit.entity.Slime; ++import org.bukkit.entity.Snowman; ++import org.bukkit.entity.Spellcaster; ++import org.bukkit.entity.Spider; ++import org.bukkit.entity.Squid; ++import org.bukkit.entity.Stray; ++import org.bukkit.entity.Strider; ++import org.bukkit.entity.Tameable; ++import org.bukkit.entity.TraderLlama; ++import org.bukkit.entity.TropicalFish; ++import org.bukkit.entity.Turtle; ++import org.bukkit.entity.Vex; ++import org.bukkit.entity.Villager; ++import org.bukkit.entity.Vindicator; ++import org.bukkit.entity.WanderingTrader; ++import org.bukkit.entity.WaterMob; ++import org.bukkit.entity.Witch; ++import org.bukkit.entity.Wither; ++import org.bukkit.entity.WitherSkeleton; ++import org.bukkit.entity.Wolf; ++import org.bukkit.entity.Zoglin; ++import org.bukkit.entity.Zombie; ++import org.bukkit.entity.ZombieHorse; ++import org.bukkit.entity.ZombieVillager; ++ ++public class MobGoalHelper { ++ ++ private static final BiMap deobfuscationMap = HashBiMap.create(); ++ private static final Map, Class> entityClassCache = new HashMap<>(); ++ private static final Map, Class> bukkitMap = new HashMap<>(); ++ ++ static final Set ignored = new HashSet<>(); ++ ++ static { ++ // TODO these kinda should be checked on each release, in case obfuscation changes ++ deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee"); ++ ++ ignored.add("goal_selector_1"); ++ ignored.add("goal_selector_2"); ++ ignored.add("selector_1"); ++ ignored.add("selector_2"); ++ ignored.add("wrapped"); ++ ++ bukkitMap.put(net.minecraft.world.entity.Mob.class, Mob.class); ++ bukkitMap.put(net.minecraft.world.entity.AgeableMob.class, Ageable.class); ++ bukkitMap.put(AmbientCreature.class, Ambient.class); ++ bukkitMap.put(Animal.class, Animals.class); ++ bukkitMap.put(net.minecraft.world.entity.ambient.Bat.class, Bat.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Bee.class, Bee.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Blaze.class, Blaze.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Cat.class, Cat.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.CaveSpider.class, CaveSpider.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Chicken.class, Chicken.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Cod.class, Cod.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Cow.class, Cow.class); ++ bukkitMap.put(PathfinderMob.class, Creature.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Creeper.class, Creeper.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Dolphin.class, Dolphin.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Drowned.class, Drowned.class); ++ bukkitMap.put(net.minecraft.world.entity.boss.enderdragon.EnderDragon.class, EnderDragon.class); ++ bukkitMap.put(EnderMan.class, Enderman.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class); ++ bukkitMap.put(AbstractFish.class, Fish.class); ++ bukkitMap.put(AbstractSchoolingFish.class, Fish.class); // close enough ++ bukkitMap.put(FlyingMob.class, Flying.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Giant.class, Giant.class); ++ bukkitMap.put(AbstractGolem.class, Golem.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Guardian.class, Guardian.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.ElderGuardian.class, ElderGuardian.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Horse.class, Horse.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.AbstractHorse.class, AbstractHorse.class); ++ bukkitMap.put(AbstractChestedHorse.class, ChestedHorse.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Donkey.class, Donkey.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Mule.class, Mule.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.SkeletonHorse.class, SkeletonHorse.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.ZombieHorse.class, ZombieHorse.class); ++ bukkitMap.put(Camel.class, org.bukkit.entity.Camel.class); ++ bukkitMap.put(AbstractIllager.class, Illager.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Illusioner.class, Illusioner.class); ++ bukkitMap.put(SpellcasterIllager.class, Spellcaster.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.IronGolem.class, IronGolem.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Llama.class, Llama.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.TraderLlama.class, TraderLlama.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.MagmaCube.class, MagmaCube.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Monster.class, Monster.class); ++ bukkitMap.put(PatrollingMonster.class, Raider.class); // close enough ++ bukkitMap.put(net.minecraft.world.entity.animal.MushroomCow.class, MushroomCow.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Ocelot.class, Ocelot.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Panda.class, Panda.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Parrot.class, Parrot.class); ++ bukkitMap.put(ShoulderRidingEntity.class, Parrot.class); // close enough ++ bukkitMap.put(net.minecraft.world.entity.monster.Phantom.class, Phantom.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Pig.class, Pig.class); ++ bukkitMap.put(ZombifiedPiglin.class, PigZombie.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Pillager.class, Pillager.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.PolarBear.class, PolarBear.class); ++ bukkitMap.put(Pufferfish.class, PufferFish.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Rabbit.class, Rabbit.class); ++ bukkitMap.put(net.minecraft.world.entity.raid.Raider.class, Raider.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Ravager.class, Ravager.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Salmon.class, Salmon.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Sheep.class, Sheep.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Shulker.class, Shulker.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Silverfish.class, Silverfish.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Skeleton.class, Skeleton.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.AbstractSkeleton.class, AbstractSkeleton.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Stray.class, Stray.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.WitherSkeleton.class, WitherSkeleton.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Slime.class, Slime.class); ++ bukkitMap.put(SnowGolem.class, Snowman.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Spider.class, Spider.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Squid.class, Squid.class); ++ bukkitMap.put(TamableAnimal.class, Tameable.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.TropicalFish.class, TropicalFish.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Turtle.class, Turtle.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Vex.class, Vex.class); ++ bukkitMap.put(net.minecraft.world.entity.npc.Villager.class, Villager.class); ++ bukkitMap.put(net.minecraft.world.entity.npc.AbstractVillager.class, AbstractVillager.class); ++ bukkitMap.put(net.minecraft.world.entity.npc.WanderingTrader.class, WanderingTrader.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Vindicator.class, Vindicator.class); ++ bukkitMap.put(WaterAnimal.class, WaterMob.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Witch.class, Witch.class); ++ bukkitMap.put(WitherBoss.class, Wither.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Wolf.class, Wolf.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Zombie.class, Zombie.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Husk.class, Husk.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.ZombieVillager.class, ZombieVillager.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.hoglin.Hoglin.class, Hoglin.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.piglin.Piglin.class, Piglin.class); ++ bukkitMap.put(AbstractPiglin.class, PiglinAbstract.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.piglin.PiglinBrute.class, PiglinBrute.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Strider.class, Strider.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Zoglin.class, Zoglin.class); ++ bukkitMap.put(net.minecraft.world.entity.GlowSquid.class, org.bukkit.entity.GlowSquid.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.axolotl.Axolotl.class, org.bukkit.entity.Axolotl.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.goat.Goat.class, org.bukkit.entity.Goat.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.frog.Frog.class, org.bukkit.entity.Frog.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.frog.Tadpole.class, org.bukkit.entity.Tadpole.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.warden.Warden.class, org.bukkit.entity.Warden.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.allay.Allay.class, org.bukkit.entity.Allay.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.sniffer.Sniffer.class, org.bukkit.entity.Sniffer.class); ++ bukkitMap.put(Breeze.class, org.bukkit.entity.Breeze.class); ++ } ++ ++ public static String getUsableName(Class clazz) { ++ String name = ObfHelper.INSTANCE.deobfClassName(clazz.getName()); ++ name = name.substring(name.lastIndexOf(".") + 1); ++ boolean flag = false; ++ // inner classes ++ if (name.contains("$")) { ++ String cut = name.substring(name.indexOf("$") + 1); ++ if (cut.length() <= 2) { ++ name = name.replace("Entity", ""); ++ name = name.replace("$", "_"); ++ flag = true; ++ } else { ++ // mapped, wooo ++ name = cut; ++ } ++ } ++ name = name.replace("PathfinderGoal", ""); ++ name = name.replace("TargetGoal", ""); ++ name = name.replace("Goal", ""); ++ StringBuilder sb = new StringBuilder(); ++ for (char c : name.toCharArray()) { ++ if (c >= 'A' && c <= 'Z') { ++ sb.append("_"); ++ sb.append(Character.toLowerCase(c)); ++ } else { ++ sb.append(c); ++ } ++ } ++ name = sb.toString(); ++ name = name.replaceFirst("_", ""); ++ ++ if (flag && !deobfuscationMap.containsKey(name.toLowerCase()) && !ignored.contains(name)) { ++ System.out.println("need to map " + clazz.getName() + " (" + name.toLowerCase() + ")"); ++ } ++ ++ // did we rename this key? ++ return deobfuscationMap.getOrDefault(name, name); ++ } ++ ++ public static EnumSet vanillaToPaper(Goal goal) { ++ EnumSet goals = EnumSet.noneOf(GoalType.class); ++ for (GoalType type : GoalType.values()) { ++ if (goal.getFlags().contains(paperToVanilla(type))) { ++ goals.add(type); ++ } ++ } ++ return goals; ++ } ++ ++ public static GoalType vanillaToPaper(Goal.Flag type) { ++ switch (type) { ++ case MOVE: ++ return GoalType.MOVE; ++ case LOOK: ++ return GoalType.LOOK; ++ case JUMP: ++ return GoalType.JUMP; ++ case UNKNOWN_BEHAVIOR: ++ return GoalType.UNKNOWN_BEHAVIOR; ++ case TARGET: ++ return GoalType.TARGET; ++ default: ++ throw new IllegalArgumentException("Unknown vanilla mob goal type " + type.name()); ++ } ++ } ++ ++ public static EnumSet paperToVanilla(EnumSet types) { ++ EnumSet goals = EnumSet.noneOf(Goal.Flag.class); ++ for (GoalType type : types) { ++ goals.add(paperToVanilla(type)); ++ } ++ return goals; ++ } ++ ++ public static Goal.Flag paperToVanilla(GoalType type) { ++ switch (type) { ++ case MOVE: ++ return Goal.Flag.MOVE; ++ case LOOK: ++ return Goal.Flag.LOOK; ++ case JUMP: ++ return Goal.Flag.JUMP; ++ case UNKNOWN_BEHAVIOR: ++ return Goal.Flag.UNKNOWN_BEHAVIOR; ++ case TARGET: ++ return Goal.Flag.TARGET; ++ default: ++ throw new IllegalArgumentException("Unknown paper mob goal type " + type.name()); ++ } ++ } ++ ++ public static GoalKey getKey(Class goalClass) { ++ String name = getUsableName(goalClass); ++ if (ignored.contains(name)) { ++ //noinspection unchecked ++ return (GoalKey) GoalKey.of(Mob.class, NamespacedKey.minecraft(name)); ++ } ++ return GoalKey.of(getEntity(goalClass), NamespacedKey.minecraft(name)); ++ } ++ ++ public static Class getEntity(Class goalClass) { ++ //noinspection unchecked ++ return (Class) entityClassCache.computeIfAbsent(goalClass, key -> { ++ for (Constructor ctor : key.getDeclaredConstructors()) { ++ for (int i = 0; i < ctor.getParameterCount(); i++) { ++ Class param = ctor.getParameterTypes()[i]; ++ if (net.minecraft.world.entity.Mob.class.isAssignableFrom(param)) { ++ //noinspection unchecked ++ return toBukkitClass((Class) param); ++ } else if (RangedAttackMob.class.isAssignableFrom(param)) { ++ return RangedEntity.class; ++ } ++ } ++ } ++ throw new RuntimeException("Can't figure out applicable entity for mob goal " + goalClass); // maybe just return EntityInsentient? ++ }); ++ } ++ ++ public static Class toBukkitClass(Class nmsClass) { ++ Class bukkitClass = bukkitMap.get(nmsClass); ++ if (bukkitClass == null) { ++ throw new RuntimeException("Can't figure out applicable bukkit entity for nms entity " + nmsClass); // maybe just return Mob? ++ } ++ return bukkitClass; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b5f75ad725f5933db8f0688b2c0b27d620919241 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java +@@ -0,0 +1,53 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import org.bukkit.entity.Mob; ++ ++/** ++ * Wraps api in vanilla ++ */ ++public class PaperCustomGoal extends net.minecraft.world.entity.ai.goal.Goal { ++ ++ private final Goal handle; ++ ++ public PaperCustomGoal(Goal handle) { ++ this.handle = handle; ++ ++ this.setFlags(MobGoalHelper.paperToVanilla(handle.getTypes())); ++ if (this.getFlags().size() == 0) { ++ this.getFlags().add(Flag.UNKNOWN_BEHAVIOR); ++ } ++ } ++ ++ @Override ++ public boolean canUse() { ++ return handle.shouldActivate(); ++ } ++ ++ @Override ++ public boolean canContinueToUse() { ++ return handle.shouldStayActive(); ++ } ++ ++ @Override ++ public void start() { ++ handle.start(); ++ } ++ ++ @Override ++ public void stop() { ++ handle.stop(); ++ } ++ ++ @Override ++ public void tick() { ++ handle.tick(); ++ } ++ ++ public Goal getHandle() { ++ return handle; ++ } ++ ++ public GoalKey getKey() { ++ return handle.getKey(); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java +new file mode 100644 +index 0000000000000000000000000000000000000000..953b0f88cbf0e73c390f8086344f772ac03192bb +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java +@@ -0,0 +1,213 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import java.util.Collection; ++import java.util.EnumSet; ++import java.util.HashSet; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Set; ++import net.minecraft.world.entity.ai.goal.GoalSelector; ++import net.minecraft.world.entity.ai.goal.WrappedGoal; ++import org.bukkit.craftbukkit.entity.CraftMob; ++import org.bukkit.entity.Mob; ++ ++public class PaperMobGoals implements MobGoals { ++ ++ @Override ++ public void addGoal(T mob, int priority, Goal goal) { ++ CraftMob craftMob = (CraftMob) mob; ++ getHandle(craftMob, goal.getTypes()).addGoal(priority, new PaperCustomGoal<>(goal)); ++ } ++ ++ @Override ++ public void removeGoal(T mob, Goal goal) { ++ CraftMob craftMob = (CraftMob) mob; ++ if (goal instanceof PaperCustomGoal) { ++ getHandle(craftMob, goal.getTypes()).removeGoal((net.minecraft.world.entity.ai.goal.Goal) goal); ++ } else if (goal instanceof PaperVanillaGoal) { ++ getHandle(craftMob, goal.getTypes()).removeGoal(((PaperVanillaGoal) goal).getHandle()); ++ } else { ++ List toRemove = new LinkedList<>(); ++ for (WrappedGoal item : getHandle(craftMob, goal.getTypes()).getAvailableGoals()) { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ if (((PaperCustomGoal) item.getGoal()).getHandle() == goal) { ++ toRemove.add(item.getGoal()); ++ } ++ } ++ } ++ ++ for (net.minecraft.world.entity.ai.goal.Goal g : toRemove) { ++ getHandle(craftMob, goal.getTypes()).removeGoal(g); ++ } ++ } ++ } ++ ++ @Override ++ public void removeAllGoals(T mob) { ++ for (GoalType type : GoalType.values()) { ++ removeAllGoals(mob, type); ++ } ++ } ++ ++ @Override ++ public void removeAllGoals(T mob, GoalType type) { ++ for (Goal goal : getAllGoals(mob, type)) { ++ removeGoal(mob, goal); ++ } ++ } ++ ++ @Override ++ public void removeGoal(T mob, GoalKey key) { ++ for (Goal goal : getGoals(mob, key)) { ++ removeGoal(mob, goal); ++ } ++ } ++ ++ @Override ++ public boolean hasGoal(T mob, GoalKey key) { ++ for (Goal g : getAllGoals(mob)) { ++ if (g.getKey().equals(key)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ @Override ++ public Goal getGoal(T mob, GoalKey key) { ++ for (Goal g : getAllGoals(mob)) { ++ if (g.getKey().equals(key)) { ++ return g; ++ } ++ } ++ return null; ++ } ++ ++ @Override ++ public Collection> getGoals(T mob, GoalKey key) { ++ Set> goals = new HashSet<>(); ++ for (Goal g : getAllGoals(mob)) { ++ if (g.getKey().equals(key)) { ++ goals.add(g); ++ } ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getAllGoals(T mob) { ++ Set> goals = new HashSet<>(); ++ for (GoalType type : GoalType.values()) { ++ goals.addAll(getAllGoals(mob, type)); ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getAllGoals(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ for (WrappedGoal item : getHandle(craftMob, type).getAvailableGoals()) { ++ if (!item.getGoal().getFlags().contains(MobGoalHelper.paperToVanilla(type))) { ++ continue; ++ } ++ ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ goals.add(item.getGoal().asPaperVanillaGoal()); ++ } ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getAllGoalsWithout(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ for (GoalType internalType : GoalType.values()) { ++ if (internalType == type) { ++ continue; ++ } ++ for (WrappedGoal item : getHandle(craftMob, internalType).getAvailableGoals()) { ++ if (item.getGoal().getFlags().contains(MobGoalHelper.paperToVanilla(type))) { ++ continue; ++ } ++ ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ goals.add(item.getGoal().asPaperVanillaGoal()); ++ } ++ } ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getRunningGoals(T mob) { ++ Set> goals = new HashSet<>(); ++ for (GoalType type : GoalType.values()) { ++ goals.addAll(getRunningGoals(mob, type)); ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getRunningGoals(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ getHandle(craftMob, type).getRunningGoals() ++ .filter(item -> item.getGoal().getFlags().contains(MobGoalHelper.paperToVanilla(type))) ++ .forEach(item -> { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ goals.add(item.getGoal().asPaperVanillaGoal()); ++ } ++ }); ++ return goals; ++ } ++ ++ @Override ++ public Collection> getRunningGoalsWithout(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ for (GoalType internalType : GoalType.values()) { ++ if (internalType == type) { ++ continue; ++ } ++ getHandle(craftMob, internalType).getRunningGoals() ++ .filter(item -> !item.getGoal().getFlags().contains(MobGoalHelper.paperToVanilla(type))) ++ .forEach(item -> { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ goals.add(item.getGoal().asPaperVanillaGoal()); ++ } ++ }); ++ } ++ return goals; ++ } ++ ++ private GoalSelector getHandle(CraftMob mob, EnumSet types) { ++ if (types.contains(GoalType.TARGET)) { ++ return mob.getHandle().targetSelector; ++ } else { ++ return mob.getHandle().goalSelector; ++ } ++ } ++ ++ private GoalSelector getHandle(CraftMob mob, GoalType type) { ++ if (type == GoalType.TARGET) { ++ return mob.getHandle().targetSelector; ++ } else { ++ return mob.getHandle().goalSelector; ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b5c594a5499556ad452d9939c75e150af8252e90 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java +@@ -0,0 +1,61 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import java.util.EnumSet; ++import net.minecraft.world.entity.ai.goal.Goal; ++import org.bukkit.entity.Mob; ++ ++/** ++ * Wraps vanilla in api ++ */ ++public class PaperVanillaGoal implements VanillaGoal { ++ ++ private final Goal handle; ++ private final GoalKey key; ++ ++ private final EnumSet types; ++ ++ public PaperVanillaGoal(Goal handle) { ++ this.handle = handle; ++ this.key = MobGoalHelper.getKey(handle.getClass()); ++ this.types = MobGoalHelper.vanillaToPaper(handle); ++ } ++ ++ public Goal getHandle() { ++ return handle; ++ } ++ ++ @Override ++ public boolean shouldActivate() { ++ return handle.canUse(); ++ } ++ ++ @Override ++ public boolean shouldStayActive() { ++ return handle.canContinueToUse(); ++ } ++ ++ @Override ++ public void start() { ++ handle.start(); ++ } ++ ++ @Override ++ public void stop() { ++ handle.stop(); ++ } ++ ++ @Override ++ public void tick() { ++ handle.tick(); ++ } ++ ++ @Override ++ public GoalKey getKey() { ++ return key; ++ } ++ ++ @Override ++ public EnumSet getTypes() { ++ return types; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +index 6667ecc4b7eded4e20a415cef1e1b1179e6710b8..16f9a98b8a939e5ca7e2dc04f87134a7ed66736b 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +@@ -51,7 +51,19 @@ public abstract class Goal { + return Mth.positiveCeilDiv(serverTicks, 2); + } + ++ // Paper start - Mob goal api ++ private com.destroystokyo.paper.entity.ai.PaperVanillaGoal vanillaGoal; ++ public com.destroystokyo.paper.entity.ai.Goal asPaperVanillaGoal() { ++ if(this.vanillaGoal == null) { ++ this.vanillaGoal = new com.destroystokyo.paper.entity.ai.PaperVanillaGoal<>(this); ++ } ++ //noinspection unchecked ++ return (com.destroystokyo.paper.entity.ai.Goal) this.vanillaGoal; ++ } ++ // Paper end - Mob goal api ++ + public static enum Flag { ++ UNKNOWN_BEHAVIOR, // Paper - add UNKNOWN_BEHAVIOR + MOVE, + LOOK, + JUMP, +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 0a6b283aee63adb09cf1466fb18406f941466cd0..12b76fd9d3d5e8dcca31d7e82f6956e13447ff64 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2889,5 +2889,11 @@ public final class CraftServer implements Server { + public boolean isStopping() { + return net.minecraft.server.MinecraftServer.getServer().hasStopped(); + } ++ ++ private com.destroystokyo.paper.entity.ai.MobGoals mobGoals = new com.destroystokyo.paper.entity.ai.PaperMobGoals(); ++ @Override ++ public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { ++ return mobGoals; ++ } + // Paper end + } diff --git a/patches/server/0369-Add-villager-reputation-API.patch b/patches/server/0368-Add-villager-reputation-API.patch similarity index 100% rename from patches/server/0369-Add-villager-reputation-API.patch rename to patches/server/0368-Add-villager-reputation-API.patch diff --git a/patches/server/0368-Implement-Mob-Goal-API.patch b/patches/server/0368-Implement-Mob-Goal-API.patch deleted file mode 100644 index 296c7977a66d..000000000000 --- a/patches/server/0368-Implement-Mob-Goal-API.patch +++ /dev/null @@ -1,785 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MiniDigger -Date: Fri, 3 Jan 2020 16:26:19 +0100 -Subject: [PATCH] Implement Mob Goal API - - -diff --git a/build.gradle.kts b/build.gradle.kts -index 450f7c03bdcc109938ba9b66328bdbb2c96c03c9..c6241f858209ed662d8720217d143340916024e9 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -44,6 +44,7 @@ dependencies { - runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") - runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") - -+ testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test - testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") - testImplementation("org.hamcrest:hamcrest:2.2") - testImplementation("org.mockito:mockito-core:5.5.0") -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8117578ced94aa6bf01871f6526a388385c4adf2 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -@@ -0,0 +1,376 @@ -+package com.destroystokyo.paper.entity.ai; -+ -+import com.destroystokyo.paper.entity.RangedEntity; -+import com.destroystokyo.paper.util.set.OptimizedSmallEnumSet; -+import com.google.common.collect.BiMap; -+import com.google.common.collect.HashBiMap; -+import io.papermc.paper.util.ObfHelper; -+import java.lang.reflect.Constructor; -+import java.util.EnumSet; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+import net.minecraft.world.entity.FlyingMob; -+import net.minecraft.world.entity.PathfinderMob; -+import net.minecraft.world.entity.TamableAnimal; -+import net.minecraft.world.entity.ai.goal.Goal; -+import net.minecraft.world.entity.ambient.AmbientCreature; -+import net.minecraft.world.entity.animal.AbstractFish; -+import net.minecraft.world.entity.animal.AbstractGolem; -+import net.minecraft.world.entity.animal.AbstractSchoolingFish; -+import net.minecraft.world.entity.animal.Animal; -+import net.minecraft.world.entity.animal.Pufferfish; -+import net.minecraft.world.entity.animal.ShoulderRidingEntity; -+import net.minecraft.world.entity.animal.SnowGolem; -+import net.minecraft.world.entity.animal.WaterAnimal; -+import net.minecraft.world.entity.animal.camel.Camel; -+import net.minecraft.world.entity.animal.horse.AbstractChestedHorse; -+import net.minecraft.world.entity.boss.wither.WitherBoss; -+import net.minecraft.world.entity.monster.AbstractIllager; -+import net.minecraft.world.entity.monster.EnderMan; -+import net.minecraft.world.entity.monster.PatrollingMonster; -+import net.minecraft.world.entity.monster.RangedAttackMob; -+import net.minecraft.world.entity.monster.SpellcasterIllager; -+import net.minecraft.world.entity.monster.ZombifiedPiglin; -+import net.minecraft.world.entity.monster.breeze.Breeze; -+import net.minecraft.world.entity.monster.piglin.AbstractPiglin; -+import org.bukkit.NamespacedKey; -+import org.bukkit.entity.AbstractHorse; -+import org.bukkit.entity.AbstractSkeleton; -+import org.bukkit.entity.AbstractVillager; -+import org.bukkit.entity.Ageable; -+import org.bukkit.entity.Ambient; -+import org.bukkit.entity.Animals; -+import org.bukkit.entity.Bat; -+import org.bukkit.entity.Bee; -+import org.bukkit.entity.Blaze; -+import org.bukkit.entity.Cat; -+import org.bukkit.entity.CaveSpider; -+import org.bukkit.entity.ChestedHorse; -+import org.bukkit.entity.Chicken; -+import org.bukkit.entity.Cod; -+import org.bukkit.entity.Cow; -+import org.bukkit.entity.Creature; -+import org.bukkit.entity.Creeper; -+import org.bukkit.entity.Dolphin; -+import org.bukkit.entity.Donkey; -+import org.bukkit.entity.Drowned; -+import org.bukkit.entity.ElderGuardian; -+import org.bukkit.entity.EnderDragon; -+import org.bukkit.entity.Enderman; -+import org.bukkit.entity.Endermite; -+import org.bukkit.entity.Evoker; -+import org.bukkit.entity.Fish; -+import org.bukkit.entity.Flying; -+import org.bukkit.entity.Fox; -+import org.bukkit.entity.Ghast; -+import org.bukkit.entity.Giant; -+import org.bukkit.entity.Golem; -+import org.bukkit.entity.Guardian; -+import org.bukkit.entity.Hoglin; -+import org.bukkit.entity.Horse; -+import org.bukkit.entity.Husk; -+import org.bukkit.entity.Illager; -+import org.bukkit.entity.Illusioner; -+import org.bukkit.entity.IronGolem; -+import org.bukkit.entity.Llama; -+import org.bukkit.entity.MagmaCube; -+import org.bukkit.entity.Mob; -+import org.bukkit.entity.Monster; -+import org.bukkit.entity.Mule; -+import org.bukkit.entity.MushroomCow; -+import org.bukkit.entity.Ocelot; -+import org.bukkit.entity.Panda; -+import org.bukkit.entity.Parrot; -+import org.bukkit.entity.Phantom; -+import org.bukkit.entity.Pig; -+import org.bukkit.entity.PigZombie; -+import org.bukkit.entity.Piglin; -+import org.bukkit.entity.PiglinAbstract; -+import org.bukkit.entity.PiglinBrute; -+import org.bukkit.entity.Pillager; -+import org.bukkit.entity.PolarBear; -+import org.bukkit.entity.PufferFish; -+import org.bukkit.entity.Rabbit; -+import org.bukkit.entity.Raider; -+import org.bukkit.entity.Ravager; -+import org.bukkit.entity.Salmon; -+import org.bukkit.entity.Sheep; -+import org.bukkit.entity.Shulker; -+import org.bukkit.entity.Silverfish; -+import org.bukkit.entity.Skeleton; -+import org.bukkit.entity.SkeletonHorse; -+import org.bukkit.entity.Slime; -+import org.bukkit.entity.Snowman; -+import org.bukkit.entity.Spellcaster; -+import org.bukkit.entity.Spider; -+import org.bukkit.entity.Squid; -+import org.bukkit.entity.Stray; -+import org.bukkit.entity.Strider; -+import org.bukkit.entity.Tameable; -+import org.bukkit.entity.TraderLlama; -+import org.bukkit.entity.TropicalFish; -+import org.bukkit.entity.Turtle; -+import org.bukkit.entity.Vex; -+import org.bukkit.entity.Villager; -+import org.bukkit.entity.Vindicator; -+import org.bukkit.entity.WanderingTrader; -+import org.bukkit.entity.WaterMob; -+import org.bukkit.entity.Witch; -+import org.bukkit.entity.Wither; -+import org.bukkit.entity.WitherSkeleton; -+import org.bukkit.entity.Wolf; -+import org.bukkit.entity.Zoglin; -+import org.bukkit.entity.Zombie; -+import org.bukkit.entity.ZombieHorse; -+import org.bukkit.entity.ZombieVillager; -+ -+public class MobGoalHelper { -+ -+ private static final BiMap deobfuscationMap = HashBiMap.create(); -+ private static final Map, Class> entityClassCache = new HashMap<>(); -+ private static final Map, Class> bukkitMap = new HashMap<>(); -+ -+ static final Set ignored = new HashSet<>(); -+ -+ static { -+ // TODO these kinda should be checked on each release, in case obfuscation changes -+ deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee"); -+ -+ ignored.add("goal_selector_1"); -+ ignored.add("goal_selector_2"); -+ ignored.add("selector_1"); -+ ignored.add("selector_2"); -+ ignored.add("wrapped"); -+ -+ bukkitMap.put(net.minecraft.world.entity.Mob.class, Mob.class); -+ bukkitMap.put(net.minecraft.world.entity.AgeableMob.class, Ageable.class); -+ bukkitMap.put(AmbientCreature.class, Ambient.class); -+ bukkitMap.put(Animal.class, Animals.class); -+ bukkitMap.put(net.minecraft.world.entity.ambient.Bat.class, Bat.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Bee.class, Bee.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Blaze.class, Blaze.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Cat.class, Cat.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.CaveSpider.class, CaveSpider.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Chicken.class, Chicken.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Cod.class, Cod.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Cow.class, Cow.class); -+ bukkitMap.put(PathfinderMob.class, Creature.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Creeper.class, Creeper.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Dolphin.class, Dolphin.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Drowned.class, Drowned.class); -+ bukkitMap.put(net.minecraft.world.entity.boss.enderdragon.EnderDragon.class, EnderDragon.class); -+ bukkitMap.put(EnderMan.class, Enderman.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class); -+ bukkitMap.put(AbstractFish.class, Fish.class); -+ bukkitMap.put(AbstractSchoolingFish.class, Fish.class); // close enough -+ bukkitMap.put(FlyingMob.class, Flying.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Giant.class, Giant.class); -+ bukkitMap.put(AbstractGolem.class, Golem.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Guardian.class, Guardian.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.ElderGuardian.class, ElderGuardian.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Horse.class, Horse.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.AbstractHorse.class, AbstractHorse.class); -+ bukkitMap.put(AbstractChestedHorse.class, ChestedHorse.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Donkey.class, Donkey.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Mule.class, Mule.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.SkeletonHorse.class, SkeletonHorse.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.ZombieHorse.class, ZombieHorse.class); -+ bukkitMap.put(Camel.class, org.bukkit.entity.Camel.class); -+ bukkitMap.put(AbstractIllager.class, Illager.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Illusioner.class, Illusioner.class); -+ bukkitMap.put(SpellcasterIllager.class, Spellcaster.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.IronGolem.class, IronGolem.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Llama.class, Llama.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.TraderLlama.class, TraderLlama.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.MagmaCube.class, MagmaCube.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Monster.class, Monster.class); -+ bukkitMap.put(PatrollingMonster.class, Raider.class); // close enough -+ bukkitMap.put(net.minecraft.world.entity.animal.MushroomCow.class, MushroomCow.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Ocelot.class, Ocelot.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Panda.class, Panda.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Parrot.class, Parrot.class); -+ bukkitMap.put(ShoulderRidingEntity.class, Parrot.class); // close enough -+ bukkitMap.put(net.minecraft.world.entity.monster.Phantom.class, Phantom.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Pig.class, Pig.class); -+ bukkitMap.put(ZombifiedPiglin.class, PigZombie.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Pillager.class, Pillager.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.PolarBear.class, PolarBear.class); -+ bukkitMap.put(Pufferfish.class, PufferFish.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Rabbit.class, Rabbit.class); -+ bukkitMap.put(net.minecraft.world.entity.raid.Raider.class, Raider.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Ravager.class, Ravager.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Salmon.class, Salmon.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Sheep.class, Sheep.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Shulker.class, Shulker.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Silverfish.class, Silverfish.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Skeleton.class, Skeleton.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.AbstractSkeleton.class, AbstractSkeleton.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Stray.class, Stray.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.WitherSkeleton.class, WitherSkeleton.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Slime.class, Slime.class); -+ bukkitMap.put(SnowGolem.class, Snowman.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Spider.class, Spider.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Squid.class, Squid.class); -+ bukkitMap.put(TamableAnimal.class, Tameable.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.TropicalFish.class, TropicalFish.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Turtle.class, Turtle.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Vex.class, Vex.class); -+ bukkitMap.put(net.minecraft.world.entity.npc.Villager.class, Villager.class); -+ bukkitMap.put(net.minecraft.world.entity.npc.AbstractVillager.class, AbstractVillager.class); -+ bukkitMap.put(net.minecraft.world.entity.npc.WanderingTrader.class, WanderingTrader.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Vindicator.class, Vindicator.class); -+ bukkitMap.put(WaterAnimal.class, WaterMob.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Witch.class, Witch.class); -+ bukkitMap.put(WitherBoss.class, Wither.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Wolf.class, Wolf.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Zombie.class, Zombie.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Husk.class, Husk.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.ZombieVillager.class, ZombieVillager.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.hoglin.Hoglin.class, Hoglin.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.piglin.Piglin.class, Piglin.class); -+ bukkitMap.put(AbstractPiglin.class, PiglinAbstract.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.piglin.PiglinBrute.class, PiglinBrute.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Strider.class, Strider.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Zoglin.class, Zoglin.class); -+ bukkitMap.put(net.minecraft.world.entity.GlowSquid.class, org.bukkit.entity.GlowSquid.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.axolotl.Axolotl.class, org.bukkit.entity.Axolotl.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.goat.Goat.class, org.bukkit.entity.Goat.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.frog.Frog.class, org.bukkit.entity.Frog.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.frog.Tadpole.class, org.bukkit.entity.Tadpole.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.warden.Warden.class, org.bukkit.entity.Warden.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.allay.Allay.class, org.bukkit.entity.Allay.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.sniffer.Sniffer.class, org.bukkit.entity.Sniffer.class); -+ bukkitMap.put(Breeze.class, org.bukkit.entity.Breeze.class); -+ } -+ -+ public static String getUsableName(Class clazz) { -+ String name = ObfHelper.INSTANCE.deobfClassName(clazz.getName()); -+ name = name.substring(name.lastIndexOf(".") + 1); -+ boolean flag = false; -+ // inner classes -+ if (name.contains("$")) { -+ String cut = name.substring(name.indexOf("$") + 1); -+ if (cut.length() <= 2) { -+ name = name.replace("Entity", ""); -+ name = name.replace("$", "_"); -+ flag = true; -+ } else { -+ // mapped, wooo -+ name = cut; -+ } -+ } -+ name = name.replace("PathfinderGoal", ""); -+ name = name.replace("TargetGoal", ""); -+ name = name.replace("Goal", ""); -+ StringBuilder sb = new StringBuilder(); -+ for (char c : name.toCharArray()) { -+ if (c >= 'A' && c <= 'Z') { -+ sb.append("_"); -+ sb.append(Character.toLowerCase(c)); -+ } else { -+ sb.append(c); -+ } -+ } -+ name = sb.toString(); -+ name = name.replaceFirst("_", ""); -+ -+ if (flag && !deobfuscationMap.containsKey(name.toLowerCase()) && !ignored.contains(name)) { -+ System.out.println("need to map " + clazz.getName() + " (" + name.toLowerCase() + ")"); -+ } -+ -+ // did we rename this key? -+ return deobfuscationMap.getOrDefault(name, name); -+ } -+ -+ public static EnumSet vanillaToPaper(Goal goal) { -+ EnumSet goals = EnumSet.noneOf(GoalType.class); -+ for (GoalType type : GoalType.values()) { -+ if (goal.getFlags().contains(paperToVanilla(type))) { -+ goals.add(type); -+ } -+ } -+ return goals; -+ } -+ -+ public static GoalType vanillaToPaper(Goal.Flag type) { -+ switch (type) { -+ case MOVE: -+ return GoalType.MOVE; -+ case LOOK: -+ return GoalType.LOOK; -+ case JUMP: -+ return GoalType.JUMP; -+ case UNKNOWN_BEHAVIOR: -+ return GoalType.UNKNOWN_BEHAVIOR; -+ case TARGET: -+ return GoalType.TARGET; -+ default: -+ throw new IllegalArgumentException("Unknown vanilla mob goal type " + type.name()); -+ } -+ } -+ -+ public static EnumSet paperToVanilla(EnumSet types) { -+ EnumSet goals = EnumSet.noneOf(Goal.Flag.class); -+ for (GoalType type : types) { -+ goals.add(paperToVanilla(type)); -+ } -+ return goals; -+ } -+ -+ public static Goal.Flag paperToVanilla(GoalType type) { -+ switch (type) { -+ case MOVE: -+ return Goal.Flag.MOVE; -+ case LOOK: -+ return Goal.Flag.LOOK; -+ case JUMP: -+ return Goal.Flag.JUMP; -+ case UNKNOWN_BEHAVIOR: -+ return Goal.Flag.UNKNOWN_BEHAVIOR; -+ case TARGET: -+ return Goal.Flag.TARGET; -+ default: -+ throw new IllegalArgumentException("Unknown paper mob goal type " + type.name()); -+ } -+ } -+ -+ public static GoalKey getKey(Class goalClass) { -+ String name = getUsableName(goalClass); -+ if (ignored.contains(name)) { -+ //noinspection unchecked -+ return (GoalKey) GoalKey.of(Mob.class, NamespacedKey.minecraft(name)); -+ } -+ return GoalKey.of(getEntity(goalClass), NamespacedKey.minecraft(name)); -+ } -+ -+ public static Class getEntity(Class goalClass) { -+ //noinspection unchecked -+ return (Class) entityClassCache.computeIfAbsent(goalClass, key -> { -+ for (Constructor ctor : key.getDeclaredConstructors()) { -+ for (int i = 0; i < ctor.getParameterCount(); i++) { -+ Class param = ctor.getParameterTypes()[i]; -+ if (net.minecraft.world.entity.Mob.class.isAssignableFrom(param)) { -+ //noinspection unchecked -+ return toBukkitClass((Class) param); -+ } else if (RangedAttackMob.class.isAssignableFrom(param)) { -+ return RangedEntity.class; -+ } -+ } -+ } -+ throw new RuntimeException("Can't figure out applicable entity for mob goal " + goalClass); // maybe just return EntityInsentient? -+ }); -+ } -+ -+ public static Class toBukkitClass(Class nmsClass) { -+ Class bukkitClass = bukkitMap.get(nmsClass); -+ if (bukkitClass == null) { -+ throw new RuntimeException("Can't figure out applicable bukkit entity for nms entity " + nmsClass); // maybe just return Mob? -+ } -+ return bukkitClass; -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b5f75ad725f5933db8f0688b2c0b27d620919241 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java -@@ -0,0 +1,53 @@ -+package com.destroystokyo.paper.entity.ai; -+ -+import org.bukkit.entity.Mob; -+ -+/** -+ * Wraps api in vanilla -+ */ -+public class PaperCustomGoal extends net.minecraft.world.entity.ai.goal.Goal { -+ -+ private final Goal handle; -+ -+ public PaperCustomGoal(Goal handle) { -+ this.handle = handle; -+ -+ this.setFlags(MobGoalHelper.paperToVanilla(handle.getTypes())); -+ if (this.getFlags().size() == 0) { -+ this.getFlags().add(Flag.UNKNOWN_BEHAVIOR); -+ } -+ } -+ -+ @Override -+ public boolean canUse() { -+ return handle.shouldActivate(); -+ } -+ -+ @Override -+ public boolean canContinueToUse() { -+ return handle.shouldStayActive(); -+ } -+ -+ @Override -+ public void start() { -+ handle.start(); -+ } -+ -+ @Override -+ public void stop() { -+ handle.stop(); -+ } -+ -+ @Override -+ public void tick() { -+ handle.tick(); -+ } -+ -+ public Goal getHandle() { -+ return handle; -+ } -+ -+ public GoalKey getKey() { -+ return handle.getKey(); -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java -new file mode 100644 -index 0000000000000000000000000000000000000000..953b0f88cbf0e73c390f8086344f772ac03192bb ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java -@@ -0,0 +1,213 @@ -+package com.destroystokyo.paper.entity.ai; -+ -+import java.util.Collection; -+import java.util.EnumSet; -+import java.util.HashSet; -+import java.util.LinkedList; -+import java.util.List; -+import java.util.Set; -+import net.minecraft.world.entity.ai.goal.GoalSelector; -+import net.minecraft.world.entity.ai.goal.WrappedGoal; -+import org.bukkit.craftbukkit.entity.CraftMob; -+import org.bukkit.entity.Mob; -+ -+public class PaperMobGoals implements MobGoals { -+ -+ @Override -+ public void addGoal(T mob, int priority, Goal goal) { -+ CraftMob craftMob = (CraftMob) mob; -+ getHandle(craftMob, goal.getTypes()).addGoal(priority, new PaperCustomGoal<>(goal)); -+ } -+ -+ @Override -+ public void removeGoal(T mob, Goal goal) { -+ CraftMob craftMob = (CraftMob) mob; -+ if (goal instanceof PaperCustomGoal) { -+ getHandle(craftMob, goal.getTypes()).removeGoal((net.minecraft.world.entity.ai.goal.Goal) goal); -+ } else if (goal instanceof PaperVanillaGoal) { -+ getHandle(craftMob, goal.getTypes()).removeGoal(((PaperVanillaGoal) goal).getHandle()); -+ } else { -+ List toRemove = new LinkedList<>(); -+ for (WrappedGoal item : getHandle(craftMob, goal.getTypes()).getAvailableGoals()) { -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ if (((PaperCustomGoal) item.getGoal()).getHandle() == goal) { -+ toRemove.add(item.getGoal()); -+ } -+ } -+ } -+ -+ for (net.minecraft.world.entity.ai.goal.Goal g : toRemove) { -+ getHandle(craftMob, goal.getTypes()).removeGoal(g); -+ } -+ } -+ } -+ -+ @Override -+ public void removeAllGoals(T mob) { -+ for (GoalType type : GoalType.values()) { -+ removeAllGoals(mob, type); -+ } -+ } -+ -+ @Override -+ public void removeAllGoals(T mob, GoalType type) { -+ for (Goal goal : getAllGoals(mob, type)) { -+ removeGoal(mob, goal); -+ } -+ } -+ -+ @Override -+ public void removeGoal(T mob, GoalKey key) { -+ for (Goal goal : getGoals(mob, key)) { -+ removeGoal(mob, goal); -+ } -+ } -+ -+ @Override -+ public boolean hasGoal(T mob, GoalKey key) { -+ for (Goal g : getAllGoals(mob)) { -+ if (g.getKey().equals(key)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ @Override -+ public Goal getGoal(T mob, GoalKey key) { -+ for (Goal g : getAllGoals(mob)) { -+ if (g.getKey().equals(key)) { -+ return g; -+ } -+ } -+ return null; -+ } -+ -+ @Override -+ public Collection> getGoals(T mob, GoalKey key) { -+ Set> goals = new HashSet<>(); -+ for (Goal g : getAllGoals(mob)) { -+ if (g.getKey().equals(key)) { -+ goals.add(g); -+ } -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getAllGoals(T mob) { -+ Set> goals = new HashSet<>(); -+ for (GoalType type : GoalType.values()) { -+ goals.addAll(getAllGoals(mob, type)); -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getAllGoals(T mob, GoalType type) { -+ CraftMob craftMob = (CraftMob) mob; -+ Set> goals = new HashSet<>(); -+ for (WrappedGoal item : getHandle(craftMob, type).getAvailableGoals()) { -+ if (!item.getGoal().getFlags().contains(MobGoalHelper.paperToVanilla(type))) { -+ continue; -+ } -+ -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); -+ } else { -+ goals.add(item.getGoal().asPaperVanillaGoal()); -+ } -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getAllGoalsWithout(T mob, GoalType type) { -+ CraftMob craftMob = (CraftMob) mob; -+ Set> goals = new HashSet<>(); -+ for (GoalType internalType : GoalType.values()) { -+ if (internalType == type) { -+ continue; -+ } -+ for (WrappedGoal item : getHandle(craftMob, internalType).getAvailableGoals()) { -+ if (item.getGoal().getFlags().contains(MobGoalHelper.paperToVanilla(type))) { -+ continue; -+ } -+ -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); -+ } else { -+ goals.add(item.getGoal().asPaperVanillaGoal()); -+ } -+ } -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getRunningGoals(T mob) { -+ Set> goals = new HashSet<>(); -+ for (GoalType type : GoalType.values()) { -+ goals.addAll(getRunningGoals(mob, type)); -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getRunningGoals(T mob, GoalType type) { -+ CraftMob craftMob = (CraftMob) mob; -+ Set> goals = new HashSet<>(); -+ getHandle(craftMob, type).getRunningGoals() -+ .filter(item -> item.getGoal().getFlags().contains(MobGoalHelper.paperToVanilla(type))) -+ .forEach(item -> { -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); -+ } else { -+ goals.add(item.getGoal().asPaperVanillaGoal()); -+ } -+ }); -+ return goals; -+ } -+ -+ @Override -+ public Collection> getRunningGoalsWithout(T mob, GoalType type) { -+ CraftMob craftMob = (CraftMob) mob; -+ Set> goals = new HashSet<>(); -+ for (GoalType internalType : GoalType.values()) { -+ if (internalType == type) { -+ continue; -+ } -+ getHandle(craftMob, internalType).getRunningGoals() -+ .filter(item -> !item.getGoal().getFlags().contains(MobGoalHelper.paperToVanilla(type))) -+ .forEach(item -> { -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); -+ } else { -+ goals.add(item.getGoal().asPaperVanillaGoal()); -+ } -+ }); -+ } -+ return goals; -+ } -+ -+ private GoalSelector getHandle(CraftMob mob, EnumSet types) { -+ if (types.contains(GoalType.TARGET)) { -+ return mob.getHandle().targetSelector; -+ } else { -+ return mob.getHandle().goalSelector; -+ } -+ } -+ -+ private GoalSelector getHandle(CraftMob mob, GoalType type) { -+ if (type == GoalType.TARGET) { -+ return mob.getHandle().targetSelector; -+ } else { -+ return mob.getHandle().goalSelector; -+ } -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b5c594a5499556ad452d9939c75e150af8252e90 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java -@@ -0,0 +1,61 @@ -+package com.destroystokyo.paper.entity.ai; -+ -+import java.util.EnumSet; -+import net.minecraft.world.entity.ai.goal.Goal; -+import org.bukkit.entity.Mob; -+ -+/** -+ * Wraps vanilla in api -+ */ -+public class PaperVanillaGoal implements VanillaGoal { -+ -+ private final Goal handle; -+ private final GoalKey key; -+ -+ private final EnumSet types; -+ -+ public PaperVanillaGoal(Goal handle) { -+ this.handle = handle; -+ this.key = MobGoalHelper.getKey(handle.getClass()); -+ this.types = MobGoalHelper.vanillaToPaper(handle); -+ } -+ -+ public Goal getHandle() { -+ return handle; -+ } -+ -+ @Override -+ public boolean shouldActivate() { -+ return handle.canUse(); -+ } -+ -+ @Override -+ public boolean shouldStayActive() { -+ return handle.canContinueToUse(); -+ } -+ -+ @Override -+ public void start() { -+ handle.start(); -+ } -+ -+ @Override -+ public void stop() { -+ handle.stop(); -+ } -+ -+ @Override -+ public void tick() { -+ handle.tick(); -+ } -+ -+ @Override -+ public GoalKey getKey() { -+ return key; -+ } -+ -+ @Override -+ public EnumSet getTypes() { -+ return types; -+ } -+} -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java -index 6667ecc4b7eded4e20a415cef1e1b1179e6710b8..16f9a98b8a939e5ca7e2dc04f87134a7ed66736b 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java -@@ -51,7 +51,19 @@ public abstract class Goal { - return Mth.positiveCeilDiv(serverTicks, 2); - } - -+ // Paper start - Mob goal api -+ private com.destroystokyo.paper.entity.ai.PaperVanillaGoal vanillaGoal; -+ public com.destroystokyo.paper.entity.ai.Goal asPaperVanillaGoal() { -+ if(this.vanillaGoal == null) { -+ this.vanillaGoal = new com.destroystokyo.paper.entity.ai.PaperVanillaGoal<>(this); -+ } -+ //noinspection unchecked -+ return (com.destroystokyo.paper.entity.ai.Goal) this.vanillaGoal; -+ } -+ // Paper end - Mob goal api -+ - public static enum Flag { -+ UNKNOWN_BEHAVIOR, // Paper - add UNKNOWN_BEHAVIOR - MOVE, - LOOK, - JUMP, -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index a40c30be9923da16f84f11e6678f1d380ad5bc69..9c27232b680e42c08a37347ed84669686a0cb2d1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2877,5 +2877,11 @@ public final class CraftServer implements Server { - public boolean isStopping() { - return net.minecraft.server.MinecraftServer.getServer().hasStopped(); - } -+ -+ private com.destroystokyo.paper.entity.ai.MobGoals mobGoals = new com.destroystokyo.paper.entity.ai.PaperMobGoals(); -+ @Override -+ public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { -+ return mobGoals; -+ } - // Paper end - } diff --git a/patches/server/0369-Option-for-maximum-exp-value-when-merging-orbs.patch b/patches/server/0369-Option-for-maximum-exp-value-when-merging-orbs.patch new file mode 100644 index 000000000000..92d8c7cf1759 --- /dev/null +++ b/patches/server/0369-Option-for-maximum-exp-value-when-merging-orbs.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 10 Nov 2017 23:03:12 -0500 +Subject: [PATCH] Option for maximum exp value when merging orbs + + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index c9fbc54d7ba10da4f4c376e029b64fb0249171a3..fdfefa9cba2feb1d27676a6003780382b8f99cb4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -702,16 +702,30 @@ public class CraftEventFactory { + if (entity instanceof net.minecraft.world.entity.ExperienceOrb xp) { + double radius = world.spigotConfig.expMerge; + if (radius > 0) { ++ // Paper start - Maximum exp value when merging; Whole section has been tweaked, see comments for specifics ++ final int maxValue = world.paperConfig().entities.behavior.experienceMergeMaxValue; ++ final boolean mergeUnconditionally = world.paperConfig().entities.behavior.experienceMergeMaxValue <= 0; ++ if (mergeUnconditionally || xp.value < maxValue) { // Paper - Skip iteration if unnecessary ++ + List entities = world.getEntities(entity, entity.getBoundingBox().inflate(radius, radius, radius)); + for (Entity e : entities) { + if (e instanceof net.minecraft.world.entity.ExperienceOrb loopItem) { +- if (!loopItem.isRemoved()) { ++ // Paper start ++ if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue)) { ++ long newTotal = (long)xp.value + (long)loopItem.value; ++ if ((int) newTotal < 0) continue; // Overflow ++ if (maxValue > 0 && newTotal > (long)maxValue) { ++ loopItem.value = (int) (newTotal - maxValue); ++ xp.value = maxValue; ++ } else { + xp.value += loopItem.value; + loopItem.discard(); ++ } // Paper end - Maximum exp value when merging + } + } + } + } ++ } // Paper end - End iteration skip check - All tweaking ends here + } + // Spigot end + diff --git a/patches/server/0370-ExperienceOrbMergeEvent.patch b/patches/server/0370-ExperienceOrbMergeEvent.patch new file mode 100644 index 000000000000..790fbce8a6d5 --- /dev/null +++ b/patches/server/0370-ExperienceOrbMergeEvent.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 19 Dec 2017 22:57:26 -0500 +Subject: [PATCH] ExperienceOrbMergeEvent + +Has to be reimplemented at one point maybe +Fired when the server is about to merge 2 experience orbs +Plugins can cancel this if they want to ensure experience orbs do not lose important +metadata such as spawn reason, or conditionally move data from source to target. + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index fdfefa9cba2feb1d27676a6003780382b8f99cb4..2fbcd11ff457c9569bf011f94ed9658c7a85b743 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -711,7 +711,7 @@ public class CraftEventFactory { + for (Entity e : entities) { + if (e instanceof net.minecraft.world.entity.ExperienceOrb loopItem) { + // Paper start +- if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue)) { ++ if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue) && new com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent((org.bukkit.entity.ExperienceOrb) entity.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) loopItem.getBukkitEntity()).callEvent()) { // Paper - ExperienceOrbMergeEvent + long newTotal = (long)xp.value + (long)loopItem.value; + if ((int) newTotal < 0) continue; // Overflow + if (maxValue > 0 && newTotal > (long)maxValue) { diff --git a/patches/server/0370-Option-for-maximum-exp-value-when-merging-orbs.patch b/patches/server/0370-Option-for-maximum-exp-value-when-merging-orbs.patch deleted file mode 100644 index de7f3bafaa5f..000000000000 --- a/patches/server/0370-Option-for-maximum-exp-value-when-merging-orbs.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Fri, 10 Nov 2017 23:03:12 -0500 -Subject: [PATCH] Option for maximum exp value when merging orbs - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 77e1ff2725e015b76f6919f3f51d5d56a1e484a0..93213cbd9682505d679661d4fc506ae4b9c67eec 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -702,16 +702,30 @@ public class CraftEventFactory { - if (entity instanceof net.minecraft.world.entity.ExperienceOrb xp) { - double radius = world.spigotConfig.expMerge; - if (radius > 0) { -+ // Paper start - Maximum exp value when merging; Whole section has been tweaked, see comments for specifics -+ final int maxValue = world.paperConfig().entities.behavior.experienceMergeMaxValue; -+ final boolean mergeUnconditionally = world.paperConfig().entities.behavior.experienceMergeMaxValue <= 0; -+ if (mergeUnconditionally || xp.value < maxValue) { // Paper - Skip iteration if unnecessary -+ - List entities = world.getEntities(entity, entity.getBoundingBox().inflate(radius, radius, radius)); - for (Entity e : entities) { - if (e instanceof net.minecraft.world.entity.ExperienceOrb loopItem) { -- if (!loopItem.isRemoved()) { -+ // Paper start -+ if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue)) { -+ long newTotal = (long)xp.value + (long)loopItem.value; -+ if ((int) newTotal < 0) continue; // Overflow -+ if (maxValue > 0 && newTotal > (long)maxValue) { -+ loopItem.value = (int) (newTotal - maxValue); -+ xp.value = maxValue; -+ } else { - xp.value += loopItem.value; - loopItem.discard(); -+ } // Paper end - Maximum exp value when merging - } - } - } - } -+ } // Paper end - End iteration skip check - All tweaking ends here - } - // Spigot end - diff --git a/patches/server/0371-ExperienceOrbMergeEvent.patch b/patches/server/0371-ExperienceOrbMergeEvent.patch deleted file mode 100644 index 49e9d240ea60..000000000000 --- a/patches/server/0371-ExperienceOrbMergeEvent.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 19 Dec 2017 22:57:26 -0500 -Subject: [PATCH] ExperienceOrbMergeEvent - -Has to be reimplemented at one point maybe -Fired when the server is about to merge 2 experience orbs -Plugins can cancel this if they want to ensure experience orbs do not lose important -metadata such as spawn reason, or conditionally move data from source to target. - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 93213cbd9682505d679661d4fc506ae4b9c67eec..64236f5f126998157bb7bbae40ed0cd1550281e5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -711,7 +711,7 @@ public class CraftEventFactory { - for (Entity e : entities) { - if (e instanceof net.minecraft.world.entity.ExperienceOrb loopItem) { - // Paper start -- if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue)) { -+ if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue) && new com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent((org.bukkit.entity.ExperienceOrb) entity.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) loopItem.getBukkitEntity()).callEvent()) { // Paper - ExperienceOrbMergeEvent - long newTotal = (long)xp.value + (long)loopItem.value; - if ((int) newTotal < 0) continue; // Overflow - if (maxValue > 0 && newTotal > (long)maxValue) { diff --git a/patches/server/0371-Fix-PotionEffect-ignores-icon-flag.patch b/patches/server/0371-Fix-PotionEffect-ignores-icon-flag.patch new file mode 100644 index 000000000000..7f8b5c39dd43 --- /dev/null +++ b/patches/server/0371-Fix-PotionEffect-ignores-icon-flag.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Fri, 8 May 2020 00:49:18 -0400 +Subject: [PATCH] Fix PotionEffect ignores icon flag + +Co-authored-by: Tamion <70228790+notTamion@users.noreply.github.com> + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index fd18531ff94daa6dc2994a69e1e647078a5a664c..9a9d119e76fca75a9e531f4bbd204ab8eb9a1263 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -468,7 +468,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + + @Override + public boolean addPotionEffect(PotionEffect effect, boolean force) { +- this.getHandle().addEffect(new MobEffectInstance(CraftPotionEffectType.bukkitToMinecraft(effect.getType()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()), EntityPotionEffectEvent.Cause.PLUGIN); ++ this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon + return true; + } + +@@ -489,7 +489,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + @Override + public PotionEffect getPotionEffect(PotionEffectType type) { + MobEffectInstance handle = this.getHandle().getEffect(CraftPotionEffectType.bukkitToMinecraft(type)); +- return (handle == null) ? null : new PotionEffect(CraftPotionEffectType.minecraftToBukkit(handle.getEffect()), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible()); ++ return (handle == null) ? null : org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(handle); // Paper + } + + @Override +@@ -501,7 +501,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public Collection getActivePotionEffects() { + List effects = new ArrayList(); + for (MobEffectInstance handle : this.getHandle().activeEffects.values()) { +- effects.add(new PotionEffect(CraftPotionEffectType.minecraftToBukkit(handle.getEffect()), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible())); ++ effects.add(org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(handle)); // Paper + } + return effects; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java +index e29679a92da5ec05e122bb972a5ee469059a7a0a..844fb8c662a409670f631228f687d85c5436d3dd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java ++++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java +@@ -73,7 +73,7 @@ public class CraftPotionUtil { + + public static MobEffectInstance fromBukkit(PotionEffect effect) { + MobEffect type = CraftPotionEffectType.bukkitToMinecraft(effect.getType()); +- return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()); ++ return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon()); // Paper + } + + public static PotionEffect toBukkit(MobEffectInstance effect) { +@@ -82,7 +82,7 @@ public class CraftPotionUtil { + int duration = effect.getDuration(); + boolean ambient = effect.isAmbient(); + boolean particles = effect.isVisible(); +- return new PotionEffect(type, duration, amp, ambient, particles); ++ return new PotionEffect(type, duration, amp, ambient, particles, effect.showIcon()); // Paper + } + + public static boolean equals(MobEffect mobEffect, PotionEffectType type) { diff --git a/patches/server/0372-Fix-PotionEffect-ignores-icon-flag.patch b/patches/server/0372-Fix-PotionEffect-ignores-icon-flag.patch deleted file mode 100644 index 59291a76d64e..000000000000 --- a/patches/server/0372-Fix-PotionEffect-ignores-icon-flag.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Fri, 8 May 2020 00:49:18 -0400 -Subject: [PATCH] Fix PotionEffect ignores icon flag - -Co-authored-by: Tamion <70228790+notTamion@users.noreply.github.com> - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index d5135ac97b2d4af7391a58799497c649cfcf8041..c15db60f4a198c0fe754c3579ff93870e968e639 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -455,7 +455,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - - @Override - public boolean addPotionEffect(PotionEffect effect, boolean force) { -- this.getHandle().addEffect(new MobEffectInstance(CraftPotionEffectType.bukkitToMinecraft(effect.getType()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()), EntityPotionEffectEvent.Cause.PLUGIN); -+ this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon - return true; - } - -@@ -476,7 +476,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - @Override - public PotionEffect getPotionEffect(PotionEffectType type) { - MobEffectInstance handle = this.getHandle().getEffect(CraftPotionEffectType.bukkitToMinecraft(type)); -- return (handle == null) ? null : new PotionEffect(CraftPotionEffectType.minecraftToBukkit(handle.getEffect()), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible()); -+ return (handle == null) ? null : org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(handle); // Paper - } - - @Override -@@ -488,7 +488,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - public Collection getActivePotionEffects() { - List effects = new ArrayList(); - for (MobEffectInstance handle : this.getHandle().activeEffects.values()) { -- effects.add(new PotionEffect(CraftPotionEffectType.minecraftToBukkit(handle.getEffect()), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible())); -+ effects.add(org.bukkit.craftbukkit.potion.CraftPotionUtil.toBukkit(handle)); // Paper - } - return effects; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java -index e29679a92da5ec05e122bb972a5ee469059a7a0a..844fb8c662a409670f631228f687d85c5436d3dd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java -+++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java -@@ -73,7 +73,7 @@ public class CraftPotionUtil { - - public static MobEffectInstance fromBukkit(PotionEffect effect) { - MobEffect type = CraftPotionEffectType.bukkitToMinecraft(effect.getType()); -- return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()); -+ return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon()); // Paper - } - - public static PotionEffect toBukkit(MobEffectInstance effect) { -@@ -82,7 +82,7 @@ public class CraftPotionUtil { - int duration = effect.getDuration(); - boolean ambient = effect.isAmbient(); - boolean particles = effect.isVisible(); -- return new PotionEffect(type, duration, amp, ambient, particles); -+ return new PotionEffect(type, duration, amp, ambient, particles, effect.showIcon()); // Paper - } - - public static boolean equals(MobEffect mobEffect, PotionEffectType type) { diff --git a/patches/server/0373-Potential-bed-API.patch b/patches/server/0372-Potential-bed-API.patch similarity index 100% rename from patches/server/0373-Potential-bed-API.patch rename to patches/server/0372-Potential-bed-API.patch diff --git a/patches/server/0373-Wait-for-Async-Tasks-during-shutdown.patch b/patches/server/0373-Wait-for-Async-Tasks-during-shutdown.patch new file mode 100644 index 000000000000..bf00f6de7dea --- /dev/null +++ b/patches/server/0373-Wait-for-Async-Tasks-during-shutdown.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 10 May 2020 22:16:17 -0400 +Subject: [PATCH] Wait for Async Tasks during shutdown + +Server.reload() had this logic to give time for tasks to shutdown, +however shutdown did not... + +Adds a 5 second grace period for any async tasks to finish and warns +if any are still running after that delay just as reload does. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 390bc01c19d5fed7ed455ddf9823697469e0d7fe..f786bf10a42fa4e9f610416959c60c52729c277b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -931,6 +931,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { ++ try { ++ Thread.sleep(100); ++ } catch (InterruptedException e) {} ++ pollCount++; ++ } ++ ++ List overdueWorkers = getScheduler().getActiveWorkers(); ++ for (BukkitWorker worker : overdueWorkers) { ++ Plugin plugin = worker.getOwner(); ++ getLogger().log(Level.SEVERE, String.format( ++ "Nag author(s): '%s' of '%s' about the following: %s", ++ plugin.getPluginMeta().getAuthors(), ++ plugin.getPluginMeta().getDisplayName(), ++ "This plugin is not properly shutting down its async tasks when it is being shut down. This task may throw errors during the final shutdown logs and might not complete before process dies." ++ )); ++ } ++ } ++ // Paper end - Wait for Async Tasks during shutdown ++ + @Override + public void reloadData() { + ReloadCommand.reload(this.console); diff --git a/patches/server/0375-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch b/patches/server/0374-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch similarity index 100% rename from patches/server/0375-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch rename to patches/server/0374-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch diff --git a/patches/server/0374-Wait-for-Async-Tasks-during-shutdown.patch b/patches/server/0374-Wait-for-Async-Tasks-during-shutdown.patch deleted file mode 100644 index 90df74f85d04..000000000000 --- a/patches/server/0374-Wait-for-Async-Tasks-during-shutdown.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 10 May 2020 22:16:17 -0400 -Subject: [PATCH] Wait for Async Tasks during shutdown - -Server.reload() had this logic to give time for tasks to shutdown, -however shutdown did not... - -Adds a 5 second grace period for any async tasks to finish and warns -if any are still running after that delay just as reload does. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 4170f834c63807440c9bcda76cdcf93807de0eb1..1913eb3eb6a1ddc2d96240cbc50d4cf7325ebd3b 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -931,6 +931,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { -+ try { -+ Thread.sleep(100); -+ } catch (InterruptedException e) {} -+ pollCount++; -+ } -+ -+ List overdueWorkers = getScheduler().getActiveWorkers(); -+ for (BukkitWorker worker : overdueWorkers) { -+ Plugin plugin = worker.getOwner(); -+ getLogger().log(Level.SEVERE, String.format( -+ "Nag author(s): '%s' of '%s' about the following: %s", -+ plugin.getPluginMeta().getAuthors(), -+ plugin.getPluginMeta().getDisplayName(), -+ "This plugin is not properly shutting down its async tasks when it is being shut down. This task may throw errors during the final shutdown logs and might not complete before process dies." -+ )); -+ } -+ } -+ // Paper end - Wait for Async Tasks during shutdown -+ - @Override - public void reloadData() { - ReloadCommand.reload(this.console); diff --git a/patches/server/0376-Ensure-safe-gateway-teleport.patch b/patches/server/0375-Ensure-safe-gateway-teleport.patch similarity index 100% rename from patches/server/0376-Ensure-safe-gateway-teleport.patch rename to patches/server/0375-Ensure-safe-gateway-teleport.patch diff --git a/patches/server/0377-Add-option-for-console-having-all-permissions.patch b/patches/server/0376-Add-option-for-console-having-all-permissions.patch similarity index 100% rename from patches/server/0377-Add-option-for-console-having-all-permissions.patch rename to patches/server/0376-Add-option-for-console-having-all-permissions.patch diff --git a/patches/server/0378-Fix-villager-trading-demand-MC-163962.patch b/patches/server/0377-Fix-villager-trading-demand-MC-163962.patch similarity index 100% rename from patches/server/0378-Fix-villager-trading-demand-MC-163962.patch rename to patches/server/0377-Fix-villager-trading-demand-MC-163962.patch diff --git a/patches/server/0379-Maps-shouldn-t-load-chunks.patch b/patches/server/0378-Maps-shouldn-t-load-chunks.patch similarity index 100% rename from patches/server/0379-Maps-shouldn-t-load-chunks.patch rename to patches/server/0378-Maps-shouldn-t-load-chunks.patch diff --git a/patches/server/0380-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch b/patches/server/0379-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch similarity index 100% rename from patches/server/0380-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch rename to patches/server/0379-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch diff --git a/patches/server/0381-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch b/patches/server/0380-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch similarity index 100% rename from patches/server/0381-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch rename to patches/server/0380-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch diff --git a/patches/server/0382-Fix-piston-physics-inconsistency-MC-188840.patch b/patches/server/0381-Fix-piston-physics-inconsistency-MC-188840.patch similarity index 100% rename from patches/server/0382-Fix-piston-physics-inconsistency-MC-188840.patch rename to patches/server/0381-Fix-piston-physics-inconsistency-MC-188840.patch diff --git a/patches/server/0382-Fix-sand-duping.patch b/patches/server/0382-Fix-sand-duping.patch new file mode 100644 index 000000000000..955e201da124 --- /dev/null +++ b/patches/server/0382-Fix-sand-duping.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 12 Jun 2020 13:33:19 -0700 +Subject: [PATCH] Fix sand duping + +If the falling block dies during teleportation (entity#move), then we need +to detect that by placing a check after the move. + +diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +index e2f90b822f25bf100eaba0cf4518849f788ee2fa..67875e854d66b62c36fcca455f02f5abf3ebfdb3 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -131,6 +131,11 @@ public class FallingBlockEntity extends Entity { + + @Override + public void tick() { ++ // Paper start - fix sand duping ++ if (this.isRemoved()) { ++ return; ++ } ++ // Paper end - fix sand duping + if (this.blockState.isAir()) { + this.discard(); + } else { +@@ -142,6 +147,11 @@ public class FallingBlockEntity extends Entity { + } + + this.move(MoverType.SELF, this.getDeltaMovement()); ++ // Paper start - fix sand duping ++ if (this.isRemoved()) { ++ return; ++ } ++ // Paper end - fix sand duping + // Paper start - Configurable falling blocks height nerf + if (this.level().paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) { + if (this.dropItem && this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { diff --git a/patches/server/0384-Fix-missing-chunks-due-to-integer-overflow.patch b/patches/server/0383-Fix-missing-chunks-due-to-integer-overflow.patch similarity index 100% rename from patches/server/0384-Fix-missing-chunks-due-to-integer-overflow.patch rename to patches/server/0383-Fix-missing-chunks-due-to-integer-overflow.patch diff --git a/patches/server/0383-Fix-sand-duping.patch b/patches/server/0383-Fix-sand-duping.patch deleted file mode 100644 index 5e5e88e51f67..000000000000 --- a/patches/server/0383-Fix-sand-duping.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 12 Jun 2020 13:33:19 -0700 -Subject: [PATCH] Fix sand duping - -If the falling block dies during teleportation (entity#move), then we need -to detect that by placing a check after the move. - -diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -index 1b9dfc32ce13dc9ec2fab60750dc1184dbddc5bd..2452c7f0a3ed1faf9b90351bea3389382c677d05 100644 ---- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -@@ -131,6 +131,11 @@ public class FallingBlockEntity extends Entity { - - @Override - public void tick() { -+ // Paper start - fix sand duping -+ if (this.isRemoved()) { -+ return; -+ } -+ // Paper end - fix sand duping - if (this.blockState.isAir()) { - this.discard(); - } else { -@@ -142,6 +147,11 @@ public class FallingBlockEntity extends Entity { - } - - this.move(MoverType.SELF, this.getDeltaMovement()); -+ // Paper start - fix sand duping -+ if (this.isRemoved()) { -+ return; -+ } -+ // Paper end - fix sand duping - // Paper start - Configurable falling blocks height nerf - if (this.level().paperConfig().fixes.fallingBlockHeightNerf.test(v -> this.getY() > v)) { - if (this.dropItem && this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { diff --git a/patches/server/0384-Prevent-position-desync-causing-tp-exploit.patch b/patches/server/0384-Prevent-position-desync-causing-tp-exploit.patch new file mode 100644 index 000000000000..60bf017cb9b1 --- /dev/null +++ b/patches/server/0384-Prevent-position-desync-causing-tp-exploit.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 12 Jun 2020 16:51:39 -0700 +Subject: [PATCH] Prevent position desync causing tp exploit + +Caused the server to revert to the player's overworld coordinates +after teleporting into the end. + +Sidenote: The underlying issue is that the move call can teleport +entities and do other things like kill the entity. In the future, +to fix all exploits derieved from this usually unexpected +behaviour, we need to move all of this dangerous logic outside +of the move call and into an appropriate place in the tick method. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 874a1998b6aaff9f4c7818481298ad51e1adc525..67470ad0c09ae94e5fab775a28cdefdf1241c5fa 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1366,6 +1366,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + this.player.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); + this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move ++ // Paper start - prevent position desync ++ if (this.awaitingPositionFromClient != null) { ++ return; // ... thanks Mojang for letting move calls teleport across dimensions. ++ } ++ // Paper end - prevent position desync + double d11 = d7; + + d6 = d0 - this.player.getX(); diff --git a/patches/server/0386-Inventory-getHolder-method-without-block-snapshot.patch b/patches/server/0385-Inventory-getHolder-method-without-block-snapshot.patch similarity index 100% rename from patches/server/0386-Inventory-getHolder-method-without-block-snapshot.patch rename to patches/server/0385-Inventory-getHolder-method-without-block-snapshot.patch diff --git a/patches/server/0385-Prevent-position-desync-causing-tp-exploit.patch b/patches/server/0385-Prevent-position-desync-causing-tp-exploit.patch deleted file mode 100644 index 9ce0b93c5a8d..000000000000 --- a/patches/server/0385-Prevent-position-desync-causing-tp-exploit.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 12 Jun 2020 16:51:39 -0700 -Subject: [PATCH] Prevent position desync causing tp exploit - -Caused the server to revert to the player's overworld coordinates -after teleporting into the end. - -Sidenote: The underlying issue is that the move call can teleport -entities and do other things like kill the entity. In the future, -to fix all exploits derieved from this usually unexpected -behaviour, we need to move all of this dangerous logic outside -of the move call and into an appropriate place in the tick method. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 2e239328e31318e873973f86422d7aa469ee61e1..41c7156c03da9660ce9d0bfb6e06350e200e8aed 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1366,6 +1366,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - this.player.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); - this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move -+ // Paper start - prevent position desync -+ if (this.awaitingPositionFromClient != null) { -+ return; // ... thanks Mojang for letting move calls teleport across dimensions. -+ } -+ // Paper end - prevent position desync - double d11 = d7; - - d6 = d0 - this.player.getX(); diff --git a/patches/server/0387-Improve-Arrow-API.patch b/patches/server/0386-Improve-Arrow-API.patch similarity index 100% rename from patches/server/0387-Improve-Arrow-API.patch rename to patches/server/0386-Improve-Arrow-API.patch diff --git a/patches/server/0387-Add-PlayerRecipeBookClickEvent.patch b/patches/server/0387-Add-PlayerRecipeBookClickEvent.patch new file mode 100644 index 000000000000..d3bf03c1b3ff --- /dev/null +++ b/patches/server/0387-Add-PlayerRecipeBookClickEvent.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Fri, 5 Jun 2020 18:24:06 -0400 +Subject: [PATCH] Add PlayerRecipeBookClickEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 67470ad0c09ae94e5fab775a28cdefdf1241c5fa..98826ba4ab062e029359968c221ba320b2d2daeb 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2962,16 +2962,40 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + if (!this.player.containerMenu.stillValid(this.player)) { + ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); + } else { ++ // Paper start - Add PlayerRecipeBookClickEvent ++ ResourceLocation recipeName = packet.getRecipe(); ++ boolean makeAll = packet.isShiftDown(); ++ com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent paperEvent = new com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent( ++ this.player.getBukkitEntity(), org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(recipeName), makeAll ++ ); ++ if (!paperEvent.callEvent()) { ++ return; ++ } ++ recipeName = CraftNamespacedKey.toMinecraft(paperEvent.getRecipe()); ++ makeAll = paperEvent.isMakeAll(); ++ if (org.bukkit.event.player.PlayerRecipeBookClickEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ // Paper end - Add PlayerRecipeBookClickEvent + // CraftBukkit start - implement PlayerRecipeBookClickEvent +- org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(CraftNamespacedKey.fromMinecraft(packet.getRecipe())); ++ org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(CraftNamespacedKey.fromMinecraft(recipeName)); // Paper + if (recipe == null) { + return; + } +- org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, packet.isShiftDown()); ++ // Paper start - Add PlayerRecipeBookClickEvent ++ org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, makeAll); ++ recipeName = CraftNamespacedKey.toMinecraft(((org.bukkit.Keyed) event.getRecipe()).getKey()); ++ makeAll = event.isShiftClick(); ++ } ++ if (!(this.player.containerMenu instanceof RecipeBookMenu recipeBookMenu)) { ++ return; ++ } ++ // Paper end - Add PlayerRecipeBookClickEvent + + // Cast to keyed should be safe as the recipe will never be a MerchantRecipe. +- this.server.getRecipeManager().byKey(CraftNamespacedKey.toMinecraft(((org.bukkit.Keyed) event.getRecipe()).getKey())).ifPresent((recipeholder) -> { +- ((RecipeBookMenu) this.player.containerMenu).handlePlacement(event.isShiftClick(), recipeholder, this.player); ++ // Paper start - Add PlayerRecipeBookClickEvent ++ final boolean finalMakeAll = makeAll; ++ this.server.getRecipeManager().byKey(recipeName).ifPresent((recipeholder) -> { ++ recipeBookMenu.handlePlacement(finalMakeAll, recipeholder, this.player); ++ // Paper end - Add PlayerRecipeBookClickEvent + }); + // CraftBukkit end + } diff --git a/patches/server/0388-Add-PlayerRecipeBookClickEvent.patch b/patches/server/0388-Add-PlayerRecipeBookClickEvent.patch deleted file mode 100644 index 4862a88a6308..000000000000 --- a/patches/server/0388-Add-PlayerRecipeBookClickEvent.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Fri, 5 Jun 2020 18:24:06 -0400 -Subject: [PATCH] Add PlayerRecipeBookClickEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 41c7156c03da9660ce9d0bfb6e06350e200e8aed..66727b650e31ceb587657e112a1b0e7be5d47608 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2962,16 +2962,40 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - if (!this.player.containerMenu.stillValid(this.player)) { - ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); - } else { -+ // Paper start - Add PlayerRecipeBookClickEvent -+ ResourceLocation recipeName = packet.getRecipe(); -+ boolean makeAll = packet.isShiftDown(); -+ com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent paperEvent = new com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent( -+ this.player.getBukkitEntity(), org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(recipeName), makeAll -+ ); -+ if (!paperEvent.callEvent()) { -+ return; -+ } -+ recipeName = CraftNamespacedKey.toMinecraft(paperEvent.getRecipe()); -+ makeAll = paperEvent.isMakeAll(); -+ if (org.bukkit.event.player.PlayerRecipeBookClickEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ // Paper end - Add PlayerRecipeBookClickEvent - // CraftBukkit start - implement PlayerRecipeBookClickEvent -- org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(CraftNamespacedKey.fromMinecraft(packet.getRecipe())); -+ org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(CraftNamespacedKey.fromMinecraft(recipeName)); // Paper - if (recipe == null) { - return; - } -- org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, packet.isShiftDown()); -+ // Paper start - Add PlayerRecipeBookClickEvent -+ org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, makeAll); -+ recipeName = CraftNamespacedKey.toMinecraft(((org.bukkit.Keyed) event.getRecipe()).getKey()); -+ makeAll = event.isShiftClick(); -+ } -+ if (!(this.player.containerMenu instanceof RecipeBookMenu recipeBookMenu)) { -+ return; -+ } -+ // Paper end - Add PlayerRecipeBookClickEvent - - // Cast to keyed should be safe as the recipe will never be a MerchantRecipe. -- this.server.getRecipeManager().byKey(CraftNamespacedKey.toMinecraft(((org.bukkit.Keyed) event.getRecipe()).getKey())).ifPresent((recipeholder) -> { -- ((RecipeBookMenu) this.player.containerMenu).handlePlacement(event.isShiftClick(), recipeholder, this.player); -+ // Paper start - Add PlayerRecipeBookClickEvent -+ final boolean finalMakeAll = makeAll; -+ this.server.getRecipeManager().byKey(recipeName).ifPresent((recipeholder) -> { -+ recipeBookMenu.handlePlacement(finalMakeAll, recipeholder, this.player); -+ // Paper end - Add PlayerRecipeBookClickEvent - }); - // CraftBukkit end - } diff --git a/patches/server/0389-Hide-sync-chunk-writes-behind-flag.patch b/patches/server/0388-Hide-sync-chunk-writes-behind-flag.patch similarity index 100% rename from patches/server/0389-Hide-sync-chunk-writes-behind-flag.patch rename to patches/server/0388-Hide-sync-chunk-writes-behind-flag.patch diff --git a/patches/server/0389-Add-permission-for-command-blocks.patch b/patches/server/0389-Add-permission-for-command-blocks.patch new file mode 100644 index 000000000000..6948cb2f180a --- /dev/null +++ b/patches/server/0389-Add-permission-for-command-blocks.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 16 May 2020 10:05:30 +0200 +Subject: [PATCH] Add permission for command blocks + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index e8b12b27e5ec74afb940f575e5ce78e5905d55f4..c3eb4b6372eed0b7eb636f495ce494b676767b6e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -397,7 +397,7 @@ public class ServerPlayerGameMode { + BlockEntity tileentity = this.level.getBlockEntity(pos); + Block block = iblockdata.getBlock(); + +- if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) { ++ if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks() && !(block instanceof net.minecraft.world.level.block.CommandBlock && (this.player.isCreative() && this.player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission + this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3); + return false; + } else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) { +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 98826ba4ab062e029359968c221ba320b2d2daeb..e9a3edbb66c79664f35150f052b6ff3534d0904c 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -778,7 +778,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (!this.server.isCommandBlockEnabled()) { + this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); +- } else if (!this.player.canUseGameMasterBlocks()) { ++ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission + this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); + } else { + BaseCommandBlock commandblocklistenerabstract = null; +@@ -845,7 +845,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (!this.server.isCommandBlockEnabled()) { + this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); +- } else if (!this.player.canUseGameMasterBlocks()) { ++ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission + this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); + } else { + BaseCommandBlock commandblocklistenerabstract = packet.getCommandBlock(this.player.level()); +diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +index ac0aeb53176069d0835b6b08c8d871edae846763..c56f5173fda6b38c2dcaea196217f2f5a7d7c641 100644 +--- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java ++++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +@@ -198,7 +198,7 @@ public abstract class BaseCommandBlock implements CommandSource { + } + + public InteractionResult usedBy(Player player) { +- if (!player.canUseGameMasterBlocks()) { ++ if (!player.canUseGameMasterBlocks() && (!player.isCreative() || !player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission + return InteractionResult.PASS; + } else { + if (player.getCommandSenderWorld().isClientSide) { +diff --git a/src/main/java/net/minecraft/world/level/block/CommandBlock.java b/src/main/java/net/minecraft/world/level/block/CommandBlock.java +index 840226771819024de2c6e84f08f6e354e96474ba..7ef14e4441a329c680a5dfe4bfb5033ffcb8f9d5 100644 +--- a/src/main/java/net/minecraft/world/level/block/CommandBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CommandBlock.java +@@ -143,7 +143,7 @@ public class CommandBlock extends BaseEntityBlock implements GameMasterBlock { + public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + BlockEntity tileentity = world.getBlockEntity(pos); + +- if (tileentity instanceof CommandBlockEntity && player.canUseGameMasterBlocks()) { ++ if (tileentity instanceof CommandBlockEntity && (player.canUseGameMasterBlocks() || (player.isCreative() && player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission + player.openCommandBlock((CommandBlockEntity) tileentity); + return InteractionResult.sidedSuccess(world.isClientSide); + } else { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java +index 245ad120a36b6defca7e6889faae1ca5fc33d0c7..e0e61115ada9a49d4c528c5d4e02a1ca571d9531 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java +@@ -16,6 +16,7 @@ public final class CraftDefaultPermissions { + DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".nbt.copy", "Gives the user the ability to copy NBT in creative", org.bukkit.permissions.PermissionDefault.TRUE, parent); + DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick", "Gives the user the ability to use the debug stick in creative", org.bukkit.permissions.PermissionDefault.OP, parent); + DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick.always", "Gives the user the ability to use the debug stick in all game modes", org.bukkit.permissions.PermissionDefault.FALSE/* , parent */); // Paper - should not have this parent, as it's not a "vanilla" utility ++ DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".commandblock", "Gives the user the ability to use command blocks.", org.bukkit.permissions.PermissionDefault.OP, parent); // Paper + // Spigot end + parent.recalculatePermissibles(); + } diff --git a/patches/server/0390-Add-permission-for-command-blocks.patch b/patches/server/0390-Add-permission-for-command-blocks.patch deleted file mode 100644 index 134762ebfa61..000000000000 --- a/patches/server/0390-Add-permission-for-command-blocks.patch +++ /dev/null @@ -1,79 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Sat, 16 May 2020 10:05:30 +0200 -Subject: [PATCH] Add permission for command blocks - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index e8b12b27e5ec74afb940f575e5ce78e5905d55f4..c3eb4b6372eed0b7eb636f495ce494b676767b6e 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -397,7 +397,7 @@ public class ServerPlayerGameMode { - BlockEntity tileentity = this.level.getBlockEntity(pos); - Block block = iblockdata.getBlock(); - -- if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) { -+ if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks() && !(block instanceof net.minecraft.world.level.block.CommandBlock && (this.player.isCreative() && this.player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission - this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3); - return false; - } else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) { -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 66727b650e31ceb587657e112a1b0e7be5d47608..7ab30b8831b561374b7764148c4e5ed7451bafab 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -778,7 +778,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (!this.server.isCommandBlockEnabled()) { - this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); -- } else if (!this.player.canUseGameMasterBlocks()) { -+ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission - this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); - } else { - BaseCommandBlock commandblocklistenerabstract = null; -@@ -845,7 +845,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (!this.server.isCommandBlockEnabled()) { - this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); -- } else if (!this.player.canUseGameMasterBlocks()) { -+ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission - this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); - } else { - BaseCommandBlock commandblocklistenerabstract = packet.getCommandBlock(this.player.level()); -diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -index ac0aeb53176069d0835b6b08c8d871edae846763..c56f5173fda6b38c2dcaea196217f2f5a7d7c641 100644 ---- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -+++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -@@ -198,7 +198,7 @@ public abstract class BaseCommandBlock implements CommandSource { - } - - public InteractionResult usedBy(Player player) { -- if (!player.canUseGameMasterBlocks()) { -+ if (!player.canUseGameMasterBlocks() && (!player.isCreative() || !player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission - return InteractionResult.PASS; - } else { - if (player.getCommandSenderWorld().isClientSide) { -diff --git a/src/main/java/net/minecraft/world/level/block/CommandBlock.java b/src/main/java/net/minecraft/world/level/block/CommandBlock.java -index 840226771819024de2c6e84f08f6e354e96474ba..7ef14e4441a329c680a5dfe4bfb5033ffcb8f9d5 100644 ---- a/src/main/java/net/minecraft/world/level/block/CommandBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CommandBlock.java -@@ -143,7 +143,7 @@ public class CommandBlock extends BaseEntityBlock implements GameMasterBlock { - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { - BlockEntity tileentity = world.getBlockEntity(pos); - -- if (tileentity instanceof CommandBlockEntity && player.canUseGameMasterBlocks()) { -+ if (tileentity instanceof CommandBlockEntity && (player.canUseGameMasterBlocks() || (player.isCreative() && player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission - player.openCommandBlock((CommandBlockEntity) tileentity); - return InteractionResult.sidedSuccess(world.isClientSide); - } else { -diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java -index 245ad120a36b6defca7e6889faae1ca5fc33d0c7..e0e61115ada9a49d4c528c5d4e02a1ca571d9531 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java -@@ -16,6 +16,7 @@ public final class CraftDefaultPermissions { - DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".nbt.copy", "Gives the user the ability to copy NBT in creative", org.bukkit.permissions.PermissionDefault.TRUE, parent); - DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick", "Gives the user the ability to use the debug stick in creative", org.bukkit.permissions.PermissionDefault.OP, parent); - DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick.always", "Gives the user the ability to use the debug stick in all game modes", org.bukkit.permissions.PermissionDefault.FALSE/* , parent */); // Paper - should not have this parent, as it's not a "vanilla" utility -+ DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".commandblock", "Gives the user the ability to use command blocks.", org.bukkit.permissions.PermissionDefault.OP, parent); // Paper - // Spigot end - parent.recalculatePermissibles(); - } diff --git a/patches/server/0390-Ensure-Entity-position-and-AABB-are-never-invalid.patch b/patches/server/0390-Ensure-Entity-position-and-AABB-are-never-invalid.patch new file mode 100644 index 000000000000..3c79f532654e --- /dev/null +++ b/patches/server/0390-Ensure-Entity-position-and-AABB-are-never-invalid.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 10 May 2020 22:12:46 -0400 +Subject: [PATCH] Ensure Entity position and AABB are never invalid + +Co-authored-by: Spottedleaf + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7fc411c91b722d1f3494ef8eb37eeed7bbf10475..5528fc04283c4578a6c9b47e9ddcc63d6a77e76a 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -639,8 +639,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + public void setPos(double x, double y, double z) { +- this.setPosRaw(x, y, z); +- this.setBoundingBox(this.makeBoundingBox()); ++ this.setPosRaw(x, y, z, true); // Paper - Block invalid positions and bounding box; force update ++ // this.setBoundingBox(this.makeBoundingBox()); // Paper - Block invalid positions and bounding box; move into setPosRaw + } + + protected AABB makeBoundingBox() { +@@ -4133,7 +4133,29 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale); + } + ++ // Paper start - Block invalid positions and bounding box ++ public static boolean checkPosition(Entity entity, double newX, double newY, double newZ) { ++ if (Double.isFinite(newX) && Double.isFinite(newY) && Double.isFinite(newZ)) { ++ return true; ++ } ++ ++ String entityInfo; ++ try { ++ entityInfo = entity.toString(); ++ } catch (Exception ex) { ++ entityInfo = "[Entity info unavailable] "; ++ } ++ LOGGER.error("New entity position is invalid! Tried to set invalid position ({},{},{}) for entity {} located at {}, entity info: {}", newX, newY, newZ, entity.getClass().getName(), entity.position, entityInfo, new Throwable()); ++ return false; ++ } + public final void setPosRaw(double x, double y, double z) { ++ this.setPosRaw(x, y, z, false); ++ } ++ public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { ++ if (!checkPosition(this, x, y, z)) { ++ return; ++ } ++ // Paper end - Block invalid positions and bounding box + if (this.position.x != x || this.position.y != y || this.position.z != z) { + this.position = new Vec3(x, y, z); + int i = Mth.floor(x); +@@ -4151,6 +4173,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + this.levelCallback.onMove(); + } + ++ // Paper start - Block invalid positions and bounding box; don't allow desync of pos and AABB ++ // hanging has its own special logic ++ if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || this.position.x != x || this.position.y != y || this.position.z != z)) { ++ this.setBoundingBox(this.makeBoundingBox()); ++ } ++ // Paper end - Block invalid positions and bounding box + } + + public void checkDespawn() {} diff --git a/patches/server/0391-Ensure-Entity-position-and-AABB-are-never-invalid.patch b/patches/server/0391-Ensure-Entity-position-and-AABB-are-never-invalid.patch deleted file mode 100644 index c74d2ed12ef6..000000000000 --- a/patches/server/0391-Ensure-Entity-position-and-AABB-are-never-invalid.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 10 May 2020 22:12:46 -0400 -Subject: [PATCH] Ensure Entity position and AABB are never invalid - -Co-authored-by: Spottedleaf - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 47094050283625e3b494f5ab6955a2f9c736388d..70380d5807cac6dec19ebe581d685b1e32d8830b 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -639,8 +639,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - public void setPos(double x, double y, double z) { -- this.setPosRaw(x, y, z); -- this.setBoundingBox(this.makeBoundingBox()); -+ this.setPosRaw(x, y, z, true); // Paper - Block invalid positions and bounding box; force update -+ // this.setBoundingBox(this.makeBoundingBox()); // Paper - Block invalid positions and bounding box; move into setPosRaw - } - - protected AABB makeBoundingBox() { -@@ -4130,7 +4130,29 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale); - } - -+ // Paper start - Block invalid positions and bounding box -+ public static boolean checkPosition(Entity entity, double newX, double newY, double newZ) { -+ if (Double.isFinite(newX) && Double.isFinite(newY) && Double.isFinite(newZ)) { -+ return true; -+ } -+ -+ String entityInfo; -+ try { -+ entityInfo = entity.toString(); -+ } catch (Exception ex) { -+ entityInfo = "[Entity info unavailable] "; -+ } -+ LOGGER.error("New entity position is invalid! Tried to set invalid position ({},{},{}) for entity {} located at {}, entity info: {}", newX, newY, newZ, entity.getClass().getName(), entity.position, entityInfo, new Throwable()); -+ return false; -+ } - public final void setPosRaw(double x, double y, double z) { -+ this.setPosRaw(x, y, z, false); -+ } -+ public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { -+ if (!checkPosition(this, x, y, z)) { -+ return; -+ } -+ // Paper end - Block invalid positions and bounding box - if (this.position.x != x || this.position.y != y || this.position.z != z) { - this.position = new Vec3(x, y, z); - int i = Mth.floor(x); -@@ -4148,6 +4170,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - this.levelCallback.onMove(); - } - -+ // Paper start - Block invalid positions and bounding box; don't allow desync of pos and AABB -+ // hanging has its own special logic -+ if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || this.position.x != x || this.position.y != y || this.position.z != z)) { -+ this.setBoundingBox(this.makeBoundingBox()); -+ } -+ // Paper end - Block invalid positions and bounding box - } - - public void checkDespawn() {} diff --git a/patches/server/0391-Fix-Per-World-Difficulty-Remembering-Difficulty.patch b/patches/server/0391-Fix-Per-World-Difficulty-Remembering-Difficulty.patch new file mode 100644 index 000000000000..78ad4f527d1a --- /dev/null +++ b/patches/server/0391-Fix-Per-World-Difficulty-Remembering-Difficulty.patch @@ -0,0 +1,131 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 28 Jun 2020 03:59:10 -0400 +Subject: [PATCH] Fix Per World Difficulty / Remembering Difficulty + +Fixes per world difficulty with /difficulty command and also +makes it so that the server keeps the last difficulty used instead +of restoring the server.properties every single load. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index f786bf10a42fa4e9f610416959c60c52729c277b..21176d6e57ae9e975b1e7de14b3364365cc1012d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -825,7 +825,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + return Component.translatable("commands.difficulty.success", difficulty.getDisplayName()); + }, true); +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index c41c53ee3b1a8b5c2c41fc9846f557eeb4d10f9b..ebea8a827aad108dd6d4222e8dfd251d2cea657a 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -325,7 +325,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + + @Override + public void forceDifficulty() { +- this.setDifficulty(this.getProperties().difficulty, true); ++ // this.setDifficulty(this.getProperties().difficulty, true); // Paper - per level difficulty; Don't overwrite level.dat's difficulty, keep current + } + + @Override +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index e5653695a3fbcd260ce44ca37291406a1033a3fa..966b86a2b26a32aad2656d1f2beb6daf5b81b3b1 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1161,7 +1161,7 @@ public class ServerPlayer extends Player { + this.isChangingDimension = true; // CraftBukkit - Set teleport invulnerability only if player changing worlds + + this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(worldserver), (byte) 3)); +- this.connection.send(new ClientboundChangeDifficultyPacket(this.level().getDifficulty(), this.level().getLevelData().isDifficultyLocked())); ++ this.connection.send(new ClientboundChangeDifficultyPacket(worldserver.getDifficulty(), this.level().getLevelData().isDifficultyLocked())); // Paper - per level difficulty + PlayerList playerlist = this.server.getPlayerList(); + + playerlist.sendPlayerPermissionLevel(this); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index e9a3edbb66c79664f35150f052b6ff3534d0904c..18c7189725cc7c2d4438ea711fd72b752d0db0e0 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3165,7 +3165,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.hasPermissions(2) || this.isSingleplayerOwner()) { +- this.server.setDifficulty(packet.getDifficulty(), false); ++ // this.server.setDifficulty(packet.getDifficulty(), false); // Paper - per level difficulty; don't allow clients to change this + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index a5c84867708385ae78951872410914835ab3e7e5..edbf53c69bf788c2ac3b7d1be258e37cb801a5f6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -967,8 +967,8 @@ public final class CraftServer implements Server { + org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot + this.console.paperConfigurations.reloadConfigs(this.console); + for (ServerLevel world : this.console.getAllLevels()) { +- world.serverLevelData.setDifficulty(config.difficulty); +- world.setSpawnSettings(config.spawnMonsters, config.spawnAnimals); ++ // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty ++ world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters, config.spawnAnimals); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) + + for (SpawnCategory spawnCategory : SpawnCategory.values()) { + if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 6bb9d76f8c5f3dc683caecfbb7c7c2ca41cba659..aac25db85143ba09e96ef0775786732958f42ffb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1134,7 +1134,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setDifficulty(Difficulty difficulty) { +- this.getHandle().serverLevelData.setDifficulty(net.minecraft.world.Difficulty.byId(difficulty.getValue())); ++ this.getHandle().getServer().setDifficulty(this.getHandle(), net.minecraft.world.Difficulty.byId(difficulty.getValue()), true); // Paper - per level difficulty; don't skip other difficulty-changing logic + } + + @Override diff --git a/patches/server/0392-Fix-Per-World-Difficulty-Remembering-Difficulty.patch b/patches/server/0392-Fix-Per-World-Difficulty-Remembering-Difficulty.patch deleted file mode 100644 index 9482b89bc089..000000000000 --- a/patches/server/0392-Fix-Per-World-Difficulty-Remembering-Difficulty.patch +++ /dev/null @@ -1,131 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 28 Jun 2020 03:59:10 -0400 -Subject: [PATCH] Fix Per World Difficulty / Remembering Difficulty - -Fixes per world difficulty with /difficulty command and also -makes it so that the server keeps the last difficulty used instead -of restoring the server.properties every single load. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index f786bf10a42fa4e9f610416959c60c52729c277b..21176d6e57ae9e975b1e7de14b3364365cc1012d 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -825,7 +825,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { - return Component.translatable("commands.difficulty.success", difficulty.getDisplayName()); - }, true); -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index f269441cce34a0b5fb4da4764caeb22ff27cfb00..317f9048be060778104c8ac3494599c2141b7aac 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -325,7 +325,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - - @Override - public void forceDifficulty() { -- this.setDifficulty(this.getProperties().difficulty, true); -+ // this.setDifficulty(this.getProperties().difficulty, true); // Paper - per level difficulty; Don't overwrite level.dat's difficulty, keep current - } - - @Override -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index e3413273076b697d560c927ea0e12f34722a79c1..ab92c2c0ff5ebb395670c23fe0e3a8122b215874 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1161,7 +1161,7 @@ public class ServerPlayer extends Player { - this.isChangingDimension = true; // CraftBukkit - Set teleport invulnerability only if player changing worlds - - this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(worldserver), (byte) 3)); -- this.connection.send(new ClientboundChangeDifficultyPacket(this.level().getDifficulty(), this.level().getLevelData().isDifficultyLocked())); -+ this.connection.send(new ClientboundChangeDifficultyPacket(worldserver.getDifficulty(), this.level().getLevelData().isDifficultyLocked())); // Paper - per level difficulty - PlayerList playerlist = this.server.getPlayerList(); - - playerlist.sendPlayerPermissionLevel(this); -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 7ab30b8831b561374b7764148c4e5ed7451bafab..5487f04a6061d007c38bfad66e9b8251b5b0353b 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3165,7 +3165,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (this.player.hasPermissions(2) || this.isSingleplayerOwner()) { -- this.server.setDifficulty(packet.getDifficulty(), false); -+ // this.server.setDifficulty(packet.getDifficulty(), false); // Paper - per level difficulty; don't allow clients to change this - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index e7d73f2b56680bcbdd8b4e06547a518ec8145549..04a3b84f3dab7d8eff16a19b1c3cd0a70ba450c5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -955,8 +955,8 @@ public final class CraftServer implements Server { - org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot - this.console.paperConfigurations.reloadConfigs(this.console); - for (ServerLevel world : this.console.getAllLevels()) { -- world.serverLevelData.setDifficulty(config.difficulty); -- world.setSpawnSettings(config.spawnMonsters, config.spawnAnimals); -+ // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty -+ world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters, config.spawnAnimals); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) - - for (SpawnCategory spawnCategory : SpawnCategory.values()) { - if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 6ab78bead1fd4e7c02c597b23824f5e4f80132c9..c44cfa0dd5794976c74211467ac0b7022cce17e9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1121,7 +1121,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void setDifficulty(Difficulty difficulty) { -- this.getHandle().serverLevelData.setDifficulty(net.minecraft.world.Difficulty.byId(difficulty.getValue())); -+ this.getHandle().getServer().setDifficulty(this.getHandle(), net.minecraft.world.Difficulty.byId(difficulty.getValue()), true); // Paper - per level difficulty; don't skip other difficulty-changing logic - } - - @Override diff --git a/patches/server/0393-Paper-dumpitem-command.patch b/patches/server/0392-Paper-dumpitem-command.patch similarity index 100% rename from patches/server/0393-Paper-dumpitem-command.patch rename to patches/server/0392-Paper-dumpitem-command.patch diff --git a/patches/server/0394-Improve-Legacy-Component-serialization-size.patch b/patches/server/0393-Improve-Legacy-Component-serialization-size.patch similarity index 100% rename from patches/server/0394-Improve-Legacy-Component-serialization-size.patch rename to patches/server/0393-Improve-Legacy-Component-serialization-size.patch diff --git a/patches/server/0394-Add-Plugin-Tickets-to-API-Chunk-Methods.patch b/patches/server/0394-Add-Plugin-Tickets-to-API-Chunk-Methods.patch new file mode 100644 index 000000000000..45d64256b9ff --- /dev/null +++ b/patches/server/0394-Add-Plugin-Tickets-to-API-Chunk-Methods.patch @@ -0,0 +1,103 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 9 Jun 2020 03:33:03 -0400 +Subject: [PATCH] Add Plugin Tickets to API Chunk Methods + +Like previous versions, plugins loading chunks kept them loaded until +they garbage collected to avoid constant spamming of chunk loads + +This adds tickets to a few more places so that they can be unloaded. + +Additionally, this drops their ticket level to BORDER so they wont be ticking +so they will just sit inactive instead. + +Using .loadChunk to keep a chunk ticking was a horrible idea for upstream +when we have TWO methods that are able to do that already in the API. + +Also reduce their collection count down to a maximum of 1 second. Barely +anyone knows what chunk-gc is in bukkit.yml as its less relevant now, and +since this wasn't spigot behavior, this is safe to mostly ignore (unless someone +wants it to collect even faster, they can restore that setting back to 1 instead of 20+) + +Not adding it to .getType() though to keep behavior consistent with vanilla for performance reasons. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index edbf53c69bf788c2ac3b7d1be258e37cb801a5f6..c05fd6a179e8d142b3f5a8977ae7afab8c609a4e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -379,7 +379,7 @@ public final class CraftServer implements Server { + this.overrideSpawnLimits(); + console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); + this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); +- TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks"); ++ TicketType.PLUGIN.timeout = Math.min(20, this.configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second + this.minimumAPI = this.configuration.getString("settings.minimum-api"); + this.loadIcon(); + +@@ -947,7 +947,7 @@ public final class CraftServer implements Server { + this.console.setMotd(config.motd); + this.overrideSpawnLimits(); + this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); +- TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks"); ++ TicketType.PLUGIN.timeout = Math.min(20, configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second + this.minimumAPI = this.configuration.getString("settings.minimum-api"); + this.printSaveWarning = false; + this.console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index aac25db85143ba09e96ef0775786732958f42ffb..af782882479910f13b54081df2443387135874e0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -288,7 +288,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public Chunk getChunkAt(int x, int z) { +- net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) this.world.getChunk(x, z, ChunkStatus.FULL, true); ++ // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it ++ net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); ++ if (chunk == null) { ++ this.addTicket(x, z); ++ chunk = this.world.getChunkSource().getChunk(x, z, true); ++ } ++ // Paper end + return new CraftChunk(chunk); + } + +@@ -302,6 +308,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return new CraftChunk(this.getHandle(), x, z); + } + ++ // Paper start ++ private void addTicket(int x, int z) { ++ io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE)); // Paper ++ } ++ // Paper end ++ + @Override + public Chunk getChunkAt(Block block) { + Preconditions.checkArgument(block != null, "null block"); +@@ -353,7 +365,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public boolean unloadChunkRequest(int x, int z) { + org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot + if (this.isChunkLoaded(x, z)) { +- this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); ++ this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper + } + + return true; +@@ -438,7 +450,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + + if (chunk instanceof net.minecraft.world.level.chunk.LevelChunk) { +- this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); ++ this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper + return true; + } + +@@ -2221,6 +2233,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + io.papermc.paper.chunk.system.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> { + net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { + net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)c; ++ if (chunk != null) this.addTicket(x, z); // Paper + ret.complete(chunk == null ? null : new CraftChunk(chunk)); + }); + }); diff --git a/patches/server/0396-Add-BlockStateMeta-clearBlockState.patch b/patches/server/0395-Add-BlockStateMeta-clearBlockState.patch similarity index 100% rename from patches/server/0396-Add-BlockStateMeta-clearBlockState.patch rename to patches/server/0395-Add-BlockStateMeta-clearBlockState.patch diff --git a/patches/server/0395-Add-Plugin-Tickets-to-API-Chunk-Methods.patch b/patches/server/0395-Add-Plugin-Tickets-to-API-Chunk-Methods.patch deleted file mode 100644 index 8a3e2169f4cd..000000000000 --- a/patches/server/0395-Add-Plugin-Tickets-to-API-Chunk-Methods.patch +++ /dev/null @@ -1,103 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 9 Jun 2020 03:33:03 -0400 -Subject: [PATCH] Add Plugin Tickets to API Chunk Methods - -Like previous versions, plugins loading chunks kept them loaded until -they garbage collected to avoid constant spamming of chunk loads - -This adds tickets to a few more places so that they can be unloaded. - -Additionally, this drops their ticket level to BORDER so they wont be ticking -so they will just sit inactive instead. - -Using .loadChunk to keep a chunk ticking was a horrible idea for upstream -when we have TWO methods that are able to do that already in the API. - -Also reduce their collection count down to a maximum of 1 second. Barely -anyone knows what chunk-gc is in bukkit.yml as its less relevant now, and -since this wasn't spigot behavior, this is safe to mostly ignore (unless someone -wants it to collect even faster, they can restore that setting back to 1 instead of 20+) - -Not adding it to .getType() though to keep behavior consistent with vanilla for performance reasons. - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 7803075383cdde77444991e72f49ed67627f95ed..936928dda8b09386ef936cec9da65eb76b1ccf1d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -377,7 +377,7 @@ public final class CraftServer implements Server { - this.overrideSpawnLimits(); - console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); - this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); -- TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks"); -+ TicketType.PLUGIN.timeout = Math.min(20, this.configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second - this.minimumAPI = this.configuration.getString("settings.minimum-api"); - this.loadIcon(); - -@@ -935,7 +935,7 @@ public final class CraftServer implements Server { - this.console.setMotd(config.motd); - this.overrideSpawnLimits(); - this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); -- TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks"); -+ TicketType.PLUGIN.timeout = Math.min(20, configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second - this.minimumAPI = this.configuration.getString("settings.minimum-api"); - this.printSaveWarning = false; - this.console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index c44cfa0dd5794976c74211467ac0b7022cce17e9..6070409175b106ba6920adf4fa860215cd6f3087 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -282,7 +282,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public Chunk getChunkAt(int x, int z) { -- net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) this.world.getChunk(x, z, ChunkStatus.FULL, true); -+ // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it -+ net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); -+ if (chunk == null) { -+ this.addTicket(x, z); -+ chunk = this.world.getChunkSource().getChunk(x, z, true); -+ } -+ // Paper end - return new CraftChunk(chunk); - } - -@@ -296,6 +302,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return new CraftChunk(this.getHandle(), x, z); - } - -+ // Paper start -+ private void addTicket(int x, int z) { -+ io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE)); // Paper -+ } -+ // Paper end -+ - @Override - public Chunk getChunkAt(Block block) { - Preconditions.checkArgument(block != null, "null block"); -@@ -347,7 +359,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public boolean unloadChunkRequest(int x, int z) { - org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot - if (this.isChunkLoaded(x, z)) { -- this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); -+ this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper - } - - return true; -@@ -432,7 +444,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - } - - if (chunk instanceof net.minecraft.world.level.chunk.LevelChunk) { -- this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); -+ this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper - return true; - } - -@@ -2184,6 +2196,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - io.papermc.paper.chunk.system.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> { - net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { - net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)c; -+ if (chunk != null) this.addTicket(x, z); // Paper - ret.complete(chunk == null ? null : new CraftChunk(chunk)); - }); - }); diff --git a/patches/server/0397-Support-old-UUID-format-for-NBT.patch b/patches/server/0396-Support-old-UUID-format-for-NBT.patch similarity index 100% rename from patches/server/0397-Support-old-UUID-format-for-NBT.patch rename to patches/server/0396-Support-old-UUID-format-for-NBT.patch diff --git a/patches/server/0397-Convert-legacy-attributes-in-Item-Meta.patch b/patches/server/0397-Convert-legacy-attributes-in-Item-Meta.patch new file mode 100644 index 000000000000..73fa3e1bc2a5 --- /dev/null +++ b/patches/server/0397-Convert-legacy-attributes-in-Item-Meta.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 1 Jul 2020 04:50:22 -0400 +Subject: [PATCH] Convert legacy attributes in Item Meta + + +diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +index d4dba8c733c7560e5108b8d239b52e593f8debec..ea48f1119a940056c37d1d203437bfbfdf13663b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +@@ -9,6 +9,20 @@ import org.bukkit.attribute.AttributeInstance; + public class CraftAttributeMap implements Attributable { + + private final AttributeMap handle; ++ // Paper start - convert legacy attributes ++ private static final com.google.common.collect.ImmutableMap legacyNMS = com.google.common.collect.ImmutableMap.builder().put("generic.maxHealth", "generic.max_health").put("Max Health", "generic.max_health").put("zombie.spawnReinforcements", "zombie.spawn_reinforcements").put("Spawn Reinforcements Chance", "zombie.spawn_reinforcements").put("horse.jumpStrength", "horse.jump_strength").put("Jump Strength", "horse.jump_strength").put("generic.followRange", "generic.follow_range").put("Follow Range", "generic.follow_range").put("generic.knockbackResistance", "generic.knockback_resistance").put("Knockback Resistance", "generic.knockback_resistance").put("generic.movementSpeed", "generic.movement_speed").put("Movement Speed", "generic.movement_speed").put("generic.flyingSpeed", "generic.flying_speed").put("Flying Speed", "generic.flying_speed").put("generic.attackDamage", "generic.attack_damage").put("generic.attackKnockback", "generic.attack_knockback").put("generic.attackSpeed", "generic.attack_speed").put("generic.armorToughness", "generic.armor_toughness").build(); ++ ++ public static String convertIfNeeded(String nms) { ++ if (nms == null) { ++ return null; ++ } ++ nms = legacyNMS.getOrDefault(nms, nms); ++ if (!nms.toLowerCase().equals(nms) || nms.indexOf(' ') != -1) { ++ return null; ++ } ++ return nms; ++ } ++ // Paper end + + public CraftAttributeMap(AttributeMap handle) { + this.handle = handle; +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 629fa76e6c7c2ede36ab855bb3a7a65dfd601449..a3713c5ab624b8d54ddcd69ae7587346ebbaed69 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -485,7 +485,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + AttributeModifier attribMod = CraftAttributeInstance.convert(nmsModifier); + +- String attributeName = entry.getString(CraftMetaItem.ATTRIBUTES_IDENTIFIER.NBT); ++ String attributeName = org.bukkit.craftbukkit.attribute.CraftAttributeMap.convertIfNeeded(entry.getString(CraftMetaItem.ATTRIBUTES_IDENTIFIER.NBT)); // Paper + if (attributeName == null || attributeName.isEmpty()) { + continue; + } diff --git a/patches/server/0398-Convert-legacy-attributes-in-Item-Meta.patch b/patches/server/0398-Convert-legacy-attributes-in-Item-Meta.patch deleted file mode 100644 index e639bfc5c6f1..000000000000 --- a/patches/server/0398-Convert-legacy-attributes-in-Item-Meta.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 1 Jul 2020 04:50:22 -0400 -Subject: [PATCH] Convert legacy attributes in Item Meta - - -diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -index d4dba8c733c7560e5108b8d239b52e593f8debec..ea48f1119a940056c37d1d203437bfbfdf13663b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -+++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -@@ -9,6 +9,20 @@ import org.bukkit.attribute.AttributeInstance; - public class CraftAttributeMap implements Attributable { - - private final AttributeMap handle; -+ // Paper start - convert legacy attributes -+ private static final com.google.common.collect.ImmutableMap legacyNMS = com.google.common.collect.ImmutableMap.builder().put("generic.maxHealth", "generic.max_health").put("Max Health", "generic.max_health").put("zombie.spawnReinforcements", "zombie.spawn_reinforcements").put("Spawn Reinforcements Chance", "zombie.spawn_reinforcements").put("horse.jumpStrength", "horse.jump_strength").put("Jump Strength", "horse.jump_strength").put("generic.followRange", "generic.follow_range").put("Follow Range", "generic.follow_range").put("generic.knockbackResistance", "generic.knockback_resistance").put("Knockback Resistance", "generic.knockback_resistance").put("generic.movementSpeed", "generic.movement_speed").put("Movement Speed", "generic.movement_speed").put("generic.flyingSpeed", "generic.flying_speed").put("Flying Speed", "generic.flying_speed").put("generic.attackDamage", "generic.attack_damage").put("generic.attackKnockback", "generic.attack_knockback").put("generic.attackSpeed", "generic.attack_speed").put("generic.armorToughness", "generic.armor_toughness").build(); -+ -+ public static String convertIfNeeded(String nms) { -+ if (nms == null) { -+ return null; -+ } -+ nms = legacyNMS.getOrDefault(nms, nms); -+ if (!nms.toLowerCase().equals(nms) || nms.indexOf(' ') != -1) { -+ return null; -+ } -+ return nms; -+ } -+ // Paper end - - public CraftAttributeMap(AttributeMap handle) { - this.handle = handle; -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index ed50a1e492643842a094fc90fb52cbec4e8f5f3d..f9f57f4ab75776dbaa4dc39d30e32b2c778b1955 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -485,7 +485,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - - AttributeModifier attribMod = CraftAttributeInstance.convert(nmsModifier); - -- String attributeName = entry.getString(CraftMetaItem.ATTRIBUTES_IDENTIFIER.NBT); -+ String attributeName = org.bukkit.craftbukkit.attribute.CraftAttributeMap.convertIfNeeded(entry.getString(CraftMetaItem.ATTRIBUTES_IDENTIFIER.NBT)); // Paper - if (attributeName == null || attributeName.isEmpty()) { - continue; - } diff --git a/patches/server/0398-Do-not-accept-invalid-client-settings.patch b/patches/server/0398-Do-not-accept-invalid-client-settings.patch new file mode 100644 index 000000000000..47657e138e8e --- /dev/null +++ b/patches/server/0398-Do-not-accept-invalid-client-settings.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 7 May 2022 14:58:53 -0700 +Subject: [PATCH] Do not accept invalid client settings + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 18c7189725cc7c2d4438ea711fd72b752d0db0e0..22d6c914ffbe591d3de19b89a0e87d1042bf1772 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3157,6 +3157,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + @Override + public void handleClientInformation(ServerboundClientInformationPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ // Paper start - do not accept invalid information ++ if (packet.information().viewDistance() < 0) { ++ LOGGER.warn("Disconnecting " + this.player.getScoreboardName() + " for invalid view distance: " + packet.information().viewDistance()); ++ this.disconnect("Invalid client settings", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); ++ return; ++ } ++ // Paper end - do not accept invalid information + this.player.updateOptions(packet.information()); + this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper + } diff --git a/patches/server/0399-Do-not-accept-invalid-client-settings.patch b/patches/server/0399-Do-not-accept-invalid-client-settings.patch deleted file mode 100644 index 340f0e9657fb..000000000000 --- a/patches/server/0399-Do-not-accept-invalid-client-settings.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 7 May 2022 14:58:53 -0700 -Subject: [PATCH] Do not accept invalid client settings - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 5487f04a6061d007c38bfad66e9b8251b5b0353b..540c33baacc57f5ec46b2f373d1cca2857463ecd 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3157,6 +3157,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - @Override - public void handleClientInformation(ServerboundClientInformationPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); -+ // Paper start - do not accept invalid information -+ if (packet.information().viewDistance() < 0) { -+ LOGGER.warn("Disconnecting " + this.player.getScoreboardName() + " for invalid view distance: " + packet.information().viewDistance()); -+ this.disconnect("Invalid client settings", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); -+ return; -+ } -+ // Paper end - do not accept invalid information - this.player.updateOptions(packet.information()); - this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper - } diff --git a/patches/server/0400-Improve-fix-EntityTargetLivingEntityEvent.patch b/patches/server/0399-Improve-fix-EntityTargetLivingEntityEvent.patch similarity index 100% rename from patches/server/0400-Improve-fix-EntityTargetLivingEntityEvent.patch rename to patches/server/0399-Improve-fix-EntityTargetLivingEntityEvent.patch diff --git a/patches/server/0401-Add-entity-liquid-API.patch b/patches/server/0400-Add-entity-liquid-API.patch similarity index 100% rename from patches/server/0401-Add-entity-liquid-API.patch rename to patches/server/0400-Add-entity-liquid-API.patch diff --git a/patches/server/0401-Update-itemstack-legacy-name-and-lore.patch b/patches/server/0401-Update-itemstack-legacy-name-and-lore.patch new file mode 100644 index 000000000000..49a60bd241f1 --- /dev/null +++ b/patches/server/0401-Update-itemstack-legacy-name-and-lore.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 1 Jul 2020 11:57:40 -0500 +Subject: [PATCH] Update itemstack legacy name and lore + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 0a3fec9b82a4d744f9046aebe30f80bb6e56c500..4a6e128c62c890c34b62f826d586ae6a424e7f01 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -196,6 +196,44 @@ public final class ItemStack { + list.sort((java.util.Comparator) enchantSorter); // Paper + } catch (Exception ignored) {} + } ++ ++ private void processText() { ++ CompoundTag display = getTagElement("display"); ++ if (display != null) { ++ if (display.contains("Name", net.minecraft.nbt.Tag.TAG_STRING)) { ++ String json = display.getString("Name"); ++ if (json != null && json.contains("\u00A7")) { ++ try { ++ display.put("Name", convert(json)); ++ } catch (com.google.gson.JsonParseException jsonparseexception) { ++ display.remove("Name"); ++ } ++ } ++ } ++ if (display.contains("Lore", net.minecraft.nbt.Tag.TAG_LIST)) { ++ ListTag list = display.getList("Lore", net.minecraft.nbt.Tag.TAG_STRING); ++ for (int index = 0; index < list.size(); index++) { ++ String json = list.getString(index); ++ if (json != null && json.contains("\u00A7")) { // Only try if it has legacy in the unparsed json ++ try { ++ list.set(index, convert(json)); ++ } catch (com.google.gson.JsonParseException e) { ++ list.set(index, net.minecraft.nbt.StringTag.valueOf(org.bukkit.craftbukkit.util.CraftChatMessage.toJSON(net.minecraft.network.chat.Component.literal("")))); ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ private net.minecraft.nbt.StringTag convert(String json) { ++ Component component = Component.Serializer.fromJson(json); ++ if (component.getContents() instanceof final net.minecraft.network.chat.contents.PlainTextContents plainTextContents && plainTextContents.text().contains("\u00A7") && component.getSiblings().isEmpty()) { ++ // Only convert if the root component is a single comp with legacy in it, don't convert already normal components ++ component = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(plainTextContents.text())[0]; ++ } ++ return net.minecraft.nbt.StringTag.valueOf(org.bukkit.craftbukkit.util.CraftChatMessage.toJSON(component)); ++ } + // Paper end + + public ItemStack(ItemLike item) { +@@ -245,6 +283,7 @@ public final class ItemStack { + if (nbttagcompound.contains("tag", 10)) { + this.tag = nbttagcompound.getCompound("tag").copy(); + this.processEnchantOrder(this.tag); // Paper ++ this.processText(); // Paper - Update itemstack legacy name and lore + this.getItem().verifyTagAfterLoad(this.tag); + } + diff --git a/patches/server/0402-Add-PrepareResultEvent.patch b/patches/server/0402-Add-PrepareResultEvent.patch new file mode 100644 index 000000000000..4a6f392905de --- /dev/null +++ b/patches/server/0402-Add-PrepareResultEvent.patch @@ -0,0 +1,165 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 3 Jul 2020 11:58:56 -0500 +Subject: [PATCH] Add PrepareResultEvent + +Adds a new event for all crafting stations that generate a result slot item + +Anvil, Grindstone and Smithing now extend this event + +diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +index 878d3c3089635a515fa7f54c956159a1bb6ce29b..cab3e0ba471c93764b5949ad68a0f2cce4d00099 100644 +--- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +@@ -338,6 +338,7 @@ public class AnvilMenu extends ItemCombinerMenu { + } + + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + return true; + } else { + return false; +diff --git a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +index fe1ce65b35e83ee0ada77e44b080729346bb3c2d..ca3c8b31967a6efd7b0caacb091ab2151e7c0bee 100644 +--- a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +@@ -150,6 +150,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { + this.setupResultSlot(itemstack, itemstack1, itemstack2); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + } + + private void setupResultSlot(ItemStack map, ItemStack item, ItemStack oldResult) { +diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +index 45242f0ed5a0f98953df5f27fb76874d2d9e3473..1783661f38a6f5fb655ea83953b9467bd91a1302 100644 +--- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +@@ -159,6 +159,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { + super.slotsChanged(inventory); + if (inventory == this.repairSlots) { + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + } + + } +diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +index 4087e381b2250be387b608d8742f6a6009a52879..eb36a69b8da492aec9609cc9ef80d7d68ff9af03 100644 +--- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +@@ -110,6 +110,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { + super.slotsChanged(inventory); + if (inventory == this.inputSlots) { + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, this instanceof SmithingMenu ? 3 : 2); // Paper - Add PrepareResultEvent + } + + } +diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +index 5c209a3d81db5326f63c506077fa0bfd241b4b12..a98157f600837898dd8ef12671c4bb713e30f30c 100644 +--- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +@@ -248,7 +248,8 @@ public class LoomMenu extends AbstractContainerMenu { + this.resultSlot.set(ItemStack.EMPTY); + } + +- this.broadcastChanges(); ++ // this.broadcastChanges(); // Paper - Add PrepareResultEvent; done below ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 3); // Paper - Add PrepareResultEvent + } else { + this.resultSlot.set(ItemStack.EMPTY); + this.selectablePatterns = List.of(); +diff --git a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java +index 59d9f990a87ab5214fa51e3a6e933bf5ae71b613..1e9e70263996afa294458364aa70e738b5aabea1 100644 +--- a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java +@@ -115,6 +115,7 @@ public class SmithingMenu extends ItemCombinerMenu { + } + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + } + + @Override +diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +index 9c2fe69ced7a46bbd8b0fbe10fa67d0a39b0f375..e40d9dbdbe5359c38af6d764d01c9be422654aaa 100644 +--- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +@@ -181,6 +181,7 @@ public class StonecutterMenu extends AbstractContainerMenu { + this.setupRecipeList(inventory, itemstack); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent + } + + private void setupRecipeList(Container input, ItemStack stack) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 2fbcd11ff457c9569bf011f94ed9658c7a85b743..5a7946d3877eece469f21ee512342847101b2f67 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1646,6 +1646,12 @@ public class CraftEventFactory { + } + + public static PrepareAnvilEvent callPrepareAnvilEvent(InventoryView view, ItemStack item) { ++ // Paper start - Add PrepareResultEvent ++ if (true) { ++ view.getTopInventory().setItem(net.minecraft.world.inventory.AnvilMenu.RESULT_SLOT, CraftItemStack.asCraftMirror(item)); ++ return null; // verify nothing uses return - disable event: handled below in PrepareResult ++ } ++ // Paper end - Add PrepareResultEvent + PrepareAnvilEvent event = new PrepareAnvilEvent(view, CraftItemStack.asCraftMirror(item).clone()); + event.getView().getPlayer().getServer().getPluginManager().callEvent(event); + event.getInventory().setItem(2, event.getResult()); +@@ -1653,6 +1659,12 @@ public class CraftEventFactory { + } + + public static PrepareGrindstoneEvent callPrepareGrindstoneEvent(InventoryView view, ItemStack item) { ++ // Paper start - Add PrepareResultEvent ++ if (true) { ++ view.getTopInventory().setItem(net.minecraft.world.inventory.GrindstoneMenu.RESULT_SLOT, CraftItemStack.asCraftMirror(item)); ++ return null; // verify nothing uses return - disable event: handled below in PrepareResult ++ } ++ // Paper end - Add PrepareResultEvent + PrepareGrindstoneEvent event = new PrepareGrindstoneEvent(view, CraftItemStack.asCraftMirror(item).clone()); + event.getView().getPlayer().getServer().getPluginManager().callEvent(event); + event.getInventory().setItem(2, event.getResult()); +@@ -1660,12 +1672,39 @@ public class CraftEventFactory { + } + + public static PrepareSmithingEvent callPrepareSmithingEvent(InventoryView view, ItemStack item) { ++ // Paper start - Add PrepareResultEvent ++ if (true) { ++ view.getTopInventory().setItem(net.minecraft.world.inventory.SmithingMenu.RESULT_SLOT, CraftItemStack.asCraftMirror(item)); ++ return null; // verify nothing uses return - disable event: handled below in PrepareResult ++ } ++ // Paper end - Add PrepareResultEvent + PrepareSmithingEvent event = new PrepareSmithingEvent(view, CraftItemStack.asCraftMirror(item).clone()); + event.getView().getPlayer().getServer().getPluginManager().callEvent(event); + event.getInventory().setResult(event.getResult()); + return event; + } + ++ // Paper start - Add PrepareResultEvent ++ public static void callPrepareResultEvent(AbstractContainerMenu container, int resultSlot) { ++ final com.destroystokyo.paper.event.inventory.PrepareResultEvent event; ++ InventoryView view = container.getBukkitView(); ++ org.bukkit.inventory.ItemStack origItem = view.getTopInventory().getItem(resultSlot); ++ CraftItemStack result = origItem != null ? CraftItemStack.asCraftCopy(origItem) : null; ++ if (view.getTopInventory() instanceof org.bukkit.inventory.AnvilInventory) { ++ event = new PrepareAnvilEvent(view, result); ++ } else if (view.getTopInventory() instanceof org.bukkit.inventory.GrindstoneInventory) { ++ event = new PrepareGrindstoneEvent(view, result); ++ } else if (view.getTopInventory() instanceof org.bukkit.inventory.SmithingInventory) { ++ event = new PrepareSmithingEvent(view, result); ++ } else { ++ event = new com.destroystokyo.paper.event.inventory.PrepareResultEvent(view, result); ++ } ++ event.callEvent(); ++ event.getInventory().setItem(resultSlot, event.getResult()); ++ container.broadcastChanges();; ++ } ++ // Paper end - Add PrepareResultEvent ++ + /** + * Mob spawner event. + */ diff --git a/patches/server/0402-Update-itemstack-legacy-name-and-lore.patch b/patches/server/0402-Update-itemstack-legacy-name-and-lore.patch deleted file mode 100644 index 18dbfa4f6ea8..000000000000 --- a/patches/server/0402-Update-itemstack-legacy-name-and-lore.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Wed, 1 Jul 2020 11:57:40 -0500 -Subject: [PATCH] Update itemstack legacy name and lore - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 9b9504ca32d8cc7c037e0a96f2d8aa03d5c5495d..556d8d395df3660ec7923c6814bc281c5fce442f 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -195,6 +195,44 @@ public final class ItemStack { - list.sort((java.util.Comparator) enchantSorter); // Paper - } catch (Exception ignored) {} - } -+ -+ private void processText() { -+ CompoundTag display = getTagElement("display"); -+ if (display != null) { -+ if (display.contains("Name", net.minecraft.nbt.Tag.TAG_STRING)) { -+ String json = display.getString("Name"); -+ if (json != null && json.contains("\u00A7")) { -+ try { -+ display.put("Name", convert(json)); -+ } catch (com.google.gson.JsonParseException jsonparseexception) { -+ display.remove("Name"); -+ } -+ } -+ } -+ if (display.contains("Lore", net.minecraft.nbt.Tag.TAG_LIST)) { -+ ListTag list = display.getList("Lore", net.minecraft.nbt.Tag.TAG_STRING); -+ for (int index = 0; index < list.size(); index++) { -+ String json = list.getString(index); -+ if (json != null && json.contains("\u00A7")) { // Only try if it has legacy in the unparsed json -+ try { -+ list.set(index, convert(json)); -+ } catch (com.google.gson.JsonParseException e) { -+ list.set(index, net.minecraft.nbt.StringTag.valueOf(org.bukkit.craftbukkit.util.CraftChatMessage.toJSON(net.minecraft.network.chat.Component.literal("")))); -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ private net.minecraft.nbt.StringTag convert(String json) { -+ Component component = Component.Serializer.fromJson(json); -+ if (component.getContents() instanceof final net.minecraft.network.chat.contents.PlainTextContents plainTextContents && plainTextContents.text().contains("\u00A7") && component.getSiblings().isEmpty()) { -+ // Only convert if the root component is a single comp with legacy in it, don't convert already normal components -+ component = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(plainTextContents.text())[0]; -+ } -+ return net.minecraft.nbt.StringTag.valueOf(org.bukkit.craftbukkit.util.CraftChatMessage.toJSON(component)); -+ } - // Paper end - - public ItemStack(ItemLike item) { -@@ -244,6 +282,7 @@ public final class ItemStack { - if (nbttagcompound.contains("tag", 10)) { - this.tag = nbttagcompound.getCompound("tag").copy(); - this.processEnchantOrder(this.tag); // Paper -+ this.processText(); // Paper - Update itemstack legacy name and lore - this.getItem().verifyTagAfterLoad(this.tag); - } - diff --git a/patches/server/0403-Add-PrepareResultEvent.patch b/patches/server/0403-Add-PrepareResultEvent.patch deleted file mode 100644 index cb127c83aa49..000000000000 --- a/patches/server/0403-Add-PrepareResultEvent.patch +++ /dev/null @@ -1,165 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Fri, 3 Jul 2020 11:58:56 -0500 -Subject: [PATCH] Add PrepareResultEvent - -Adds a new event for all crafting stations that generate a result slot item - -Anvil, Grindstone and Smithing now extend this event - -diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -index 878d3c3089635a515fa7f54c956159a1bb6ce29b..cab3e0ba471c93764b5949ad68a0f2cce4d00099 100644 ---- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -@@ -338,6 +338,7 @@ public class AnvilMenu extends ItemCombinerMenu { - } - - this.createResult(); -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent - return true; - } else { - return false; -diff --git a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -index fe1ce65b35e83ee0ada77e44b080729346bb3c2d..ca3c8b31967a6efd7b0caacb091ab2151e7c0bee 100644 ---- a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -@@ -150,6 +150,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { - this.setupResultSlot(itemstack, itemstack1, itemstack2); - } - -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent - } - - private void setupResultSlot(ItemStack map, ItemStack item, ItemStack oldResult) { -diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -index 45242f0ed5a0f98953df5f27fb76874d2d9e3473..1783661f38a6f5fb655ea83953b9467bd91a1302 100644 ---- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -@@ -159,6 +159,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { - super.slotsChanged(inventory); - if (inventory == this.repairSlots) { - this.createResult(); -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent - } - - } -diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -index 4087e381b2250be387b608d8742f6a6009a52879..eb36a69b8da492aec9609cc9ef80d7d68ff9af03 100644 ---- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -@@ -110,6 +110,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { - super.slotsChanged(inventory); - if (inventory == this.inputSlots) { - this.createResult(); -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, this instanceof SmithingMenu ? 3 : 2); // Paper - Add PrepareResultEvent - } - - } -diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -index 5c209a3d81db5326f63c506077fa0bfd241b4b12..a98157f600837898dd8ef12671c4bb713e30f30c 100644 ---- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -@@ -248,7 +248,8 @@ public class LoomMenu extends AbstractContainerMenu { - this.resultSlot.set(ItemStack.EMPTY); - } - -- this.broadcastChanges(); -+ // this.broadcastChanges(); // Paper - Add PrepareResultEvent; done below -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 3); // Paper - Add PrepareResultEvent - } else { - this.resultSlot.set(ItemStack.EMPTY); - this.selectablePatterns = List.of(); -diff --git a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java -index 59d9f990a87ab5214fa51e3a6e933bf5ae71b613..1e9e70263996afa294458364aa70e738b5aabea1 100644 ---- a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java -@@ -115,6 +115,7 @@ public class SmithingMenu extends ItemCombinerMenu { - } - } - -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent - } - - @Override -diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -index 9c2fe69ced7a46bbd8b0fbe10fa67d0a39b0f375..e40d9dbdbe5359c38af6d764d01c9be422654aaa 100644 ---- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -@@ -181,6 +181,7 @@ public class StonecutterMenu extends AbstractContainerMenu { - this.setupRecipeList(inventory, itemstack); - } - -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - Add PrepareResultEvent - } - - private void setupRecipeList(Container input, ItemStack stack) { -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 4dddda15446857caa628d01c496aa1398b436894..58a1c1f48655b7403379bcd46945f4b16f73d790 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1715,6 +1715,12 @@ public class CraftEventFactory { - } - - public static PrepareAnvilEvent callPrepareAnvilEvent(InventoryView view, ItemStack item) { -+ // Paper start - Add PrepareResultEvent -+ if (true) { -+ view.getTopInventory().setItem(net.minecraft.world.inventory.AnvilMenu.RESULT_SLOT, CraftItemStack.asCraftMirror(item)); -+ return null; // verify nothing uses return - disable event: handled below in PrepareResult -+ } -+ // Paper end - Add PrepareResultEvent - PrepareAnvilEvent event = new PrepareAnvilEvent(view, CraftItemStack.asCraftMirror(item).clone()); - event.getView().getPlayer().getServer().getPluginManager().callEvent(event); - event.getInventory().setItem(2, event.getResult()); -@@ -1722,6 +1728,12 @@ public class CraftEventFactory { - } - - public static PrepareGrindstoneEvent callPrepareGrindstoneEvent(InventoryView view, ItemStack item) { -+ // Paper start - Add PrepareResultEvent -+ if (true) { -+ view.getTopInventory().setItem(net.minecraft.world.inventory.GrindstoneMenu.RESULT_SLOT, CraftItemStack.asCraftMirror(item)); -+ return null; // verify nothing uses return - disable event: handled below in PrepareResult -+ } -+ // Paper end - Add PrepareResultEvent - PrepareGrindstoneEvent event = new PrepareGrindstoneEvent(view, CraftItemStack.asCraftMirror(item).clone()); - event.getView().getPlayer().getServer().getPluginManager().callEvent(event); - event.getInventory().setItem(2, event.getResult()); -@@ -1729,12 +1741,39 @@ public class CraftEventFactory { - } - - public static PrepareSmithingEvent callPrepareSmithingEvent(InventoryView view, ItemStack item) { -+ // Paper start - Add PrepareResultEvent -+ if (true) { -+ view.getTopInventory().setItem(net.minecraft.world.inventory.SmithingMenu.RESULT_SLOT, CraftItemStack.asCraftMirror(item)); -+ return null; // verify nothing uses return - disable event: handled below in PrepareResult -+ } -+ // Paper end - Add PrepareResultEvent - PrepareSmithingEvent event = new PrepareSmithingEvent(view, CraftItemStack.asCraftMirror(item).clone()); - event.getView().getPlayer().getServer().getPluginManager().callEvent(event); - event.getInventory().setResult(event.getResult()); - return event; - } - -+ // Paper start - Add PrepareResultEvent -+ public static void callPrepareResultEvent(AbstractContainerMenu container, int resultSlot) { -+ final com.destroystokyo.paper.event.inventory.PrepareResultEvent event; -+ InventoryView view = container.getBukkitView(); -+ org.bukkit.inventory.ItemStack origItem = view.getTopInventory().getItem(resultSlot); -+ CraftItemStack result = origItem != null ? CraftItemStack.asCraftCopy(origItem) : null; -+ if (view.getTopInventory() instanceof org.bukkit.inventory.AnvilInventory) { -+ event = new PrepareAnvilEvent(view, result); -+ } else if (view.getTopInventory() instanceof org.bukkit.inventory.GrindstoneInventory) { -+ event = new PrepareGrindstoneEvent(view, result); -+ } else if (view.getTopInventory() instanceof org.bukkit.inventory.SmithingInventory) { -+ event = new PrepareSmithingEvent(view, result); -+ } else { -+ event = new com.destroystokyo.paper.event.inventory.PrepareResultEvent(view, result); -+ } -+ event.callEvent(); -+ event.getInventory().setItem(resultSlot, event.getResult()); -+ container.broadcastChanges();; -+ } -+ // Paper end - Add PrepareResultEvent -+ - /** - * Mob spawner event. - */ diff --git a/patches/server/0403-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch b/patches/server/0403-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch new file mode 100644 index 000000000000..08e42c9d20af --- /dev/null +++ b/patches/server/0403-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 5 Jul 2020 14:59:31 -0400 +Subject: [PATCH] Don't check chunk for portal on world gen entity add + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 035faf890c02ebd5bdbb430dc473e7a1bc7b9fd1..0e009d34a17b7fbebb8bd815cef9df191cd906a5 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3523,7 +3523,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + Entity entity = this.getVehicle(); + + super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation +- if (entity != null && entity != this.getVehicle() && !this.level().isClientSide) { ++ if (entity != null && entity != this.getVehicle() && !this.level().isClientSide && entity.valid) { // Paper - don't process on world gen + this.dismountVehicle(entity); + } + diff --git a/patches/server/0404-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch b/patches/server/0404-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch deleted file mode 100644 index d46fd307e0d2..000000000000 --- a/patches/server/0404-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 5 Jul 2020 14:59:31 -0400 -Subject: [PATCH] Don't check chunk for portal on world gen entity add - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index b7bf58ddbc02989777c5c8dd58f6dd34acf57507..115dbe71ff1e9996e8307a389569303a320101f4 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3510,7 +3510,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - Entity entity = this.getVehicle(); - - super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation -- if (entity != null && entity != this.getVehicle() && !this.level().isClientSide) { -+ if (entity != null && entity != this.getVehicle() && !this.level().isClientSide && entity.valid) { // Paper - don't process on world gen - this.dismountVehicle(entity); - } - diff --git a/patches/server/0405-Fix-arrows-never-despawning-MC-125757.patch b/patches/server/0404-Fix-arrows-never-despawning-MC-125757.patch similarity index 100% rename from patches/server/0405-Fix-arrows-never-despawning-MC-125757.patch rename to patches/server/0404-Fix-arrows-never-despawning-MC-125757.patch diff --git a/patches/server/0406-Thread-Safe-Vanilla-Command-permission-checking.patch b/patches/server/0405-Thread-Safe-Vanilla-Command-permission-checking.patch similarity index 100% rename from patches/server/0406-Thread-Safe-Vanilla-Command-permission-checking.patch rename to patches/server/0405-Thread-Safe-Vanilla-Command-permission-checking.patch diff --git a/patches/server/0407-Fix-SPIGOT-5989.patch b/patches/server/0406-Fix-SPIGOT-5989.patch similarity index 100% rename from patches/server/0407-Fix-SPIGOT-5989.patch rename to patches/server/0406-Fix-SPIGOT-5989.patch diff --git a/patches/server/0408-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch b/patches/server/0407-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch similarity index 100% rename from patches/server/0408-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch rename to patches/server/0407-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch diff --git a/patches/server/0409-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch b/patches/server/0408-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch similarity index 100% rename from patches/server/0409-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch rename to patches/server/0408-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch diff --git a/patches/server/0410-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch b/patches/server/0409-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch similarity index 100% rename from patches/server/0410-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch rename to patches/server/0409-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch diff --git a/patches/server/0411-Optimize-NetworkManager-Exception-Handling.patch b/patches/server/0410-Optimize-NetworkManager-Exception-Handling.patch similarity index 100% rename from patches/server/0411-Optimize-NetworkManager-Exception-Handling.patch rename to patches/server/0410-Optimize-NetworkManager-Exception-Handling.patch diff --git a/patches/server/0412-Fix-some-rails-connecting-improperly.patch b/patches/server/0411-Fix-some-rails-connecting-improperly.patch similarity index 100% rename from patches/server/0412-Fix-some-rails-connecting-improperly.patch rename to patches/server/0411-Fix-some-rails-connecting-improperly.patch diff --git a/patches/server/0413-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch b/patches/server/0412-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch similarity index 100% rename from patches/server/0413-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch rename to patches/server/0412-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch diff --git a/patches/server/0413-Brand-support.patch b/patches/server/0413-Brand-support.patch new file mode 100644 index 000000000000..5c4d8ed97e39 --- /dev/null +++ b/patches/server/0413-Brand-support.patch @@ -0,0 +1,76 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: DigitalRegent +Date: Sat, 11 Apr 2020 13:10:58 +0200 +Subject: [PATCH] Brand support + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 966b86a2b26a32aad2656d1f2beb6daf5b81b3b1..587cce4d3878bc5cba5f4f4e58eacce2f656e242 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -273,6 +273,7 @@ public class ServerPlayer extends Player { + public boolean isRealPlayer; // Paper + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper + public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent ++ public @Nullable String clientBrandName = null; // Paper - Brand support + + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { + super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); +diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index e69043316372d98b122ed3788fda79cdd36849e8..6597e6e9987ddb5906909c22704fdfb6557aee8e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -55,6 +55,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + private volatile boolean suspendFlushingOnServerThread = false; + public final java.util.Map packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks + private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit ++ protected static final ResourceLocation MINECRAFT_BRAND = new ResourceLocation("brand"); // Paper - Brand support + + public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit + this.server = minecraftserver; +@@ -110,6 +111,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + + @Override + public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { ++ // Paper start - Brand support ++ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload brandPayload) { ++ this.player.clientBrandName = brandPayload.brand(); ++ } ++ // Paper end - Brand support + if (!(packet.payload() instanceof ServerboundCustomPayloadPacket.UnknownPayload)) { + return; + } +@@ -141,6 +147,15 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + try { + byte[] data = new byte[payload.readableBytes()]; + payload.readBytes(data); ++ // Paper start - Brand support; Retain this incase upstream decides to 'break' the new mechanism in favour of backwards compat... ++ if (identifier.equals(MINECRAFT_BRAND)) { ++ try { ++ this.player.clientBrandName = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.copiedBuffer(data)).readUtf(256); ++ } catch (StringIndexOutOfBoundsException ex) { ++ this.player.clientBrandName = "illegal"; ++ } ++ } ++ // Paper end - Brand support + this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index d6bddec130167af3d72555535045568ee941bb88..6e9fc623f5a56753e2c78c3ff63c6f4de7cbcccb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -3056,6 +3056,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + // Paper end + }; + ++ // Paper start - brand support ++ @Override ++ public String getClientBrandName() { ++ return getHandle().clientBrandName; ++ } ++ // Paper end ++ + public Player.Spigot spigot() + { + return this.spigot; diff --git a/patches/server/0414-Add-playPickupItemAnimation-to-LivingEntity.patch b/patches/server/0414-Add-playPickupItemAnimation-to-LivingEntity.patch new file mode 100644 index 000000000000..56a928d96fd2 --- /dev/null +++ b/patches/server/0414-Add-playPickupItemAnimation-to-LivingEntity.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 23 Aug 2020 19:36:22 +0200 +Subject: [PATCH] Add playPickupItemAnimation to LivingEntity + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 9a9d119e76fca75a9e531f4bbd204ab8eb9a1263..bf581842476b8f554987b452c291a55a1dfc92c5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -949,5 +949,10 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + ((Mob) getHandle()).getJumpControl().jump(); + } + } ++ ++ @Override ++ public void playPickupItemAnimation(org.bukkit.entity.Item item, int quantity) { ++ getHandle().take(((CraftItem) item).getHandle(), quantity); ++ } + // Paper end + } diff --git a/patches/server/0414-Brand-support.patch b/patches/server/0414-Brand-support.patch deleted file mode 100644 index e6b4bbb729d3..000000000000 --- a/patches/server/0414-Brand-support.patch +++ /dev/null @@ -1,76 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: DigitalRegent -Date: Sat, 11 Apr 2020 13:10:58 +0200 -Subject: [PATCH] Brand support - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index ab92c2c0ff5ebb395670c23fe0e3a8122b215874..dc41eb243510fdb1de9ca3a0a8cb871af5272876 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -273,6 +273,7 @@ public class ServerPlayer extends Player { - public boolean isRealPlayer; // Paper - public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper - public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent -+ public @Nullable String clientBrandName = null; // Paper - Brand support - - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { - super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index e69043316372d98b122ed3788fda79cdd36849e8..6597e6e9987ddb5906909c22704fdfb6557aee8e 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -55,6 +55,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - private volatile boolean suspendFlushingOnServerThread = false; - public final java.util.Map packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks - private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit -+ protected static final ResourceLocation MINECRAFT_BRAND = new ResourceLocation("brand"); // Paper - Brand support - - public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit - this.server = minecraftserver; -@@ -110,6 +111,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - - @Override - public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { -+ // Paper start - Brand support -+ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload brandPayload) { -+ this.player.clientBrandName = brandPayload.brand(); -+ } -+ // Paper end - Brand support - if (!(packet.payload() instanceof ServerboundCustomPayloadPacket.UnknownPayload)) { - return; - } -@@ -141,6 +147,15 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - try { - byte[] data = new byte[payload.readableBytes()]; - payload.readBytes(data); -+ // Paper start - Brand support; Retain this incase upstream decides to 'break' the new mechanism in favour of backwards compat... -+ if (identifier.equals(MINECRAFT_BRAND)) { -+ try { -+ this.player.clientBrandName = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.copiedBuffer(data)).readUtf(256); -+ } catch (StringIndexOutOfBoundsException ex) { -+ this.player.clientBrandName = "illegal"; -+ } -+ } -+ // Paper end - Brand support - this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); - } catch (Exception ex) { - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index dcea3f827a79de3581adff51f34220a1d656e8e9..8a289cd0876a8c063a2b5f75ce8eb41f4be98acf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -3005,6 +3005,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - // Paper end - }; - -+ // Paper start - brand support -+ @Override -+ public String getClientBrandName() { -+ return getHandle().clientBrandName; -+ } -+ // Paper end -+ - public Player.Spigot spigot() - { - return this.spigot; diff --git a/patches/server/0415-Add-playPickupItemAnimation-to-LivingEntity.patch b/patches/server/0415-Add-playPickupItemAnimation-to-LivingEntity.patch deleted file mode 100644 index 2a27938ebb41..000000000000 --- a/patches/server/0415-Add-playPickupItemAnimation-to-LivingEntity.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sun, 23 Aug 2020 19:36:22 +0200 -Subject: [PATCH] Add playPickupItemAnimation to LivingEntity - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index c15db60f4a198c0fe754c3579ff93870e968e639..2550546b200f331ef83b20bf5e119a003cadacba 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -936,5 +936,10 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - ((Mob) getHandle()).getJumpControl().jump(); - } - } -+ -+ @Override -+ public void playPickupItemAnimation(org.bukkit.entity.Item item, int quantity) { -+ getHandle().take(((CraftItem) item).getHandle(), quantity); -+ } - // Paper end - } diff --git a/patches/server/0416-Don-t-require-FACING-data.patch b/patches/server/0415-Don-t-require-FACING-data.patch similarity index 100% rename from patches/server/0416-Don-t-require-FACING-data.patch rename to patches/server/0415-Don-t-require-FACING-data.patch diff --git a/patches/server/0416-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch b/patches/server/0416-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch new file mode 100644 index 000000000000..066bed27a219 --- /dev/null +++ b/patches/server/0416-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 22 Aug 2020 23:36:21 +0200 +Subject: [PATCH] Fix SpawnChangeEvent not firing for all use-cases + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index d2da284aa7284c5205e656c48262061980893be6..aa2f23c4f7d25d0f92ff025bb1840aff1b053fa3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1726,9 +1726,11 @@ public class ServerLevel extends Level implements WorldGenLevel { + public void setDefaultSpawnPos(BlockPos pos, float angle) { + // Paper start - Configurable Keep Spawn Loaded range per world + BlockPos prevSpawn = this.getSharedSpawnPos(); ++ Location prevSpawnLoc = this.getWorld().getSpawnLocation(); // Paper - Call SpawnChangeEvent + //ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(new BlockPosition(this.worldData.a(), 0, this.worldData.c())); + + this.levelData.setSpawn(pos, angle); ++ new org.bukkit.event.world.SpawnChangeEvent(this.getWorld(), prevSpawnLoc).callEvent(); // Paper - Call SpawnChangeEvent + if (this.keepSpawnInMemory) { + // if this keepSpawnInMemory is false a plugin has already removed our tickets, do not re-add + this.removeTicketsForSpawn(this.paperConfig().spawn.keepSpawnLoadedRange * 16, prevSpawn); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index af782882479910f13b54081df2443387135874e0..dbe1e59572ca0f98783db456bdab6ee4e79f7689 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -268,12 +268,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public boolean setSpawnLocation(int x, int y, int z, float angle) { + try { +- Location previousLocation = this.getSpawnLocation(); +- this.world.levelData.setSpawn(new BlockPos(x, y, z), angle); ++ // Location previousLocation = this.getSpawnLocation(); // Paper - Call SpawnChangeEvent; moved to nms.ServerLevel ++ this.world.setDefaultSpawnPos(new BlockPos(x, y, z), angle); // Paper - use ServerLevel#setDefaultSpawnPos + ++ // Paper start - Call SpawnChangeEvent; move to nms.ServerLevel + // Notify anyone who's listening. +- SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); +- this.server.getPluginManager().callEvent(event); ++ // SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); ++ // server.getPluginManager().callEvent(event); ++ // Paper end - Call SpawnChangeEvent + + return true; + } catch (Exception e) { diff --git a/patches/server/0418-Add-moon-phase-API.patch b/patches/server/0417-Add-moon-phase-API.patch similarity index 100% rename from patches/server/0418-Add-moon-phase-API.patch rename to patches/server/0417-Add-moon-phase-API.patch diff --git a/patches/server/0417-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch b/patches/server/0417-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch deleted file mode 100644 index 682fbc24650d..000000000000 --- a/patches/server/0417-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 22 Aug 2020 23:36:21 +0200 -Subject: [PATCH] Fix SpawnChangeEvent not firing for all use-cases - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index d2da284aa7284c5205e656c48262061980893be6..aa2f23c4f7d25d0f92ff025bb1840aff1b053fa3 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1726,9 +1726,11 @@ public class ServerLevel extends Level implements WorldGenLevel { - public void setDefaultSpawnPos(BlockPos pos, float angle) { - // Paper start - Configurable Keep Spawn Loaded range per world - BlockPos prevSpawn = this.getSharedSpawnPos(); -+ Location prevSpawnLoc = this.getWorld().getSpawnLocation(); // Paper - Call SpawnChangeEvent - //ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(new BlockPosition(this.worldData.a(), 0, this.worldData.c())); - - this.levelData.setSpawn(pos, angle); -+ new org.bukkit.event.world.SpawnChangeEvent(this.getWorld(), prevSpawnLoc).callEvent(); // Paper - Call SpawnChangeEvent - if (this.keepSpawnInMemory) { - // if this keepSpawnInMemory is false a plugin has already removed our tickets, do not re-add - this.removeTicketsForSpawn(this.paperConfig().spawn.keepSpawnLoadedRange * 16, prevSpawn); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 6070409175b106ba6920adf4fa860215cd6f3087..89119833db9660377d4d4cd7d69c7a16f23f8c12 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -262,12 +262,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @Override - public boolean setSpawnLocation(int x, int y, int z, float angle) { - try { -- Location previousLocation = this.getSpawnLocation(); -- this.world.levelData.setSpawn(new BlockPos(x, y, z), angle); -+ // Location previousLocation = this.getSpawnLocation(); // Paper - Call SpawnChangeEvent; moved to nms.ServerLevel -+ this.world.setDefaultSpawnPos(new BlockPos(x, y, z), angle); // Paper - use ServerLevel#setDefaultSpawnPos - -+ // Paper start - Call SpawnChangeEvent; move to nms.ServerLevel - // Notify anyone who's listening. -- SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); -- this.server.getPluginManager().callEvent(event); -+ // SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); -+ // server.getPluginManager().callEvent(event); -+ // Paper end - Call SpawnChangeEvent - - return true; - } catch (Exception e) { diff --git a/patches/server/0418-Do-not-let-the-server-load-chunks-from-newer-version.patch b/patches/server/0418-Do-not-let-the-server-load-chunks-from-newer-version.patch new file mode 100644 index 000000000000..6f37b0bcc059 --- /dev/null +++ b/patches/server/0418-Do-not-let-the-server-load-chunks-from-newer-version.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 23 Jul 2019 20:44:47 -0500 +Subject: [PATCH] Do not let the server load chunks from newer versions + +If the server attempts to load a chunk generated by a newer version of +the game, immediately stop the server to prevent data corruption. + +You can override this functionality at your own peril. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index cbc8e95c8f890f0c0eb717d4d2ae3f427dc260d8..3b046dc106b96b7ca2b148d605e8b7c97453d033 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -84,6 +84,10 @@ public class ChunkSerializer { + public static final String BLOCK_LIGHT_TAG = "BlockLight"; + public static final String SKY_LIGHT_TAG = "SkyLight"; + ++ // Paper start - Do not let the server load chunks from newer versions ++ private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion(); ++ private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); ++ // Paper end - Do not let the server load chunks from newer versions + public ChunkSerializer() {} + + // Paper start - guard against serializing mismatching coordinates +@@ -99,6 +103,15 @@ public class ChunkSerializer { + } + // Paper end - guard against serializing mismatching coordinates + public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) { ++ // Paper start - Do not let the server load chunks from newer versions ++ if (nbt.contains("DataVersion", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { ++ final int dataVersion = nbt.getInt("DataVersion"); ++ if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) { ++ new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace(); ++ System.exit(1); ++ } ++ } ++ // Paper end - Do not let the server load chunks from newer versions + ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate + + if (!Objects.equals(chunkPos, chunkcoordintpair1)) { diff --git a/patches/server/0419-Do-not-let-the-server-load-chunks-from-newer-version.patch b/patches/server/0419-Do-not-let-the-server-load-chunks-from-newer-version.patch deleted file mode 100644 index 1c14f5e79482..000000000000 --- a/patches/server/0419-Do-not-let-the-server-load-chunks-from-newer-version.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Tue, 23 Jul 2019 20:44:47 -0500 -Subject: [PATCH] Do not let the server load chunks from newer versions - -If the server attempts to load a chunk generated by a newer version of -the game, immediately stop the server to prevent data corruption. - -You can override this functionality at your own peril. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 5ef782ef14a9a880cb3db433bbee2d4a70d33718..29aaedbe70901fdd98f15f2ca5ba382106091d1a 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -84,6 +84,10 @@ public class ChunkSerializer { - public static final String BLOCK_LIGHT_TAG = "BlockLight"; - public static final String SKY_LIGHT_TAG = "SkyLight"; - -+ // Paper start - Do not let the server load chunks from newer versions -+ private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion(); -+ private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); -+ // Paper end - Do not let the server load chunks from newer versions - public ChunkSerializer() {} - - // Paper start - guard against serializing mismatching coordinates -@@ -99,6 +103,15 @@ public class ChunkSerializer { - } - // Paper end - guard against serializing mismatching coordinates - public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) { -+ // Paper start - Do not let the server load chunks from newer versions -+ if (nbt.contains("DataVersion", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { -+ final int dataVersion = nbt.getInt("DataVersion"); -+ if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) { -+ new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace(); -+ System.exit(1); -+ } -+ } -+ // Paper end - Do not let the server load chunks from newer versions - ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate - - if (!Objects.equals(chunkPos, chunkcoordintpair1)) { diff --git a/patches/server/0419-Prevent-headless-pistons-from-being-created.patch b/patches/server/0419-Prevent-headless-pistons-from-being-created.patch new file mode 100644 index 000000000000..c4ae7a0fb8d8 --- /dev/null +++ b/patches/server/0419-Prevent-headless-pistons-from-being-created.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: commandblockguy +Date: Fri, 14 Aug 2020 14:44:14 -0500 +Subject: [PATCH] Prevent headless pistons from being created + +Prevent headless pistons from being created by explosions or tree/mushroom growth. + +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index b03b4d366cae39081a7b70524e8615c986d76362..cd939ab6958e8eb632056d32f68e2fcae7735d64 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -205,6 +205,15 @@ public class Explosion { + + if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) { + set.add(blockposition); ++ // Paper start - prevent headless pistons from forming ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { ++ net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockposition); ++ if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) { ++ net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING); ++ set.add(blockposition.relative(direction.getOpposite())); ++ } ++ } ++ // Paper end - prevent headless pistons from forming + } + + d4 += d0 * 0.30000001192092896D; diff --git a/patches/server/0420-Add-BellRingEvent.patch b/patches/server/0420-Add-BellRingEvent.patch new file mode 100644 index 000000000000..b24256750927 --- /dev/null +++ b/patches/server/0420-Add-BellRingEvent.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Eearslya Sleiarion +Date: Sun, 23 Aug 2020 13:04:02 +0200 +Subject: [PATCH] Add BellRingEvent + +Add a new event, BellRingEvent, to trigger whenever a player rings a +village bell. Passes along the bell block and the player who rang it. + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 5a7946d3877eece469f21ee512342847101b2f67..42702b6196ad816bf1bd5df189cc99c58562da24 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -366,10 +366,11 @@ public class CraftEventFactory { + return tradeSelectEvent; + } + ++ @SuppressWarnings("deprecation") // Paper use deprecated event to maintain compat (it extends modern event) + public static boolean handleBellRingEvent(Level world, BlockPos position, Direction direction, Entity entity) { + Block block = CraftBlock.at(world, position); + BlockFace bukkitDirection = CraftBlock.notchToBlockFace(direction); +- BellRingEvent event = new BellRingEvent(block, bukkitDirection, (entity != null) ? entity.getBukkitEntity() : null); ++ BellRingEvent event = new io.papermc.paper.event.block.BellRingEvent(block, bukkitDirection, (entity != null) ? entity.getBukkitEntity() : null); // Paper - deprecated BellRingEvent + Bukkit.getPluginManager().callEvent(event); + return !event.isCancelled(); + } diff --git a/patches/server/0420-Prevent-headless-pistons-from-being-created.patch b/patches/server/0420-Prevent-headless-pistons-from-being-created.patch deleted file mode 100644 index 856b2719cc52..000000000000 --- a/patches/server/0420-Prevent-headless-pistons-from-being-created.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: commandblockguy -Date: Fri, 14 Aug 2020 14:44:14 -0500 -Subject: [PATCH] Prevent headless pistons from being created - -Prevent headless pistons from being created by explosions or tree/mushroom growth. - -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index 653036ca797ed4e87f0cc15898d55ede2ed96206..c7075aaf417b1dc9eab4a19b72fac50d2a44286b 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -205,6 +205,15 @@ public class Explosion { - - if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) { - set.add(blockposition); -+ // Paper start - prevent headless pistons from forming -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { -+ net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockposition); -+ if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) { -+ net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING); -+ set.add(blockposition.relative(direction.getOpposite())); -+ } -+ } -+ // Paper end - prevent headless pistons from forming - } - - d4 += d0 * 0.30000001192092896D; diff --git a/patches/server/0421-Add-BellRingEvent.patch b/patches/server/0421-Add-BellRingEvent.patch deleted file mode 100644 index 6fe7f72b6583..000000000000 --- a/patches/server/0421-Add-BellRingEvent.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Eearslya Sleiarion -Date: Sun, 23 Aug 2020 13:04:02 +0200 -Subject: [PATCH] Add BellRingEvent - -Add a new event, BellRingEvent, to trigger whenever a player rings a -village bell. Passes along the bell block and the player who rang it. - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 8ac093866aec59b4e031b953d4c3c3af66733812..915f49bbfca87682a3ae497dfcd335268f974efe 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -366,10 +366,11 @@ public class CraftEventFactory { - return tradeSelectEvent; - } - -+ @SuppressWarnings("deprecation") // Paper use deprecated event to maintain compat (it extends modern event) - public static boolean handleBellRingEvent(Level world, BlockPos position, Direction direction, Entity entity) { - Block block = CraftBlock.at(world, position); - BlockFace bukkitDirection = CraftBlock.notchToBlockFace(direction); -- BellRingEvent event = new BellRingEvent(block, bukkitDirection, (entity != null) ? entity.getBukkitEntity() : null); -+ BellRingEvent event = new io.papermc.paper.event.block.BellRingEvent(block, bukkitDirection, (entity != null) ? entity.getBukkitEntity() : null); // Paper - deprecated BellRingEvent - Bukkit.getPluginManager().callEvent(event); - return !event.isCancelled(); - } diff --git a/patches/server/0422-Add-zombie-targets-turtle-egg-config.patch b/patches/server/0421-Add-zombie-targets-turtle-egg-config.patch similarity index 100% rename from patches/server/0422-Add-zombie-targets-turtle-egg-config.patch rename to patches/server/0421-Add-zombie-targets-turtle-egg-config.patch diff --git a/patches/server/0422-Buffer-joins-to-world.patch b/patches/server/0422-Buffer-joins-to-world.patch new file mode 100644 index 000000000000..35f260467723 --- /dev/null +++ b/patches/server/0422-Buffer-joins-to-world.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Wed, 19 Aug 2020 05:05:54 +0100 +Subject: [PATCH] Buffer joins to world + +This patch buffers the number of logins which will attempt to join +the world per tick, this attempts to reduce the impact that join floods +has on the server + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 44e62675a2d612a8d727d9ce6db5fb85d1a0bcc8..1113380f6f142d2faf36191aae158c747bed5bb9 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -415,14 +415,29 @@ public class Connection extends SimpleChannelInboundHandler> { + } + } + ++ private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world ++ private static int joinAttemptsThisTick; // Paper - Buffer joins to world ++ private static int currTick; // Paper - Buffer joins to world + public void tick() { + this.flushQueue(); ++ // Paper start - Buffer joins to world ++ if (Connection.currTick != net.minecraft.server.MinecraftServer.currentTick) { ++ Connection.currTick = net.minecraft.server.MinecraftServer.currentTick; ++ Connection.joinAttemptsThisTick = 0; ++ } ++ // Paper end - Buffer joins to world + PacketListener packetlistener = this.packetListener; + + if (packetlistener instanceof TickablePacketListener) { + TickablePacketListener tickablepacketlistener = (TickablePacketListener) packetlistener; + ++ // Paper start - Buffer joins to world ++ if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener) ++ || loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING ++ || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) { + tickablepacketlistener.tick(); ++ } ++ // Paper end - Buffer joins to world + } + + if (!this.isConnected() && !this.disconnectionHandled) { diff --git a/patches/server/0423-Buffer-joins-to-world.patch b/patches/server/0423-Buffer-joins-to-world.patch deleted file mode 100644 index 04aef85833db..000000000000 --- a/patches/server/0423-Buffer-joins-to-world.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Wed, 19 Aug 2020 05:05:54 +0100 -Subject: [PATCH] Buffer joins to world - -This patch buffers the number of logins which will attempt to join -the world per tick, this attempts to reduce the impact that join floods -has on the server - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index dc9f8625e15c49ea64d2b7d9515d36d5ef834820..777681a58417684a35a875c869ab22e50bb27da5 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -415,14 +415,29 @@ public class Connection extends SimpleChannelInboundHandler> { - } - } - -+ private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world -+ private static int joinAttemptsThisTick; // Paper - Buffer joins to world -+ private static int currTick; // Paper - Buffer joins to world - public void tick() { - this.flushQueue(); -+ // Paper start - Buffer joins to world -+ if (Connection.currTick != net.minecraft.server.MinecraftServer.currentTick) { -+ Connection.currTick = net.minecraft.server.MinecraftServer.currentTick; -+ Connection.joinAttemptsThisTick = 0; -+ } -+ // Paper end - Buffer joins to world - PacketListener packetlistener = this.packetListener; - - if (packetlistener instanceof TickablePacketListener) { - TickablePacketListener tickablepacketlistener = (TickablePacketListener) packetlistener; - -+ // Paper start - Buffer joins to world -+ if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener) -+ || loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING -+ || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) { - tickablepacketlistener.tick(); -+ } -+ // Paper end - Buffer joins to world - } - - if (!this.isConnected() && !this.disconnectionHandled) { diff --git a/patches/server/0423-Fix-hex-colors-not-working-in-some-kick-messages.patch b/patches/server/0423-Fix-hex-colors-not-working-in-some-kick-messages.patch new file mode 100644 index 000000000000..c003ab262622 --- /dev/null +++ b/patches/server/0423-Fix-hex-colors-not-working-in-some-kick-messages.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Thu, 27 Aug 2020 16:57:25 -0400 +Subject: [PATCH] Fix hex colors not working in some kick messages + + +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index 08a7461a92ae84cac69e4bb57a099d1f35ff1c1a..9b611fc277baf99a5d24b30a11f0efa77b22693f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -77,14 +77,16 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + } + // CraftBukkit end + if (packet.protocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) { +- MutableComponent ichatmutablecomponent; ++ net.kyori.adventure.text.Component adventureComponent; // Paper - Fix hex colors not working in some kick messages + + if (packet.protocolVersion() < SharedConstants.getCurrentVersion().getProtocolVersion()) { // Spigot - SPIGOT-7546: Handle version check correctly for outdated client message +- ichatmutablecomponent = Component.literal( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot ++ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages + } else { +- ichatmutablecomponent = Component.literal( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot ++ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages + } + ++ Component ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(adventureComponent); // Paper - Fix hex colors not working in some kick messages ++ + this.connection.send(new ClientboundLoginDisconnectPacket(ichatmutablecomponent)); + this.connection.disconnect(ichatmutablecomponent); + } else { +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index f4ed58b03876c35f8964a8a1b8ce89961b9ee6d3..a7da99ac31bbcb8b6f1814a2d5509c7067aafb08 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -99,7 +99,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + // CraftBukkit start + @Deprecated + public void disconnect(String s) { +- this.disconnect(Component.literal(s)); ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s))); // Paper - Fix hex colors not working in some kick messages + } + // CraftBukkit end + diff --git a/patches/server/0424-Fix-hex-colors-not-working-in-some-kick-messages.patch b/patches/server/0424-Fix-hex-colors-not-working-in-some-kick-messages.patch deleted file mode 100644 index 57e037284073..000000000000 --- a/patches/server/0424-Fix-hex-colors-not-working-in-some-kick-messages.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Thu, 27 Aug 2020 16:57:25 -0400 -Subject: [PATCH] Fix hex colors not working in some kick messages - - -diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -index 08a7461a92ae84cac69e4bb57a099d1f35ff1c1a..9b611fc277baf99a5d24b30a11f0efa77b22693f 100644 ---- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -@@ -77,14 +77,16 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - } - // CraftBukkit end - if (packet.protocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) { -- MutableComponent ichatmutablecomponent; -+ net.kyori.adventure.text.Component adventureComponent; // Paper - Fix hex colors not working in some kick messages - - if (packet.protocolVersion() < SharedConstants.getCurrentVersion().getProtocolVersion()) { // Spigot - SPIGOT-7546: Handle version check correctly for outdated client message -- ichatmutablecomponent = Component.literal( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot -+ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages - } else { -- ichatmutablecomponent = Component.literal( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot -+ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages - } - -+ Component ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(adventureComponent); // Paper - Fix hex colors not working in some kick messages -+ - this.connection.send(new ClientboundLoginDisconnectPacket(ichatmutablecomponent)); - this.connection.disconnect(ichatmutablecomponent); - } else { -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index ba0bdd685c49c02bcb1b6d840ddceb9049565d34..d4658328eeecb1c9e3e25eec14dea07e9e2a8b74 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -99,7 +99,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - // CraftBukkit start - @Deprecated - public void disconnect(String s) { -- this.disconnect(Component.literal(s)); -+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s))); // Paper - Fix hex colors not working in some kick messages - } - // CraftBukkit end - diff --git a/patches/server/0424-PortalCreateEvent-needs-to-know-its-entity.patch b/patches/server/0424-PortalCreateEvent-needs-to-know-its-entity.patch new file mode 100644 index 000000000000..910ff0e5d289 --- /dev/null +++ b/patches/server/0424-PortalCreateEvent-needs-to-know-its-entity.patch @@ -0,0 +1,107 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Fri, 21 Aug 2020 20:57:54 +0200 +Subject: [PATCH] PortalCreateEvent needs to know its entity + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 4a6e128c62c890c34b62f826d586ae6a424e7f01..9d2d305a5e66b9f3d94f6464736f5bb40adae591 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -480,7 +480,7 @@ public final class ItemStack { + 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); ++ block.getBlock().onPlace(block, world, newblockposition, oldBlock, true, context); // Paper - pass context + } + + 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/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +index e707553bbddf84bc48ec7186da00c3eb0632946d..839469c1249829b42e752e5a1b613550c3f65bba 100644 +--- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +@@ -144,12 +144,19 @@ public abstract class BaseFireBlock extends Block { + + @Override + public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { ++ // Paper start - UseOnContext param ++ this.onPlace(state, world, pos, oldState, notify, null); ++ } ++ ++ @Override ++ public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify, net.minecraft.world.item.context.UseOnContext context) { ++ // Paper end - UseOnContext param + if (!oldState.is(state.getBlock())) { + if (BaseFireBlock.inPortalDimension(world)) { + Optional optional = PortalShape.findEmptyPortalShape(world, pos, Direction.Axis.X); + + if (optional.isPresent()) { +- ((PortalShape) optional.get()).createPortalBlocks(); ++ ((PortalShape) optional.get()).createPortalBlocks(context); // Paper - pass context param + return; + } + } +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 65b2873ca8032a64a4968b7587637644df1aeca5..c5116d12f3c073f0a8695a8cd00545e6d947644d 100644 +--- a/src/main/java/net/minecraft/world/level/block/FireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java +@@ -368,8 +368,10 @@ public class FireBlock extends BaseFireBlock { + } + + @Override +- public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { +- super.onPlace(state, world, pos, oldState, notify); ++ // Paper start - UseOnContext param ++ public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify, net.minecraft.world.item.context.UseOnContext context) { ++ super.onPlace(state, world, pos, oldState, notify, context); ++ // Paper end - UseOnContext param + world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world.random)); + } + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 6f60622f2dad5f82fb24505612e7e3a32722ab93..c7b6377aafd32f67eb8ba4dedd7cce5841b2d58d 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -163,6 +163,13 @@ public abstract class BlockBehaviour implements FeatureElement { + DebugPackets.sendNeighborsUpdatePacket(world, pos); + } + ++ // Paper start - UseOnContext param ++ @Deprecated ++ public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify, net.minecraft.world.item.context.UseOnContext context) { ++ this.onPlace(state, world, pos, oldState, notify); ++ } ++ // Paper end - UseOnContext param ++ + /** @deprecated */ + @Deprecated + public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { +diff --git a/src/main/java/net/minecraft/world/level/portal/PortalShape.java b/src/main/java/net/minecraft/world/level/portal/PortalShape.java +index 9fc4997277dd199cf6cffccceb3a9735398c5356..912cee9ec45876f831ca230b59a1be3b48ce6aa5 100644 +--- a/src/main/java/net/minecraft/world/level/portal/PortalShape.java ++++ b/src/main/java/net/minecraft/world/level/portal/PortalShape.java +@@ -190,7 +190,14 @@ public class PortalShape { + } + + // CraftBukkit start - return boolean ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper + public boolean createPortalBlocks() { ++ // Paper start - UseOnContext param ++ return this.createPortalBlocks(null); ++ } ++ ++ public boolean createPortalBlocks(net.minecraft.world.item.context.UseOnContext useOnContext) { ++ // Paper end - UseOnContext param + org.bukkit.World bworld = this.level.getMinecraftWorld().getWorld(); + + // Copy below for loop +@@ -200,7 +207,7 @@ public class PortalShape { + this.blocks.setBlock(blockposition, iblockdata, 18); + }); + +- PortalCreateEvent event = new PortalCreateEvent((java.util.List) (java.util.List) this.blocks.getList(), bworld, null, PortalCreateEvent.CreateReason.FIRE); ++ PortalCreateEvent event = new PortalCreateEvent((java.util.List) (java.util.List) blocks.getList(), bworld, useOnContext == null || useOnContext.getPlayer() == null ? null : useOnContext.getPlayer().getBukkitEntity(), PortalCreateEvent.CreateReason.FIRE); // Paper - pass entity param + this.level.getMinecraftWorld().getServer().server.getPluginManager().callEvent(event); + + if (event.isCancelled()) { diff --git a/patches/server/0426-Add-more-Evoker-API.patch b/patches/server/0425-Add-more-Evoker-API.patch similarity index 100% rename from patches/server/0426-Add-more-Evoker-API.patch rename to patches/server/0425-Add-more-Evoker-API.patch diff --git a/patches/server/0425-PortalCreateEvent-needs-to-know-its-entity.patch b/patches/server/0425-PortalCreateEvent-needs-to-know-its-entity.patch deleted file mode 100644 index c6107581a45b..000000000000 --- a/patches/server/0425-PortalCreateEvent-needs-to-know-its-entity.patch +++ /dev/null @@ -1,107 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Fri, 21 Aug 2020 20:57:54 +0200 -Subject: [PATCH] PortalCreateEvent needs to know its entity - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 556d8d395df3660ec7923c6814bc281c5fce442f..b4f2b75960674e81c8189dc908523c56ae2e5079 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -478,7 +478,7 @@ public final class ItemStack { - 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); -+ block.getBlock().onPlace(block, world, newblockposition, oldBlock, true, context); // Paper - pass context - } - - 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/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -index e707553bbddf84bc48ec7186da00c3eb0632946d..839469c1249829b42e752e5a1b613550c3f65bba 100644 ---- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -@@ -144,12 +144,19 @@ public abstract class BaseFireBlock extends Block { - - @Override - public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { -+ // Paper start - UseOnContext param -+ this.onPlace(state, world, pos, oldState, notify, null); -+ } -+ -+ @Override -+ public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify, net.minecraft.world.item.context.UseOnContext context) { -+ // Paper end - UseOnContext param - if (!oldState.is(state.getBlock())) { - if (BaseFireBlock.inPortalDimension(world)) { - Optional optional = PortalShape.findEmptyPortalShape(world, pos, Direction.Axis.X); - - if (optional.isPresent()) { -- ((PortalShape) optional.get()).createPortalBlocks(); -+ ((PortalShape) optional.get()).createPortalBlocks(context); // Paper - pass context param - return; - } - } -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 65b2873ca8032a64a4968b7587637644df1aeca5..c5116d12f3c073f0a8695a8cd00545e6d947644d 100644 ---- a/src/main/java/net/minecraft/world/level/block/FireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java -@@ -368,8 +368,10 @@ public class FireBlock extends BaseFireBlock { - } - - @Override -- public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { -- super.onPlace(state, world, pos, oldState, notify); -+ // Paper start - UseOnContext param -+ public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify, net.minecraft.world.item.context.UseOnContext context) { -+ super.onPlace(state, world, pos, oldState, notify, context); -+ // Paper end - UseOnContext param - world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world.random)); - } - -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 6f60622f2dad5f82fb24505612e7e3a32722ab93..c7b6377aafd32f67eb8ba4dedd7cce5841b2d58d 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -163,6 +163,13 @@ public abstract class BlockBehaviour implements FeatureElement { - DebugPackets.sendNeighborsUpdatePacket(world, pos); - } - -+ // Paper start - UseOnContext param -+ @Deprecated -+ public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify, net.minecraft.world.item.context.UseOnContext context) { -+ this.onPlace(state, world, pos, oldState, notify); -+ } -+ // Paper end - UseOnContext param -+ - /** @deprecated */ - @Deprecated - public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { -diff --git a/src/main/java/net/minecraft/world/level/portal/PortalShape.java b/src/main/java/net/minecraft/world/level/portal/PortalShape.java -index 9fc4997277dd199cf6cffccceb3a9735398c5356..912cee9ec45876f831ca230b59a1be3b48ce6aa5 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalShape.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalShape.java -@@ -190,7 +190,14 @@ public class PortalShape { - } - - // CraftBukkit start - return boolean -+ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - public boolean createPortalBlocks() { -+ // Paper start - UseOnContext param -+ return this.createPortalBlocks(null); -+ } -+ -+ public boolean createPortalBlocks(net.minecraft.world.item.context.UseOnContext useOnContext) { -+ // Paper end - UseOnContext param - org.bukkit.World bworld = this.level.getMinecraftWorld().getWorld(); - - // Copy below for loop -@@ -200,7 +207,7 @@ public class PortalShape { - this.blocks.setBlock(blockposition, iblockdata, 18); - }); - -- PortalCreateEvent event = new PortalCreateEvent((java.util.List) (java.util.List) this.blocks.getList(), bworld, null, PortalCreateEvent.CreateReason.FIRE); -+ PortalCreateEvent event = new PortalCreateEvent((java.util.List) (java.util.List) blocks.getList(), bworld, useOnContext == null || useOnContext.getPlayer() == null ? null : useOnContext.getPlayer().getBukkitEntity(), PortalCreateEvent.CreateReason.FIRE); // Paper - pass entity param - this.level.getMinecraftWorld().getServer().server.getPluginManager().callEvent(event); - - if (event.isCancelled()) { diff --git a/patches/server/0427-Add-methods-to-get-translation-keys.patch b/patches/server/0426-Add-methods-to-get-translation-keys.patch similarity index 100% rename from patches/server/0427-Add-methods-to-get-translation-keys.patch rename to patches/server/0426-Add-methods-to-get-translation-keys.patch diff --git a/patches/server/0428-Create-HoverEvent-from-ItemStack-Entity.patch b/patches/server/0427-Create-HoverEvent-from-ItemStack-Entity.patch similarity index 100% rename from patches/server/0428-Create-HoverEvent-from-ItemStack-Entity.patch rename to patches/server/0427-Create-HoverEvent-from-ItemStack-Entity.patch diff --git a/patches/server/0428-Cache-block-data-strings.patch b/patches/server/0428-Cache-block-data-strings.patch new file mode 100644 index 000000000000..92b4d334d6aa --- /dev/null +++ b/patches/server/0428-Cache-block-data-strings.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: miclebrick +Date: Thu, 6 Dec 2018 19:52:50 -0500 +Subject: [PATCH] Cache block data strings + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 21176d6e57ae9e975b1e7de14b3364365cc1012d..47ad733022e5d17d839209e4163e8508e57b43c8 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2068,6 +2068,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop>, Enum[]> ENUM_VALUES = new HashMap<>(); ++ private static final Map>, Enum[]> ENUM_VALUES = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - cache block data strings; make thread safe + + /** + * Convert an NMS Enum (usually a BlockStateEnum) to its appropriate Bukkit +@@ -537,9 +537,39 @@ public class CraftBlockData implements BlockData { + Preconditions.checkState(CraftBlockData.MAP.put(nms, bukkit) == null, "Duplicate mapping %s->%s", nms, bukkit); + } + ++ // Paper start - cache block data strings ++ private static Map stringDataCache = new java.util.concurrent.ConcurrentHashMap<>(); ++ ++ static { ++ // cache all of the default states at startup, will not cache ones with the custom states inside of the ++ // brackets in a different order, though ++ reloadCache(); ++ } ++ ++ public static void reloadCache() { ++ stringDataCache.clear(); ++ Block.BLOCK_STATE_REGISTRY.forEach(blockData -> stringDataCache.put(blockData.toString(), blockData.createCraftBlockData())); ++ } ++ // Paper end - cache block data strings ++ + public static CraftBlockData newData(Material material, String data) { + Preconditions.checkArgument(material == null || material.isBlock(), "Cannot get data for not block %s", material); + ++ // Paper start - cache block data strings ++ if (material != null) { ++ Block block = CraftBlockType.bukkitToMinecraft(material); ++ if (block != null) { ++ net.minecraft.resources.ResourceLocation key = BuiltInRegistries.BLOCK.getKey(block); ++ data = data == null ? key.toString() : key + data; ++ } ++ } ++ ++ CraftBlockData cached = stringDataCache.computeIfAbsent(data, s -> createNewData(null, s)); ++ return (CraftBlockData) cached.clone(); ++ } ++ ++ private static CraftBlockData createNewData(Material material, String data) { ++ // Paper end - cache block data strings + net.minecraft.world.level.block.state.BlockState blockData; + Block block = CraftBlockType.bukkitToMinecraft(material); + Map, Comparable> parsed = null; diff --git a/patches/server/0429-Cache-block-data-strings.patch b/patches/server/0429-Cache-block-data-strings.patch deleted file mode 100644 index b814a2a2e470..000000000000 --- a/patches/server/0429-Cache-block-data-strings.patch +++ /dev/null @@ -1,71 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: miclebrick -Date: Thu, 6 Dec 2018 19:52:50 -0500 -Subject: [PATCH] Cache block data strings - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 9d41c8e93aa97a78da26bef5cfbed8412d4e1451..dde00d50b28928e35e1f95ef3f0eb46828e9d4bc 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2068,6 +2068,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop>, Enum[]> ENUM_VALUES = new HashMap<>(); -+ private static final Map>, Enum[]> ENUM_VALUES = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - cache block data strings; make thread safe - - /** - * Convert an NMS Enum (usually a BlockStateEnum) to its appropriate Bukkit -@@ -537,9 +537,39 @@ public class CraftBlockData implements BlockData { - Preconditions.checkState(CraftBlockData.MAP.put(nms, bukkit) == null, "Duplicate mapping %s->%s", nms, bukkit); - } - -+ // Paper start - cache block data strings -+ private static Map stringDataCache = new java.util.concurrent.ConcurrentHashMap<>(); -+ -+ static { -+ // cache all of the default states at startup, will not cache ones with the custom states inside of the -+ // brackets in a different order, though -+ reloadCache(); -+ } -+ -+ public static void reloadCache() { -+ stringDataCache.clear(); -+ Block.BLOCK_STATE_REGISTRY.forEach(blockData -> stringDataCache.put(blockData.toString(), blockData.createCraftBlockData())); -+ } -+ // Paper end - cache block data strings -+ - public static CraftBlockData newData(Material material, String data) { - Preconditions.checkArgument(material == null || material.isBlock(), "Cannot get data for not block %s", material); - -+ // Paper start - cache block data strings -+ if (material != null) { -+ Block block = CraftBlockType.bukkitToMinecraft(material); -+ if (block != null) { -+ net.minecraft.resources.ResourceLocation key = BuiltInRegistries.BLOCK.getKey(block); -+ data = data == null ? key.toString() : key + data; -+ } -+ } -+ -+ CraftBlockData cached = stringDataCache.computeIfAbsent(data, s -> createNewData(null, s)); -+ return (CraftBlockData) cached.clone(); -+ } -+ -+ private static CraftBlockData createNewData(Material material, String data) { -+ // Paper end - cache block data strings - net.minecraft.world.level.block.state.BlockState blockData; - Block block = CraftBlockType.bukkitToMinecraft(material); - Map, Comparable> parsed = null; diff --git a/patches/server/0429-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch b/patches/server/0429-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch new file mode 100644 index 000000000000..26f5e206935f --- /dev/null +++ b/patches/server/0429-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 25 Aug 2020 20:45:36 -0400 +Subject: [PATCH] Fix Entity Teleportation and cancel velocity if teleported + +Uses correct setPositionRotation for Entity teleporting instead of setLocation +as this is how Vanilla teleports entities. + +Cancel any pending motion when teleported. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 22d6c914ffbe591d3de19b89a0e87d1042bf1772..4e37b9a495babec58f60b59db0e034d5e033b198 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -656,7 +656,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + return; + } + +- this.player.absMoveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); ++ this.player.moveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); // Paper - Fix Entity Teleportation and cancel velocity if teleported + this.lastGoodX = this.awaitingPositionFromClient.x; + this.lastGoodY = this.awaitingPositionFromClient.y; + this.lastGoodZ = this.awaitingPositionFromClient.z; +@@ -1587,7 +1587,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // CraftBukkit end + + this.awaitingTeleportTime = this.tickCount; +- this.player.absMoveTo(d0, d1, d2, f, f1); ++ this.player.moveTo(d0, d1, d2, f, f1); // Paper - Fix Entity Teleportation and cancel velocity if teleported + this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport)); + } + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 2811177ed3f1f58c63820ddf6ec382f9c806ecd8..0d1afa04c234837f3bc2b50b62e4171ce653cdb2 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -161,6 +161,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + + // CraftBukkit start + private static final int CURRENT_LEVEL = 2; ++ public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation + static boolean isLevelAtLeast(CompoundTag tag, int level) { + return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; + } +@@ -1772,6 +1773,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + public void moveTo(double x, double y, double z, float yaw, float pitch) { ++ // Paper start - Fix Entity Teleportation and cancel velocity if teleported ++ if (!preserveMotion) { ++ this.deltaMovement = Vec3.ZERO; ++ } else { ++ this.preserveMotion = false; ++ } ++ // Paper end - Fix Entity Teleportation and cancel velocity if teleported + this.setPosRaw(x, y, z); + this.setYRot(yaw); + this.setXRot(pitch); +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index b771f954f3fccd92e15196bf542e0d3703cfb71a..41d2793e69bd664456b5e3c5891b03bdcb31d103 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -162,6 +162,7 @@ public abstract class BaseSpawner { + return; + } + ++ entity.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; preserve entity motion from tag + entity.moveTo(entity.getX(), entity.getY(), entity.getZ(), randomsource.nextFloat() * 360.0F, 0.0F); + if (entity instanceof Mob) { + Mob entityinsentient = (Mob) entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index c17bb16567ad2354dc80e710469730b1dbc55b08..d5e8c8ed7528cdac203a7594ccf9576db0e5f019 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -237,7 +237,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + + // entity.setLocation() throws no event, and so cannot be cancelled +- this.entity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); ++ entity.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); // Paper - use proper moveTo, as per vanilla teleporting + // SPIGOT-619: Force sync head rotation also + this.entity.setYHeadRot(location.getYaw()); + diff --git a/patches/server/0431-Add-additional-open-container-api-to-HumanEntity.patch b/patches/server/0430-Add-additional-open-container-api-to-HumanEntity.patch similarity index 100% rename from patches/server/0431-Add-additional-open-container-api-to-HumanEntity.patch rename to patches/server/0430-Add-additional-open-container-api-to-HumanEntity.patch diff --git a/patches/server/0430-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch b/patches/server/0430-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch deleted file mode 100644 index 1481a759dc39..000000000000 --- a/patches/server/0430-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 25 Aug 2020 20:45:36 -0400 -Subject: [PATCH] Fix Entity Teleportation and cancel velocity if teleported - -Uses correct setPositionRotation for Entity teleporting instead of setLocation -as this is how Vanilla teleports entities. - -Cancel any pending motion when teleported. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 540c33baacc57f5ec46b2f373d1cca2857463ecd..e87ffaed1be9e849ea98bde8622a0a0a4645c435 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -656,7 +656,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - return; - } - -- this.player.absMoveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); -+ this.player.moveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); // Paper - Fix Entity Teleportation and cancel velocity if teleported - this.lastGoodX = this.awaitingPositionFromClient.x; - this.lastGoodY = this.awaitingPositionFromClient.y; - this.lastGoodZ = this.awaitingPositionFromClient.z; -@@ -1587,7 +1587,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // CraftBukkit end - - this.awaitingTeleportTime = this.tickCount; -- this.player.absMoveTo(d0, d1, d2, f, f1); -+ this.player.moveTo(d0, d1, d2, f, f1); // Paper - Fix Entity Teleportation and cancel velocity if teleported - this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport)); - } - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 70380d5807cac6dec19ebe581d685b1e32d8830b..9946f04c7e5877f094293c4c0976aeecf5c83c56 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -161,6 +161,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - - // CraftBukkit start - private static final int CURRENT_LEVEL = 2; -+ public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation - static boolean isLevelAtLeast(CompoundTag tag, int level) { - return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; - } -@@ -1773,6 +1774,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - public void moveTo(double x, double y, double z, float yaw, float pitch) { -+ // Paper start - Fix Entity Teleportation and cancel velocity if teleported -+ if (!preserveMotion) { -+ this.deltaMovement = Vec3.ZERO; -+ } else { -+ this.preserveMotion = false; -+ } -+ // Paper end - Fix Entity Teleportation and cancel velocity if teleported - this.setPosRaw(x, y, z); - this.setYRot(yaw); - this.setXRot(pitch); -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index b771f954f3fccd92e15196bf542e0d3703cfb71a..41d2793e69bd664456b5e3c5891b03bdcb31d103 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -162,6 +162,7 @@ public abstract class BaseSpawner { - return; - } - -+ entity.preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; preserve entity motion from tag - entity.moveTo(entity.getX(), entity.getY(), entity.getZ(), randomsource.nextFloat() * 360.0F, 0.0F); - if (entity instanceof Mob) { - Mob entityinsentient = (Mob) entity; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index c17bb16567ad2354dc80e710469730b1dbc55b08..d5e8c8ed7528cdac203a7594ccf9576db0e5f019 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -237,7 +237,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - } - - // entity.setLocation() throws no event, and so cannot be cancelled -- this.entity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); -+ entity.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); // Paper - use proper moveTo, as per vanilla teleporting - // SPIGOT-619: Force sync head rotation also - this.entity.setYHeadRot(location.getYaw()); - diff --git a/patches/server/0432-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch b/patches/server/0431-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch similarity index 100% rename from patches/server/0432-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch rename to patches/server/0431-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch diff --git a/patches/server/0432-Extend-block-drop-capture-to-capture-all-items-added.patch b/patches/server/0432-Extend-block-drop-capture-to-capture-all-items-added.patch new file mode 100644 index 000000000000..f7a5dbb2e8cd --- /dev/null +++ b/patches/server/0432-Extend-block-drop-capture-to-capture-all-items-added.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 17 Sep 2020 00:36:05 +0100 +Subject: [PATCH] Extend block drop capture to capture all items added to the + world + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index aa2f23c4f7d25d0f92ff025bb1840aff1b053fa3..a668e204946943bcc963ad41ea3029111267eef9 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1224,6 +1224,12 @@ public class ServerLevel extends Level implements WorldGenLevel { + // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit + return false; + } else { ++ // Paper start - capture all item additions to the world ++ if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { ++ captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); ++ return true; ++ } ++ // Paper end - capture all item additions to the world + // SPIGOT-6415: Don't call spawn event when reason is null. For example when an entity teleports to a new world. + if (spawnReason != null && !CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) { + return false; +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index c3eb4b6372eed0b7eb636f495ce494b676767b6e..a03d1a85019afdc42de2b8449fc38384c4dac51e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -429,10 +429,12 @@ public class ServerPlayerGameMode { + // return true; // CraftBukkit + } + // CraftBukkit start ++ java.util.List itemsToDrop = this.level.captureDrops; // Paper - capture all item additions to the world ++ this.level.captureDrops = null; // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff + if (event.isDropItems()) { +- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, this.level.captureDrops); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - capture all item additions to the world + } +- this.level.captureDrops = null; ++ //this.level.captureDrops = null; // Paper - capture all item additions to the world; move up + + // Drop event experience + if (flag && event != null) { diff --git a/patches/server/0433-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch b/patches/server/0433-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch new file mode 100644 index 000000000000..4c2ebf298dd4 --- /dev/null +++ b/patches/server/0433-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MeFisto94 +Date: Fri, 28 Aug 2020 01:41:26 +0200 +Subject: [PATCH] Expose the Entity Counter to allow plugins to use valid and + non-conflicting Entity Ids + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 8dc1bb56bc3d82d7b3dd7b3a495c5bd5740f49ad..d9b5e3ea1defabff373a4f90e41effc086e75bab 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -4387,4 +4387,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + + void accept(Entity entity, double x, double y, double z); + } ++ ++ // Paper start - Expose entity id counter ++ public static int nextEntityId() { ++ return ENTITY_COUNTER.incrementAndGet(); ++ } ++ // Paper end - Expose entity id counter + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index ef4b15e0ae11a54ec49e40f1d694dae58ae95e03..2bc3d9c02256269845d140764b7b1b201e38b569 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -520,6 +520,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); + return compound; + } ++ ++ @Override ++ public int nextEntityId() { ++ return net.minecraft.world.entity.Entity.nextEntityId(); ++ } + // Paper end + + /** diff --git a/patches/server/0433-Extend-block-drop-capture-to-capture-all-items-added.patch b/patches/server/0433-Extend-block-drop-capture-to-capture-all-items-added.patch deleted file mode 100644 index 771fb682ab08..000000000000 --- a/patches/server/0433-Extend-block-drop-capture-to-capture-all-items-added.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Thu, 17 Sep 2020 00:36:05 +0100 -Subject: [PATCH] Extend block drop capture to capture all items added to the - world - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index aa2f23c4f7d25d0f92ff025bb1840aff1b053fa3..a668e204946943bcc963ad41ea3029111267eef9 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1224,6 +1224,12 @@ public class ServerLevel extends Level implements WorldGenLevel { - // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit - return false; - } else { -+ // Paper start - capture all item additions to the world -+ if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { -+ captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); -+ return true; -+ } -+ // Paper end - capture all item additions to the world - // SPIGOT-6415: Don't call spawn event when reason is null. For example when an entity teleports to a new world. - if (spawnReason != null && !CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) { - return false; -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 6721b086ec4e8efe29b75a0e08dc15015e180c09..f66ce9ae705b0fbe17a1bb437bad6808ff47ed92 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -429,10 +429,12 @@ public class ServerPlayerGameMode { - // return true; // CraftBukkit - } - // CraftBukkit start -+ java.util.List itemsToDrop = this.level.captureDrops; // Paper - capture all item additions to the world -+ this.level.captureDrops = null; // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff - if (event.isDropItems()) { -- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, this.level.captureDrops); -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - capture all item additions to the world - } -- this.level.captureDrops = null; -+ //this.level.captureDrops = null; // Paper - capture all item additions to the world; move up - - // Drop event experience - if (flag && event != null) { diff --git a/patches/server/0434-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch b/patches/server/0434-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch deleted file mode 100644 index 15d6357897de..000000000000 --- a/patches/server/0434-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch +++ /dev/null @@ -1,38 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MeFisto94 -Date: Fri, 28 Aug 2020 01:41:26 +0200 -Subject: [PATCH] Expose the Entity Counter to allow plugins to use valid and - non-conflicting Entity Ids - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 9946f04c7e5877f094293c4c0976aeecf5c83c56..5dce8180a7ddfdd5fd32509010bdbaa66788a7c2 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -4384,4 +4384,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - - void accept(Entity entity, double x, double y, double z); - } -+ -+ // Paper start - Expose entity id counter -+ public static int nextEntityId() { -+ return ENTITY_COUNTER.incrementAndGet(); -+ } -+ // Paper end - Expose entity id counter - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 41fb303191783ad9e531331dc8468f95139432b9..fc6dee1d9eb47025e26fcc20eb15a36bfe090d9d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -505,6 +505,11 @@ public final class CraftMagicNumbers implements UnsafeValues { - Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); - return compound; - } -+ -+ @Override -+ public int nextEntityId() { -+ return net.minecraft.world.entity.Entity.nextEntityId(); -+ } - // Paper end - - /** diff --git a/patches/server/0435-Lazily-track-plugin-scoreboards-by-default.patch b/patches/server/0434-Lazily-track-plugin-scoreboards-by-default.patch similarity index 100% rename from patches/server/0435-Lazily-track-plugin-scoreboards-by-default.patch rename to patches/server/0434-Lazily-track-plugin-scoreboards-by-default.patch diff --git a/patches/server/0435-Entity-isTicking.patch b/patches/server/0435-Entity-isTicking.patch new file mode 100644 index 000000000000..0ce0d8633a56 --- /dev/null +++ b/patches/server/0435-Entity-isTicking.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 3 Oct 2020 21:39:16 -0500 +Subject: [PATCH] Entity#isTicking + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index d9b5e3ea1defabff373a4f90e41effc086e75bab..7c436b2a6d9b516469088a6d67f07b6b621f5201 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -4392,5 +4392,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + public static int nextEntityId() { + return ENTITY_COUNTER.incrementAndGet(); + } ++ ++ public boolean isTicking() { ++ return ((net.minecraft.server.level.ServerChunkCache) level.getChunkSource()).isPositionTicking(this); ++ } + // Paper end - Expose entity id counter + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index d5e8c8ed7528cdac203a7594ccf9576db0e5f019..6e302dff4c0b48694d234091b1637ff3b7b4b098 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1058,5 +1058,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public boolean isInLava() { + return getHandle().isInLava(); + } ++ ++ @Override ++ public boolean isTicking() { ++ return getHandle().isTicking(); ++ } + // Paper end + } diff --git a/patches/server/0436-Entity-isTicking.patch b/patches/server/0436-Entity-isTicking.patch deleted file mode 100644 index 0dd291d2b599..000000000000 --- a/patches/server/0436-Entity-isTicking.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 3 Oct 2020 21:39:16 -0500 -Subject: [PATCH] Entity#isTicking - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 5dce8180a7ddfdd5fd32509010bdbaa66788a7c2..f4f5175c3c8c12d390470b7f44ad9d2ca39ff1fc 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -4389,5 +4389,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - public static int nextEntityId() { - return ENTITY_COUNTER.incrementAndGet(); - } -+ -+ public boolean isTicking() { -+ return ((net.minecraft.server.level.ServerChunkCache) level.getChunkSource()).isPositionTicking(this); -+ } - // Paper end - Expose entity id counter - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index d5e8c8ed7528cdac203a7594ccf9576db0e5f019..6e302dff4c0b48694d234091b1637ff3b7b4b098 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1058,5 +1058,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - public boolean isInLava() { - return getHandle().isInLava(); - } -+ -+ @Override -+ public boolean isTicking() { -+ return getHandle().isTicking(); -+ } - // Paper end - } diff --git a/patches/server/0436-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch b/patches/server/0436-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch new file mode 100644 index 000000000000..ad63085c7a01 --- /dev/null +++ b/patches/server/0436-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 3 Oct 2020 22:00:27 -0500 +Subject: [PATCH] Fix deop kicking non-whitelisted player when white list is + not enabled + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 47ad733022e5d17d839209e4163e8508e57b43c8..c2892af4e98d6a436a43fea950726cc1ca60ebda 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2151,13 +2151,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = Lists.newArrayList(playerlist.getPlayers()); + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + +- if (!whitelist.isWhiteListed(entityplayer.getGameProfile())) { ++ if (!whitelist.isWhiteListed(entityplayer.getGameProfile()) && !this.getPlayerList().isOp(entityplayer.getGameProfile())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420) + entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.not_whitelisted")); + } + } diff --git a/patches/server/0438-Fix-Concurrency-issue-in-ShufflingList.patch b/patches/server/0437-Fix-Concurrency-issue-in-ShufflingList.patch similarity index 100% rename from patches/server/0438-Fix-Concurrency-issue-in-ShufflingList.patch rename to patches/server/0437-Fix-Concurrency-issue-in-ShufflingList.patch diff --git a/patches/server/0437-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch b/patches/server/0437-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch deleted file mode 100644 index fd15b22600e6..000000000000 --- a/patches/server/0437-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 3 Oct 2020 22:00:27 -0500 -Subject: [PATCH] Fix deop kicking non-whitelisted player when white list is - not enabled - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index dde00d50b28928e35e1f95ef3f0eb46828e9d4bc..d1bd7ccd6e36497849837072c8f1326336409b42 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2151,13 +2151,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = Lists.newArrayList(playerlist.getPlayers()); - Iterator iterator = list.iterator(); - - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); - -- if (!whitelist.isWhiteListed(entityplayer.getGameProfile())) { -+ if (!whitelist.isWhiteListed(entityplayer.getGameProfile()) && !this.getPlayerList().isOp(entityplayer.getGameProfile())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420) - entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.not_whitelisted")); - } - } diff --git a/patches/server/0438-Reset-Ender-Crystals-on-Dragon-Spawn.patch b/patches/server/0438-Reset-Ender-Crystals-on-Dragon-Spawn.patch new file mode 100644 index 000000000000..b93dbed55c30 --- /dev/null +++ b/patches/server/0438-Reset-Ender-Crystals-on-Dragon-Spawn.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 1 Jun 2016 23:29:17 -0400 +Subject: [PATCH] Reset Ender Crystals on Dragon Spawn + +Crystals can end up in a bad state in certain conditions which causes +an exception on the expected number of crystals going negative. + +This ensures the crystals/pillars are in expected state when the dragon spawns. + +See #3522 + +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 cad7a1b28c9d7a3e67dbf0865cbf232ebd39a8d9..3953bbfdaf3e93468108d194c215e6242e14f067 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 +@@ -472,6 +472,7 @@ public class EndDragonFight { + entityenderdragon.moveTo((double) this.origin.getX(), (double) (128 + this.origin.getY()), (double) this.origin.getZ(), this.level.random.nextFloat() * 360.0F, 0.0F); + this.level.addFreshEntity(entityenderdragon); + this.dragonUUID = entityenderdragon.getUUID(); ++ this.resetSpikeCrystals(); // Paper - Reset ender crystals on dragon spawn + } + + return entityenderdragon; diff --git a/patches/server/0439-Fix-for-large-move-vectors-crashing-server.patch b/patches/server/0439-Fix-for-large-move-vectors-crashing-server.patch new file mode 100644 index 000000000000..5b7b0e19dbf2 --- /dev/null +++ b/patches/server/0439-Fix-for-large-move-vectors-crashing-server.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 17 May 2020 23:47:33 -0700 +Subject: [PATCH] Fix for large move vectors crashing server + +Check movement distance also based on current position. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 4e37b9a495babec58f60b59db0e034d5e033b198..5e605b9bdb76ca0c9529e7351432578855cc7fa2 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -472,9 +472,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + float prevYaw = this.player.getYRot(); + float prevPitch = this.player.getXRot(); + // CraftBukkit end +- double d0 = entity.getX(); +- double d1 = entity.getY(); +- double d2 = entity.getZ(); ++ double d0 = entity.getX();final double fromX = d0; // Paper - OBFHELPER ++ double d1 = entity.getY();final double fromY = d1; // Paper - OBFHELPER ++ double d2 = entity.getZ();final double fromZ = d2; // Paper - OBFHELPER + double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); final double toX = d3; // Paper - OBFHELPER + double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); final double toY = d4; // Paper - OBFHELPER + double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); final double toZ = d5; // Paper - OBFHELPER +@@ -484,7 +484,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + double d7 = d4 - this.vehicleFirstGoodY; + double d8 = d5 - this.vehicleFirstGoodZ; + double d9 = entity.getDeltaMovement().lengthSqr(); +- double d10 = d6 * d6 + d7 * d7 + d8 * d8; ++ // Paper start - fix large move vectors killing the server ++ double currDeltaX = toX - fromX; ++ double currDeltaY = toY - fromY; ++ double currDeltaZ = toZ - fromZ; ++ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); ++ double otherFieldX = d3 - this.vehicleLastGoodX; ++ double otherFieldY = d4 - this.vehicleLastGoodY - 1.0E-6D; ++ double otherFieldZ = d5 - this.vehicleLastGoodZ; ++ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); ++ // Paper end - fix large move vectors killing the server + + // CraftBukkit start - handle custom speeds and skipped ticks + this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; +@@ -530,9 +539,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); + +- d6 = d3 - this.vehicleLastGoodX; +- d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; +- d8 = d5 - this.vehicleLastGoodZ; ++ d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above ++ d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; // Paper - diff on change, used for checking large move vectors above ++ d8 = d5 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above + boolean flag1 = entity.verticalCollisionBelow; + + if (entity instanceof LivingEntity) { +@@ -1272,7 +1281,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + double d7 = d1 - this.firstGoodY; + double d8 = d2 - this.firstGoodZ; + double d9 = this.player.getDeltaMovement().lengthSqr(); +- double d10 = d6 * d6 + d7 * d7 + d8 * d8; ++ // Paper start - fix large move vectors killing the server ++ double currDeltaX = toX - prevX; ++ double currDeltaY = toY - prevY; ++ double currDeltaZ = toZ - prevZ; ++ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); ++ double otherFieldX = d0 - this.lastGoodX; ++ double otherFieldY = d1 - this.lastGoodY; ++ double otherFieldZ = d2 - this.lastGoodZ; ++ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); ++ // Paper end - fix large move vectors killing the server + + if (this.player.isSleeping()) { + if (d10 > 1.0D) { +@@ -1326,9 +1344,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + AABB axisalignedbb = this.player.getBoundingBox(); + +- d6 = d0 - this.lastGoodX; +- d7 = d1 - this.lastGoodY; +- d8 = d2 - this.lastGoodZ; ++ d6 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above ++ d7 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above ++ d8 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above + boolean flag = d7 > 0.0D; + + if (this.player.onGround() && !packet.isOnGround() && flag) { diff --git a/patches/server/0439-Reset-Ender-Crystals-on-Dragon-Spawn.patch b/patches/server/0439-Reset-Ender-Crystals-on-Dragon-Spawn.patch deleted file mode 100644 index 150b197347c1..000000000000 --- a/patches/server/0439-Reset-Ender-Crystals-on-Dragon-Spawn.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 1 Jun 2016 23:29:17 -0400 -Subject: [PATCH] Reset Ender Crystals on Dragon Spawn - -Crystals can end up in a bad state in certain conditions which causes -an exception on the expected number of crystals going negative. - -This ensures the crystals/pillars are in expected state when the dragon spawns. - -See #3522 - -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 d6b6c83a6d660107956a28c16fde2260583722aa..43f9635cfb3b1f57774ec0e33e0909d08a01db91 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 -@@ -472,6 +472,7 @@ public class EndDragonFight { - entityenderdragon.moveTo((double) this.origin.getX(), (double) (128 + this.origin.getY()), (double) this.origin.getZ(), this.level.random.nextFloat() * 360.0F, 0.0F); - this.level.addFreshEntity(entityenderdragon); - this.dragonUUID = entityenderdragon.getUUID(); -+ this.resetSpikeCrystals(); // Paper - Reset ender crystals on dragon spawn - } - - return entityenderdragon; diff --git a/patches/server/0440-Fix-for-large-move-vectors-crashing-server.patch b/patches/server/0440-Fix-for-large-move-vectors-crashing-server.patch deleted file mode 100644 index 3335427256b3..000000000000 --- a/patches/server/0440-Fix-for-large-move-vectors-crashing-server.patch +++ /dev/null @@ -1,86 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 17 May 2020 23:47:33 -0700 -Subject: [PATCH] Fix for large move vectors crashing server - -Check movement distance also based on current position. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index e87ffaed1be9e849ea98bde8622a0a0a4645c435..43e94e1dcf3e4c6c45cba2c7d75ac938938514b2 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -472,9 +472,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - float prevYaw = this.player.getYRot(); - float prevPitch = this.player.getXRot(); - // CraftBukkit end -- double d0 = entity.getX(); -- double d1 = entity.getY(); -- double d2 = entity.getZ(); -+ double d0 = entity.getX();final double fromX = d0; // Paper - OBFHELPER -+ double d1 = entity.getY();final double fromY = d1; // Paper - OBFHELPER -+ double d2 = entity.getZ();final double fromZ = d2; // Paper - OBFHELPER - double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); final double toX = d3; // Paper - OBFHELPER - double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); final double toY = d4; // Paper - OBFHELPER - double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); final double toZ = d5; // Paper - OBFHELPER -@@ -484,7 +484,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - double d7 = d4 - this.vehicleFirstGoodY; - double d8 = d5 - this.vehicleFirstGoodZ; - double d9 = entity.getDeltaMovement().lengthSqr(); -- double d10 = d6 * d6 + d7 * d7 + d8 * d8; -+ // Paper start - fix large move vectors killing the server -+ double currDeltaX = toX - fromX; -+ double currDeltaY = toY - fromY; -+ double currDeltaZ = toZ - fromZ; -+ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); -+ double otherFieldX = d3 - this.vehicleLastGoodX; -+ double otherFieldY = d4 - this.vehicleLastGoodY - 1.0E-6D; -+ double otherFieldZ = d5 - this.vehicleLastGoodZ; -+ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); -+ // Paper end - fix large move vectors killing the server - - // CraftBukkit start - handle custom speeds and skipped ticks - this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; -@@ -530,9 +539,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); - -- d6 = d3 - this.vehicleLastGoodX; -- d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; -- d8 = d5 - this.vehicleLastGoodZ; -+ d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above -+ d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; // Paper - diff on change, used for checking large move vectors above -+ d8 = d5 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above - boolean flag1 = entity.verticalCollisionBelow; - - if (entity instanceof LivingEntity) { -@@ -1272,7 +1281,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - double d7 = d1 - this.firstGoodY; - double d8 = d2 - this.firstGoodZ; - double d9 = this.player.getDeltaMovement().lengthSqr(); -- double d10 = d6 * d6 + d7 * d7 + d8 * d8; -+ // Paper start - fix large move vectors killing the server -+ double currDeltaX = toX - prevX; -+ double currDeltaY = toY - prevY; -+ double currDeltaZ = toZ - prevZ; -+ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); -+ double otherFieldX = d0 - this.lastGoodX; -+ double otherFieldY = d1 - this.lastGoodY; -+ double otherFieldZ = d2 - this.lastGoodZ; -+ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); -+ // Paper end - fix large move vectors killing the server - - if (this.player.isSleeping()) { - if (d10 > 1.0D) { -@@ -1326,9 +1344,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - AABB axisalignedbb = this.player.getBoundingBox(); - -- d6 = d0 - this.lastGoodX; -- d7 = d1 - this.lastGoodY; -- d8 = d2 - this.lastGoodZ; -+ d6 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above -+ d7 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above -+ d8 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above - boolean flag = d7 > 0.0D; - - if (this.player.onGround() && !packet.isOnGround() && flag) { diff --git a/patches/server/0440-Optimise-getType-calls.patch b/patches/server/0440-Optimise-getType-calls.patch new file mode 100644 index 000000000000..6dd01d7860d0 --- /dev/null +++ b/patches/server/0440-Optimise-getType-calls.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 3 Jun 2020 11:37:13 -0700 +Subject: [PATCH] Optimise getType calls + +Remove the map lookup for converting from Block->Bukkit Material + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockState.java b/src/main/java/net/minecraft/world/level/block/state/BlockState.java +index da878e180c6b94f98dc82c6e8395f63ecc9b2c1e..e33a4ade1dd40ba482e2ca51c3b3ddad284106c3 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockState.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockState.java +@@ -10,6 +10,16 @@ import net.minecraft.world.level.block.state.properties.Property; + public class BlockState extends BlockBehaviour.BlockStateBase { + public static final Codec CODEC = codec(BuiltInRegistries.BLOCK.byNameCodec(), Block::defaultBlockState).stable(); + ++ // Paper start - optimise getType calls ++ org.bukkit.Material cachedMaterial; ++ ++ public final org.bukkit.Material getBukkitMaterial() { ++ if (this.cachedMaterial == null) { ++ this.cachedMaterial = org.bukkit.craftbukkit.block.CraftBlockType.minecraftToBukkit(this.getBlock()); ++ } ++ return this.cachedMaterial; ++ } ++ // Paper end - optimise getType calls + public BlockState(Block block, ImmutableMap, Comparable> propertyMap, MapCodec codec) { + super(block, propertyMap, codec); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +index f2ce97e46cdbda0f8960eed9b601c797d8eaef48..85029f1acfdbb411d9ebdf95838d6db3898f4e58 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +@@ -99,7 +99,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { + public Material getBlockType(int x, int y, int z) { + this.validateChunkCoordinates(x, y, z); + +- return CraftBlockType.minecraftToBukkit(this.blockids[this.getSectionIndex(y)].get(x, y & 0xF, z).getBlock()); ++ return this.blockids[this.getSectionIndex(y)].get(x, y & 0xF, z).getBukkitMaterial(); // Paper - optimise getType calls + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index a586442422a2b2c06b785af0d261d3e19eb1d59b..aa644231425b9622437538b5c092d4064a40cced 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -220,7 +220,7 @@ public class CraftBlock implements Block { + + @Override + public Material getType() { +- return CraftBlockType.minecraftToBukkit(this.world.getBlockState(this.position).getBlock()); ++ return this.world.getBlockState(this.position).getBukkitMaterial(); // Paper - optimise getType calls + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +index 8e5279abccdd074d565f246420b2b12b81189b67..928a301627134b49915b0ceaeabb7dc350605dc2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +@@ -170,7 +170,7 @@ public class CraftBlockState implements BlockState { + + @Override + public Material getType() { +- return CraftBlockType.minecraftToBukkit(this.data.getBlock()); ++ return this.data.getBukkitMaterial(); // Paper - optimise getType calls + } + + public void setFlag(int flag) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +index 587396f5ed01939cd3ddce10fecf86ba80eb9c73..01a1479711b7f7bb87db275a1edfab5a95464cca 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -61,7 +61,7 @@ public class CraftBlockData implements BlockData { + + @Override + public Material getMaterial() { +- return CraftBlockType.minecraftToBukkit(this.state.getBlock()); ++ return this.state.getBukkitMaterial(); // Paper - optimise getType calls + } + + public net.minecraft.world.level.block.state.BlockState getState() { +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +index c96aaa185d9d929cb19f427be82053f0cfa13bad..0fb580530d0b6d4d63ea4b85fec9240eb5c74df4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +@@ -96,7 +96,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { + + @Override + public Material getType(int x, int y, int z) { +- return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock()); ++ return this.getTypeId(x, y, z).getBukkitMaterial(); // Paper - optimise getType calls + } + + @Override diff --git a/patches/server/0441-Optimise-getType-calls.patch b/patches/server/0441-Optimise-getType-calls.patch deleted file mode 100644 index 3ae56f539f5d..000000000000 --- a/patches/server/0441-Optimise-getType-calls.patch +++ /dev/null @@ -1,93 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 3 Jun 2020 11:37:13 -0700 -Subject: [PATCH] Optimise getType calls - -Remove the map lookup for converting from Block->Bukkit Material - -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockState.java b/src/main/java/net/minecraft/world/level/block/state/BlockState.java -index da878e180c6b94f98dc82c6e8395f63ecc9b2c1e..e33a4ade1dd40ba482e2ca51c3b3ddad284106c3 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockState.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockState.java -@@ -10,6 +10,16 @@ import net.minecraft.world.level.block.state.properties.Property; - public class BlockState extends BlockBehaviour.BlockStateBase { - public static final Codec CODEC = codec(BuiltInRegistries.BLOCK.byNameCodec(), Block::defaultBlockState).stable(); - -+ // Paper start - optimise getType calls -+ org.bukkit.Material cachedMaterial; -+ -+ public final org.bukkit.Material getBukkitMaterial() { -+ if (this.cachedMaterial == null) { -+ this.cachedMaterial = org.bukkit.craftbukkit.block.CraftBlockType.minecraftToBukkit(this.getBlock()); -+ } -+ return this.cachedMaterial; -+ } -+ // Paper end - optimise getType calls - public BlockState(Block block, ImmutableMap, Comparable> propertyMap, MapCodec codec) { - super(block, propertyMap, codec); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -index f2ce97e46cdbda0f8960eed9b601c797d8eaef48..85029f1acfdbb411d9ebdf95838d6db3898f4e58 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -@@ -99,7 +99,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { - public Material getBlockType(int x, int y, int z) { - this.validateChunkCoordinates(x, y, z); - -- return CraftBlockType.minecraftToBukkit(this.blockids[this.getSectionIndex(y)].get(x, y & 0xF, z).getBlock()); -+ return this.blockids[this.getSectionIndex(y)].get(x, y & 0xF, z).getBukkitMaterial(); // Paper - optimise getType calls - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index a586442422a2b2c06b785af0d261d3e19eb1d59b..aa644231425b9622437538b5c092d4064a40cced 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -220,7 +220,7 @@ public class CraftBlock implements Block { - - @Override - public Material getType() { -- return CraftBlockType.minecraftToBukkit(this.world.getBlockState(this.position).getBlock()); -+ return this.world.getBlockState(this.position).getBukkitMaterial(); // Paper - optimise getType calls - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -index 8e5279abccdd074d565f246420b2b12b81189b67..928a301627134b49915b0ceaeabb7dc350605dc2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -@@ -170,7 +170,7 @@ public class CraftBlockState implements BlockState { - - @Override - public Material getType() { -- return CraftBlockType.minecraftToBukkit(this.data.getBlock()); -+ return this.data.getBukkitMaterial(); // Paper - optimise getType calls - } - - public void setFlag(int flag) { -diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -index d95f56356a8d1fc82f548d93038bd81c57f46f9e..a9a69191895ba2f66b133c162d1b31b1dfc73101 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -@@ -61,7 +61,7 @@ public class CraftBlockData implements BlockData { - - @Override - public Material getMaterial() { -- return CraftBlockType.minecraftToBukkit(this.state.getBlock()); -+ return this.state.getBukkitMaterial(); // Paper - optimise getType calls - } - - public net.minecraft.world.level.block.state.BlockState getState() { -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java -index c96aaa185d9d929cb19f427be82053f0cfa13bad..0fb580530d0b6d4d63ea4b85fec9240eb5c74df4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java -@@ -96,7 +96,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { - - @Override - public Material getType(int x, int y, int z) { -- return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock()); -+ return this.getTypeId(x, y, z).getBukkitMaterial(); // Paper - optimise getType calls - } - - @Override diff --git a/patches/server/0442-Villager-resetOffers.patch b/patches/server/0441-Villager-resetOffers.patch similarity index 100% rename from patches/server/0442-Villager-resetOffers.patch rename to patches/server/0441-Villager-resetOffers.patch diff --git a/patches/server/0443-Retain-block-place-order-when-capturing-blockstates.patch b/patches/server/0442-Retain-block-place-order-when-capturing-blockstates.patch similarity index 100% rename from patches/server/0443-Retain-block-place-order-when-capturing-blockstates.patch rename to patches/server/0442-Retain-block-place-order-when-capturing-blockstates.patch diff --git a/patches/server/0444-Reduce-blockpos-allocation-from-pathfinding.patch b/patches/server/0443-Reduce-blockpos-allocation-from-pathfinding.patch similarity index 100% rename from patches/server/0444-Reduce-blockpos-allocation-from-pathfinding.patch rename to patches/server/0443-Reduce-blockpos-allocation-from-pathfinding.patch diff --git a/patches/server/0445-Fix-item-locations-dropped-from-campfires.patch b/patches/server/0444-Fix-item-locations-dropped-from-campfires.patch similarity index 100% rename from patches/server/0445-Fix-item-locations-dropped-from-campfires.patch rename to patches/server/0444-Fix-item-locations-dropped-from-campfires.patch diff --git a/patches/server/0446-Fix-bell-block-entity-memory-leak.patch b/patches/server/0445-Fix-bell-block-entity-memory-leak.patch similarity index 100% rename from patches/server/0446-Fix-bell-block-entity-memory-leak.patch rename to patches/server/0445-Fix-bell-block-entity-memory-leak.patch diff --git a/patches/server/0447-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch b/patches/server/0446-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch similarity index 100% rename from patches/server/0447-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch rename to patches/server/0446-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch diff --git a/patches/server/0447-Add-getOfflinePlayerIfCached-String.patch b/patches/server/0447-Add-getOfflinePlayerIfCached-String.patch new file mode 100644 index 000000000000..d72a0cdabeee --- /dev/null +++ b/patches/server/0447-Add-getOfflinePlayerIfCached-String.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: oxygencraft <21054297+oxygencraft@users.noreply.github.com> +Date: Sun, 25 Oct 2020 18:34:50 +1100 +Subject: [PATCH] Add getOfflinePlayerIfCached(String) + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index c05fd6a179e8d142b3f5a8977ae7afab8c609a4e..c353f7a3a9ad0099ef7330dde988d1a174a0e327 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1921,6 +1921,28 @@ public final class CraftServer implements Server { + return result; + } + ++ // Paper start ++ @Override ++ @Nullable ++ public OfflinePlayer getOfflinePlayerIfCached(String name) { ++ Preconditions.checkArgument(name != null, "Name cannot be null"); ++ Preconditions.checkArgument(!name.isEmpty(), "Name cannot be empty"); ++ ++ OfflinePlayer result = getPlayerExact(name); ++ if (result == null) { ++ GameProfile profile = console.getProfileCache().getProfileIfCached(name); ++ ++ if (profile != null) { ++ result = getOfflinePlayer(profile); ++ } ++ } else { ++ offlinePlayers.remove(result.getUniqueId()); ++ } ++ ++ return result; ++ } ++ // Paper end ++ + @Override + public OfflinePlayer getOfflinePlayer(UUID id) { + Preconditions.checkArgument(id != null, "UUID id cannot be null"); diff --git a/patches/server/0448-Add-getOfflinePlayerIfCached-String.patch b/patches/server/0448-Add-getOfflinePlayerIfCached-String.patch deleted file mode 100644 index 4818495f9856..000000000000 --- a/patches/server/0448-Add-getOfflinePlayerIfCached-String.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: oxygencraft <21054297+oxygencraft@users.noreply.github.com> -Date: Sun, 25 Oct 2020 18:34:50 +1100 -Subject: [PATCH] Add getOfflinePlayerIfCached(String) - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 936928dda8b09386ef936cec9da65eb76b1ccf1d..811c0f36bc314ca79e062fa149c6d19fc2734087 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1909,6 +1909,28 @@ public final class CraftServer implements Server { - return result; - } - -+ // Paper start -+ @Override -+ @Nullable -+ public OfflinePlayer getOfflinePlayerIfCached(String name) { -+ Preconditions.checkArgument(name != null, "Name cannot be null"); -+ Preconditions.checkArgument(!name.isEmpty(), "Name cannot be empty"); -+ -+ OfflinePlayer result = getPlayerExact(name); -+ if (result == null) { -+ GameProfile profile = console.getProfileCache().getProfileIfCached(name); -+ -+ if (profile != null) { -+ result = getOfflinePlayer(profile); -+ } -+ } else { -+ offlinePlayers.remove(result.getUniqueId()); -+ } -+ -+ return result; -+ } -+ // Paper end -+ - @Override - public OfflinePlayer getOfflinePlayer(UUID id) { - Preconditions.checkArgument(id != null, "UUID id cannot be null"); diff --git a/patches/server/0449-Add-ignore-discounts-API.patch b/patches/server/0448-Add-ignore-discounts-API.patch similarity index 100% rename from patches/server/0449-Add-ignore-discounts-API.patch rename to patches/server/0448-Add-ignore-discounts-API.patch diff --git a/patches/server/0449-Toggle-for-removing-existing-dragon.patch b/patches/server/0449-Toggle-for-removing-existing-dragon.patch new file mode 100644 index 000000000000..7f400cb1aa55 --- /dev/null +++ b/patches/server/0449-Toggle-for-removing-existing-dragon.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Wed, 30 Sep 2020 22:49:14 +0200 +Subject: [PATCH] Toggle for removing existing dragon + + +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 3953bbfdaf3e93468108d194c215e6242e14f067..46f286a68d04aced44acbb97041a74e2668c13d8 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 +@@ -209,7 +209,7 @@ public class EndDragonFight { + this.dragonUUID = entityenderdragon.getUUID(); + EndDragonFight.LOGGER.info("Found that there's a dragon still alive ({})", entityenderdragon); + this.dragonKilled = false; +- if (!flag) { ++ if (!flag && this.level.paperConfig().entities.behavior.shouldRemoveDragon) { // Paper - Toggle for removing existing dragon + EndDragonFight.LOGGER.info("But we didn't have a portal, let's remove it."); + entityenderdragon.discard(); + this.dragonUUID = null; diff --git a/patches/server/0450-Fix-client-lag-on-advancement-loading.patch b/patches/server/0450-Fix-client-lag-on-advancement-loading.patch new file mode 100644 index 000000000000..4882b3fd231d --- /dev/null +++ b/patches/server/0450-Fix-client-lag-on-advancement-loading.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Sat, 31 Oct 2020 11:49:01 -0700 +Subject: [PATCH] Fix client lag on advancement loading + +When new advancements are added via the UnsafeValues#loadAdvancement +API, it triggers a full datapack reload when this is not necessary. The +advancement is already loaded directly into the advancement registry, +and the point of saving the advancement to the Bukkit datapack seems to +be for persistence. By removing the call to reload datapacks when an +advancement is loaded, the client no longer completely freezes up when +adding a new advancement. +To ensure the client still receives the updated advancement data, we +manually reload the advancement data for all players, which +normally takes place as a part of the datapack reloading. + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 2bc3d9c02256269845d140764b7b1b201e38b569..14d094d9af9b76277859901db908b8a36b24986b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -334,7 +334,13 @@ public final class CraftMagicNumbers implements UnsafeValues { + Bukkit.getLogger().log(Level.SEVERE, "Error saving advancement " + key, ex); + } + +- MinecraftServer.getServer().getPlayerList().reloadResources(); ++ // Paper start - Fix client lag on advancement loading ++ //MinecraftServer.getServer().getPlayerList().reload(); ++ MinecraftServer.getServer().getPlayerList().getPlayers().forEach(player -> { ++ player.getAdvancements().reload(MinecraftServer.getServer().getAdvancements()); ++ player.getAdvancements().flushDirty(player); ++ }); ++ // Paper end - Fix client lag on advancement loading + + return bukkit; + } diff --git a/patches/server/0450-Toggle-for-removing-existing-dragon.patch b/patches/server/0450-Toggle-for-removing-existing-dragon.patch deleted file mode 100644 index 3ee5c38ad843..000000000000 --- a/patches/server/0450-Toggle-for-removing-existing-dragon.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Wed, 30 Sep 2020 22:49:14 +0200 -Subject: [PATCH] Toggle for removing existing dragon - - -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 43f9635cfb3b1f57774ec0e33e0909d08a01db91..65fb65a9a6037b007c3659f1d1a32ef31097824a 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 -@@ -209,7 +209,7 @@ public class EndDragonFight { - this.dragonUUID = entityenderdragon.getUUID(); - EndDragonFight.LOGGER.info("Found that there's a dragon still alive ({})", entityenderdragon); - this.dragonKilled = false; -- if (!flag) { -+ if (!flag && this.level.paperConfig().entities.behavior.shouldRemoveDragon) { // Paper - Toggle for removing existing dragon - EndDragonFight.LOGGER.info("But we didn't have a portal, let's remove it."); - entityenderdragon.discard(); - this.dragonUUID = null; diff --git a/patches/server/0451-Fix-client-lag-on-advancement-loading.patch b/patches/server/0451-Fix-client-lag-on-advancement-loading.patch deleted file mode 100644 index a7c34bcee82a..000000000000 --- a/patches/server/0451-Fix-client-lag-on-advancement-loading.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sat, 31 Oct 2020 11:49:01 -0700 -Subject: [PATCH] Fix client lag on advancement loading - -When new advancements are added via the UnsafeValues#loadAdvancement -API, it triggers a full datapack reload when this is not necessary. The -advancement is already loaded directly into the advancement registry, -and the point of saving the advancement to the Bukkit datapack seems to -be for persistence. By removing the call to reload datapacks when an -advancement is loaded, the client no longer completely freezes up when -adding a new advancement. -To ensure the client still receives the updated advancement data, we -manually reload the advancement data for all players, which -normally takes place as a part of the datapack reloading. - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 88deea72f7456c178f54f6a245c84a64ffab6926..f48702923beb6d99b23dfa2466396bf4d4e8fbc8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -329,7 +329,13 @@ public final class CraftMagicNumbers implements UnsafeValues { - Bukkit.getLogger().log(Level.SEVERE, "Error saving advancement " + key, ex); - } - -- MinecraftServer.getServer().getPlayerList().reloadResources(); -+ // Paper start - Fix client lag on advancement loading -+ //MinecraftServer.getServer().getPlayerList().reload(); -+ MinecraftServer.getServer().getPlayerList().getPlayers().forEach(player -> { -+ player.getAdvancements().reload(MinecraftServer.getServer().getAdvancements()); -+ player.getAdvancements().flushDirty(player); -+ }); -+ // Paper end - Fix client lag on advancement loading - - return bukkit; - } diff --git a/patches/server/0452-Item-no-age-no-player-pickup.patch b/patches/server/0451-Item-no-age-no-player-pickup.patch similarity index 100% rename from patches/server/0452-Item-no-age-no-player-pickup.patch rename to patches/server/0451-Item-no-age-no-player-pickup.patch diff --git a/patches/server/0452-Beacon-API-custom-effect-ranges.patch b/patches/server/0452-Beacon-API-custom-effect-ranges.patch new file mode 100644 index 000000000000..88e2a56086a0 --- /dev/null +++ b/patches/server/0452-Beacon-API-custom-effect-ranges.patch @@ -0,0 +1,131 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 24 Jun 2020 12:39:08 -0600 +Subject: [PATCH] Beacon API - custom effect ranges + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index a1097950766ad31393340b423ea3f98a1f555368..49f25826aea528d9da085b9e65cb4c85cd78c415 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -82,6 +82,26 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + return (BeaconBlockEntity.hasSecondaryEffect(this.levels, this.primaryPower, this.secondaryPower)) ? CraftPotionUtil.toBukkit(new MobEffectInstance(this.secondaryPower, BeaconBlockEntity.getLevel(this.levels), BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower), true, true)) : null; + } + // CraftBukkit end ++ // Paper start - Custom beacon ranges ++ private final String PAPER_RANGE_TAG = "Paper.Range"; ++ private double effectRange = -1; ++ ++ public double getEffectRange() { ++ if (this.effectRange < 0) { ++ return this.levels * 10 + 10; ++ } else { ++ return effectRange; ++ } ++ } ++ ++ public void setEffectRange(double range) { ++ this.effectRange = range; ++ } ++ ++ public void resetEffectRange() { ++ this.effectRange = -1; ++ } ++ // Paper end - Custom beacon ranges + + @Nullable + static MobEffect filterEffect(@Nullable MobEffect effect) { +@@ -197,7 +217,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + } + + if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) { +- BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower); ++ BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges + BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); + } + } +@@ -283,8 +303,13 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + } + + public static List getHumansInRange(Level world, BlockPos blockposition, int i) { ++ // Paper start - Custom beacon ranges ++ return BeaconBlockEntity.getHumansInRange(world, blockposition, i, null); ++ } ++ public static List getHumansInRange(Level world, BlockPos blockposition, int i, @Nullable BeaconBlockEntity blockEntity) { ++ // Paper end - Custom beacon ranges + { +- double d0 = (double) (i * 10 + 10); ++ double d0 = blockEntity != null ? blockEntity.getEffectRange() : (i * 10 + 10); // Paper - Custom beacon ranges + + AABB axisalignedbb = (new AABB(blockposition)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D); + List list = world.getEntitiesOfClass(Player.class, axisalignedbb); +@@ -325,12 +350,17 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + } + + private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable MobEffect primaryEffect, @Nullable MobEffect secondaryEffect) { ++ // Paper start - Custom beacon ranges ++ BeaconBlockEntity.applyEffects(world, pos, beaconLevel, primaryEffect, secondaryEffect, null); ++ } ++ private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable MobEffect primaryEffect, @Nullable MobEffect secondaryEffect, @Nullable BeaconBlockEntity blockEntity) { ++ // Paper end - Custom beacon ranges + if (!world.isClientSide && primaryEffect != null) { + double d0 = (double) (beaconLevel * 10 + 10); + byte b0 = BeaconBlockEntity.getAmplification(beaconLevel, primaryEffect, secondaryEffect); + + int j = BeaconBlockEntity.getLevel(beaconLevel); +- List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel); ++ List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel, blockEntity); // Paper - Custom beacon ranges + + BeaconBlockEntity.applyEffect(list, primaryEffect, j, b0, true, pos); // Paper - BeaconEffectEvent + +@@ -393,6 +423,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + } + + this.lockKey = LockCode.fromTag(nbt); ++ this.effectRange = nbt.contains(PAPER_RANGE_TAG, 6) ? nbt.getDouble(PAPER_RANGE_TAG) : -1; // Paper - Custom beacon ranges + } + + @Override +@@ -406,6 +437,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + } + + this.lockKey.addToTag(nbt); ++ nbt.putDouble(PAPER_RANGE_TAG, this.effectRange); // Paper - Custom beacon ranges + } + + public void setCustomName(@Nullable Component customName) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +index c4890927419e27fd35e4e373fb09dcb182234fbf..2d77ee30f87491bd413d11687d9fd6def843104c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +@@ -32,7 +32,7 @@ public class CraftBeacon extends CraftBlockEntityState implem + if (tileEntity instanceof BeaconBlockEntity) { + BeaconBlockEntity beacon = (BeaconBlockEntity) tileEntity; + +- Collection nms = BeaconBlockEntity.getHumansInRange(beacon.getLevel(), beacon.getBlockPos(), beacon.levels); ++ Collection nms = BeaconBlockEntity.getHumansInRange(beacon.getLevel(), beacon.getBlockPos(), beacon.levels, beacon); // Paper - Custom beacon ranges + Collection bukkit = new ArrayList(nms.size()); + + for (Player human : nms) { +@@ -114,4 +114,21 @@ public class CraftBeacon extends CraftBlockEntityState implem + public CraftBeacon copy() { + return new CraftBeacon(this); + } ++ ++ // Paper start ++ @Override ++ public double getEffectRange() { ++ return this.getSnapshot().getEffectRange(); ++ } ++ ++ @Override ++ public void setEffectRange(double range) { ++ this.getSnapshot().setEffectRange(range); ++ } ++ ++ @Override ++ public void resetEffectRange() { ++ this.getSnapshot().resetEffectRange(); ++ } ++ // Paper end + } diff --git a/patches/server/0453-Add-API-for-quit-reason.patch b/patches/server/0453-Add-API-for-quit-reason.patch new file mode 100644 index 000000000000..d179e4055230 --- /dev/null +++ b/patches/server/0453-Add-API-for-quit-reason.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 14 Nov 2020 16:19:52 +0100 +Subject: [PATCH] Add API for quit reason + + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 1113380f6f142d2faf36191aae158c747bed5bb9..ddc84de84c8a503a01e40c42fe83558af7159f8f 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -179,12 +179,15 @@ public class Connection extends SimpleChannelInboundHandler> { + + this.handlingFault = true; + if (this.channel.isOpen()) { ++ net.minecraft.server.level.ServerPlayer player = this.getPlayer(); // Paper - Add API for quit reason + if (throwable instanceof TimeoutException) { + Connection.LOGGER.debug("Timeout", throwable); ++ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.TIMED_OUT; // Paper - Add API for quit reason + this.disconnect(Component.translatable("disconnect.timeout")); + } else { + MutableComponent ichatmutablecomponent = Component.translatable("disconnect.genericReason", "Internal Exception: " + throwable); + ++ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.ERRONEOUS_STATE; // Paper - Add API for quit reason + if (flag) { + Connection.LOGGER.debug("Failed to sent packet", throwable); + if (this.getSending() == PacketFlow.CLIENTBOUND) { +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 587cce4d3878bc5cba5f4f4e58eacce2f656e242..6e62c5037a69468e7b4c8115a6623d48538de307 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -274,6 +274,7 @@ public class ServerPlayer extends Player { + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper + public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent + public @Nullable String clientBrandName = null; // Paper - Brand support ++ public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event + + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { + super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); +diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index 6597e6e9987ddb5906909c22704fdfb6557aee8e..6bb846d3ee2fb54ab3ffa116607f2a83e538460e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -315,6 +315,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + final Component ichatbasecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()); // Paper - Adventure + // CraftBukkit end + ++ this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason + this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), PacketSendListener.thenRun(() -> { + this.connection.disconnect(ichatbasecomponent); + })); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 4959c1cc9f1ceead9da42e6d12903d13882a1c17..5777bb6bd01d01c0ff333d7a593744b6e62ddd58 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -577,7 +577,7 @@ public abstract class PlayerList { + entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason + } + +- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), 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()))); // Paper - Adventure ++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), 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())), entityplayer.quitReason); // Paper - Adventure & Add API for quit reason + this.cserver.getPluginManager().callEvent(playerQuitEvent); + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + diff --git a/patches/server/0453-Beacon-API-custom-effect-ranges.patch b/patches/server/0453-Beacon-API-custom-effect-ranges.patch deleted file mode 100644 index 4a1999c4d4a9..000000000000 --- a/patches/server/0453-Beacon-API-custom-effect-ranges.patch +++ /dev/null @@ -1,131 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 24 Jun 2020 12:39:08 -0600 -Subject: [PATCH] Beacon API - custom effect ranges - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index f3b9a692417dbfc67b939a753c29ad1c159e274b..80bd394920902cd67ef22bf9a14db64800a3ab97 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -82,6 +82,26 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - return (BeaconBlockEntity.hasSecondaryEffect(this.levels, this.primaryPower, this.secondaryPower)) ? CraftPotionUtil.toBukkit(new MobEffectInstance(this.secondaryPower, BeaconBlockEntity.getLevel(this.levels), BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower), true, true)) : null; - } - // CraftBukkit end -+ // Paper start - Custom beacon ranges -+ private final String PAPER_RANGE_TAG = "Paper.Range"; -+ private double effectRange = -1; -+ -+ public double getEffectRange() { -+ if (this.effectRange < 0) { -+ return this.levels * 10 + 10; -+ } else { -+ return effectRange; -+ } -+ } -+ -+ public void setEffectRange(double range) { -+ this.effectRange = range; -+ } -+ -+ public void resetEffectRange() { -+ this.effectRange = -1; -+ } -+ // Paper end - Custom beacon ranges - - @Nullable - static MobEffect filterEffect(@Nullable MobEffect effect) { -@@ -197,7 +217,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - } - - if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) { -- BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower); -+ BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges - BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); - } - } -@@ -283,8 +303,13 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - } - - public static List getHumansInRange(Level world, BlockPos blockposition, int i) { -+ // Paper start - Custom beacon ranges -+ return BeaconBlockEntity.getHumansInRange(world, blockposition, i, null); -+ } -+ public static List getHumansInRange(Level world, BlockPos blockposition, int i, @Nullable BeaconBlockEntity blockEntity) { -+ // Paper end - Custom beacon ranges - { -- double d0 = (double) (i * 10 + 10); -+ double d0 = blockEntity != null ? blockEntity.getEffectRange() : (i * 10 + 10); // Paper - Custom beacon ranges - - AABB axisalignedbb = (new AABB(blockposition)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D); - List list = world.getEntitiesOfClass(Player.class, axisalignedbb); -@@ -325,12 +350,17 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - } - - private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable MobEffect primaryEffect, @Nullable MobEffect secondaryEffect) { -+ // Paper start - Custom beacon ranges -+ BeaconBlockEntity.applyEffects(world, pos, beaconLevel, primaryEffect, secondaryEffect, null); -+ } -+ private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable MobEffect primaryEffect, @Nullable MobEffect secondaryEffect, @Nullable BeaconBlockEntity blockEntity) { -+ // Paper end - Custom beacon ranges - if (!world.isClientSide && primaryEffect != null) { - double d0 = (double) (beaconLevel * 10 + 10); - byte b0 = BeaconBlockEntity.getAmplification(beaconLevel, primaryEffect, secondaryEffect); - - int j = BeaconBlockEntity.getLevel(beaconLevel); -- List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel); -+ List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel, blockEntity); // Paper - Custom beacon ranges - - BeaconBlockEntity.applyEffect(list, primaryEffect, j, b0, true, pos); // Paper - BeaconEffectEvent - -@@ -393,6 +423,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - } - - this.lockKey = LockCode.fromTag(nbt); -+ this.effectRange = nbt.contains(PAPER_RANGE_TAG, 6) ? nbt.getDouble(PAPER_RANGE_TAG) : -1; // Paper - Custom beacon ranges - } - - @Override -@@ -406,6 +437,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - } - - this.lockKey.addToTag(nbt); -+ nbt.putDouble(PAPER_RANGE_TAG, this.effectRange); // Paper - Custom beacon ranges - } - - public void setCustomName(@Nullable Component customName) { -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java -index c4890927419e27fd35e4e373fb09dcb182234fbf..2d77ee30f87491bd413d11687d9fd6def843104c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java -@@ -32,7 +32,7 @@ public class CraftBeacon extends CraftBlockEntityState implem - if (tileEntity instanceof BeaconBlockEntity) { - BeaconBlockEntity beacon = (BeaconBlockEntity) tileEntity; - -- Collection nms = BeaconBlockEntity.getHumansInRange(beacon.getLevel(), beacon.getBlockPos(), beacon.levels); -+ Collection nms = BeaconBlockEntity.getHumansInRange(beacon.getLevel(), beacon.getBlockPos(), beacon.levels, beacon); // Paper - Custom beacon ranges - Collection bukkit = new ArrayList(nms.size()); - - for (Player human : nms) { -@@ -114,4 +114,21 @@ public class CraftBeacon extends CraftBlockEntityState implem - public CraftBeacon copy() { - return new CraftBeacon(this); - } -+ -+ // Paper start -+ @Override -+ public double getEffectRange() { -+ return this.getSnapshot().getEffectRange(); -+ } -+ -+ @Override -+ public void setEffectRange(double range) { -+ this.getSnapshot().setEffectRange(range); -+ } -+ -+ @Override -+ public void resetEffectRange() { -+ this.getSnapshot().resetEffectRange(); -+ } -+ // Paper end - } diff --git a/patches/server/0454-Add-API-for-quit-reason.patch b/patches/server/0454-Add-API-for-quit-reason.patch deleted file mode 100644 index 8986435ffbe7..000000000000 --- a/patches/server/0454-Add-API-for-quit-reason.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Sat, 14 Nov 2020 16:19:52 +0100 -Subject: [PATCH] Add API for quit reason - - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 777681a58417684a35a875c869ab22e50bb27da5..1e495daf53a53260e1a3b1c86365edb9abad1e80 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -179,12 +179,15 @@ public class Connection extends SimpleChannelInboundHandler> { - - this.handlingFault = true; - if (this.channel.isOpen()) { -+ net.minecraft.server.level.ServerPlayer player = this.getPlayer(); // Paper - Add API for quit reason - if (throwable instanceof TimeoutException) { - Connection.LOGGER.debug("Timeout", throwable); -+ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.TIMED_OUT; // Paper - Add API for quit reason - this.disconnect(Component.translatable("disconnect.timeout")); - } else { - MutableComponent ichatmutablecomponent = Component.translatable("disconnect.genericReason", "Internal Exception: " + throwable); - -+ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.ERRONEOUS_STATE; // Paper - Add API for quit reason - if (flag) { - Connection.LOGGER.debug("Failed to sent packet", throwable); - if (this.getSending() == PacketFlow.CLIENTBOUND) { -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index dc41eb243510fdb1de9ca3a0a8cb871af5272876..effafbcd8400cc40956d9cf36757e83f7f803038 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -274,6 +274,7 @@ public class ServerPlayer extends Player { - public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper - public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent - public @Nullable String clientBrandName = null; // Paper - Brand support -+ public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event - - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { - super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 6597e6e9987ddb5906909c22704fdfb6557aee8e..6bb846d3ee2fb54ab3ffa116607f2a83e538460e 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -315,6 +315,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - final Component ichatbasecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()); // Paper - Adventure - // CraftBukkit end - -+ this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason - this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), PacketSendListener.thenRun(() -> { - this.connection.disconnect(ichatbasecomponent); - })); -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 4959c1cc9f1ceead9da42e6d12903d13882a1c17..5777bb6bd01d01c0ff333d7a593744b6e62ddd58 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -577,7 +577,7 @@ public abstract class PlayerList { - entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason - } - -- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), 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()))); // Paper - Adventure -+ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), 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())), entityplayer.quitReason); // Paper - Adventure & Add API for quit reason - this.cserver.getPluginManager().callEvent(playerQuitEvent); - entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); - diff --git a/patches/server/0455-Add-Wandering-Trader-spawn-rate-config-options.patch b/patches/server/0454-Add-Wandering-Trader-spawn-rate-config-options.patch similarity index 100% rename from patches/server/0455-Add-Wandering-Trader-spawn-rate-config-options.patch rename to patches/server/0454-Add-Wandering-Trader-spawn-rate-config-options.patch diff --git a/patches/server/0456-Expose-world-spawn-angle.patch b/patches/server/0455-Expose-world-spawn-angle.patch similarity index 100% rename from patches/server/0456-Expose-world-spawn-angle.patch rename to patches/server/0455-Expose-world-spawn-angle.patch diff --git a/patches/server/0457-Add-Destroy-Speed-API.patch b/patches/server/0456-Add-Destroy-Speed-API.patch similarity index 100% rename from patches/server/0457-Add-Destroy-Speed-API.patch rename to patches/server/0456-Add-Destroy-Speed-API.patch diff --git a/patches/server/0457-Fix-Player-spawnParticle-x-y-z-precision-loss.patch b/patches/server/0457-Fix-Player-spawnParticle-x-y-z-precision-loss.patch new file mode 100644 index 000000000000..80a1eb67cbb2 --- /dev/null +++ b/patches/server/0457-Fix-Player-spawnParticle-x-y-z-precision-loss.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Esophose +Date: Sat, 3 Oct 2020 18:57:47 -0600 +Subject: [PATCH] Fix Player spawnParticle x/y/z precision loss + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 6e9fc623f5a56753e2c78c3ff63c6f4de7cbcccb..ec4ebbdde676806c4b2348408a8004dd66cbc44b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2619,7 +2619,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + if (data != null) { + Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType()); + } +- ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(CraftParticle.createParticleParam(particle, data), true, (float) x, (float) y, (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); ++ ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(CraftParticle.createParticleParam(particle, data), true, x, y, z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); // Paper - Fix x/y/z coordinate precision loss + this.getHandle().connection.send(packetplayoutworldparticles); + + } diff --git a/patches/server/0458-Add-LivingEntity-clearActiveItem.patch b/patches/server/0458-Add-LivingEntity-clearActiveItem.patch new file mode 100644 index 000000000000..642aedd9b632 --- /dev/null +++ b/patches/server/0458-Add-LivingEntity-clearActiveItem.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Anrza +Date: Wed, 15 Jul 2020 12:08:49 +0200 +Subject: [PATCH] Add LivingEntity#clearActiveItem + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index bf581842476b8f554987b452c291a55a1dfc92c5..e71f71988790af1a09f65d73dd081490002b821c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -916,6 +916,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return getHandle().getUseItem().asBukkitMirror(); + } + ++ // Paper start ++ @Override ++ public void clearActiveItem() { ++ getHandle().stopUsingItem(); ++ } ++ // Paper end ++ + @Override + public int getItemUseRemainingTime() { + return getHandle().getUseItemRemainingTicks(); diff --git a/patches/server/0458-Fix-Player-spawnParticle-x-y-z-precision-loss.patch b/patches/server/0458-Fix-Player-spawnParticle-x-y-z-precision-loss.patch deleted file mode 100644 index 5219e4f2197c..000000000000 --- a/patches/server/0458-Fix-Player-spawnParticle-x-y-z-precision-loss.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Esophose -Date: Sat, 3 Oct 2020 18:57:47 -0600 -Subject: [PATCH] Fix Player spawnParticle x/y/z precision loss - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 8a289cd0876a8c063a2b5f75ce8eb41f4be98acf..45288a75ddbbf0bb03bacce7a88c59e740914dbe 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2568,7 +2568,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - if (data != null) { - Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType()); - } -- ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(CraftParticle.createParticleParam(particle, data), true, (float) x, (float) y, (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); -+ ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(CraftParticle.createParticleParam(particle, data), true, x, y, z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); // Paper - Fix x/y/z coordinate precision loss - this.getHandle().connection.send(packetplayoutworldparticles); - - } diff --git a/patches/server/0459-Add-LivingEntity-clearActiveItem.patch b/patches/server/0459-Add-LivingEntity-clearActiveItem.patch deleted file mode 100644 index 8cd8b28d5250..000000000000 --- a/patches/server/0459-Add-LivingEntity-clearActiveItem.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Anrza -Date: Wed, 15 Jul 2020 12:08:49 +0200 -Subject: [PATCH] Add LivingEntity#clearActiveItem - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 2550546b200f331ef83b20bf5e119a003cadacba..88b1887c0d4d01ecedb1b622ee718993b4d92579 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -903,6 +903,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return getHandle().getUseItem().asBukkitMirror(); - } - -+ // Paper start -+ @Override -+ public void clearActiveItem() { -+ getHandle().stopUsingItem(); -+ } -+ // Paper end -+ - @Override - public int getItemUseRemainingTime() { - return getHandle().getUseItemRemainingTicks(); diff --git a/patches/server/0460-Add-PlayerItemCooldownEvent.patch b/patches/server/0459-Add-PlayerItemCooldownEvent.patch similarity index 100% rename from patches/server/0460-Add-PlayerItemCooldownEvent.patch rename to patches/server/0459-Add-PlayerItemCooldownEvent.patch diff --git a/patches/server/0461-Significantly-improve-performance-of-the-end-generat.patch b/patches/server/0460-Significantly-improve-performance-of-the-end-generat.patch similarity index 100% rename from patches/server/0461-Significantly-improve-performance-of-the-end-generat.patch rename to patches/server/0460-Significantly-improve-performance-of-the-end-generat.patch diff --git a/patches/server/0462-More-lightning-API.patch b/patches/server/0461-More-lightning-API.patch similarity index 100% rename from patches/server/0462-More-lightning-API.patch rename to patches/server/0461-More-lightning-API.patch diff --git a/patches/server/0462-Climbing-should-not-bypass-cramming-gamerule.patch b/patches/server/0462-Climbing-should-not-bypass-cramming-gamerule.patch new file mode 100644 index 000000000000..851360dd1bbe --- /dev/null +++ b/patches/server/0462-Climbing-should-not-bypass-cramming-gamerule.patch @@ -0,0 +1,156 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 23 Aug 2020 20:59:00 +0200 +Subject: [PATCH] Climbing should not bypass cramming gamerule + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7c436b2a6d9b516469088a6d67f07b6b621f5201..863cca1cb36fd8cd147fe8526b2ebd5294e16236 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1970,6 +1970,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + public boolean isPushable() { ++ // Paper start - Climbing should not bypass cramming gamerule ++ return isCollidable(false); ++ } ++ ++ public boolean isCollidable(boolean ignoreClimbing) { ++ // Paper end - Climbing should not bypass cramming gamerule + return false; + } + +diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +index ee4495b67c46cf1282cdd6ad15b224b0b7b10bfb..e382a29b441b656f35bc24cb90f95cb4def433d2 100644 +--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +@@ -46,11 +46,16 @@ public final class EntitySelector { + } + + public static Predicate pushableBy(Entity entity) { ++ // Paper start - Climbing should not bypass cramming gamerule ++ return pushable(entity, false); ++ } ++ public static Predicate pushable(Entity entity, boolean ignoreClimbing) { ++ // Paper end - Climbing should not bypass cramming gamerule + PlayerTeam scoreboardteam = entity.getTeam(); + Team.CollisionRule scoreboardteambase_enumteampush = scoreboardteam == null ? Team.CollisionRule.ALWAYS : scoreboardteam.getCollisionRule(); + + return (Predicate) (scoreboardteambase_enumteampush == Team.CollisionRule.NEVER ? Predicates.alwaysFalse() : EntitySelector.NO_SPECTATORS.and((entity1) -> { +- if (!entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API ++ if (!entity1.isCollidable(ignoreClimbing) || !entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API // Paper - Climbing should not bypass cramming gamerule + return false; + } else if (entity.level().isClientSide && (!(entity1 instanceof Player) || !((Player) entity1).isLocalPlayer())) { + return false; +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 0e009d34a17b7fbebb8bd815cef9df191cd906a5..ec47dc4cb19e742b033f98706b52619483a8bec0 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3439,7 +3439,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + return; + } + // Paper end - don't run getEntities if we're not going to use its result +- List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this)); ++ List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - Climbing should not bypass cramming gamerule + + if (!list.isEmpty()) { + // Paper - don't run getEntities if we're not going to use its result; moved up +@@ -3629,9 +3629,16 @@ public abstract class LivingEntity extends Entity implements Attackable { + return !this.isRemoved() && this.collides; // CraftBukkit + } + ++ // Paper start - Climbing should not bypass cramming gamerule + @Override + public boolean isPushable() { +- return this.isAlive() && !this.isSpectator() && !this.onClimbable() && this.collides; // CraftBukkit ++ return this.isCollidable(this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule); ++ } ++ ++ @Override ++ public boolean isCollidable(boolean ignoreClimbing) { ++ return this.isAlive() && !this.isSpectator() && (ignoreClimbing || !this.onClimbable()) && this.collides; // CraftBukkit ++ // Paper end - Climbing should not bypass cramming gamerule + } + + // CraftBukkit start - collidable API +diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java +index bc9a710a92662b1f69b0f5b289780fe0a0d5ed32..44fa2d4f90389f5526746bd94a2450c03340bd0b 100644 +--- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java ++++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java +@@ -90,7 +90,7 @@ public class Bat extends AmbientCreature { + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule + return false; + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Parrot.java b/src/main/java/net/minecraft/world/entity/animal/Parrot.java +index fbad0e512b990c3d6885ecf92766ba6fd851cc20..f3f48225c2a1e4bd3d0091d1b4b7e4e150850ed2 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java +@@ -383,8 +383,8 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder { + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule + return true; + } + diff --git a/patches/server/0464-Add-missing-default-perms-for-commands.patch b/patches/server/0463-Add-missing-default-perms-for-commands.patch similarity index 100% rename from patches/server/0464-Add-missing-default-perms-for-commands.patch rename to patches/server/0463-Add-missing-default-perms-for-commands.patch diff --git a/patches/server/0463-Climbing-should-not-bypass-cramming-gamerule.patch b/patches/server/0463-Climbing-should-not-bypass-cramming-gamerule.patch deleted file mode 100644 index ad99ead472f2..000000000000 --- a/patches/server/0463-Climbing-should-not-bypass-cramming-gamerule.patch +++ /dev/null @@ -1,156 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sun, 23 Aug 2020 20:59:00 +0200 -Subject: [PATCH] Climbing should not bypass cramming gamerule - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index f4f5175c3c8c12d390470b7f44ad9d2ca39ff1fc..55395837df277eac393949aa7447d69e57c5d756 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1966,6 +1966,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - public boolean isPushable() { -+ // Paper start - Climbing should not bypass cramming gamerule -+ return isCollidable(false); -+ } -+ -+ public boolean isCollidable(boolean ignoreClimbing) { -+ // Paper end - Climbing should not bypass cramming gamerule - return false; - } - -diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java -index ee4495b67c46cf1282cdd6ad15b224b0b7b10bfb..e382a29b441b656f35bc24cb90f95cb4def433d2 100644 ---- a/src/main/java/net/minecraft/world/entity/EntitySelector.java -+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java -@@ -46,11 +46,16 @@ public final class EntitySelector { - } - - public static Predicate pushableBy(Entity entity) { -+ // Paper start - Climbing should not bypass cramming gamerule -+ return pushable(entity, false); -+ } -+ public static Predicate pushable(Entity entity, boolean ignoreClimbing) { -+ // Paper end - Climbing should not bypass cramming gamerule - PlayerTeam scoreboardteam = entity.getTeam(); - Team.CollisionRule scoreboardteambase_enumteampush = scoreboardteam == null ? Team.CollisionRule.ALWAYS : scoreboardteam.getCollisionRule(); - - return (Predicate) (scoreboardteambase_enumteampush == Team.CollisionRule.NEVER ? Predicates.alwaysFalse() : EntitySelector.NO_SPECTATORS.and((entity1) -> { -- if (!entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API -+ if (!entity1.isCollidable(ignoreClimbing) || !entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API // Paper - Climbing should not bypass cramming gamerule - return false; - } else if (entity.level().isClientSide && (!(entity1 instanceof Player) || !((Player) entity1).isLocalPlayer())) { - return false; -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 115dbe71ff1e9996e8307a389569303a320101f4..95ee76c0e96318866a7b21355c9617c2ca79776e 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3426,7 +3426,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - return; - } - // Paper end - don't run getEntities if we're not going to use its result -- List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this)); -+ List list = this.level().getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - Climbing should not bypass cramming gamerule - - if (!list.isEmpty()) { - // Paper - don't run getEntities if we're not going to use its result; moved up -@@ -3616,9 +3616,16 @@ public abstract class LivingEntity extends Entity implements Attackable { - return !this.isRemoved() && this.collides; // CraftBukkit - } - -+ // Paper start - Climbing should not bypass cramming gamerule - @Override - public boolean isPushable() { -- return this.isAlive() && !this.isSpectator() && !this.onClimbable() && this.collides; // CraftBukkit -+ return this.isCollidable(this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule); -+ } -+ -+ @Override -+ public boolean isCollidable(boolean ignoreClimbing) { -+ return this.isAlive() && !this.isSpectator() && (ignoreClimbing || !this.onClimbable()) && this.collides; // CraftBukkit -+ // Paper end - Climbing should not bypass cramming gamerule - } - - // CraftBukkit start - collidable API -diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java -index bc9a710a92662b1f69b0f5b289780fe0a0d5ed32..44fa2d4f90389f5526746bd94a2450c03340bd0b 100644 ---- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java -+++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java -@@ -90,7 +90,7 @@ public class Bat extends AmbientCreature { - } - - @Override -- public boolean isPushable() { -+ public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule - return false; - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Parrot.java b/src/main/java/net/minecraft/world/entity/animal/Parrot.java -index fbad0e512b990c3d6885ecf92766ba6fd851cc20..f3f48225c2a1e4bd3d0091d1b4b7e4e150850ed2 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java -@@ -383,8 +383,8 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder { - } - - @Override -- public boolean isPushable() { -+ public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule - return true; - } - diff --git a/patches/server/0465-Add-PlayerShearBlockEvent.patch b/patches/server/0464-Add-PlayerShearBlockEvent.patch similarity index 100% rename from patches/server/0465-Add-PlayerShearBlockEvent.patch rename to patches/server/0464-Add-PlayerShearBlockEvent.patch diff --git a/patches/server/0465-Limit-recipe-packets.patch b/patches/server/0465-Limit-recipe-packets.patch new file mode 100644 index 000000000000..1ff22e4f8859 --- /dev/null +++ b/patches/server/0465-Limit-recipe-packets.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 12 Dec 2020 23:45:28 +0000 +Subject: [PATCH] Limit recipe packets + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 5e605b9bdb76ca0c9529e7351432578855cc7fa2..87ff69ffbd1e8fa3e88ce2561b56958c23040a10 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -257,6 +257,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // CraftBukkit start - multithreaded fields + private final AtomicInteger chatSpamTickCount = new AtomicInteger(); + private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits ++ private final java.util.concurrent.atomic.AtomicInteger recipeSpamPackets = new java.util.concurrent.atomic.AtomicInteger(); // Paper - auto recipe limit + // CraftBukkit end + private int dropSpamTickCount; + private double firstGoodX; +@@ -376,6 +377,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // CraftBukkit start + for (int spam; (spam = this.chatSpamTickCount.get()) > 0 && !this.chatSpamTickCount.compareAndSet(spam, spam - 1); ) ; + if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - configurable tab spam limits ++ if (recipeSpamPackets.get() > 0) recipeSpamPackets.getAndDecrement(); // Paper - auto recipe limit + /* Use thread-safe field access instead + if (this.chatSpamTickCount > 0) { + --this.chatSpamTickCount; +@@ -2974,6 +2976,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + @Override + public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) { ++ // Paper start - auto recipe limit ++ if (!org.bukkit.Bukkit.isPrimaryThread()) { ++ if (this.recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { ++ this.server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam"))); ++ return; ++ } ++ } ++ // Paper end - auto recipe limit + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + this.player.resetLastActionTime(); + if (!this.player.isSpectator() && this.player.containerMenu.containerId == packet.getContainerId() && this.player.containerMenu instanceof RecipeBookMenu) { diff --git a/patches/server/0467-Fix-CraftSound-backwards-compatibility.patch b/patches/server/0466-Fix-CraftSound-backwards-compatibility.patch similarity index 100% rename from patches/server/0467-Fix-CraftSound-backwards-compatibility.patch rename to patches/server/0466-Fix-CraftSound-backwards-compatibility.patch diff --git a/patches/server/0466-Limit-recipe-packets.patch b/patches/server/0466-Limit-recipe-packets.patch deleted file mode 100644 index 09ebc50be566..000000000000 --- a/patches/server/0466-Limit-recipe-packets.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sat, 12 Dec 2020 23:45:28 +0000 -Subject: [PATCH] Limit recipe packets - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 43e94e1dcf3e4c6c45cba2c7d75ac938938514b2..8b8a8d6d6473c14f9e0621bee82fb861de1d209f 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -257,6 +257,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // CraftBukkit start - multithreaded fields - private final AtomicInteger chatSpamTickCount = new AtomicInteger(); - private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits -+ private final java.util.concurrent.atomic.AtomicInteger recipeSpamPackets = new java.util.concurrent.atomic.AtomicInteger(); // Paper - auto recipe limit - // CraftBukkit end - private int dropSpamTickCount; - private double firstGoodX; -@@ -376,6 +377,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // CraftBukkit start - for (int spam; (spam = this.chatSpamTickCount.get()) > 0 && !this.chatSpamTickCount.compareAndSet(spam, spam - 1); ) ; - if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - configurable tab spam limits -+ if (recipeSpamPackets.get() > 0) recipeSpamPackets.getAndDecrement(); // Paper - auto recipe limit - /* Use thread-safe field access instead - if (this.chatSpamTickCount > 0) { - --this.chatSpamTickCount; -@@ -2974,6 +2976,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - @Override - public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) { -+ // Paper start - auto recipe limit -+ if (!org.bukkit.Bukkit.isPrimaryThread()) { -+ if (this.recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { -+ this.server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam"))); -+ return; -+ } -+ } -+ // Paper end - auto recipe limit - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - this.player.resetLastActionTime(); - if (!this.player.isSpectator() && this.player.containerMenu.containerId == packet.getContainerId() && this.player.containerMenu instanceof RecipeBookMenu) { diff --git a/patches/server/0468-Player-Chunk-Load-Unload-Events.patch b/patches/server/0467-Player-Chunk-Load-Unload-Events.patch similarity index 100% rename from patches/server/0468-Player-Chunk-Load-Unload-Events.patch rename to patches/server/0467-Player-Chunk-Load-Unload-Events.patch diff --git a/patches/server/0469-Optimize-Dynamic-get-Missing-Keys.patch b/patches/server/0468-Optimize-Dynamic-get-Missing-Keys.patch similarity index 100% rename from patches/server/0469-Optimize-Dynamic-get-Missing-Keys.patch rename to patches/server/0468-Optimize-Dynamic-get-Missing-Keys.patch diff --git a/patches/server/0469-Expose-LivingEntity-hurt-direction.patch b/patches/server/0469-Expose-LivingEntity-hurt-direction.patch new file mode 100644 index 000000000000..1cb5f9dcf741 --- /dev/null +++ b/patches/server/0469-Expose-LivingEntity-hurt-direction.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mark Vainomaa +Date: Sun, 13 Dec 2020 05:32:05 +0200 +Subject: [PATCH] Expose LivingEntity hurt direction + + +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 d00d6cf28212ed72f49953a198caa447aefc138c..203f36776f41c46172b77a195d3702dd6af7409e 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -179,7 +179,7 @@ public abstract class Player extends LivingEntity { + private Optional lastDeathLocation; + @Nullable + public FishingHook fishing; +- protected float hurtDir; ++ public float hurtDir; // Paper - protected -> public + public boolean affectsSpawning = true; // Paper - Affects Spawning API + + // CraftBukkit start +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 93644aefd2e6c97eca2735812b2b7b4bd039cfb5..40f848d117c1a4f4fc2f11861c5f142071d56977 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -126,6 +126,13 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + } + } + ++ // Paper start ++ @Override ++ public void setHurtDirection(float hurtDirection) { ++ this.getHandle().hurtDir = hurtDirection; ++ } ++ // Paper end ++ + @Override + public int getSleepTicks() { + return this.getHandle().sleepCounter; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index e71f71988790af1a09f65d73dd081490002b821c..06f25a9453bcc8f304cc83b599f8a54112a6ed01 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -961,5 +961,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void playPickupItemAnimation(org.bukkit.entity.Item item, int quantity) { + getHandle().take(((CraftItem) item).getHandle(), quantity); + } ++ ++ @Override ++ public float getHurtDirection() { ++ return this.getHandle().getHurtDir(); ++ } ++ ++ @Override ++ public void setHurtDirection(float hurtDirection) { ++ throw new UnsupportedOperationException("Cannot set the hurt direction on a non player"); ++ } + // Paper end + } diff --git a/patches/server/0470-Add-OBSTRUCTED-reason-to-BedEnterResult.patch b/patches/server/0470-Add-OBSTRUCTED-reason-to-BedEnterResult.patch new file mode 100644 index 000000000000..28d1b8ccb1d7 --- /dev/null +++ b/patches/server/0470-Add-OBSTRUCTED-reason-to-BedEnterResult.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 24 Dec 2020 12:43:39 -0800 +Subject: [PATCH] Add OBSTRUCTED reason to BedEnterResult + + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 42702b6196ad816bf1bd5df189cc99c58562da24..40d6e655a09888ee95eb136cb8a6f919a1f74aa6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -301,6 +301,10 @@ public class CraftEventFactory { + return BedEnterResult.TOO_FAR_AWAY; + case NOT_SAFE: + return BedEnterResult.NOT_SAFE; ++ // Paper start ++ case OBSTRUCTED: ++ return BedEnterResult.OBSTRUCTED; ++ // Paper end + default: + return BedEnterResult.OTHER_PROBLEM; + } diff --git a/patches/server/0470-Expose-LivingEntity-hurt-direction.patch b/patches/server/0470-Expose-LivingEntity-hurt-direction.patch deleted file mode 100644 index 2b3ccc9d7cd6..000000000000 --- a/patches/server/0470-Expose-LivingEntity-hurt-direction.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mark Vainomaa -Date: Sun, 13 Dec 2020 05:32:05 +0200 -Subject: [PATCH] Expose LivingEntity hurt direction - - -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 bae09577905084f3e3d845b9cd3eaea9f46899d1..10caa677309c5a8191830c98597468079e784459 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -178,7 +178,7 @@ public abstract class Player extends LivingEntity { - private Optional lastDeathLocation; - @Nullable - public FishingHook fishing; -- protected float hurtDir; -+ public float hurtDir; // Paper - protected -> public - public boolean affectsSpawning = true; // Paper - Affects Spawning API - - // CraftBukkit start -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 93644aefd2e6c97eca2735812b2b7b4bd039cfb5..40f848d117c1a4f4fc2f11861c5f142071d56977 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -126,6 +126,13 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - } - } - -+ // Paper start -+ @Override -+ public void setHurtDirection(float hurtDirection) { -+ this.getHandle().hurtDir = hurtDirection; -+ } -+ // Paper end -+ - @Override - public int getSleepTicks() { - return this.getHandle().sleepCounter; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 88b1887c0d4d01ecedb1b622ee718993b4d92579..30934acb059016a996b2c3b2f635e606d4e8a526 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -948,5 +948,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - public void playPickupItemAnimation(org.bukkit.entity.Item item, int quantity) { - getHandle().take(((CraftItem) item).getHandle(), quantity); - } -+ -+ @Override -+ public float getHurtDirection() { -+ return this.getHandle().getHurtDir(); -+ } -+ -+ @Override -+ public void setHurtDirection(float hurtDirection) { -+ throw new UnsupportedOperationException("Cannot set the hurt direction on a non player"); -+ } - // Paper end - } diff --git a/patches/server/0471-Add-OBSTRUCTED-reason-to-BedEnterResult.patch b/patches/server/0471-Add-OBSTRUCTED-reason-to-BedEnterResult.patch deleted file mode 100644 index 4531ba2a86bf..000000000000 --- a/patches/server/0471-Add-OBSTRUCTED-reason-to-BedEnterResult.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 24 Dec 2020 12:43:39 -0800 -Subject: [PATCH] Add OBSTRUCTED reason to BedEnterResult - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 915f49bbfca87682a3ae497dfcd335268f974efe..23fb49657e07c99e10d238420ecb177ca515a2af 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -301,6 +301,10 @@ public class CraftEventFactory { - return BedEnterResult.TOO_FAR_AWAY; - case NOT_SAFE: - return BedEnterResult.NOT_SAFE; -+ // Paper start -+ case OBSTRUCTED: -+ return BedEnterResult.OBSTRUCTED; -+ // Paper end - default: - return BedEnterResult.OTHER_PROBLEM; - } diff --git a/patches/server/0472-Fix-crash-from-invalid-ingredient-lists-in-VillagerA.patch b/patches/server/0471-Fix-crash-from-invalid-ingredient-lists-in-VillagerA.patch similarity index 100% rename from patches/server/0472-Fix-crash-from-invalid-ingredient-lists-in-VillagerA.patch rename to patches/server/0471-Fix-crash-from-invalid-ingredient-lists-in-VillagerA.patch diff --git a/patches/server/0473-Add-TargetHitEvent.patch b/patches/server/0472-Add-TargetHitEvent.patch similarity index 100% rename from patches/server/0473-Add-TargetHitEvent.patch rename to patches/server/0472-Add-TargetHitEvent.patch diff --git a/patches/server/0473-MC-4-Fix-item-position-desync.patch b/patches/server/0473-MC-4-Fix-item-position-desync.patch new file mode 100644 index 000000000000..f4df6d1b5826 --- /dev/null +++ b/patches/server/0473-MC-4-Fix-item-position-desync.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 8 Dec 2020 20:24:52 -0600 +Subject: [PATCH] MC-4: Fix item position desync + +This fixes item position desync (MC-4) by running the item coordinates +through the encode/decode methods of the packet that causes the precision +loss, which forces the server to lose the same precision as the client +keeping them in sync. + +diff --git a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java +index 05ac41e136da43284fb24a6b698ebd36318278fb..3c4ac79c094dc2fff7de94150a34b7bf814ac0de 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java ++++ b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java +@@ -9,12 +9,12 @@ public class VecDeltaCodec { + + @VisibleForTesting + static long encode(double value) { +- return Math.round(value * 4096.0D); ++ return Math.round(value * 4096.0D); // Paper - Fix MC-4; diff on change + } + + @VisibleForTesting + static double decode(long value) { +- return (double)value / 4096.0D; ++ return (double)value / 4096.0D; // Paper - Fix MC-4; diff on change + } + + public Vec3 decode(long x, long y, long z) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 863cca1cb36fd8cd147fe8526b2ebd5294e16236..7b4ddf822a8948a233d831946df97cc84692ba65 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -4170,6 +4170,16 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + return; + } + // Paper end - Block invalid positions and bounding box ++ // Paper start - Fix MC-4 ++ if (this instanceof ItemEntity) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.fixEntityPositionDesync) { ++ // encode/decode from ClientboundMoveEntityPacket ++ x = Mth.lfloor(x * 4096.0D) * (1 / 4096.0D); ++ y = Mth.lfloor(y * 4096.0D) * (1 / 4096.0D); ++ z = Mth.lfloor(z * 4096.0D) * (1 / 4096.0D); ++ } ++ } ++ // Paper end - Fix MC-4 + if (this.position.x != x || this.position.y != y || this.position.z != z) { + this.position = new Vec3(x, y, z); + int i = Mth.floor(x); diff --git a/patches/server/0475-Additional-Block-Material-API.patch b/patches/server/0474-Additional-Block-Material-API.patch similarity index 100% rename from patches/server/0475-Additional-Block-Material-API.patch rename to patches/server/0474-Additional-Block-Material-API.patch diff --git a/patches/server/0474-MC-4-Fix-item-position-desync.patch b/patches/server/0474-MC-4-Fix-item-position-desync.patch deleted file mode 100644 index 244dd4bf2cb7..000000000000 --- a/patches/server/0474-MC-4-Fix-item-position-desync.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Tue, 8 Dec 2020 20:24:52 -0600 -Subject: [PATCH] MC-4: Fix item position desync - -This fixes item position desync (MC-4) by running the item coordinates -through the encode/decode methods of the packet that causes the precision -loss, which forces the server to lose the same precision as the client -keeping them in sync. - -diff --git a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -index 05ac41e136da43284fb24a6b698ebd36318278fb..3c4ac79c094dc2fff7de94150a34b7bf814ac0de 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -+++ b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -@@ -9,12 +9,12 @@ public class VecDeltaCodec { - - @VisibleForTesting - static long encode(double value) { -- return Math.round(value * 4096.0D); -+ return Math.round(value * 4096.0D); // Paper - Fix MC-4; diff on change - } - - @VisibleForTesting - static double decode(long value) { -- return (double)value / 4096.0D; -+ return (double)value / 4096.0D; // Paper - Fix MC-4; diff on change - } - - public Vec3 decode(long x, long y, long z) { -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 55395837df277eac393949aa7447d69e57c5d756..da5dcea43fdbc0ad0acb1130d363cc8cbea16dfa 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -4167,6 +4167,16 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - return; - } - // Paper end - Block invalid positions and bounding box -+ // Paper start - Fix MC-4 -+ if (this instanceof ItemEntity) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.fixEntityPositionDesync) { -+ // encode/decode from ClientboundMoveEntityPacket -+ x = Mth.lfloor(x * 4096.0D) * (1 / 4096.0D); -+ y = Mth.lfloor(y * 4096.0D) * (1 / 4096.0D); -+ z = Mth.lfloor(z * 4096.0D) * (1 / 4096.0D); -+ } -+ } -+ // Paper end - Fix MC-4 - if (this.position.x != x || this.position.y != y || this.position.z != z) { - this.position = new Vec3(x, y, z); - int i = Mth.floor(x); diff --git a/patches/server/0476-Fix-harming-potion-dupe.patch b/patches/server/0475-Fix-harming-potion-dupe.patch similarity index 100% rename from patches/server/0476-Fix-harming-potion-dupe.patch rename to patches/server/0475-Fix-harming-potion-dupe.patch diff --git a/patches/server/0477-API-to-get-Material-from-Boats-and-Minecarts.patch b/patches/server/0476-API-to-get-Material-from-Boats-and-Minecarts.patch similarity index 100% rename from patches/server/0477-API-to-get-Material-from-Boats-and-Minecarts.patch rename to patches/server/0476-API-to-get-Material-from-Boats-and-Minecarts.patch diff --git a/patches/server/0478-Cache-burn-durations.patch b/patches/server/0477-Cache-burn-durations.patch similarity index 100% rename from patches/server/0478-Cache-burn-durations.patch rename to patches/server/0477-Cache-burn-durations.patch diff --git a/patches/server/0479-Allow-disabling-mob-spawner-spawn-egg-transformation.patch b/patches/server/0478-Allow-disabling-mob-spawner-spawn-egg-transformation.patch similarity index 100% rename from patches/server/0479-Allow-disabling-mob-spawner-spawn-egg-transformation.patch rename to patches/server/0478-Allow-disabling-mob-spawner-spawn-egg-transformation.patch diff --git a/patches/server/0480-Fix-Not-a-string-Map-Conversion-spam.patch b/patches/server/0479-Fix-Not-a-string-Map-Conversion-spam.patch similarity index 100% rename from patches/server/0480-Fix-Not-a-string-Map-Conversion-spam.patch rename to patches/server/0479-Fix-Not-a-string-Map-Conversion-spam.patch diff --git a/patches/server/0481-Add-PlayerFlowerPotManipulateEvent.patch b/patches/server/0480-Add-PlayerFlowerPotManipulateEvent.patch similarity index 100% rename from patches/server/0481-Add-PlayerFlowerPotManipulateEvent.patch rename to patches/server/0480-Add-PlayerFlowerPotManipulateEvent.patch diff --git a/patches/server/0481-Fix-interact-event-not-being-called-sometimes.patch b/patches/server/0481-Fix-interact-event-not-being-called-sometimes.patch new file mode 100644 index 000000000000..26bc4885cd08 --- /dev/null +++ b/patches/server/0481-Fix-interact-event-not-being-called-sometimes.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheMolkaPL +Date: Sun, 21 Jun 2020 17:21:46 +0200 +Subject: [PATCH] Fix interact event not being called sometimes + +* Call PlayerInteractEvent when left-clicking on a block in adventure + mode. +* Call PlayerInteractEvent when left-clicking an Entity that is out of + range in adventure/survival (entity reach is 3.0). + +Co-authored-by: Moulberry + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 87ff69ffbd1e8fa3e88ce2561b56958c23040a10..730b30ee33f0fb2a98454080045608ff538c8c04 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1760,7 +1760,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + MutableComponent ichatmutablecomponent = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED); + + this.player.sendSystemMessage(ichatmutablecomponent, true); +- } else if (enuminteractionresult.shouldSwing()) { ++ } else if (enuminteractionresult.shouldSwing() && !this.player.gameMode.interactResult) { // Paper - Call interact event + this.player.swing(enumhand, true); + } + } +@@ -2306,13 +2306,20 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + double d3 = this.player.gameMode.getGameModeForPlayer() == GameType.CREATIVE ? 5.0D : 4.5D; + // SPIGOT-5607: Only call interact event if no block or entity is being clicked. Use bukkit ray trace method, because it handles blocks and entities at the same time + // SPIGOT-7429: Make sure to call PlayerInteractEvent for spectators and non-pickable entities +- org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.1, entity -> { ++ org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.0, entity -> { // Paper - Call interact event; change raySize from 0.1 to 0.0 + Entity handle = ((CraftEntity) entity).getHandle(); + return entity != this.player.getBukkitEntity() && this.player.getBukkitEntity().canSee(entity) && !handle.isSpectator() && handle.isPickable() && !handle.isPassengerOfSameVehicle(this.player); + }); + if (result == null) { + CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); +- } ++ } else { // Paper start - Call interact event ++ GameType gameType = this.player.gameMode.getGameModeForPlayer(); ++ if (gameType == GameType.ADVENTURE && result.getHitBlock() != null) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, ((org.bukkit.craftbukkit.block.CraftBlock) result.getHitBlock()).getPosition(), org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(result.getHitBlockFace()), this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); ++ } else if (gameType != GameType.CREATIVE && result.getHitEntity() != null && origin.toVector().distanceSquared(result.getHitPosition()) > 3.0D * 3.0D) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); ++ } ++ } // Paper end - Call interact event + + // Arm swing animation + PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == InteractionHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING); diff --git a/patches/server/0482-Fix-interact-event-not-being-called-sometimes.patch b/patches/server/0482-Fix-interact-event-not-being-called-sometimes.patch deleted file mode 100644 index f7f89fa24d2b..000000000000 --- a/patches/server/0482-Fix-interact-event-not-being-called-sometimes.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TheMolkaPL -Date: Sun, 21 Jun 2020 17:21:46 +0200 -Subject: [PATCH] Fix interact event not being called sometimes - -* Call PlayerInteractEvent when left-clicking on a block in adventure - mode. -* Call PlayerInteractEvent when left-clicking an Entity that is out of - range in adventure/survival (entity reach is 3.0). - -Co-authored-by: Moulberry - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 8b8a8d6d6473c14f9e0621bee82fb861de1d209f..d4b9f2b82527852c8fde8299801d54c9ba76371a 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1760,7 +1760,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - MutableComponent ichatmutablecomponent = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED); - - this.player.sendSystemMessage(ichatmutablecomponent, true); -- } else if (enuminteractionresult.shouldSwing()) { -+ } else if (enuminteractionresult.shouldSwing() && !this.player.gameMode.interactResult) { // Paper - Call interact event - this.player.swing(enumhand, true); - } - } -@@ -2306,13 +2306,20 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - double d3 = this.player.gameMode.getGameModeForPlayer() == GameType.CREATIVE ? 5.0D : 4.5D; - // SPIGOT-5607: Only call interact event if no block or entity is being clicked. Use bukkit ray trace method, because it handles blocks and entities at the same time - // SPIGOT-7429: Make sure to call PlayerInteractEvent for spectators and non-pickable entities -- org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.1, entity -> { -+ org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.0, entity -> { // Paper - Call interact event; change raySize from 0.1 to 0.0 - Entity handle = ((CraftEntity) entity).getHandle(); - return entity != this.player.getBukkitEntity() && this.player.getBukkitEntity().canSee(entity) && !handle.isSpectator() && handle.isPickable() && !handle.isPassengerOfSameVehicle(this.player); - }); - if (result == null) { - CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); -- } -+ } else { // Paper start - Call interact event -+ GameType gameType = this.player.gameMode.getGameModeForPlayer(); -+ if (gameType == GameType.ADVENTURE && result.getHitBlock() != null) { -+ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, ((org.bukkit.craftbukkit.block.CraftBlock) result.getHitBlock()).getPosition(), org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(result.getHitBlockFace()), this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); -+ } else if (gameType != GameType.CREATIVE && result.getHitEntity() != null && origin.toVector().distanceSquared(result.getHitPosition()) > 3.0D * 3.0D) { -+ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); -+ } -+ } // Paper end - Call interact event - - // Arm swing animation - PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == InteractionHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING); diff --git a/patches/server/0483-Zombie-API-breaking-doors.patch b/patches/server/0482-Zombie-API-breaking-doors.patch similarity index 100% rename from patches/server/0483-Zombie-API-breaking-doors.patch rename to patches/server/0482-Zombie-API-breaking-doors.patch diff --git a/patches/server/0484-Fix-nerfed-slime-when-splitting.patch b/patches/server/0483-Fix-nerfed-slime-when-splitting.patch similarity index 100% rename from patches/server/0484-Fix-nerfed-slime-when-splitting.patch rename to patches/server/0483-Fix-nerfed-slime-when-splitting.patch diff --git a/patches/server/0485-Add-EntityLoadCrossbowEvent.patch b/patches/server/0484-Add-EntityLoadCrossbowEvent.patch similarity index 100% rename from patches/server/0485-Add-EntityLoadCrossbowEvent.patch rename to patches/server/0484-Add-EntityLoadCrossbowEvent.patch diff --git a/patches/server/0485-Add-WorldGameRuleChangeEvent.patch b/patches/server/0485-Add-WorldGameRuleChangeEvent.patch new file mode 100644 index 000000000000..634bd091ff15 --- /dev/null +++ b/patches/server/0485-Add-WorldGameRuleChangeEvent.patch @@ -0,0 +1,98 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 20 Dec 2020 16:41:44 -0800 +Subject: [PATCH] Add WorldGameRuleChangeEvent + + +diff --git a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java +index c8c358531dbc167e249bac2af246c5e34fbdd4df..10c1790226e25da3b9b599c9a40de57d5727ddc4 100644 +--- a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java ++++ b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java +@@ -33,7 +33,7 @@ public class GameRuleCommand { + CommandSourceStack commandlistenerwrapper = (CommandSourceStack) context.getSource(); + T t0 = commandlistenerwrapper.getLevel().getGameRules().getRule(key); // CraftBukkit + +- t0.setFromArgument(context, "value"); ++ t0.setFromArgument(context, "value", key); // Paper - Add WorldGameRuleChangeEvent + commandlistenerwrapper.sendSuccess(() -> { + return Component.translatable("commands.gamerule.set", key.getId(), t0.toString()); + }, true); +diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java +index ba825ff3df79903aefe1a4cb14efab9a302397d7..ac7a5410b01a6741e3b548d153f37ea1d8c1a4cb 100644 +--- a/src/main/java/net/minecraft/world/level/GameRules.java ++++ b/src/main/java/net/minecraft/world/level/GameRules.java +@@ -286,10 +286,10 @@ public class GameRules { + this.type = type; + } + +- protected abstract void updateFromArgument(CommandContext context, String name); ++ protected abstract void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey); // Paper - Add WorldGameRuleChangeEvent + +- public void setFromArgument(CommandContext context, String name) { +- this.updateFromArgument(context, name); ++ public void setFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper - Add WorldGameRuleChangeEvent ++ this.updateFromArgument(context, name, gameRuleKey); // Paper - Add WorldGameRuleChangeEvent + this.onChanged(((CommandSourceStack) context.getSource()).getLevel()); // CraftBukkit - per-world + } + +@@ -347,8 +347,11 @@ public class GameRules { + } + + @Override +- protected void updateFromArgument(CommandContext context, String name) { +- this.value = BoolArgumentType.getBool(context, name); ++ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(BoolArgumentType.getBool(context, name))); ++ if (!event.callEvent()) return; ++ this.value = Boolean.parseBoolean(event.getValue()); ++ // Paper end - Add WorldGameRuleChangeEvent + } + + public boolean get() { +@@ -412,8 +415,11 @@ public class GameRules { + } + + @Override +- protected void updateFromArgument(CommandContext context, String name) { +- this.value = IntegerArgumentType.getInteger(context, name); ++ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(IntegerArgumentType.getInteger(context, name))); ++ if (!event.callEvent()) return; ++ this.value = Integer.parseInt(event.getValue()); ++ // Paper end - Add WorldGameRuleChangeEvent + } + + public int get() { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index dbe1e59572ca0f98783db456bdab6ee4e79f7689..9d44a2ce6da1b6338e5a1aaa9238483b64c9a34f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1864,8 +1864,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + if (!this.isGameRule(rule)) return false; + ++ // Paper start - Add WorldGameRuleChangeEvent ++ GameRule gameRule = GameRule.getByName(rule); ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(this, null, gameRule, value); ++ if (!event.callEvent()) return false; ++ // Paper end - Add WorldGameRuleChangeEvent + GameRules.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule)); +- handle.deserialize(value); ++ handle.deserialize(event.getValue()); // Paper - Add WorldGameRuleChangeEvent + handle.onChanged(this.getHandle()); + return true; + } +@@ -1901,8 +1906,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + if (!this.isGameRule(rule.getName())) return false; + ++ // Paper start - Add WorldGameRuleChangeEvent ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(this, null, rule, String.valueOf(newValue)); ++ if (!event.callEvent()) return false; ++ // Paper end - Add WorldGameRuleChangeEvent + GameRules.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule.getName())); +- handle.deserialize(newValue.toString()); ++ handle.deserialize(event.getValue()); // Paper - Add WorldGameRuleChangeEvent + handle.onChanged(this.getHandle()); + return true; + } diff --git a/patches/server/0486-Add-ServerResourcesReloadedEvent.patch b/patches/server/0486-Add-ServerResourcesReloadedEvent.patch new file mode 100644 index 000000000000..b01f7c1460ed --- /dev/null +++ b/patches/server/0486-Add-ServerResourcesReloadedEvent.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Dec 2020 20:04:01 -0800 +Subject: [PATCH] Add ServerResourcesReloadedEvent + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index c2892af4e98d6a436a43fea950726cc1ca60ebda..06b7816bafa3ac1093b796ca6e7bb3462df8bfec 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2036,7 +2036,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop reloadResources(Collection dataPacks) { ++ return this.reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); ++ } ++ public CompletableFuture reloadResources(Collection dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) { ++ // Paper end - Add ServerResourcesReloadedEvent + RegistryAccess.Frozen iregistrycustom_dimension = this.registries.getAccessForLoading(RegistryLayer.RELOADABLE); + CompletableFuture completablefuture = CompletableFuture.supplyAsync(() -> { + Stream stream = dataPacks.stream(); // CraftBukkit - decompile error +@@ -2069,6 +2075,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop dataPacks, CommandSourceStack source) { +- source.getServer().reloadResources(dataPacks).exceptionally((throwable) -> { ++ source.getServer().reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally((throwable) -> { // Paper - Add ServerResourcesReloadedEvent + ReloadCommand.LOGGER.warn("Failed to execute reload", throwable); + source.sendFailure(Component.translatable("commands.reload.failure")); + return null; +@@ -50,7 +50,7 @@ public class ReloadCommand { + WorldData savedata = minecraftserver.getWorldData(); + Collection collection = resourcepackrepository.getSelectedIds(); + Collection collection1 = ReloadCommand.discoverNewPacks(resourcepackrepository, savedata, collection); +- minecraftserver.reloadResources(collection1); ++ minecraftserver.reloadResources(collection1, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper - Add ServerResourcesReloadedEvent + } + // CraftBukkit end + diff --git a/patches/server/0486-Add-WorldGameRuleChangeEvent.patch b/patches/server/0486-Add-WorldGameRuleChangeEvent.patch deleted file mode 100644 index f24bdcc6de90..000000000000 --- a/patches/server/0486-Add-WorldGameRuleChangeEvent.patch +++ /dev/null @@ -1,98 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 20 Dec 2020 16:41:44 -0800 -Subject: [PATCH] Add WorldGameRuleChangeEvent - - -diff --git a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java -index c8c358531dbc167e249bac2af246c5e34fbdd4df..10c1790226e25da3b9b599c9a40de57d5727ddc4 100644 ---- a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java -+++ b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java -@@ -33,7 +33,7 @@ public class GameRuleCommand { - CommandSourceStack commandlistenerwrapper = (CommandSourceStack) context.getSource(); - T t0 = commandlistenerwrapper.getLevel().getGameRules().getRule(key); // CraftBukkit - -- t0.setFromArgument(context, "value"); -+ t0.setFromArgument(context, "value", key); // Paper - Add WorldGameRuleChangeEvent - commandlistenerwrapper.sendSuccess(() -> { - return Component.translatable("commands.gamerule.set", key.getId(), t0.toString()); - }, true); -diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java -index 334001cb749600c973c82391e1c11f0e40bd2dfb..f3cdf1fa7731eb7bb1cb89aa6a37204d81257cb0 100644 ---- a/src/main/java/net/minecraft/world/level/GameRules.java -+++ b/src/main/java/net/minecraft/world/level/GameRules.java -@@ -285,10 +285,10 @@ public class GameRules { - this.type = type; - } - -- protected abstract void updateFromArgument(CommandContext context, String name); -+ protected abstract void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey); // Paper - Add WorldGameRuleChangeEvent - -- public void setFromArgument(CommandContext context, String name) { -- this.updateFromArgument(context, name); -+ public void setFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper - Add WorldGameRuleChangeEvent -+ this.updateFromArgument(context, name, gameRuleKey); // Paper - Add WorldGameRuleChangeEvent - this.onChanged(((CommandSourceStack) context.getSource()).getServer()); - } - -@@ -346,8 +346,11 @@ public class GameRules { - } - - @Override -- protected void updateFromArgument(CommandContext context, String name) { -- this.value = BoolArgumentType.getBool(context, name); -+ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent -+ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(BoolArgumentType.getBool(context, name))); -+ if (!event.callEvent()) return; -+ this.value = Boolean.parseBoolean(event.getValue()); -+ // Paper end - Add WorldGameRuleChangeEvent - } - - public boolean get() { -@@ -411,8 +414,11 @@ public class GameRules { - } - - @Override -- protected void updateFromArgument(CommandContext context, String name) { -- this.value = IntegerArgumentType.getInteger(context, name); -+ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent -+ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(IntegerArgumentType.getInteger(context, name))); -+ if (!event.callEvent()) return; -+ this.value = Integer.parseInt(event.getValue()); -+ // Paper end - Add WorldGameRuleChangeEvent - } - - public int get() { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 46a168a3e0db80c1584931c13eedeab420aa76c6..75387d9507add359e7b35527c6a69b6a96cdff5c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1851,8 +1851,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - if (!this.isGameRule(rule)) return false; - -+ // Paper start - Add WorldGameRuleChangeEvent -+ GameRule gameRule = GameRule.getByName(rule); -+ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(this, null, gameRule, value); -+ if (!event.callEvent()) return false; -+ // Paper end - Add WorldGameRuleChangeEvent - GameRules.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule)); -- handle.deserialize(value); -+ handle.deserialize(event.getValue()); // Paper - Add WorldGameRuleChangeEvent - handle.onChanged(this.getHandle().getServer()); - return true; - } -@@ -1888,8 +1893,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - if (!this.isGameRule(rule.getName())) return false; - -+ // Paper start - Add WorldGameRuleChangeEvent -+ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(this, null, rule, String.valueOf(newValue)); -+ if (!event.callEvent()) return false; -+ // Paper end - Add WorldGameRuleChangeEvent - GameRules.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule.getName())); -- handle.deserialize(newValue.toString()); -+ handle.deserialize(event.getValue()); // Paper - Add WorldGameRuleChangeEvent - handle.onChanged(this.getHandle().getServer()); - return true; - } diff --git a/patches/server/0487-Add-ServerResourcesReloadedEvent.patch b/patches/server/0487-Add-ServerResourcesReloadedEvent.patch deleted file mode 100644 index db864afdeab5..000000000000 --- a/patches/server/0487-Add-ServerResourcesReloadedEvent.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 2 Dec 2020 20:04:01 -0800 -Subject: [PATCH] Add ServerResourcesReloadedEvent - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index d1bd7ccd6e36497849837072c8f1326336409b42..4fd56dc6f1a9dc15b639d6aeba29e678354ee7f8 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2036,7 +2036,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop reloadResources(Collection dataPacks) { -+ return this.reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); -+ } -+ public CompletableFuture reloadResources(Collection dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) { -+ // Paper end - Add ServerResourcesReloadedEvent - RegistryAccess.Frozen iregistrycustom_dimension = this.registries.getAccessForLoading(RegistryLayer.RELOADABLE); - CompletableFuture completablefuture = CompletableFuture.supplyAsync(() -> { - Stream stream = dataPacks.stream(); // CraftBukkit - decompile error -@@ -2069,6 +2075,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop dataPacks, CommandSourceStack source) { -- source.getServer().reloadResources(dataPacks).exceptionally((throwable) -> { -+ source.getServer().reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally((throwable) -> { // Paper - Add ServerResourcesReloadedEvent - ReloadCommand.LOGGER.warn("Failed to execute reload", throwable); - source.sendFailure(Component.translatable("commands.reload.failure")); - return null; -@@ -50,7 +50,7 @@ public class ReloadCommand { - WorldData savedata = minecraftserver.getWorldData(); - Collection collection = resourcepackrepository.getSelectedIds(); - Collection collection1 = ReloadCommand.discoverNewPacks(resourcepackrepository, savedata, collection); -- minecraftserver.reloadResources(collection1); -+ minecraftserver.reloadResources(collection1, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper - Add ServerResourcesReloadedEvent - } - // CraftBukkit end - diff --git a/patches/server/0488-Add-world-settings-for-mobs-picking-up-loot.patch b/patches/server/0487-Add-world-settings-for-mobs-picking-up-loot.patch similarity index 100% rename from patches/server/0488-Add-world-settings-for-mobs-picking-up-loot.patch rename to patches/server/0487-Add-world-settings-for-mobs-picking-up-loot.patch diff --git a/patches/server/0488-Add-BlockFailedDispenseEvent.patch b/patches/server/0488-Add-BlockFailedDispenseEvent.patch new file mode 100644 index 000000000000..4b6e082326b5 --- /dev/null +++ b/patches/server/0488-Add-BlockFailedDispenseEvent.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheViperShow <29604693+TheViperShow@users.noreply.github.com> +Date: Wed, 22 Apr 2020 09:40:38 +0200 +Subject: [PATCH] Add BlockFailedDispenseEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +index d77ca08832105e82cee265eea222e0d64a8876ef..5593a0aa9e618071b6521b213dde0f628348c3dc 100644 +--- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +@@ -95,8 +95,10 @@ public class DispenserBlock extends BaseEntityBlock { + int i = tileentitydispenser.getRandomSlot(world.random); + + if (i < 0) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) { // Paper - Add BlockFailedDispenseEvent + world.levelEvent(1001, pos, 0); + world.gameEvent(GameEvent.BLOCK_ACTIVATE, pos, GameEvent.Context.of(tileentitydispenser.getBlockState())); ++ } // Paper - Add BlockFailedDispenseEvent + } else { + ItemStack itemstack = tileentitydispenser.getItem(i); + DispenseItemBehavior idispensebehavior = this.getDispenseMethod(itemstack); +diff --git a/src/main/java/net/minecraft/world/level/block/DropperBlock.java b/src/main/java/net/minecraft/world/level/block/DropperBlock.java +index 913ed110d8402d377152753325901eb7f3ac82d6..1d13f8a1009d6eda351c697052d499d594a6aaa8 100644 +--- a/src/main/java/net/minecraft/world/level/block/DropperBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DropperBlock.java +@@ -59,6 +59,7 @@ public class DropperBlock extends DispenserBlock { + int i = tileentitydispenser.getRandomSlot(world.random); + + if (i < 0) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) // Paper - Add BlockFailedDispenseEvent + world.levelEvent(1001, pos, 0); + } else { + ItemStack itemstack = tileentitydispenser.getItem(i); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 40d6e655a09888ee95eb136cb8a6f919a1f74aa6..29473d4bd174e8d2e6ee9ecf348edb41af5f6ea3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -2017,4 +2017,12 @@ public class CraftEventFactory { + return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion()); + } + // Paper end - WitchReadyPotionEvent ++ ++ // Paper start ++ public static boolean handleBlockFailedDispenseEvent(ServerLevel serverLevel, BlockPos pos) { ++ org.bukkit.block.Block block = CraftBlock.at(serverLevel, pos); ++ io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block); ++ return event.callEvent(); ++ } ++ // Paper end + } diff --git a/patches/server/0489-Add-BlockFailedDispenseEvent.patch b/patches/server/0489-Add-BlockFailedDispenseEvent.patch deleted file mode 100644 index df46dd7acf88..000000000000 --- a/patches/server/0489-Add-BlockFailedDispenseEvent.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TheViperShow <29604693+TheViperShow@users.noreply.github.com> -Date: Wed, 22 Apr 2020 09:40:38 +0200 -Subject: [PATCH] Add BlockFailedDispenseEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -index d77ca08832105e82cee265eea222e0d64a8876ef..5593a0aa9e618071b6521b213dde0f628348c3dc 100644 ---- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -@@ -95,8 +95,10 @@ public class DispenserBlock extends BaseEntityBlock { - int i = tileentitydispenser.getRandomSlot(world.random); - - if (i < 0) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) { // Paper - Add BlockFailedDispenseEvent - world.levelEvent(1001, pos, 0); - world.gameEvent(GameEvent.BLOCK_ACTIVATE, pos, GameEvent.Context.of(tileentitydispenser.getBlockState())); -+ } // Paper - Add BlockFailedDispenseEvent - } else { - ItemStack itemstack = tileentitydispenser.getItem(i); - DispenseItemBehavior idispensebehavior = this.getDispenseMethod(itemstack); -diff --git a/src/main/java/net/minecraft/world/level/block/DropperBlock.java b/src/main/java/net/minecraft/world/level/block/DropperBlock.java -index 913ed110d8402d377152753325901eb7f3ac82d6..1d13f8a1009d6eda351c697052d499d594a6aaa8 100644 ---- a/src/main/java/net/minecraft/world/level/block/DropperBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DropperBlock.java -@@ -59,6 +59,7 @@ public class DropperBlock extends DispenserBlock { - int i = tileentitydispenser.getRandomSlot(world.random); - - if (i < 0) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) // Paper - Add BlockFailedDispenseEvent - world.levelEvent(1001, pos, 0); - } else { - ItemStack itemstack = tileentitydispenser.getItem(i); -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index cddd081d451f38e90d8b49a16abb0c95d498defe..6a95328293e3600b7a560074a0e6083db9cd3e1f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -2072,4 +2072,12 @@ public class CraftEventFactory { - return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion()); - } - // Paper end - WitchReadyPotionEvent -+ -+ // Paper start -+ public static boolean handleBlockFailedDispenseEvent(ServerLevel serverLevel, BlockPos pos) { -+ org.bukkit.block.Block block = CraftBlock.at(serverLevel, pos); -+ io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block); -+ return event.callEvent(); -+ } -+ // Paper end - } diff --git a/patches/server/0490-Add-PlayerLecternPageChangeEvent.patch b/patches/server/0489-Add-PlayerLecternPageChangeEvent.patch similarity index 100% rename from patches/server/0490-Add-PlayerLecternPageChangeEvent.patch rename to patches/server/0489-Add-PlayerLecternPageChangeEvent.patch diff --git a/patches/server/0491-Add-PlayerLoomPatternSelectEvent.patch b/patches/server/0490-Add-PlayerLoomPatternSelectEvent.patch similarity index 100% rename from patches/server/0491-Add-PlayerLoomPatternSelectEvent.patch rename to patches/server/0490-Add-PlayerLoomPatternSelectEvent.patch diff --git a/patches/server/0492-Configurable-door-breaking-difficulty.patch b/patches/server/0491-Configurable-door-breaking-difficulty.patch similarity index 100% rename from patches/server/0492-Configurable-door-breaking-difficulty.patch rename to patches/server/0491-Configurable-door-breaking-difficulty.patch diff --git a/patches/server/0493-Empty-commands-shall-not-be-dispatched.patch b/patches/server/0492-Empty-commands-shall-not-be-dispatched.patch similarity index 100% rename from patches/server/0493-Empty-commands-shall-not-be-dispatched.patch rename to patches/server/0492-Empty-commands-shall-not-be-dispatched.patch diff --git a/patches/server/0494-Remove-stale-POIs.patch b/patches/server/0493-Remove-stale-POIs.patch similarity index 100% rename from patches/server/0494-Remove-stale-POIs.patch rename to patches/server/0493-Remove-stale-POIs.patch diff --git a/patches/server/0495-Fix-villager-boat-exploit.patch b/patches/server/0494-Fix-villager-boat-exploit.patch similarity index 100% rename from patches/server/0495-Fix-villager-boat-exploit.patch rename to patches/server/0494-Fix-villager-boat-exploit.patch diff --git a/patches/server/0495-Add-sendOpLevel-API.patch b/patches/server/0495-Add-sendOpLevel-API.patch new file mode 100644 index 000000000000..c311c2ba8a76 --- /dev/null +++ b/patches/server/0495-Add-sendOpLevel-API.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Tue, 29 Dec 2020 15:03:03 +0100 +Subject: [PATCH] Add sendOpLevel API + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index e629a560ae2163ea45ede727b97a301e8fa6f1e8..f336b52a529c3c0ddccb36ace8b441fba61b99dd 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1100,6 +1100,11 @@ public abstract class PlayerList { + } + + private void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel) { ++ // Paper start - Add sendOpLevel API ++ this.sendPlayerPermissionLevel(player, permissionLevel, true); ++ } ++ public void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel, boolean recalculatePermissions) { ++ // Paper end - Add sendOpLevel API + if (player.connection != null) { + byte b0; + +@@ -1114,8 +1119,10 @@ public abstract class PlayerList { + player.connection.send(new ClientboundEntityEventPacket(player, b0)); + } + ++ if (recalculatePermissions) { // Paper - Add sendOpLevel API + player.getBukkitEntity().recalculatePermissions(); // CraftBukkit + this.server.getCommands().sendCommands(player); ++ } // Paper - Add sendOpLevel API + } + + public boolean isWhiteListed(GameProfile profile) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index ec4ebbdde676806c4b2348408a8004dd66cbc44b..a9f30ba8d6d6e3d488f46b0bd79bf77b660c1b82 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -597,6 +597,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + // Paper end + ++ // Paper start - Add sendOpLevel API ++ @Override ++ public void sendOpLevel(byte level) { ++ Preconditions.checkArgument(level >= 0 && level <= 4, "Level must be within [0, 4]"); ++ ++ this.getHandle().getServer().getPlayerList().sendPlayerPermissionLevel(this.getHandle(), level, false); ++ } ++ // Paper end - Add sendOpLevel API ++ + @Override + public void setCompassTarget(Location loc) { + Preconditions.checkArgument(loc != null, "Location cannot be null"); diff --git a/patches/server/0496-Add-sendOpLevel-API.patch b/patches/server/0496-Add-sendOpLevel-API.patch deleted file mode 100644 index 9c3630f10e69..000000000000 --- a/patches/server/0496-Add-sendOpLevel-API.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Tue, 29 Dec 2020 15:03:03 +0100 -Subject: [PATCH] Add sendOpLevel API - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index e629a560ae2163ea45ede727b97a301e8fa6f1e8..f336b52a529c3c0ddccb36ace8b441fba61b99dd 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1100,6 +1100,11 @@ public abstract class PlayerList { - } - - private void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel) { -+ // Paper start - Add sendOpLevel API -+ this.sendPlayerPermissionLevel(player, permissionLevel, true); -+ } -+ public void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel, boolean recalculatePermissions) { -+ // Paper end - Add sendOpLevel API - if (player.connection != null) { - byte b0; - -@@ -1114,8 +1119,10 @@ public abstract class PlayerList { - player.connection.send(new ClientboundEntityEventPacket(player, b0)); - } - -+ if (recalculatePermissions) { // Paper - Add sendOpLevel API - player.getBukkitEntity().recalculatePermissions(); // CraftBukkit - this.server.getCommands().sendCommands(player); -+ } // Paper - Add sendOpLevel API - } - - public boolean isWhiteListed(GameProfile profile) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index eccc82f4c8eeb3a2b6ae2abbd1a38d447a2bfda8..537590236e9a0ac2946953ebbebe0044e17ccc72 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -591,6 +591,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - // Paper end - -+ // Paper start - Add sendOpLevel API -+ @Override -+ public void sendOpLevel(byte level) { -+ Preconditions.checkArgument(level >= 0 && level <= 4, "Level must be within [0, 4]"); -+ -+ this.getHandle().getServer().getPlayerList().sendPlayerPermissionLevel(this.getHandle(), level, false); -+ } -+ // Paper end - Add sendOpLevel API -+ - @Override - public void setCompassTarget(Location loc) { - Preconditions.checkArgument(loc != null, "Location cannot be null"); diff --git a/patches/server/0496-TODO-Registry-Modification-API.patch b/patches/server/0496-TODO-Registry-Modification-API.patch new file mode 100644 index 000000000000..b47ca6aff77b --- /dev/null +++ b/patches/server/0496-TODO-Registry-Modification-API.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 27 Feb 2023 18:28:39 -0800 +Subject: [PATCH] TODO Registry Modification API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +index 826c45f35c80ae4bf536fbe3b48354e3626fe2e1..110987de809339b4ce14eaf377782ebf3460164a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +@@ -119,6 +119,7 @@ public class CraftRegistry implements Registry { + if (bukkitClass == DamageType.class) { + return new CraftRegistry<>(DamageType.class, registryHolder.registryOrThrow(Registries.DAMAGE_TYPE), CraftDamageType::new); + } ++ // TODO registry modification API + + return null; + } diff --git a/patches/server/0497-Add-StructuresLocateEvent.patch b/patches/server/0497-Add-StructuresLocateEvent.patch new file mode 100644 index 000000000000..5f8804f44dc7 --- /dev/null +++ b/patches/server/0497-Add-StructuresLocateEvent.patch @@ -0,0 +1,219 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dfsek +Date: Wed, 16 Sep 2020 01:12:29 -0700 +Subject: [PATCH] Add StructuresLocateEvent + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java b/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2667067fd13f61e0464ba88ae4e4a7078351d1a8 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java +@@ -0,0 +1,35 @@ ++package io.papermc.paper.world.structure; ++ ++import java.util.Objects; ++import net.minecraft.core.Registry; ++import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.level.levelgen.structure.Structure; ++import org.bukkit.NamespacedKey; ++import org.bukkit.StructureType; ++import org.bukkit.craftbukkit.CraftRegistry; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++@Deprecated(forRemoval = true) ++public final class PaperConfiguredStructure { ++ ++ private PaperConfiguredStructure() { ++ } ++ ++ @Deprecated(forRemoval = true) ++ public static final class LegacyRegistry extends CraftRegistry { ++ ++ public LegacyRegistry(final Registry minecraftRegistry) { ++ super(ConfiguredStructure.class, minecraftRegistry, LegacyRegistry::minecraftToBukkit); ++ } ++ ++ private static @Nullable ConfiguredStructure minecraftToBukkit(NamespacedKey key, Structure nms) { ++ final ResourceLocation structureTypeLoc = Objects.requireNonNull(BuiltInRegistries.STRUCTURE_TYPE.getKey(nms.type()), "unexpected structure type " + nms.type()); ++ final @Nullable StructureType structureType = StructureType.getStructureTypes().get(structureTypeLoc.getPath()); ++ return structureType == null ? null : new ConfiguredStructure(key, structureType); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +index 3e57142dd9cb23d43857d5a4cb30962e4b696b74..a6d5b3fa7e3437e0aec54eec4079e9f3267c64b8 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -121,6 +121,24 @@ public abstract class ChunkGenerator { + + @Nullable + public Pair> findNearestMapStructure(ServerLevel world, HolderSet structures, BlockPos center, int radius, boolean skipReferencedStructures) { ++ // Paper start - StructuresLocateEvent ++ final org.bukkit.World bukkitWorld = world.getWorld(); ++ final org.bukkit.Location origin = io.papermc.paper.util.MCUtil.toLocation(world, center); ++ final List apiStructures = structures.stream().map(Holder::value).map(nms -> org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(nms)).toList(); ++ if (!apiStructures.isEmpty()) { ++ final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, apiStructures, radius, skipReferencedStructures); ++ if (!event.callEvent()) { ++ return null; ++ } ++ if (event.getResult() != null) { ++ return Pair.of(io.papermc.paper.util.MCUtil.toBlockPos(event.getResult().pos()), world.registryAccess().registryOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(event.getResult().structure()))); ++ } ++ center = io.papermc.paper.util.MCUtil.toBlockPosition(event.getOrigin()); ++ radius = event.getRadius(); ++ skipReferencedStructures = event.shouldFindUnexplored(); ++ structures = HolderSet.direct(api -> world.registryAccess().registryOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(api)), event.getStructures()); ++ } ++ // Paper end + ChunkGeneratorStructureState chunkgeneratorstructurestate = world.getChunkSource().getGeneratorState(); + Map>> map = new Object2ObjectArrayMap(); + Iterator iterator = structures.iterator(); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +index 110987de809339b4ce14eaf377782ebf3460164a..b12b99253543445475b73a1d3d7c6364856b49e8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +@@ -120,6 +120,11 @@ public class CraftRegistry implements Registry { + return new CraftRegistry<>(DamageType.class, registryHolder.registryOrThrow(Registries.DAMAGE_TYPE), CraftDamageType::new); + } + // TODO registry modification API ++ // Paper start - remove this after a while along with all ConfiguredStructure stuff ++ if (bukkitClass == io.papermc.paper.world.structure.ConfiguredStructure.class) { ++ return new io.papermc.paper.world.structure.PaperConfiguredStructure.LegacyRegistry(registryHolder.registryOrThrow(Registries.STRUCTURE)); ++ } ++ // Paper end + + return null; + } +diff --git a/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java b/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9178fe0d01b998ca1442bf2511f8fc00db9388ba +--- /dev/null ++++ b/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java +@@ -0,0 +1,96 @@ ++package io.papermc.paper.world.structure; ++ ++import io.papermc.paper.registry.Reference; ++import net.minecraft.core.Registry; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.Bootstrap; ++import net.minecraft.world.level.levelgen.structure.Structure; ++import net.minecraft.world.level.levelgen.structure.BuiltinStructures; ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.jupiter.api.AfterAll; ++import org.junit.jupiter.api.BeforeAll; ++import org.junit.jupiter.api.Test; ++ ++import java.io.PrintStream; ++import java.lang.reflect.Field; ++import java.lang.reflect.Modifier; ++import java.util.LinkedHashMap; ++import java.util.Map; ++import java.util.StringJoiner; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++import static org.junit.jupiter.api.Assertions.assertNotNull; ++import static org.junit.jupiter.api.Assertions.assertTrue; ++ ++@Deprecated(forRemoval = true) ++public class ConfiguredStructureTest extends AbstractTestingBase { ++ ++ private static final Map BUILT_IN_STRUCTURES = new LinkedHashMap<>(); ++ private static final Map> DEFAULT_CONFIGURED_STRUCTURES = new LinkedHashMap<>(); ++ ++ private static PrintStream out; ++ ++ @BeforeAll ++ public static void collectStructures() throws ReflectiveOperationException { ++ out = System.out; ++ System.setOut(Bootstrap.STDOUT); ++ for (Field field : BuiltinStructures.class.getDeclaredFields()) { ++ if (field.getType().equals(ResourceKey.class) && Modifier.isStatic(field.getModifiers())) { ++ BUILT_IN_STRUCTURES.put(((ResourceKey) field.get(null)).location(), field.getName()); ++ } ++ } ++ for (Field field : ConfiguredStructure.class.getDeclaredFields()) { ++ if (field.getType().equals(Reference.class) && Modifier.isStatic(field.getModifiers())) { ++ final Reference ref = (Reference) field.get(null); ++ DEFAULT_CONFIGURED_STRUCTURES.put(ref.getKey(), ref); ++ } ++ } ++ } ++ ++ @Test ++ public void testMinecraftToApi() { ++ Registry structureRegistry = AbstractTestingBase.REGISTRY_CUSTOM.registryOrThrow(Registries.STRUCTURE); ++ assertEquals(BUILT_IN_STRUCTURES.size(), structureRegistry.size(), "configured structure maps should be the same size"); ++ ++ Map missing = new LinkedHashMap<>(); ++ for (Structure feature : structureRegistry) { ++ final ResourceLocation key = structureRegistry.getKey(feature); ++ assertNotNull(key, "Missing built-in registry key"); ++ if (key.equals(BuiltinStructures.ANCIENT_CITY.location()) || key.equals(BuiltinStructures.TRAIL_RUINS.location()) || key.equals(BuiltinStructures.TRIAL_CHAMBERS.location())) { ++ continue; // TODO remove when upstream adds "jigsaw" StructureType ++ } ++ if (DEFAULT_CONFIGURED_STRUCTURES.get(CraftNamespacedKey.fromMinecraft(key)) == null) { ++ missing.put(key, feature); ++ } ++ } ++ ++ assertTrue(missing.isEmpty(), printMissing(missing)); ++ } ++ ++ @Test ++ public void testApiToMinecraft() { ++ Registry structureRegistry = AbstractTestingBase.REGISTRY_CUSTOM.registryOrThrow(Registries.STRUCTURE); ++ for (NamespacedKey apiKey : DEFAULT_CONFIGURED_STRUCTURES.keySet()) { ++ assertTrue(structureRegistry.containsKey(CraftNamespacedKey.toMinecraft(apiKey)), apiKey + " does not have a minecraft counterpart"); ++ } ++ } ++ ++ private static String printMissing(Map missing) { ++ final StringJoiner joiner = new StringJoiner("\n", "Missing: \n", ""); ++ ++ missing.forEach((key, configuredFeature) -> { ++ joiner.add("public static final Reference " + BUILT_IN_STRUCTURES.get(key) + " = create(\"" + key.getPath() + "\");"); ++ }); ++ ++ return joiner.toString(); ++ } ++ ++ @AfterAll ++ public static void after() { ++ System.setOut(out); ++ } ++} +diff --git a/src/test/java/org/bukkit/registry/PerRegistryTest.java b/src/test/java/org/bukkit/registry/PerRegistryTest.java +index 4e4ea083063daf22f1bb785ef212958ea889c43b..1c4966520b6401e6571aa44d5934dfa280bc80e3 100644 +--- a/src/test/java/org/bukkit/registry/PerRegistryTest.java ++++ b/src/test/java/org/bukkit/registry/PerRegistryTest.java +@@ -36,6 +36,7 @@ public class PerRegistryTest extends AbstractTestingBase { + if (!(object instanceof CraftRegistry registry)) { + continue; + } ++ if (object instanceof io.papermc.paper.world.structure.PaperConfiguredStructure.LegacyRegistry) continue; // Paper - skip + + data.add(Arguments.of(registry)); + } catch (ReflectiveOperationException e) { +diff --git a/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java b/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java +index 4adaafafb7140e983a4e90f0ff0deaaf0887a9a5..65cc33c45553e755371ec4313dd38bb61eb7d61c 100644 +--- a/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java ++++ b/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java +@@ -23,6 +23,7 @@ public class RegistryArgumentAddedTest extends AbstractTestingBase { + + Set> loadedRegistries = new HashSet<>(DummyServer.registers.keySet()); + Set> notFound = new HashSet<>(); ++ loadedRegistries.remove(io.papermc.paper.world.structure.ConfiguredStructure.class); // Paper - ignore + + RegistriesArgumentProvider + .getData() diff --git a/patches/server/0497-TODO-Registry-Modification-API.patch b/patches/server/0497-TODO-Registry-Modification-API.patch deleted file mode 100644 index 5a7f3ae78dbc..000000000000 --- a/patches/server/0497-TODO-Registry-Modification-API.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 27 Feb 2023 18:28:39 -0800 -Subject: [PATCH] TODO Registry Modification API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -index 72c2bc09ce6eefc63c3bab5a8f183e48316d0196..b84e984e53834ef338afd7b61a656eb82a14349a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -@@ -114,6 +114,7 @@ public class CraftRegistry implements Registry { - if (bukkitClass == TrimPattern.class) { - return new CraftRegistry<>(TrimPattern.class, registryHolder.registryOrThrow(Registries.TRIM_PATTERN), CraftTrimPattern::new); - } -+ // TODO registry modification API - - return null; - } diff --git a/patches/server/0498-Add-StructuresLocateEvent.patch b/patches/server/0498-Add-StructuresLocateEvent.patch deleted file mode 100644 index 34dd0f2443b2..000000000000 --- a/patches/server/0498-Add-StructuresLocateEvent.patch +++ /dev/null @@ -1,219 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: dfsek -Date: Wed, 16 Sep 2020 01:12:29 -0700 -Subject: [PATCH] Add StructuresLocateEvent - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java b/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2667067fd13f61e0464ba88ae4e4a7078351d1a8 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java -@@ -0,0 +1,35 @@ -+package io.papermc.paper.world.structure; -+ -+import java.util.Objects; -+import net.minecraft.core.Registry; -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.level.levelgen.structure.Structure; -+import org.bukkit.NamespacedKey; -+import org.bukkit.StructureType; -+import org.bukkit.craftbukkit.CraftRegistry; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+@Deprecated(forRemoval = true) -+public final class PaperConfiguredStructure { -+ -+ private PaperConfiguredStructure() { -+ } -+ -+ @Deprecated(forRemoval = true) -+ public static final class LegacyRegistry extends CraftRegistry { -+ -+ public LegacyRegistry(final Registry minecraftRegistry) { -+ super(ConfiguredStructure.class, minecraftRegistry, LegacyRegistry::minecraftToBukkit); -+ } -+ -+ private static @Nullable ConfiguredStructure minecraftToBukkit(NamespacedKey key, Structure nms) { -+ final ResourceLocation structureTypeLoc = Objects.requireNonNull(BuiltInRegistries.STRUCTURE_TYPE.getKey(nms.type()), "unexpected structure type " + nms.type()); -+ final @Nullable StructureType structureType = StructureType.getStructureTypes().get(structureTypeLoc.getPath()); -+ return structureType == null ? null : new ConfiguredStructure(key, structureType); -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -index 3e57142dd9cb23d43857d5a4cb30962e4b696b74..a6d5b3fa7e3437e0aec54eec4079e9f3267c64b8 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -121,6 +121,24 @@ public abstract class ChunkGenerator { - - @Nullable - public Pair> findNearestMapStructure(ServerLevel world, HolderSet structures, BlockPos center, int radius, boolean skipReferencedStructures) { -+ // Paper start - StructuresLocateEvent -+ final org.bukkit.World bukkitWorld = world.getWorld(); -+ final org.bukkit.Location origin = io.papermc.paper.util.MCUtil.toLocation(world, center); -+ final List apiStructures = structures.stream().map(Holder::value).map(nms -> org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(nms)).toList(); -+ if (!apiStructures.isEmpty()) { -+ final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, apiStructures, radius, skipReferencedStructures); -+ if (!event.callEvent()) { -+ return null; -+ } -+ if (event.getResult() != null) { -+ return Pair.of(io.papermc.paper.util.MCUtil.toBlockPos(event.getResult().pos()), world.registryAccess().registryOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(event.getResult().structure()))); -+ } -+ center = io.papermc.paper.util.MCUtil.toBlockPosition(event.getOrigin()); -+ radius = event.getRadius(); -+ skipReferencedStructures = event.shouldFindUnexplored(); -+ structures = HolderSet.direct(api -> world.registryAccess().registryOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(api)), event.getStructures()); -+ } -+ // Paper end - ChunkGeneratorStructureState chunkgeneratorstructurestate = world.getChunkSource().getGeneratorState(); - Map>> map = new Object2ObjectArrayMap(); - Iterator iterator = structures.iterator(); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -index b84e984e53834ef338afd7b61a656eb82a14349a..13270b2197c594dc03d089aea46aa410dd9efd13 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -@@ -115,6 +115,11 @@ public class CraftRegistry implements Registry { - return new CraftRegistry<>(TrimPattern.class, registryHolder.registryOrThrow(Registries.TRIM_PATTERN), CraftTrimPattern::new); - } - // TODO registry modification API -+ // Paper start - remove this after a while along with all ConfiguredStructure stuff -+ if (bukkitClass == io.papermc.paper.world.structure.ConfiguredStructure.class) { -+ return new io.papermc.paper.world.structure.PaperConfiguredStructure.LegacyRegistry(registryHolder.registryOrThrow(Registries.STRUCTURE)); -+ } -+ // Paper end - - return null; - } -diff --git a/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java b/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9178fe0d01b998ca1442bf2511f8fc00db9388ba ---- /dev/null -+++ b/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java -@@ -0,0 +1,96 @@ -+package io.papermc.paper.world.structure; -+ -+import io.papermc.paper.registry.Reference; -+import net.minecraft.core.Registry; -+import net.minecraft.core.registries.Registries; -+import net.minecraft.resources.ResourceKey; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.Bootstrap; -+import net.minecraft.world.level.levelgen.structure.Structure; -+import net.minecraft.world.level.levelgen.structure.BuiltinStructures; -+import org.bukkit.NamespacedKey; -+import org.bukkit.craftbukkit.util.CraftNamespacedKey; -+import org.bukkit.support.AbstractTestingBase; -+import org.junit.jupiter.api.AfterAll; -+import org.junit.jupiter.api.BeforeAll; -+import org.junit.jupiter.api.Test; -+ -+import java.io.PrintStream; -+import java.lang.reflect.Field; -+import java.lang.reflect.Modifier; -+import java.util.LinkedHashMap; -+import java.util.Map; -+import java.util.StringJoiner; -+ -+import static org.junit.jupiter.api.Assertions.assertEquals; -+import static org.junit.jupiter.api.Assertions.assertNotNull; -+import static org.junit.jupiter.api.Assertions.assertTrue; -+ -+@Deprecated(forRemoval = true) -+public class ConfiguredStructureTest extends AbstractTestingBase { -+ -+ private static final Map BUILT_IN_STRUCTURES = new LinkedHashMap<>(); -+ private static final Map> DEFAULT_CONFIGURED_STRUCTURES = new LinkedHashMap<>(); -+ -+ private static PrintStream out; -+ -+ @BeforeAll -+ public static void collectStructures() throws ReflectiveOperationException { -+ out = System.out; -+ System.setOut(Bootstrap.STDOUT); -+ for (Field field : BuiltinStructures.class.getDeclaredFields()) { -+ if (field.getType().equals(ResourceKey.class) && Modifier.isStatic(field.getModifiers())) { -+ BUILT_IN_STRUCTURES.put(((ResourceKey) field.get(null)).location(), field.getName()); -+ } -+ } -+ for (Field field : ConfiguredStructure.class.getDeclaredFields()) { -+ if (field.getType().equals(Reference.class) && Modifier.isStatic(field.getModifiers())) { -+ final Reference ref = (Reference) field.get(null); -+ DEFAULT_CONFIGURED_STRUCTURES.put(ref.getKey(), ref); -+ } -+ } -+ } -+ -+ @Test -+ public void testMinecraftToApi() { -+ Registry structureRegistry = AbstractTestingBase.REGISTRY_CUSTOM.registryOrThrow(Registries.STRUCTURE); -+ assertEquals(BUILT_IN_STRUCTURES.size(), structureRegistry.size(), "configured structure maps should be the same size"); -+ -+ Map missing = new LinkedHashMap<>(); -+ for (Structure feature : structureRegistry) { -+ final ResourceLocation key = structureRegistry.getKey(feature); -+ assertNotNull(key, "Missing built-in registry key"); -+ if (key.equals(BuiltinStructures.ANCIENT_CITY.location()) || key.equals(BuiltinStructures.TRAIL_RUINS.location()) || key.equals(BuiltinStructures.TRIAL_CHAMBERS.location())) { -+ continue; // TODO remove when upstream adds "jigsaw" StructureType -+ } -+ if (DEFAULT_CONFIGURED_STRUCTURES.get(CraftNamespacedKey.fromMinecraft(key)) == null) { -+ missing.put(key, feature); -+ } -+ } -+ -+ assertTrue(missing.isEmpty(), printMissing(missing)); -+ } -+ -+ @Test -+ public void testApiToMinecraft() { -+ Registry structureRegistry = AbstractTestingBase.REGISTRY_CUSTOM.registryOrThrow(Registries.STRUCTURE); -+ for (NamespacedKey apiKey : DEFAULT_CONFIGURED_STRUCTURES.keySet()) { -+ assertTrue(structureRegistry.containsKey(CraftNamespacedKey.toMinecraft(apiKey)), apiKey + " does not have a minecraft counterpart"); -+ } -+ } -+ -+ private static String printMissing(Map missing) { -+ final StringJoiner joiner = new StringJoiner("\n", "Missing: \n", ""); -+ -+ missing.forEach((key, configuredFeature) -> { -+ joiner.add("public static final Reference " + BUILT_IN_STRUCTURES.get(key) + " = create(\"" + key.getPath() + "\");"); -+ }); -+ -+ return joiner.toString(); -+ } -+ -+ @AfterAll -+ public static void after() { -+ System.setOut(out); -+ } -+} -diff --git a/src/test/java/org/bukkit/registry/PerRegistryTest.java b/src/test/java/org/bukkit/registry/PerRegistryTest.java -index 4e4ea083063daf22f1bb785ef212958ea889c43b..1c4966520b6401e6571aa44d5934dfa280bc80e3 100644 ---- a/src/test/java/org/bukkit/registry/PerRegistryTest.java -+++ b/src/test/java/org/bukkit/registry/PerRegistryTest.java -@@ -36,6 +36,7 @@ public class PerRegistryTest extends AbstractTestingBase { - if (!(object instanceof CraftRegistry registry)) { - continue; - } -+ if (object instanceof io.papermc.paper.world.structure.PaperConfiguredStructure.LegacyRegistry) continue; // Paper - skip - - data.add(Arguments.of(registry)); - } catch (ReflectiveOperationException e) { -diff --git a/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java b/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java -index 4adaafafb7140e983a4e90f0ff0deaaf0887a9a5..65cc33c45553e755371ec4313dd38bb61eb7d61c 100644 ---- a/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java -+++ b/src/test/java/org/bukkit/registry/RegistryArgumentAddedTest.java -@@ -23,6 +23,7 @@ public class RegistryArgumentAddedTest extends AbstractTestingBase { - - Set> loadedRegistries = new HashSet<>(DummyServer.registers.keySet()); - Set> notFound = new HashSet<>(); -+ loadedRegistries.remove(io.papermc.paper.world.structure.ConfiguredStructure.class); // Paper - ignore - - RegistriesArgumentProvider - .getData() diff --git a/patches/server/0498-Collision-option-for-requiring-a-player-participant.patch b/patches/server/0498-Collision-option-for-requiring-a-player-participant.patch new file mode 100644 index 000000000000..befdb583618e --- /dev/null +++ b/patches/server/0498-Collision-option-for-requiring-a-player-participant.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 14 Nov 2020 16:48:37 +0100 +Subject: [PATCH] Collision option for requiring a player participant + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 5443895e76c9822a5bdfb20d0364c9492cb8f58c..54283c4534f485a50a0cb5c34518dca5013c894f 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1835,6 +1835,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + public void push(Entity entity) { + if (!this.isPassengerOfSameVehicle(entity)) { + if (!entity.noPhysics && !this.noPhysics) { ++ if (this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper - Collision option for requiring a player participant + double d0 = entity.getX() - this.getX(); + double d1 = entity.getZ() - this.getZ(); + double d2 = Mth.absMax(d0, d1); +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +index be16ab6da56d2aa2a21ee378cfc44dbb14e108b3..9a6b6120c248a57d9dc86ca215146f6de980bd0d 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +@@ -792,6 +792,7 @@ public abstract class AbstractMinecart extends VehicleEntity { + public void push(Entity entity) { + if (!this.level().isClientSide) { + if (!entity.noPhysics && !this.noPhysics) { ++ if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant + if (!this.hasPassenger(entity)) { + // CraftBukkit start + VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity()); +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +index 0178eb918179b12d7d8eb56cd72e5bfc34cfdbaf..1ced6d60a74fac028804b3c2d938e89af4706823 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +@@ -207,6 +207,7 @@ public class Boat extends VehicleEntity implements VariantHolder { + + @Override + public void push(Entity entity) { ++ if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant + if (entity instanceof Boat) { + if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) { + // CraftBukkit start diff --git a/patches/server/0499-Collision-option-for-requiring-a-player-participant.patch b/patches/server/0499-Collision-option-for-requiring-a-player-participant.patch deleted file mode 100644 index b0c31542c165..000000000000 --- a/patches/server/0499-Collision-option-for-requiring-a-player-participant.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Sat, 14 Nov 2020 16:48:37 +0100 -Subject: [PATCH] Collision option for requiring a player participant - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index da5dcea43fdbc0ad0acb1130d363cc8cbea16dfa..4fd7cd7b2dba4047f36a52c510f12d61f281a95f 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1836,6 +1836,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - public void push(Entity entity) { - if (!this.isPassengerOfSameVehicle(entity)) { - if (!entity.noPhysics && !this.noPhysics) { -+ if (this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper - Collision option for requiring a player participant - double d0 = entity.getX() - this.getX(); - double d1 = entity.getZ() - this.getZ(); - double d2 = Mth.absMax(d0, d1); -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -index be16ab6da56d2aa2a21ee378cfc44dbb14e108b3..9a6b6120c248a57d9dc86ca215146f6de980bd0d 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -@@ -792,6 +792,7 @@ public abstract class AbstractMinecart extends VehicleEntity { - public void push(Entity entity) { - if (!this.level().isClientSide) { - if (!entity.noPhysics && !this.noPhysics) { -+ if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant - if (!this.hasPassenger(entity)) { - // CraftBukkit start - VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity()); -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -index 0178eb918179b12d7d8eb56cd72e5bfc34cfdbaf..1ced6d60a74fac028804b3c2d938e89af4706823 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -@@ -207,6 +207,7 @@ public class Boat extends VehicleEntity implements VariantHolder { - - @Override - public void push(Entity entity) { -+ if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant - if (entity instanceof Boat) { - if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) { - // CraftBukkit start diff --git a/patches/server/0500-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch b/patches/server/0499-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch similarity index 100% rename from patches/server/0500-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch rename to patches/server/0499-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch diff --git a/patches/server/0501-Return-chat-component-with-empty-text-instead-of-thr.patch b/patches/server/0500-Return-chat-component-with-empty-text-instead-of-thr.patch similarity index 100% rename from patches/server/0501-Return-chat-component-with-empty-text-instead-of-thr.patch rename to patches/server/0500-Return-chat-component-with-empty-text-instead-of-thr.patch diff --git a/patches/server/0502-Make-schedule-command-per-world.patch b/patches/server/0501-Make-schedule-command-per-world.patch similarity index 100% rename from patches/server/0502-Make-schedule-command-per-world.patch rename to patches/server/0501-Make-schedule-command-per-world.patch diff --git a/patches/server/0503-Configurable-max-leash-distance.patch b/patches/server/0502-Configurable-max-leash-distance.patch similarity index 100% rename from patches/server/0503-Configurable-max-leash-distance.patch rename to patches/server/0502-Configurable-max-leash-distance.patch diff --git a/patches/server/0503-Add-BlockPreDispenseEvent.patch b/patches/server/0503-Add-BlockPreDispenseEvent.patch new file mode 100644 index 000000000000..98d1f8bb39f0 --- /dev/null +++ b/patches/server/0503-Add-BlockPreDispenseEvent.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Madeline Miller +Date: Sun, 17 Jan 2021 13:16:09 +1000 +Subject: [PATCH] Add BlockPreDispenseEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +index 5593a0aa9e618071b6521b213dde0f628348c3dc..644e64850479cea20a98b8a65503ccf3a34fd32a 100644 +--- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +@@ -104,6 +104,7 @@ public class DispenserBlock extends BaseEntityBlock { + DispenseItemBehavior idispensebehavior = this.getDispenseMethod(itemstack); + + if (idispensebehavior != DispenseItemBehavior.NOOP) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent + DispenserBlock.eventFired = false; // CraftBukkit - reset event status + tileentitydispenser.setItem(i, idispensebehavior.dispense(sourceblock, itemstack)); + } +diff --git a/src/main/java/net/minecraft/world/level/block/DropperBlock.java b/src/main/java/net/minecraft/world/level/block/DropperBlock.java +index 1d13f8a1009d6eda351c697052d499d594a6aaa8..9a8a0fb958e8ec782111507bae957f854b2aac72 100644 +--- a/src/main/java/net/minecraft/world/level/block/DropperBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DropperBlock.java +@@ -70,6 +70,7 @@ public class DropperBlock extends DispenserBlock { + ItemStack itemstack1; + + if (iinventory == null) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent + itemstack1 = DropperBlock.DISPENSE_BEHAVIOUR.dispense(sourceblock, itemstack); + } else { + // CraftBukkit start - Fire event when pushing items into other inventories +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 29473d4bd174e8d2e6ee9ecf348edb41af5f6ea3..b3f20ea2a334856200004ed72d709853396fa024 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -2024,5 +2024,11 @@ public class CraftEventFactory { + io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block); + return event.callEvent(); + } ++ ++ public static boolean handleBlockPreDispenseEvent(ServerLevel serverLevel, BlockPos pos, ItemStack itemStack, int slot) { ++ org.bukkit.block.Block block = CraftBlock.at(serverLevel, pos); ++ io.papermc.paper.event.block.BlockPreDispenseEvent event = new io.papermc.paper.event.block.BlockPreDispenseEvent(block, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), slot); ++ return event.callEvent(); ++ } + // Paper end + } diff --git a/patches/server/0504-Add-BlockPreDispenseEvent.patch b/patches/server/0504-Add-BlockPreDispenseEvent.patch deleted file mode 100644 index 3a245d3a0328..000000000000 --- a/patches/server/0504-Add-BlockPreDispenseEvent.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Madeline Miller -Date: Sun, 17 Jan 2021 13:16:09 +1000 -Subject: [PATCH] Add BlockPreDispenseEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -index 5593a0aa9e618071b6521b213dde0f628348c3dc..644e64850479cea20a98b8a65503ccf3a34fd32a 100644 ---- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -@@ -104,6 +104,7 @@ public class DispenserBlock extends BaseEntityBlock { - DispenseItemBehavior idispensebehavior = this.getDispenseMethod(itemstack); - - if (idispensebehavior != DispenseItemBehavior.NOOP) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent - DispenserBlock.eventFired = false; // CraftBukkit - reset event status - tileentitydispenser.setItem(i, idispensebehavior.dispense(sourceblock, itemstack)); - } -diff --git a/src/main/java/net/minecraft/world/level/block/DropperBlock.java b/src/main/java/net/minecraft/world/level/block/DropperBlock.java -index 1d13f8a1009d6eda351c697052d499d594a6aaa8..9a8a0fb958e8ec782111507bae957f854b2aac72 100644 ---- a/src/main/java/net/minecraft/world/level/block/DropperBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DropperBlock.java -@@ -70,6 +70,7 @@ public class DropperBlock extends DispenserBlock { - ItemStack itemstack1; - - if (iinventory == null) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent - itemstack1 = DropperBlock.DISPENSE_BEHAVIOUR.dispense(sourceblock, itemstack); - } else { - // CraftBukkit start - Fire event when pushing items into other inventories -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 6a95328293e3600b7a560074a0e6083db9cd3e1f..456c1df6b5956b521e8f379b9020ed53f66a365b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -2079,5 +2079,11 @@ public class CraftEventFactory { - io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block); - return event.callEvent(); - } -+ -+ public static boolean handleBlockPreDispenseEvent(ServerLevel serverLevel, BlockPos pos, ItemStack itemStack, int slot) { -+ org.bukkit.block.Block block = CraftBlock.at(serverLevel, pos); -+ io.papermc.paper.event.block.BlockPreDispenseEvent event = new io.papermc.paper.event.block.BlockPreDispenseEvent(block, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), slot); -+ return event.callEvent(); -+ } - // Paper end - } diff --git a/patches/server/0505-Add-PlayerChangeBeaconEffectEvent.patch b/patches/server/0504-Add-PlayerChangeBeaconEffectEvent.patch similarity index 100% rename from patches/server/0505-Add-PlayerChangeBeaconEffectEvent.patch rename to patches/server/0504-Add-PlayerChangeBeaconEffectEvent.patch diff --git a/patches/server/0505-Add-toggle-for-always-placing-the-dragon-egg.patch b/patches/server/0505-Add-toggle-for-always-placing-the-dragon-egg.patch new file mode 100644 index 000000000000..25361d7e14e6 --- /dev/null +++ b/patches/server/0505-Add-toggle-for-always-placing-the-dragon-egg.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 26 Nov 2020 11:47:24 +0000 +Subject: [PATCH] Add toggle for always placing the dragon egg + + +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 46f286a68d04aced44acbb97041a74e2668c13d8..957af0553d4b794a1b26a6591dcc0165b0509c6a 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 +@@ -407,7 +407,7 @@ public class EndDragonFight { + this.dragonEvent.setVisible(false); + this.spawnExitPortal(true); + this.spawnNewGateway(); +- if (!this.previouslyKilled) { ++ if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - Add toggle for always placing the dragon egg + this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); + } + diff --git a/patches/server/0507-Add-PlayerStonecutterRecipeSelectEvent.patch b/patches/server/0506-Add-PlayerStonecutterRecipeSelectEvent.patch similarity index 100% rename from patches/server/0507-Add-PlayerStonecutterRecipeSelectEvent.patch rename to patches/server/0506-Add-PlayerStonecutterRecipeSelectEvent.patch diff --git a/patches/server/0506-Add-toggle-for-always-placing-the-dragon-egg.patch b/patches/server/0506-Add-toggle-for-always-placing-the-dragon-egg.patch deleted file mode 100644 index b5bb0c62686a..000000000000 --- a/patches/server/0506-Add-toggle-for-always-placing-the-dragon-egg.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Thu, 26 Nov 2020 11:47:24 +0000 -Subject: [PATCH] Add toggle for always placing the dragon egg - - -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 65fb65a9a6037b007c3659f1d1a32ef31097824a..87a8a888536203070bcecc0f477e92e666df2c2a 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 -@@ -407,7 +407,7 @@ public class EndDragonFight { - this.dragonEvent.setVisible(false); - this.spawnExitPortal(true); - this.spawnNewGateway(); -- if (!this.previouslyKilled) { -+ if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - Add toggle for always placing the dragon egg - this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); - } - diff --git a/patches/server/0507-Expand-EntityUnleashEvent.patch b/patches/server/0507-Expand-EntityUnleashEvent.patch new file mode 100644 index 000000000000..6f5459abacc7 --- /dev/null +++ b/patches/server/0507-Expand-EntityUnleashEvent.patch @@ -0,0 +1,140 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Fri, 29 Jan 2021 15:13:11 +0100 +Subject: [PATCH] Expand EntityUnleashEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 645fb2ec7d969068eb10d59d43a512c74cca5a58..8b239769a3a7ce6f85d472ddb2ff7ea7de0ce5c0 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1299,12 +1299,15 @@ public abstract class Mob extends LivingEntity implements Targeting { + return InteractionResult.PASS; + } else if (this.getLeashHolder() == player) { + // CraftBukkit start - fire PlayerUnleashEntityEvent +- if (CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand).isCancelled()) { ++ // Paper start - Expand EntityUnleashEvent ++ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.getAbilities().instabuild); ++ if (event.isCancelled()) { ++ // Paper end - Expand EntityUnleashEvent + ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, this.getLeashHolder())); + return InteractionResult.PASS; + } + // CraftBukkit end +- this.dropLeash(true, !player.getAbilities().instabuild); ++ this.dropLeash(true, event.isDropLeash()); // Paper - Expand EntityUnleashEvent + this.gameEvent(GameEvent.ENTITY_INTERACT, player); + return InteractionResult.sidedSuccess(this.level().isClientSide); + } else { +@@ -1472,8 +1475,11 @@ public abstract class Mob extends LivingEntity implements Targeting { + + if (this.leashHolder != null) { + if (!this.isAlive() || !this.leashHolder.isAlive()) { +- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE)); // CraftBukkit +- this.dropLeash(true, !this.leashHolder.pluginRemoved);// CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin ++ // Paper start - Expand EntityUnleashEvent ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? EntityUnleashEvent.UnleashReason.PLAYER_UNLEASH : EntityUnleashEvent.UnleashReason.HOLDER_GONE, !this.leashHolder.pluginRemoved); ++ this.level().getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end - Expand EntityUnleashEvent + } + + } +@@ -1536,8 +1542,11 @@ public abstract class Mob extends LivingEntity implements Targeting { + boolean flag1 = super.startRiding(entity, force); + + if (flag1 && this.isLeashed()) { +- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit +- this.dropLeash(true, true); ++ // Paper start - Expand EntityUnleashEvent ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN, true); ++ if (!event.callEvent()) { return flag1; } ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end - Expand EntityUnleashEvent + } + + return flag1; +@@ -1727,8 +1736,11 @@ public abstract class Mob extends LivingEntity implements Targeting { + @Override + protected void removeAfterChangingDimensions() { + super.removeAfterChangingDimensions(); +- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit +- this.dropLeash(true, false); ++ // Paper start - Expand EntityUnleashEvent ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN, false); ++ this.level().getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end - Expand EntityUnleashEvent + this.getAllSlots().forEach((itemstack) -> { + if (!itemstack.isEmpty()) { + itemstack.setCount(0); +diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +index 85a9bcbd229b56317c2de15670a04c6d0eb51e18..d6393210cfee53685f83c8491bea8b9c13b01eea 100644 +--- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java ++++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +@@ -56,8 +56,11 @@ public abstract class PathfinderMob extends Mob { + + if (this instanceof TamableAnimal && ((TamableAnimal) this).isInSittingPose()) { + if (f > entity.level().paperConfig().misc.maxLeashDistance) { // Paper - Configurable max leash distance +- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit +- this.dropLeash(true, true); ++ // Paper start - Expand EntityUnleashEvent ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); ++ if (!event.callEvent()) return; ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end - Expand EntityUnleashEvent + } + + return; +@@ -65,8 +68,11 @@ public abstract class PathfinderMob extends Mob { + + this.onLeashDistance(f); + if (f > entity.level().paperConfig().misc.maxLeashDistance) { // Paper - Configurable max leash distance +- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit +- this.dropLeash(true, true); ++ // Paper start - Expand EntityUnleashEvent ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); ++ if (!event.callEvent()) return; ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end - Expand EntityUnleashEvent + this.goalSelector.disableControlFlag(Goal.Flag.MOVE); + } else if (f > 6.0F) { + double d0 = (entity.getX() - this.getX()) / (double) f; +diff --git a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java +index 16784fcc853e23689a854e7dc6c03ed8182a164e..006aba8bbb34a0d45ef626a1d299e81909cf9ba1 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java +@@ -126,11 +126,14 @@ public class LeashFenceKnotEntity extends HangingEntity { + + if (entityinsentient1.isLeashed() && entityinsentient1.getLeashHolder() == this) { + // CraftBukkit start +- if (CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient1, player, hand).isCancelled()) { ++ // Paper start - Expand EntityUnleashEvent ++ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient1, player, hand, !player.getAbilities().instabuild); ++ if (event.isCancelled()) { ++ // Paper end - Expand EntityUnleashEvent + die = false; + continue; + } +- entityinsentient1.dropLeash(true, !player.getAbilities().instabuild); // false -> survival mode boolean ++ entityinsentient1.dropLeash(true, event.isDropLeash()); // false -> survival mode boolean // Paper - Expand EntityUnleashEvent + // CraftBukkit end + flag1 = true; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index b3f20ea2a334856200004ed72d709853396fa024..75b0a6327d8fbf82ac816eae4fdf4f922a0f3113 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1568,8 +1568,10 @@ public class CraftEventFactory { + Bukkit.getPluginManager().callEvent(new PlayerRecipeBookSettingsChangeEvent(player.getBukkitEntity(), bukkitType, open, filter)); + } + +- public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Mob entity, net.minecraft.world.entity.player.Player player, InteractionHand enumhand) { +- PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), CraftEquipmentSlot.getHand(enumhand)); ++ // Paper start - Expand EntityUnleashEvent ++ public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Mob entity, net.minecraft.world.entity.player.Player player, InteractionHand enumhand, boolean dropLeash) { ++ PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), CraftEquipmentSlot.getHand(enumhand), dropLeash); ++ // Paper end - Expand EntityUnleashEvent + entity.level().getCraftServer().getPluginManager().callEvent(event); + return event; + } diff --git a/patches/server/0508-Expand-EntityUnleashEvent.patch b/patches/server/0508-Expand-EntityUnleashEvent.patch deleted file mode 100644 index fa402496d96c..000000000000 --- a/patches/server/0508-Expand-EntityUnleashEvent.patch +++ /dev/null @@ -1,140 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Fri, 29 Jan 2021 15:13:11 +0100 -Subject: [PATCH] Expand EntityUnleashEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 236b10b86cdf6e0a723d8c9f199dde9cc983198e..a05914c6d86ad898d03641b6e8a44f7a1f1161dd 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1299,12 +1299,15 @@ public abstract class Mob extends LivingEntity implements Targeting { - return InteractionResult.PASS; - } else if (this.getLeashHolder() == player) { - // CraftBukkit start - fire PlayerUnleashEntityEvent -- if (CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand).isCancelled()) { -+ // Paper start - Expand EntityUnleashEvent -+ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.getAbilities().instabuild); -+ if (event.isCancelled()) { -+ // Paper end - Expand EntityUnleashEvent - ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, this.getLeashHolder())); - return InteractionResult.PASS; - } - // CraftBukkit end -- this.dropLeash(true, !player.getAbilities().instabuild); -+ this.dropLeash(true, event.isDropLeash()); // Paper - Expand EntityUnleashEvent - this.gameEvent(GameEvent.ENTITY_INTERACT, player); - return InteractionResult.sidedSuccess(this.level().isClientSide); - } else { -@@ -1472,8 +1475,11 @@ public abstract class Mob extends LivingEntity implements Targeting { - - if (this.leashHolder != null) { - if (!this.isAlive() || !this.leashHolder.isAlive()) { -- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE)); // CraftBukkit -- this.dropLeash(true, !this.leashHolder.pluginRemoved);// CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin -+ // Paper start - Expand EntityUnleashEvent -+ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? EntityUnleashEvent.UnleashReason.PLAYER_UNLEASH : EntityUnleashEvent.UnleashReason.HOLDER_GONE, !this.leashHolder.pluginRemoved); -+ this.level().getCraftServer().getPluginManager().callEvent(event); // CraftBukkit -+ this.dropLeash(true, event.isDropLeash()); -+ // Paper end - Expand EntityUnleashEvent - } - - } -@@ -1536,8 +1542,11 @@ public abstract class Mob extends LivingEntity implements Targeting { - boolean flag1 = super.startRiding(entity, force); - - if (flag1 && this.isLeashed()) { -- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit -- this.dropLeash(true, true); -+ // Paper start - Expand EntityUnleashEvent -+ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN, true); -+ if (!event.callEvent()) { return flag1; } -+ this.dropLeash(true, event.isDropLeash()); -+ // Paper end - Expand EntityUnleashEvent - } - - return flag1; -@@ -1727,8 +1736,11 @@ public abstract class Mob extends LivingEntity implements Targeting { - @Override - protected void removeAfterChangingDimensions() { - super.removeAfterChangingDimensions(); -- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit -- this.dropLeash(true, false); -+ // Paper start - Expand EntityUnleashEvent -+ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN, false); -+ this.level().getCraftServer().getPluginManager().callEvent(event); // CraftBukkit -+ this.dropLeash(true, event.isDropLeash()); -+ // Paper end - Expand EntityUnleashEvent - this.getAllSlots().forEach((itemstack) -> { - if (!itemstack.isEmpty()) { - itemstack.setCount(0); -diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java -index 85a9bcbd229b56317c2de15670a04c6d0eb51e18..d6393210cfee53685f83c8491bea8b9c13b01eea 100644 ---- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java -+++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java -@@ -56,8 +56,11 @@ public abstract class PathfinderMob extends Mob { - - if (this instanceof TamableAnimal && ((TamableAnimal) this).isInSittingPose()) { - if (f > entity.level().paperConfig().misc.maxLeashDistance) { // Paper - Configurable max leash distance -- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit -- this.dropLeash(true, true); -+ // Paper start - Expand EntityUnleashEvent -+ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); -+ if (!event.callEvent()) return; -+ this.dropLeash(true, event.isDropLeash()); -+ // Paper end - Expand EntityUnleashEvent - } - - return; -@@ -65,8 +68,11 @@ public abstract class PathfinderMob extends Mob { - - this.onLeashDistance(f); - if (f > entity.level().paperConfig().misc.maxLeashDistance) { // Paper - Configurable max leash distance -- this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit -- this.dropLeash(true, true); -+ // Paper start - Expand EntityUnleashEvent -+ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); -+ if (!event.callEvent()) return; -+ this.dropLeash(true, event.isDropLeash()); -+ // Paper end - Expand EntityUnleashEvent - this.goalSelector.disableControlFlag(Goal.Flag.MOVE); - } else if (f > 6.0F) { - double d0 = (entity.getX() - this.getX()) / (double) f; -diff --git a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java -index 16784fcc853e23689a854e7dc6c03ed8182a164e..006aba8bbb34a0d45ef626a1d299e81909cf9ba1 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java -@@ -126,11 +126,14 @@ public class LeashFenceKnotEntity extends HangingEntity { - - if (entityinsentient1.isLeashed() && entityinsentient1.getLeashHolder() == this) { - // CraftBukkit start -- if (CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient1, player, hand).isCancelled()) { -+ // Paper start - Expand EntityUnleashEvent -+ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient1, player, hand, !player.getAbilities().instabuild); -+ if (event.isCancelled()) { -+ // Paper end - Expand EntityUnleashEvent - die = false; - continue; - } -- entityinsentient1.dropLeash(true, !player.getAbilities().instabuild); // false -> survival mode boolean -+ entityinsentient1.dropLeash(true, event.isDropLeash()); // false -> survival mode boolean // Paper - Expand EntityUnleashEvent - // CraftBukkit end - flag1 = true; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 456c1df6b5956b521e8f379b9020ed53f66a365b..7763486f60057ae88649c2692908b4d1cfdac6ab 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1637,8 +1637,10 @@ public class CraftEventFactory { - Bukkit.getPluginManager().callEvent(new PlayerRecipeBookSettingsChangeEvent(player.getBukkitEntity(), bukkitType, open, filter)); - } - -- public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Mob entity, net.minecraft.world.entity.player.Player player, InteractionHand enumhand) { -- PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), CraftEquipmentSlot.getHand(enumhand)); -+ // Paper start - Expand EntityUnleashEvent -+ public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Mob entity, net.minecraft.world.entity.player.Player player, InteractionHand enumhand, boolean dropLeash) { -+ PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), CraftEquipmentSlot.getHand(enumhand), dropLeash); -+ // Paper end - Expand EntityUnleashEvent - entity.level().getCraftServer().getPluginManager().callEvent(event); - return event; - } diff --git a/patches/server/0508-Reset-shield-blocking-on-dimension-change.patch b/patches/server/0508-Reset-shield-blocking-on-dimension-change.patch new file mode 100644 index 000000000000..b59a53ac0f9b --- /dev/null +++ b/patches/server/0508-Reset-shield-blocking-on-dimension-change.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yive +Date: Sun, 24 Jan 2021 08:55:19 -0800 +Subject: [PATCH] Reset shield blocking on dimension change + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 6e62c5037a69468e7b4c8115a6623d48538de307..8c65b05e66372cf3c92f823d72e94c18fe77622b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1198,6 +1198,11 @@ public class ServerPlayer extends Player { + this.level().getCraftServer().getPluginManager().callEvent(changeEvent); + // CraftBukkit end + } ++ // Paper start - Reset shield blocking on dimension change ++ if (this.isBlocking()) { ++ this.stopUsingItem(); ++ } ++ // Paper end - Reset shield blocking on dimension change + + return this; + } diff --git a/patches/server/0509-Add-DragonEggFormEvent.patch b/patches/server/0509-Add-DragonEggFormEvent.patch new file mode 100644 index 000000000000..f90545d9df9f --- /dev/null +++ b/patches/server/0509-Add-DragonEggFormEvent.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Mon, 25 Jan 2021 14:53:57 +0100 +Subject: [PATCH] Add DragonEggFormEvent + + +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 957af0553d4b794a1b26a6591dcc0165b0509c6a..1d5edcad4c5bfe48711cfce7c46a9c4606196ae3 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 +@@ -407,8 +407,22 @@ public class EndDragonFight { + this.dragonEvent.setVisible(false); + this.spawnExitPortal(true); + this.spawnNewGateway(); ++ // Paper start - Add DragonEggFormEvent ++ BlockPos eggPosition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)); ++ org.bukkit.craftbukkit.block.CraftBlockState eggState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.level, eggPosition); ++ eggState.setData(Blocks.DRAGON_EGG.defaultBlockState()); ++ io.papermc.paper.event.block.DragonEggFormEvent eggEvent = new io.papermc.paper.event.block.DragonEggFormEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this.level, eggPosition), eggState, ++ new org.bukkit.craftbukkit.boss.CraftDragonBattle(this)); ++ // Paper end - Add DragonEggFormEvent + if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - Add toggle for always placing the dragon egg +- this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); ++ // Paper start - Add DragonEggFormEvent ++ // this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); ++ } else { ++ eggEvent.setCancelled(true); ++ } ++ if (eggEvent.callEvent()) { ++ eggEvent.getNewState().update(true); ++ // Paper end - Add DragonEggFormEvent + } + + this.previouslyKilled = true; diff --git a/patches/server/0509-Reset-shield-blocking-on-dimension-change.patch b/patches/server/0509-Reset-shield-blocking-on-dimension-change.patch deleted file mode 100644 index 4d81ca2b6d65..000000000000 --- a/patches/server/0509-Reset-shield-blocking-on-dimension-change.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Yive -Date: Sun, 24 Jan 2021 08:55:19 -0800 -Subject: [PATCH] Reset shield blocking on dimension change - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index effafbcd8400cc40956d9cf36757e83f7f803038..5a26a7d6e052c0533f73b1930da6c801f23cb521 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1198,6 +1198,11 @@ public class ServerPlayer extends Player { - this.level().getCraftServer().getPluginManager().callEvent(changeEvent); - // CraftBukkit end - } -+ // Paper start - Reset shield blocking on dimension change -+ if (this.isBlocking()) { -+ this.stopUsingItem(); -+ } -+ // Paper end - Reset shield blocking on dimension change - - return this; - } diff --git a/patches/server/0510-Add-DragonEggFormEvent.patch b/patches/server/0510-Add-DragonEggFormEvent.patch deleted file mode 100644 index c55507f8cd8e..000000000000 --- a/patches/server/0510-Add-DragonEggFormEvent.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Trigary -Date: Mon, 25 Jan 2021 14:53:57 +0100 -Subject: [PATCH] Add DragonEggFormEvent - - -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 87a8a888536203070bcecc0f477e92e666df2c2a..5b14d63e7c354cd51d67ddc045cc86a0f7b36811 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 -@@ -407,8 +407,22 @@ public class EndDragonFight { - this.dragonEvent.setVisible(false); - this.spawnExitPortal(true); - this.spawnNewGateway(); -+ // Paper start - Add DragonEggFormEvent -+ BlockPos eggPosition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)); -+ org.bukkit.craftbukkit.block.CraftBlockState eggState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.level, eggPosition); -+ eggState.setData(Blocks.DRAGON_EGG.defaultBlockState()); -+ io.papermc.paper.event.block.DragonEggFormEvent eggEvent = new io.papermc.paper.event.block.DragonEggFormEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this.level, eggPosition), eggState, -+ new org.bukkit.craftbukkit.boss.CraftDragonBattle(this)); -+ // Paper end - Add DragonEggFormEvent - if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - Add toggle for always placing the dragon egg -- this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); -+ // Paper start - Add DragonEggFormEvent -+ // this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.origin)), Blocks.DRAGON_EGG.defaultBlockState()); -+ } else { -+ eggEvent.setCancelled(true); -+ } -+ if (eggEvent.callEvent()) { -+ eggEvent.getNewState().update(true); -+ // Paper end - Add DragonEggFormEvent - } - - this.previouslyKilled = true; diff --git a/patches/server/0510-Add-EntityMoveEvent.patch b/patches/server/0510-Add-EntityMoveEvent.patch new file mode 100644 index 000000000000..567b6fa9e2df --- /dev/null +++ b/patches/server/0510-Add-EntityMoveEvent.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 11 Feb 2020 21:56:48 -0600 +Subject: [PATCH] Add EntityMoveEvent + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 06b7816bafa3ac1093b796ca6e7bb3462df8bfec..3f061e1135bac08e6ea1530407ff2667904339a5 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1526,6 +1526,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent ++ worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent + + this.profiler.push(() -> { + return worldserver + " " + worldserver.dimension().location(); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index b93976abbc98c1beffe4b464735c9b5a71cb45a4..f75722633789155af38184982ce6459a6ff1178e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -222,6 +222,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + public final LevelStorageSource.LevelStorageAccess convertable; + public final UUID uuid; + public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent ++ public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent + + public LevelChunk getChunkIfLoaded(int x, int z) { + return this.chunkSource.getChunk(x, z, false); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index ec47dc4cb19e742b033f98706b52619483a8bec0..2dddf7a6f3bab4acf0c9d02a35a0cedb27154dea 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3374,6 +3374,20 @@ public abstract class LivingEntity extends Entity implements Attackable { + + this.pushEntities(); + this.level().getProfiler().pop(); ++ // Paper start - Add EntityMoveEvent ++ if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { ++ if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { ++ Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); ++ Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); ++ io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone()); ++ if (!event.callEvent()) { ++ this.absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); ++ } else if (!to.equals(event.getTo())) { ++ this.absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); ++ } ++ } ++ } ++ // Paper end - Add EntityMoveEvent + if (!this.level().isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { + this.hurt(this.damageSources().drown(), 1.0F); + } diff --git a/patches/server/0511-Add-EntityMoveEvent.patch b/patches/server/0511-Add-EntityMoveEvent.patch deleted file mode 100644 index d6955d442c7a..000000000000 --- a/patches/server/0511-Add-EntityMoveEvent.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Tue, 11 Feb 2020 21:56:48 -0600 -Subject: [PATCH] Add EntityMoveEvent - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 06b7816bafa3ac1093b796ca6e7bb3462df8bfec..3f061e1135bac08e6ea1530407ff2667904339a5 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1526,6 +1526,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent -+ worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent - - this.profiler.push(() -> { - return worldserver + " " + worldserver.dimension().location(); -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index b93976abbc98c1beffe4b464735c9b5a71cb45a4..f75722633789155af38184982ce6459a6ff1178e 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -222,6 +222,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - public final LevelStorageSource.LevelStorageAccess convertable; - public final UUID uuid; - public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent -+ public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent - - public LevelChunk getChunkIfLoaded(int x, int z) { - return this.chunkSource.getChunk(x, z, false); -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 6150f5c17ba80cb7acd5b114d9dec79e6008e89a..fced5d9a6d60bd673f44c0754725831b8510b95a 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3361,6 +3361,20 @@ public abstract class LivingEntity extends Entity implements Attackable { - - this.pushEntities(); - this.level().getProfiler().pop(); -+ // Paper start - Add EntityMoveEvent -+ if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { -+ if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { -+ Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); -+ Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); -+ io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone()); -+ if (!event.callEvent()) { -+ this.absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); -+ } else if (!to.equals(event.getTo())) { -+ this.absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); -+ } -+ } -+ } -+ // Paper end - Add EntityMoveEvent - if (!this.level().isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { - this.hurt(this.damageSources().drown(), 1.0F); - } diff --git a/patches/server/0512-added-option-to-disable-pathfinding-updates-on-block.patch b/patches/server/0511-added-option-to-disable-pathfinding-updates-on-block.patch similarity index 100% rename from patches/server/0512-added-option-to-disable-pathfinding-updates-on-block.patch rename to patches/server/0511-added-option-to-disable-pathfinding-updates-on-block.patch diff --git a/patches/server/0513-Inline-shift-direction-fields.patch b/patches/server/0512-Inline-shift-direction-fields.patch similarity index 100% rename from patches/server/0513-Inline-shift-direction-fields.patch rename to patches/server/0512-Inline-shift-direction-fields.patch diff --git a/patches/server/0513-Allow-adding-items-to-BlockDropItemEvent.patch b/patches/server/0513-Allow-adding-items-to-BlockDropItemEvent.patch new file mode 100644 index 000000000000..ac583d715bac --- /dev/null +++ b/patches/server/0513-Allow-adding-items-to-BlockDropItemEvent.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Wed, 20 Jan 2021 14:23:37 -0600 +Subject: [PATCH] Allow adding items to BlockDropItemEvent + + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 75b0a6327d8fbf82ac816eae4fdf4f922a0f3113..5112da69c528be09c2b5d5bcac70fce0fb0054a1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -450,13 +450,30 @@ public class CraftEventFactory { + } + + public static void handleBlockDropItemEvent(Block block, BlockState state, ServerPlayer player, List items) { +- BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), Lists.transform(items, (item) -> (org.bukkit.entity.Item) item.getBukkitEntity())); ++ // Paper start - Allow adding items to BlockDropItemEvent ++ List list = new ArrayList<>(); ++ for (ItemEntity item : items) { ++ list.add((Item) item.getBukkitEntity()); ++ } ++ BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), list); ++ // Paper end - Allow adding items to BlockDropItemEvent + Bukkit.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +- for (ItemEntity item : items) { +- item.level().addFreshEntity(item); ++ // Paper start - Allow adding items to BlockDropItemEvent ++ for (Item bukkit : list) { ++ if (!bukkit.isValid()) { ++ Entity item = ((org.bukkit.craftbukkit.entity.CraftItem) bukkit).getHandle(); ++ item.level().addFreshEntity(item); ++ } ++ } ++ } else { ++ for (Item bukkit : list) { ++ if (bukkit.isValid()) { ++ bukkit.remove(); ++ } + } ++ // Paper end - Allow adding items to BlockDropItemEvent + } + } + diff --git a/patches/server/0515-Add-getMainThreadExecutor-to-BukkitScheduler.patch b/patches/server/0514-Add-getMainThreadExecutor-to-BukkitScheduler.patch similarity index 100% rename from patches/server/0515-Add-getMainThreadExecutor-to-BukkitScheduler.patch rename to patches/server/0514-Add-getMainThreadExecutor-to-BukkitScheduler.patch diff --git a/patches/server/0514-Allow-adding-items-to-BlockDropItemEvent.patch b/patches/server/0514-Allow-adding-items-to-BlockDropItemEvent.patch deleted file mode 100644 index 4f2a76f71189..000000000000 --- a/patches/server/0514-Allow-adding-items-to-BlockDropItemEvent.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Wed, 20 Jan 2021 14:23:37 -0600 -Subject: [PATCH] Allow adding items to BlockDropItemEvent - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 54b0e6e6c21c02bd6a2a702f6b6416d573f62f9c..166035d8f708b94a9563c3802418ab6200071bc6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -450,13 +450,30 @@ public class CraftEventFactory { - } - - public static void handleBlockDropItemEvent(Block block, BlockState state, ServerPlayer player, List items) { -- BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), Lists.transform(items, (item) -> (org.bukkit.entity.Item) item.getBukkitEntity())); -+ // Paper start - Allow adding items to BlockDropItemEvent -+ List list = new ArrayList<>(); -+ for (ItemEntity item : items) { -+ list.add((Item) item.getBukkitEntity()); -+ } -+ BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), list); -+ // Paper end - Allow adding items to BlockDropItemEvent - Bukkit.getPluginManager().callEvent(event); - - if (!event.isCancelled()) { -- for (ItemEntity item : items) { -- item.level().addFreshEntity(item); -+ // Paper start - Allow adding items to BlockDropItemEvent -+ for (Item bukkit : list) { -+ if (!bukkit.isValid()) { -+ Entity item = ((org.bukkit.craftbukkit.entity.CraftItem) bukkit).getHandle(); -+ item.level().addFreshEntity(item); -+ } -+ } -+ } else { -+ for (Item bukkit : list) { -+ if (bukkit.isValid()) { -+ bukkit.remove(); -+ } - } -+ // Paper end - Allow adding items to BlockDropItemEvent - } - } - diff --git a/patches/server/0515-living-entity-allow-attribute-registration.patch b/patches/server/0515-living-entity-allow-attribute-registration.patch new file mode 100644 index 000000000000..cc5fbf104d5a --- /dev/null +++ b/patches/server/0515-living-entity-allow-attribute-registration.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ysl3000 +Date: Sat, 24 Oct 2020 16:37:44 +0200 +Subject: [PATCH] living entity allow attribute registration + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +index d5dfb08f550f5644ff5164170d6c16a3b25a3748..897d7632ecfea40890433474870dd7a5e534d8ab 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +@@ -152,4 +152,12 @@ public class AttributeMap { + } + + } ++ ++ // Paper - start - living entity allow attribute registration ++ public void registerAttribute(Attribute attributeBase) { ++ AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute); ++ attributes.put(attributeBase, attributeModifiable); ++ } ++ // Paper - end - living entity allow attribute registration ++ + } +diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +index ea48f1119a940056c37d1d203437bfbfdf13663b..8a678df56fcf30535957e111d81ad07be5b501ec 100644 +--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +@@ -35,4 +35,11 @@ public class CraftAttributeMap implements Attributable { + + return (nms == null) ? null : new CraftAttributeInstance(nms, attribute); + } ++ // Paper start - living entity allow attribute registration ++ @Override ++ public void registerAttribute(Attribute attribute) { ++ Preconditions.checkArgument(attribute != null, "attribute"); ++ handle.registerAttribute(CraftAttribute.bukkitToMinecraft(attribute)); ++ } ++ // Paper end - living entity allow attribute registration + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 06f25a9453bcc8f304cc83b599f8a54112a6ed01..72d9a9696c95374bed29e2e453c7750d0cf06170 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -739,6 +739,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return this.getHandle().craftAttributes.getAttribute(attribute); + } + ++ // Paper start - living entity allow attribute registration ++ @Override ++ public void registerAttribute(Attribute attribute) { ++ getHandle().craftAttributes.registerAttribute(attribute); ++ } ++ // Paper end - living entity allow attribute registration ++ + @Override + public void setAI(boolean ai) { + if (this.getHandle() instanceof Mob) { diff --git a/patches/server/0517-fix-dead-slime-setSize-invincibility.patch b/patches/server/0516-fix-dead-slime-setSize-invincibility.patch similarity index 100% rename from patches/server/0517-fix-dead-slime-setSize-invincibility.patch rename to patches/server/0516-fix-dead-slime-setSize-invincibility.patch diff --git a/patches/server/0516-living-entity-allow-attribute-registration.patch b/patches/server/0516-living-entity-allow-attribute-registration.patch deleted file mode 100644 index 9d1df1faf3e6..000000000000 --- a/patches/server/0516-living-entity-allow-attribute-registration.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: ysl3000 -Date: Sat, 24 Oct 2020 16:37:44 +0200 -Subject: [PATCH] living entity allow attribute registration - - -diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index d5dfb08f550f5644ff5164170d6c16a3b25a3748..897d7632ecfea40890433474870dd7a5e534d8ab 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -152,4 +152,12 @@ public class AttributeMap { - } - - } -+ -+ // Paper - start - living entity allow attribute registration -+ public void registerAttribute(Attribute attributeBase) { -+ AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute); -+ attributes.put(attributeBase, attributeModifiable); -+ } -+ // Paper - end - living entity allow attribute registration -+ - } -diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -index ea48f1119a940056c37d1d203437bfbfdf13663b..8a678df56fcf30535957e111d81ad07be5b501ec 100644 ---- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -+++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -@@ -35,4 +35,11 @@ public class CraftAttributeMap implements Attributable { - - return (nms == null) ? null : new CraftAttributeInstance(nms, attribute); - } -+ // Paper start - living entity allow attribute registration -+ @Override -+ public void registerAttribute(Attribute attribute) { -+ Preconditions.checkArgument(attribute != null, "attribute"); -+ handle.registerAttribute(CraftAttribute.bukkitToMinecraft(attribute)); -+ } -+ // Paper end - living entity allow attribute registration - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 30934acb059016a996b2c3b2f635e606d4e8a526..7d8d503b46b1380e4b8a52d76fc5cc55759de80b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -726,6 +726,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return this.getHandle().craftAttributes.getAttribute(attribute); - } - -+ // Paper start - living entity allow attribute registration -+ @Override -+ public void registerAttribute(Attribute attribute) { -+ getHandle().craftAttributes.registerAttribute(attribute); -+ } -+ // Paper end - living entity allow attribute registration -+ - @Override - public void setAI(boolean ai) { - if (this.getHandle() instanceof Mob) { diff --git a/patches/server/0518-Merchant-getRecipes-should-return-an-immutable-list.patch b/patches/server/0517-Merchant-getRecipes-should-return-an-immutable-list.patch similarity index 100% rename from patches/server/0518-Merchant-getRecipes-should-return-an-immutable-list.patch rename to patches/server/0517-Merchant-getRecipes-should-return-an-immutable-list.patch diff --git a/patches/server/0519-Expose-Tracked-Players.patch b/patches/server/0518-Expose-Tracked-Players.patch similarity index 100% rename from patches/server/0519-Expose-Tracked-Players.patch rename to patches/server/0518-Expose-Tracked-Players.patch diff --git a/patches/server/0520-Improve-ServerGUI.patch b/patches/server/0519-Improve-ServerGUI.patch similarity index 100% rename from patches/server/0520-Improve-ServerGUI.patch rename to patches/server/0519-Improve-ServerGUI.patch diff --git a/patches/server/0520-fix-converting-txt-to-json-file.patch b/patches/server/0520-fix-converting-txt-to-json-file.patch new file mode 100644 index 000000000000..6b167b94ba16 --- /dev/null +++ b/patches/server/0520-fix-converting-txt-to-json-file.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 4 Jan 2021 19:49:15 -0800 +Subject: [PATCH] fix converting txt to json file + + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java +index a20d47f54f12dfc0a5f76dd969238e34c958b618..1c9cf5e1c4ee05724ffcdbd77a19bca1ab2be4d3 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java +@@ -18,6 +18,11 @@ public class DedicatedPlayerList extends PlayerList { + this.setViewDistance(dedicatedServerProperties.viewDistance); + this.setSimulationDistance(dedicatedServerProperties.simulationDistance); + super.setUsingWhiteList(dedicatedServerProperties.whiteList.get()); ++ // Paper start - fix converting txt to json file; moved from constructor ++ } ++ @Override ++ public void loadAndSaveFiles() { ++ // Paper end - fix converting txt to json file + this.loadUserBanList(); + this.saveUserBanList(); + this.loadIpBanList(); +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index ebea8a827aad108dd6d4222e8dfd251d2cea657a..2a0d3212edeee828b9fe04c153ee05ea5b0875ec 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -199,6 +199,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess()); + this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); + // Paper end - initialize global and world-defaults configuration ++ // Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save ++ if (this.convertOldUsers()) { ++ this.getProfileCache().save(false); // Paper ++ } ++ this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames ++ // Paper end - fix converting txt to json file + org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread + io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics +@@ -253,9 +259,6 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); + } + +- if (this.convertOldUsers()) { +- this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving +- } + + if (!OldUsersConverter.serverReadyAfterUserconversion(this)) { + return false; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index f336b52a529c3c0ddccb36ace8b441fba61b99dd..41c682c24b2f984e6cd0cc63eed5acc09f00d649 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -176,6 +176,7 @@ public abstract class PlayerList { + this.maxPlayers = maxPlayers; + this.playerIo = saveHandler; + } ++ abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor + + public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { + player.isRealPlayer = true; // Paper diff --git a/patches/server/0522-Add-worldborder-events.patch b/patches/server/0521-Add-worldborder-events.patch similarity index 100% rename from patches/server/0522-Add-worldborder-events.patch rename to patches/server/0521-Add-worldborder-events.patch diff --git a/patches/server/0521-fix-converting-txt-to-json-file.patch b/patches/server/0521-fix-converting-txt-to-json-file.patch deleted file mode 100644 index f4fc2d65415c..000000000000 --- a/patches/server/0521-fix-converting-txt-to-json-file.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 4 Jan 2021 19:49:15 -0800 -Subject: [PATCH] fix converting txt to json file - - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java -index a20d47f54f12dfc0a5f76dd969238e34c958b618..1c9cf5e1c4ee05724ffcdbd77a19bca1ab2be4d3 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java -@@ -18,6 +18,11 @@ public class DedicatedPlayerList extends PlayerList { - this.setViewDistance(dedicatedServerProperties.viewDistance); - this.setSimulationDistance(dedicatedServerProperties.simulationDistance); - super.setUsingWhiteList(dedicatedServerProperties.whiteList.get()); -+ // Paper start - fix converting txt to json file; moved from constructor -+ } -+ @Override -+ public void loadAndSaveFiles() { -+ // Paper end - fix converting txt to json file - this.loadUserBanList(); - this.saveUserBanList(); - this.loadIpBanList(); -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 317f9048be060778104c8ac3494599c2141b7aac..413f2ab9b6df22f083df684a8a2e7e97a7a312a1 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -199,6 +199,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess()); - this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); - // Paper end - initialize global and world-defaults configuration -+ // Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save -+ if (this.convertOldUsers()) { -+ this.getProfileCache().save(false); // Paper -+ } -+ this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames -+ // Paper end - fix converting txt to json file - org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread - io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command - com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics -@@ -253,9 +259,6 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); - } - -- if (this.convertOldUsers()) { -- this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving -- } - - if (!OldUsersConverter.serverReadyAfterUserconversion(this)) { - return false; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 065a189566d1eea08b1013ce64d1f2282d0c2dc3..fde0b21f3e3349cf55ed6e9c200cf0b63dcb11a9 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -176,6 +176,7 @@ public abstract class PlayerList { - this.maxPlayers = maxPlayers; - this.playerIo = saveHandler; - } -+ abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor - - public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { - player.isRealPlayer = true; // Paper diff --git a/patches/server/0523-Add-PlayerNameEntityEvent.patch b/patches/server/0522-Add-PlayerNameEntityEvent.patch similarity index 100% rename from patches/server/0523-Add-PlayerNameEntityEvent.patch rename to patches/server/0522-Add-PlayerNameEntityEvent.patch diff --git a/patches/server/0524-Prevent-grindstones-from-overstacking-items.patch b/patches/server/0523-Prevent-grindstones-from-overstacking-items.patch similarity index 100% rename from patches/server/0524-Prevent-grindstones-from-overstacking-items.patch rename to patches/server/0523-Prevent-grindstones-from-overstacking-items.patch diff --git a/patches/server/0525-Add-recipe-to-cook-events.patch b/patches/server/0524-Add-recipe-to-cook-events.patch similarity index 100% rename from patches/server/0525-Add-recipe-to-cook-events.patch rename to patches/server/0524-Add-recipe-to-cook-events.patch diff --git a/patches/server/0526-Add-Block-isValidTool.patch b/patches/server/0525-Add-Block-isValidTool.patch similarity index 100% rename from patches/server/0526-Add-Block-isValidTool.patch rename to patches/server/0525-Add-Block-isValidTool.patch diff --git a/patches/server/0526-Allow-using-signs-inside-spawn-protection.patch b/patches/server/0526-Allow-using-signs-inside-spawn-protection.patch new file mode 100644 index 000000000000..a749fc47e72a --- /dev/null +++ b/patches/server/0526-Allow-using-signs-inside-spawn-protection.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Anton Lindroth +Date: Wed, 15 Apr 2020 01:54:02 +0200 +Subject: [PATCH] Allow using signs inside spawn protection + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 730b30ee33f0fb2a98454080045608ff538c8c04..437c642f0df201528eb2fcd49ae21b67db13df86 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1752,7 +1752,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + int i = this.player.level().getMaxBuildHeight(); + + if (blockposition.getY() < i) { +- if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract(this.player, blockposition)) { ++ if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && (worldserver.mayInteract(this.player, blockposition) || (worldserver.paperConfig().spawn.allowUsingSignsInsideSpawnProtection && worldserver.getBlockState(blockposition).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // Paper - Allow using signs inside spawn protection + this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706 + InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock); + diff --git a/patches/server/0527-Allow-using-signs-inside-spawn-protection.patch b/patches/server/0527-Allow-using-signs-inside-spawn-protection.patch deleted file mode 100644 index 77ada4cbb9c2..000000000000 --- a/patches/server/0527-Allow-using-signs-inside-spawn-protection.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Anton Lindroth -Date: Wed, 15 Apr 2020 01:54:02 +0200 -Subject: [PATCH] Allow using signs inside spawn protection - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index d4b9f2b82527852c8fde8299801d54c9ba76371a..c2e663082bbb956aabdcdb6372964f3e85541494 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1752,7 +1752,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - int i = this.player.level().getMaxBuildHeight(); - - if (blockposition.getY() < i) { -- if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract(this.player, blockposition)) { -+ if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && (worldserver.mayInteract(this.player, blockposition) || (worldserver.paperConfig().spawn.allowUsingSignsInsideSpawnProtection && worldserver.getBlockState(blockposition).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // Paper - Allow using signs inside spawn protection - this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706 - InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock); - diff --git a/patches/server/0527-Expand-world-key-API.patch b/patches/server/0527-Expand-world-key-API.patch new file mode 100644 index 000000000000..aff444361410 --- /dev/null +++ b/patches/server/0527-Expand-world-key-API.patch @@ -0,0 +1,84 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 6 Jan 2021 00:34:04 -0800 +Subject: [PATCH] Expand world key API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index 0fb214ddd919b568da64541fd9b531c65caa5fad..9cd267f53505658d1c75187b662c4d9f68cd6bae 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -505,5 +505,10 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + public io.papermc.paper.world.MoonPhase getMoonPhase() { + return io.papermc.paper.world.MoonPhase.getPhase(this.getHandle().dayTime() / 24000L); + } ++ ++ @Override ++ public org.bukkit.NamespacedKey getKey() { ++ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location()); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index c353f7a3a9ad0099ef7330dde988d1a174a0e327..b453673f7bab09a1b10898a7d1f85d133e63ac72 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1149,9 +1149,15 @@ public final class CraftServer implements Server { + File folder = new File(this.getWorldContainer(), name); + World world = this.getWorld(name); + +- if (world != null) { +- return world; ++ // Paper start ++ World worldByKey = this.getWorld(creator.key()); ++ if (world != null || worldByKey != null) { ++ if (world == worldByKey) { ++ return world; ++ } ++ throw new IllegalArgumentException("Cannot create a world with key " + creator.key() + " and name " + name + " one (or both) already match a world that exists"); + } ++ // Paper end + + if (folder.exists()) { + Preconditions.checkArgument(folder.isDirectory(), "File (%s) exists and isn't a folder", name); +@@ -1275,7 +1281,7 @@ public final class CraftServer implements Server { + } else if (name.equals(levelName + "_the_end")) { + worldKey = net.minecraft.world.level.Level.END; + } else { +- worldKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name.toLowerCase(java.util.Locale.ENGLISH))); ++ worldKey = ResourceKey.create(Registries.DIMENSION, new net.minecraft.resources.ResourceLocation(creator.key().getNamespace().toLowerCase(java.util.Locale.ENGLISH), creator.key().getKey().toLowerCase(java.util.Locale.ENGLISH))); // Paper + } + + ServerLevel internal = (ServerLevel) new ServerLevel(this.console, this.console.executor, worldSession, worlddata, worldKey, worlddimension, this.getServer().progressListenerFactory.create(11), +@@ -1368,6 +1374,15 @@ public final class CraftServer implements Server { + return null; + } + ++ // Paper start ++ @Override ++ public World getWorld(NamespacedKey worldKey) { ++ ServerLevel worldServer = console.getLevel(ResourceKey.create(net.minecraft.core.registries.Registries.DIMENSION, CraftNamespacedKey.toMinecraft(worldKey))); ++ if (worldServer == null) return null; ++ return worldServer.getWorld(); ++ } ++ // Paper end ++ + public void addWorld(World world) { + // Check if a World already exists with the UID. + if (this.getWorld(world.getUID()) != null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 14d094d9af9b76277859901db908b8a36b24986b..4d695428cf838d2f5661fdeaaa58b82de693d64e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -531,6 +531,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + public int nextEntityId() { + return net.minecraft.world.entity.Entity.nextEntityId(); + } ++ ++ @Override ++ public String getMainLevelName() { ++ return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName; ++ } + // Paper end + + /** diff --git a/patches/server/0529-Add-fast-alternative-constructor-for-Rotations.patch b/patches/server/0528-Add-fast-alternative-constructor-for-Rotations.patch similarity index 100% rename from patches/server/0529-Add-fast-alternative-constructor-for-Rotations.patch rename to patches/server/0528-Add-fast-alternative-constructor-for-Rotations.patch diff --git a/patches/server/0528-Expand-world-key-API.patch b/patches/server/0528-Expand-world-key-API.patch deleted file mode 100644 index de0d6bde9fc4..000000000000 --- a/patches/server/0528-Expand-world-key-API.patch +++ /dev/null @@ -1,84 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 6 Jan 2021 00:34:04 -0800 -Subject: [PATCH] Expand world key API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index 0fb214ddd919b568da64541fd9b531c65caa5fad..9cd267f53505658d1c75187b662c4d9f68cd6bae 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -505,5 +505,10 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - public io.papermc.paper.world.MoonPhase getMoonPhase() { - return io.papermc.paper.world.MoonPhase.getPhase(this.getHandle().dayTime() / 24000L); - } -+ -+ @Override -+ public org.bukkit.NamespacedKey getKey() { -+ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location()); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index f027e405e4c80a66110d351961612faa8828bdb0..cf508b4ddd21d006b88eac6588a0f56eb20c5ec7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1137,9 +1137,15 @@ public final class CraftServer implements Server { - File folder = new File(this.getWorldContainer(), name); - World world = this.getWorld(name); - -- if (world != null) { -- return world; -+ // Paper start -+ World worldByKey = this.getWorld(creator.key()); -+ if (world != null || worldByKey != null) { -+ if (world == worldByKey) { -+ return world; -+ } -+ throw new IllegalArgumentException("Cannot create a world with key " + creator.key() + " and name " + name + " one (or both) already match a world that exists"); - } -+ // Paper end - - if (folder.exists()) { - Preconditions.checkArgument(folder.isDirectory(), "File (%s) exists and isn't a folder", name); -@@ -1263,7 +1269,7 @@ public final class CraftServer implements Server { - } else if (name.equals(levelName + "_the_end")) { - worldKey = net.minecraft.world.level.Level.END; - } else { -- worldKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name.toLowerCase(java.util.Locale.ENGLISH))); -+ worldKey = ResourceKey.create(Registries.DIMENSION, new net.minecraft.resources.ResourceLocation(creator.key().getNamespace().toLowerCase(java.util.Locale.ENGLISH), creator.key().getKey().toLowerCase(java.util.Locale.ENGLISH))); // Paper - } - - ServerLevel internal = (ServerLevel) new ServerLevel(this.console, this.console.executor, worldSession, worlddata, worldKey, worlddimension, this.getServer().progressListenerFactory.create(11), -@@ -1356,6 +1362,15 @@ public final class CraftServer implements Server { - return null; - } - -+ // Paper start -+ @Override -+ public World getWorld(NamespacedKey worldKey) { -+ ServerLevel worldServer = console.getLevel(ResourceKey.create(net.minecraft.core.registries.Registries.DIMENSION, CraftNamespacedKey.toMinecraft(worldKey))); -+ if (worldServer == null) return null; -+ return worldServer.getWorld(); -+ } -+ // Paper end -+ - public void addWorld(World world) { - // Check if a World already exists with the UID. - if (this.getWorld(world.getUID()) != null) { -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 67c8e62c22f11ea4524e4229d2b89695313bf5d2..e0118a0c780cb60eb8545cb9c61f37c0986752b0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -516,6 +516,11 @@ public final class CraftMagicNumbers implements UnsafeValues { - public int nextEntityId() { - return net.minecraft.world.entity.Entity.nextEntityId(); - } -+ -+ @Override -+ public String getMainLevelName() { -+ return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName; -+ } - // Paper end - - /** diff --git a/patches/server/0529-Item-Rarity-API.patch b/patches/server/0529-Item-Rarity-API.patch new file mode 100644 index 000000000000..d0d483075a66 --- /dev/null +++ b/patches/server/0529-Item-Rarity-API.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 12 Mar 2021 17:09:42 -0800 +Subject: [PATCH] Item Rarity API + +== AT == +public net.minecraft.world.item.Item rarity + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 4d695428cf838d2f5661fdeaaa58b82de693d64e..b928b3550bc74fd91c6762bc025a0bf6c766541b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -536,6 +536,20 @@ public final class CraftMagicNumbers implements UnsafeValues { + public String getMainLevelName() { + return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName; + } ++ ++ @Override ++ public io.papermc.paper.inventory.ItemRarity getItemRarity(org.bukkit.Material material) { ++ Item item = getItem(material); ++ if (item == null) { ++ throw new IllegalArgumentException(material + " is not an item, and rarity does not apply to blocks"); ++ } ++ return io.papermc.paper.inventory.ItemRarity.values()[item.rarity.ordinal()]; ++ } ++ ++ @Override ++ public io.papermc.paper.inventory.ItemRarity getItemStackRarity(org.bukkit.inventory.ItemStack itemStack) { ++ return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; ++ } + // Paper end + + /** +diff --git a/src/test/java/io/papermc/paper/inventory/ItemRarityTest.java b/src/test/java/io/papermc/paper/inventory/ItemRarityTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d310f86c57e4521ad7666d3f738f53ac83d221f2 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/inventory/ItemRarityTest.java +@@ -0,0 +1,24 @@ ++package io.papermc.paper.inventory; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import net.minecraft.world.item.Rarity; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++ ++public class ItemRarityTest { ++ ++ @Test ++ public void testConvertFromNmsToBukkit() { ++ for (Rarity nmsRarity : Rarity.values()) { ++ assertEquals(ItemRarity.values()[nmsRarity.ordinal()].name(), nmsRarity.name(), "rarity names are mis-matched"); ++ } ++ } ++ ++ @Test ++ public void testRarityFormatting() { ++ for (Rarity nmsRarity : Rarity.values()) { ++ assertEquals(nmsRarity.color, PaperAdventure.asVanilla(ItemRarity.values()[nmsRarity.ordinal()].color), "rarity formatting is mis-matched"); ++ } ++ } ++} diff --git a/patches/server/0531-Drop-carried-item-when-player-has-disconnected.patch b/patches/server/0530-Drop-carried-item-when-player-has-disconnected.patch similarity index 100% rename from patches/server/0531-Drop-carried-item-when-player-has-disconnected.patch rename to patches/server/0530-Drop-carried-item-when-player-has-disconnected.patch diff --git a/patches/server/0530-Item-Rarity-API.patch b/patches/server/0530-Item-Rarity-API.patch deleted file mode 100644 index 47ad27274a09..000000000000 --- a/patches/server/0530-Item-Rarity-API.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 12 Mar 2021 17:09:42 -0800 -Subject: [PATCH] Item Rarity API - -== AT == -public net.minecraft.world.item.Item rarity - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index e0118a0c780cb60eb8545cb9c61f37c0986752b0..841e5887c455fafd826965e4f84180750f303eaa 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -521,6 +521,20 @@ public final class CraftMagicNumbers implements UnsafeValues { - public String getMainLevelName() { - return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName; - } -+ -+ @Override -+ public io.papermc.paper.inventory.ItemRarity getItemRarity(org.bukkit.Material material) { -+ Item item = getItem(material); -+ if (item == null) { -+ throw new IllegalArgumentException(material + " is not an item, and rarity does not apply to blocks"); -+ } -+ return io.papermc.paper.inventory.ItemRarity.values()[item.rarity.ordinal()]; -+ } -+ -+ @Override -+ public io.papermc.paper.inventory.ItemRarity getItemStackRarity(org.bukkit.inventory.ItemStack itemStack) { -+ return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; -+ } - // Paper end - - /** -diff --git a/src/test/java/io/papermc/paper/inventory/ItemRarityTest.java b/src/test/java/io/papermc/paper/inventory/ItemRarityTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d310f86c57e4521ad7666d3f738f53ac83d221f2 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/inventory/ItemRarityTest.java -@@ -0,0 +1,24 @@ -+package io.papermc.paper.inventory; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import net.minecraft.world.item.Rarity; -+import org.junit.jupiter.api.Test; -+ -+import static org.junit.jupiter.api.Assertions.assertEquals; -+ -+public class ItemRarityTest { -+ -+ @Test -+ public void testConvertFromNmsToBukkit() { -+ for (Rarity nmsRarity : Rarity.values()) { -+ assertEquals(ItemRarity.values()[nmsRarity.ordinal()].name(), nmsRarity.name(), "rarity names are mis-matched"); -+ } -+ } -+ -+ @Test -+ public void testRarityFormatting() { -+ for (Rarity nmsRarity : Rarity.values()) { -+ assertEquals(nmsRarity.color, PaperAdventure.asVanilla(ItemRarity.values()[nmsRarity.ordinal()].color), "rarity formatting is mis-matched"); -+ } -+ } -+} diff --git a/patches/server/0531-forced-whitelist-use-configurable-kick-message.patch b/patches/server/0531-forced-whitelist-use-configurable-kick-message.patch new file mode 100644 index 000000000000..48f96930a9b2 --- /dev/null +++ b/patches/server/0531-forced-whitelist-use-configurable-kick-message.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Sat, 27 Mar 2021 09:24:23 +0100 +Subject: [PATCH] forced whitelist: use configurable kick message + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 3f061e1135bac08e6ea1530407ff2667904339a5..773e4850956a7ffcd78cc241a598fd13bcfe1d20 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2167,7 +2167,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Mon, 5 Apr 2021 18:35:15 -0700 +Subject: [PATCH] Don't ignore result of PlayerEditBookEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 437c642f0df201528eb2fcd49ae21b67db13df86..b1a0711dbedaaf79aac49b8c594d47e997c2613d 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1187,7 +1187,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + itemstack.addTagElement("pages", nbttaglist); +- CraftEventFactory.handleEditBookEvent(this.player, slot, handItem, itemstack); // CraftBukkit ++ this.player.getInventory().setItem(slot, CraftEventFactory.handleEditBookEvent(this.player, slot, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) + } + + @Override diff --git a/patches/server/0532-forced-whitelist-use-configurable-kick-message.patch b/patches/server/0532-forced-whitelist-use-configurable-kick-message.patch deleted file mode 100644 index da6064c246a7..000000000000 --- a/patches/server/0532-forced-whitelist-use-configurable-kick-message.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Trigary -Date: Sat, 27 Mar 2021 09:24:23 +0100 -Subject: [PATCH] forced whitelist: use configurable kick message - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 37c4245a30ee6a5f786364aa46dee832396ba4fd..b3b78ad64c5cd9bde4756c5e619d0188ec8f2608 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2167,7 +2167,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Mon, 5 Apr 2021 18:35:15 -0700 -Subject: [PATCH] Don't ignore result of PlayerEditBookEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index c2e663082bbb956aabdcdb6372964f3e85541494..f0551a35583e05e3e0437eda6e78f9672d00648f 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1187,7 +1187,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - itemstack.addTagElement("pages", nbttaglist); -- CraftEventFactory.handleEditBookEvent(this.player, slot, handItem, itemstack); // CraftBukkit -+ this.player.getInventory().setItem(slot, CraftEventFactory.handleEditBookEvent(this.player, slot, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) - } - - @Override diff --git a/patches/server/0533-Expose-protocol-version.patch b/patches/server/0533-Expose-protocol-version.patch new file mode 100644 index 000000000000..e81a519f775d --- /dev/null +++ b/patches/server/0533-Expose-protocol-version.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Fri, 26 Mar 2021 11:23:17 +0100 +Subject: [PATCH] Expose protocol version + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index b928b3550bc74fd91c6762bc025a0bf6c766541b..44b0acb82f10a35bc0c42fdb6fd685aadc76d789 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -550,6 +550,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + public io.papermc.paper.inventory.ItemRarity getItemStackRarity(org.bukkit.inventory.ItemStack itemStack) { + return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; + } ++ ++ @Override ++ public int getProtocolVersion() { ++ return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); ++ } + // Paper end + + /** diff --git a/patches/server/0534-Enhance-console-tab-completions-for-brigadier-comman.patch b/patches/server/0534-Enhance-console-tab-completions-for-brigadier-comman.patch new file mode 100644 index 000000000000..c1c74a847048 --- /dev/null +++ b/patches/server/0534-Enhance-console-tab-completions-for-brigadier-comman.patch @@ -0,0 +1,295 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Tue, 30 Mar 2021 16:06:08 -0700 +Subject: [PATCH] Enhance console tab completions for brigadier commands + + +diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +index a4070b59e261f0f1ac4beec47b11492f4724bf27..c5d5648f4ca603ef2b1df723b58f9caf4dd3c722 100644 +--- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java ++++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +@@ -16,11 +16,15 @@ public final class PaperConsole extends SimpleTerminalConsole { + + @Override + protected LineReader buildReader(LineReaderBuilder builder) { +- return super.buildReader(builder ++ builder + .appName("Paper") + .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) + .completer(new ConsoleCommandCompleter(this.server)) +- ); ++ .option(LineReader.Option.COMPLETE_IN_WORD, true); ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierHighlighting) { ++ builder.highlighter(new io.papermc.paper.console.BrigadierCommandHighlighter(this.server)); ++ } ++ return super.buildReader(builder); + } + + @Override +diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7a4f4c0a0fdcabd2bc4aa26dc9d76fc150b8435c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java +@@ -0,0 +1,99 @@ ++package io.papermc.paper.console; ++ ++import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion; ++import com.google.common.base.Suppliers; ++import com.mojang.brigadier.CommandDispatcher; ++import com.mojang.brigadier.ParseResults; ++import com.mojang.brigadier.StringReader; ++import com.mojang.brigadier.suggestion.Suggestion; ++import io.papermc.paper.adventure.PaperAdventure; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.function.Supplier; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.network.chat.ComponentUtils; ++import net.minecraft.server.dedicated.DedicatedServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.jline.reader.Candidate; ++import org.jline.reader.LineReader; ++import org.jline.reader.ParsedLine; ++ ++import static com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion; ++ ++public final class BrigadierCommandCompleter { ++ private final Supplier commandSourceStack; ++ private final DedicatedServer server; ++ ++ public BrigadierCommandCompleter(final @NonNull DedicatedServer server) { ++ this.server = server; ++ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack); ++ } ++ ++ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List candidates, final @NonNull List existing) { ++ //noinspection ConstantConditions ++ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet ++ return; ++ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierCompletions) { ++ this.addCandidates(candidates, Collections.emptyList(), existing); ++ return; ++ } ++ final CommandDispatcher dispatcher = this.server.getCommands().getDispatcher(); ++ final ParseResults results = dispatcher.parse(prepareStringReader(line.line()), this.commandSourceStack.get()); ++ this.addCandidates( ++ candidates, ++ dispatcher.getCompletionSuggestions(results, line.cursor()).join().getList(), ++ existing ++ ); ++ } ++ ++ private void addCandidates( ++ final @NonNull List candidates, ++ final @NonNull List brigSuggestions, ++ final @NonNull List existing ++ ) { ++ final List completions = new ArrayList<>(); ++ brigSuggestions.forEach(it -> completions.add(toCompletion(it))); ++ for (final Completion completion : existing) { ++ if (completion.suggestion().isEmpty() || brigSuggestions.stream().anyMatch(it -> it.getText().equals(completion.suggestion()))) { ++ continue; ++ } ++ completions.add(completion); ++ } ++ for (final Completion completion : completions) { ++ if (completion.suggestion().isEmpty()) { ++ continue; ++ } ++ candidates.add(toCandidate(completion)); ++ } ++ } ++ ++ private static @NonNull Candidate toCandidate(final @NonNull Completion completion) { ++ final String suggestionText = completion.suggestion(); ++ final String suggestionTooltip = PaperAdventure.ANSI_SERIALIZER.serializeOr(completion.tooltip(), null); ++ return new Candidate( ++ suggestionText, ++ suggestionText, ++ null, ++ suggestionTooltip, ++ null, ++ null, ++ false ++ ); ++ } ++ ++ private static @NonNull Completion toCompletion(final @NonNull Suggestion suggestion) { ++ if (suggestion.getTooltip() == null) { ++ return completion(suggestion.getText()); ++ } ++ return completion(suggestion.getText(), PaperAdventure.asAdventure(ComponentUtils.fromMessage(suggestion.getTooltip()))); ++ } ++ ++ static @NonNull StringReader prepareStringReader(final @NonNull String line) { ++ final StringReader stringReader = new StringReader(line); ++ if (stringReader.canRead() && stringReader.peek() == '/') { ++ stringReader.skip(); ++ } ++ return stringReader; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dd9d77d7c7f1a5a130a1f4c15e5b1e68ae3753e1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java +@@ -0,0 +1,70 @@ ++package io.papermc.paper.console; ++ ++import com.google.common.base.Suppliers; ++import com.mojang.brigadier.ParseResults; ++import com.mojang.brigadier.context.ParsedCommandNode; ++import com.mojang.brigadier.tree.LiteralCommandNode; ++import java.util.function.Supplier; ++import java.util.regex.Pattern; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.server.dedicated.DedicatedServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.jline.reader.Highlighter; ++import org.jline.reader.LineReader; ++import org.jline.utils.AttributedString; ++import org.jline.utils.AttributedStringBuilder; ++import org.jline.utils.AttributedStyle; ++ ++public final class BrigadierCommandHighlighter implements Highlighter { ++ private static final int[] COLORS = {AttributedStyle.CYAN, AttributedStyle.YELLOW, AttributedStyle.GREEN, AttributedStyle.MAGENTA, /* Client uses GOLD here, not BLUE, however there is no GOLD AttributedStyle. */ AttributedStyle.BLUE}; ++ private final Supplier commandSourceStack; ++ private final DedicatedServer server; ++ ++ public BrigadierCommandHighlighter(final @NonNull DedicatedServer server) { ++ this.server = server; ++ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack); ++ } ++ ++ @Override ++ public AttributedString highlight(final @NonNull LineReader reader, final @NonNull String buffer) { ++ //noinspection ConstantConditions ++ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet ++ return new AttributedString(buffer, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); ++ } ++ final AttributedStringBuilder builder = new AttributedStringBuilder(); ++ final ParseResults results = this.server.getCommands().getDispatcher().parse(BrigadierCommandCompleter.prepareStringReader(buffer), this.commandSourceStack.get()); ++ int pos = 0; ++ if (buffer.startsWith("/")) { ++ builder.append("/", AttributedStyle.DEFAULT); ++ pos = 1; ++ } ++ int component = -1; ++ for (final ParsedCommandNode node : results.getContext().getLastChild().getNodes()) { ++ if (node.getRange().getStart() >= buffer.length()) { ++ break; ++ } ++ final int start = node.getRange().getStart(); ++ final int end = Math.min(node.getRange().getEnd(), buffer.length()); ++ builder.append(buffer.substring(pos, start), AttributedStyle.DEFAULT); ++ if (node.getNode() instanceof LiteralCommandNode) { ++ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT); ++ } else { ++ if (++component >= COLORS.length) { ++ component = 0; ++ } ++ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT.foreground(COLORS[component])); ++ } ++ pos = end; ++ } ++ if (pos < buffer.length()) { ++ builder.append((buffer.substring(pos)), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); ++ } ++ return builder.toAttributedString(); ++ } ++ ++ @Override ++ public void setErrorPattern(final Pattern errorPattern) {} ++ ++ @Override ++ public void setErrorIndex(final int errorIndex) {} ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 2a0d3212edeee828b9fe04c153ee05ea5b0875ec..dd13627f140ab93901a32901eeb51488421ae91f 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -173,7 +173,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + + thread.setDaemon(true); + thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER)); +- thread.start(); ++ // thread.start(); // Paper - Enhance console tab completions for brigadier commands; moved down + DedicatedServer.LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName()); + if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) { + DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); +@@ -206,6 +206,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames + // Paper end - fix converting txt to json file + org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread ++ thread.start(); // Paper - Enhance console tab completions for brigadier commands; start console thread after MinecraftServer.console & PaperConfig are initialized + io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +index 8f82041f0482df22a6a9ea38d50d56228131775d..3e93a6c489972ff2b4ecff3d83cc72b2d5c970f8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +@@ -18,9 +18,11 @@ import org.bukkit.event.server.TabCompleteEvent; + + public class ConsoleCommandCompleter implements Completer { + private final DedicatedServer server; // Paper - CraftServer -> DedicatedServer ++ private final io.papermc.paper.console.BrigadierCommandCompleter brigadierCompleter; // Paper - Enhance console tab completions for brigadier commands + + public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer + this.server = server; ++ this.brigadierCompleter = new io.papermc.paper.console.BrigadierCommandCompleter(this.server); // Paper - Enhance console tab completions for brigadier commands + } + + // Paper start - Change method signature for JLine update +@@ -64,7 +66,7 @@ public class ConsoleCommandCompleter implements Completer { + } + } + +- if (!completions.isEmpty()) { ++ if (false && !completions.isEmpty()) { + for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { + if (completion.suggestion().isEmpty()) { + continue; +@@ -80,6 +82,7 @@ public class ConsoleCommandCompleter implements Completer { + )); + } + } ++ this.addCompletions(reader, line, candidates, completions); + return; + } + +@@ -99,10 +102,12 @@ public class ConsoleCommandCompleter implements Completer { + try { + List offers = waitable.get(); + if (offers == null) { ++ this.addCompletions(reader, line, candidates, Collections.emptyList()); // Paper - Enhance console tab completions for brigadier commands + return; // Paper - Method returns void + } + + // Paper start - JLine update ++ /* + for (String completion : offers) { + if (completion.isEmpty()) { + continue; +@@ -110,6 +115,8 @@ public class ConsoleCommandCompleter implements Completer { + + candidates.add(new Candidate(completion)); + } ++ */ ++ this.addCompletions(reader, line, candidates, offers.stream().map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::completion).collect(java.util.stream.Collectors.toList())); + // Paper end + + // Paper start - JLine handles cursor now +@@ -138,5 +145,9 @@ public class ConsoleCommandCompleter implements Completer { + } + return false; + } ++ ++ private void addCompletions(final LineReader reader, final ParsedLine line, final List candidates, final List existing) { ++ this.brigadierCompleter.complete(reader, line, candidates, existing); ++ } + // Paper end + } diff --git a/patches/server/0534-Expose-protocol-version.patch b/patches/server/0534-Expose-protocol-version.patch deleted file mode 100644 index 789c4e233c31..000000000000 --- a/patches/server/0534-Expose-protocol-version.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Fri, 26 Mar 2021 11:23:17 +0100 -Subject: [PATCH] Expose protocol version - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 841e5887c455fafd826965e4f84180750f303eaa..584ea52824c17b3008204df2480a2bf9f14acb82 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -535,6 +535,11 @@ public final class CraftMagicNumbers implements UnsafeValues { - public io.papermc.paper.inventory.ItemRarity getItemStackRarity(org.bukkit.inventory.ItemStack itemStack) { - return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; - } -+ -+ @Override -+ public int getProtocolVersion() { -+ return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); -+ } - // Paper end - - /** diff --git a/patches/server/0535-Enhance-console-tab-completions-for-brigadier-comman.patch b/patches/server/0535-Enhance-console-tab-completions-for-brigadier-comman.patch deleted file mode 100644 index e0aec17a3c41..000000000000 --- a/patches/server/0535-Enhance-console-tab-completions-for-brigadier-comman.patch +++ /dev/null @@ -1,295 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Tue, 30 Mar 2021 16:06:08 -0700 -Subject: [PATCH] Enhance console tab completions for brigadier commands - - -diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -index a4070b59e261f0f1ac4beec47b11492f4724bf27..c5d5648f4ca603ef2b1df723b58f9caf4dd3c722 100644 ---- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -+++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -@@ -16,11 +16,15 @@ public final class PaperConsole extends SimpleTerminalConsole { - - @Override - protected LineReader buildReader(LineReaderBuilder builder) { -- return super.buildReader(builder -+ builder - .appName("Paper") - .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) - .completer(new ConsoleCommandCompleter(this.server)) -- ); -+ .option(LineReader.Option.COMPLETE_IN_WORD, true); -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierHighlighting) { -+ builder.highlighter(new io.papermc.paper.console.BrigadierCommandHighlighter(this.server)); -+ } -+ return super.buildReader(builder); - } - - @Override -diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7a4f4c0a0fdcabd2bc4aa26dc9d76fc150b8435c ---- /dev/null -+++ b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java -@@ -0,0 +1,99 @@ -+package io.papermc.paper.console; -+ -+import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion; -+import com.google.common.base.Suppliers; -+import com.mojang.brigadier.CommandDispatcher; -+import com.mojang.brigadier.ParseResults; -+import com.mojang.brigadier.StringReader; -+import com.mojang.brigadier.suggestion.Suggestion; -+import io.papermc.paper.adventure.PaperAdventure; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.List; -+import java.util.function.Supplier; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.network.chat.ComponentUtils; -+import net.minecraft.server.dedicated.DedicatedServer; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.jline.reader.Candidate; -+import org.jline.reader.LineReader; -+import org.jline.reader.ParsedLine; -+ -+import static com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion; -+ -+public final class BrigadierCommandCompleter { -+ private final Supplier commandSourceStack; -+ private final DedicatedServer server; -+ -+ public BrigadierCommandCompleter(final @NonNull DedicatedServer server) { -+ this.server = server; -+ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack); -+ } -+ -+ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List candidates, final @NonNull List existing) { -+ //noinspection ConstantConditions -+ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet -+ return; -+ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierCompletions) { -+ this.addCandidates(candidates, Collections.emptyList(), existing); -+ return; -+ } -+ final CommandDispatcher dispatcher = this.server.getCommands().getDispatcher(); -+ final ParseResults results = dispatcher.parse(prepareStringReader(line.line()), this.commandSourceStack.get()); -+ this.addCandidates( -+ candidates, -+ dispatcher.getCompletionSuggestions(results, line.cursor()).join().getList(), -+ existing -+ ); -+ } -+ -+ private void addCandidates( -+ final @NonNull List candidates, -+ final @NonNull List brigSuggestions, -+ final @NonNull List existing -+ ) { -+ final List completions = new ArrayList<>(); -+ brigSuggestions.forEach(it -> completions.add(toCompletion(it))); -+ for (final Completion completion : existing) { -+ if (completion.suggestion().isEmpty() || brigSuggestions.stream().anyMatch(it -> it.getText().equals(completion.suggestion()))) { -+ continue; -+ } -+ completions.add(completion); -+ } -+ for (final Completion completion : completions) { -+ if (completion.suggestion().isEmpty()) { -+ continue; -+ } -+ candidates.add(toCandidate(completion)); -+ } -+ } -+ -+ private static @NonNull Candidate toCandidate(final @NonNull Completion completion) { -+ final String suggestionText = completion.suggestion(); -+ final String suggestionTooltip = PaperAdventure.ANSI_SERIALIZER.serializeOr(completion.tooltip(), null); -+ return new Candidate( -+ suggestionText, -+ suggestionText, -+ null, -+ suggestionTooltip, -+ null, -+ null, -+ false -+ ); -+ } -+ -+ private static @NonNull Completion toCompletion(final @NonNull Suggestion suggestion) { -+ if (suggestion.getTooltip() == null) { -+ return completion(suggestion.getText()); -+ } -+ return completion(suggestion.getText(), PaperAdventure.asAdventure(ComponentUtils.fromMessage(suggestion.getTooltip()))); -+ } -+ -+ static @NonNull StringReader prepareStringReader(final @NonNull String line) { -+ final StringReader stringReader = new StringReader(line); -+ if (stringReader.canRead() && stringReader.peek() == '/') { -+ stringReader.skip(); -+ } -+ return stringReader; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dd9d77d7c7f1a5a130a1f4c15e5b1e68ae3753e1 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java -@@ -0,0 +1,70 @@ -+package io.papermc.paper.console; -+ -+import com.google.common.base.Suppliers; -+import com.mojang.brigadier.ParseResults; -+import com.mojang.brigadier.context.ParsedCommandNode; -+import com.mojang.brigadier.tree.LiteralCommandNode; -+import java.util.function.Supplier; -+import java.util.regex.Pattern; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.server.dedicated.DedicatedServer; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.jline.reader.Highlighter; -+import org.jline.reader.LineReader; -+import org.jline.utils.AttributedString; -+import org.jline.utils.AttributedStringBuilder; -+import org.jline.utils.AttributedStyle; -+ -+public final class BrigadierCommandHighlighter implements Highlighter { -+ private static final int[] COLORS = {AttributedStyle.CYAN, AttributedStyle.YELLOW, AttributedStyle.GREEN, AttributedStyle.MAGENTA, /* Client uses GOLD here, not BLUE, however there is no GOLD AttributedStyle. */ AttributedStyle.BLUE}; -+ private final Supplier commandSourceStack; -+ private final DedicatedServer server; -+ -+ public BrigadierCommandHighlighter(final @NonNull DedicatedServer server) { -+ this.server = server; -+ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack); -+ } -+ -+ @Override -+ public AttributedString highlight(final @NonNull LineReader reader, final @NonNull String buffer) { -+ //noinspection ConstantConditions -+ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet -+ return new AttributedString(buffer, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); -+ } -+ final AttributedStringBuilder builder = new AttributedStringBuilder(); -+ final ParseResults results = this.server.getCommands().getDispatcher().parse(BrigadierCommandCompleter.prepareStringReader(buffer), this.commandSourceStack.get()); -+ int pos = 0; -+ if (buffer.startsWith("/")) { -+ builder.append("/", AttributedStyle.DEFAULT); -+ pos = 1; -+ } -+ int component = -1; -+ for (final ParsedCommandNode node : results.getContext().getLastChild().getNodes()) { -+ if (node.getRange().getStart() >= buffer.length()) { -+ break; -+ } -+ final int start = node.getRange().getStart(); -+ final int end = Math.min(node.getRange().getEnd(), buffer.length()); -+ builder.append(buffer.substring(pos, start), AttributedStyle.DEFAULT); -+ if (node.getNode() instanceof LiteralCommandNode) { -+ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT); -+ } else { -+ if (++component >= COLORS.length) { -+ component = 0; -+ } -+ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT.foreground(COLORS[component])); -+ } -+ pos = end; -+ } -+ if (pos < buffer.length()) { -+ builder.append((buffer.substring(pos)), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); -+ } -+ return builder.toAttributedString(); -+ } -+ -+ @Override -+ public void setErrorPattern(final Pattern errorPattern) {} -+ -+ @Override -+ public void setErrorIndex(final int errorIndex) {} -+} -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 413f2ab9b6df22f083df684a8a2e7e97a7a312a1..46b581e1f930eda3671a283ad89ed1c2094e2cd6 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -173,7 +173,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - - thread.setDaemon(true); - thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER)); -- thread.start(); -+ // thread.start(); // Paper - Enhance console tab completions for brigadier commands; moved down - DedicatedServer.LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName()); - if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) { - DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); -@@ -206,6 +206,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames - // Paper end - fix converting txt to json file - org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread -+ thread.start(); // Paper - Enhance console tab completions for brigadier commands; start console thread after MinecraftServer.console & PaperConfig are initialized - io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command - com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics - com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now -diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -index 8f82041f0482df22a6a9ea38d50d56228131775d..3e93a6c489972ff2b4ecff3d83cc72b2d5c970f8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -@@ -18,9 +18,11 @@ import org.bukkit.event.server.TabCompleteEvent; - - public class ConsoleCommandCompleter implements Completer { - private final DedicatedServer server; // Paper - CraftServer -> DedicatedServer -+ private final io.papermc.paper.console.BrigadierCommandCompleter brigadierCompleter; // Paper - Enhance console tab completions for brigadier commands - - public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer - this.server = server; -+ this.brigadierCompleter = new io.papermc.paper.console.BrigadierCommandCompleter(this.server); // Paper - Enhance console tab completions for brigadier commands - } - - // Paper start - Change method signature for JLine update -@@ -64,7 +66,7 @@ public class ConsoleCommandCompleter implements Completer { - } - } - -- if (!completions.isEmpty()) { -+ if (false && !completions.isEmpty()) { - for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { - if (completion.suggestion().isEmpty()) { - continue; -@@ -80,6 +82,7 @@ public class ConsoleCommandCompleter implements Completer { - )); - } - } -+ this.addCompletions(reader, line, candidates, completions); - return; - } - -@@ -99,10 +102,12 @@ public class ConsoleCommandCompleter implements Completer { - try { - List offers = waitable.get(); - if (offers == null) { -+ this.addCompletions(reader, line, candidates, Collections.emptyList()); // Paper - Enhance console tab completions for brigadier commands - return; // Paper - Method returns void - } - - // Paper start - JLine update -+ /* - for (String completion : offers) { - if (completion.isEmpty()) { - continue; -@@ -110,6 +115,8 @@ public class ConsoleCommandCompleter implements Completer { - - candidates.add(new Candidate(completion)); - } -+ */ -+ this.addCompletions(reader, line, candidates, offers.stream().map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::completion).collect(java.util.stream.Collectors.toList())); - // Paper end - - // Paper start - JLine handles cursor now -@@ -138,5 +145,9 @@ public class ConsoleCommandCompleter implements Completer { - } - return false; - } -+ -+ private void addCompletions(final LineReader reader, final ParsedLine line, final List candidates, final List existing) { -+ this.brigadierCompleter.complete(reader, line, candidates, existing); -+ } - // Paper end - } diff --git a/patches/server/0535-Fix-PlayerItemConsumeEvent-cancelling-properly.patch b/patches/server/0535-Fix-PlayerItemConsumeEvent-cancelling-properly.patch new file mode 100644 index 000000000000..5acc31dd3642 --- /dev/null +++ b/patches/server/0535-Fix-PlayerItemConsumeEvent-cancelling-properly.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Fri, 19 Mar 2021 00:33:15 -0500 +Subject: [PATCH] Fix PlayerItemConsumeEvent cancelling properly + +When the active item is not cleared, the item is still readied +for use and will repeatedly trigger the PlayerItemConsumeEvent +till their item is switched. +This patch clears the active item when the event is cancelled + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 2dddf7a6f3bab4acf0c9d02a35a0cedb27154dea..89842b86b419117a92f79b7bfb57a4aa4351f9f8 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3872,6 +3872,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.level().getCraftServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { ++ this.stopUsingItem(); // Paper - event is using an item, clear active item to reset its use + // Update client + ((ServerPlayer) this).getBukkitEntity().updateInventory(); + ((ServerPlayer) this).getBukkitEntity().updateScaledHealth(); diff --git a/patches/server/0537-Add-bypass-host-check.patch b/patches/server/0536-Add-bypass-host-check.patch similarity index 100% rename from patches/server/0537-Add-bypass-host-check.patch rename to patches/server/0536-Add-bypass-host-check.patch diff --git a/patches/server/0536-Fix-PlayerItemConsumeEvent-cancelling-properly.patch b/patches/server/0536-Fix-PlayerItemConsumeEvent-cancelling-properly.patch deleted file mode 100644 index 0eee744cf677..000000000000 --- a/patches/server/0536-Fix-PlayerItemConsumeEvent-cancelling-properly.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: chickeneer -Date: Fri, 19 Mar 2021 00:33:15 -0500 -Subject: [PATCH] Fix PlayerItemConsumeEvent cancelling properly - -When the active item is not cleared, the item is still readied -for use and will repeatedly trigger the PlayerItemConsumeEvent -till their item is switched. -This patch clears the active item when the event is cancelled - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index bcd1a608796065eb705363bdc246151b68553c0e..297fb36316df04903bd083af523c6b35c284ac7b 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3859,6 +3859,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.level().getCraftServer().getPluginManager().callEvent(event); - - if (event.isCancelled()) { -+ this.stopUsingItem(); // Paper - event is using an item, clear active item to reset its use - // Update client - ((ServerPlayer) this).getBukkitEntity().updateInventory(); - ((ServerPlayer) this).getBukkitEntity().updateScaledHealth(); diff --git a/patches/server/0538-Set-area-affect-cloud-rotation.patch b/patches/server/0537-Set-area-affect-cloud-rotation.patch similarity index 100% rename from patches/server/0538-Set-area-affect-cloud-rotation.patch rename to patches/server/0537-Set-area-affect-cloud-rotation.patch diff --git a/patches/server/0539-add-isDeeplySleeping-to-HumanEntity.patch b/patches/server/0538-add-isDeeplySleeping-to-HumanEntity.patch similarity index 100% rename from patches/server/0539-add-isDeeplySleeping-to-HumanEntity.patch rename to patches/server/0538-add-isDeeplySleeping-to-HumanEntity.patch diff --git a/patches/server/0540-add-consumeFuel-to-FurnaceBurnEvent.patch b/patches/server/0539-add-consumeFuel-to-FurnaceBurnEvent.patch similarity index 100% rename from patches/server/0540-add-consumeFuel-to-FurnaceBurnEvent.patch rename to patches/server/0539-add-consumeFuel-to-FurnaceBurnEvent.patch diff --git a/patches/server/0541-add-get-set-drop-chance-to-EntityEquipment.patch b/patches/server/0540-add-get-set-drop-chance-to-EntityEquipment.patch similarity index 100% rename from patches/server/0541-add-get-set-drop-chance-to-EntityEquipment.patch rename to patches/server/0540-add-get-set-drop-chance-to-EntityEquipment.patch diff --git a/patches/server/0542-fix-PigZombieAngerEvent-cancellation.patch b/patches/server/0541-fix-PigZombieAngerEvent-cancellation.patch similarity index 100% rename from patches/server/0542-fix-PigZombieAngerEvent-cancellation.patch rename to patches/server/0541-fix-PigZombieAngerEvent-cancellation.patch diff --git a/patches/server/0542-fix-PlayerItemHeldEvent-firing-twice.patch b/patches/server/0542-fix-PlayerItemHeldEvent-firing-twice.patch new file mode 100644 index 000000000000..2d2a115414af --- /dev/null +++ b/patches/server/0542-fix-PlayerItemHeldEvent-firing-twice.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Thu, 22 Apr 2021 19:02:07 -0700 +Subject: [PATCH] fix PlayerItemHeldEvent firing twice + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index b1a0711dbedaaf79aac49b8c594d47e997c2613d..8fcb3361c510cb1ef0b6405d0077c82ba155d586 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1921,6 +1921,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.isImmobile()) return; // CraftBukkit + if (packet.getSlot() >= 0 && packet.getSlot() < Inventory.getSelectionSize()) { ++ if (packet.getSlot() == this.player.getInventory().selected) { return; } // Paper - don't fire itemheldevent when there wasn't a slot change + PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getCraftPlayer(), this.player.getInventory().selected, packet.getSlot()); + this.cserver.getPluginManager().callEvent(event); + if (event.isCancelled()) { diff --git a/patches/server/0543-Add-PlayerDeepSleepEvent.patch b/patches/server/0543-Add-PlayerDeepSleepEvent.patch new file mode 100644 index 000000000000..7dfa56bff731 --- /dev/null +++ b/patches/server/0543-Add-PlayerDeepSleepEvent.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 21 Apr 2021 15:58:19 -0700 +Subject: [PATCH] Add PlayerDeepSleepEvent + + +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 203f36776f41c46172b77a195d3702dd6af7409e..e765b6f1163edb363ddebe0c83ca733a061ff103 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -247,6 +247,13 @@ public abstract class Player extends LivingEntity { + + if (this.isSleeping()) { + ++this.sleepCounter; ++ // Paper start - Add PlayerDeepSleepEvent ++ if (this.sleepCounter == SLEEP_DURATION) { ++ if (!new io.papermc.paper.event.player.PlayerDeepSleepEvent((org.bukkit.entity.Player) getBukkitEntity()).callEvent()) { ++ this.sleepCounter = Integer.MIN_VALUE; ++ } ++ } ++ // Paper end - Add PlayerDeepSleepEvent + if (this.sleepCounter > 100) { + this.sleepCounter = 100; + } diff --git a/patches/server/0543-fix-PlayerItemHeldEvent-firing-twice.patch b/patches/server/0543-fix-PlayerItemHeldEvent-firing-twice.patch deleted file mode 100644 index 0fbae7a4aeed..000000000000 --- a/patches/server/0543-fix-PlayerItemHeldEvent-firing-twice.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: chickeneer -Date: Thu, 22 Apr 2021 19:02:07 -0700 -Subject: [PATCH] fix PlayerItemHeldEvent firing twice - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index f0551a35583e05e3e0437eda6e78f9672d00648f..c460baee67b23bd00dba149518d8afdb33743e3a 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1921,6 +1921,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (this.player.isImmobile()) return; // CraftBukkit - if (packet.getSlot() >= 0 && packet.getSlot() < Inventory.getSelectionSize()) { -+ if (packet.getSlot() == this.player.getInventory().selected) { return; } // Paper - don't fire itemheldevent when there wasn't a slot change - PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getCraftPlayer(), this.player.getInventory().selected, packet.getSlot()); - this.cserver.getPluginManager().callEvent(event); - if (event.isCancelled()) { diff --git a/patches/server/0544-Add-PlayerDeepSleepEvent.patch b/patches/server/0544-Add-PlayerDeepSleepEvent.patch deleted file mode 100644 index fe75f0e3c213..000000000000 --- a/patches/server/0544-Add-PlayerDeepSleepEvent.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 21 Apr 2021 15:58:19 -0700 -Subject: [PATCH] Add PlayerDeepSleepEvent - - -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 10caa677309c5a8191830c98597468079e784459..f47874dc9270d177aa7c39266e36713d0c934640 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -246,6 +246,13 @@ public abstract class Player extends LivingEntity { - - if (this.isSleeping()) { - ++this.sleepCounter; -+ // Paper start - Add PlayerDeepSleepEvent -+ if (this.sleepCounter == SLEEP_DURATION) { -+ if (!new io.papermc.paper.event.player.PlayerDeepSleepEvent((org.bukkit.entity.Player) getBukkitEntity()).callEvent()) { -+ this.sleepCounter = Integer.MIN_VALUE; -+ } -+ } -+ // Paper end - Add PlayerDeepSleepEvent - if (this.sleepCounter > 100) { - this.sleepCounter = 100; - } diff --git a/patches/server/0544-More-World-API.patch b/patches/server/0544-More-World-API.patch new file mode 100644 index 000000000000..7f887ba98d84 --- /dev/null +++ b/patches/server/0544-More-World-API.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 7 Jul 2020 10:52:34 -0700 +Subject: [PATCH] More World API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 9d44a2ce6da1b6338e5a1aaa9238483b64c9a34f..c90cbf4681e1c57fcec553b01d99a26316f896e5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2110,6 +2110,53 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return new CraftStructureSearchResult(CraftStructure.minecraftToBukkit(found.getSecond().value()), CraftLocation.toBukkit(found.getFirst(), this)); + } + ++ // Paper start ++ @Override ++ public boolean isUltrawarm() { ++ return getHandle().dimensionType().ultraWarm(); ++ } ++ ++ @Override ++ public double getCoordinateScale() { ++ return getHandle().dimensionType().coordinateScale(); ++ } ++ ++ @Override ++ public boolean hasSkylight() { ++ return getHandle().dimensionType().hasSkyLight(); ++ } ++ ++ @Override ++ public boolean hasBedrockCeiling() { ++ return getHandle().dimensionType().hasSkyLight(); ++ } ++ ++ @Override ++ public boolean doesBedWork() { ++ return getHandle().dimensionType().bedWorks(); ++ } ++ ++ @Override ++ public boolean doesRespawnAnchorWork() { ++ return getHandle().dimensionType().respawnAnchorWorks(); ++ } ++ ++ @Override ++ public boolean isFixedTime() { ++ return getHandle().dimensionType().hasFixedTime(); ++ } ++ ++ @Override ++ public Collection getInfiniburn() { ++ return com.google.common.collect.Sets.newHashSet(com.google.common.collect.Iterators.transform(net.minecraft.core.registries.BuiltInRegistries.BLOCK.getTagOrEmpty(this.getHandle().dimensionType().infiniburn()).iterator(), blockHolder -> CraftBlockType.minecraftToBukkit(blockHolder.value()))); ++ } ++ ++ @Override ++ public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { ++ getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); ++ } ++ // Paper end ++ + @Override + public BiomeSearchResult locateNearestBiome(Location origin, int radius, Biome... biomes) { + return this.locateNearestBiome(origin, radius, 32, 64, biomes); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java +index 3071ac1ac0e733d73dade49597a39f7d156bbc04..967445b2eb158454100a27369a1f463d69f54f27 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java +@@ -12,4 +12,13 @@ public final class CraftVector { + public static net.minecraft.world.phys.Vec3 toNMS(org.bukkit.util.Vector bukkit) { + return new net.minecraft.world.phys.Vec3(bukkit.getX(), bukkit.getY(), bukkit.getZ()); + } ++ // Paper start ++ public static org.bukkit.util.Vector toBukkit(net.minecraft.core.BlockPos blockPosition) { ++ return new org.bukkit.util.Vector(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); ++ } ++ ++ public static net.minecraft.core.BlockPos toBlockPos(org.bukkit.util.Vector bukkit) { ++ return net.minecraft.core.BlockPos.containing(bukkit.getX(), bukkit.getY(), bukkit.getZ()); ++ } ++ // Paper end + } diff --git a/patches/server/0546-Add-PlayerBedFailEnterEvent.patch b/patches/server/0545-Add-PlayerBedFailEnterEvent.patch similarity index 100% rename from patches/server/0546-Add-PlayerBedFailEnterEvent.patch rename to patches/server/0545-Add-PlayerBedFailEnterEvent.patch diff --git a/patches/server/0545-More-World-API.patch b/patches/server/0545-More-World-API.patch deleted file mode 100644 index e1ff14e424bc..000000000000 --- a/patches/server/0545-More-World-API.patch +++ /dev/null @@ -1,82 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 7 Jul 2020 10:52:34 -0700 -Subject: [PATCH] More World API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 75387d9507add359e7b35527c6a69b6a96cdff5c..6724d0b4e857a9671eac89445ceb70c070b29929 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -2097,6 +2097,53 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return new CraftStructureSearchResult(CraftStructure.minecraftToBukkit(found.getSecond().value()), CraftLocation.toBukkit(found.getFirst(), this)); - } - -+ // Paper start -+ @Override -+ public boolean isUltrawarm() { -+ return getHandle().dimensionType().ultraWarm(); -+ } -+ -+ @Override -+ public double getCoordinateScale() { -+ return getHandle().dimensionType().coordinateScale(); -+ } -+ -+ @Override -+ public boolean hasSkylight() { -+ return getHandle().dimensionType().hasSkyLight(); -+ } -+ -+ @Override -+ public boolean hasBedrockCeiling() { -+ return getHandle().dimensionType().hasSkyLight(); -+ } -+ -+ @Override -+ public boolean doesBedWork() { -+ return getHandle().dimensionType().bedWorks(); -+ } -+ -+ @Override -+ public boolean doesRespawnAnchorWork() { -+ return getHandle().dimensionType().respawnAnchorWorks(); -+ } -+ -+ @Override -+ public boolean isFixedTime() { -+ return getHandle().dimensionType().hasFixedTime(); -+ } -+ -+ @Override -+ public Collection getInfiniburn() { -+ return com.google.common.collect.Sets.newHashSet(com.google.common.collect.Iterators.transform(net.minecraft.core.registries.BuiltInRegistries.BLOCK.getTagOrEmpty(this.getHandle().dimensionType().infiniburn()).iterator(), blockHolder -> CraftBlockType.minecraftToBukkit(blockHolder.value()))); -+ } -+ -+ @Override -+ public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { -+ getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); -+ } -+ // Paper end -+ - @Override - public BiomeSearchResult locateNearestBiome(Location origin, int radius, Biome... biomes) { - return this.locateNearestBiome(origin, radius, 32, 64, biomes); -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java -index 3071ac1ac0e733d73dade49597a39f7d156bbc04..967445b2eb158454100a27369a1f463d69f54f27 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java -@@ -12,4 +12,13 @@ public final class CraftVector { - public static net.minecraft.world.phys.Vec3 toNMS(org.bukkit.util.Vector bukkit) { - return new net.minecraft.world.phys.Vec3(bukkit.getX(), bukkit.getY(), bukkit.getZ()); - } -+ // Paper start -+ public static org.bukkit.util.Vector toBukkit(net.minecraft.core.BlockPos blockPosition) { -+ return new org.bukkit.util.Vector(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); -+ } -+ -+ public static net.minecraft.core.BlockPos toBlockPos(org.bukkit.util.Vector bukkit) { -+ return net.minecraft.core.BlockPos.containing(bukkit.getX(), bukkit.getY(), bukkit.getZ()); -+ } -+ // Paper end - } diff --git a/patches/server/0546-Implement-methods-to-convert-between-Component-and-B.patch b/patches/server/0546-Implement-methods-to-convert-between-Component-and-B.patch new file mode 100644 index 000000000000..ebc86dc755cb --- /dev/null +++ b/patches/server/0546-Implement-methods-to-convert-between-Component-and-B.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Sat, 24 Apr 2021 02:09:32 -0700 +Subject: [PATCH] Implement methods to convert between Component and + Brigadier's Message + + +diff --git a/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dd6012b6a097575b2d1471be5069eccee4537c0a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java +@@ -0,0 +1,30 @@ ++package io.papermc.paper.brigadier; ++ ++import com.mojang.brigadier.Message; ++import io.papermc.paper.adventure.PaperAdventure; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.ComponentLike; ++import net.minecraft.network.chat.ComponentUtils; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++import static java.util.Objects.requireNonNull; ++ ++public enum PaperBrigadierProviderImpl implements PaperBrigadierProvider { ++ INSTANCE; ++ ++ PaperBrigadierProviderImpl() { ++ PaperBrigadierProvider.initialize(this); ++ } ++ ++ @Override ++ public @NonNull Message message(final @NonNull ComponentLike componentLike) { ++ requireNonNull(componentLike, "componentLike"); ++ return PaperAdventure.asVanilla(componentLike.asComponent()); ++ } ++ ++ @Override ++ public @NonNull Component componentFromMessage(final @NonNull Message message) { ++ requireNonNull(message, "message"); ++ return PaperAdventure.asAdventure(ComponentUtils.fromMessage(message)); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index dd13627f140ab93901a32901eeb51488421ae91f..a78b14b828526549e1309e94c271667043129a87 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -210,6 +210,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now ++ io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // Paper - init PaperBrigadierProvider + + this.setPvpAllowed(dedicatedserverproperties.pvp); + this.setFlightAllowed(dedicatedserverproperties.allowFlight); diff --git a/patches/server/0547-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch b/patches/server/0547-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch new file mode 100644 index 000000000000..03b096e682f4 --- /dev/null +++ b/patches/server/0547-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Fri, 23 Apr 2021 22:42:42 +0100 +Subject: [PATCH] Expand PlayerRespawnEvent, fix passed parameter issues + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 8fcb3361c510cb1ef0b6405d0077c82ba155d586..2004712adede7e3307785389dd7aa4ba5465110f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2628,7 +2628,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + case PERFORM_RESPAWN: + if (this.player.wonGame) { + this.player.wonGame = false; +- this.player = this.server.getPlayerList().respawn(this.player, true, RespawnReason.END_PORTAL); ++ this.player = this.server.getPlayerList().respawn(this.player, this.server.getLevel(this.player.getRespawnDimension()), true, null, true, RespawnReason.END_PORTAL, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL); // Paper - Expand PlayerRespawnEvent + CriteriaTriggers.CHANGED_DIMENSION.trigger(this.player, Level.END, Level.OVERWORLD); + } else { + if (this.player.getHealth() > 0.0F) { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 0ec9312fb2992aa2a7972734cd4be4b1621c594a..27ae2ac95d4f53c1c16b35f737fa6c138ddcc644 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -779,6 +779,12 @@ public abstract class PlayerList { + } + + public ServerPlayer respawn(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation, RespawnReason reason) { ++ // Paper start - Expand PlayerRespawnEvent ++ return respawn(entityplayer, worldserver, flag, location, avoidSuffocation, reason, new org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag[0]); ++ } ++ ++ public ServerPlayer respawn(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation, RespawnReason reason, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag...respawnFlags) { ++ // Paper end - Expand PlayerRespawnEvent + entityplayer.stopRiding(); // CraftBukkit + this.players.remove(entityplayer); + this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot +@@ -820,6 +826,7 @@ public abstract class PlayerList { + + // Paper start - Add PlayerPostRespawnEvent + boolean isBedSpawn = false; ++ boolean isAnchorSpawn = false; // Paper - Fix PlayerRespawnEvent + boolean isRespawn = false; + boolean isLocAltered = false; // Paper - Fix SPIGOT-5989 + // Paper end - Add PlayerPostRespawnEvent +@@ -840,6 +847,7 @@ public abstract class PlayerList { + if (optional.isPresent()) { + BlockState iblockdata = worldserver1.getBlockState(blockposition); + boolean flag3 = iblockdata.is(Blocks.RESPAWN_ANCHOR); ++ isAnchorSpawn = flag3; // Paper - Fix PlayerRespawnEvent + Vec3 vec3d = (Vec3) optional.get(); + float f1; + +@@ -868,7 +876,7 @@ public abstract class PlayerList { + } + + Player respawnPlayer = entityplayer1.getBukkitEntity(); +- PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !flag2, flag2, reason); ++ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !isAnchorSpawn, isAnchorSpawn, reason, com.google.common.collect.ImmutableSet.builder().add(respawnFlags)); // Paper - PlayerRespawnEvent changes + this.cserver.getPluginManager().callEvent(respawnEvent); + // Spigot Start + if (entityplayer.connection.isDisconnected()) { diff --git a/patches/server/0547-Implement-methods-to-convert-between-Component-and-B.patch b/patches/server/0547-Implement-methods-to-convert-between-Component-and-B.patch deleted file mode 100644 index 74bdc39e9b8e..000000000000 --- a/patches/server/0547-Implement-methods-to-convert-between-Component-and-B.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sat, 24 Apr 2021 02:09:32 -0700 -Subject: [PATCH] Implement methods to convert between Component and - Brigadier's Message - - -diff --git a/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dd6012b6a097575b2d1471be5069eccee4537c0a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java -@@ -0,0 +1,30 @@ -+package io.papermc.paper.brigadier; -+ -+import com.mojang.brigadier.Message; -+import io.papermc.paper.adventure.PaperAdventure; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.ComponentLike; -+import net.minecraft.network.chat.ComponentUtils; -+import org.checkerframework.checker.nullness.qual.NonNull; -+ -+import static java.util.Objects.requireNonNull; -+ -+public enum PaperBrigadierProviderImpl implements PaperBrigadierProvider { -+ INSTANCE; -+ -+ PaperBrigadierProviderImpl() { -+ PaperBrigadierProvider.initialize(this); -+ } -+ -+ @Override -+ public @NonNull Message message(final @NonNull ComponentLike componentLike) { -+ requireNonNull(componentLike, "componentLike"); -+ return PaperAdventure.asVanilla(componentLike.asComponent()); -+ } -+ -+ @Override -+ public @NonNull Component componentFromMessage(final @NonNull Message message) { -+ requireNonNull(message, "message"); -+ return PaperAdventure.asAdventure(ComponentUtils.fromMessage(message)); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 46b581e1f930eda3671a283ad89ed1c2094e2cd6..959ce2efe156c8ff2e3d1b57cde27f4da548c3ef 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -210,6 +210,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command - com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics - com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now -+ io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // Paper - init PaperBrigadierProvider - - this.setPvpAllowed(dedicatedserverproperties.pvp); - this.setFlightAllowed(dedicatedserverproperties.allowFlight); diff --git a/patches/server/0548-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch b/patches/server/0548-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch deleted file mode 100644 index 967badf8e028..000000000000 --- a/patches/server/0548-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch +++ /dev/null @@ -1,62 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HexedHero <6012891+HexedHero@users.noreply.github.com> -Date: Fri, 23 Apr 2021 22:42:42 +0100 -Subject: [PATCH] Expand PlayerRespawnEvent, fix passed parameter issues - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index c460baee67b23bd00dba149518d8afdb33743e3a..189b16f5c7bad3540671019136ed0ef8dfaacf74 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2628,7 +2628,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - case PERFORM_RESPAWN: - if (this.player.wonGame) { - this.player.wonGame = false; -- this.player = this.server.getPlayerList().respawn(this.player, true, RespawnReason.END_PORTAL); -+ this.player = this.server.getPlayerList().respawn(this.player, this.server.getLevel(this.player.getRespawnDimension()), true, null, true, RespawnReason.END_PORTAL, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL); // Paper - Expand PlayerRespawnEvent - CriteriaTriggers.CHANGED_DIMENSION.trigger(this.player, Level.END, Level.OVERWORLD); - } else { - if (this.player.getHealth() > 0.0F) { -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 0ec9312fb2992aa2a7972734cd4be4b1621c594a..27ae2ac95d4f53c1c16b35f737fa6c138ddcc644 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -779,6 +779,12 @@ public abstract class PlayerList { - } - - public ServerPlayer respawn(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation, RespawnReason reason) { -+ // Paper start - Expand PlayerRespawnEvent -+ return respawn(entityplayer, worldserver, flag, location, avoidSuffocation, reason, new org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag[0]); -+ } -+ -+ public ServerPlayer respawn(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation, RespawnReason reason, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag...respawnFlags) { -+ // Paper end - Expand PlayerRespawnEvent - entityplayer.stopRiding(); // CraftBukkit - this.players.remove(entityplayer); - this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot -@@ -820,6 +826,7 @@ public abstract class PlayerList { - - // Paper start - Add PlayerPostRespawnEvent - boolean isBedSpawn = false; -+ boolean isAnchorSpawn = false; // Paper - Fix PlayerRespawnEvent - boolean isRespawn = false; - boolean isLocAltered = false; // Paper - Fix SPIGOT-5989 - // Paper end - Add PlayerPostRespawnEvent -@@ -840,6 +847,7 @@ public abstract class PlayerList { - if (optional.isPresent()) { - BlockState iblockdata = worldserver1.getBlockState(blockposition); - boolean flag3 = iblockdata.is(Blocks.RESPAWN_ANCHOR); -+ isAnchorSpawn = flag3; // Paper - Fix PlayerRespawnEvent - Vec3 vec3d = (Vec3) optional.get(); - float f1; - -@@ -868,7 +876,7 @@ public abstract class PlayerList { - } - - Player respawnPlayer = entityplayer1.getBukkitEntity(); -- PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !flag2, flag2, reason); -+ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !isAnchorSpawn, isAnchorSpawn, reason, com.google.common.collect.ImmutableSet.builder().add(respawnFlags)); // Paper - PlayerRespawnEvent changes - this.cserver.getPluginManager().callEvent(respawnEvent); - // Spigot Start - if (entityplayer.connection.isDisconnected()) { diff --git a/patches/server/0548-Introduce-beacon-activation-deactivation-events.patch b/patches/server/0548-Introduce-beacon-activation-deactivation-events.patch new file mode 100644 index 000000000000..f793b533a269 --- /dev/null +++ b/patches/server/0548-Introduce-beacon-activation-deactivation-events.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spyridon Pagkalos +Date: Thu, 25 Mar 2021 20:28:04 +0200 +Subject: [PATCH] Introduce beacon activation/deactivation events + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index 49f25826aea528d9da085b9e65cb4c85cd78c415..61a618f09af475407a78343eecb4352052b1df1e 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -221,6 +221,15 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); + } + } ++ // Paper start - beacon activation/deactivation events ++ if (i1 <= 0 && blockEntity.levels > 0) { ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ new io.papermc.paper.event.block.BeaconActivatedEvent(block).callEvent(); ++ } else if (i1 > 0 && blockEntity.levels <= 0) { ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); ++ } ++ // Paper end - beacon activation/deactivation events + + if (blockEntity.lastCheckY >= l) { + blockEntity.lastCheckY = world.getMinBuildHeight() - 1; +@@ -278,6 +287,10 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + + @Override + public void setRemoved() { ++ // Paper start - beacon activation/deactivation events ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); ++ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); ++ // Paper end - beacon activation/deactivation events + BeaconBlockEntity.playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE); + super.setRemoved(); + } diff --git a/patches/server/0549-Add-Channel-initialization-listeners.patch b/patches/server/0549-Add-Channel-initialization-listeners.patch new file mode 100644 index 000000000000..51eee16dd93b --- /dev/null +++ b/patches/server/0549-Add-Channel-initialization-listeners.patch @@ -0,0 +1,155 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 29 Apr 2021 21:19:33 +0200 +Subject: [PATCH] Add Channel initialization listeners + + +diff --git a/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java b/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java +new file mode 100644 +index 0000000000000000000000000000000000000000..88099df34c2d74daba9645aadf65b446ca795a91 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java +@@ -0,0 +1,15 @@ ++package io.papermc.paper.network; ++ ++import io.netty.channel.Channel; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++/** ++ * Internal API to register channel initialization listeners. ++ *

    ++ * This is not officially supported API and we make no guarantees to the existence or state of this interface. ++ */ ++@FunctionalInterface ++public interface ChannelInitializeListener { ++ ++ void afterInitChannel(@NonNull Channel channel); ++} +diff --git a/src/main/java/io/papermc/paper/network/ChannelInitializeListenerHolder.java b/src/main/java/io/papermc/paper/network/ChannelInitializeListenerHolder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..30e62719e0a83525daa33cf41cb61df360c0e046 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/network/ChannelInitializeListenerHolder.java +@@ -0,0 +1,74 @@ ++package io.papermc.paper.network; ++ ++import io.netty.channel.Channel; ++import net.kyori.adventure.key.Key; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++ ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.Map; ++ ++/** ++ * Internal API to register channel initialization listeners. ++ *

    ++ * This is not officially supported API and we make no guarantees to the existence or state of this class. ++ */ ++public final class ChannelInitializeListenerHolder { ++ ++ private static final Map LISTENERS = new HashMap<>(); ++ private static final Map IMMUTABLE_VIEW = Collections.unmodifiableMap(LISTENERS); ++ ++ private ChannelInitializeListenerHolder() { ++ } ++ ++ /** ++ * Registers whether an initialization listener is registered under the given key. ++ * ++ * @param key key ++ * @return whether an initialization listener is registered under the given key ++ */ ++ public static boolean hasListener(@NonNull Key key) { ++ return LISTENERS.containsKey(key); ++ } ++ ++ /** ++ * Registers a channel initialization listener called after ServerConnection is initialized. ++ * ++ * @param key key ++ * @param listener initialization listeners ++ */ ++ public static void addListener(@NonNull Key key, @NonNull ChannelInitializeListener listener) { ++ LISTENERS.put(key, listener); ++ } ++ ++ /** ++ * Removes and returns an initialization listener registered by the given key if present. ++ * ++ * @param key key ++ * @return removed initialization listener if present ++ */ ++ public static @Nullable ChannelInitializeListener removeListener(@NonNull Key key) { ++ return LISTENERS.remove(key); ++ } ++ ++ /** ++ * Returns an immutable map of registered initialization listeners. ++ * ++ * @return immutable map of registered initialization listeners ++ */ ++ public static @NonNull Map getListeners() { ++ return IMMUTABLE_VIEW; ++ } ++ ++ /** ++ * Calls the registered listeners with the given channel. ++ * ++ * @param channel channel ++ */ ++ public static void callListeners(@NonNull Channel channel) { ++ for (ChannelInitializeListener listener : LISTENERS.values()) { ++ listener.afterInitChannel(channel); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/network/ConnectionEvent.java b/src/main/java/io/papermc/paper/network/ConnectionEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0d7e7db9e37ef0183c32b217bd944fb4f41ab83a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/network/ConnectionEvent.java +@@ -0,0 +1,10 @@ ++package io.papermc.paper.network; ++ ++/** ++ * Internal connection pipeline events. ++ */ ++public enum ConnectionEvent { ++ ++ COMPRESSION_THRESHOLD_SET, ++ COMPRESSION_DISABLED ++} +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index ddc84de84c8a503a01e40c42fe83558af7159f8f..37b16918451859c22f92bcbcbce05c16b8beff75 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -633,6 +633,7 @@ public class Connection extends SimpleChannelInboundHandler> { + } else { + this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressionThreshold)); + } ++ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners + } else { + if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) { + this.channel.pipeline().remove("decompress"); +@@ -641,6 +642,7 @@ public class Connection extends SimpleChannelInboundHandler> { + if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) { + this.channel.pipeline().remove("compress"); + } ++ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_DISABLED); // Paper - Add Channel initialization listeners + } + + } +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index 3b6bafb242d2623c15f26acdacd036478c7dc214..25ddfe8e5da65e4ac70be2820ba139e7f3852c0c 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -113,6 +113,7 @@ public class ServerConnectionListener { + pending.add(object); // Paper - prevent blocking on adding a new connection while the server is ticking + ((Connection) object).configurePacketHandler(channelpipeline); + ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); ++ io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners + } + }).group(eventloopgroup).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit + } diff --git a/patches/server/0549-Introduce-beacon-activation-deactivation-events.patch b/patches/server/0549-Introduce-beacon-activation-deactivation-events.patch deleted file mode 100644 index e4a4750e9ebe..000000000000 --- a/patches/server/0549-Introduce-beacon-activation-deactivation-events.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spyridon Pagkalos -Date: Thu, 25 Mar 2021 20:28:04 +0200 -Subject: [PATCH] Introduce beacon activation/deactivation events - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index 80bd394920902cd67ef22bf9a14db64800a3ab97..2ceadc753eb99a08881f1292de789528f4a3de85 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -221,6 +221,15 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); - } - } -+ // Paper start - beacon activation/deactivation events -+ if (i1 <= 0 && blockEntity.levels > 0) { -+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); -+ new io.papermc.paper.event.block.BeaconActivatedEvent(block).callEvent(); -+ } else if (i1 > 0 && blockEntity.levels <= 0) { -+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); -+ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); -+ } -+ // Paper end - beacon activation/deactivation events - - if (blockEntity.lastCheckY >= l) { - blockEntity.lastCheckY = world.getMinBuildHeight() - 1; -@@ -278,6 +287,10 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - - @Override - public void setRemoved() { -+ // Paper start - beacon activation/deactivation events -+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); -+ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); -+ // Paper end - beacon activation/deactivation events - BeaconBlockEntity.playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE); - super.setRemoved(); - } diff --git a/patches/server/0550-Add-Channel-initialization-listeners.patch b/patches/server/0550-Add-Channel-initialization-listeners.patch deleted file mode 100644 index d3df58efe122..000000000000 --- a/patches/server/0550-Add-Channel-initialization-listeners.patch +++ /dev/null @@ -1,155 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Thu, 29 Apr 2021 21:19:33 +0200 -Subject: [PATCH] Add Channel initialization listeners - - -diff --git a/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java b/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java -new file mode 100644 -index 0000000000000000000000000000000000000000..88099df34c2d74daba9645aadf65b446ca795a91 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java -@@ -0,0 +1,15 @@ -+package io.papermc.paper.network; -+ -+import io.netty.channel.Channel; -+import org.checkerframework.checker.nullness.qual.NonNull; -+ -+/** -+ * Internal API to register channel initialization listeners. -+ *

    -+ * This is not officially supported API and we make no guarantees to the existence or state of this interface. -+ */ -+@FunctionalInterface -+public interface ChannelInitializeListener { -+ -+ void afterInitChannel(@NonNull Channel channel); -+} -diff --git a/src/main/java/io/papermc/paper/network/ChannelInitializeListenerHolder.java b/src/main/java/io/papermc/paper/network/ChannelInitializeListenerHolder.java -new file mode 100644 -index 0000000000000000000000000000000000000000..30e62719e0a83525daa33cf41cb61df360c0e046 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/network/ChannelInitializeListenerHolder.java -@@ -0,0 +1,74 @@ -+package io.papermc.paper.network; -+ -+import io.netty.channel.Channel; -+import net.kyori.adventure.key.Key; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+ -+import java.util.Collections; -+import java.util.HashMap; -+import java.util.Map; -+ -+/** -+ * Internal API to register channel initialization listeners. -+ *

    -+ * This is not officially supported API and we make no guarantees to the existence or state of this class. -+ */ -+public final class ChannelInitializeListenerHolder { -+ -+ private static final Map LISTENERS = new HashMap<>(); -+ private static final Map IMMUTABLE_VIEW = Collections.unmodifiableMap(LISTENERS); -+ -+ private ChannelInitializeListenerHolder() { -+ } -+ -+ /** -+ * Registers whether an initialization listener is registered under the given key. -+ * -+ * @param key key -+ * @return whether an initialization listener is registered under the given key -+ */ -+ public static boolean hasListener(@NonNull Key key) { -+ return LISTENERS.containsKey(key); -+ } -+ -+ /** -+ * Registers a channel initialization listener called after ServerConnection is initialized. -+ * -+ * @param key key -+ * @param listener initialization listeners -+ */ -+ public static void addListener(@NonNull Key key, @NonNull ChannelInitializeListener listener) { -+ LISTENERS.put(key, listener); -+ } -+ -+ /** -+ * Removes and returns an initialization listener registered by the given key if present. -+ * -+ * @param key key -+ * @return removed initialization listener if present -+ */ -+ public static @Nullable ChannelInitializeListener removeListener(@NonNull Key key) { -+ return LISTENERS.remove(key); -+ } -+ -+ /** -+ * Returns an immutable map of registered initialization listeners. -+ * -+ * @return immutable map of registered initialization listeners -+ */ -+ public static @NonNull Map getListeners() { -+ return IMMUTABLE_VIEW; -+ } -+ -+ /** -+ * Calls the registered listeners with the given channel. -+ * -+ * @param channel channel -+ */ -+ public static void callListeners(@NonNull Channel channel) { -+ for (ChannelInitializeListener listener : LISTENERS.values()) { -+ listener.afterInitChannel(channel); -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/network/ConnectionEvent.java b/src/main/java/io/papermc/paper/network/ConnectionEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0d7e7db9e37ef0183c32b217bd944fb4f41ab83a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/network/ConnectionEvent.java -@@ -0,0 +1,10 @@ -+package io.papermc.paper.network; -+ -+/** -+ * Internal connection pipeline events. -+ */ -+public enum ConnectionEvent { -+ -+ COMPRESSION_THRESHOLD_SET, -+ COMPRESSION_DISABLED -+} -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 1e495daf53a53260e1a3b1c86365edb9abad1e80..c7e4d38f67a196b6334e0cc2b9ce9bd96fdc5b0a 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -633,6 +633,7 @@ public class Connection extends SimpleChannelInboundHandler> { - } else { - this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressionThreshold)); - } -+ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners - } else { - if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) { - this.channel.pipeline().remove("decompress"); -@@ -641,6 +642,7 @@ public class Connection extends SimpleChannelInboundHandler> { - if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) { - this.channel.pipeline().remove("compress"); - } -+ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_DISABLED); // Paper - Add Channel initialization listeners - } - - } -diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -index 3b6bafb242d2623c15f26acdacd036478c7dc214..25ddfe8e5da65e4ac70be2820ba139e7f3852c0c 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -@@ -113,6 +113,7 @@ public class ServerConnectionListener { - pending.add(object); // Paper - prevent blocking on adding a new connection while the server is ticking - ((Connection) object).configurePacketHandler(channelpipeline); - ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); -+ io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners - } - }).group(eventloopgroup).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit - } diff --git a/patches/server/0551-Send-empty-commands-if-tab-completion-is-disabled.patch b/patches/server/0550-Send-empty-commands-if-tab-completion-is-disabled.patch similarity index 100% rename from patches/server/0551-Send-empty-commands-if-tab-completion-is-disabled.patch rename to patches/server/0550-Send-empty-commands-if-tab-completion-is-disabled.patch diff --git a/patches/server/0552-Add-more-WanderingTrader-API.patch b/patches/server/0551-Add-more-WanderingTrader-API.patch similarity index 100% rename from patches/server/0552-Add-more-WanderingTrader-API.patch rename to patches/server/0551-Add-more-WanderingTrader-API.patch diff --git a/patches/server/0553-Add-EntityBlockStorage-clearEntities.patch b/patches/server/0552-Add-EntityBlockStorage-clearEntities.patch similarity index 100% rename from patches/server/0553-Add-EntityBlockStorage-clearEntities.patch rename to patches/server/0552-Add-EntityBlockStorage-clearEntities.patch diff --git a/patches/server/0554-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch b/patches/server/0553-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch similarity index 100% rename from patches/server/0554-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch rename to patches/server/0553-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch diff --git a/patches/server/0555-Add-HiddenPotionEffect-API.patch b/patches/server/0554-Add-HiddenPotionEffect-API.patch similarity index 100% rename from patches/server/0555-Add-HiddenPotionEffect-API.patch rename to patches/server/0554-Add-HiddenPotionEffect-API.patch diff --git a/patches/server/0556-Inventory-close.patch b/patches/server/0555-Inventory-close.patch similarity index 100% rename from patches/server/0556-Inventory-close.patch rename to patches/server/0555-Inventory-close.patch diff --git a/patches/server/0557-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch b/patches/server/0556-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch similarity index 100% rename from patches/server/0557-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch rename to patches/server/0556-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch diff --git a/patches/server/0557-Add-basic-Datapack-API.patch b/patches/server/0557-Add-basic-Datapack-API.patch new file mode 100644 index 000000000000..93aebb5a9460 --- /dev/null +++ b/patches/server/0557-Add-basic-Datapack-API.patch @@ -0,0 +1,125 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Connor Linfoot +Date: Sun, 16 May 2021 15:07:34 +0100 +Subject: [PATCH] Add basic Datapack API + + +diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapack.java b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9b7dd8a0fba4547f5268b3f99e21ddbe6b5bf566 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java +@@ -0,0 +1,50 @@ ++package io.papermc.paper.datapack; ++ ++import io.papermc.paper.event.server.ServerResourcesReloadedEvent; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.packs.repository.Pack; ++import java.util.List; ++import java.util.stream.Collectors; ++ ++public class PaperDatapack implements Datapack { ++ private final String name; ++ private final Compatibility compatibility; ++ private final boolean enabled; ++ ++ PaperDatapack(Pack loader, boolean enabled) { ++ this.name = loader.getId(); ++ this.compatibility = Compatibility.valueOf(loader.getCompatibility().name()); ++ this.enabled = enabled; ++ } ++ ++ @Override ++ public String getName() { ++ return name; ++ } ++ ++ @Override ++ public Compatibility getCompatibility() { ++ return compatibility; ++ } ++ ++ @Override ++ public boolean isEnabled() { ++ return enabled; ++ } ++ ++ @Override ++ public void setEnabled(boolean enabled) { ++ if (enabled == this.enabled) { ++ return; ++ } ++ ++ MinecraftServer server = MinecraftServer.getServer(); ++ List enabledKeys = server.getPackRepository().getSelectedPacks().stream().map(Pack::getId).collect(Collectors.toList()); ++ if (enabled) { ++ enabledKeys.add(this.name); ++ } else { ++ enabledKeys.remove(this.name); ++ } ++ server.reloadResources(enabledKeys, ServerResourcesReloadedEvent.Cause.PLUGIN); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cf4374493c11057451a62a655514415cf6b298e0 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java +@@ -0,0 +1,25 @@ ++package io.papermc.paper.datapack; ++ ++import java.util.Collection; ++import java.util.stream.Collectors; ++import net.minecraft.server.packs.repository.Pack; ++import net.minecraft.server.packs.repository.PackRepository; ++ ++public class PaperDatapackManager implements DatapackManager { ++ private final PackRepository repository; ++ ++ public PaperDatapackManager(PackRepository repository) { ++ this.repository = repository; ++ } ++ ++ @Override ++ public Collection getPacks() { ++ Collection enabledPacks = repository.getSelectedPacks(); ++ return repository.getAvailablePacks().stream().map(loader -> new PaperDatapack(loader, enabledPacks.contains(loader))).collect(Collectors.toList()); ++ } ++ ++ @Override ++ public Collection getEnabledPacks() { ++ return repository.getSelectedPacks().stream().map(loader -> new PaperDatapack(loader, true)).collect(Collectors.toList()); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index b453673f7bab09a1b10898a7d1f85d133e63ac72..236e73eed6caac7f98236ca418185a143f78c5fa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -303,6 +303,7 @@ public final class CraftServer implements Server { + public boolean ignoreVanillaPermissions = false; + private final List playerView; + public int reloadCount; ++ private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper + public static Exception excessiveVelEx; // Paper - Velocity warnings + + static { +@@ -387,6 +388,7 @@ public final class CraftServer implements Server { + if (this.configuration.getBoolean("settings.use-map-color-cache")) { + MapPalette.setMapColorCache(new CraftMapColorCache(this.logger)); + } ++ datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper + } + + public boolean getCommandBlockOverride(String command) { +@@ -2957,5 +2959,11 @@ public final class CraftServer implements Server { + public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { + return mobGoals; + } ++ ++ @Override ++ public io.papermc.paper.datapack.PaperDatapackManager getDatapackManager() { ++ return datapackManager; ++ } ++ + // Paper end + } diff --git a/patches/server/0558-Add-basic-Datapack-API.patch b/patches/server/0558-Add-basic-Datapack-API.patch deleted file mode 100644 index 3716abc0b249..000000000000 --- a/patches/server/0558-Add-basic-Datapack-API.patch +++ /dev/null @@ -1,125 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Connor Linfoot -Date: Sun, 16 May 2021 15:07:34 +0100 -Subject: [PATCH] Add basic Datapack API - - -diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapack.java b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9b7dd8a0fba4547f5268b3f99e21ddbe6b5bf566 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java -@@ -0,0 +1,50 @@ -+package io.papermc.paper.datapack; -+ -+import io.papermc.paper.event.server.ServerResourcesReloadedEvent; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.packs.repository.Pack; -+import java.util.List; -+import java.util.stream.Collectors; -+ -+public class PaperDatapack implements Datapack { -+ private final String name; -+ private final Compatibility compatibility; -+ private final boolean enabled; -+ -+ PaperDatapack(Pack loader, boolean enabled) { -+ this.name = loader.getId(); -+ this.compatibility = Compatibility.valueOf(loader.getCompatibility().name()); -+ this.enabled = enabled; -+ } -+ -+ @Override -+ public String getName() { -+ return name; -+ } -+ -+ @Override -+ public Compatibility getCompatibility() { -+ return compatibility; -+ } -+ -+ @Override -+ public boolean isEnabled() { -+ return enabled; -+ } -+ -+ @Override -+ public void setEnabled(boolean enabled) { -+ if (enabled == this.enabled) { -+ return; -+ } -+ -+ MinecraftServer server = MinecraftServer.getServer(); -+ List enabledKeys = server.getPackRepository().getSelectedPacks().stream().map(Pack::getId).collect(Collectors.toList()); -+ if (enabled) { -+ enabledKeys.add(this.name); -+ } else { -+ enabledKeys.remove(this.name); -+ } -+ server.reloadResources(enabledKeys, ServerResourcesReloadedEvent.Cause.PLUGIN); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cf4374493c11057451a62a655514415cf6b298e0 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java -@@ -0,0 +1,25 @@ -+package io.papermc.paper.datapack; -+ -+import java.util.Collection; -+import java.util.stream.Collectors; -+import net.minecraft.server.packs.repository.Pack; -+import net.minecraft.server.packs.repository.PackRepository; -+ -+public class PaperDatapackManager implements DatapackManager { -+ private final PackRepository repository; -+ -+ public PaperDatapackManager(PackRepository repository) { -+ this.repository = repository; -+ } -+ -+ @Override -+ public Collection getPacks() { -+ Collection enabledPacks = repository.getSelectedPacks(); -+ return repository.getAvailablePacks().stream().map(loader -> new PaperDatapack(loader, enabledPacks.contains(loader))).collect(Collectors.toList()); -+ } -+ -+ @Override -+ public Collection getEnabledPacks() { -+ return repository.getSelectedPacks().stream().map(loader -> new PaperDatapack(loader, true)).collect(Collectors.toList()); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 4c84e41e12ab81e374a6ba831e078bd538d7afbc..50a79cefb97a33bc59d7c706a45bbdb0e739147c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -301,6 +301,7 @@ public final class CraftServer implements Server { - public boolean ignoreVanillaPermissions = false; - private final List playerView; - public int reloadCount; -+ private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper - public static Exception excessiveVelEx; // Paper - Velocity warnings - - static { -@@ -385,6 +386,7 @@ public final class CraftServer implements Server { - if (this.configuration.getBoolean("settings.use-map-color-cache")) { - MapPalette.setMapColorCache(new CraftMapColorCache(this.logger)); - } -+ datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper - } - - public boolean getCommandBlockOverride(String command) { -@@ -2945,5 +2947,11 @@ public final class CraftServer implements Server { - public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { - return mobGoals; - } -+ -+ @Override -+ public io.papermc.paper.datapack.PaperDatapackManager getDatapackManager() { -+ return datapackManager; -+ } -+ - // Paper end - } diff --git a/patches/server/0559-Add-environment-variable-to-disable-server-gui.patch b/patches/server/0558-Add-environment-variable-to-disable-server-gui.patch similarity index 100% rename from patches/server/0559-Add-environment-variable-to-disable-server-gui.patch rename to patches/server/0558-Add-environment-variable-to-disable-server-gui.patch diff --git a/patches/server/0559-Expand-PlayerGameModeChangeEvent.patch b/patches/server/0559-Expand-PlayerGameModeChangeEvent.patch new file mode 100644 index 000000000000..0b100fa9e9a1 --- /dev/null +++ b/patches/server/0559-Expand-PlayerGameModeChangeEvent.patch @@ -0,0 +1,158 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 15 May 2021 10:04:43 -0700 +Subject: [PATCH] Expand PlayerGameModeChangeEvent + + +diff --git a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java +index 50e581613156cca765dfd8e3596dcb3b58520cec..21d6a2a5d1722d44146384c28a3cba2df9b42771 100644 +--- a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java ++++ b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java +@@ -25,9 +25,13 @@ public class DefaultGameModeCommands { + GameType gameType = minecraftServer.getForcedGameType(); + if (gameType != null) { + for(ServerPlayer serverPlayer : minecraftServer.getPlayerList().getPlayers()) { +- if (serverPlayer.setGameMode(gameType)) { +- ++i; ++ // Paper start - Expand PlayerGameModeChangeEvent ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty()); ++ if (event != null && event.isCancelled()) { ++ source.sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false); + } ++ // Paper end - Expand PlayerGameModeChangeEvent ++ ++i; + } + } + +diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java +index aee8618e27b893b72931e925724dd683d2e6d2aa..f7c9127346261d83413ca03a1cdaa84975ae17d6 100644 +--- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java ++++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java +@@ -48,9 +48,14 @@ public class GameModeCommand { + int i = 0; + + for(ServerPlayer serverPlayer : targets) { +- if (serverPlayer.setGameMode(gameMode)) { ++ // Paper start - Expand PlayerGameModeChangeEvent ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty()); ++ if (event != null && !event.isCancelled()) { + logGamemodeChange(context.getSource(), serverPlayer, gameMode); + ++i; ++ } else if (event != null && event.cancelMessage() != null) { ++ context.getSource().sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true); ++ // Paper end - Expand PlayerGameModeChangeEvent + } + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 8c65b05e66372cf3c92f823d72e94c18fe77622b..a09ef51e94a0bf9f51bf358e7cf77dd5d272aab2 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1940,8 +1940,16 @@ public class ServerPlayer extends Player { + } + + public boolean setGameMode(GameType gameMode) { +- if (!this.gameMode.changeGameModeForPlayer(gameMode)) { +- return false; ++ // Paper start - Expand PlayerGameModeChangeEvent ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); ++ return event == null ? false : event.isCancelled(); ++ } ++ @Nullable ++ public org.bukkit.event.player.PlayerGameModeChangeEvent setGameMode(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component message) { ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.gameMode.changeGameModeForPlayer(gameMode, cause, message); ++ if (event == null || event.isCancelled()) { ++ return null; ++ // Paper end - Expand PlayerGameModeChangeEvent + } else { + this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) gameMode.getId())); + if (gameMode == GameType.SPECTATOR) { +@@ -1953,7 +1961,7 @@ public class ServerPlayer extends Player { + + this.onUpdateAbilities(); + this.updateEffectVisibility(); +- return true; ++ return event; // Paper - Expand PlayerGameModeChangeEvent + } + } + +@@ -2365,6 +2373,16 @@ public class ServerPlayer extends Player { + } + + public void loadGameTypes(@Nullable CompoundTag nbt) { ++ // Paper start - Expand PlayerGameModeChangeEvent ++ if (this.server.getForcedGameType() != null && this.server.getForcedGameType() != ServerPlayer.readPlayerMode(nbt, "playerGameType")) { ++ if (new org.bukkit.event.player.PlayerGameModeChangeEvent(this.getBukkitEntity(), org.bukkit.GameMode.getByValue(this.server.getDefaultGameType().getId()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null).callEvent()) { ++ this.gameMode.setGameModeForPlayer(this.server.getForcedGameType(), GameType.DEFAULT_MODE); ++ } else { ++ this.gameMode.setGameModeForPlayer(ServerPlayer.readPlayerMode(nbt,"playerGameType"), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); ++ } ++ return; ++ } ++ // Paper end - Expand PlayerGameModeChangeEvent + this.gameMode.setGameModeForPlayer(this.calculateGameModeForNewPlayer(ServerPlayer.readPlayerMode(nbt, "playerGameType")), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index a03d1a85019afdc42de2b8449fc38384c4dac51e..4fe571915b247ec612b2376dce57991e441f63c2 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -73,21 +73,28 @@ public class ServerPlayerGameMode { + } + + public boolean changeGameModeForPlayer(GameType gameMode) { ++ // Paper start - Expand PlayerGameModeChangeEvent ++ PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); ++ return event != null && event.isCancelled(); ++ } ++ @Nullable ++ public PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component cancelMessage) { ++ // Paper end - Expand PlayerGameModeChangeEvent + if (gameMode == this.gameModeForPlayer) { +- return false; ++ return null; // Paper - Expand PlayerGameModeChangeEvent + } else { + // CraftBukkit start +- PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId())); ++ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId()), cause, cancelMessage); // Paper + this.level.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { +- return false; ++ return event; // Paper - Expand PlayerGameModeChangeEvent + } + // CraftBukkit end + this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer); + this.player.onUpdateAbilities(); + this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit + this.level.updateSleepingPlayerList(); +- return true; ++ return event; // Paper - Expand PlayerGameModeChangeEvent + } + } + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 2004712adede7e3307785389dd7aa4ba5465110f..c54a1c2bf8719791047eb9ccc6cbe26c7541f125 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2637,7 +2637,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + this.player = this.server.getPlayerList().respawn(this.player, false, RespawnReason.DEATH); + if (this.server.isHardcore()) { +- this.player.setGameMode(GameType.SPECTATOR); ++ this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper - Expand PlayerGameModeChangeEvent + ((GameRules.BooleanValue) this.player.level().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.player.serverLevel()); // CraftBukkit - per-world + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index a9f30ba8d6d6e3d488f46b0bd79bf77b660c1b82..fa5bf1ef9cb4df06eabce00ccdd86a408ddaef8f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1562,7 +1562,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + Preconditions.checkArgument(mode != null, "GameMode cannot be null"); + if (this.getHandle().connection == null) return; + +- this.getHandle().setGameMode(GameType.byId(mode.getValue())); ++ this.getHandle().setGameMode(GameType.byId(mode.getValue()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.PLUGIN, null); // Paper - Expand PlayerGameModeChangeEvent + } + + @Override diff --git a/patches/server/0560-Expand-PlayerGameModeChangeEvent.patch b/patches/server/0560-Expand-PlayerGameModeChangeEvent.patch deleted file mode 100644 index cfd1e1cd2653..000000000000 --- a/patches/server/0560-Expand-PlayerGameModeChangeEvent.patch +++ /dev/null @@ -1,158 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 15 May 2021 10:04:43 -0700 -Subject: [PATCH] Expand PlayerGameModeChangeEvent - - -diff --git a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java -index 50e581613156cca765dfd8e3596dcb3b58520cec..21d6a2a5d1722d44146384c28a3cba2df9b42771 100644 ---- a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java -+++ b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java -@@ -25,9 +25,13 @@ public class DefaultGameModeCommands { - GameType gameType = minecraftServer.getForcedGameType(); - if (gameType != null) { - for(ServerPlayer serverPlayer : minecraftServer.getPlayerList().getPlayers()) { -- if (serverPlayer.setGameMode(gameType)) { -- ++i; -+ // Paper start - Expand PlayerGameModeChangeEvent -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty()); -+ if (event != null && event.isCancelled()) { -+ source.sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false); - } -+ // Paper end - Expand PlayerGameModeChangeEvent -+ ++i; - } - } - -diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java -index aee8618e27b893b72931e925724dd683d2e6d2aa..f7c9127346261d83413ca03a1cdaa84975ae17d6 100644 ---- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java -+++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java -@@ -48,9 +48,14 @@ public class GameModeCommand { - int i = 0; - - for(ServerPlayer serverPlayer : targets) { -- if (serverPlayer.setGameMode(gameMode)) { -+ // Paper start - Expand PlayerGameModeChangeEvent -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty()); -+ if (event != null && !event.isCancelled()) { - logGamemodeChange(context.getSource(), serverPlayer, gameMode); - ++i; -+ } else if (event != null && event.cancelMessage() != null) { -+ context.getSource().sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true); -+ // Paper end - Expand PlayerGameModeChangeEvent - } - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 5a26a7d6e052c0533f73b1930da6c801f23cb521..895c2cd385622fcc426e9e920ff35109f444b569 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1940,8 +1940,16 @@ public class ServerPlayer extends Player { - } - - public boolean setGameMode(GameType gameMode) { -- if (!this.gameMode.changeGameModeForPlayer(gameMode)) { -- return false; -+ // Paper start - Expand PlayerGameModeChangeEvent -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); -+ return event == null ? false : event.isCancelled(); -+ } -+ @Nullable -+ public org.bukkit.event.player.PlayerGameModeChangeEvent setGameMode(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component message) { -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.gameMode.changeGameModeForPlayer(gameMode, cause, message); -+ if (event == null || event.isCancelled()) { -+ return null; -+ // Paper end - Expand PlayerGameModeChangeEvent - } else { - this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) gameMode.getId())); - if (gameMode == GameType.SPECTATOR) { -@@ -1953,7 +1961,7 @@ public class ServerPlayer extends Player { - - this.onUpdateAbilities(); - this.updateEffectVisibility(); -- return true; -+ return event; // Paper - Expand PlayerGameModeChangeEvent - } - } - -@@ -2365,6 +2373,16 @@ public class ServerPlayer extends Player { - } - - public void loadGameTypes(@Nullable CompoundTag nbt) { -+ // Paper start - Expand PlayerGameModeChangeEvent -+ if (this.server.getForcedGameType() != null && this.server.getForcedGameType() != ServerPlayer.readPlayerMode(nbt, "playerGameType")) { -+ if (new org.bukkit.event.player.PlayerGameModeChangeEvent(this.getBukkitEntity(), org.bukkit.GameMode.getByValue(this.server.getDefaultGameType().getId()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null).callEvent()) { -+ this.gameMode.setGameModeForPlayer(this.server.getForcedGameType(), GameType.DEFAULT_MODE); -+ } else { -+ this.gameMode.setGameModeForPlayer(ServerPlayer.readPlayerMode(nbt,"playerGameType"), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); -+ } -+ return; -+ } -+ // Paper end - Expand PlayerGameModeChangeEvent - this.gameMode.setGameModeForPlayer(this.calculateGameModeForNewPlayer(ServerPlayer.readPlayerMode(nbt, "playerGameType")), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index a03d1a85019afdc42de2b8449fc38384c4dac51e..4fe571915b247ec612b2376dce57991e441f63c2 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -73,21 +73,28 @@ public class ServerPlayerGameMode { - } - - public boolean changeGameModeForPlayer(GameType gameMode) { -+ // Paper start - Expand PlayerGameModeChangeEvent -+ PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); -+ return event != null && event.isCancelled(); -+ } -+ @Nullable -+ public PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component cancelMessage) { -+ // Paper end - Expand PlayerGameModeChangeEvent - if (gameMode == this.gameModeForPlayer) { -- return false; -+ return null; // Paper - Expand PlayerGameModeChangeEvent - } else { - // CraftBukkit start -- PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId())); -+ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId()), cause, cancelMessage); // Paper - this.level.getCraftServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { -- return false; -+ return event; // Paper - Expand PlayerGameModeChangeEvent - } - // CraftBukkit end - this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer); - this.player.onUpdateAbilities(); - this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit - this.level.updateSleepingPlayerList(); -- return true; -+ return event; // Paper - Expand PlayerGameModeChangeEvent - } - } - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 189b16f5c7bad3540671019136ed0ef8dfaacf74..813538573b24cc0fd30c6b67c138449809c36404 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2637,7 +2637,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - this.player = this.server.getPlayerList().respawn(this.player, false, RespawnReason.DEATH); - if (this.server.isHardcore()) { -- this.player.setGameMode(GameType.SPECTATOR); -+ this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper - Expand PlayerGameModeChangeEvent - ((GameRules.BooleanValue) this.player.level().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.server); - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index deff963f73b5317be6f82945c01e45f337675103..9356752eae2499654f26fb60490490adbc1010c9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1532,7 +1532,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - Preconditions.checkArgument(mode != null, "GameMode cannot be null"); - if (this.getHandle().connection == null) return; - -- this.getHandle().setGameMode(GameType.byId(mode.getValue())); -+ this.getHandle().setGameMode(GameType.byId(mode.getValue()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.PLUGIN, null); // Paper - Expand PlayerGameModeChangeEvent - } - - @Override diff --git a/patches/server/0560-ItemStack-repair-check-API.patch b/patches/server/0560-ItemStack-repair-check-API.patch new file mode 100644 index 000000000000..daefbc2d1d29 --- /dev/null +++ b/patches/server/0560-ItemStack-repair-check-API.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 15 May 2021 22:11:11 -0700 +Subject: [PATCH] ItemStack repair check API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 44b0acb82f10a35bc0c42fdb6fd685aadc76d789..6eca86a2ab7145f4c60abb90cd46b4098ad8fb56 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -551,6 +551,14 @@ public final class CraftMagicNumbers implements UnsafeValues { + return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; + } + ++ @Override ++ public boolean isValidRepairItemStack(org.bukkit.inventory.ItemStack itemToBeRepaired, org.bukkit.inventory.ItemStack repairMaterial) { ++ if (!itemToBeRepaired.getType().isItem() || !repairMaterial.getType().isItem()) { ++ return false; ++ } ++ return CraftMagicNumbers.getItem(itemToBeRepaired.getType()).isValidRepairItem(CraftItemStack.asNMSCopy(itemToBeRepaired), CraftItemStack.asNMSCopy(repairMaterial)); ++ } ++ + @Override + public int getProtocolVersion() { + return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); +diff --git a/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6b8d360ef86e181a680ad77f28b7dd7368dddfe7 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java +@@ -0,0 +1,48 @@ ++package io.papermc.paper.util; ++ ++import org.bukkit.Material; ++import org.bukkit.inventory.ItemStack; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertFalse; ++import static org.junit.jupiter.api.Assertions.assertThrows; ++import static org.junit.jupiter.api.Assertions.assertTrue; ++ ++public class ItemStackRepairCheckTest extends AbstractTestingBase { ++ ++ @Test ++ public void testIsRepariableBy() { ++ ItemStack diamondPick = new ItemStack(Material.DIAMOND_PICKAXE); ++ ++ assertTrue(diamondPick.isRepairableBy(new ItemStack(Material.DIAMOND)), "diamond pick isn't repairable by a diamond"); ++ } ++ ++ @Test ++ public void testCanRepair() { ++ ItemStack diamond = new ItemStack(Material.DIAMOND); ++ ++ assertTrue(diamond.canRepair(new ItemStack(Material.DIAMOND_AXE)), "diamond can't repair a diamond axe"); ++ } ++ ++ @Test ++ public void testIsNotRepairableBy() { ++ ItemStack notDiamondPick = new ItemStack(Material.ACACIA_SAPLING); ++ ++ assertFalse(notDiamondPick.isRepairableBy(new ItemStack(Material.DIAMOND)), "acacia sapling is repairable by a diamond"); ++ } ++ ++ @Test ++ public void testCanNotRepair() { ++ ItemStack diamond = new ItemStack(Material.DIAMOND); ++ ++ assertFalse(diamond.canRepair(new ItemStack(Material.OAK_BUTTON)), "diamond can repair oak button"); ++ } ++ ++ @Test ++ public void testInvalidItem() { ++ ItemStack badItemStack = new ItemStack(Material.ACACIA_WALL_SIGN); ++ ++ assertFalse(badItemStack.isRepairableBy(new ItemStack(Material.DIAMOND)), "acacia wall sign is repairable by diamond"); ++ } ++} diff --git a/patches/server/0561-ItemStack-repair-check-API.patch b/patches/server/0561-ItemStack-repair-check-API.patch deleted file mode 100644 index b4a619206915..000000000000 --- a/patches/server/0561-ItemStack-repair-check-API.patch +++ /dev/null @@ -1,79 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 15 May 2021 22:11:11 -0700 -Subject: [PATCH] ItemStack repair check API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 584ea52824c17b3008204df2480a2bf9f14acb82..b0f365fe009be57cdf64983d9975c6bd873a33b5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -536,6 +536,14 @@ public final class CraftMagicNumbers implements UnsafeValues { - return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; - } - -+ @Override -+ public boolean isValidRepairItemStack(org.bukkit.inventory.ItemStack itemToBeRepaired, org.bukkit.inventory.ItemStack repairMaterial) { -+ if (!itemToBeRepaired.getType().isItem() || !repairMaterial.getType().isItem()) { -+ return false; -+ } -+ return CraftMagicNumbers.getItem(itemToBeRepaired.getType()).isValidRepairItem(CraftItemStack.asNMSCopy(itemToBeRepaired), CraftItemStack.asNMSCopy(repairMaterial)); -+ } -+ - @Override - public int getProtocolVersion() { - return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); -diff --git a/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6b8d360ef86e181a680ad77f28b7dd7368dddfe7 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java -@@ -0,0 +1,48 @@ -+package io.papermc.paper.util; -+ -+import org.bukkit.Material; -+import org.bukkit.inventory.ItemStack; -+import org.bukkit.support.AbstractTestingBase; -+import org.junit.jupiter.api.Test; -+ -+import static org.junit.jupiter.api.Assertions.assertFalse; -+import static org.junit.jupiter.api.Assertions.assertThrows; -+import static org.junit.jupiter.api.Assertions.assertTrue; -+ -+public class ItemStackRepairCheckTest extends AbstractTestingBase { -+ -+ @Test -+ public void testIsRepariableBy() { -+ ItemStack diamondPick = new ItemStack(Material.DIAMOND_PICKAXE); -+ -+ assertTrue(diamondPick.isRepairableBy(new ItemStack(Material.DIAMOND)), "diamond pick isn't repairable by a diamond"); -+ } -+ -+ @Test -+ public void testCanRepair() { -+ ItemStack diamond = new ItemStack(Material.DIAMOND); -+ -+ assertTrue(diamond.canRepair(new ItemStack(Material.DIAMOND_AXE)), "diamond can't repair a diamond axe"); -+ } -+ -+ @Test -+ public void testIsNotRepairableBy() { -+ ItemStack notDiamondPick = new ItemStack(Material.ACACIA_SAPLING); -+ -+ assertFalse(notDiamondPick.isRepairableBy(new ItemStack(Material.DIAMOND)), "acacia sapling is repairable by a diamond"); -+ } -+ -+ @Test -+ public void testCanNotRepair() { -+ ItemStack diamond = new ItemStack(Material.DIAMOND); -+ -+ assertFalse(diamond.canRepair(new ItemStack(Material.OAK_BUTTON)), "diamond can repair oak button"); -+ } -+ -+ @Test -+ public void testInvalidItem() { -+ ItemStack badItemStack = new ItemStack(Material.ACACIA_WALL_SIGN); -+ -+ assertFalse(badItemStack.isRepairableBy(new ItemStack(Material.DIAMOND)), "acacia wall sign is repairable by diamond"); -+ } -+} diff --git a/patches/server/0561-More-Enchantment-API.patch b/patches/server/0561-More-Enchantment-API.patch new file mode 100644 index 000000000000..70329e262705 --- /dev/null +++ b/patches/server/0561-More-Enchantment-API.patch @@ -0,0 +1,169 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 6 May 2021 19:57:58 -0700 +Subject: [PATCH] More Enchantment API + +== AT == +public net.minecraft.world.item.enchantment.Enchantment slots + +Co-authored-by: Luis + +diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +index 7aa4035a4df1ddcc71065034eafd569ca59be810..5b7579395e61684592758f408d61cffe57f8b21d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java ++++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +@@ -81,7 +81,7 @@ public class CraftEnchantment extends Enchantment implements Handleable getActiveSlots() { ++ return java.util.stream.Stream.of(handle.slots).map(org.bukkit.craftbukkit.CraftEquipmentSlot::getSlot).collect(java.util.stream.Collectors.toSet()); ++ } ++ ++ public static io.papermc.paper.enchantments.EnchantmentRarity fromNMSRarity(net.minecraft.world.item.enchantment.Enchantment.Rarity nmsRarity) { ++ if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.COMMON) { ++ return io.papermc.paper.enchantments.EnchantmentRarity.COMMON; ++ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.UNCOMMON) { ++ return io.papermc.paper.enchantments.EnchantmentRarity.UNCOMMON; ++ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.RARE) { ++ return io.papermc.paper.enchantments.EnchantmentRarity.RARE; ++ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.VERY_RARE) { ++ return io.papermc.paper.enchantments.EnchantmentRarity.VERY_RARE; ++ } ++ ++ throw new IllegalArgumentException(String.format("Unable to convert %s to a enum value of %s.", nmsRarity, io.papermc.paper.enchantments.EnchantmentRarity.class)); ++ } + // Paper end + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 72d9a9696c95374bed29e2e453c7750d0cf06170..40fbd911943abd6f6cc7910b5179c196bb3fe8f8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -978,5 +978,21 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void setHurtDirection(float hurtDirection) { + throw new UnsupportedOperationException("Cannot set the hurt direction on a non player"); + } ++ ++ public static MobType fromBukkitEntityCategory(EntityCategory entityCategory) { ++ switch (entityCategory) { ++ case NONE: ++ return MobType.UNDEFINED; ++ case UNDEAD: ++ return MobType.UNDEAD; ++ case ARTHROPOD: ++ return MobType.ARTHROPOD; ++ case ILLAGER: ++ return MobType.ILLAGER; ++ case WATER: ++ return MobType.WATER; ++ } ++ throw new IllegalArgumentException(entityCategory + " is an unrecognized entity category"); ++ } + // Paper end + } +diff --git a/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java b/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..03a53ceb0dc3aaff7b5d10ec57f74d71be90ec3a +--- /dev/null ++++ b/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java +@@ -0,0 +1,18 @@ ++package io.papermc.paper.enchantments; ++ ++import net.minecraft.world.item.enchantment.Enchantment.Rarity; ++import org.bukkit.craftbukkit.enchantments.CraftEnchantment; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertNotNull; ++ ++public class EnchantmentRarityTest { ++ ++ @Test ++ public void test() { ++ for (Rarity nmsRarity : Rarity.values()) { ++ // Will throw exception if a bukkit counterpart is not found ++ CraftEnchantment.fromNMSRarity(nmsRarity); ++ } ++ } ++} +diff --git a/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java b/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4ee48ef89f0e31a7bda4b04453fca8177874f540 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java +@@ -0,0 +1,34 @@ ++package io.papermc.paper.entity; ++ ++import com.google.common.base.Joiner; ++import com.google.common.collect.Maps; ++import com.google.common.collect.Sets; ++import net.minecraft.world.entity.MobType; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.entity.EntityCategory; ++import org.junit.jupiter.api.Test; ++ ++import java.lang.reflect.Field; ++import java.util.Map; ++import java.util.Set; ++ ++import static org.junit.jupiter.api.Assertions.assertTrue; ++ ++public class EntityCategoryTest { ++ ++ @Test ++ public void test() throws IllegalAccessException { ++ ++ Map enumMonsterTypeFieldMap = Maps.newHashMap(); ++ for (Field field : MobType.class.getDeclaredFields()) { ++ if (field.getType() == MobType.class) { ++ enumMonsterTypeFieldMap.put( (MobType) field.get(null), field.getName()); ++ } ++ } ++ ++ for (EntityCategory entityCategory : EntityCategory.values()) { ++ enumMonsterTypeFieldMap.remove(CraftLivingEntity.fromBukkitEntityCategory(entityCategory)); ++ } ++ assertTrue(enumMonsterTypeFieldMap.size() == 0, MobType.class.getName() + " instance(s): " + Joiner.on(", ").join(enumMonsterTypeFieldMap.values()) + " do not have bukkit equivalents"); ++ } ++} diff --git a/patches/server/0562-More-Enchantment-API.patch b/patches/server/0562-More-Enchantment-API.patch deleted file mode 100644 index 9b5fad2bb57f..000000000000 --- a/patches/server/0562-More-Enchantment-API.patch +++ /dev/null @@ -1,169 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 6 May 2021 19:57:58 -0700 -Subject: [PATCH] More Enchantment API - -== AT == -public net.minecraft.world.item.enchantment.Enchantment slots - -Co-authored-by: Luis - -diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -index 7aa4035a4df1ddcc71065034eafd569ca59be810..5b7579395e61684592758f408d61cffe57f8b21d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -+++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -@@ -81,7 +81,7 @@ public class CraftEnchantment extends Enchantment implements Handleable getActiveSlots() { -+ return java.util.stream.Stream.of(handle.slots).map(org.bukkit.craftbukkit.CraftEquipmentSlot::getSlot).collect(java.util.stream.Collectors.toSet()); -+ } -+ -+ public static io.papermc.paper.enchantments.EnchantmentRarity fromNMSRarity(net.minecraft.world.item.enchantment.Enchantment.Rarity nmsRarity) { -+ if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.COMMON) { -+ return io.papermc.paper.enchantments.EnchantmentRarity.COMMON; -+ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.UNCOMMON) { -+ return io.papermc.paper.enchantments.EnchantmentRarity.UNCOMMON; -+ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.RARE) { -+ return io.papermc.paper.enchantments.EnchantmentRarity.RARE; -+ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.VERY_RARE) { -+ return io.papermc.paper.enchantments.EnchantmentRarity.VERY_RARE; -+ } -+ -+ throw new IllegalArgumentException(String.format("Unable to convert %s to a enum value of %s.", nmsRarity, io.papermc.paper.enchantments.EnchantmentRarity.class)); -+ } - // Paper end - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 7d8d503b46b1380e4b8a52d76fc5cc55759de80b..1474177f66a1428406bec2c55cd995a172f27dcf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -965,5 +965,21 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - public void setHurtDirection(float hurtDirection) { - throw new UnsupportedOperationException("Cannot set the hurt direction on a non player"); - } -+ -+ public static MobType fromBukkitEntityCategory(EntityCategory entityCategory) { -+ switch (entityCategory) { -+ case NONE: -+ return MobType.UNDEFINED; -+ case UNDEAD: -+ return MobType.UNDEAD; -+ case ARTHROPOD: -+ return MobType.ARTHROPOD; -+ case ILLAGER: -+ return MobType.ILLAGER; -+ case WATER: -+ return MobType.WATER; -+ } -+ throw new IllegalArgumentException(entityCategory + " is an unrecognized entity category"); -+ } - // Paper end - } -diff --git a/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java b/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..03a53ceb0dc3aaff7b5d10ec57f74d71be90ec3a ---- /dev/null -+++ b/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java -@@ -0,0 +1,18 @@ -+package io.papermc.paper.enchantments; -+ -+import net.minecraft.world.item.enchantment.Enchantment.Rarity; -+import org.bukkit.craftbukkit.enchantments.CraftEnchantment; -+import org.junit.jupiter.api.Test; -+ -+import static org.junit.jupiter.api.Assertions.assertNotNull; -+ -+public class EnchantmentRarityTest { -+ -+ @Test -+ public void test() { -+ for (Rarity nmsRarity : Rarity.values()) { -+ // Will throw exception if a bukkit counterpart is not found -+ CraftEnchantment.fromNMSRarity(nmsRarity); -+ } -+ } -+} -diff --git a/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java b/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4ee48ef89f0e31a7bda4b04453fca8177874f540 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java -@@ -0,0 +1,34 @@ -+package io.papermc.paper.entity; -+ -+import com.google.common.base.Joiner; -+import com.google.common.collect.Maps; -+import com.google.common.collect.Sets; -+import net.minecraft.world.entity.MobType; -+import org.bukkit.craftbukkit.entity.CraftLivingEntity; -+import org.bukkit.entity.EntityCategory; -+import org.junit.jupiter.api.Test; -+ -+import java.lang.reflect.Field; -+import java.util.Map; -+import java.util.Set; -+ -+import static org.junit.jupiter.api.Assertions.assertTrue; -+ -+public class EntityCategoryTest { -+ -+ @Test -+ public void test() throws IllegalAccessException { -+ -+ Map enumMonsterTypeFieldMap = Maps.newHashMap(); -+ for (Field field : MobType.class.getDeclaredFields()) { -+ if (field.getType() == MobType.class) { -+ enumMonsterTypeFieldMap.put( (MobType) field.get(null), field.getName()); -+ } -+ } -+ -+ for (EntityCategory entityCategory : EntityCategory.values()) { -+ enumMonsterTypeFieldMap.remove(CraftLivingEntity.fromBukkitEntityCategory(entityCategory)); -+ } -+ assertTrue(enumMonsterTypeFieldMap.size() == 0, MobType.class.getName() + " instance(s): " + Joiner.on(", ").join(enumMonsterTypeFieldMap.values()) + " do not have bukkit equivalents"); -+ } -+} diff --git a/patches/server/0562-Move-range-check-for-block-placing-up.patch b/patches/server/0562-Move-range-check-for-block-placing-up.patch new file mode 100644 index 000000000000..6ef0f8fe8573 --- /dev/null +++ b/patches/server/0562-Move-range-check-for-block-placing-up.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Wed, 8 Jun 2022 10:52:18 +0200 +Subject: [PATCH] Move range check for block placing up + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index c54a1c2bf8719791047eb9ccc6cbe26c7541f125..60acd12ab02a2a3c1fd788af2695b6a699646986 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1738,6 +1738,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + if (itemstack.isItemEnabled(worldserver.enabledFeatures())) { + BlockHitResult movingobjectpositionblock = packet.getHitResult(); + Vec3 vec3d = movingobjectpositionblock.getLocation(); ++ // Paper start - improve distance check ++ if (!Double.isFinite(vec3d.x) || !Double.isFinite(vec3d.y) || !Double.isFinite(vec3d.z)) { ++ return; ++ } ++ // Paper end - improve distance check + BlockPos blockposition = movingobjectpositionblock.getBlockPos(); + Vec3 vec3d1 = Vec3.atCenterOf(blockposition); + diff --git a/patches/server/0564-Add-Mob-lookAt-API.patch b/patches/server/0563-Add-Mob-lookAt-API.patch similarity index 100% rename from patches/server/0564-Add-Mob-lookAt-API.patch rename to patches/server/0563-Add-Mob-lookAt-API.patch diff --git a/patches/server/0563-Move-range-check-for-block-placing-up.patch b/patches/server/0563-Move-range-check-for-block-placing-up.patch deleted file mode 100644 index a8136afa9f5b..000000000000 --- a/patches/server/0563-Move-range-check-for-block-placing-up.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Wed, 8 Jun 2022 10:52:18 +0200 -Subject: [PATCH] Move range check for block placing up - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 813538573b24cc0fd30c6b67c138449809c36404..13e817da70328e4fc13327255afc88cfc848e5d2 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1738,6 +1738,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - if (itemstack.isItemEnabled(worldserver.enabledFeatures())) { - BlockHitResult movingobjectpositionblock = packet.getHitResult(); - Vec3 vec3d = movingobjectpositionblock.getLocation(); -+ // Paper start - improve distance check -+ if (!Double.isFinite(vec3d.x) || !Double.isFinite(vec3d.y) || !Double.isFinite(vec3d.z)) { -+ return; -+ } -+ // Paper end - improve distance check - BlockPos blockposition = movingobjectpositionblock.getBlockPos(); - Vec3 vec3d1 = Vec3.atCenterOf(blockposition); - diff --git a/patches/server/0565-Correctly-check-if-bucket-dispenses-will-succeed-for.patch b/patches/server/0564-Correctly-check-if-bucket-dispenses-will-succeed-for.patch similarity index 100% rename from patches/server/0565-Correctly-check-if-bucket-dispenses-will-succeed-for.patch rename to patches/server/0564-Correctly-check-if-bucket-dispenses-will-succeed-for.patch diff --git a/patches/server/0565-Add-Unix-domain-socket-support.patch b/patches/server/0565-Add-Unix-domain-socket-support.patch new file mode 100644 index 000000000000..27de36be4c7e --- /dev/null +++ b/patches/server/0565-Add-Unix-domain-socket-support.patch @@ -0,0 +1,137 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Tue, 11 May 2021 17:39:22 -0400 +Subject: [PATCH] Add Unix domain socket support + + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index a78b14b828526549e1309e94c271667043129a87..6b4c9ef02931491dd048646ead494892f06504c5 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -219,6 +219,20 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist); + // this.worldData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading + DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode); ++ // Paper start - Unix domain socket support ++ java.net.SocketAddress bindAddress; ++ if (this.getLocalIp().startsWith("unix:")) { ++ if (!io.netty.channel.epoll.Epoll.isAvailable()) { ++ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); ++ DedicatedServer.LOGGER.error("You are trying to use a Unix domain socket but you're not on a supported OS."); ++ return false; ++ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && !org.spigotmc.SpigotConfig.bungee) { ++ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); ++ DedicatedServer.LOGGER.error("Unix domain sockets require IPs to be forwarded from a proxy."); ++ return false; ++ } ++ bindAddress = new io.netty.channel.unix.DomainSocketAddress(this.getLocalIp().substring("unix:".length())); ++ } else { + InetAddress inetaddress = null; + + if (!this.getLocalIp().isEmpty()) { +@@ -228,12 +242,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + if (this.getPort() < 0) { + this.setPort(dedicatedserverproperties.serverPort); + } ++ bindAddress = new java.net.InetSocketAddress(inetaddress, this.getPort()); ++ } ++ // Paper end - Unix domain socket support + + this.initializeKeyPair(); + DedicatedServer.LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort()); + + try { +- this.getConnection().startTcpServerListener(inetaddress, this.getPort()); ++ this.getConnection().bind(bindAddress); // Paper - Unix domain socket support + } catch (IOException ioexception) { + DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!"); + DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString()); +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index 25ddfe8e5da65e4ac70be2820ba139e7f3852c0c..87abd6274f9da9367094bad0c28acfa47e01c50e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -76,7 +76,12 @@ public class ServerConnectionListener { + this.running = true; + } + ++ // Paper start - Unix domain socket support + public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException { ++ bind(new java.net.InetSocketAddress(address, port)); ++ } ++ public void bind(java.net.SocketAddress address) throws IOException { ++ // Paper end - Unix domain socket support + List list = this.channels; + + synchronized (this.channels) { +@@ -84,7 +89,13 @@ public class ServerConnectionListener { + EventLoopGroup eventloopgroup; + + if (Epoll.isAvailable() && this.server.isEpollEnabled()) { ++ // Paper start - Unix domain socket support ++ if (address instanceof io.netty.channel.unix.DomainSocketAddress) { ++ oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class; ++ } else { + oclass = EpollServerSocketChannel.class; ++ } ++ // Paper end - Unix domain socket support + eventloopgroup = (EventLoopGroup) ServerConnectionListener.SERVER_EPOLL_EVENT_GROUP.get(); + ServerConnectionListener.LOGGER.info("Using epoll channel type"); + } else { +@@ -115,7 +126,7 @@ public class ServerConnectionListener { + ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); + io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners + } +- }).group(eventloopgroup).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit ++ }).group(eventloopgroup).localAddress(address)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit // Paper - Unix domain socket support + } + } + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 60acd12ab02a2a3c1fd788af2695b6a699646986..bd212b89412c099216828ab5653ae3b9e1ec5665 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2471,6 +2471,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // Spigot Start + public SocketAddress getRawAddress() + { ++ // Paper start - Unix domain socket support; this can be nullable in the case of a Unix domain socket, so if it is, fake something ++ if (connection.channel.remoteAddress() == null) { ++ return new java.net.InetSocketAddress(java.net.InetAddress.getLoopbackAddress(), 0); ++ } ++ // Paper end - Unix domain socket support + return this.connection.channel.remoteAddress(); + } + // Spigot End +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index 0c6d172c8b723d2ceff7443dfe50ae280cb6dc2d..a53dd1ea02bd19826cd9fd337459b08e9533bce8 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -45,6 +45,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + this.connection.setClientboundProtocolAfterHandshake(ClientIntent.LOGIN); + // CraftBukkit start - Connection throttle + try { ++ if (!(this.connection.channel.localAddress() instanceof io.netty.channel.unix.DomainSocketAddress)) { // Paper - Unix domain socket support; the connection throttle is useless when you have a Unix domain socket + long currentTime = System.currentTimeMillis(); + long connectionThrottle = this.server.server.getConnectionThrottle(); + InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); +@@ -73,6 +74,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + } + } + } ++ } // Paper - Unix domain socket support + } catch (Throwable t) { + org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t); + } +@@ -131,8 +133,11 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + // Paper end - PlayerHandshakeEvent + // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! + if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper - Add bypass host check ++ // Paper start - Unix domain socket support ++ java.net.SocketAddress socketAddress = this.connection.getRemoteAddress(); + this.connection.hostname = split[0]; +- this.connection.address = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getPort()); ++ this.connection.address = new java.net.InetSocketAddress(split[1], socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0); ++ // Paper end - Unix domain socket support + this.connection.spoofedUUID = com.mojang.util.UndashedUuid.fromStringLenient( split[2] ); + } else + { diff --git a/patches/server/0566-Add-EntityInsideBlockEvent.patch b/patches/server/0566-Add-EntityInsideBlockEvent.patch new file mode 100644 index 000000000000..a63f8ecbfe42 --- /dev/null +++ b/patches/server/0566-Add-EntityInsideBlockEvent.patch @@ -0,0 +1,282 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 8 May 2021 18:02:36 -0700 +Subject: [PATCH] Add EntityInsideBlockEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +index 839469c1249829b42e752e5a1b613550c3f65bba..ceb5f9867f714b3b6a4602c787574dfa83c006f6 100644 +--- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +@@ -124,6 +124,7 @@ public abstract class BaseFireBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!entity.fireImmune()) { + entity.setRemainingFireTicks(entity.getRemainingFireTicks() + 1); + if (entity.getRemainingFireTicks() == 0) { +diff --git a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java +index 040e55edea53a2ebab7cc8fe6f85206c9301e11a..0d573c05f4f8838d4492f749ca473f7a9e8d60dd 100644 +--- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java +@@ -76,6 +76,7 @@ public abstract class BasePressurePlateBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide) { + int i = this.getSignalForState(state); + +diff --git a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java +index 546dbe28edbba32ab2aede1260fbd2c9baa9fe1a..0d92bd6f1e4f3470a62f573add3490220e60ef7a 100644 +--- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java +@@ -177,6 +177,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide) { + if (state.getValue(BigDripleafBlock.TILT) == Tilt.NONE && BigDripleafBlock.canEntityTilt(pos, entity) && !world.hasNeighborSignal(pos)) { + // CraftBukkit start - tilt dripleaf +diff --git a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java +index 240e01063b5d684020ed2d7d73fc60c64fd8cf2e..78d98a442ea3c14500ac6ae597ff2a5080b7ce15 100644 +--- a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java +@@ -47,6 +47,7 @@ public class BubbleColumnBlock extends Block implements BucketPickup { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + BlockState blockState = world.getBlockState(pos.above()); + if (blockState.isAir()) { + entity.onAboveBubbleCol(state.getValue(DRAG_DOWN)); +diff --git a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java +index 3356f327c9adae6c2f3354b4417f3954012c945a..0118c4ef4f5ed0e724b379b5a563e2b6976803a2 100644 +--- a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java +@@ -206,6 +206,7 @@ public class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide && this.type.canButtonBeActivatedByArrows() && !(Boolean) state.getValue(ButtonBlock.POWERED)) { + this.checkPressed(state, world, pos); + } +diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java +index 0e8c337dde0cfa2ac289c79904ecd2affc86d70a..ba4aaf850af36a84517c70581e141157c4f15b99 100644 +--- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java +@@ -121,6 +121,7 @@ public class CactusBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + entity.hurt(world.damageSources().cactus().directBlock(world, pos), 1.0F); // CraftBukkit + } + +diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java +index a139cf0c584719e8e360ea83bdc107dfe3c577ea..9c7ee02d3aa3c33b45db4dc5c079495a69d60b15 100644 +--- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java +@@ -105,6 +105,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if ((Boolean) state.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) { + entity.hurt(world.damageSources().inFire().directBlock(world, pos), (float) this.fireDamage); // CraftBukkit + } +diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java +index a7809bb2a468c7ad7ef7ba795afd93dd2a63cadc..aa029bee9839497e48ff639e286a024280150362 100644 +--- a/src/main/java/net/minecraft/world/level/block/CropBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java +@@ -174,6 +174,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity instanceof Ravager && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit + world.destroyBlock(pos, true, entity); + } +diff --git a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +index 17242c24d73c9ffb1c976a45925f85d1aa9e96b3..57e542d5c8b887acecedf76c08c8d4379d712c0f 100644 +--- a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +@@ -51,6 +51,7 @@ public class DetectorRailBlock extends BaseRailBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide) { + if (!(Boolean) state.getValue(DetectorRailBlock.POWERED)) { + this.checkPressed(world, pos, state); +diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +index 07629f6106f384751c376d2a99ba2e8b905e49c6..9ee2fd0914ff7836517ca143d51db6150967cb0e 100644 +--- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +@@ -52,6 +52,7 @@ public class EndPortalBlock extends BaseEntityBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (world instanceof ServerLevel && entity.canChangeDimensions() && Shapes.joinIsNotEmpty(Shapes.create(entity.getBoundingBox().move((double) (-pos.getX()), (double) (-pos.getY()), (double) (-pos.getZ()))), state.getShape(world, pos), BooleanOp.AND)) { + ResourceKey resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends + ServerLevel worldserver = ((ServerLevel) world).getServer().getLevel(resourcekey); +diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +index d5c83f3b9d398b2a025e6729980b1b87b35f38a8..68978e1629381ada161225c53a236a54deae6481 100644 +--- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +@@ -78,6 +78,7 @@ public class FrogspawnBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.getType().equals(EntityType.FALLING_BLOCK)) { + this.destroyBlock(world, pos); + } +diff --git a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java +index 8b84359316e559b94ae6a2d757bda2286d99a903..39e2fe8c5f5a2a4d4f3a7be3645923b5a1dca875 100644 +--- a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java +@@ -61,6 +61,7 @@ public class HoneyBlock extends HalfTransparentBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (this.isSlidingDown(pos, entity)) { + this.maybeDoSlideAchievement(entity, pos); + this.doSlideMovement(entity); +diff --git a/src/main/java/net/minecraft/world/level/block/HopperBlock.java b/src/main/java/net/minecraft/world/level/block/HopperBlock.java +index 99a5821a55f2d2947722d64d60f4ee4ba5dfa74c..04e69d6066faf1c605aeeabe827dc20fc96a3568 100644 +--- a/src/main/java/net/minecraft/world/level/block/HopperBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/HopperBlock.java +@@ -200,6 +200,7 @@ public class HopperBlock extends BaseEntityBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + BlockEntity blockEntity = world.getBlockEntity(pos); + if (blockEntity instanceof HopperBlockEntity) { + HopperBlockEntity.entityInside(world, pos, state, entity, (HopperBlockEntity)blockEntity); +diff --git a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java +index 7e926a4546f89da22080ef28c3858ac7b3cf80f1..29d4553df54236706c76fdec0bb14b5d9fb82ff2 100644 +--- a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java +@@ -32,6 +32,7 @@ public class LavaCauldronBlock extends AbstractCauldronBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (this.isEntityInsideContent(state, pos, entity)) { + entity.lavaHurt(); + } +diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +index 64247c3a81273277b95656885c78eca3e883ef13..18ca086e28f4295cb9303919222d7c8ae0ca5d9a 100644 +--- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +@@ -65,6 +65,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { + // CraftBukkit start + if (entity.mayInteract(world, pos)) { +diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +index 2a8f97d97ae7f268da920b5e3b9719743fa9a8e0..1b5cc5d6aa0b4313da980ce175c54145852d0db0 100644 +--- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +@@ -90,6 +90,7 @@ public class NetherPortalBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity.canChangeDimensions()) { + // CraftBukkit start - Entity in portal + EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); +diff --git a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java +index f7154d56ad42e0dde497e0e585cc554d34865e3b..690abba9d27ab4061de4dd3676292b1859036868 100644 +--- a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java +@@ -94,6 +94,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity instanceof Ravager && world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + world.destroyBlock(pos, true, entity); + } +diff --git a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java +index b38658b84e821435ba5a3fc7218b72406da00e47..0dfcac8cfcbb09fe04486bff60119f7985714454 100644 +--- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java +@@ -63,6 +63,7 @@ public class PowderSnowBlock extends Block implements BucketPickup { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!(entity instanceof LivingEntity) || entity.getFeetBlockState().is((Block) this)) { + entity.makeStuckInBlock(state, new Vec3(0.8999999761581421D, 1.5D, 0.8999999761581421D)); + if (world.isClientSide) { +diff --git a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java +index 53d39c5557bd1a68f39aaf7950f42cc1aadc3337..62ed86114c1a6e724ccfea119a331356d0a22600 100644 +--- a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java +@@ -83,6 +83,7 @@ public class SweetBerryBushBlock extends BushBlock implements BonemealableBlock + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (entity instanceof LivingEntity && entity.getType() != EntityType.FOX && entity.getType() != EntityType.BEE) { + entity.makeStuckInBlock(state, new Vec3(0.800000011920929D, 0.75D, 0.800000011920929D)); + if (!world.isClientSide && (Integer) state.getValue(SweetBerryBushBlock.AGE) > 0 && (entity.xOld != entity.getX() || entity.zOld != entity.getZ())) { +diff --git a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java +index d092ee2aa4a37c89642133dca7049737c55a4245..003fd247b12323cca5fd82a6cdf31bd897afd682 100644 +--- a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java +@@ -134,6 +134,7 @@ public class TripWireBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide) { + if (!(Boolean) state.getValue(TripWireBlock.POWERED)) { + this.checkPressed(world, pos); +diff --git a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java +index d89a4c30599cedcb8ce17899631cb58c8a6a2195..61abbcfe97e3d3e3da5ee658672549d14594ad17 100644 +--- a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java +@@ -34,6 +34,7 @@ public class WaterlilyBlock extends BushBlock { + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { + super.entityInside(state, world, pos, entity); ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (world instanceof ServerLevel && entity instanceof Boat) { + // CraftBukkit start + if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState())) { +diff --git a/src/main/java/net/minecraft/world/level/block/WebBlock.java b/src/main/java/net/minecraft/world/level/block/WebBlock.java +index 7206dfa5bdfb94ff98bcdfc735367c22493e925e..4905de6446f07e2fff53a3c8580b64b96306c373 100644 +--- a/src/main/java/net/minecraft/world/level/block/WebBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WebBlock.java +@@ -22,6 +22,7 @@ public class WebBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + entity.makeStuckInBlock(state, new Vec3(0.25D, (double)0.05F, 0.25D)); + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java +index c10a01013f8393c677834136babed15817b45611..a49027c73ca461e0610914138cc660c89965f956 100644 +--- a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java +@@ -61,6 +61,7 @@ public class WitherRoseBlock extends FlowerBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide && world.getDifficulty() != Difficulty.PEACEFUL) { + if (entity instanceof LivingEntity) { + LivingEntity entityliving = (LivingEntity) entity; diff --git a/patches/server/0566-Add-Unix-domain-socket-support.patch b/patches/server/0566-Add-Unix-domain-socket-support.patch deleted file mode 100644 index 7be6e655aaef..000000000000 --- a/patches/server/0566-Add-Unix-domain-socket-support.patch +++ /dev/null @@ -1,137 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Tue, 11 May 2021 17:39:22 -0400 -Subject: [PATCH] Add Unix domain socket support - - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 959ce2efe156c8ff2e3d1b57cde27f4da548c3ef..212ce0957d623776a11779c4a476c76bc7c1c0bd 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -219,6 +219,20 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist); - // this.worldData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading - DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode); -+ // Paper start - Unix domain socket support -+ java.net.SocketAddress bindAddress; -+ if (this.getLocalIp().startsWith("unix:")) { -+ if (!io.netty.channel.epoll.Epoll.isAvailable()) { -+ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); -+ DedicatedServer.LOGGER.error("You are trying to use a Unix domain socket but you're not on a supported OS."); -+ return false; -+ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && !org.spigotmc.SpigotConfig.bungee) { -+ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); -+ DedicatedServer.LOGGER.error("Unix domain sockets require IPs to be forwarded from a proxy."); -+ return false; -+ } -+ bindAddress = new io.netty.channel.unix.DomainSocketAddress(this.getLocalIp().substring("unix:".length())); -+ } else { - InetAddress inetaddress = null; - - if (!this.getLocalIp().isEmpty()) { -@@ -228,12 +242,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - if (this.getPort() < 0) { - this.setPort(dedicatedserverproperties.serverPort); - } -+ bindAddress = new java.net.InetSocketAddress(inetaddress, this.getPort()); -+ } -+ // Paper end - Unix domain socket support - - this.initializeKeyPair(); - DedicatedServer.LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort()); - - try { -- this.getConnection().startTcpServerListener(inetaddress, this.getPort()); -+ this.getConnection().bind(bindAddress); // Paper - Unix domain socket support - } catch (IOException ioexception) { - DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!"); - DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString()); -diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -index 25ddfe8e5da65e4ac70be2820ba139e7f3852c0c..87abd6274f9da9367094bad0c28acfa47e01c50e 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -@@ -76,7 +76,12 @@ public class ServerConnectionListener { - this.running = true; - } - -+ // Paper start - Unix domain socket support - public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException { -+ bind(new java.net.InetSocketAddress(address, port)); -+ } -+ public void bind(java.net.SocketAddress address) throws IOException { -+ // Paper end - Unix domain socket support - List list = this.channels; - - synchronized (this.channels) { -@@ -84,7 +89,13 @@ public class ServerConnectionListener { - EventLoopGroup eventloopgroup; - - if (Epoll.isAvailable() && this.server.isEpollEnabled()) { -+ // Paper start - Unix domain socket support -+ if (address instanceof io.netty.channel.unix.DomainSocketAddress) { -+ oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class; -+ } else { - oclass = EpollServerSocketChannel.class; -+ } -+ // Paper end - Unix domain socket support - eventloopgroup = (EventLoopGroup) ServerConnectionListener.SERVER_EPOLL_EVENT_GROUP.get(); - ServerConnectionListener.LOGGER.info("Using epoll channel type"); - } else { -@@ -115,7 +126,7 @@ public class ServerConnectionListener { - ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); - io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners - } -- }).group(eventloopgroup).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit -+ }).group(eventloopgroup).localAddress(address)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit // Paper - Unix domain socket support - } - } - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 13e817da70328e4fc13327255afc88cfc848e5d2..4824c5f0f4c0e165e3622aac23e501f56e09fc73 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2471,6 +2471,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // Spigot Start - public SocketAddress getRawAddress() - { -+ // Paper start - Unix domain socket support; this can be nullable in the case of a Unix domain socket, so if it is, fake something -+ if (connection.channel.remoteAddress() == null) { -+ return new java.net.InetSocketAddress(java.net.InetAddress.getLoopbackAddress(), 0); -+ } -+ // Paper end - Unix domain socket support - return this.connection.channel.remoteAddress(); - } - // Spigot End -diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -index 0c6d172c8b723d2ceff7443dfe50ae280cb6dc2d..a53dd1ea02bd19826cd9fd337459b08e9533bce8 100644 ---- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -@@ -45,6 +45,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - this.connection.setClientboundProtocolAfterHandshake(ClientIntent.LOGIN); - // CraftBukkit start - Connection throttle - try { -+ if (!(this.connection.channel.localAddress() instanceof io.netty.channel.unix.DomainSocketAddress)) { // Paper - Unix domain socket support; the connection throttle is useless when you have a Unix domain socket - long currentTime = System.currentTimeMillis(); - long connectionThrottle = this.server.server.getConnectionThrottle(); - InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); -@@ -73,6 +74,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - } - } - } -+ } // Paper - Unix domain socket support - } catch (Throwable t) { - org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t); - } -@@ -131,8 +133,11 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - // Paper end - PlayerHandshakeEvent - // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! - if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper - Add bypass host check -+ // Paper start - Unix domain socket support -+ java.net.SocketAddress socketAddress = this.connection.getRemoteAddress(); - this.connection.hostname = split[0]; -- this.connection.address = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getPort()); -+ this.connection.address = new java.net.InetSocketAddress(split[1], socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0); -+ // Paper end - Unix domain socket support - this.connection.spoofedUUID = com.mojang.util.UndashedUuid.fromStringLenient( split[2] ); - } else - { diff --git a/patches/server/0567-Add-EntityInsideBlockEvent.patch b/patches/server/0567-Add-EntityInsideBlockEvent.patch deleted file mode 100644 index addc980ab191..000000000000 --- a/patches/server/0567-Add-EntityInsideBlockEvent.patch +++ /dev/null @@ -1,282 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 8 May 2021 18:02:36 -0700 -Subject: [PATCH] Add EntityInsideBlockEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -index 91400c16b8bd8953265bf37ec1cd34ac95133e32..118403953629b405b9db78de1bf684b31289c499 100644 ---- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -@@ -124,6 +124,7 @@ public abstract class BaseFireBlock extends Block { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!entity.fireImmune()) { - entity.setRemainingFireTicks(entity.getRemainingFireTicks() + 1); - if (entity.getRemainingFireTicks() == 0) { -diff --git a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java -index 040e55edea53a2ebab7cc8fe6f85206c9301e11a..0d573c05f4f8838d4492f749ca473f7a9e8d60dd 100644 ---- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java -@@ -76,6 +76,7 @@ public abstract class BasePressurePlateBlock extends Block { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide) { - int i = this.getSignalForState(state); - -diff --git a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -index 546dbe28edbba32ab2aede1260fbd2c9baa9fe1a..0d92bd6f1e4f3470a62f573add3490220e60ef7a 100644 ---- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -@@ -177,6 +177,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide) { - if (state.getValue(BigDripleafBlock.TILT) == Tilt.NONE && BigDripleafBlock.canEntityTilt(pos, entity) && !world.hasNeighborSignal(pos)) { - // CraftBukkit start - tilt dripleaf -diff --git a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -index 240e01063b5d684020ed2d7d73fc60c64fd8cf2e..78d98a442ea3c14500ac6ae597ff2a5080b7ce15 100644 ---- a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -@@ -47,6 +47,7 @@ public class BubbleColumnBlock extends Block implements BucketPickup { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - BlockState blockState = world.getBlockState(pos.above()); - if (blockState.isAir()) { - entity.onAboveBubbleCol(state.getValue(DRAG_DOWN)); -diff --git a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java -index 3356f327c9adae6c2f3354b4417f3954012c945a..0118c4ef4f5ed0e724b379b5a563e2b6976803a2 100644 ---- a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java -@@ -206,6 +206,7 @@ public class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide && this.type.canButtonBeActivatedByArrows() && !(Boolean) state.getValue(ButtonBlock.POWERED)) { - this.checkPressed(state, world, pos); - } -diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -index 43ecbeaced4d50910a59b24934908ff40d894770..c5a0cefc6b7e19d8a277dbc59e54f465a994a858 100644 ---- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -@@ -121,6 +121,7 @@ public class CactusBlock extends Block { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - CraftEventFactory.blockDamage = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); // CraftBukkit - entity.hurt(world.damageSources().cactus(), 1.0F); - CraftEventFactory.blockDamage = null; // CraftBukkit -diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -index cbabee578a6fd62234d0aa350d911c6c7d02e0b2..20dbfeb68ac33ee8ba8214edcca0d7f7ce1be58e 100644 ---- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -@@ -108,6 +108,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if ((Boolean) state.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) { - org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = CraftBlock.at(world, pos); // CraftBukkit - entity.hurt(world.damageSources().inFire(), (float) this.fireDamage); -diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java -index a7809bb2a468c7ad7ef7ba795afd93dd2a63cadc..aa029bee9839497e48ff639e286a024280150362 100644 ---- a/src/main/java/net/minecraft/world/level/block/CropBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java -@@ -174,6 +174,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity instanceof Ravager && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit - world.destroyBlock(pos, true, entity); - } -diff --git a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -index 17242c24d73c9ffb1c976a45925f85d1aa9e96b3..57e542d5c8b887acecedf76c08c8d4379d712c0f 100644 ---- a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -@@ -51,6 +51,7 @@ public class DetectorRailBlock extends BaseRailBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide) { - if (!(Boolean) state.getValue(DetectorRailBlock.POWERED)) { - this.checkPressed(world, pos, state); -diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -index 07629f6106f384751c376d2a99ba2e8b905e49c6..9ee2fd0914ff7836517ca143d51db6150967cb0e 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -@@ -52,6 +52,7 @@ public class EndPortalBlock extends BaseEntityBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (world instanceof ServerLevel && entity.canChangeDimensions() && Shapes.joinIsNotEmpty(Shapes.create(entity.getBoundingBox().move((double) (-pos.getX()), (double) (-pos.getY()), (double) (-pos.getZ()))), state.getShape(world, pos), BooleanOp.AND)) { - ResourceKey resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends - ServerLevel worldserver = ((ServerLevel) world).getServer().getLevel(resourcekey); -diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -index d5c83f3b9d398b2a025e6729980b1b87b35f38a8..68978e1629381ada161225c53a236a54deae6481 100644 ---- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -@@ -78,6 +78,7 @@ public class FrogspawnBlock extends Block { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity.getType().equals(EntityType.FALLING_BLOCK)) { - this.destroyBlock(world, pos); - } -diff --git a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java -index 8b84359316e559b94ae6a2d757bda2286d99a903..39e2fe8c5f5a2a4d4f3a7be3645923b5a1dca875 100644 ---- a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java -@@ -61,6 +61,7 @@ public class HoneyBlock extends HalfTransparentBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (this.isSlidingDown(pos, entity)) { - this.maybeDoSlideAchievement(entity, pos); - this.doSlideMovement(entity); -diff --git a/src/main/java/net/minecraft/world/level/block/HopperBlock.java b/src/main/java/net/minecraft/world/level/block/HopperBlock.java -index 99a5821a55f2d2947722d64d60f4ee4ba5dfa74c..04e69d6066faf1c605aeeabe827dc20fc96a3568 100644 ---- a/src/main/java/net/minecraft/world/level/block/HopperBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/HopperBlock.java -@@ -200,6 +200,7 @@ public class HopperBlock extends BaseEntityBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - BlockEntity blockEntity = world.getBlockEntity(pos); - if (blockEntity instanceof HopperBlockEntity) { - HopperBlockEntity.entityInside(world, pos, state, entity, (HopperBlockEntity)blockEntity); -diff --git a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java -index 7e926a4546f89da22080ef28c3858ac7b3cf80f1..29d4553df54236706c76fdec0bb14b5d9fb82ff2 100644 ---- a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java -@@ -32,6 +32,7 @@ public class LavaCauldronBlock extends AbstractCauldronBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (this.isEntityInsideContent(state, pos, entity)) { - entity.lavaHurt(); - } -diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -index 64247c3a81273277b95656885c78eca3e883ef13..18ca086e28f4295cb9303919222d7c8ae0ca5d9a 100644 ---- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -@@ -65,6 +65,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { - // CraftBukkit start - if (entity.mayInteract(world, pos)) { -diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -index 2a8f97d97ae7f268da920b5e3b9719743fa9a8e0..1b5cc5d6aa0b4313da980ce175c54145852d0db0 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -@@ -90,6 +90,7 @@ public class NetherPortalBlock extends Block { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity.canChangeDimensions()) { - // CraftBukkit start - Entity in portal - EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); -diff --git a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java -index f7154d56ad42e0dde497e0e585cc554d34865e3b..690abba9d27ab4061de4dd3676292b1859036868 100644 ---- a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java -@@ -94,6 +94,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity instanceof Ravager && world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { - world.destroyBlock(pos, true, entity); - } -diff --git a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -index b38658b84e821435ba5a3fc7218b72406da00e47..0dfcac8cfcbb09fe04486bff60119f7985714454 100644 ---- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -@@ -63,6 +63,7 @@ public class PowderSnowBlock extends Block implements BucketPickup { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!(entity instanceof LivingEntity) || entity.getFeetBlockState().is((Block) this)) { - entity.makeStuckInBlock(state, new Vec3(0.8999999761581421D, 1.5D, 0.8999999761581421D)); - if (world.isClientSide) { -diff --git a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java -index c733b9dc26370322e404b56ac3feb3417948cb90..6c1ed9d37adb97b47f0288a5986b805ee0e13842 100644 ---- a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java -@@ -84,6 +84,7 @@ public class SweetBerryBushBlock extends BushBlock implements BonemealableBlock - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (entity instanceof LivingEntity && entity.getType() != EntityType.FOX && entity.getType() != EntityType.BEE) { - entity.makeStuckInBlock(state, new Vec3(0.800000011920929D, 0.75D, 0.800000011920929D)); - if (!world.isClientSide && (Integer) state.getValue(SweetBerryBushBlock.AGE) > 0 && (entity.xOld != entity.getX() || entity.zOld != entity.getZ())) { -diff --git a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -index d092ee2aa4a37c89642133dca7049737c55a4245..003fd247b12323cca5fd82a6cdf31bd897afd682 100644 ---- a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -@@ -134,6 +134,7 @@ public class TripWireBlock extends Block { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide) { - if (!(Boolean) state.getValue(TripWireBlock.POWERED)) { - this.checkPressed(world, pos); -diff --git a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -index d89a4c30599cedcb8ce17899631cb58c8a6a2195..61abbcfe97e3d3e3da5ee658672549d14594ad17 100644 ---- a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -@@ -34,6 +34,7 @@ public class WaterlilyBlock extends BushBlock { - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { - super.entityInside(state, world, pos, entity); -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (world instanceof ServerLevel && entity instanceof Boat) { - // CraftBukkit start - if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState())) { -diff --git a/src/main/java/net/minecraft/world/level/block/WebBlock.java b/src/main/java/net/minecraft/world/level/block/WebBlock.java -index 7206dfa5bdfb94ff98bcdfc735367c22493e925e..4905de6446f07e2fff53a3c8580b64b96306c373 100644 ---- a/src/main/java/net/minecraft/world/level/block/WebBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WebBlock.java -@@ -22,6 +22,7 @@ public class WebBlock extends Block { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - entity.makeStuckInBlock(state, new Vec3(0.25D, (double)0.05F, 0.25D)); - } - } -diff --git a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java -index c10a01013f8393c677834136babed15817b45611..a49027c73ca461e0610914138cc660c89965f956 100644 ---- a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java -@@ -61,6 +61,7 @@ public class WitherRoseBlock extends FlowerBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide && world.getDifficulty() != Difficulty.PEACEFUL) { - if (entity instanceof LivingEntity) { - LivingEntity entityliving = (LivingEntity) entity; diff --git a/patches/server/0567-Attributes-API-for-item-defaults.patch b/patches/server/0567-Attributes-API-for-item-defaults.patch new file mode 100644 index 000000000000..65265cb696e0 --- /dev/null +++ b/patches/server/0567-Attributes-API-for-item-defaults.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 8 May 2021 15:01:54 -0700 +Subject: [PATCH] Attributes API for item defaults + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 6eca86a2ab7145f4c60abb90cd46b4098ad8fb56..b89171cb89e3d38f3260ead8549cccde904db7c4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -559,6 +559,19 @@ public final class CraftMagicNumbers implements UnsafeValues { + return CraftMagicNumbers.getItem(itemToBeRepaired.getType()).isValidRepairItem(CraftItemStack.asNMSCopy(itemToBeRepaired), CraftItemStack.asNMSCopy(repairMaterial)); + } + ++ @Override ++ public Multimap getItemAttributes(Material material, EquipmentSlot equipmentSlot) { ++ Item item = CraftMagicNumbers.getItem(material); ++ if (item == null) { ++ throw new IllegalArgumentException(material + " is not an item and therefore does not have attributes"); ++ } ++ ImmutableMultimap.Builder attributeMapBuilder = ImmutableMultimap.builder(); ++ item.getDefaultAttributeModifiers(CraftEquipmentSlot.getNMS(equipmentSlot)).forEach((attributeBase, attributeModifier) -> { ++ attributeMapBuilder.put(CraftAttribute.stringToBukkit(net.minecraft.core.registries.BuiltInRegistries.ATTRIBUTE.getKey(attributeBase).toString()), CraftAttributeInstance.convert(attributeModifier, equipmentSlot)); ++ }); ++ return attributeMapBuilder.build(); ++ } ++ + @Override + public int getProtocolVersion() { + return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); diff --git a/patches/server/0568-Add-cause-to-Weather-ThunderChangeEvents.patch b/patches/server/0568-Add-cause-to-Weather-ThunderChangeEvents.patch new file mode 100644 index 000000000000..04bbf4641aaa --- /dev/null +++ b/patches/server/0568-Add-cause-to-Weather-ThunderChangeEvents.patch @@ -0,0 +1,118 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Dec 2020 18:23:26 -0800 +Subject: [PATCH] Add cause to Weather/ThunderChangeEvents + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index bc7177bc0699e64933399a1ed0e66fee0663636d..3f0745a9bfba35c0b133aa4bf2312e1b5eb640aa 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -434,8 +434,8 @@ public class ServerLevel extends Level implements WorldGenLevel { + this.serverLevelData.setClearWeatherTime(clearDuration); + this.serverLevelData.setRainTime(rainDuration); + this.serverLevelData.setThunderTime(rainDuration); +- this.serverLevelData.setRaining(raining); +- this.serverLevelData.setThundering(thundering); ++ this.serverLevelData.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents ++ this.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents + } + + @Override +@@ -868,8 +868,8 @@ public class ServerLevel extends Level implements WorldGenLevel { + this.serverLevelData.setThunderTime(j); + this.serverLevelData.setRainTime(k); + this.serverLevelData.setClearWeatherTime(i); +- this.serverLevelData.setThundering(flag1); +- this.serverLevelData.setRaining(flag2); ++ this.serverLevelData.setThundering(flag1, org.bukkit.event.weather.ThunderChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents ++ this.serverLevelData.setRaining(flag2, org.bukkit.event.weather.WeatherChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents + } + + this.oThunderLevel = this.thunderLevel; +@@ -936,14 +936,14 @@ public class ServerLevel extends Level implements WorldGenLevel { + @VisibleForTesting + public void resetWeatherCycle() { + // CraftBukkit start +- this.serverLevelData.setRaining(false); ++ this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents + // If we stop due to everyone sleeping we should reset the weather duration to some other random value. + // Not that everyone ever manages to get the whole server to sleep at the same time.... + if (!this.serverLevelData.isRaining()) { + this.serverLevelData.setRainTime(0); + } + // CraftBukkit end +- this.serverLevelData.setThundering(false); ++ this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents + // CraftBukkit start + // If we stop due to everyone sleeping we should reset the weather duration to some other random value. + // Not that everyone ever manages to get the whole server to sleep at the same time.... +diff --git a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java +index f5ac36fa54f3d3b39de103c95abb9ca3adfe8dda..59ba982dc96ce47e47399514e8f74d2b972dbe1e 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java ++++ b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java +@@ -373,6 +373,11 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + @Override + public void setThundering(boolean thundering) { ++ // Paper start - Add cause to Weather/ThunderChangeEvents ++ this.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.UNKNOWN); ++ } ++ public void setThundering(boolean thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause cause) { ++ // Paper end - Add cause to Weather/ThunderChangeEvents + // CraftBukkit start + if (this.thundering == thundering) { + return; +@@ -380,7 +385,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); + if (world != null) { +- ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering); ++ ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering, cause); // Paper - Add cause to Weather/ThunderChangeEvents + Bukkit.getServer().getPluginManager().callEvent(thunder); + if (thunder.isCancelled()) { + return; +@@ -407,6 +412,12 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + @Override + public void setRaining(boolean raining) { ++ // Paper start - Add cause to Weather/ThunderChangeEvents ++ this.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.UNKNOWN); ++ } ++ ++ public void setRaining(boolean raining, org.bukkit.event.weather.WeatherChangeEvent.Cause cause) { ++ // Paper end - Add cause to Weather/ThunderChangeEvents + // CraftBukkit start + if (this.raining == raining) { + return; +@@ -414,7 +425,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); + if (world != null) { +- WeatherChangeEvent weather = new WeatherChangeEvent(world, raining); ++ WeatherChangeEvent weather = new WeatherChangeEvent(world, raining, cause); // Paper - Add cause to Weather/ThunderChangeEvents + Bukkit.getServer().getPluginManager().callEvent(weather); + if (weather.isCancelled()) { + return; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index c90cbf4681e1c57fcec553b01d99a26316f896e5..05b9e7011d1c127052b73a464fc86331e2a4774a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1177,7 +1177,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setStorm(boolean hasStorm) { +- this.world.levelData.setRaining(hasStorm); ++ this.world.serverLevelData.setRaining(hasStorm, org.bukkit.event.weather.WeatherChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents + this.setWeatherDuration(0); // Reset weather duration (legacy behaviour) + this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) + } +@@ -1199,7 +1199,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setThundering(boolean thundering) { +- this.world.serverLevelData.setThundering(thundering); ++ this.world.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents + this.setThunderDuration(0); // Reset weather duration (legacy behaviour) + this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) + } diff --git a/patches/server/0568-Attributes-API-for-item-defaults.patch b/patches/server/0568-Attributes-API-for-item-defaults.patch deleted file mode 100644 index 395e09608dfb..000000000000 --- a/patches/server/0568-Attributes-API-for-item-defaults.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 8 May 2021 15:01:54 -0700 -Subject: [PATCH] Attributes API for item defaults - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index b0f365fe009be57cdf64983d9975c6bd873a33b5..fd35c5102aa4e14f5eb707884be64120c2b13276 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -544,6 +544,19 @@ public final class CraftMagicNumbers implements UnsafeValues { - return CraftMagicNumbers.getItem(itemToBeRepaired.getType()).isValidRepairItem(CraftItemStack.asNMSCopy(itemToBeRepaired), CraftItemStack.asNMSCopy(repairMaterial)); - } - -+ @Override -+ public Multimap getItemAttributes(Material material, EquipmentSlot equipmentSlot) { -+ Item item = CraftMagicNumbers.getItem(material); -+ if (item == null) { -+ throw new IllegalArgumentException(material + " is not an item and therefore does not have attributes"); -+ } -+ ImmutableMultimap.Builder attributeMapBuilder = ImmutableMultimap.builder(); -+ item.getDefaultAttributeModifiers(CraftEquipmentSlot.getNMS(equipmentSlot)).forEach((attributeBase, attributeModifier) -> { -+ attributeMapBuilder.put(CraftAttribute.stringToBukkit(net.minecraft.core.registries.BuiltInRegistries.ATTRIBUTE.getKey(attributeBase).toString()), CraftAttributeInstance.convert(attributeModifier, equipmentSlot)); -+ }); -+ return attributeMapBuilder.build(); -+ } -+ - @Override - public int getProtocolVersion() { - return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); diff --git a/patches/server/0569-Add-cause-to-Weather-ThunderChangeEvents.patch b/patches/server/0569-Add-cause-to-Weather-ThunderChangeEvents.patch deleted file mode 100644 index eb81834be810..000000000000 --- a/patches/server/0569-Add-cause-to-Weather-ThunderChangeEvents.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 2 Dec 2020 18:23:26 -0800 -Subject: [PATCH] Add cause to Weather/ThunderChangeEvents - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 379be7cd3ce1808cf0cf50e50ac7e8de8c8f652c..da2a8f57b733b84106ed0818f4402d9c9d854481 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -434,8 +434,8 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.serverLevelData.setClearWeatherTime(clearDuration); - this.serverLevelData.setRainTime(rainDuration); - this.serverLevelData.setThunderTime(rainDuration); -- this.serverLevelData.setRaining(raining); -- this.serverLevelData.setThundering(thundering); -+ this.serverLevelData.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents -+ this.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents - } - - @Override -@@ -868,8 +868,8 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.serverLevelData.setThunderTime(j); - this.serverLevelData.setRainTime(k); - this.serverLevelData.setClearWeatherTime(i); -- this.serverLevelData.setThundering(flag1); -- this.serverLevelData.setRaining(flag2); -+ this.serverLevelData.setThundering(flag1, org.bukkit.event.weather.ThunderChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents -+ this.serverLevelData.setRaining(flag2, org.bukkit.event.weather.WeatherChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents - } - - this.oThunderLevel = this.thunderLevel; -@@ -936,14 +936,14 @@ public class ServerLevel extends Level implements WorldGenLevel { - @VisibleForTesting - public void resetWeatherCycle() { - // CraftBukkit start -- this.serverLevelData.setRaining(false); -+ this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents - // If we stop due to everyone sleeping we should reset the weather duration to some other random value. - // Not that everyone ever manages to get the whole server to sleep at the same time.... - if (!this.serverLevelData.isRaining()) { - this.serverLevelData.setRainTime(0); - } - // CraftBukkit end -- this.serverLevelData.setThundering(false); -+ this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents - // CraftBukkit start - // If we stop due to everyone sleeping we should reset the weather duration to some other random value. - // Not that everyone ever manages to get the whole server to sleep at the same time.... -diff --git a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java -index f5ac36fa54f3d3b39de103c95abb9ca3adfe8dda..59ba982dc96ce47e47399514e8f74d2b972dbe1e 100644 ---- a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java -+++ b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java -@@ -373,6 +373,11 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { - - @Override - public void setThundering(boolean thundering) { -+ // Paper start - Add cause to Weather/ThunderChangeEvents -+ this.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.UNKNOWN); -+ } -+ public void setThundering(boolean thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause cause) { -+ // Paper end - Add cause to Weather/ThunderChangeEvents - // CraftBukkit start - if (this.thundering == thundering) { - return; -@@ -380,7 +385,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { - - org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); - if (world != null) { -- ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering); -+ ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering, cause); // Paper - Add cause to Weather/ThunderChangeEvents - Bukkit.getServer().getPluginManager().callEvent(thunder); - if (thunder.isCancelled()) { - return; -@@ -407,6 +412,12 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { - - @Override - public void setRaining(boolean raining) { -+ // Paper start - Add cause to Weather/ThunderChangeEvents -+ this.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.UNKNOWN); -+ } -+ -+ public void setRaining(boolean raining, org.bukkit.event.weather.WeatherChangeEvent.Cause cause) { -+ // Paper end - Add cause to Weather/ThunderChangeEvents - // CraftBukkit start - if (this.raining == raining) { - return; -@@ -414,7 +425,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { - - org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); - if (world != null) { -- WeatherChangeEvent weather = new WeatherChangeEvent(world, raining); -+ WeatherChangeEvent weather = new WeatherChangeEvent(world, raining, cause); // Paper - Add cause to Weather/ThunderChangeEvents - Bukkit.getServer().getPluginManager().callEvent(weather); - if (weather.isCancelled()) { - return; -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 6724d0b4e857a9671eac89445ceb70c070b29929..c70599006c16ea342ad1b50915cda13673431e79 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1164,7 +1164,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void setStorm(boolean hasStorm) { -- this.world.levelData.setRaining(hasStorm); -+ this.world.serverLevelData.setRaining(hasStorm, org.bukkit.event.weather.WeatherChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents - this.setWeatherDuration(0); // Reset weather duration (legacy behaviour) - this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) - } -@@ -1186,7 +1186,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void setThundering(boolean thundering) { -- this.world.serverLevelData.setThundering(thundering); -+ this.world.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents - this.setThunderDuration(0); // Reset weather duration (legacy behaviour) - this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) - } diff --git a/patches/server/0570-More-Lidded-Block-API.patch b/patches/server/0569-More-Lidded-Block-API.patch similarity index 100% rename from patches/server/0570-More-Lidded-Block-API.patch rename to patches/server/0569-More-Lidded-Block-API.patch diff --git a/patches/server/0571-Limit-item-frame-cursors-on-maps.patch b/patches/server/0570-Limit-item-frame-cursors-on-maps.patch similarity index 100% rename from patches/server/0571-Limit-item-frame-cursors-on-maps.patch rename to patches/server/0570-Limit-item-frame-cursors-on-maps.patch diff --git a/patches/server/0571-Add-PlayerKickEvent-causes.patch b/patches/server/0571-Add-PlayerKickEvent-causes.patch new file mode 100644 index 000000000000..e394713f3c39 --- /dev/null +++ b/patches/server/0571-Add-PlayerKickEvent-causes.patch @@ -0,0 +1,544 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 15 May 2021 20:30:45 -0700 +Subject: [PATCH] Add PlayerKickEvent causes + + +diff --git a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java +index 96814e626a95e4e3c2f4df1a0339d37bb02f2e61..ba12919c3f9aec34a9e64993b143ae92be5eb172 100644 +--- a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java ++++ b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java +@@ -35,16 +35,16 @@ public class SignedMessageChain { + return (signature, body) -> { + SignedMessageLink signedMessageLink = this.advanceLink(); + if (signedMessageLink == null) { +- throw new SignedMessageChain.DecodeException(Component.translatable("chat.disabled.chain_broken"), false); ++ 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); ++ 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)) { +- throw new SignedMessageChain.DecodeException(Component.translatable("multiplayer.disconnect.out_of_order_chat"), true); ++ 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(); + PlayerChatMessage playerChatMessage = new PlayerChatMessage(signedMessageLink, signature, body, (Component)null, FilterMask.PASS_THROUGH); + if (!playerChatMessage.verify(signatureValidator)) { +- throw new SignedMessageChain.DecodeException(Component.translatable("multiplayer.disconnect.unsigned_chat"), true); ++ throw new SignedMessageChain.DecodeException(Component.translatable("multiplayer.disconnect.unsigned_chat"), true, org.bukkit.event.player.PlayerKickEvent.Cause.UNSIGNED_CHAT); // Paper - kick event causes + } else { + if (playerChatMessage.hasExpiredServer(Instant.now())) { + LOGGER.warn("Received expired chat: '{}'. Is the client/server system time unsynchronized?", (Object)body.content()); +@@ -68,10 +68,17 @@ public class SignedMessageChain { + + public static class DecodeException extends ThrowingComponent { + private final boolean shouldDisconnect; ++ public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause; // Paper - kick event causes + + public DecodeException(Component message, boolean shouldDisconnect) { ++ // Paper start - kick event causes ++ this(message, shouldDisconnect, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); ++ } ++ public DecodeException(Component message, boolean shouldDisconnect, org.bukkit.event.player.PlayerKickEvent.Cause kickCause) { ++ // Paper end - kick event causes + super(message); + this.shouldDisconnect = shouldDisconnect; ++ this.kickCause = kickCause; // Paper - kick event causes + } + + public boolean shouldDisconnect() { +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 773e4850956a7ffcd78cc241a598fd13bcfe1d20..7ee46b9f98794d1fec0a8feea71fd495f9199dd0 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2167,7 +2167,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + return Component.translatable("commands.kick.success", serverPlayer.getDisplayName(), reason); + }, true); +diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index 6bb846d3ee2fb54ab3ffa116607f2a83e538460e..a65a1466dab52fca75cda16a4b22fef03b6207a0 100644 +--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -95,7 +95,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } else if (!this.isSingleplayerOwner()) { + // Paper start - This needs to be handled on the main thread for plugins + server.submit(() -> { +- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); ++ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause + }); + // Paper end - This needs to be handled on the main thread for plugins + } +@@ -131,7 +131,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); +- this.disconnect("Invalid payload REGISTER!"); ++ this.disconnect("Invalid payload REGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) { + try { +@@ -141,7 +141,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex); +- this.disconnect("Invalid payload UNREGISTER!"); ++ this.disconnect("Invalid payload UNREGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } else { + try { +@@ -159,7 +159,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); +- this.disconnect("Invalid custom payload!"); ++ this.disconnect("Invalid custom payload!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } + +@@ -175,7 +175,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + PacketUtils.ensureRunningOnSameThread(packet, this, (BlockableEventLoop) this.server); + if (packet.action() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) { + ServerCommonPacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id()); +- this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); ++ this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause + } + // Paper start - adventure pack callbacks + // call the callbacks before the previously-existing event so the event has final say +@@ -207,7 +207,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + if (this.keepAlivePending) { + if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getScoreboardName()); // more info +- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); ++ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause + } + } else { + if (elapsedTime >= 15000L) { // 15 seconds +@@ -260,18 +260,28 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } + + // CraftBukkit start +- @Deprecated ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper + public void disconnect(String s) { // Paper +- this.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s)); // Paper ++ this.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // Paper + } + // CraftBukkit end + ++ // Paper start - kick event cause ++ public void disconnect(String s, PlayerKickEvent.Cause cause) { ++ this.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s), cause); ++ } ++ + // Paper start ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper + public void disconnect(final Component reason) { +- this.disconnect(io.papermc.paper.adventure.PaperAdventure.asAdventure(reason)); ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asAdventure(reason), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); ++ } ++ ++ public void disconnect(final Component reason, PlayerKickEvent.Cause cause) { ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asAdventure(reason), cause); + } + +- public void disconnect(net.kyori.adventure.text.Component reason) { ++ public void disconnect(net.kyori.adventure.text.Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event cause + // Paper end + // CraftBukkit start - fire PlayerKickEvent + if (this.processedDisconnect) { +@@ -281,7 +291,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + Waitable waitable = new Waitable() { + @Override + protected Object evaluate() { +- ServerCommonPacketListenerImpl.this.disconnect(reason); // Paper - adventure ++ ServerCommonPacketListenerImpl.this.disconnect(reason, cause); // Paper - adventure + return null; + } + }; +@@ -300,7 +310,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + + net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure + +- PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), reason, leaveMessage); // Paper - adventure ++ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), reason, leaveMessage, cause); // Paper - adventure + + if (this.cserver.getServer().isRunning()) { + this.cserver.getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index bd212b89412c099216828ab5653ae3b9e1ec5665..28a0570988f93b21f530a6cca87efa429f83079d 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -341,7 +341,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) { + if (++this.aboveGroundTickCount > 80) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); +- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer); // Paper - use configurable kick message ++ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause + return; + } + } else { +@@ -360,7 +360,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + if (this.clientVehicleIsFloating && this.player.getRootVehicle().getControllingPassenger() == this.player) { + if (++this.aboveGroundVehicleTickCount > 80) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); +- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle); // Paper - use configurable kick message ++ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause + return; + } + } else { +@@ -391,7 +391,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { + this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 +- this.disconnect(Component.translatable("multiplayer.disconnect.idling")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause + } + + } +@@ -461,7 +461,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(), packet.getY(), packet.getZ(), packet.getYRot(), packet.getXRot())) { +- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause + } else { + Entity entity = this.player.getRootVehicle(); + +@@ -663,7 +663,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (packet.getId() == this.awaitingTeleport) { + if (this.awaitingPositionFromClient == null) { +- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause + return; + } + +@@ -721,7 +721,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async + // CraftBukkit start + if (this.chatSpamTickCount.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper - configurable tab spam limits +- this.disconnect(Component.translatable("disconnect.spam")); ++ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - Kick event cause + return; + } + // CraftBukkit end +@@ -881,7 +881,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // Paper start - validate pick item position + if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.getInventory().items.size())) { + ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); +- this.disconnect("Invalid hotbar selection (Hacking?)"); ++ this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed +@@ -1066,7 +1066,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; + if (byteLength > 256 * 4) { + ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!"); +- server.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause + return; + } + byteTotal += byteLength; +@@ -1089,14 +1089,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + if (byteTotal > byteAllowed) { + ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); +- server.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause + return; + } + } + // Paper end - Book size limits + // CraftBukkit start + if (this.lastBookTick + 20 > MinecraftServer.currentTick) { +- this.disconnect("Book edited too quickly!"); ++ this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + this.lastBookTick = MinecraftServer.currentTick; +@@ -1240,7 +1240,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + public void handleMovePlayer(ServerboundMovePlayerPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) { +- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause + } else { + ServerLevel worldserver = this.player.serverLevel(); + +@@ -1660,7 +1660,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.dropCount++; + if (this.dropCount >= 20) { + ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " dropped their items too quickly!"); +- this.disconnect("You dropped your items too quickly (Hacking?)"); ++ this.disconnect("You dropped your items too quickly (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + } +@@ -1943,7 +1943,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.player.resetLastActionTime(); + } else { + ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); +- this.disconnect("Invalid hotbar selection (Hacking?)"); // CraftBukkit ++ this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // CraftBukkit // Paper - kick event cause + } + } + +@@ -1956,7 +1956,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + // CraftBukkit end + if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.message())) { +- this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add cause + } else { + Optional optional = this.tryHandleChat(packet.lastSeenMessages()); + +@@ -1988,7 +1988,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + @Override + public void handleChatCommand(ServerboundChatCommandPacket packet) { + if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.command())) { +- this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper + } else { + Optional optional = this.tryHandleChat(packet.lastSeenMessages()); + +@@ -2044,7 +2044,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + private void handleMessageDecodeFailure(SignedMessageChain.DecodeException exception) { + ServerGamePacketListenerImpl.LOGGER.warn("Failed to update secure chat state for {}: '{}'", this.player.getGameProfile().getName(), exception.getComponent().getString()); + if (exception.shouldDisconnect()) { +- this.disconnect(exception.getComponent()); ++ this.disconnect(exception.getComponent(), exception.kickCause); // Paper - kick event causes + } else { + this.player.sendSystemMessage(exception.getComponent().copy().withStyle(ChatFormatting.RED)); + } +@@ -2092,7 +2092,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + if (optional.isEmpty()) { + ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); +- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED); ++ this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes + } + + return optional; +@@ -2278,7 +2278,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // this.chatSpamTickCount += 20; + if (this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { + // CraftBukkit end +- this.disconnect(Component.translatable("disconnect.spam")); ++ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause + } + + } +@@ -2290,7 +2290,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + synchronized (this.lastSeenMessages) { + if (!this.lastSeenMessages.applyOffset(packet.offset())) { + ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); +- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED); ++ this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes + } + + } +@@ -2443,7 +2443,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + if (i > 4096) { +- this.disconnect(Component.translatable("multiplayer.disconnect.too_many_pending_chats")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.too_many_pending_chats"), org.bukkit.event.player.PlayerKickEvent.Cause.TOO_MANY_PENDING_CHATS); // Paper - kick event cause + } + + } +@@ -2500,7 +2500,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // Spigot Start + if ( entity == this.player && !this.player.isSpectator() ) + { +- this.disconnect( "Cannot interact with self!" ); ++ this.disconnect( "Cannot interact with self!" , org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION ); // Paper - kick event cause + return; + } + // Spigot End +@@ -2599,7 +2599,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // CraftBukkit end + } + } else { +- ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked")); ++ ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - add cause + ServerGamePacketListenerImpl.LOGGER.warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString()); + } + } +@@ -2997,7 +2997,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // Paper start - auto recipe limit + if (!org.bukkit.Bukkit.isPrimaryThread()) { + if (this.recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { +- this.server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam"))); ++ this.server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause + return; + } + } +@@ -3239,7 +3239,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + if (!Objects.equals(profilepublickey_a, profilepublickey_a1)) { + if (profilepublickey_a != null && profilepublickey_a1.expiresAt().isBefore(profilepublickey_a.expiresAt())) { +- this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY); ++ this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY, org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes + } else { + try { + SignatureValidator signaturevalidator = this.server.getProfileKeySignatureValidator(); +@@ -3252,7 +3252,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.resetPlayerChatState(remotechatsession_a.validate(this.player.getGameProfile(), signaturevalidator)); + } catch (ProfilePublicKey.ValidationException profilepublickey_b) { + ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); +- this.disconnect(profilepublickey_b.getComponent()); ++ this.disconnect(profilepublickey_b.getComponent(), profilepublickey_b.kickCause); // Paper - kick event causes + } + + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 27ae2ac95d4f53c1c16b35f737fa6c138ddcc644..1f3f316cd1946c4a0e1ba767a93beec7eb9f3f2b 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -682,7 +682,7 @@ public abstract class PlayerList { + while (iterator.hasNext()) { + entityplayer = (ServerPlayer) iterator.next(); + this.save(entityplayer); // CraftBukkit - Force the player's inventory to be saved +- entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login")); ++ entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause + } + + // Instead of kicking then returning, we need to store the kick reason +@@ -1318,8 +1318,8 @@ public abstract class PlayerList { + // Paper end + // CraftBukkit start - disconnect safely + for (ServerPlayer player : this.players) { +- if (isRestarting) player.connection.disconnect(org.spigotmc.SpigotConfig.restartMessage); else // Paper +- player.connection.disconnect(this.server.server.shutdownMessage()); // CraftBukkit - add custom shutdown message // Paper - Adventure ++ if (isRestarting) player.connection.disconnect(org.spigotmc.SpigotConfig.restartMessage, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); else // Paper - kick event cause (cause is never used here) ++ player.connection.disconnect(this.server.server.shutdownMessage(), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // CraftBukkit - add custom shutdown message // Paper - Adventure & KickEventCause (cause is never used here) + } + // CraftBukkit end + +diff --git a/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java b/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java +index 6724d0a1af13e97bc1d3bd94fd43fef742a0deab..20ba0a0c9eae28658888a77dd2170f629bbcb65b 100644 +--- a/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java ++++ b/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java +@@ -24,7 +24,7 @@ public record ProfilePublicKey(ProfilePublicKey.Data data) { + + public static ProfilePublicKey createValidated(SignatureValidator servicesSignatureVerifier, UUID playerUuid, ProfilePublicKey.Data publicKeyData) throws ProfilePublicKey.ValidationException { + if (!publicKeyData.validateSignature(servicesSignatureVerifier, playerUuid)) { +- throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE); ++ throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE, org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PUBLIC_KEY_SIGNATURE); // Paper - kick event causes + } else { + return new ProfilePublicKey(publicKeyData); + } +@@ -81,8 +81,16 @@ public record ProfilePublicKey(ProfilePublicKey.Data data) { + } + + public static class ValidationException extends ThrowingComponent { ++ public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause; // Paper ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper + public ValidationException(Component messageText) { ++ // Paper start ++ this(messageText, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); ++ } ++ public ValidationException(Component messageText, org.bukkit.event.player.PlayerKickEvent.Cause kickCause) { ++ // Paper end + super(messageText); ++ this.kickCause = kickCause; // Paper + } + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index fa5bf1ef9cb4df06eabce00ccdd86a408ddaef8f..5ff0081fa3cdd34698b4d995a0845709bb5b397f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -555,7 +555,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot + if (this.getHandle().connection == null) return; + +- this.getHandle().connection.disconnect(message == null ? "" : message); ++ this.getHandle().connection.disconnect(message == null ? "" : message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause + } + + // Paper start +@@ -567,10 +567,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void kick(final net.kyori.adventure.text.Component message) { ++ kick(message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); ++ } ++ ++ @Override ++ public void kick(net.kyori.adventure.text.Component message, org.bukkit.event.player.PlayerKickEvent.Cause cause) { + org.spigotmc.AsyncCatcher.catchOp("player kick"); + final ServerGamePacketListenerImpl connection = this.getHandle().connection; + if (connection != null) { +- connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message); ++ connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message, cause); + } + } + +@@ -629,7 +634,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + // Paper start - Improve chat handling + if (ServerGamePacketListenerImpl.isChatMessageIllegal(msg)) { +- this.getHandle().connection.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); ++ this.getHandle().connection.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - kick event causes + } else { + if (msg.startsWith("/")) { + this.getHandle().connection.handleCommand(msg); +diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java +index 051b9e3a5d29a5840d596468e3ddd013bedc8da3..e3b262add194a126e731c68e68f3139a00cacacb 100644 +--- a/src/main/java/org/spigotmc/RestartCommand.java ++++ b/src/main/java/org/spigotmc/RestartCommand.java +@@ -73,7 +73,7 @@ public class RestartCommand extends Command + // Kick all players + for ( ServerPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) ) + { +- p.connection.disconnect(SpigotConfig.restartMessage); ++ p.connection.disconnect(SpigotConfig.restartMessage, org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used)) + } + // Give the socket a chance to send the packets + try diff --git a/patches/server/0572-Add-PlayerKickEvent-causes.patch b/patches/server/0572-Add-PlayerKickEvent-causes.patch deleted file mode 100644 index b4470c2ecf88..000000000000 --- a/patches/server/0572-Add-PlayerKickEvent-causes.patch +++ /dev/null @@ -1,544 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 15 May 2021 20:30:45 -0700 -Subject: [PATCH] Add PlayerKickEvent causes - - -diff --git a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java -index 96814e626a95e4e3c2f4df1a0339d37bb02f2e61..ba12919c3f9aec34a9e64993b143ae92be5eb172 100644 ---- a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java -+++ b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java -@@ -35,16 +35,16 @@ public class SignedMessageChain { - return (signature, body) -> { - SignedMessageLink signedMessageLink = this.advanceLink(); - if (signedMessageLink == null) { -- throw new SignedMessageChain.DecodeException(Component.translatable("chat.disabled.chain_broken"), false); -+ 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); -+ 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)) { -- throw new SignedMessageChain.DecodeException(Component.translatable("multiplayer.disconnect.out_of_order_chat"), true); -+ 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(); - PlayerChatMessage playerChatMessage = new PlayerChatMessage(signedMessageLink, signature, body, (Component)null, FilterMask.PASS_THROUGH); - if (!playerChatMessage.verify(signatureValidator)) { -- throw new SignedMessageChain.DecodeException(Component.translatable("multiplayer.disconnect.unsigned_chat"), true); -+ throw new SignedMessageChain.DecodeException(Component.translatable("multiplayer.disconnect.unsigned_chat"), true, org.bukkit.event.player.PlayerKickEvent.Cause.UNSIGNED_CHAT); // Paper - kick event causes - } else { - if (playerChatMessage.hasExpiredServer(Instant.now())) { - LOGGER.warn("Received expired chat: '{}'. Is the client/server system time unsynchronized?", (Object)body.content()); -@@ -68,10 +68,17 @@ public class SignedMessageChain { - - public static class DecodeException extends ThrowingComponent { - private final boolean shouldDisconnect; -+ public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause; // Paper - kick event causes - - public DecodeException(Component message, boolean shouldDisconnect) { -+ // Paper start - kick event causes -+ this(message, shouldDisconnect, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); -+ } -+ public DecodeException(Component message, boolean shouldDisconnect, org.bukkit.event.player.PlayerKickEvent.Cause kickCause) { -+ // Paper end - kick event causes - super(message); - this.shouldDisconnect = shouldDisconnect; -+ this.kickCause = kickCause; // Paper - kick event causes - } - - public boolean shouldDisconnect() { -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 773e4850956a7ffcd78cc241a598fd13bcfe1d20..7ee46b9f98794d1fec0a8feea71fd495f9199dd0 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2167,7 +2167,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { - return Component.translatable("commands.kick.success", serverPlayer.getDisplayName(), reason); - }, true); -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 6bb846d3ee2fb54ab3ffa116607f2a83e538460e..a65a1466dab52fca75cda16a4b22fef03b6207a0 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -95,7 +95,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - } else if (!this.isSingleplayerOwner()) { - // Paper start - This needs to be handled on the main thread for plugins - server.submit(() -> { -- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); -+ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause - }); - // Paper end - This needs to be handled on the main thread for plugins - } -@@ -131,7 +131,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - } - } catch (Exception ex) { - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); -- this.disconnect("Invalid payload REGISTER!"); -+ this.disconnect("Invalid payload REGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause - } - } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) { - try { -@@ -141,7 +141,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - } - } catch (Exception ex) { - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex); -- this.disconnect("Invalid payload UNREGISTER!"); -+ this.disconnect("Invalid payload UNREGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause - } - } else { - try { -@@ -159,7 +159,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); - } catch (Exception ex) { - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); -- this.disconnect("Invalid custom payload!"); -+ this.disconnect("Invalid custom payload!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause - } - } - -@@ -175,7 +175,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - PacketUtils.ensureRunningOnSameThread(packet, this, (BlockableEventLoop) this.server); - if (packet.action() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) { - ServerCommonPacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id()); -- this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); -+ this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause - } - // Paper start - adventure pack callbacks - // call the callbacks before the previously-existing event so the event has final say -@@ -207,7 +207,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - if (this.keepAlivePending) { - if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected - ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getScoreboardName()); // more info -- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); -+ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause - } - } else { - if (elapsedTime >= 15000L) { // 15 seconds -@@ -260,18 +260,28 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - } - - // CraftBukkit start -- @Deprecated -+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - public void disconnect(String s) { // Paper -- this.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s)); // Paper -+ this.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // Paper - } - // CraftBukkit end - -+ // Paper start - kick event cause -+ public void disconnect(String s, PlayerKickEvent.Cause cause) { -+ this.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s), cause); -+ } -+ - // Paper start -+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - public void disconnect(final Component reason) { -- this.disconnect(io.papermc.paper.adventure.PaperAdventure.asAdventure(reason)); -+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asAdventure(reason), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); -+ } -+ -+ public void disconnect(final Component reason, PlayerKickEvent.Cause cause) { -+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asAdventure(reason), cause); - } - -- public void disconnect(net.kyori.adventure.text.Component reason) { -+ public void disconnect(net.kyori.adventure.text.Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event cause - // Paper end - // CraftBukkit start - fire PlayerKickEvent - if (this.processedDisconnect) { -@@ -281,7 +291,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - Waitable waitable = new Waitable() { - @Override - protected Object evaluate() { -- ServerCommonPacketListenerImpl.this.disconnect(reason); // Paper - adventure -+ ServerCommonPacketListenerImpl.this.disconnect(reason, cause); // Paper - adventure - return null; - } - }; -@@ -300,7 +310,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - - net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure - -- PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), reason, leaveMessage); // Paper - adventure -+ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), reason, leaveMessage, cause); // Paper - adventure - - if (this.cserver.getServer().isRunning()) { - this.cserver.getPluginManager().callEvent(event); -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 4824c5f0f4c0e165e3622aac23e501f56e09fc73..1dcc0852bcaf44efaa9ff1e63560ddb9968a494a 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -341,7 +341,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) { - if (++this.aboveGroundTickCount > 80) { - ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); -- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer); // Paper - use configurable kick message -+ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause - return; - } - } else { -@@ -360,7 +360,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - if (this.clientVehicleIsFloating && this.player.getRootVehicle().getControllingPassenger() == this.player) { - if (++this.aboveGroundVehicleTickCount > 80) { - ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); -- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle); // Paper - use configurable kick message -+ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause - return; - } - } else { -@@ -391,7 +391,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { - this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 -- this.disconnect(Component.translatable("multiplayer.disconnect.idling")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause - } - - } -@@ -461,7 +461,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(), packet.getY(), packet.getZ(), packet.getYRot(), packet.getXRot())) { -- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause - } else { - Entity entity = this.player.getRootVehicle(); - -@@ -663,7 +663,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (packet.getId() == this.awaitingTeleport) { - if (this.awaitingPositionFromClient == null) { -- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause - return; - } - -@@ -721,7 +721,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async - // CraftBukkit start - if (this.chatSpamTickCount.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper - configurable tab spam limits -- this.disconnect(Component.translatable("disconnect.spam")); -+ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - Kick event cause - return; - } - // CraftBukkit end -@@ -881,7 +881,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // Paper start - validate pick item position - if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.getInventory().items.size())) { - ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); -- this.disconnect("Invalid hotbar selection (Hacking?)"); -+ this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause - return; - } - this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed -@@ -1066,7 +1066,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; - if (byteLength > 256 * 4) { - ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!"); -- server.scheduleOnMain(() -> this.disconnect("Book too large!")); -+ server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause - return; - } - byteTotal += byteLength; -@@ -1089,14 +1089,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - if (byteTotal > byteAllowed) { - ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); -- server.scheduleOnMain(() -> this.disconnect("Book too large!")); -+ server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause - return; - } - } - // Paper end - Book size limits - // CraftBukkit start - if (this.lastBookTick + 20 > MinecraftServer.currentTick) { -- this.disconnect("Book edited too quickly!"); -+ this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause - return; - } - this.lastBookTick = MinecraftServer.currentTick; -@@ -1240,7 +1240,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - public void handleMovePlayer(ServerboundMovePlayerPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) { -- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause - } else { - ServerLevel worldserver = this.player.serverLevel(); - -@@ -1660,7 +1660,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.dropCount++; - if (this.dropCount >= 20) { - ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " dropped their items too quickly!"); -- this.disconnect("You dropped your items too quickly (Hacking?)"); -+ this.disconnect("You dropped your items too quickly (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause - return; - } - } -@@ -1943,7 +1943,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.player.resetLastActionTime(); - } else { - ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); -- this.disconnect("Invalid hotbar selection (Hacking?)"); // CraftBukkit -+ this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // CraftBukkit // Paper - kick event cause - } - } - -@@ -1956,7 +1956,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - // CraftBukkit end - if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.message())) { -- this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add cause - } else { - Optional optional = this.tryHandleChat(packet.lastSeenMessages()); - -@@ -1988,7 +1988,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - @Override - public void handleChatCommand(ServerboundChatCommandPacket packet) { - if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.command())) { -- this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - } else { - Optional optional = this.tryHandleChat(packet.lastSeenMessages()); - -@@ -2044,7 +2044,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private void handleMessageDecodeFailure(SignedMessageChain.DecodeException exception) { - ServerGamePacketListenerImpl.LOGGER.warn("Failed to update secure chat state for {}: '{}'", this.player.getGameProfile().getName(), exception.getComponent().getString()); - if (exception.shouldDisconnect()) { -- this.disconnect(exception.getComponent()); -+ this.disconnect(exception.getComponent(), exception.kickCause); // Paper - kick event causes - } else { - this.player.sendSystemMessage(exception.getComponent().copy().withStyle(ChatFormatting.RED)); - } -@@ -2092,7 +2092,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - if (optional.isEmpty()) { - ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); -- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED); -+ this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes - } - - return optional; -@@ -2278,7 +2278,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // this.chatSpamTickCount += 20; - if (this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { - // CraftBukkit end -- this.disconnect(Component.translatable("disconnect.spam")); -+ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause - } - - } -@@ -2290,7 +2290,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - synchronized (this.lastSeenMessages) { - if (!this.lastSeenMessages.applyOffset(packet.offset())) { - ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); -- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED); -+ this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes - } - - } -@@ -2443,7 +2443,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - if (i > 4096) { -- this.disconnect(Component.translatable("multiplayer.disconnect.too_many_pending_chats")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.too_many_pending_chats"), org.bukkit.event.player.PlayerKickEvent.Cause.TOO_MANY_PENDING_CHATS); // Paper - kick event cause - } - - } -@@ -2500,7 +2500,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // Spigot Start - if ( entity == this.player && !this.player.isSpectator() ) - { -- this.disconnect( "Cannot interact with self!" ); -+ this.disconnect( "Cannot interact with self!" , org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION ); // Paper - kick event cause - return; - } - // Spigot End -@@ -2599,7 +2599,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // CraftBukkit end - } - } else { -- ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked")); -+ ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - add cause - ServerGamePacketListenerImpl.LOGGER.warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString()); - } - } -@@ -2997,7 +2997,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // Paper start - auto recipe limit - if (!org.bukkit.Bukkit.isPrimaryThread()) { - if (this.recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { -- this.server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam"))); -+ this.server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause - return; - } - } -@@ -3239,7 +3239,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - if (!Objects.equals(profilepublickey_a, profilepublickey_a1)) { - if (profilepublickey_a != null && profilepublickey_a1.expiresAt().isBefore(profilepublickey_a.expiresAt())) { -- this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY); -+ this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY, org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes - } else { - try { - SignatureValidator signaturevalidator = this.server.getProfileKeySignatureValidator(); -@@ -3252,7 +3252,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.resetPlayerChatState(remotechatsession_a.validate(this.player.getGameProfile(), signaturevalidator)); - } catch (ProfilePublicKey.ValidationException profilepublickey_b) { - ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); -- this.disconnect(profilepublickey_b.getComponent()); -+ this.disconnect(profilepublickey_b.getComponent(), profilepublickey_b.kickCause); // Paper - kick event causes - } - - } -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 27ae2ac95d4f53c1c16b35f737fa6c138ddcc644..1f3f316cd1946c4a0e1ba767a93beec7eb9f3f2b 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -682,7 +682,7 @@ public abstract class PlayerList { - while (iterator.hasNext()) { - entityplayer = (ServerPlayer) iterator.next(); - this.save(entityplayer); // CraftBukkit - Force the player's inventory to be saved -- entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login")); -+ entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause - } - - // Instead of kicking then returning, we need to store the kick reason -@@ -1318,8 +1318,8 @@ public abstract class PlayerList { - // Paper end - // CraftBukkit start - disconnect safely - for (ServerPlayer player : this.players) { -- if (isRestarting) player.connection.disconnect(org.spigotmc.SpigotConfig.restartMessage); else // Paper -- player.connection.disconnect(this.server.server.shutdownMessage()); // CraftBukkit - add custom shutdown message // Paper - Adventure -+ if (isRestarting) player.connection.disconnect(org.spigotmc.SpigotConfig.restartMessage, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); else // Paper - kick event cause (cause is never used here) -+ player.connection.disconnect(this.server.server.shutdownMessage(), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // CraftBukkit - add custom shutdown message // Paper - Adventure & KickEventCause (cause is never used here) - } - // CraftBukkit end - -diff --git a/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java b/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java -index 6724d0a1af13e97bc1d3bd94fd43fef742a0deab..20ba0a0c9eae28658888a77dd2170f629bbcb65b 100644 ---- a/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java -+++ b/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java -@@ -24,7 +24,7 @@ public record ProfilePublicKey(ProfilePublicKey.Data data) { - - public static ProfilePublicKey createValidated(SignatureValidator servicesSignatureVerifier, UUID playerUuid, ProfilePublicKey.Data publicKeyData) throws ProfilePublicKey.ValidationException { - if (!publicKeyData.validateSignature(servicesSignatureVerifier, playerUuid)) { -- throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE); -+ throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE, org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PUBLIC_KEY_SIGNATURE); // Paper - kick event causes - } else { - return new ProfilePublicKey(publicKeyData); - } -@@ -81,8 +81,16 @@ public record ProfilePublicKey(ProfilePublicKey.Data data) { - } - - public static class ValidationException extends ThrowingComponent { -+ public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause; // Paper -+ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - public ValidationException(Component messageText) { -+ // Paper start -+ this(messageText, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); -+ } -+ public ValidationException(Component messageText, org.bukkit.event.player.PlayerKickEvent.Cause kickCause) { -+ // Paper end - super(messageText); -+ this.kickCause = kickCause; // Paper - } - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 9356752eae2499654f26fb60490490adbc1010c9..8b54fe6ee1c07a70f9823f1a2a13887620a6dfda 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -549,7 +549,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot - if (this.getHandle().connection == null) return; - -- this.getHandle().connection.disconnect(message == null ? "" : message); -+ this.getHandle().connection.disconnect(message == null ? "" : message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause - } - - // Paper start -@@ -561,10 +561,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void kick(final net.kyori.adventure.text.Component message) { -+ kick(message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); -+ } -+ -+ @Override -+ public void kick(net.kyori.adventure.text.Component message, org.bukkit.event.player.PlayerKickEvent.Cause cause) { - org.spigotmc.AsyncCatcher.catchOp("player kick"); - final ServerGamePacketListenerImpl connection = this.getHandle().connection; - if (connection != null) { -- connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message); -+ connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message, cause); - } - } - -@@ -623,7 +628,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - // Paper start - Improve chat handling - if (ServerGamePacketListenerImpl.isChatMessageIllegal(msg)) { -- this.getHandle().connection.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); -+ this.getHandle().connection.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - kick event causes - } else { - if (msg.startsWith("/")) { - this.getHandle().connection.handleCommand(msg); -diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java -index 051b9e3a5d29a5840d596468e3ddd013bedc8da3..e3b262add194a126e731c68e68f3139a00cacacb 100644 ---- a/src/main/java/org/spigotmc/RestartCommand.java -+++ b/src/main/java/org/spigotmc/RestartCommand.java -@@ -73,7 +73,7 @@ public class RestartCommand extends Command - // Kick all players - for ( ServerPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) ) - { -- p.connection.disconnect(SpigotConfig.restartMessage); -+ p.connection.disconnect(SpigotConfig.restartMessage, org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used)) - } - // Give the socket a chance to send the packets - try diff --git a/patches/server/0573-Add-PufferFishStateChangeEvent.patch b/patches/server/0572-Add-PufferFishStateChangeEvent.patch similarity index 100% rename from patches/server/0573-Add-PufferFishStateChangeEvent.patch rename to patches/server/0572-Add-PufferFishStateChangeEvent.patch diff --git a/patches/server/0574-Fix-PlayerBucketEmptyEvent-result-itemstack.patch b/patches/server/0573-Fix-PlayerBucketEmptyEvent-result-itemstack.patch similarity index 100% rename from patches/server/0574-Fix-PlayerBucketEmptyEvent-result-itemstack.patch rename to patches/server/0573-Fix-PlayerBucketEmptyEvent-result-itemstack.patch diff --git a/patches/server/0574-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch b/patches/server/0574-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch new file mode 100644 index 000000000000..12b8b851c306 --- /dev/null +++ b/patches/server/0574-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch @@ -0,0 +1,91 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 29 May 2020 20:29:02 -0400 +Subject: [PATCH] Synchronize PalettedContainer instead of + ThreadingDetector/Semaphore + +Mojang has flaws in their logic about chunks being concurrently +wrote to. So we constantly see crashes around multiple threads writing. + +Additionally, java has optimized synchronization so well that its +in many times faster than trying to manage read write locks for low +contention situations. + +And this is extremely a low contention situation. + +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 b8fb6d1d85e07f5165bfaf7d80807e069b595851..dd62e257e16974a6d556a7f5e2d113a2cbc08981 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -32,14 +32,14 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + public final IdMap registry; + private volatile PalettedContainer.Data data; + private final PalettedContainer.Strategy strategy; +- private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); ++ // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused + + public void acquire() { +- this.threadingDetector.checkAndLock(); ++ // this.threadingDetector.checkAndLock(); // Paper - disable this - use proper synchronization + } + + public void release() { +- this.threadingDetector.checkAndUnlock(); ++ // this.threadingDetector.checkAndUnlock(); // Paper - disable this + } + + public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { +@@ -91,7 +91,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + + @Override +- public int onResize(int newBits, T object) { ++ public synchronized int onResize(int newBits, T object) { // Paper - synchronize + PalettedContainer.Data data = this.data; + PalettedContainer.Data data2 = this.createOrReuseData(data, newBits); + data2.copyFrom(data.palette, data.storage); +@@ -116,7 +116,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + return this.getAndSet(this.strategy.getIndex(x, y, z), value); + } + +- private T getAndSet(int index, T value) { ++ private synchronized T getAndSet(int index, T value) { // Paper - synchronize + int i = this.data.palette.idFor(value); + int j = this.data.storage.getAndSet(index, i); + return this.data.palette.valueFor(j); +@@ -133,7 +133,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + + } + +- private void set(int index, T value) { ++ private synchronized void set(int index, T value) { // Paper - synchronize + int i = this.data.palette.idFor(value); + this.data.storage.set(index, i); + } +@@ -158,7 +158,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + }); + } + +- public void read(FriendlyByteBuf buf) { ++ public synchronized void read(FriendlyByteBuf buf) { // Paper - synchronize + this.acquire(); + + try { +@@ -174,7 +174,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + + @Override +- public void write(FriendlyByteBuf buf) { ++ public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize + this.acquire(); + + try { +@@ -229,7 +229,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + + @Override +- public PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { ++ public synchronized PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { // Paper - synchronize + this.acquire(); + + PalettedContainerRO.PackedData var12; diff --git a/patches/server/0576-Add-option-to-fix-items-merging-through-walls.patch b/patches/server/0575-Add-option-to-fix-items-merging-through-walls.patch similarity index 100% rename from patches/server/0576-Add-option-to-fix-items-merging-through-walls.patch rename to patches/server/0575-Add-option-to-fix-items-merging-through-walls.patch diff --git a/patches/server/0575-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch b/patches/server/0575-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch deleted file mode 100644 index 4a44dd7ad087..000000000000 --- a/patches/server/0575-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch +++ /dev/null @@ -1,91 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 29 May 2020 20:29:02 -0400 -Subject: [PATCH] Synchronize PalettedContainer instead of - ThreadingDetector/Semaphore - -Mojang has flaws in their logic about chunks being concurrently -wrote to. So we constantly see crashes around multiple threads writing. - -Additionally, java has optimized synchronization so well that its -in many times faster than trying to manage read write locks for low -contention situations. - -And this is extremely a low contention situation. - -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 1219200cd915d6239a32a2bd09d325cd8fa9b346..dfae0918079425df92d958b04275be8ae60d4b60 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -32,14 +32,14 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - public final IdMap registry; - private volatile PalettedContainer.Data data; - private final PalettedContainer.Strategy strategy; -- private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); -+ // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused - - public void acquire() { -- this.threadingDetector.checkAndLock(); -+ // this.threadingDetector.checkAndLock(); // Paper - disable this - use proper synchronization - } - - public void release() { -- this.threadingDetector.checkAndUnlock(); -+ // this.threadingDetector.checkAndUnlock(); // Paper - disable this - } - - public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { -@@ -91,7 +91,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - - @Override -- public int onResize(int newBits, T object) { -+ public synchronized int onResize(int newBits, T object) { // Paper - synchronize - PalettedContainer.Data data = this.data; - PalettedContainer.Data data2 = this.createOrReuseData(data, newBits); - data2.copyFrom(data.palette, data.storage); -@@ -116,7 +116,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - return this.getAndSet(this.strategy.getIndex(x, y, z), value); - } - -- private T getAndSet(int index, T value) { -+ private synchronized T getAndSet(int index, T value) { // Paper - synchronize - int i = this.data.palette.idFor(value); - int j = this.data.storage.getAndSet(index, i); - return this.data.palette.valueFor(j); -@@ -133,7 +133,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - - } - -- private void set(int index, T value) { -+ private synchronized void set(int index, T value) { // Paper - synchronize - int i = this.data.palette.idFor(value); - this.data.storage.set(index, i); - } -@@ -158,7 +158,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - }); - } - -- public void read(FriendlyByteBuf buf) { -+ public synchronized void read(FriendlyByteBuf buf) { // Paper - synchronize - this.acquire(); - - try { -@@ -174,7 +174,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - - @Override -- public void write(FriendlyByteBuf buf) { -+ public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize - this.acquire(); - - try { -@@ -229,7 +229,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - - @Override -- public PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { -+ public synchronized PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { // Paper - synchronize - this.acquire(); - - PalettedContainerRO.PackedData var12; diff --git a/patches/server/0577-Add-BellRevealRaiderEvent.patch b/patches/server/0576-Add-BellRevealRaiderEvent.patch similarity index 100% rename from patches/server/0577-Add-BellRevealRaiderEvent.patch rename to patches/server/0576-Add-BellRevealRaiderEvent.patch diff --git a/patches/server/0578-Fix-invulnerable-end-crystals.patch b/patches/server/0577-Fix-invulnerable-end-crystals.patch similarity index 100% rename from patches/server/0578-Fix-invulnerable-end-crystals.patch rename to patches/server/0577-Fix-invulnerable-end-crystals.patch diff --git a/patches/server/0579-Add-ElderGuardianAppearanceEvent.patch b/patches/server/0578-Add-ElderGuardianAppearanceEvent.patch similarity index 100% rename from patches/server/0579-Add-ElderGuardianAppearanceEvent.patch rename to patches/server/0578-Add-ElderGuardianAppearanceEvent.patch diff --git a/patches/server/0579-Fix-dangerous-end-portal-logic.patch b/patches/server/0579-Fix-dangerous-end-portal-logic.patch new file mode 100644 index 000000000000..cde0c19f0c43 --- /dev/null +++ b/patches/server/0579-Fix-dangerous-end-portal-logic.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 4 Jun 2021 17:06:52 -0400 +Subject: [PATCH] Fix dangerous end portal logic + +End portals could teleport entities during move calls. Stupid +logic given the caller will never expect that kind of thing, +and will result in all kinds of dupes. + +Move the tick logic into the post tick, where portaling was +designed to happen in the first place. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7a0ad3e2600517d1472fd31d0f1b2e04c5e2d804..e4ba2914184072835e4447511b70a94f1ebc9eea 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -420,6 +420,36 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + return this.originWorld; + } + // Paper end - Entity origin API ++ // Paper start - make end portalling safe ++ public BlockPos portalBlock; ++ public ServerLevel portalWorld; ++ public void tickEndPortal() { ++ BlockPos pos = this.portalBlock; ++ ServerLevel world = this.portalWorld; ++ this.portalBlock = null; ++ this.portalWorld = null; ++ ++ if (pos == null || world == null || world != this.level) { ++ return; ++ } ++ ++ if (this.isPassenger() || this.isVehicle() || !this.canChangeDimensions() || this.isRemoved() || !this.valid || !this.isAlive()) { ++ return; ++ } ++ ++ ResourceKey resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends ++ ServerLevel worldserver = world.getServer().getLevel(resourcekey); ++ ++ org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(this.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); ++ event.callEvent(); ++ ++ if (this instanceof ServerPlayer) { ++ ((ServerPlayer) this).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); ++ return; ++ } ++ this.teleportTo(worldserver, null); ++ } ++ // Paper end - make end portalling safe + public float getBukkitYaw() { + return this.yRot; + } +@@ -2782,6 +2812,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + this.processPortalCooldown(); ++ this.tickEndPortal(); // Paper - make end portalling safe + } + } + +diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +index 9ee2fd0914ff7836517ca143d51db6150967cb0e..a0c1db8cfebaa0344012cc0af18d6231cdcdcbb8 100644 +--- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +@@ -61,16 +61,10 @@ public class EndPortalBlock extends BaseEntityBlock { + // return; // CraftBukkit - always fire event in case plugins wish to change it + } + +- // CraftBukkit start - Entity in portal +- EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); +- world.getCraftServer().getPluginManager().callEvent(event); +- +- if (entity instanceof ServerPlayer) { +- ((ServerPlayer) entity).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); +- return; +- } +- // CraftBukkit end +- entity.changeDimension(worldserver); ++ // Paper start - move all of this logic into portal tick ++ entity.portalWorld = ((ServerLevel)world); ++ entity.portalBlock = pos.immutable(); ++ // Paper end - move all of this logic into portal tick + } + + } diff --git a/patches/server/0580-Fix-dangerous-end-portal-logic.patch b/patches/server/0580-Fix-dangerous-end-portal-logic.patch deleted file mode 100644 index ed189860b8f8..000000000000 --- a/patches/server/0580-Fix-dangerous-end-portal-logic.patch +++ /dev/null @@ -1,86 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 4 Jun 2021 17:06:52 -0400 -Subject: [PATCH] Fix dangerous end portal logic - -End portals could teleport entities during move calls. Stupid -logic given the caller will never expect that kind of thing, -and will result in all kinds of dupes. - -Move the tick logic into the post tick, where portaling was -designed to happen in the first place. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 4fd7cd7b2dba4047f36a52c510f12d61f281a95f..50ebb94f4403bdf532af423d5204364d538667ee 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -420,6 +420,36 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - return this.originWorld; - } - // Paper end - Entity origin API -+ // Paper start - make end portalling safe -+ public BlockPos portalBlock; -+ public ServerLevel portalWorld; -+ public void tickEndPortal() { -+ BlockPos pos = this.portalBlock; -+ ServerLevel world = this.portalWorld; -+ this.portalBlock = null; -+ this.portalWorld = null; -+ -+ if (pos == null || world == null || world != this.level) { -+ return; -+ } -+ -+ if (this.isPassenger() || this.isVehicle() || !this.canChangeDimensions() || this.isRemoved() || !this.valid || !this.isAlive()) { -+ return; -+ } -+ -+ ResourceKey resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends -+ ServerLevel worldserver = world.getServer().getLevel(resourcekey); -+ -+ org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(this.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); -+ event.callEvent(); -+ -+ if (this instanceof ServerPlayer) { -+ ((ServerPlayer) this).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); -+ return; -+ } -+ this.teleportTo(worldserver, null); -+ } -+ // Paper end - make end portalling safe - public float getBukkitYaw() { - return this.yRot; - } -@@ -2778,6 +2808,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - this.processPortalCooldown(); -+ this.tickEndPortal(); // Paper - make end portalling safe - } - } - -diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -index 9ee2fd0914ff7836517ca143d51db6150967cb0e..a0c1db8cfebaa0344012cc0af18d6231cdcdcbb8 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -@@ -61,16 +61,10 @@ public class EndPortalBlock extends BaseEntityBlock { - // return; // CraftBukkit - always fire event in case plugins wish to change it - } - -- // CraftBukkit start - Entity in portal -- EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); -- world.getCraftServer().getPluginManager().callEvent(event); -- -- if (entity instanceof ServerPlayer) { -- ((ServerPlayer) entity).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); -- return; -- } -- // CraftBukkit end -- entity.changeDimension(worldserver); -+ // Paper start - move all of this logic into portal tick -+ entity.portalWorld = ((ServerLevel)world); -+ entity.portalBlock = pos.immutable(); -+ // Paper end - move all of this logic into portal tick - } - - } diff --git a/patches/server/0581-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch b/patches/server/0580-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch similarity index 100% rename from patches/server/0581-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch rename to patches/server/0580-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch diff --git a/patches/server/0581-Make-item-validations-configurable.patch b/patches/server/0581-Make-item-validations-configurable.patch new file mode 100644 index 000000000000..3bbdbee6ea31 --- /dev/null +++ b/patches/server/0581-Make-item-validations-configurable.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 4 Jun 2021 12:12:35 -0700 +Subject: [PATCH] Make item validations configurable + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +index e0d4798e244add64cbe43201604ad9d57701515f..c5d1ba7a1be3f102edcdfdc05fc50b30ef1f775b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +@@ -89,11 +89,11 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + super(tag); + + if (tag.contains(CraftMetaBook.BOOK_TITLE.NBT)) { +- this.title = limit( tag.getString(CraftMetaBook.BOOK_TITLE.NBT), 8192 ); // Spigot ++ this.title = limit( tag.getString(CraftMetaBook.BOOK_TITLE.NBT), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.book.title); // Spigot // Paper - make configurable + } + + if (tag.contains(CraftMetaBook.BOOK_AUTHOR.NBT)) { +- this.author = limit( tag.getString(CraftMetaBook.BOOK_AUTHOR.NBT), 8192 ); // Spigot ++ this.author = limit( tag.getString(CraftMetaBook.BOOK_AUTHOR.NBT), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.book.author ); // Spigot // Paper - make configurable + } + + if (tag.contains(CraftMetaBook.RESOLVED.NBT)) { +@@ -121,7 +121,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + } else { + page = this.validatePage(page); + } +- this.pages.add( limit( page, 16384 ) ); // Spigot ++ this.pages.add( limit( page, io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.book.page ) ); // Spigot // Paper - make configurable + } + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index a3713c5ab624b8d54ddcd69ae7587346ebbaed69..6c797f35a10e8491718f38eb08f31b1e6182a8d1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -362,7 +362,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + CompoundTag display = tag.getCompound(CraftMetaItem.DISPLAY.NBT); + + if (display.contains(CraftMetaItem.NAME.NBT)) { +- this.displayName = limit( display.getString(CraftMetaItem.NAME.NBT), 8192 ); // Spigot ++ this.displayName = limit( display.getString(CraftMetaItem.NAME.NBT), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.displayName ); // Spigot // Paper - make configurable + } + + if (display.contains(CraftMetaItem.LOCNAME.NBT)) { +@@ -373,7 +373,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + ListTag list = display.getList(CraftMetaItem.LORE.NBT, CraftMagicNumbers.NBT.TAG_STRING); + this.lore = new ArrayList(list.size()); + for (int index = 0; index < list.size(); index++) { +- String line = limit( list.getString(index), 8192 ); // Spigot ++ String line = limit( list.getString(index), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.loreLine ); // Spigot // Paper - make configurable + this.lore.add(line); + } + } diff --git a/patches/server/0582-Line-Of-Sight-Changes.patch b/patches/server/0582-Line-Of-Sight-Changes.patch new file mode 100644 index 000000000000..89f7b37eeb90 --- /dev/null +++ b/patches/server/0582-Line-Of-Sight-Changes.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TwoLeggedCat <80929284+TwoLeggedCat@users.noreply.github.com> +Date: Sat, 29 May 2021 14:33:25 -0500 +Subject: [PATCH] Line Of Sight Changes + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 89842b86b419117a92f79b7bfb57a4aa4351f9f8..78befbf1e5f506c9dfd703c3e796742fe17d13d7 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3619,7 +3619,8 @@ public abstract class LivingEntity extends Entity implements Attackable { + Vec3 vec3d = new Vec3(this.getX(), this.getEyeY(), this.getZ()); + Vec3 vec3d1 = new Vec3(entity.getX(), entity.getEyeY(), entity.getZ()); + +- return vec3d1.distanceTo(vec3d) > 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; ++ // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists ++ return vec3d1.distanceToSqr(vec3d) > 128.0D * 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; // Paper - Perf: Use distance squared + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index 9cd267f53505658d1c75187b662c4d9f68cd6bae..5f4958d28b6d79fe9e589e4794d9a7e8ab67c8b3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -510,5 +510,21 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + public org.bukkit.NamespacedKey getKey() { + return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location()); + } ++ ++ public boolean lineOfSightExists(Location from, Location to) { ++ Preconditions.checkArgument(from != null, "from parameter in lineOfSightExists cannot be null"); ++ Preconditions.checkArgument(to != null, "to parameter in lineOfSightExists cannot be null"); ++ if (from.getWorld() != to.getWorld()) { ++ return false; ++ } ++ ++ net.minecraft.world.phys.Vec3 start = new net.minecraft.world.phys.Vec3(from.getX(), from.getY(), from.getZ()); ++ net.minecraft.world.phys.Vec3 end = new net.minecraft.world.phys.Vec3(to.getX(), to.getY(), to.getZ()); ++ if (end.distanceToSqr(start) > 128D * 128D) { ++ return false; // Return early if the distance is greater than 128 blocks ++ } ++ ++ return this.getHandle().clip(new net.minecraft.world.level.ClipContext(start, end, net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, net.minecraft.world.phys.shapes.CollisionContext.empty())).getType() == net.minecraft.world.phys.HitResult.Type.MISS; ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 40fbd911943abd6f6cc7910b5179c196bb3fe8f8..cc3de2a961f474afee982cb94813bc48649d352a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -612,6 +612,23 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return this.getHandle().hasLineOfSight(((CraftEntity) other).getHandle()); + } + ++ // Paper start ++ @Override ++ public boolean hasLineOfSight(Location loc) { ++ if (this.getHandle().level() != ((CraftWorld) loc.getWorld()).getHandle()) { ++ return false; ++ } ++ ++ net.minecraft.world.phys.Vec3 start = new net.minecraft.world.phys.Vec3(this.getHandle().getX(), this.getHandle().getEyeY(), this.getHandle().getZ()); ++ net.minecraft.world.phys.Vec3 end = new net.minecraft.world.phys.Vec3(loc.getX(), loc.getY(), loc.getZ()); ++ if (end.distanceToSqr(start) > 128D * 128D) { ++ return false; // Return early if the distance is greater than 128 blocks ++ } ++ ++ return this.getHandle().level().clipDirect(start, end, net.minecraft.world.phys.shapes.CollisionContext.of(this.getHandle())) == net.minecraft.world.phys.HitResult.Type.MISS; ++ } ++ // Paper end ++ + @Override + public boolean getRemoveWhenFarAway() { + return this.getHandle() instanceof Mob && !((Mob) this.getHandle()).isPersistenceRequired(); diff --git a/patches/server/0582-Make-item-validations-configurable.patch b/patches/server/0582-Make-item-validations-configurable.patch deleted file mode 100644 index 111d7005070e..000000000000 --- a/patches/server/0582-Make-item-validations-configurable.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 4 Jun 2021 12:12:35 -0700 -Subject: [PATCH] Make item validations configurable - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java -index 5e01357208fe52c1d270c68cb19029ea0f4057bb..6d85237b21650edf1d2dc71abaf0edbe7a8aef6b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java -@@ -88,11 +88,11 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { - super(tag); - - if (tag.contains(CraftMetaBook.BOOK_TITLE.NBT)) { -- this.title = limit( tag.getString(CraftMetaBook.BOOK_TITLE.NBT), 8192 ); // Spigot -+ this.title = limit( tag.getString(CraftMetaBook.BOOK_TITLE.NBT), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.book.title); // Spigot // Paper - make configurable - } - - if (tag.contains(CraftMetaBook.BOOK_AUTHOR.NBT)) { -- this.author = limit( tag.getString(CraftMetaBook.BOOK_AUTHOR.NBT), 8192 ); // Spigot -+ this.author = limit( tag.getString(CraftMetaBook.BOOK_AUTHOR.NBT), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.book.author ); // Spigot // Paper - make configurable - } - - if (tag.contains(CraftMetaBook.RESOLVED.NBT)) { -@@ -120,7 +120,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { - } else { - page = this.validatePage(page); - } -- this.pages.add( limit( page, 16384 ) ); // Spigot -+ this.pages.add( limit( page, io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.book.page ) ); // Spigot // Paper - make configurable - } - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index f9f57f4ab75776dbaa4dc39d30e32b2c778b1955..d42f4640a53221ffa7a479bce681374527cb3a0c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -362,7 +362,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - CompoundTag display = tag.getCompound(CraftMetaItem.DISPLAY.NBT); - - if (display.contains(CraftMetaItem.NAME.NBT)) { -- this.displayName = limit( display.getString(CraftMetaItem.NAME.NBT), 8192 ); // Spigot -+ this.displayName = limit( display.getString(CraftMetaItem.NAME.NBT), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.displayName ); // Spigot // Paper - make configurable - } - - if (display.contains(CraftMetaItem.LOCNAME.NBT)) { -@@ -373,7 +373,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - ListTag list = display.getList(CraftMetaItem.LORE.NBT, CraftMagicNumbers.NBT.TAG_STRING); - this.lore = new ArrayList(list.size()); - for (int index = 0; index < list.size(); index++) { -- String line = limit( list.getString(index), 8192 ); // Spigot -+ String line = limit( list.getString(index), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.loreLine ); // Spigot // Paper - make configurable - this.lore.add(line); - } - } diff --git a/patches/server/0583-Line-Of-Sight-Changes.patch b/patches/server/0583-Line-Of-Sight-Changes.patch deleted file mode 100644 index dff2ccc4dde2..000000000000 --- a/patches/server/0583-Line-Of-Sight-Changes.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TwoLeggedCat <80929284+TwoLeggedCat@users.noreply.github.com> -Date: Sat, 29 May 2021 14:33:25 -0500 -Subject: [PATCH] Line Of Sight Changes - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 297fb36316df04903bd083af523c6b35c284ac7b..cedd1f8063504cc58b2735a8d53a3c39a605bf5f 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3606,7 +3606,8 @@ public abstract class LivingEntity extends Entity implements Attackable { - Vec3 vec3d = new Vec3(this.getX(), this.getEyeY(), this.getZ()); - Vec3 vec3d1 = new Vec3(entity.getX(), entity.getEyeY(), entity.getZ()); - -- return vec3d1.distanceTo(vec3d) > 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; -+ // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists -+ return vec3d1.distanceToSqr(vec3d) > 128.0D * 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; // Paper - Perf: Use distance squared - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index 9cd267f53505658d1c75187b662c4d9f68cd6bae..5f4958d28b6d79fe9e589e4794d9a7e8ab67c8b3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -510,5 +510,21 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - public org.bukkit.NamespacedKey getKey() { - return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location()); - } -+ -+ public boolean lineOfSightExists(Location from, Location to) { -+ Preconditions.checkArgument(from != null, "from parameter in lineOfSightExists cannot be null"); -+ Preconditions.checkArgument(to != null, "to parameter in lineOfSightExists cannot be null"); -+ if (from.getWorld() != to.getWorld()) { -+ return false; -+ } -+ -+ net.minecraft.world.phys.Vec3 start = new net.minecraft.world.phys.Vec3(from.getX(), from.getY(), from.getZ()); -+ net.minecraft.world.phys.Vec3 end = new net.minecraft.world.phys.Vec3(to.getX(), to.getY(), to.getZ()); -+ if (end.distanceToSqr(start) > 128D * 128D) { -+ return false; // Return early if the distance is greater than 128 blocks -+ } -+ -+ return this.getHandle().clip(new net.minecraft.world.level.ClipContext(start, end, net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, net.minecraft.world.phys.shapes.CollisionContext.empty())).getType() == net.minecraft.world.phys.HitResult.Type.MISS; -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 1474177f66a1428406bec2c55cd995a172f27dcf..55bb8e5e8e09e35320094389bf68d204d21e4f9e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -599,6 +599,23 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return this.getHandle().hasLineOfSight(((CraftEntity) other).getHandle()); - } - -+ // Paper start -+ @Override -+ public boolean hasLineOfSight(Location loc) { -+ if (this.getHandle().level() != ((CraftWorld) loc.getWorld()).getHandle()) { -+ return false; -+ } -+ -+ net.minecraft.world.phys.Vec3 start = new net.minecraft.world.phys.Vec3(this.getHandle().getX(), this.getHandle().getEyeY(), this.getHandle().getZ()); -+ net.minecraft.world.phys.Vec3 end = new net.minecraft.world.phys.Vec3(loc.getX(), loc.getY(), loc.getZ()); -+ if (end.distanceToSqr(start) > 128D * 128D) { -+ return false; // Return early if the distance is greater than 128 blocks -+ } -+ -+ return this.getHandle().level().clipDirect(start, end, net.minecraft.world.phys.shapes.CollisionContext.of(this.getHandle())) == net.minecraft.world.phys.HitResult.Type.MISS; -+ } -+ // Paper end -+ - @Override - public boolean getRemoveWhenFarAway() { - return this.getHandle() instanceof Mob && !((Mob) this.getHandle()).isPersistenceRequired(); diff --git a/patches/server/0583-add-per-world-spawn-limits.patch b/patches/server/0583-add-per-world-spawn-limits.patch new file mode 100644 index 000000000000..5433a49ee83e --- /dev/null +++ b/patches/server/0583-add-per-world-spawn-limits.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chase +Date: Wed, 2 Dec 2020 22:43:39 -0800 +Subject: [PATCH] add per world spawn limits + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 05b9e7011d1c127052b73a464fc86331e2a4774a..fce62b18da2d6ee8d10688107ca3179abfa3781b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -221,6 +221,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { + this.biomeProvider = biomeProvider; + + this.environment = env; ++ // Paper start - per world spawn limits ++ for (SpawnCategory spawnCategory : SpawnCategory.values()) { ++ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { ++ setSpawnLimit(spawnCategory, this.world.paperConfig().entities.spawning.spawnLimits.getInt(CraftSpawnCategory.toNMS(spawnCategory))); ++ } ++ } ++ // Paper end - per world spawn limits + } + + @Override diff --git a/patches/server/0584-Fix-potions-splash-events.patch b/patches/server/0584-Fix-potions-splash-events.patch new file mode 100644 index 000000000000..c3914e23c43d --- /dev/null +++ b/patches/server/0584-Fix-potions-splash-events.patch @@ -0,0 +1,192 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 20 May 2021 20:40:53 -0700 +Subject: [PATCH] Fix potions splash events + +Fix PotionSplashEvent for water splash potions +Fixes SPIGOT-6221: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-6221 +Fix splash events cancellation that still show particles/sound + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +index b87077c47a0131c5f4ca085b6b32e657043a9e1a..77235314f4ccc28255b98f2bb52f553fe93313f3 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +@@ -105,56 +105,77 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + Potion potionregistry = PotionUtils.getPotion(itemstack); + List list = PotionUtils.getMobEffects(itemstack); + boolean flag = potionregistry == Potions.WATER && list.isEmpty(); ++ boolean showParticles = true; // Paper - Fix potions splash events + + if (flag) { +- this.applyWater(); ++ showParticles = this.applyWater(hitResult); // Paper - Fix potions splash events + } else if (true || !list.isEmpty()) { // CraftBukkit - Call event even if no effects to apply + if (this.isLingering()) { +- this.makeAreaOfEffectCloud(itemstack, potionregistry, hitResult); // CraftBukkit - Pass MovingObjectPosition ++ showParticles = this.makeAreaOfEffectCloud(itemstack, potionregistry, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper + } else { +- this.applySplash(list, hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition ++ showParticles = this.applySplash(list, hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper + } + } + ++ if (showParticles) { // Paper - Fix potions splash events + int i = potionregistry.hasInstantEffects() ? 2007 : 2002; + + this.level().levelEvent(i, this.blockPosition(), PotionUtils.getColor(itemstack)); ++ } // Paper - Fix potions splash events + this.discard(); + } + } + +- private void applyWater() { ++ private static final Predicate APPLY_WATER_GET_ENTITIES_PREDICATE = ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE.or(Axolotl.class::isInstance); // Paper - Fix potions splash events ++ private boolean applyWater(@Nullable HitResult hitResult) { // Paper - Fix potions splash events + AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); +- List list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE); ++ // Paper start - Fix potions splash events ++ List list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.APPLY_WATER_GET_ENTITIES_PREDICATE); ++ Map affected = new HashMap<>(); ++ java.util.Set rehydrate = new java.util.HashSet<>(); ++ java.util.Set extinguish = new java.util.HashSet<>(); + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { + net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator.next(); ++ if (entityliving instanceof Axolotl axolotl) { ++ rehydrate.add(((org.bukkit.entity.Axolotl) axolotl.getBukkitEntity())); ++ } + double d0 = this.distanceToSqr((Entity) entityliving); + + if (d0 < 16.0D) { + if (entityliving.isSensitiveToWater()) { +- entityliving.hurt(this.damageSources().indirectMagic(this, this.getOwner()), 1.0F); ++ affected.put(entityliving.getBukkitLivingEntity(), 1.0); + } + + if (entityliving.isOnFire() && entityliving.isAlive()) { +- entityliving.extinguishFire(); ++ extinguish.add(entityliving.getBukkitLivingEntity()); + } + } + } + +- List list1 = this.level().getEntitiesOfClass(Axolotl.class, axisalignedbb); +- Iterator iterator1 = list1.iterator(); +- +- while (iterator1.hasNext()) { +- Axolotl axolotl = (Axolotl) iterator1.next(); +- +- axolotl.rehydrate(); ++ io.papermc.paper.event.entity.WaterBottleSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callWaterBottleSplashEvent( ++ this, hitResult, affected, rehydrate, extinguish ++ ); ++ if (!event.isCancelled()) { ++ for (LivingEntity affectedEntity : event.getToDamage()) { ++ ((CraftLivingEntity) affectedEntity).getHandle().hurt(this.damageSources().indirectMagic(this, this.getOwner()), 1.0F); ++ } ++ for (LivingEntity toExtinguish : event.getToExtinguish()) { ++ ((CraftLivingEntity) toExtinguish).getHandle().extinguishFire(); ++ } ++ for (LivingEntity toRehydrate : event.getToRehydrate()) { ++ if (((CraftLivingEntity) toRehydrate).getHandle() instanceof Axolotl axolotl) { ++ axolotl.rehydrate(); ++ } ++ } ++ // Paper end - Fix potions splash events + } ++ return !event.isCancelled(); // Paper - Fix potions splash events + + } + +- private void applySplash(List list, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition ++ private boolean applySplash(List list, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events + AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); + List list1 = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb); + Map affected = new HashMap(); // CraftBukkit +@@ -172,6 +193,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + if (d0 < 16.0D) { + double d1; + ++ // Paper - diff on change, used when calling the splash event for water splash potions + if (entityliving == entity) { + d1 = 1.0D; + } else { +@@ -226,10 +248,11 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + } + } + } ++ return !event.isCancelled(); // Paper - Fix potions splash events + + } + +- private void makeAreaOfEffectCloud(ItemStack itemstack, Potion potionregistry, HitResult position) { // CraftBukkit - Pass MovingObjectPosition ++ private boolean makeAreaOfEffectCloud(ItemStack itemstack, Potion potionregistry, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - return boolean + AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ()); + Entity entity = this.getOwner(); + +@@ -244,10 +267,12 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + entityareaeffectcloud.setPotion(potionregistry); + Iterator iterator = PotionUtils.getCustomEffects(itemstack).iterator(); + ++ boolean noEffects = potionregistry.getEffects().isEmpty(); // Paper - Fix potions splash events + while (iterator.hasNext()) { + MobEffectInstance mobeffect = (MobEffectInstance) iterator.next(); + + entityareaeffectcloud.addEffect(new MobEffectInstance(mobeffect)); ++ noEffects = false; // Paper - Fix potions splash events + } + + CompoundTag nbttagcompound = itemstack.getTag(); +@@ -258,12 +283,13 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + + // CraftBukkit start + org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, position, entityareaeffectcloud); +- if (!(event.isCancelled() || entityareaeffectcloud.isRemoved())) { ++ if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (noEffects && entityareaeffectcloud.effects.isEmpty() && entityareaeffectcloud.getPotion().getEffects().isEmpty()))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling + this.level().addFreshEntity(entityareaeffectcloud); + } else { + entityareaeffectcloud.discard(); + } + // CraftBukkit end ++ return !event.isCancelled(); // Paper - Fix potions splash events + } + + public boolean isLingering() { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 5112da69c528be09c2b5d5bcac70fce0fb0054a1..e57bafa1d071a2fefe44a150bc5754e76d78cdd9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -874,6 +874,32 @@ public class CraftEventFactory { + return event; + } + ++ // Paper start - Fix potions splash events ++ public static io.papermc.paper.event.entity.WaterBottleSplashEvent callWaterBottleSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult hitResult, Map affectedEntities, java.util.Set rehydrate, java.util.Set extinguish) { ++ ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); ++ ++ Block hitBlock = null; ++ BlockFace hitFace = null; ++ org.bukkit.entity.Entity hitEntity = null; ++ ++ if (hitResult != null) { ++ if (hitResult.getType() == HitResult.Type.BLOCK) { ++ BlockHitResult blockHitResult = (BlockHitResult) hitResult; ++ hitBlock = CraftBlock.at(potion.level(), blockHitResult.getBlockPos()); ++ hitFace = CraftBlock.notchToBlockFace(blockHitResult.getDirection()); ++ } else if (hitResult.getType() == HitResult.Type.ENTITY) { ++ hitEntity = ((EntityHitResult) hitResult).getEntity().getBukkitEntity(); ++ } ++ } ++ ++ io.papermc.paper.event.entity.WaterBottleSplashEvent event = new io.papermc.paper.event.entity.WaterBottleSplashEvent( ++ thrownPotion, hitEntity, hitBlock, hitFace, affectedEntities, rehydrate, extinguish ++ ); ++ event.callEvent(); ++ return event; ++ } ++ // Paper end - Fix potions splash events ++ + /** + * BlockFadeEvent + */ diff --git a/patches/server/0584-add-per-world-spawn-limits.patch b/patches/server/0584-add-per-world-spawn-limits.patch deleted file mode 100644 index b5e0294a7cb9..000000000000 --- a/patches/server/0584-add-per-world-spawn-limits.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: chase -Date: Wed, 2 Dec 2020 22:43:39 -0800 -Subject: [PATCH] add per world spawn limits - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index c70599006c16ea342ad1b50915cda13673431e79..eb915b7e07666ef1cad55dc32882ecc962ab2ae3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -215,6 +215,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { - this.biomeProvider = biomeProvider; - - this.environment = env; -+ // Paper start - per world spawn limits -+ for (SpawnCategory spawnCategory : SpawnCategory.values()) { -+ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { -+ setSpawnLimit(spawnCategory, this.world.paperConfig().entities.spawning.spawnLimits.getInt(CraftSpawnCategory.toNMS(spawnCategory))); -+ } -+ } -+ // Paper end - per world spawn limits - } - - @Override diff --git a/patches/server/0586-Add-more-LimitedRegion-API.patch b/patches/server/0585-Add-more-LimitedRegion-API.patch similarity index 100% rename from patches/server/0586-Add-more-LimitedRegion-API.patch rename to patches/server/0585-Add-more-LimitedRegion-API.patch diff --git a/patches/server/0585-Fix-potions-splash-events.patch b/patches/server/0585-Fix-potions-splash-events.patch deleted file mode 100644 index fbea3d41b320..000000000000 --- a/patches/server/0585-Fix-potions-splash-events.patch +++ /dev/null @@ -1,192 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 20 May 2021 20:40:53 -0700 -Subject: [PATCH] Fix potions splash events - -Fix PotionSplashEvent for water splash potions -Fixes SPIGOT-6221: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-6221 -Fix splash events cancellation that still show particles/sound - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -index b87077c47a0131c5f4ca085b6b32e657043a9e1a..77235314f4ccc28255b98f2bb52f553fe93313f3 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -@@ -105,56 +105,77 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - Potion potionregistry = PotionUtils.getPotion(itemstack); - List list = PotionUtils.getMobEffects(itemstack); - boolean flag = potionregistry == Potions.WATER && list.isEmpty(); -+ boolean showParticles = true; // Paper - Fix potions splash events - - if (flag) { -- this.applyWater(); -+ showParticles = this.applyWater(hitResult); // Paper - Fix potions splash events - } else if (true || !list.isEmpty()) { // CraftBukkit - Call event even if no effects to apply - if (this.isLingering()) { -- this.makeAreaOfEffectCloud(itemstack, potionregistry, hitResult); // CraftBukkit - Pass MovingObjectPosition -+ showParticles = this.makeAreaOfEffectCloud(itemstack, potionregistry, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper - } else { -- this.applySplash(list, hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition -+ showParticles = this.applySplash(list, hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper - } - } - -+ if (showParticles) { // Paper - Fix potions splash events - int i = potionregistry.hasInstantEffects() ? 2007 : 2002; - - this.level().levelEvent(i, this.blockPosition(), PotionUtils.getColor(itemstack)); -+ } // Paper - Fix potions splash events - this.discard(); - } - } - -- private void applyWater() { -+ private static final Predicate APPLY_WATER_GET_ENTITIES_PREDICATE = ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE.or(Axolotl.class::isInstance); // Paper - Fix potions splash events -+ private boolean applyWater(@Nullable HitResult hitResult) { // Paper - Fix potions splash events - AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); -- List list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE); -+ // Paper start - Fix potions splash events -+ List list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.APPLY_WATER_GET_ENTITIES_PREDICATE); -+ Map affected = new HashMap<>(); -+ java.util.Set rehydrate = new java.util.HashSet<>(); -+ java.util.Set extinguish = new java.util.HashSet<>(); - Iterator iterator = list.iterator(); - - while (iterator.hasNext()) { - net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator.next(); -+ if (entityliving instanceof Axolotl axolotl) { -+ rehydrate.add(((org.bukkit.entity.Axolotl) axolotl.getBukkitEntity())); -+ } - double d0 = this.distanceToSqr((Entity) entityliving); - - if (d0 < 16.0D) { - if (entityliving.isSensitiveToWater()) { -- entityliving.hurt(this.damageSources().indirectMagic(this, this.getOwner()), 1.0F); -+ affected.put(entityliving.getBukkitLivingEntity(), 1.0); - } - - if (entityliving.isOnFire() && entityliving.isAlive()) { -- entityliving.extinguishFire(); -+ extinguish.add(entityliving.getBukkitLivingEntity()); - } - } - } - -- List list1 = this.level().getEntitiesOfClass(Axolotl.class, axisalignedbb); -- Iterator iterator1 = list1.iterator(); -- -- while (iterator1.hasNext()) { -- Axolotl axolotl = (Axolotl) iterator1.next(); -- -- axolotl.rehydrate(); -+ io.papermc.paper.event.entity.WaterBottleSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callWaterBottleSplashEvent( -+ this, hitResult, affected, rehydrate, extinguish -+ ); -+ if (!event.isCancelled()) { -+ for (LivingEntity affectedEntity : event.getToDamage()) { -+ ((CraftLivingEntity) affectedEntity).getHandle().hurt(this.damageSources().indirectMagic(this, this.getOwner()), 1.0F); -+ } -+ for (LivingEntity toExtinguish : event.getToExtinguish()) { -+ ((CraftLivingEntity) toExtinguish).getHandle().extinguishFire(); -+ } -+ for (LivingEntity toRehydrate : event.getToRehydrate()) { -+ if (((CraftLivingEntity) toRehydrate).getHandle() instanceof Axolotl axolotl) { -+ axolotl.rehydrate(); -+ } -+ } -+ // Paper end - Fix potions splash events - } -+ return !event.isCancelled(); // Paper - Fix potions splash events - - } - -- private void applySplash(List list, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition -+ private boolean applySplash(List list, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events - AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); - List list1 = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb); - Map affected = new HashMap(); // CraftBukkit -@@ -172,6 +193,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - if (d0 < 16.0D) { - double d1; - -+ // Paper - diff on change, used when calling the splash event for water splash potions - if (entityliving == entity) { - d1 = 1.0D; - } else { -@@ -226,10 +248,11 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - } - } - } -+ return !event.isCancelled(); // Paper - Fix potions splash events - - } - -- private void makeAreaOfEffectCloud(ItemStack itemstack, Potion potionregistry, HitResult position) { // CraftBukkit - Pass MovingObjectPosition -+ private boolean makeAreaOfEffectCloud(ItemStack itemstack, Potion potionregistry, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - return boolean - AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ()); - Entity entity = this.getOwner(); - -@@ -244,10 +267,12 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - entityareaeffectcloud.setPotion(potionregistry); - Iterator iterator = PotionUtils.getCustomEffects(itemstack).iterator(); - -+ boolean noEffects = potionregistry.getEffects().isEmpty(); // Paper - Fix potions splash events - while (iterator.hasNext()) { - MobEffectInstance mobeffect = (MobEffectInstance) iterator.next(); - - entityareaeffectcloud.addEffect(new MobEffectInstance(mobeffect)); -+ noEffects = false; // Paper - Fix potions splash events - } - - CompoundTag nbttagcompound = itemstack.getTag(); -@@ -258,12 +283,13 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - - // CraftBukkit start - org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, position, entityareaeffectcloud); -- if (!(event.isCancelled() || entityareaeffectcloud.isRemoved())) { -+ if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (noEffects && entityareaeffectcloud.effects.isEmpty() && entityareaeffectcloud.getPotion().getEffects().isEmpty()))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling - this.level().addFreshEntity(entityareaeffectcloud); - } else { - entityareaeffectcloud.discard(); - } - // CraftBukkit end -+ return !event.isCancelled(); // Paper - Fix potions splash events - } - - public boolean isLingering() { -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 64d27612d21d44950ba12be69aa6bfa339fef39c..833011c1f1746e000adc72ab092295fd4fab2ab8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -874,6 +874,32 @@ public class CraftEventFactory { - return event; - } - -+ // Paper start - Fix potions splash events -+ public static io.papermc.paper.event.entity.WaterBottleSplashEvent callWaterBottleSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult hitResult, Map affectedEntities, java.util.Set rehydrate, java.util.Set extinguish) { -+ ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); -+ -+ Block hitBlock = null; -+ BlockFace hitFace = null; -+ org.bukkit.entity.Entity hitEntity = null; -+ -+ if (hitResult != null) { -+ if (hitResult.getType() == HitResult.Type.BLOCK) { -+ BlockHitResult blockHitResult = (BlockHitResult) hitResult; -+ hitBlock = CraftBlock.at(potion.level(), blockHitResult.getBlockPos()); -+ hitFace = CraftBlock.notchToBlockFace(blockHitResult.getDirection()); -+ } else if (hitResult.getType() == HitResult.Type.ENTITY) { -+ hitEntity = ((EntityHitResult) hitResult).getEntity().getBukkitEntity(); -+ } -+ } -+ -+ io.papermc.paper.event.entity.WaterBottleSplashEvent event = new io.papermc.paper.event.entity.WaterBottleSplashEvent( -+ thrownPotion, hitEntity, hitBlock, hitFace, affectedEntities, rehydrate, extinguish -+ ); -+ event.callEvent(); -+ return event; -+ } -+ // Paper end - Fix potions splash events -+ - /** - * BlockFadeEvent - */ diff --git a/patches/server/0586-Fix-PlayerDropItemEvent-using-wrong-item.patch b/patches/server/0586-Fix-PlayerDropItemEvent-using-wrong-item.patch new file mode 100644 index 000000000000..612146273a4a --- /dev/null +++ b/patches/server/0586-Fix-PlayerDropItemEvent-using-wrong-item.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 20 Jun 2021 21:55:59 -0700 +Subject: [PATCH] Fix PlayerDropItemEvent using wrong item + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index a09ef51e94a0bf9f51bf358e7cf77dd5d272aab2..d0369b9db86dc3436e6a016f138f2ffe91da6ed4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2342,7 +2342,7 @@ public class ServerPlayer extends Player { + + if (retainOwnership) { + if (!itemstack1.isEmpty()) { +- this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), stack.getCount()); ++ this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), itemstack1.getCount()); // Paper - Fix PlayerDropItemEvent using wrong item + } + + this.awardStat(Stats.DROP); +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 e765b6f1163edb363ddebe0c83ca733a061ff103..bc205c48460f6b90fbdc83f979f7bf029c1e0f9d 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -726,6 +726,11 @@ public abstract class Player extends LivingEntity { + } + + double d0 = this.getEyeY() - 0.30000001192092896D; ++ // Paper start ++ ItemStack tmp = itemstack.copy(); ++ itemstack.setCount(0); ++ itemstack = tmp; ++ // Paper end + ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), d0, this.getZ(), itemstack); + + entityitem.setPickUpDelay(40); diff --git a/patches/server/0587-Fix-PlayerDropItemEvent-using-wrong-item.patch b/patches/server/0587-Fix-PlayerDropItemEvent-using-wrong-item.patch deleted file mode 100644 index cc481ca1f432..000000000000 --- a/patches/server/0587-Fix-PlayerDropItemEvent-using-wrong-item.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 20 Jun 2021 21:55:59 -0700 -Subject: [PATCH] Fix PlayerDropItemEvent using wrong item - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 895c2cd385622fcc426e9e920ff35109f444b569..12d3809792384643e550b34e59c58d49869ec05d 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2342,7 +2342,7 @@ public class ServerPlayer extends Player { - - if (retainOwnership) { - if (!itemstack1.isEmpty()) { -- this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), stack.getCount()); -+ this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), itemstack1.getCount()); // Paper - Fix PlayerDropItemEvent using wrong item - } - - this.awardStat(Stats.DROP); -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 f47874dc9270d177aa7c39266e36713d0c934640..00477c81dc3f5d8289b08881b119b699552e5722 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -725,6 +725,11 @@ public abstract class Player extends LivingEntity { - } - - double d0 = this.getEyeY() - 0.30000001192092896D; -+ // Paper start -+ ItemStack tmp = itemstack.copy(); -+ itemstack.setCount(0); -+ itemstack = tmp; -+ // Paper end - ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), d0, this.getZ(), itemstack); - - entityitem.setPickUpDelay(40); diff --git a/patches/server/0587-Missing-Entity-API.patch b/patches/server/0587-Missing-Entity-API.patch new file mode 100644 index 000000000000..7dacfb50bad4 --- /dev/null +++ b/patches/server/0587-Missing-Entity-API.patch @@ -0,0 +1,1340 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 21 Jun 2021 23:56:07 -0400 +Subject: [PATCH] Missing Entity API + +== AT == +public net.minecraft.world.entity.animal.Fox isDefending()Z +public net.minecraft.world.entity.animal.Fox setDefending(Z)V +public net.minecraft.world.entity.animal.Fox setFaceplanted(Z)V +public net.minecraft.world.entity.animal.Panda getEatCounter()I +public net.minecraft.world.entity.animal.Panda setEatCounter(I)V +public net.minecraft.world.entity.animal.Bee isRolling()Z +public net.minecraft.world.entity.animal.Bee setRolling(Z)V +public net.minecraft.world.entity.animal.Bee numCropsGrownSincePollination +public net.minecraft.world.entity.animal.Bee ticksWithoutNectarSinceExitingHive +public net.minecraft.world.entity.monster.piglin.Piglin isChargingCrossbow()Z +public net.minecraft.world.entity.ambient.Bat targetPosition +public net.minecraft.world.entity.monster.Ravager attackTick +public net.minecraft.world.entity.monster.Ravager stunnedTick +public net.minecraft.world.entity.monster.Ravager roarTick +public net.minecraft.world.entity.vehicle.MinecartTNT explode(D)V +public net.minecraft.world.entity.vehicle.MinecartTNT fuse +public net.minecraft.world.entity.monster.Endermite life +public net.minecraft.world.entity.projectile.AbstractArrow soundEvent +public net.minecraft.world.entity.monster.Phantom anchorPoint +public net.minecraft.world.entity.npc.WanderingTrader getWanderTarget()Lnet/minecraft/core/BlockPos; +public net.minecraft.world.entity.animal.AbstractSchoolingFish leader +public net.minecraft.world.entity.animal.AbstractSchoolingFish schoolSize +public net.minecraft.world.entity.animal.Rabbit moreCarrotTicks +public net.minecraft.world.entity.AreaEffectCloud ownerUUID +public net.minecraft.world.entity.animal.MushroomCow stewEffects + +Co-authored-by: Nassim Jahnke +Co-authored-by: Jake Potrebic +Co-authored-by: William Blake Galbreath +Co-authored-by: SoSeDiK +Co-authored-by: booky10 +Co-authored-by: Amin + +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +index 8117578ced94aa6bf01871f6526a388385c4adf2..59699c59fdfc611177fdb3136f84ab539b17d9c9 100644 +--- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +@@ -165,7 +165,7 @@ public class MobGoalHelper { + bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class); + bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class); + bukkitMap.put(AbstractFish.class, Fish.class); +- bukkitMap.put(AbstractSchoolingFish.class, Fish.class); // close enough ++ bukkitMap.put(AbstractSchoolingFish.class, io.papermc.paper.entity.SchoolableFish.class); + bukkitMap.put(FlyingMob.class, Flying.class); + bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class); + bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class); +diff --git a/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java b/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java +new file mode 100644 +index 0000000000000000000000000000000000000000..41bf71d116ffc5431586ce54abba7f8def6c1dcf +--- /dev/null ++++ b/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java +@@ -0,0 +1,52 @@ ++package io.papermc.paper.entity; ++ ++import net.minecraft.world.entity.animal.AbstractSchoolingFish; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.entity.CraftFish; ++import org.jetbrains.annotations.NotNull; ++ ++public class PaperSchoolableFish extends CraftFish implements SchoolableFish { ++ ++ public PaperSchoolableFish(CraftServer server, AbstractSchoolingFish entity) { ++ super(server, entity); ++ } ++ ++ @Override ++ public AbstractSchoolingFish getHandle() { ++ return (AbstractSchoolingFish) super.getHandle(); ++ } ++ ++ @Override ++ public void startFollowing(@NotNull SchoolableFish fish) { ++ if (this.getHandle().isFollower()) { // If following a fish already, properly remove the old one ++ this.stopFollowing(); ++ } ++ ++ this.getHandle().startFollowing(((PaperSchoolableFish) fish).getHandle()); ++ } ++ ++ @Override ++ public void stopFollowing() { ++ this.getHandle().stopFollowing(); ++ } ++ ++ @Override ++ public int getSchoolSize() { ++ return this.getHandle().schoolSize; ++ } ++ ++ @Override ++ public int getMaxSchoolSize() { ++ return this.getHandle().getMaxSchoolSize(); ++ } ++ ++ @Override ++ public SchoolableFish getSchoolLeader() { ++ AbstractSchoolingFish leader = this.getHandle().leader; ++ if (leader == null) { ++ return null; ++ } ++ ++ return (SchoolableFish) leader.getBukkitEntity(); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java b/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java +index 39ed3ca76d6b64ef3917280ec822721cc02afada..86b437836cb4b1f6e8ca9acd5f1f93b925cf9e51 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java ++++ b/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java +@@ -52,6 +52,7 @@ public abstract class AbstractSchoolingFish extends AbstractFish { + } + + public void stopFollowing() { ++ if (this.leader == null) return; // Avoid NPE, plugins can now set the leader and certain fish goals might cause this method to be called + this.leader.removeFollower(); + this.leader = null; + } +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 07ecc038a1000581335b8d18c094298f2f3b100f..91ea960ba223bae42655c581b9b6c0981f333c9b 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -553,11 +553,13 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + this.setFlag(4, hasStung); + } + ++ public net.kyori.adventure.util.TriState rollingOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Rolling override + public boolean isRolling() { + return this.getFlag(2); + } + + public void setRolling(boolean nearTarget) { ++ nearTarget = rollingOverride.toBooleanOrElse(nearTarget); // Paper - Rolling override + this.setFlag(2, nearTarget); + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java +index 71a08510a928d4570822282bb31f14013ec3834a..4aeab90e778629c355189dfe79c39c4b21f5f5ac 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java ++++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java +@@ -44,6 +44,7 @@ public class Tadpole extends AbstractFish { + public int age; + protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS); + protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING); ++ public boolean ageLocked; // Paper + + public Tadpole(EntityType type, Level world) { + super(type, world); +@@ -94,7 +95,7 @@ public class Tadpole extends AbstractFish { + @Override + public void aiStep() { + super.aiStep(); +- if (!this.level().isClientSide) { ++ if (!this.level().isClientSide && !this.ageLocked) { // Paper + this.setAge(this.age + 1); + } + +@@ -104,12 +105,14 @@ public class Tadpole extends AbstractFish { + public void addAdditionalSaveData(CompoundTag nbt) { + super.addAdditionalSaveData(nbt); + nbt.putInt("Age", this.age); ++ nbt.putBoolean("AgeLocked", this.ageLocked); // Paper + } + + @Override + public void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); + this.setAge(nbt.getInt("Age")); ++ this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper + } + + @Nullable +@@ -162,6 +165,7 @@ public class Tadpole extends AbstractFish { + CompoundTag nbttagcompound = stack.getOrCreateTag(); + + nbttagcompound.putInt("Age", this.getAge()); ++ nbttagcompound.putBoolean("AgeLocked", this.ageLocked); // Paper + } + + @Override +@@ -171,6 +175,7 @@ public class Tadpole extends AbstractFish { + this.setAge(nbt.getInt("Age")); + } + ++ this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper + } + + @Override +@@ -205,6 +210,7 @@ public class Tadpole extends AbstractFish { + } + + private void ageUp(int seconds) { ++ if (this.ageLocked) return; // Paper + this.setAge(this.age + seconds * 20); + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +index 8fc65fd7347340a89dba0b9839497aadfcc67d79..56cc6ecf7f95687db7c7c062b4ee979bfe49844b 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +@@ -695,6 +695,15 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + + } + ++ // Paper start - Horse API ++ public void setMouthOpen(boolean open) { ++ this.setFlag(FLAG_OPEN_MOUTH, open); ++ } ++ public boolean isMouthOpen() { ++ return this.getFlag(FLAG_OPEN_MOUTH); ++ } ++ // Paper end - Horse API ++ + @Override + public InteractionResult mobInteract(Player player, InteractionHand hand) { + if (!this.isVehicle() && !this.isBaby()) { +@@ -737,6 +746,11 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + this.setFlag(16, eatingGrass); + } + ++ // Paper start - Horse API ++ public void setForceStanding(boolean standing) { ++ this.setFlag(FLAG_STANDING, standing); ++ } ++ // Paper end - Horse API + public void setStanding(boolean angry) { + if (angry) { + this.setEating(false); +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +index edbb933d1f6f7fc6432f7a8b074c5dc20f47adfb..91fb62807b3c5600c83d4dc8d3fadf36e94e2133 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +@@ -74,7 +74,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, Level world) { + super(type, world); +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index 14b09adeb9222600c24f3fb846ea8aee467952e6..8241dbf7591b2f56b25cdc3ce9009c7133d2e4ef 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -84,6 +84,11 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob + return entityliving.getMobType() != MobType.UNDEAD && entityliving.attackable(); + }; + private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR); ++ // Paper start ++ private boolean canPortal = false; ++ ++ public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; } ++ // Paper end + + public WitherBoss(EntityType type, Level world) { + super(type, world); +@@ -596,7 +601,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob + + @Override + public boolean canChangeDimensions() { +- return false; ++ return super.canChangeDimensions() && canPortal; // Paper + } + + @Override +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 9637c26a3c381869f0a4dfe9189c0095387009b4..c360135b923aa8d1ed2c7caf97ede981cb605cf2 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +@@ -457,6 +457,16 @@ public class EnderMan extends Monster implements NeutralMob { + this.entityData.set(EnderMan.DATA_STARED_AT, true); + } + ++ // Paper start ++ public void setCreepy(boolean creepy) { ++ this.entityData.set(EnderMan.DATA_CREEPY, creepy); ++ } ++ ++ public void setHasBeenStaredAt(boolean hasBeenStaredAt) { ++ this.entityData.set(EnderMan.DATA_STARED_AT, hasBeenStaredAt); ++ } ++ // Paper end ++ + @Override + public boolean requiresCustomPersistence() { + return super.requiresCustomPersistence() || this.getCarriedBlock() != null; +diff --git a/src/main/java/net/minecraft/world/entity/monster/Ghast.java b/src/main/java/net/minecraft/world/entity/monster/Ghast.java +index e398a7d5c560b1d94b21fe3241365ef8592d9fc8..c135bc245f59a1af706f98b9d140dee77016b12f 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Ghast.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Ghast.java +@@ -67,6 +67,12 @@ public class Ghast extends FlyingMob implements Enemy { + return this.explosionPower; + } + ++ // Paper start ++ public void setExplosionPower(int explosionPower) { ++ this.explosionPower = explosionPower; ++ } ++ // Paper end ++ + @Override + protected boolean shouldDespawnInPeaceful() { + return true; +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 c043f63ff861ccb0194fc8cf102c27af5bcfe491..d4ac3e566b47cfc8688bcc2ab08385b6de4693f8 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +@@ -201,6 +201,12 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + } + + public void startConverting(@Nullable UUID uuid, int delay) { ++ // Paper start - missing entity behaviour api - converting without entity event ++ this.startConverting(uuid, delay, true); ++ } ++ ++ public void startConverting(@Nullable UUID uuid, int delay, boolean broadcastEntityEvent) { ++ // Paper end - missing entity behaviour api - converting without entity event + this.conversionStarter = uuid; + this.villagerConversionTime = delay; + this.getEntityData().set(ZombieVillager.DATA_CONVERTING_ID, true); +@@ -208,7 +214,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + this.removeEffect(MobEffects.WEAKNESS, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); + this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, delay, Math.min(this.level().getDifficulty().getId() - 1, 0)), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); + // CraftBukkit end +- this.level().broadcastEntityEvent(this, (byte) 16); ++ if (broadcastEntityEvent) this.level().broadcastEntityEvent(this, (byte) 16); // Paper - missing entity behaviour api - converting without entity event + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java +index 2645f52f5071bf57daf584e21a1f5cb6098110a8..cbe8593cecd84f1598649801bebcb46364044eef 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java +@@ -98,6 +98,20 @@ public class ThrownTrident extends AbstractArrow { + return (Boolean) this.entityData.get(ThrownTrident.ID_FOIL); + } + ++ // Paper start ++ public void setFoil(boolean foil) { ++ this.entityData.set(ThrownTrident.ID_FOIL, foil); ++ } ++ ++ public int getLoyalty() { ++ return this.entityData.get(ThrownTrident.ID_LOYALTY); ++ } ++ ++ public void setLoyalty(byte loyalty) { ++ this.entityData.set(ThrownTrident.ID_LOYALTY, loyalty); ++ } ++ // Paper end ++ + @Nullable + @Override + protected EntityHitResult findHitEntity(Vec3 currentPosition, Vec3 nextPosition) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +index 264b3fb45c47fbb6be78262838a5c0438860915f..f9cd595ec28f0284d11bae6bfc5bf92d56526ef9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +@@ -114,4 +114,36 @@ public abstract class CraftAbstractHorse extends CraftAnimals implements Abstrac + public AbstractHorseInventory getInventory() { + return new CraftSaddledInventory(getHandle().inventory); + } ++ ++ // Paper start - Horse API ++ @Override ++ public boolean isEatingGrass() { ++ return this.getHandle().isEating(); ++ } ++ ++ @Override ++ public void setEatingGrass(boolean eating) { ++ this.getHandle().setEating(eating); ++ } ++ ++ @Override ++ public boolean isRearing() { ++ return this.getHandle().isStanding(); ++ } ++ ++ @Override ++ public void setRearing(boolean rearing) { ++ this.getHandle().setForceStanding(rearing); ++ } ++ ++ @Override ++ public boolean isEating() { ++ return this.getHandle().isMouthOpen(); ++ } ++ ++ @Override ++ public void setEating(boolean eating) { ++ this.getHandle().setMouthOpen(eating); ++ } ++ // Paper end - Horse API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java +index 35580198ee9ea566dd2643a707653512c6cd938f..a46b2dfb2f1c0c7c3b55d81fc881e481348f98b8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java +@@ -244,4 +244,17 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud + this.getHandle().setOwner(null); + } + } ++ ++ // Paper start - owner API ++ @Override ++ public java.util.UUID getOwnerUniqueId() { ++ return this.getHandle().ownerUUID; ++ } ++ ++ @Override ++ public void setOwnerUniqueId(final java.util.UUID ownerUuid) { ++ this.getHandle().setOwner(null); ++ this.getHandle().ownerUUID = ownerUuid; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java +index b0a3531476f5a05ae846b68d825eddc35ebddea9..1bb72f28085f3885bec068b586ec222111044884 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java +@@ -27,4 +27,25 @@ public class CraftBat extends CraftAmbient implements Bat { + public void setAwake(boolean state) { + this.getHandle().setResting(!state); + } ++ // Paper start ++ @Override ++ public org.bukkit.Location getTargetLocation() { ++ net.minecraft.core.BlockPos pos = this.getHandle().targetPosition; ++ if (pos == null) { ++ return null; ++ } ++ ++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos); ++ } ++ ++ @Override ++ public void setTargetLocation(org.bukkit.Location location) { ++ net.minecraft.core.BlockPos pos = null; ++ if (location != null) { ++ pos = io.papermc.paper.util.MCUtil.toBlockPosition(location); ++ } ++ ++ this.getHandle().targetPosition = pos; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java +index cfff1be6a4a4936a2dadb2590abc3d33c123d048..3dac93b0ab5d5acf5b33dc4b0efed60319eb657b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java +@@ -86,4 +86,42 @@ public class CraftBee extends CraftAnimals implements Bee { + public void setCannotEnterHiveTicks(int ticks) { + this.getHandle().setStayOutOfHiveCountdown(ticks); + } ++ // Paper start ++ @Override ++ public void setRollingOverride(net.kyori.adventure.util.TriState rolling) { ++ this.getHandle().rollingOverride = rolling; ++ ++ this.getHandle().setRolling(this.getHandle().isRolling()); // Refresh rolling state ++ } ++ ++ @Override ++ public boolean isRolling() { ++ return this.getRollingOverride().toBooleanOrElse(this.getHandle().isRolling()); ++ } ++ ++ @Override ++ public net.kyori.adventure.util.TriState getRollingOverride() { ++ return this.getHandle().rollingOverride; ++ } ++ ++ @Override ++ public void setCropsGrownSincePollination(int crops) { ++ this.getHandle().numCropsGrownSincePollination = crops; ++ } ++ ++ @Override ++ public int getCropsGrownSincePollination() { ++ return this.getHandle().numCropsGrownSincePollination; ++ } ++ ++ @Override ++ public void setTicksSincePollination(int ticks) { ++ this.getHandle().ticksWithoutNectarSinceExitingHive = ticks; ++ } ++ ++ @Override ++ public int getTicksSincePollination() { ++ return this.getHandle().ticksWithoutNectarSinceExitingHive; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java +index b49d1e5c7389e1c2ccfe3a196b5325e5f5b190e7..42342628227742aa7ee6b84caa0e1f13b498babe 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java +@@ -66,4 +66,26 @@ public class CraftCat extends CraftTameableAnimal implements Cat { + return registry.get(CraftNamespacedKey.toMinecraft(bukkit.getKey())); + } + } ++ ++ // Paper start - More cat api ++ @Override ++ public void setLyingDown(boolean lyingDown) { ++ this.getHandle().setLying(lyingDown); ++ } ++ ++ @Override ++ public boolean isLyingDown() { ++ return this.getHandle().isLying(); ++ } ++ ++ @Override ++ public void setHeadUp(boolean headUp) { ++ this.getHandle().setRelaxStateOne(headUp); ++ } ++ ++ @Override ++ public boolean isHeadUp() { ++ return this.getHandle().isRelaxStateOne(); ++ } ++ // Paper end - More cat api + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java +index 64b75682a936e071353707f7615d6ff512fd617d..96f6e2fd9c6b20d34122abfe5c7fba732502d5a0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java +@@ -18,4 +18,26 @@ public class CraftChicken extends CraftAnimals implements Chicken { + public String toString() { + return "CraftChicken"; + } ++ ++ // Paper start ++ @Override ++ public boolean isChickenJockey() { ++ return this.getHandle().isChickenJockey(); ++ } ++ ++ @Override ++ public void setIsChickenJockey(boolean isChickenJockey) { ++ this.getHandle().setChickenJockey(isChickenJockey); ++ } ++ ++ @Override ++ public int getEggLayTime() { ++ return this.getHandle().eggTime; ++ } ++ ++ @Override ++ public void setEggLayTime(int eggLayTime) { ++ this.getHandle().eggTime = eggLayTime; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java +index fa0bf7db880063427ba12df1df1c72240fff93e9..63e6b07e3b159c74d9ef17be20b5ab43d07f0f5f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java +@@ -3,7 +3,7 @@ package org.bukkit.craftbukkit.entity; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Cod; + +-public class CraftCod extends CraftFish implements Cod { ++public class CraftCod extends io.papermc.paper.entity.PaperSchoolableFish implements Cod { // Paper - School Fish API + + public CraftCod(CraftServer server, net.minecraft.world.entity.animal.Cod entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java +index af432f9a1d255a56c31c3b97aeb4457d17f37e3e..f93f8f6509b12eb9b1e07c829278bb0822dd7988 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java +@@ -18,4 +18,36 @@ public class CraftDolphin extends CraftWaterMob implements Dolphin { + public String toString() { + return "CraftDolphin"; + } ++ ++ // Paper start - Missing Dolphin API ++ @Override ++ public int getMoistness() { ++ return this.getHandle().getMoistnessLevel(); ++ } ++ ++ @Override ++ public void setMoistness(int moistness) { ++ this.getHandle().setMoisntessLevel(moistness); ++ } ++ ++ @Override ++ public void setHasFish(boolean hasFish) { ++ this.getHandle().setGotFish(hasFish); ++ } ++ ++ @Override ++ public boolean hasFish() { ++ return this.getHandle().gotFish(); ++ } ++ ++ @Override ++ public org.bukkit.Location getTreasureLocation() { ++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), this.getHandle().getTreasurePos()); ++ } ++ ++ @Override ++ public void setTreasureLocation(org.bukkit.Location location) { ++ this.getHandle().setTreasurePos(io.papermc.paper.util.MCUtil.toBlockPosition(location)); ++ } ++ // Paper end - Missing Dolphin API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java +index 21dc209e6f98b6306833b41e2763e746047d5a94..983b9d6ddb58eff297e96e5c8b28ec427efa267d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java +@@ -40,6 +40,28 @@ public class CraftEnderman extends CraftMonster implements Enderman { + this.getHandle().setCarriedBlock(blockData == null ? null : ((CraftBlockData) blockData).getState()); + } + ++ // Paper start ++ @Override ++ public boolean isScreaming() { ++ return this.getHandle().isCreepy(); ++ } ++ ++ @Override ++ public void setScreaming(boolean screaming) { ++ this.getHandle().setCreepy(screaming); ++ } ++ ++ @Override ++ public boolean hasBeenStaredAt() { ++ return this.getHandle().hasBeenStaredAt(); ++ } ++ ++ @Override ++ public void setHasBeenStaredAt(boolean hasBeenStaredAt) { ++ this.getHandle().setHasBeenStaredAt(hasBeenStaredAt); ++ } ++ // Paper end ++ + @Override + public EnderMan getHandle() { + return (EnderMan) this.entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java +index fc0f0e841dc974d080e1abb9bbafb5165801131f..d657fd2c507a5b215aeab0a5f3e9c2ee892a27c8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java +@@ -28,4 +28,15 @@ public class CraftEndermite extends CraftMonster implements Endermite { + public void setPlayerSpawned(boolean playerSpawned) { + // Nop + } ++ // Paper start ++ @Override ++ public void setLifetimeTicks(int ticks) { ++ this.getHandle().life = ticks; ++ } ++ ++ @Override ++ public int getLifetimeTicks() { ++ return this.getHandle().life; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java +index 17164811bbcf983bef62c47bc99330074762267b..c455deb4fd2a7684bcc01a8212c362a2375c190b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java +@@ -113,4 +113,41 @@ public class CraftFox extends CraftAnimals implements Fox { + public boolean isFaceplanted() { + return this.getHandle().isFaceplanted(); + } ++ ++ // Paper start - Add more fox behavior API ++ @Override ++ public void setInterested(boolean interested) { ++ this.getHandle().setIsInterested(interested); ++ } ++ ++ @Override ++ public boolean isInterested() { ++ return this.getHandle().isInterested(); ++ } ++ ++ @Override ++ public void setLeaping(boolean leaping) { ++ this.getHandle().setIsPouncing(leaping); ++ } ++ ++ @Override ++ public boolean isLeaping() { ++ return this.getHandle().isPouncing(); ++ } ++ ++ @Override ++ public void setDefending(boolean defending) { ++ this.getHandle().setDefending(defending); ++ } ++ ++ @Override ++ public boolean isDefending() { ++ return this.getHandle().isDefending(); ++ } ++ ++ @Override ++ public void setFaceplanted(boolean faceplanted) { ++ this.getHandle().setFaceplanted(faceplanted); ++ } ++ // Paper end - Add more fox behavior API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java +index 2cec61a1bb050c1ef81c5fc3d0afafe9ff29d459..97fa4e1e70203194bd939618b2fad92665af6d59 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java +@@ -28,4 +28,17 @@ public class CraftGhast extends CraftFlying implements Ghast, CraftEnemy { + public void setCharging(boolean flag) { + this.getHandle().setCharging(flag); + } ++ ++ // Paper start ++ @Override ++ public int getExplosionPower() { ++ return this.getHandle().getExplosionPower(); ++ } ++ ++ @Override ++ public void setExplosionPower(int explosionPower) { ++ com.google.common.base.Preconditions.checkArgument(explosionPower >= 0 && explosionPower <= 127, "The explosion power has to be between 0 and 127"); ++ this.getHandle().setExplosionPower(explosionPower); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index cc3de2a961f474afee982cb94813bc48649d352a..8eb4b6c2752d68b866eab64263ede1d449ee2458 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -913,6 +913,22 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + this.getHandle().persistentInvisibility = invisible; + this.getHandle().setSharedFlag(5, invisible); + } ++ // Paper start ++ @Override ++ public float getSidewaysMovement() { ++ return this.getHandle().xxa; ++ } ++ ++ @Override ++ public float getForwardsMovement() { ++ return this.getHandle().zza; ++ } ++ ++ @Override ++ public float getUpwardsMovement() { ++ return this.getHandle().yya; ++ } ++ // Paper end + + // Paper start + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +index 9986ac517e11b076a29a8c8e3f480ec286fa5825..0ad16ee7b33582d214dab41eeee378d52c8e38ed 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +@@ -58,4 +58,36 @@ public class CraftLlama extends CraftChestedHorse implements Llama, com.destroys + public String toString() { + return "CraftLlama"; + } ++ ++ // Paper start ++ @Override ++ public boolean inCaravan() { ++ return this.getHandle().inCaravan(); ++ } ++ ++ @Override ++ public void joinCaravan(@org.jetbrains.annotations.NotNull Llama llama) { ++ this.getHandle().joinCaravan(((CraftLlama) llama).getHandle()); ++ } ++ ++ @Override ++ public void leaveCaravan() { ++ this.getHandle().leaveCaravan(); ++ } ++ ++ @Override ++ public boolean hasCaravanTail() { ++ return this.getHandle().hasCaravanTail(); ++ } ++ ++ @Override ++ public Llama getCaravanHead() { ++ return this.getHandle().getCaravanHead() == null ? null : (Llama) this.getHandle().getCaravanHead().getBukkitEntity(); ++ } ++ ++ @Override ++ public Llama getCaravanTail() { ++ return this.getHandle().caravanTail == null ? null : (Llama) this.getHandle().caravanTail.getBukkitEntity(); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java +index 17f5684cba9d3ed22d9925d1951520cc4751dfe2..3a3563a1bdbc0d84d973b3a04b50b78b4bc3d379 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java +@@ -33,4 +33,20 @@ public final class CraftMinecartHopper extends CraftMinecartContainer implements + public void setEnabled(boolean enabled) { + ((MinecartHopper) this.getHandle()).setEnabled(enabled); + } ++ // Paper start ++ @Override ++ public net.minecraft.world.entity.vehicle.MinecartHopper getHandle() { ++ return (net.minecraft.world.entity.vehicle.MinecartHopper) super.getHandle(); ++ } ++ ++ @Override ++ public int getPickupCooldown() { ++ throw new UnsupportedOperationException("Hopper minecarts don't have cooldowns"); ++ } ++ ++ @Override ++ public void setPickupCooldown(int cooldown) { ++ throw new UnsupportedOperationException("Hopper minecarts don't have cooldowns"); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index fc83dde12957e575a4f1d4bee73c320bab95606f..ae430c36ed433e337dd92f197f1717fbf00ac0e1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -148,4 +148,16 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + return getHandle().getMaxHeadXRot(); + } + // Paper end ++ ++ // Paper start ++ @Override ++ public boolean isAggressive() { ++ return this.getHandle().isAggressive(); ++ } ++ ++ @Override ++ public void setAggressive(boolean aggressive) { ++ this.getHandle().setAggressive(aggressive); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java +index cc69f471c623c65251ccf7015499d8dbdb70ffad..a41a85ad89a177759c97d661a89b8b5dc419db1b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java +@@ -89,6 +89,43 @@ public class CraftMushroomCow extends CraftCow implements MushroomCow { + this.getHandle().setVariant(net.minecraft.world.entity.animal.MushroomCow.MushroomType.values()[variant.ordinal()]); + } + ++ // Paper start ++ @Override ++ public java.util.List getStewEffects() { ++ if (this.getHandle().stewEffects == null) { ++ return java.util.List.of(); ++ } ++ ++ java.util.List nmsPairs = new java.util.ArrayList<>(this.getHandle().stewEffects.size()); ++ for (final net.minecraft.world.level.block.SuspiciousEffectHolder.EffectEntry effect : this.getHandle().stewEffects) { ++ nmsPairs.add(io.papermc.paper.potion.SuspiciousEffectEntry.create( ++ org.bukkit.craftbukkit.potion.CraftPotionEffectType.minecraftToBukkit(effect.effect()), ++ effect.duration() ++ )); ++ } ++ ++ return java.util.Collections.unmodifiableList(nmsPairs); ++ } ++ ++ @Override ++ public void setStewEffects(final java.util.List effects) { ++ if (effects.isEmpty()) { ++ this.getHandle().stewEffects = null; ++ return; ++ } ++ ++ java.util.List nmsPairs = new java.util.ArrayList<>(effects.size()); ++ for (final io.papermc.paper.potion.SuspiciousEffectEntry effect : effects) { ++ nmsPairs.add(new net.minecraft.world.level.block.SuspiciousEffectHolder.EffectEntry( ++ org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraft(effect.effect()), ++ effect.duration() ++ )); ++ } ++ ++ this.getHandle().stewEffects = nmsPairs; ++ } ++ // Paper end ++ + @Override + public String toString() { + return "CraftMushroomCow"; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java +index 5467e4a74b70ff57b49d9e6bc686c493178f8511..01d104d91de9e1319d27e39d3f474318c7809486 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java +@@ -41,6 +41,38 @@ public class CraftPanda extends CraftAnimals implements Panda { + this.getHandle().setHiddenGene(CraftPanda.toNms(gene)); + } + ++ // Paper start - Panda API ++ @Override ++ public void setSneezeTicks(int ticks) { ++ this.getHandle().setSneezeCounter(ticks); ++ } ++ ++ @Override ++ public int getSneezeTicks() { ++ return this.getHandle().getSneezeCounter(); ++ } ++ ++ @Override ++ public void setEatingTicks(int ticks) { ++ this.getHandle().setEatCounter(ticks); ++ } ++ ++ @Override ++ public int getEatingTicks() { ++ return this.getHandle().getEatCounter(); ++ } ++ ++ @Override ++ public void setUnhappyTicks(int ticks) { ++ this.getHandle().setUnhappyCounter(ticks); ++ } ++ ++ @Override ++ public Gene getCombinedGene() { ++ return CraftPanda.fromNms(this.getHandle().getVariant()); ++ } ++ // Paper end - Panda API ++ + @Override + public boolean isRolling() { + return this.getHandle().isRolling(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +index 9304e201db1ec96d0916aa8ea781f3e4bc7991e6..8338effd39b1709dbe578e247710a8e58d83e3aa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +@@ -44,5 +44,25 @@ public class CraftPhantom extends CraftFlying implements Phantom, CraftEnemy { + public void setShouldBurnInDay(boolean shouldBurnInDay) { + getHandle().setShouldBurnInDay(shouldBurnInDay); + } ++ ++ @Override ++ public org.bukkit.Location getAnchorLocation() { ++ net.minecraft.core.BlockPos pos = this.getHandle().anchorPoint; ++ if (pos == null) { ++ return null; ++ } ++ ++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos); ++ } ++ ++ @Override ++ public void setAnchorLocation(org.bukkit.Location location) { ++ net.minecraft.core.BlockPos pos = null; ++ if (location != null) { ++ pos = io.papermc.paper.util.MCUtil.toBlockPosition(location); ++ } ++ ++ this.getHandle().anchorPoint = pos; ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java +index f5ecb8c1dc92e5a4b123effd2859123b17a586d3..5124a383b60b2c8de89fa992547d0c61db760c21 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java +@@ -84,4 +84,37 @@ public class CraftPiglin extends CraftPiglinAbstract implements Piglin, com.dest + public String toString() { + return "CraftPiglin"; + } ++ // Paper start ++ @Override ++ public void setChargingCrossbow(boolean chargingCrossbow) { ++ this.getHandle().setChargingCrossbow(chargingCrossbow); ++ } ++ ++ @Override ++ public boolean isChargingCrossbow() { ++ return this.getHandle().isChargingCrossbow(); ++ } ++ ++ @Override ++ public void setDancing(boolean dancing) { ++ if (dancing) { ++ this.getHandle().getBrain().setMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING, true); ++ this.getHandle().getBrain().setMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION, this.getHandle().getOnPos()); ++ } else { ++ this.getHandle().getBrain().eraseMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING); ++ this.getHandle().getBrain().eraseMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION); ++ } ++ } ++ ++ @Override ++ public void setDancing(long duration) { ++ this.getHandle().getBrain().setMemoryWithExpiry(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING, true, duration); ++ this.getHandle().getBrain().setMemoryWithExpiry(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION, this.getHandle().getOnPos(), duration); ++ } ++ ++ @Override ++ public boolean isDancing() { ++ return this.getHandle().isDancing(); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java +index c7aec6f28e5d3546235b30f6b1112440a76163c5..fe075cfdf3097d6cb768e71b8cc360abb8eaf367 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java +@@ -17,4 +17,16 @@ public class CraftPolarBear extends CraftAnimals implements PolarBear { + public String toString() { + return "CraftPolarBear"; + } ++ ++ // Paper start ++ @Override ++ public boolean isStanding() { ++ return this.getHandle().isStanding(); ++ } ++ ++ @Override ++ public void setStanding(boolean standing) { ++ this.getHandle().setStanding(standing); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java +index 6b48b117a9cba12aae055c0ea981dfb5bc03a86e..519ef701a7d6534f7cb516f6296b95ee521f661d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java +@@ -29,4 +29,15 @@ public class CraftRabbit extends CraftAnimals implements Rabbit { + public void setRabbitType(Type type) { + this.getHandle().setVariant(net.minecraft.world.entity.animal.Rabbit.Variant.values()[type.ordinal()]); + } ++ // Paper start ++ @Override ++ public void setMoreCarrotTicks(int ticks) { ++ this.getHandle().moreCarrotTicks = ticks; ++ } ++ ++ @Override ++ public int getMoreCarrotTicks() { ++ return this.getHandle().moreCarrotTicks; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java +index cae59f77c704a5b9515dc4917ed5fdc89631ecfb..09796ce15658e3f7c223a265a547a51ee729ed40 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java +@@ -18,4 +18,35 @@ public class CraftRavager extends CraftRaider implements Ravager { + public String toString() { + return "CraftRavager"; + } ++ // Paper start - Missing Entity Behavior ++ @Override ++ public int getAttackTicks() { ++ return this.getHandle().getAttackTick(); ++ } ++ ++ @Override ++ public void setAttackTicks(int ticks) { ++ this.getHandle().attackTick = ticks; ++ } ++ ++ @Override ++ public int getStunnedTicks() { ++ return this.getHandle().getStunnedTick(); ++ } ++ ++ @Override ++ public void setStunnedTicks(int ticks) { ++ this.getHandle().stunnedTick = ticks; ++ } ++ ++ @Override ++ public int getRoarTicks() { ++ return this.getHandle().getRoarTick(); ++ } ++ ++ @Override ++ public void setRoarTicks(int ticks) { ++ this.getHandle().roarTick = ticks; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java +index b8140aa25a25870259b5644091c6643da1e14b54..d4d8ce60098c74508e2de9541bf6534988779764 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java +@@ -3,7 +3,7 @@ package org.bukkit.craftbukkit.entity; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Salmon; + +-public class CraftSalmon extends CraftFish implements Salmon { ++public class CraftSalmon extends io.papermc.paper.entity.PaperSchoolableFish implements Salmon { // Paper - Schooling Fish API + + public CraftSalmon(CraftServer server, net.minecraft.world.entity.animal.Salmon entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java +index 3f32c683ddc6999b89f2e4051eb6ae784b296b8f..dac3d34677688ac560bc1be2087a08479ef71b87 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java +@@ -67,4 +67,17 @@ public class CraftTNTPrimed extends CraftEntity implements TNTPrimed { + this.getHandle().owner = null; + } + } ++ ++ // Paper start ++ @Override ++ public void setBlockData(org.bukkit.block.data.BlockData data) { ++ com.google.common.base.Preconditions.checkArgument(data != null, "The visual block data of this tnt cannot be null. To reset it just set to the TNT default block data"); ++ this.getHandle().setBlockState(((org.bukkit.craftbukkit.block.data.CraftBlockData) data).getState()); ++ } ++ ++ @Override ++ public org.bukkit.block.data.BlockData getBlockData() { ++ return org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(this.getHandle().getBlockState()); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java +index 451a9bfd9b9b6945e224f1bb05c7951ed934b4e3..d7c6a0bbc5671ea8f2488230c94df5146a1e98b9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java +@@ -28,4 +28,15 @@ public class CraftTadpole extends CraftFish implements org.bukkit.entity.Tadpole + public void setAge(int age) { + this.getHandle().age = age; + } ++ // Paper start ++ @Override ++ public void setAgeLock(boolean lock) { ++ this.getHandle().ageLocked = lock; ++ } ++ ++ @Override ++ public boolean getAgeLock() { ++ return this.getHandle().ageLocked; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java +index 0fa00ae2b8a84e1ba5a902c1e46e561a761c54b6..20f9735c7cb76024e15dbdca7684f5c560876175 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java +@@ -31,4 +31,27 @@ public class CraftTrident extends CraftArrow implements Trident { + public String toString() { + return "CraftTrident"; + } ++ ++ // Paper start ++ @Override ++ public boolean hasGlint() { ++ return this.getHandle().isFoil(); ++ } ++ ++ @Override ++ public void setGlint(boolean glint) { ++ this.getHandle().setFoil(glint); ++ } ++ ++ @Override ++ public int getLoyaltyLevel() { ++ return this.getHandle().getLoyalty(); ++ } ++ ++ @Override ++ public void setLoyaltyLevel(int loyaltyLevel) { ++ com.google.common.base.Preconditions.checkArgument(loyaltyLevel >= 0 && loyaltyLevel <= 127, "The loyalty level has to be between 0 and 127"); ++ this.getHandle().setLoyalty((byte) loyaltyLevel); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java +index e3bde6d1c0e03407af1382a61748470063bb2e18..9e53c30801c700719c78c0fd521fd615c94e02c8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java +@@ -7,7 +7,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.TropicalFish; + import org.bukkit.entity.TropicalFish.Pattern; + +-public class CraftTropicalFish extends CraftFish implements TropicalFish { ++public class CraftTropicalFish extends io.papermc.paper.entity.PaperSchoolableFish implements TropicalFish { // Paper - Schooling Fish API + + public CraftTropicalFish(CraftServer server, net.minecraft.world.entity.animal.TropicalFish entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +index 1cfbe9c476f4a254edf3edf4b70696bbaba78558..e9ec3455eabc473e104b5342a615a38c1ac25a4f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +@@ -29,6 +29,26 @@ public class CraftVex extends CraftMonster implements Vex { + public void setSummoner(org.bukkit.entity.Mob summoner) { + getHandle().setOwner(summoner == null ? null : ((CraftMob) summoner).getHandle()); + } ++ ++ @Override ++ public boolean hasLimitedLifetime() { ++ return this.getHandle().hasLimitedLife; ++ } ++ ++ @Override ++ public void setLimitedLifetime(boolean hasLimitedLifetime) { ++ this.getHandle().hasLimitedLife = hasLimitedLifetime; ++ } ++ ++ @Override ++ public int getLimitedLifetimeTicks() { ++ return this.getHandle().limitedLifeTicks; ++ } ++ ++ @Override ++ public void setLimitedLifetimeTicks(int ticks) { ++ this.getHandle().limitedLifeTicks = ticks; ++ } + // Paper end + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java +index e2a0c11867abee6add8775259c54f2052de7b1ad..3aa23d9f22d5cd22231293fd7d1ca4cb79eb7cb3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java +@@ -60,13 +60,20 @@ public class CraftVillagerZombie extends CraftZombie implements ZombieVillager { + + @Override + public void setConversionTime(int time) { ++ // Paper start - missing entity behaviour api - converting without entity event ++ this.setConversionTime(time, true); ++ } ++ ++ @Override ++ public void setConversionTime(int time, boolean broadcastEntityEvent) { ++ // Paper end - missing entity behaviour api - converting without entity event + if (time < 0) { + this.getHandle().villagerConversionTime = -1; + this.getHandle().getEntityData().set(net.minecraft.world.entity.monster.ZombieVillager.DATA_CONVERTING_ID, false); + this.getHandle().conversionStarter = null; + this.getHandle().removeEffect(MobEffects.DAMAGE_BOOST, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); + } else { +- this.getHandle().startConverting(null, time); ++ this.getHandle().startConverting(null, time, broadcastEntityEvent); // Paper - missing entity behaviour api - converting without entity event + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java +index 0e597394a3dd08f022614fc9777302fea581eb55..3cceefa0d6278924a19641a49bdf16bcdacb2233 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java +@@ -49,5 +49,25 @@ public class CraftWanderingTrader extends CraftAbstractVillager implements Wande + public boolean canDrinkMilk() { + return getHandle().canDrinkMilk; + } ++ ++ @Override ++ public org.bukkit.Location getWanderingTowards() { ++ net.minecraft.core.BlockPos pos = this.getHandle().getWanderTarget(); ++ if (pos == null) { ++ return null; ++ } ++ ++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos); ++ } ++ ++ @Override ++ public void setWanderingTowards(org.bukkit.Location location) { ++ net.minecraft.core.BlockPos pos = null; ++ if (location != null) { ++ pos = io.papermc.paper.util.MCUtil.toBlockPosition(location); ++ } ++ ++ this.getHandle().setWanderTarget(pos); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java +index 794e4fe0a3fbd967f665b2707865c15491370c76..c284eb96a1e330078076cbe61f0f6e2ff4ed89bd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java +@@ -37,6 +37,13 @@ public class CraftWarden extends CraftMonster implements org.bukkit.entity.Warde + return this.getHandle().getAngerManagement().getActiveAnger(((CraftEntity) entity).getHandle()); + } + ++ // Paper start ++ @Override ++ public int getHighestAnger() { ++ return this.getHandle().getAngerManagement().getActiveAnger(null); ++ } ++ // Paper end ++ + @Override + public void increaseAnger(Entity entity, int increase) { + Preconditions.checkArgument(entity != null, "Entity cannot be null"); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +index 1113533d281ed159bb735040fb1f913482debf3a..7a8ce6956db56061af93ba9761f5d1057a90bc49 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +@@ -67,4 +67,36 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok + + this.getHandle().setInvulnerableTicks(ticks); + } ++ ++ // Paper start ++ @Override ++ public boolean isCharged() { ++ return getHandle().isPowered(); ++ } ++ ++ @Override ++ public int getInvulnerableTicks() { ++ return getHandle().getInvulnerableTicks(); ++ } ++ ++ @Override ++ public void setInvulnerableTicks(int ticks) { ++ getHandle().setInvulnerableTicks(ticks); ++ } ++ ++ @Override ++ public boolean canTravelThroughPortals() { ++ return getHandle().canChangeDimensions(); ++ } ++ ++ @Override ++ public void setCanTravelThroughPortals(boolean value) { ++ getHandle().setCanTravelThroughPortals(value); ++ } ++ ++ @Override ++ public void enterInvulnerabilityPhase() { ++ this.getHandle().makeInvulnerable(); ++ } ++ // Paper end + } diff --git a/patches/server/0588-Ensure-disconnect-for-book-edit-is-called-on-main.patch b/patches/server/0588-Ensure-disconnect-for-book-edit-is-called-on-main.patch new file mode 100644 index 000000000000..f09bb606829a --- /dev/null +++ b/patches/server/0588-Ensure-disconnect-for-book-edit-is-called-on-main.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 22 Jun 2021 19:58:53 +0100 +Subject: [PATCH] Ensure disconnect for book edit is called on main + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 28a0570988f93b21f530a6cca87efa429f83079d..6f4c8b6ac6f56d183796deaa0d3b5a23241e8348 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1096,7 +1096,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // Paper end - Book size limits + // CraftBukkit start + if (this.lastBookTick + 20 > MinecraftServer.currentTick) { +- this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause ++ server.scheduleOnMain(() -> this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause // Paper - Also ensure this is called on main + return; + } + this.lastBookTick = MinecraftServer.currentTick; diff --git a/patches/server/0588-Missing-Entity-API.patch b/patches/server/0588-Missing-Entity-API.patch deleted file mode 100644 index d3d7dc9346ea..000000000000 --- a/patches/server/0588-Missing-Entity-API.patch +++ /dev/null @@ -1,1353 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Mon, 21 Jun 2021 23:56:07 -0400 -Subject: [PATCH] Missing Entity API - -== AT == -public net.minecraft.world.entity.animal.Fox isDefending()Z -public net.minecraft.world.entity.animal.Fox setDefending(Z)V -public net.minecraft.world.entity.animal.Fox setFaceplanted(Z)V -public net.minecraft.world.entity.animal.Panda getEatCounter()I -public net.minecraft.world.entity.animal.Panda setEatCounter(I)V -public net.minecraft.world.entity.animal.Bee isRolling()Z -public net.minecraft.world.entity.animal.Bee setRolling(Z)V -public net.minecraft.world.entity.animal.Bee numCropsGrownSincePollination -public net.minecraft.world.entity.animal.Bee ticksWithoutNectarSinceExitingHive -public net.minecraft.world.entity.monster.piglin.Piglin isChargingCrossbow()Z -public net.minecraft.world.entity.ambient.Bat targetPosition -public net.minecraft.world.entity.monster.Ravager attackTick -public net.minecraft.world.entity.monster.Ravager stunnedTick -public net.minecraft.world.entity.monster.Ravager roarTick -public net.minecraft.world.entity.vehicle.MinecartTNT explode(D)V -public net.minecraft.world.entity.vehicle.MinecartTNT fuse -public net.minecraft.world.entity.monster.Endermite life -public net.minecraft.world.entity.projectile.AbstractArrow soundEvent -public net.minecraft.world.entity.monster.Phantom anchorPoint -public net.minecraft.world.entity.npc.WanderingTrader getWanderTarget()Lnet/minecraft/core/BlockPos; -public net.minecraft.world.entity.animal.AbstractSchoolingFish leader -public net.minecraft.world.entity.animal.AbstractSchoolingFish schoolSize -public net.minecraft.world.entity.animal.Rabbit moreCarrotTicks -public net.minecraft.world.entity.AreaEffectCloud ownerUUID -public net.minecraft.world.entity.animal.MushroomCow stewEffects - -Co-authored-by: Nassim Jahnke -Co-authored-by: Jake Potrebic -Co-authored-by: William Blake Galbreath -Co-authored-by: SoSeDiK -Co-authored-by: booky10 -Co-authored-by: Amin - -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -index 8117578ced94aa6bf01871f6526a388385c4adf2..59699c59fdfc611177fdb3136f84ab539b17d9c9 100644 ---- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -@@ -165,7 +165,7 @@ public class MobGoalHelper { - bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class); - bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class); - bukkitMap.put(AbstractFish.class, Fish.class); -- bukkitMap.put(AbstractSchoolingFish.class, Fish.class); // close enough -+ bukkitMap.put(AbstractSchoolingFish.class, io.papermc.paper.entity.SchoolableFish.class); - bukkitMap.put(FlyingMob.class, Flying.class); - bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class); - bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class); -diff --git a/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java b/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java -new file mode 100644 -index 0000000000000000000000000000000000000000..41bf71d116ffc5431586ce54abba7f8def6c1dcf ---- /dev/null -+++ b/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java -@@ -0,0 +1,52 @@ -+package io.papermc.paper.entity; -+ -+import net.minecraft.world.entity.animal.AbstractSchoolingFish; -+import org.bukkit.craftbukkit.CraftServer; -+import org.bukkit.craftbukkit.entity.CraftFish; -+import org.jetbrains.annotations.NotNull; -+ -+public class PaperSchoolableFish extends CraftFish implements SchoolableFish { -+ -+ public PaperSchoolableFish(CraftServer server, AbstractSchoolingFish entity) { -+ super(server, entity); -+ } -+ -+ @Override -+ public AbstractSchoolingFish getHandle() { -+ return (AbstractSchoolingFish) super.getHandle(); -+ } -+ -+ @Override -+ public void startFollowing(@NotNull SchoolableFish fish) { -+ if (this.getHandle().isFollower()) { // If following a fish already, properly remove the old one -+ this.stopFollowing(); -+ } -+ -+ this.getHandle().startFollowing(((PaperSchoolableFish) fish).getHandle()); -+ } -+ -+ @Override -+ public void stopFollowing() { -+ this.getHandle().stopFollowing(); -+ } -+ -+ @Override -+ public int getSchoolSize() { -+ return this.getHandle().schoolSize; -+ } -+ -+ @Override -+ public int getMaxSchoolSize() { -+ return this.getHandle().getMaxSchoolSize(); -+ } -+ -+ @Override -+ public SchoolableFish getSchoolLeader() { -+ AbstractSchoolingFish leader = this.getHandle().leader; -+ if (leader == null) { -+ return null; -+ } -+ -+ return (SchoolableFish) leader.getBukkitEntity(); -+ } -+} -diff --git a/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java b/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java -index 39ed3ca76d6b64ef3917280ec822721cc02afada..86b437836cb4b1f6e8ca9acd5f1f93b925cf9e51 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java -+++ b/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java -@@ -52,6 +52,7 @@ public abstract class AbstractSchoolingFish extends AbstractFish { - } - - public void stopFollowing() { -+ if (this.leader == null) return; // Avoid NPE, plugins can now set the leader and certain fish goals might cause this method to be called - this.leader.removeFollower(); - this.leader = null; - } -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 07ecc038a1000581335b8d18c094298f2f3b100f..91ea960ba223bae42655c581b9b6c0981f333c9b 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -553,11 +553,13 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - this.setFlag(4, hasStung); - } - -+ public net.kyori.adventure.util.TriState rollingOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Rolling override - public boolean isRolling() { - return this.getFlag(2); - } - - public void setRolling(boolean nearTarget) { -+ nearTarget = rollingOverride.toBooleanOrElse(nearTarget); // Paper - Rolling override - this.setFlag(2, nearTarget); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -index 52cc265f1663d648b6bfd03f2ac3e191b1c16d44..e42b0b19019ef74733fd19b08f882cccff920142 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -+++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -@@ -55,7 +55,7 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder stewEffects; -+ public List stewEffects; // Paper - private -> public (AT does not seem to work for this field) - @Nullable - private UUID lastLightningBoltUUID; - -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -index 71a08510a928d4570822282bb31f14013ec3834a..4aeab90e778629c355189dfe79c39c4b21f5f5ac 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -@@ -44,6 +44,7 @@ public class Tadpole extends AbstractFish { - public int age; - protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS); - protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING); -+ public boolean ageLocked; // Paper - - public Tadpole(EntityType type, Level world) { - super(type, world); -@@ -94,7 +95,7 @@ public class Tadpole extends AbstractFish { - @Override - public void aiStep() { - super.aiStep(); -- if (!this.level().isClientSide) { -+ if (!this.level().isClientSide && !this.ageLocked) { // Paper - this.setAge(this.age + 1); - } - -@@ -104,12 +105,14 @@ public class Tadpole extends AbstractFish { - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); - nbt.putInt("Age", this.age); -+ nbt.putBoolean("AgeLocked", this.ageLocked); // Paper - } - - @Override - public void readAdditionalSaveData(CompoundTag nbt) { - super.readAdditionalSaveData(nbt); - this.setAge(nbt.getInt("Age")); -+ this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper - } - - @Nullable -@@ -162,6 +165,7 @@ public class Tadpole extends AbstractFish { - CompoundTag nbttagcompound = stack.getOrCreateTag(); - - nbttagcompound.putInt("Age", this.getAge()); -+ nbttagcompound.putBoolean("AgeLocked", this.ageLocked); // Paper - } - - @Override -@@ -171,6 +175,7 @@ public class Tadpole extends AbstractFish { - this.setAge(nbt.getInt("Age")); - } - -+ this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper - } - - @Override -@@ -205,6 +210,7 @@ public class Tadpole extends AbstractFish { - } - - private void ageUp(int seconds) { -+ if (this.ageLocked) return; // Paper - this.setAge(this.age + seconds * 20); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -index 8fc65fd7347340a89dba0b9839497aadfcc67d79..56cc6ecf7f95687db7c7c062b4ee979bfe49844b 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -@@ -695,6 +695,15 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - - } - -+ // Paper start - Horse API -+ public void setMouthOpen(boolean open) { -+ this.setFlag(FLAG_OPEN_MOUTH, open); -+ } -+ public boolean isMouthOpen() { -+ return this.getFlag(FLAG_OPEN_MOUTH); -+ } -+ // Paper end - Horse API -+ - @Override - public InteractionResult mobInteract(Player player, InteractionHand hand) { - if (!this.isVehicle() && !this.isBaby()) { -@@ -737,6 +746,11 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - this.setFlag(16, eatingGrass); - } - -+ // Paper start - Horse API -+ public void setForceStanding(boolean standing) { -+ this.setFlag(FLAG_STANDING, standing); -+ } -+ // Paper end - Horse API - public void setStanding(boolean angry) { - if (angry) { - this.setEating(false); -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -index edbb933d1f6f7fc6432f7a8b074c5dc20f47adfb..91fb62807b3c5600c83d4dc8d3fadf36e94e2133 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -@@ -74,7 +74,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, Level world) { - super(type, world); -diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 14b09adeb9222600c24f3fb846ea8aee467952e6..8241dbf7591b2f56b25cdc3ce9009c7133d2e4ef 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -84,6 +84,11 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - return entityliving.getMobType() != MobType.UNDEAD && entityliving.attackable(); - }; - private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR); -+ // Paper start -+ private boolean canPortal = false; -+ -+ public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; } -+ // Paper end - - public WitherBoss(EntityType type, Level world) { - super(type, world); -@@ -596,7 +601,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - - @Override - public boolean canChangeDimensions() { -- return false; -+ return super.canChangeDimensions() && canPortal; // Paper - } - - @Override -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 9637c26a3c381869f0a4dfe9189c0095387009b4..c360135b923aa8d1ed2c7caf97ede981cb605cf2 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -@@ -457,6 +457,16 @@ public class EnderMan extends Monster implements NeutralMob { - this.entityData.set(EnderMan.DATA_STARED_AT, true); - } - -+ // Paper start -+ public void setCreepy(boolean creepy) { -+ this.entityData.set(EnderMan.DATA_CREEPY, creepy); -+ } -+ -+ public void setHasBeenStaredAt(boolean hasBeenStaredAt) { -+ this.entityData.set(EnderMan.DATA_STARED_AT, hasBeenStaredAt); -+ } -+ // Paper end -+ - @Override - public boolean requiresCustomPersistence() { - return super.requiresCustomPersistence() || this.getCarriedBlock() != null; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Ghast.java b/src/main/java/net/minecraft/world/entity/monster/Ghast.java -index e398a7d5c560b1d94b21fe3241365ef8592d9fc8..c135bc245f59a1af706f98b9d140dee77016b12f 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ghast.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ghast.java -@@ -67,6 +67,12 @@ public class Ghast extends FlyingMob implements Enemy { - return this.explosionPower; - } - -+ // Paper start -+ public void setExplosionPower(int explosionPower) { -+ this.explosionPower = explosionPower; -+ } -+ // Paper end -+ - @Override - protected boolean shouldDespawnInPeaceful() { - return true; -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 c043f63ff861ccb0194fc8cf102c27af5bcfe491..d4ac3e566b47cfc8688bcc2ab08385b6de4693f8 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -@@ -201,6 +201,12 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - } - - public void startConverting(@Nullable UUID uuid, int delay) { -+ // Paper start - missing entity behaviour api - converting without entity event -+ this.startConverting(uuid, delay, true); -+ } -+ -+ public void startConverting(@Nullable UUID uuid, int delay, boolean broadcastEntityEvent) { -+ // Paper end - missing entity behaviour api - converting without entity event - this.conversionStarter = uuid; - this.villagerConversionTime = delay; - this.getEntityData().set(ZombieVillager.DATA_CONVERTING_ID, true); -@@ -208,7 +214,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - this.removeEffect(MobEffects.WEAKNESS, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); - this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, delay, Math.min(this.level().getDifficulty().getId() - 1, 0)), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); - // CraftBukkit end -- this.level().broadcastEntityEvent(this, (byte) 16); -+ if (broadcastEntityEvent) this.level().broadcastEntityEvent(this, (byte) 16); // Paper - missing entity behaviour api - converting without entity event - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -index 2645f52f5071bf57daf584e21a1f5cb6098110a8..cbe8593cecd84f1598649801bebcb46364044eef 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -@@ -98,6 +98,20 @@ public class ThrownTrident extends AbstractArrow { - return (Boolean) this.entityData.get(ThrownTrident.ID_FOIL); - } - -+ // Paper start -+ public void setFoil(boolean foil) { -+ this.entityData.set(ThrownTrident.ID_FOIL, foil); -+ } -+ -+ public int getLoyalty() { -+ return this.entityData.get(ThrownTrident.ID_LOYALTY); -+ } -+ -+ public void setLoyalty(byte loyalty) { -+ this.entityData.set(ThrownTrident.ID_LOYALTY, loyalty); -+ } -+ // Paper end -+ - @Nullable - @Override - protected EntityHitResult findHitEntity(Vec3 currentPosition, Vec3 nextPosition) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java -index 264b3fb45c47fbb6be78262838a5c0438860915f..f9cd595ec28f0284d11bae6bfc5bf92d56526ef9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java -@@ -114,4 +114,36 @@ public abstract class CraftAbstractHorse extends CraftAnimals implements Abstrac - public AbstractHorseInventory getInventory() { - return new CraftSaddledInventory(getHandle().inventory); - } -+ -+ // Paper start - Horse API -+ @Override -+ public boolean isEatingGrass() { -+ return this.getHandle().isEating(); -+ } -+ -+ @Override -+ public void setEatingGrass(boolean eating) { -+ this.getHandle().setEating(eating); -+ } -+ -+ @Override -+ public boolean isRearing() { -+ return this.getHandle().isStanding(); -+ } -+ -+ @Override -+ public void setRearing(boolean rearing) { -+ this.getHandle().setForceStanding(rearing); -+ } -+ -+ @Override -+ public boolean isEating() { -+ return this.getHandle().isMouthOpen(); -+ } -+ -+ @Override -+ public void setEating(boolean eating) { -+ this.getHandle().setMouthOpen(eating); -+ } -+ // Paper end - Horse API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java -index 35580198ee9ea566dd2643a707653512c6cd938f..a46b2dfb2f1c0c7c3b55d81fc881e481348f98b8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java -@@ -244,4 +244,17 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud - this.getHandle().setOwner(null); - } - } -+ -+ // Paper start - owner API -+ @Override -+ public java.util.UUID getOwnerUniqueId() { -+ return this.getHandle().ownerUUID; -+ } -+ -+ @Override -+ public void setOwnerUniqueId(final java.util.UUID ownerUuid) { -+ this.getHandle().setOwner(null); -+ this.getHandle().ownerUUID = ownerUuid; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java -index b0a3531476f5a05ae846b68d825eddc35ebddea9..1bb72f28085f3885bec068b586ec222111044884 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java -@@ -27,4 +27,25 @@ public class CraftBat extends CraftAmbient implements Bat { - public void setAwake(boolean state) { - this.getHandle().setResting(!state); - } -+ // Paper start -+ @Override -+ public org.bukkit.Location getTargetLocation() { -+ net.minecraft.core.BlockPos pos = this.getHandle().targetPosition; -+ if (pos == null) { -+ return null; -+ } -+ -+ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos); -+ } -+ -+ @Override -+ public void setTargetLocation(org.bukkit.Location location) { -+ net.minecraft.core.BlockPos pos = null; -+ if (location != null) { -+ pos = io.papermc.paper.util.MCUtil.toBlockPosition(location); -+ } -+ -+ this.getHandle().targetPosition = pos; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java -index cfff1be6a4a4936a2dadb2590abc3d33c123d048..3dac93b0ab5d5acf5b33dc4b0efed60319eb657b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java -@@ -86,4 +86,42 @@ public class CraftBee extends CraftAnimals implements Bee { - public void setCannotEnterHiveTicks(int ticks) { - this.getHandle().setStayOutOfHiveCountdown(ticks); - } -+ // Paper start -+ @Override -+ public void setRollingOverride(net.kyori.adventure.util.TriState rolling) { -+ this.getHandle().rollingOverride = rolling; -+ -+ this.getHandle().setRolling(this.getHandle().isRolling()); // Refresh rolling state -+ } -+ -+ @Override -+ public boolean isRolling() { -+ return this.getRollingOverride().toBooleanOrElse(this.getHandle().isRolling()); -+ } -+ -+ @Override -+ public net.kyori.adventure.util.TriState getRollingOverride() { -+ return this.getHandle().rollingOverride; -+ } -+ -+ @Override -+ public void setCropsGrownSincePollination(int crops) { -+ this.getHandle().numCropsGrownSincePollination = crops; -+ } -+ -+ @Override -+ public int getCropsGrownSincePollination() { -+ return this.getHandle().numCropsGrownSincePollination; -+ } -+ -+ @Override -+ public void setTicksSincePollination(int ticks) { -+ this.getHandle().ticksWithoutNectarSinceExitingHive = ticks; -+ } -+ -+ @Override -+ public int getTicksSincePollination() { -+ return this.getHandle().ticksWithoutNectarSinceExitingHive; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java -index b49d1e5c7389e1c2ccfe3a196b5325e5f5b190e7..42342628227742aa7ee6b84caa0e1f13b498babe 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java -@@ -66,4 +66,26 @@ public class CraftCat extends CraftTameableAnimal implements Cat { - return registry.get(CraftNamespacedKey.toMinecraft(bukkit.getKey())); - } - } -+ -+ // Paper start - More cat api -+ @Override -+ public void setLyingDown(boolean lyingDown) { -+ this.getHandle().setLying(lyingDown); -+ } -+ -+ @Override -+ public boolean isLyingDown() { -+ return this.getHandle().isLying(); -+ } -+ -+ @Override -+ public void setHeadUp(boolean headUp) { -+ this.getHandle().setRelaxStateOne(headUp); -+ } -+ -+ @Override -+ public boolean isHeadUp() { -+ return this.getHandle().isRelaxStateOne(); -+ } -+ // Paper end - More cat api - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java -index 64b75682a936e071353707f7615d6ff512fd617d..96f6e2fd9c6b20d34122abfe5c7fba732502d5a0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java -@@ -18,4 +18,26 @@ public class CraftChicken extends CraftAnimals implements Chicken { - public String toString() { - return "CraftChicken"; - } -+ -+ // Paper start -+ @Override -+ public boolean isChickenJockey() { -+ return this.getHandle().isChickenJockey(); -+ } -+ -+ @Override -+ public void setIsChickenJockey(boolean isChickenJockey) { -+ this.getHandle().setChickenJockey(isChickenJockey); -+ } -+ -+ @Override -+ public int getEggLayTime() { -+ return this.getHandle().eggTime; -+ } -+ -+ @Override -+ public void setEggLayTime(int eggLayTime) { -+ this.getHandle().eggTime = eggLayTime; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java -index fa0bf7db880063427ba12df1df1c72240fff93e9..63e6b07e3b159c74d9ef17be20b5ab43d07f0f5f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java -@@ -3,7 +3,7 @@ package org.bukkit.craftbukkit.entity; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.Cod; - --public class CraftCod extends CraftFish implements Cod { -+public class CraftCod extends io.papermc.paper.entity.PaperSchoolableFish implements Cod { // Paper - School Fish API - - public CraftCod(CraftServer server, net.minecraft.world.entity.animal.Cod entity) { - super(server, entity); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java -index af432f9a1d255a56c31c3b97aeb4457d17f37e3e..f93f8f6509b12eb9b1e07c829278bb0822dd7988 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java -@@ -18,4 +18,36 @@ public class CraftDolphin extends CraftWaterMob implements Dolphin { - public String toString() { - return "CraftDolphin"; - } -+ -+ // Paper start - Missing Dolphin API -+ @Override -+ public int getMoistness() { -+ return this.getHandle().getMoistnessLevel(); -+ } -+ -+ @Override -+ public void setMoistness(int moistness) { -+ this.getHandle().setMoisntessLevel(moistness); -+ } -+ -+ @Override -+ public void setHasFish(boolean hasFish) { -+ this.getHandle().setGotFish(hasFish); -+ } -+ -+ @Override -+ public boolean hasFish() { -+ return this.getHandle().gotFish(); -+ } -+ -+ @Override -+ public org.bukkit.Location getTreasureLocation() { -+ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), this.getHandle().getTreasurePos()); -+ } -+ -+ @Override -+ public void setTreasureLocation(org.bukkit.Location location) { -+ this.getHandle().setTreasurePos(io.papermc.paper.util.MCUtil.toBlockPosition(location)); -+ } -+ // Paper end - Missing Dolphin API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java -index 21dc209e6f98b6306833b41e2763e746047d5a94..983b9d6ddb58eff297e96e5c8b28ec427efa267d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java -@@ -40,6 +40,28 @@ public class CraftEnderman extends CraftMonster implements Enderman { - this.getHandle().setCarriedBlock(blockData == null ? null : ((CraftBlockData) blockData).getState()); - } - -+ // Paper start -+ @Override -+ public boolean isScreaming() { -+ return this.getHandle().isCreepy(); -+ } -+ -+ @Override -+ public void setScreaming(boolean screaming) { -+ this.getHandle().setCreepy(screaming); -+ } -+ -+ @Override -+ public boolean hasBeenStaredAt() { -+ return this.getHandle().hasBeenStaredAt(); -+ } -+ -+ @Override -+ public void setHasBeenStaredAt(boolean hasBeenStaredAt) { -+ this.getHandle().setHasBeenStaredAt(hasBeenStaredAt); -+ } -+ // Paper end -+ - @Override - public EnderMan getHandle() { - return (EnderMan) this.entity; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -index fc0f0e841dc974d080e1abb9bbafb5165801131f..d657fd2c507a5b215aeab0a5f3e9c2ee892a27c8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -@@ -28,4 +28,15 @@ public class CraftEndermite extends CraftMonster implements Endermite { - public void setPlayerSpawned(boolean playerSpawned) { - // Nop - } -+ // Paper start -+ @Override -+ public void setLifetimeTicks(int ticks) { -+ this.getHandle().life = ticks; -+ } -+ -+ @Override -+ public int getLifetimeTicks() { -+ return this.getHandle().life; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java -index 17164811bbcf983bef62c47bc99330074762267b..c455deb4fd2a7684bcc01a8212c362a2375c190b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java -@@ -113,4 +113,41 @@ public class CraftFox extends CraftAnimals implements Fox { - public boolean isFaceplanted() { - return this.getHandle().isFaceplanted(); - } -+ -+ // Paper start - Add more fox behavior API -+ @Override -+ public void setInterested(boolean interested) { -+ this.getHandle().setIsInterested(interested); -+ } -+ -+ @Override -+ public boolean isInterested() { -+ return this.getHandle().isInterested(); -+ } -+ -+ @Override -+ public void setLeaping(boolean leaping) { -+ this.getHandle().setIsPouncing(leaping); -+ } -+ -+ @Override -+ public boolean isLeaping() { -+ return this.getHandle().isPouncing(); -+ } -+ -+ @Override -+ public void setDefending(boolean defending) { -+ this.getHandle().setDefending(defending); -+ } -+ -+ @Override -+ public boolean isDefending() { -+ return this.getHandle().isDefending(); -+ } -+ -+ @Override -+ public void setFaceplanted(boolean faceplanted) { -+ this.getHandle().setFaceplanted(faceplanted); -+ } -+ // Paper end - Add more fox behavior API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java -index 2cec61a1bb050c1ef81c5fc3d0afafe9ff29d459..97fa4e1e70203194bd939618b2fad92665af6d59 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java -@@ -28,4 +28,17 @@ public class CraftGhast extends CraftFlying implements Ghast, CraftEnemy { - public void setCharging(boolean flag) { - this.getHandle().setCharging(flag); - } -+ -+ // Paper start -+ @Override -+ public int getExplosionPower() { -+ return this.getHandle().getExplosionPower(); -+ } -+ -+ @Override -+ public void setExplosionPower(int explosionPower) { -+ com.google.common.base.Preconditions.checkArgument(explosionPower >= 0 && explosionPower <= 127, "The explosion power has to be between 0 and 127"); -+ this.getHandle().setExplosionPower(explosionPower); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 55bb8e5e8e09e35320094389bf68d204d21e4f9e..ed4ccda0063b4cf52fe6a4ded42c17aca396e6ff 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -900,6 +900,22 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - this.getHandle().persistentInvisibility = invisible; - this.getHandle().setSharedFlag(5, invisible); - } -+ // Paper start -+ @Override -+ public float getSidewaysMovement() { -+ return this.getHandle().xxa; -+ } -+ -+ @Override -+ public float getForwardsMovement() { -+ return this.getHandle().zza; -+ } -+ -+ @Override -+ public float getUpwardsMovement() { -+ return this.getHandle().yya; -+ } -+ // Paper end - - // Paper start - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -index 9986ac517e11b076a29a8c8e3f480ec286fa5825..0ad16ee7b33582d214dab41eeee378d52c8e38ed 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -@@ -58,4 +58,36 @@ public class CraftLlama extends CraftChestedHorse implements Llama, com.destroys - public String toString() { - return "CraftLlama"; - } -+ -+ // Paper start -+ @Override -+ public boolean inCaravan() { -+ return this.getHandle().inCaravan(); -+ } -+ -+ @Override -+ public void joinCaravan(@org.jetbrains.annotations.NotNull Llama llama) { -+ this.getHandle().joinCaravan(((CraftLlama) llama).getHandle()); -+ } -+ -+ @Override -+ public void leaveCaravan() { -+ this.getHandle().leaveCaravan(); -+ } -+ -+ @Override -+ public boolean hasCaravanTail() { -+ return this.getHandle().hasCaravanTail(); -+ } -+ -+ @Override -+ public Llama getCaravanHead() { -+ return this.getHandle().getCaravanHead() == null ? null : (Llama) this.getHandle().getCaravanHead().getBukkitEntity(); -+ } -+ -+ @Override -+ public Llama getCaravanTail() { -+ return this.getHandle().caravanTail == null ? null : (Llama) this.getHandle().caravanTail.getBukkitEntity(); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java -index 17f5684cba9d3ed22d9925d1951520cc4751dfe2..3a3563a1bdbc0d84d973b3a04b50b78b4bc3d379 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java -@@ -33,4 +33,20 @@ public final class CraftMinecartHopper extends CraftMinecartContainer implements - public void setEnabled(boolean enabled) { - ((MinecartHopper) this.getHandle()).setEnabled(enabled); - } -+ // Paper start -+ @Override -+ public net.minecraft.world.entity.vehicle.MinecartHopper getHandle() { -+ return (net.minecraft.world.entity.vehicle.MinecartHopper) super.getHandle(); -+ } -+ -+ @Override -+ public int getPickupCooldown() { -+ throw new UnsupportedOperationException("Hopper minecarts don't have cooldowns"); -+ } -+ -+ @Override -+ public void setPickupCooldown(int cooldown) { -+ throw new UnsupportedOperationException("Hopper minecarts don't have cooldowns"); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -index fc83dde12957e575a4f1d4bee73c320bab95606f..ae430c36ed433e337dd92f197f1717fbf00ac0e1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -@@ -148,4 +148,16 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { - return getHandle().getMaxHeadXRot(); - } - // Paper end -+ -+ // Paper start -+ @Override -+ public boolean isAggressive() { -+ return this.getHandle().isAggressive(); -+ } -+ -+ @Override -+ public void setAggressive(boolean aggressive) { -+ this.getHandle().setAggressive(aggressive); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java -index 77cede9c565a3bc404878c9a4028cadc90f6c010..c20f470bec5292dde7fbdbf3a6562ae12117521d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java -@@ -27,6 +27,43 @@ public class CraftMushroomCow extends CraftCow implements MushroomCow { - this.getHandle().setVariant(net.minecraft.world.entity.animal.MushroomCow.MushroomType.values()[variant.ordinal()]); - } - -+ // Paper start -+ @Override -+ public java.util.List getStewEffects() { -+ if (this.getHandle().stewEffects == null) { -+ return java.util.List.of(); -+ } -+ -+ java.util.List nmsPairs = new java.util.ArrayList<>(this.getHandle().stewEffects.size()); -+ for (final net.minecraft.world.level.block.SuspiciousEffectHolder.EffectEntry effect : this.getHandle().stewEffects) { -+ nmsPairs.add(io.papermc.paper.potion.SuspiciousEffectEntry.create( -+ org.bukkit.craftbukkit.potion.CraftPotionEffectType.minecraftToBukkit(effect.effect()), -+ effect.duration() -+ )); -+ } -+ -+ return java.util.Collections.unmodifiableList(nmsPairs); -+ } -+ -+ @Override -+ public void setStewEffects(final java.util.List effects) { -+ if (effects.isEmpty()) { -+ this.getHandle().stewEffects = null; -+ return; -+ } -+ -+ java.util.List nmsPairs = new java.util.ArrayList<>(effects.size()); -+ for (final io.papermc.paper.potion.SuspiciousEffectEntry effect : effects) { -+ nmsPairs.add(new net.minecraft.world.level.block.SuspiciousEffectHolder.EffectEntry( -+ org.bukkit.craftbukkit.potion.CraftPotionEffectType.bukkitToMinecraft(effect.effect()), -+ effect.duration() -+ )); -+ } -+ -+ this.getHandle().stewEffects = nmsPairs; -+ } -+ // Paper end -+ - @Override - public String toString() { - return "CraftMushroomCow"; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java -index 5467e4a74b70ff57b49d9e6bc686c493178f8511..01d104d91de9e1319d27e39d3f474318c7809486 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java -@@ -41,6 +41,38 @@ public class CraftPanda extends CraftAnimals implements Panda { - this.getHandle().setHiddenGene(CraftPanda.toNms(gene)); - } - -+ // Paper start - Panda API -+ @Override -+ public void setSneezeTicks(int ticks) { -+ this.getHandle().setSneezeCounter(ticks); -+ } -+ -+ @Override -+ public int getSneezeTicks() { -+ return this.getHandle().getSneezeCounter(); -+ } -+ -+ @Override -+ public void setEatingTicks(int ticks) { -+ this.getHandle().setEatCounter(ticks); -+ } -+ -+ @Override -+ public int getEatingTicks() { -+ return this.getHandle().getEatCounter(); -+ } -+ -+ @Override -+ public void setUnhappyTicks(int ticks) { -+ this.getHandle().setUnhappyCounter(ticks); -+ } -+ -+ @Override -+ public Gene getCombinedGene() { -+ return CraftPanda.fromNms(this.getHandle().getVariant()); -+ } -+ // Paper end - Panda API -+ - @Override - public boolean isRolling() { - return this.getHandle().isRolling(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -index 9304e201db1ec96d0916aa8ea781f3e4bc7991e6..8338effd39b1709dbe578e247710a8e58d83e3aa 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -@@ -44,5 +44,25 @@ public class CraftPhantom extends CraftFlying implements Phantom, CraftEnemy { - public void setShouldBurnInDay(boolean shouldBurnInDay) { - getHandle().setShouldBurnInDay(shouldBurnInDay); - } -+ -+ @Override -+ public org.bukkit.Location getAnchorLocation() { -+ net.minecraft.core.BlockPos pos = this.getHandle().anchorPoint; -+ if (pos == null) { -+ return null; -+ } -+ -+ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos); -+ } -+ -+ @Override -+ public void setAnchorLocation(org.bukkit.Location location) { -+ net.minecraft.core.BlockPos pos = null; -+ if (location != null) { -+ pos = io.papermc.paper.util.MCUtil.toBlockPosition(location); -+ } -+ -+ this.getHandle().anchorPoint = pos; -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java -index f5ecb8c1dc92e5a4b123effd2859123b17a586d3..5124a383b60b2c8de89fa992547d0c61db760c21 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java -@@ -84,4 +84,37 @@ public class CraftPiglin extends CraftPiglinAbstract implements Piglin, com.dest - public String toString() { - return "CraftPiglin"; - } -+ // Paper start -+ @Override -+ public void setChargingCrossbow(boolean chargingCrossbow) { -+ this.getHandle().setChargingCrossbow(chargingCrossbow); -+ } -+ -+ @Override -+ public boolean isChargingCrossbow() { -+ return this.getHandle().isChargingCrossbow(); -+ } -+ -+ @Override -+ public void setDancing(boolean dancing) { -+ if (dancing) { -+ this.getHandle().getBrain().setMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING, true); -+ this.getHandle().getBrain().setMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION, this.getHandle().getOnPos()); -+ } else { -+ this.getHandle().getBrain().eraseMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING); -+ this.getHandle().getBrain().eraseMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION); -+ } -+ } -+ -+ @Override -+ public void setDancing(long duration) { -+ this.getHandle().getBrain().setMemoryWithExpiry(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING, true, duration); -+ this.getHandle().getBrain().setMemoryWithExpiry(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION, this.getHandle().getOnPos(), duration); -+ } -+ -+ @Override -+ public boolean isDancing() { -+ return this.getHandle().isDancing(); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java -index c7aec6f28e5d3546235b30f6b1112440a76163c5..fe075cfdf3097d6cb768e71b8cc360abb8eaf367 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java -@@ -17,4 +17,16 @@ public class CraftPolarBear extends CraftAnimals implements PolarBear { - public String toString() { - return "CraftPolarBear"; - } -+ -+ // Paper start -+ @Override -+ public boolean isStanding() { -+ return this.getHandle().isStanding(); -+ } -+ -+ @Override -+ public void setStanding(boolean standing) { -+ this.getHandle().setStanding(standing); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java -index 6b48b117a9cba12aae055c0ea981dfb5bc03a86e..519ef701a7d6534f7cb516f6296b95ee521f661d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java -@@ -29,4 +29,15 @@ public class CraftRabbit extends CraftAnimals implements Rabbit { - public void setRabbitType(Type type) { - this.getHandle().setVariant(net.minecraft.world.entity.animal.Rabbit.Variant.values()[type.ordinal()]); - } -+ // Paper start -+ @Override -+ public void setMoreCarrotTicks(int ticks) { -+ this.getHandle().moreCarrotTicks = ticks; -+ } -+ -+ @Override -+ public int getMoreCarrotTicks() { -+ return this.getHandle().moreCarrotTicks; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java -index cae59f77c704a5b9515dc4917ed5fdc89631ecfb..09796ce15658e3f7c223a265a547a51ee729ed40 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java -@@ -18,4 +18,35 @@ public class CraftRavager extends CraftRaider implements Ravager { - public String toString() { - return "CraftRavager"; - } -+ // Paper start - Missing Entity Behavior -+ @Override -+ public int getAttackTicks() { -+ return this.getHandle().getAttackTick(); -+ } -+ -+ @Override -+ public void setAttackTicks(int ticks) { -+ this.getHandle().attackTick = ticks; -+ } -+ -+ @Override -+ public int getStunnedTicks() { -+ return this.getHandle().getStunnedTick(); -+ } -+ -+ @Override -+ public void setStunnedTicks(int ticks) { -+ this.getHandle().stunnedTick = ticks; -+ } -+ -+ @Override -+ public int getRoarTicks() { -+ return this.getHandle().getRoarTick(); -+ } -+ -+ @Override -+ public void setRoarTicks(int ticks) { -+ this.getHandle().roarTick = ticks; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java -index b8140aa25a25870259b5644091c6643da1e14b54..d4d8ce60098c74508e2de9541bf6534988779764 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java -@@ -3,7 +3,7 @@ package org.bukkit.craftbukkit.entity; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.Salmon; - --public class CraftSalmon extends CraftFish implements Salmon { -+public class CraftSalmon extends io.papermc.paper.entity.PaperSchoolableFish implements Salmon { // Paper - Schooling Fish API - - public CraftSalmon(CraftServer server, net.minecraft.world.entity.animal.Salmon entity) { - super(server, entity); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java -index 3f32c683ddc6999b89f2e4051eb6ae784b296b8f..dac3d34677688ac560bc1be2087a08479ef71b87 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java -@@ -67,4 +67,17 @@ public class CraftTNTPrimed extends CraftEntity implements TNTPrimed { - this.getHandle().owner = null; - } - } -+ -+ // Paper start -+ @Override -+ public void setBlockData(org.bukkit.block.data.BlockData data) { -+ com.google.common.base.Preconditions.checkArgument(data != null, "The visual block data of this tnt cannot be null. To reset it just set to the TNT default block data"); -+ this.getHandle().setBlockState(((org.bukkit.craftbukkit.block.data.CraftBlockData) data).getState()); -+ } -+ -+ @Override -+ public org.bukkit.block.data.BlockData getBlockData() { -+ return org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(this.getHandle().getBlockState()); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java -index 451a9bfd9b9b6945e224f1bb05c7951ed934b4e3..d7c6a0bbc5671ea8f2488230c94df5146a1e98b9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java -@@ -28,4 +28,15 @@ public class CraftTadpole extends CraftFish implements org.bukkit.entity.Tadpole - public void setAge(int age) { - this.getHandle().age = age; - } -+ // Paper start -+ @Override -+ public void setAgeLock(boolean lock) { -+ this.getHandle().ageLocked = lock; -+ } -+ -+ @Override -+ public boolean getAgeLock() { -+ return this.getHandle().ageLocked; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -index 0fa00ae2b8a84e1ba5a902c1e46e561a761c54b6..20f9735c7cb76024e15dbdca7684f5c560876175 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -@@ -31,4 +31,27 @@ public class CraftTrident extends CraftArrow implements Trident { - public String toString() { - return "CraftTrident"; - } -+ -+ // Paper start -+ @Override -+ public boolean hasGlint() { -+ return this.getHandle().isFoil(); -+ } -+ -+ @Override -+ public void setGlint(boolean glint) { -+ this.getHandle().setFoil(glint); -+ } -+ -+ @Override -+ public int getLoyaltyLevel() { -+ return this.getHandle().getLoyalty(); -+ } -+ -+ @Override -+ public void setLoyaltyLevel(int loyaltyLevel) { -+ com.google.common.base.Preconditions.checkArgument(loyaltyLevel >= 0 && loyaltyLevel <= 127, "The loyalty level has to be between 0 and 127"); -+ this.getHandle().setLoyalty((byte) loyaltyLevel); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java -index e3bde6d1c0e03407af1382a61748470063bb2e18..9e53c30801c700719c78c0fd521fd615c94e02c8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java -@@ -7,7 +7,7 @@ import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.TropicalFish; - import org.bukkit.entity.TropicalFish.Pattern; - --public class CraftTropicalFish extends CraftFish implements TropicalFish { -+public class CraftTropicalFish extends io.papermc.paper.entity.PaperSchoolableFish implements TropicalFish { // Paper - Schooling Fish API - - public CraftTropicalFish(CraftServer server, net.minecraft.world.entity.animal.TropicalFish entity) { - super(server, entity); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java -index 1cfbe9c476f4a254edf3edf4b70696bbaba78558..e9ec3455eabc473e104b5342a615a38c1ac25a4f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java -@@ -29,6 +29,26 @@ public class CraftVex extends CraftMonster implements Vex { - public void setSummoner(org.bukkit.entity.Mob summoner) { - getHandle().setOwner(summoner == null ? null : ((CraftMob) summoner).getHandle()); - } -+ -+ @Override -+ public boolean hasLimitedLifetime() { -+ return this.getHandle().hasLimitedLife; -+ } -+ -+ @Override -+ public void setLimitedLifetime(boolean hasLimitedLifetime) { -+ this.getHandle().hasLimitedLife = hasLimitedLifetime; -+ } -+ -+ @Override -+ public int getLimitedLifetimeTicks() { -+ return this.getHandle().limitedLifeTicks; -+ } -+ -+ @Override -+ public void setLimitedLifetimeTicks(int ticks) { -+ this.getHandle().limitedLifeTicks = ticks; -+ } - // Paper end - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java -index e2a0c11867abee6add8775259c54f2052de7b1ad..3aa23d9f22d5cd22231293fd7d1ca4cb79eb7cb3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java -@@ -60,13 +60,20 @@ public class CraftVillagerZombie extends CraftZombie implements ZombieVillager { - - @Override - public void setConversionTime(int time) { -+ // Paper start - missing entity behaviour api - converting without entity event -+ this.setConversionTime(time, true); -+ } -+ -+ @Override -+ public void setConversionTime(int time, boolean broadcastEntityEvent) { -+ // Paper end - missing entity behaviour api - converting without entity event - if (time < 0) { - this.getHandle().villagerConversionTime = -1; - this.getHandle().getEntityData().set(net.minecraft.world.entity.monster.ZombieVillager.DATA_CONVERTING_ID, false); - this.getHandle().conversionStarter = null; - this.getHandle().removeEffect(MobEffects.DAMAGE_BOOST, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); - } else { -- this.getHandle().startConverting(null, time); -+ this.getHandle().startConverting(null, time, broadcastEntityEvent); // Paper - missing entity behaviour api - converting without entity event - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -index 0e597394a3dd08f022614fc9777302fea581eb55..3cceefa0d6278924a19641a49bdf16bcdacb2233 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -@@ -49,5 +49,25 @@ public class CraftWanderingTrader extends CraftAbstractVillager implements Wande - public boolean canDrinkMilk() { - return getHandle().canDrinkMilk; - } -+ -+ @Override -+ public org.bukkit.Location getWanderingTowards() { -+ net.minecraft.core.BlockPos pos = this.getHandle().getWanderTarget(); -+ if (pos == null) { -+ return null; -+ } -+ -+ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos); -+ } -+ -+ @Override -+ public void setWanderingTowards(org.bukkit.Location location) { -+ net.minecraft.core.BlockPos pos = null; -+ if (location != null) { -+ pos = io.papermc.paper.util.MCUtil.toBlockPosition(location); -+ } -+ -+ this.getHandle().setWanderTarget(pos); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java -index 794e4fe0a3fbd967f665b2707865c15491370c76..c284eb96a1e330078076cbe61f0f6e2ff4ed89bd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java -@@ -37,6 +37,13 @@ public class CraftWarden extends CraftMonster implements org.bukkit.entity.Warde - return this.getHandle().getAngerManagement().getActiveAnger(((CraftEntity) entity).getHandle()); - } - -+ // Paper start -+ @Override -+ public int getHighestAnger() { -+ return this.getHandle().getAngerManagement().getActiveAnger(null); -+ } -+ // Paper end -+ - @Override - public void increaseAnger(Entity entity, int increase) { - Preconditions.checkArgument(entity != null, "Entity cannot be null"); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -index 1113533d281ed159bb735040fb1f913482debf3a..7a8ce6956db56061af93ba9761f5d1057a90bc49 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -@@ -67,4 +67,36 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok - - this.getHandle().setInvulnerableTicks(ticks); - } -+ -+ // Paper start -+ @Override -+ public boolean isCharged() { -+ return getHandle().isPowered(); -+ } -+ -+ @Override -+ public int getInvulnerableTicks() { -+ return getHandle().getInvulnerableTicks(); -+ } -+ -+ @Override -+ public void setInvulnerableTicks(int ticks) { -+ getHandle().setInvulnerableTicks(ticks); -+ } -+ -+ @Override -+ public boolean canTravelThroughPortals() { -+ return getHandle().canChangeDimensions(); -+ } -+ -+ @Override -+ public void setCanTravelThroughPortals(boolean value) { -+ getHandle().setCanTravelThroughPortals(value); -+ } -+ -+ @Override -+ public void enterInvulnerabilityPhase() { -+ this.getHandle().makeInvulnerable(); -+ } -+ // Paper end - } diff --git a/patches/server/0589-Ensure-disconnect-for-book-edit-is-called-on-main.patch b/patches/server/0589-Ensure-disconnect-for-book-edit-is-called-on-main.patch deleted file mode 100644 index 8ed00ac7e8b4..000000000000 --- a/patches/server/0589-Ensure-disconnect-for-book-edit-is-called-on-main.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Tue, 22 Jun 2021 19:58:53 +0100 -Subject: [PATCH] Ensure disconnect for book edit is called on main - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 1dcc0852bcaf44efaa9ff1e63560ddb9968a494a..a01af1e82d3a68da1016b440181c298c2f86fa06 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1096,7 +1096,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // Paper end - Book size limits - // CraftBukkit start - if (this.lastBookTick + 20 > MinecraftServer.currentTick) { -- this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause -+ server.scheduleOnMain(() -> this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause // Paper - Also ensure this is called on main - return; - } - this.lastBookTick = MinecraftServer.currentTick; diff --git a/patches/server/0590-Fix-return-value-of-Block-applyBoneMeal-always-being.patch b/patches/server/0589-Fix-return-value-of-Block-applyBoneMeal-always-being.patch similarity index 100% rename from patches/server/0590-Fix-return-value-of-Block-applyBoneMeal-always-being.patch rename to patches/server/0589-Fix-return-value-of-Block-applyBoneMeal-always-being.patch diff --git a/patches/server/0591-Use-getChunkIfLoadedImmediately-in-places.patch b/patches/server/0590-Use-getChunkIfLoadedImmediately-in-places.patch similarity index 100% rename from patches/server/0591-Use-getChunkIfLoadedImmediately-in-places.patch rename to patches/server/0590-Use-getChunkIfLoadedImmediately-in-places.patch diff --git a/patches/server/0592-Fix-commands-from-signs-not-firing-command-events.patch b/patches/server/0591-Fix-commands-from-signs-not-firing-command-events.patch similarity index 100% rename from patches/server/0592-Fix-commands-from-signs-not-firing-command-events.patch rename to patches/server/0591-Fix-commands-from-signs-not-firing-command-events.patch diff --git a/patches/server/0592-Add-PlayerArmSwingEvent.patch b/patches/server/0592-Add-PlayerArmSwingEvent.patch new file mode 100644 index 000000000000..a2db1e148cac --- /dev/null +++ b/patches/server/0592-Add-PlayerArmSwingEvent.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 12 Mar 2021 19:22:21 -0800 +Subject: [PATCH] Add PlayerArmSwingEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 6f4c8b6ac6f56d183796deaa0d3b5a23241e8348..c0f29e612a69b33bd79d05c472fe72529a6fd14b 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2328,7 +2328,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } // Paper end - Call interact event + + // Arm swing animation +- PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == InteractionHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING); ++ io.papermc.paper.event.player.PlayerArmSwingEvent event = new io.papermc.paper.event.player.PlayerArmSwingEvent(this.getCraftPlayer(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(packet.getHand())); // Paper - Add PlayerArmSwingEvent + this.cserver.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; diff --git a/patches/server/0593-Add-PlayerArmSwingEvent.patch b/patches/server/0593-Add-PlayerArmSwingEvent.patch deleted file mode 100644 index 99a1f5af8aa2..000000000000 --- a/patches/server/0593-Add-PlayerArmSwingEvent.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 12 Mar 2021 19:22:21 -0800 -Subject: [PATCH] Add PlayerArmSwingEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index a01af1e82d3a68da1016b440181c298c2f86fa06..e56506562976b305568f15a554204919f6e34ad8 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2328,7 +2328,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } // Paper end - Call interact event - - // Arm swing animation -- PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == InteractionHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING); -+ io.papermc.paper.event.player.PlayerArmSwingEvent event = new io.papermc.paper.event.player.PlayerArmSwingEvent(this.getCraftPlayer(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(packet.getHand())); // Paper - Add PlayerArmSwingEvent - this.cserver.getPluginManager().callEvent(event); - - if (event.isCancelled()) return; diff --git a/patches/server/0593-Fix-kick-event-leave-message-not-being-sent.patch b/patches/server/0593-Fix-kick-event-leave-message-not-being-sent.patch new file mode 100644 index 000000000000..58972f4e998f --- /dev/null +++ b/patches/server/0593-Fix-kick-event-leave-message-not-being-sent.patch @@ -0,0 +1,113 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 7 Jul 2021 16:19:41 -0700 +Subject: [PATCH] Fix kick event leave message not being sent + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index d0369b9db86dc3436e6a016f138f2ffe91da6ed4..7fab0411fb7d322bf5f201e44b747d8a00638d5d 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -268,7 +268,6 @@ public class ServerPlayer extends Player { + public boolean joining = true; + public boolean sentListPacket = false; + public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready +- public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent + // CraftBukkit end + public boolean isRealPlayer; // Paper + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper +diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index a65a1466dab52fca75cda16a4b22fef03b6207a0..0306771b8f90dcdd77f151c19c6c2d75c41f8feb 100644 +--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -77,6 +77,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + + @Override + public void onDisconnect(Component reason) { ++ // Paper start - Fix kick event leave message not being sent ++ this.onDisconnect(reason, null); ++ } ++ public void onDisconnect(Component reason, @Nullable net.kyori.adventure.text.Component quitMessage) { ++ // Paper end - Fix kick event leave message not being sent + if (this.isSingleplayerOwner()) { + ServerCommonPacketListenerImpl.LOGGER.info("Stopping singleplayer server as player logged out"); + this.server.halt(false); +@@ -320,7 +325,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + // Do not kick the player + return; + } +- this.player.kickLeaveMessage = event.getLeaveMessage(); // CraftBukkit - SPIGOT-3034: Forward leave message to PlayerQuitEvent + // Send the possibly modified leave message + final Component ichatbasecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()); // Paper - Adventure + // CraftBukkit end +@@ -329,7 +333,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), PacketSendListener.thenRun(() -> { + this.connection.disconnect(ichatbasecomponent); + })); +- this.onDisconnect(ichatbasecomponent); // CraftBukkit - fire quit instantly ++ this.onDisconnect(ichatbasecomponent, event.leaveMessage()); // CraftBukkit - fire quit instantly // Paper - use kick event leave message + this.connection.setReadOnly(); + MinecraftServer minecraftserver = this.server; + Connection networkmanager = this.connection; +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index c0f29e612a69b33bd79d05c472fe72529a6fd14b..8b6f37463e35162d4b228b732a0283d57dc2f1b9 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1882,6 +1882,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + @Override + public void onDisconnect(Component reason) { ++ // Paper start - Fix kick event leave message not being sent ++ this.onDisconnect(reason, null); ++ } ++ @Override ++ public void onDisconnect(Component reason, @Nullable net.kyori.adventure.text.Component quitMessage) { ++ // Paper end - Fix kick event leave message not being sent + // CraftBukkit start - Rarely it would send a disconnect line twice + if (this.processedDisconnect) { + return; +@@ -1890,11 +1896,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + // CraftBukkit end + ServerGamePacketListenerImpl.LOGGER.info("{} lost connection: {}", this.player.getName().getString(), reason.getString()); +- this.removePlayerFromWorld(); +- super.onDisconnect(reason); ++ this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent ++ super.onDisconnect(reason, quitMessage); // Paper - Fix kick event leave message not being sent + } + ++ // Paper start - Fix kick event leave message not being sent + private void removePlayerFromWorld() { ++ this.removePlayerFromWorld(null); ++ } ++ ++ private void removePlayerFromWorld(@Nullable net.kyori.adventure.text.Component quitMessage) { ++ // Paper end - Fix kick event leave message not being sent + this.chatMessageChain.close(); + // CraftBukkit start - Replace vanilla quit message handling with our own. + /* +@@ -1904,7 +1916,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + this.player.disconnect(); + // Paper start - Adventure +- net.kyori.adventure.text.Component quitMessage = this.server.getPlayerList().remove(this.player); ++ quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used + if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) { + this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false); + // Paper end +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 1f3f316cd1946c4a0e1ba767a93beec7eb9f3f2b..569d1f1682b9c785701fbb04683fea880504c94c 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -568,6 +568,11 @@ public abstract class PlayerList { + } + + 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()))); ++ } ++ public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { ++ // Paper end - Fix kick event leave message not being sent + ServerLevel worldserver = entityplayer.serverLevel(); + + entityplayer.awardStat(Stats.LEAVE_GAME); diff --git a/patches/server/0594-Add-config-for-mobs-immune-to-default-effects.patch b/patches/server/0594-Add-config-for-mobs-immune-to-default-effects.patch new file mode 100644 index 000000000000..7eb0b594c208 --- /dev/null +++ b/patches/server/0594-Add-config-for-mobs-immune-to-default-effects.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Dec 2020 21:03:02 -0800 +Subject: [PATCH] Add config for mobs immune to default effects + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 78befbf1e5f506c9dfd703c3e796742fe17d13d7..fadbb788bff1dc1c643ffbb28774d20ba6d55ce5 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1164,7 +1164,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (this.getMobType() == MobType.UNDEAD) { + MobEffect mobeffectlist = effect.getEffect(); + +- if (mobeffectlist == MobEffects.REGENERATION || mobeffectlist == MobEffects.POISON) { ++ if ((mobeffectlist == MobEffects.REGENERATION || mobeffectlist == MobEffects.POISON) && this.level().paperConfig().entities.mobEffects.undeadImmuneToCertainEffects) { // Paper - Add config for mobs immune to default effects + return false; + } + } +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index 8241dbf7591b2f56b25cdc3ce9009c7133d2e4ef..0a2c2b847dc516abf31870116056dbdbb22f31d9 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -606,7 +606,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob + + @Override + public boolean canBeAffected(MobEffectInstance effect) { +- return effect.getEffect() == MobEffects.WITHER ? false : super.canBeAffected(effect); ++ return effect.getEffect() == MobEffects.WITHER && this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.wither ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects + } + + private class WitherDoNothingGoal extends Goal { +diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java +index 6241baccd3fdee59175f616cdf69d3873074f855..4d5cfaa58bdf4e6cb975134004d14c591f6e85fa 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Spider.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java +@@ -135,7 +135,7 @@ public class Spider extends Monster { + + @Override + public boolean canBeAffected(MobEffectInstance effect) { +- return effect.getEffect() == MobEffects.POISON ? false : super.canBeAffected(effect); ++ return effect.getEffect() == MobEffects.POISON && this.level().paperConfig().entities.mobEffects.spidersImmuneToPoisonEffect ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects + } + + public boolean isClimbing() { +diff --git a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java +index 62943d43b701d9ae6d955003f4e7658f76d5bdb3..20a65c11ededcd7170704b70118da6200151fbab 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java +@@ -128,6 +128,6 @@ public class WitherSkeleton extends AbstractSkeleton { + + @Override + public boolean canBeAffected(MobEffectInstance effect) { +- return effect.getEffect() == MobEffects.WITHER ? false : super.canBeAffected(effect); ++ return effect.getEffect() == MobEffects.WITHER && this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.witherSkeleton ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects + } + } diff --git a/patches/server/0594-Fix-kick-event-leave-message-not-being-sent.patch b/patches/server/0594-Fix-kick-event-leave-message-not-being-sent.patch deleted file mode 100644 index a1233e3e595d..000000000000 --- a/patches/server/0594-Fix-kick-event-leave-message-not-being-sent.patch +++ /dev/null @@ -1,113 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 7 Jul 2021 16:19:41 -0700 -Subject: [PATCH] Fix kick event leave message not being sent - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 12d3809792384643e550b34e59c58d49869ec05d..33829587797f9bb6efccb0e5237b2aab020f837e 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -268,7 +268,6 @@ public class ServerPlayer extends Player { - public boolean joining = true; - public boolean sentListPacket = false; - public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready -- public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent - // CraftBukkit end - public boolean isRealPlayer; // Paper - public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index a65a1466dab52fca75cda16a4b22fef03b6207a0..0306771b8f90dcdd77f151c19c6c2d75c41f8feb 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -77,6 +77,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - - @Override - public void onDisconnect(Component reason) { -+ // Paper start - Fix kick event leave message not being sent -+ this.onDisconnect(reason, null); -+ } -+ public void onDisconnect(Component reason, @Nullable net.kyori.adventure.text.Component quitMessage) { -+ // Paper end - Fix kick event leave message not being sent - if (this.isSingleplayerOwner()) { - ServerCommonPacketListenerImpl.LOGGER.info("Stopping singleplayer server as player logged out"); - this.server.halt(false); -@@ -320,7 +325,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - // Do not kick the player - return; - } -- this.player.kickLeaveMessage = event.getLeaveMessage(); // CraftBukkit - SPIGOT-3034: Forward leave message to PlayerQuitEvent - // Send the possibly modified leave message - final Component ichatbasecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()); // Paper - Adventure - // CraftBukkit end -@@ -329,7 +333,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), PacketSendListener.thenRun(() -> { - this.connection.disconnect(ichatbasecomponent); - })); -- this.onDisconnect(ichatbasecomponent); // CraftBukkit - fire quit instantly -+ this.onDisconnect(ichatbasecomponent, event.leaveMessage()); // CraftBukkit - fire quit instantly // Paper - use kick event leave message - this.connection.setReadOnly(); - MinecraftServer minecraftserver = this.server; - Connection networkmanager = this.connection; -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index e56506562976b305568f15a554204919f6e34ad8..de4dc29d9cbdb739465f5df815b1e939a2bffee1 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1882,6 +1882,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - @Override - public void onDisconnect(Component reason) { -+ // Paper start - Fix kick event leave message not being sent -+ this.onDisconnect(reason, null); -+ } -+ @Override -+ public void onDisconnect(Component reason, @Nullable net.kyori.adventure.text.Component quitMessage) { -+ // Paper end - Fix kick event leave message not being sent - // CraftBukkit start - Rarely it would send a disconnect line twice - if (this.processedDisconnect) { - return; -@@ -1890,11 +1896,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - // CraftBukkit end - ServerGamePacketListenerImpl.LOGGER.info("{} lost connection: {}", this.player.getName().getString(), reason.getString()); -- this.removePlayerFromWorld(); -- super.onDisconnect(reason); -+ this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent -+ super.onDisconnect(reason, quitMessage); // Paper - Fix kick event leave message not being sent - } - -+ // Paper start - Fix kick event leave message not being sent - private void removePlayerFromWorld() { -+ this.removePlayerFromWorld(null); -+ } -+ -+ private void removePlayerFromWorld(@Nullable net.kyori.adventure.text.Component quitMessage) { -+ // Paper end - Fix kick event leave message not being sent - this.chatMessageChain.close(); - // CraftBukkit start - Replace vanilla quit message handling with our own. - /* -@@ -1904,7 +1916,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - this.player.disconnect(); - // Paper start - Adventure -- net.kyori.adventure.text.Component quitMessage = this.server.getPlayerList().remove(this.player); -+ quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used - if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) { - this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false); - // Paper end -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 1f3f316cd1946c4a0e1ba767a93beec7eb9f3f2b..569d1f1682b9c785701fbb04683fea880504c94c 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -568,6 +568,11 @@ public abstract class PlayerList { - } - - 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()))); -+ } -+ public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { -+ // Paper end - Fix kick event leave message not being sent - ServerLevel worldserver = entityplayer.serverLevel(); - - entityplayer.awardStat(Stats.LEAVE_GAME); diff --git a/patches/server/0595-Add-config-for-mobs-immune-to-default-effects.patch b/patches/server/0595-Add-config-for-mobs-immune-to-default-effects.patch deleted file mode 100644 index 3e3752992585..000000000000 --- a/patches/server/0595-Add-config-for-mobs-immune-to-default-effects.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 2 Dec 2020 21:03:02 -0800 -Subject: [PATCH] Add config for mobs immune to default effects - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index aab173a8c36f8f03cfad84a69b9a34bd19369649..8dcebc2b6e8baf4ed5f269a1b9cec9e5cd754047 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1163,7 +1163,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (this.getMobType() == MobType.UNDEAD) { - MobEffect mobeffectlist = effect.getEffect(); - -- if (mobeffectlist == MobEffects.REGENERATION || mobeffectlist == MobEffects.POISON) { -+ if ((mobeffectlist == MobEffects.REGENERATION || mobeffectlist == MobEffects.POISON) && this.level().paperConfig().entities.mobEffects.undeadImmuneToCertainEffects) { // Paper - Add config for mobs immune to default effects - return false; - } - } -diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 8241dbf7591b2f56b25cdc3ce9009c7133d2e4ef..0a2c2b847dc516abf31870116056dbdbb22f31d9 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -606,7 +606,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - - @Override - public boolean canBeAffected(MobEffectInstance effect) { -- return effect.getEffect() == MobEffects.WITHER ? false : super.canBeAffected(effect); -+ return effect.getEffect() == MobEffects.WITHER && this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.wither ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects - } - - private class WitherDoNothingGoal extends Goal { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java -index 6241baccd3fdee59175f616cdf69d3873074f855..4d5cfaa58bdf4e6cb975134004d14c591f6e85fa 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Spider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java -@@ -135,7 +135,7 @@ public class Spider extends Monster { - - @Override - public boolean canBeAffected(MobEffectInstance effect) { -- return effect.getEffect() == MobEffects.POISON ? false : super.canBeAffected(effect); -+ return effect.getEffect() == MobEffects.POISON && this.level().paperConfig().entities.mobEffects.spidersImmuneToPoisonEffect ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects - } - - public boolean isClimbing() { -diff --git a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -index 62943d43b701d9ae6d955003f4e7658f76d5bdb3..20a65c11ededcd7170704b70118da6200151fbab 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -@@ -128,6 +128,6 @@ public class WitherSkeleton extends AbstractSkeleton { - - @Override - public boolean canBeAffected(MobEffectInstance effect) { -- return effect.getEffect() == MobEffects.WITHER ? false : super.canBeAffected(effect); -+ return effect.getEffect() == MobEffects.WITHER && this.level().paperConfig().entities.mobEffects.immuneToWitherEffect.witherSkeleton ? false : super.canBeAffected(effect); // Paper - Add config for mobs immune to default effects - } - } diff --git a/patches/server/0595-Don-t-apply-cramming-damage-to-players.patch b/patches/server/0595-Don-t-apply-cramming-damage-to-players.patch new file mode 100644 index 000000000000..0882572dff99 --- /dev/null +++ b/patches/server/0595-Don-t-apply-cramming-damage-to-players.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sun, 20 Jun 2021 16:35:42 +0100 +Subject: [PATCH] Don't apply cramming damage to players + +It does not make a lot of sense to damage players if they get crammed, + especially as the usecase of teleporting lots of players to the same + location isn't too uncommon and killing all those players isn't + really what one would expect to happen. + +For those who really want it a config option is provided. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 7fab0411fb7d322bf5f201e44b747d8a00638d5d..751216261df86402c23d3f0d73944ae51e849caa 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -95,6 +95,7 @@ import net.minecraft.util.Mth; + import net.minecraft.util.RandomSource; + import net.minecraft.util.Unit; + import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.damagesource.DamageSources; + import net.minecraft.world.effect.MobEffectInstance; + import net.minecraft.world.effect.MobEffects; + import net.minecraft.world.entity.Entity; +@@ -1430,7 +1431,7 @@ public class ServerPlayer extends Player { + + @Override + public boolean isInvulnerableTo(DamageSource damageSource) { +- return super.isInvulnerableTo(damageSource) || this.isChangingDimension(); ++ return super.isInvulnerableTo(damageSource) || this.isChangingDimension() || !this.level().paperConfig().collisions.allowPlayerCrammingDamage && damageSource == damageSources().cramming(); // Paper - disable player cramming + } + + @Override diff --git a/patches/server/0596-Don-t-apply-cramming-damage-to-players.patch b/patches/server/0596-Don-t-apply-cramming-damage-to-players.patch deleted file mode 100644 index b85c69d7d77e..000000000000 --- a/patches/server/0596-Don-t-apply-cramming-damage-to-players.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Sun, 20 Jun 2021 16:35:42 +0100 -Subject: [PATCH] Don't apply cramming damage to players - -It does not make a lot of sense to damage players if they get crammed, - especially as the usecase of teleporting lots of players to the same - location isn't too uncommon and killing all those players isn't - really what one would expect to happen. - -For those who really want it a config option is provided. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 33829587797f9bb6efccb0e5237b2aab020f837e..974874a6e72b65f956ebb5605547e5d1be74d2e8 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -95,6 +95,7 @@ import net.minecraft.util.Mth; - import net.minecraft.util.RandomSource; - import net.minecraft.util.Unit; - import net.minecraft.world.damagesource.DamageSource; -+import net.minecraft.world.damagesource.DamageSources; - import net.minecraft.world.effect.MobEffectInstance; - import net.minecraft.world.effect.MobEffects; - import net.minecraft.world.entity.Entity; -@@ -1430,7 +1431,7 @@ public class ServerPlayer extends Player { - - @Override - public boolean isInvulnerableTo(DamageSource damageSource) { -- return super.isInvulnerableTo(damageSource) || this.isChangingDimension(); -+ return super.isInvulnerableTo(damageSource) || this.isChangingDimension() || !this.level().paperConfig().collisions.allowPlayerCrammingDamage && damageSource == damageSources().cramming(); // Paper - disable player cramming - } - - @Override diff --git a/patches/server/0597-Rate-options-and-timings-for-sensors-and-behaviors.patch b/patches/server/0596-Rate-options-and-timings-for-sensors-and-behaviors.patch similarity index 100% rename from patches/server/0597-Rate-options-and-timings-for-sensors-and-behaviors.patch rename to patches/server/0596-Rate-options-and-timings-for-sensors-and-behaviors.patch diff --git a/patches/server/0598-Add-missing-forceDrop-toggles.patch b/patches/server/0597-Add-missing-forceDrop-toggles.patch similarity index 100% rename from patches/server/0598-Add-missing-forceDrop-toggles.patch rename to patches/server/0597-Add-missing-forceDrop-toggles.patch diff --git a/patches/server/0598-Stinger-API.patch b/patches/server/0598-Stinger-API.patch new file mode 100644 index 000000000000..a1a70741b4bb --- /dev/null +++ b/patches/server/0598-Stinger-API.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Tue, 22 Jun 2021 23:15:44 -0400 +Subject: [PATCH] Stinger API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 8eb4b6c2752d68b866eab64263ede1d449ee2458..f288efe62c7280189359bba749a2dc3ec3f6ef49 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -338,6 +338,11 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } + // Paper end + } ++ // Paper start - Bee Stinger API ++ @Override ++ public int getBeeStingerCooldown() { ++ return getHandle().removeStingerTime; ++ } + + // Paper start - Add methods for working with arrows stuck in living entities + @Override +@@ -352,6 +357,34 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } + // Paper end - Add methods for working with arrows stuck in living entities + ++ @Override ++ public void setBeeStingerCooldown(int ticks) { ++ getHandle().removeStingerTime = ticks; ++ } ++ ++ @Override ++ public int getBeeStingersInBody() { ++ return getHandle().getStingerCount(); ++ } ++ ++ @Override ++ public void setBeeStingersInBody(int count) { ++ Preconditions.checkArgument(count >= 0, "New bee stinger amount must be >= 0"); ++ getHandle().setStingerCount(count); ++ } ++ ++ @Override ++ public void setNextBeeStingerRemoval(final int ticks) { ++ Preconditions.checkArgument(ticks >= 0, "New amount of ticks before next bee stinger removal must be >= 0"); ++ this.getHandle().removeStingerTime = ticks; ++ } ++ ++ @Override ++ public int getNextBeeStingerRemoval() { ++ return this.getHandle().removeStingerTime; ++ } ++ // Paper end - Bee Stinger API ++ + @Override + public void damage(double amount) { + this.damage(amount, this.getHandle().damageSources().generic()); diff --git a/patches/server/0600-Fix-incosistency-issue-with-empty-map-items-in-CB.patch b/patches/server/0599-Fix-incosistency-issue-with-empty-map-items-in-CB.patch similarity index 100% rename from patches/server/0600-Fix-incosistency-issue-with-empty-map-items-in-CB.patch rename to patches/server/0599-Fix-incosistency-issue-with-empty-map-items-in-CB.patch diff --git a/patches/server/0599-Stinger-API.patch b/patches/server/0599-Stinger-API.patch deleted file mode 100644 index 499d7d5c104c..000000000000 --- a/patches/server/0599-Stinger-API.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Tue, 22 Jun 2021 23:15:44 -0400 -Subject: [PATCH] Stinger API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 5effef8e6a360b4d6910be9f4b7a5363d38675ed..33d5e64fa76ef789a51197e5ec3b5ebbcb82b8c5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -337,6 +337,11 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } - // Paper end - } -+ // Paper start - Bee Stinger API -+ @Override -+ public int getBeeStingerCooldown() { -+ return getHandle().removeStingerTime; -+ } - - // Paper start - Add methods for working with arrows stuck in living entities - @Override -@@ -351,6 +356,34 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } - // Paper end - Add methods for working with arrows stuck in living entities - -+ @Override -+ public void setBeeStingerCooldown(int ticks) { -+ getHandle().removeStingerTime = ticks; -+ } -+ -+ @Override -+ public int getBeeStingersInBody() { -+ return getHandle().getStingerCount(); -+ } -+ -+ @Override -+ public void setBeeStingersInBody(int count) { -+ Preconditions.checkArgument(count >= 0, "New bee stinger amount must be >= 0"); -+ getHandle().setStingerCount(count); -+ } -+ -+ @Override -+ public void setNextBeeStingerRemoval(final int ticks) { -+ Preconditions.checkArgument(ticks >= 0, "New amount of ticks before next bee stinger removal must be >= 0"); -+ this.getHandle().removeStingerTime = ticks; -+ } -+ -+ @Override -+ public int getNextBeeStingerRemoval() { -+ return this.getHandle().removeStingerTime; -+ } -+ // Paper end - Bee Stinger API -+ - @Override - public void damage(double amount) { - this.damage(amount, null); diff --git a/patches/server/0600-Add-System.out-err-catcher.patch b/patches/server/0600-Add-System.out-err-catcher.patch new file mode 100644 index 000000000000..c9b168845244 --- /dev/null +++ b/patches/server/0600-Add-System.out-err-catcher.patch @@ -0,0 +1,118 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: underscore11code +Date: Fri, 23 Jul 2021 23:01:42 -0700 +Subject: [PATCH] Add System.out/err catcher + + +diff --git a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a8e813ca89b033f061e695288b3383bdcf128531 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java +@@ -0,0 +1,94 @@ ++package io.papermc.paper.logging; ++ ++import org.bukkit.Bukkit; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.io.OutputStream; ++import java.io.PrintStream; ++import java.util.Objects; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.ConcurrentMap; ++import java.util.concurrent.TimeUnit; ++import java.util.logging.Level; ++ ++public final class SysoutCatcher { ++ private static final boolean SUPPRESS_NAGS = Boolean.getBoolean("io.papermc.paper.suppress.sout.nags"); ++ // Nanoseconds between nag at most; if interval is caught first, this is reset. ++ // <= 0 for disabling. ++ private static final long NAG_TIMEOUT = TimeUnit.MILLISECONDS.toNanos( ++ Long.getLong("io.papermc.paper.sout.nags.timeout", TimeUnit.MINUTES.toMillis(5L))); ++ // Count since last nag; if timeout is first, this is reset. ++ // <= 0 for disabling. ++ private static final long NAG_INTERVAL = Long.getLong("io.papermc.paper.sout.nags.interval", 200L); ++ ++ // We don't particularly care about how correct this is at any given moment; let's do it on a best attempt basis. ++ // The records are also pretty small, so let's just go for a size of 64 to start... ++ // ++ // Content: Plugin name => nag object ++ // Why plugin name?: This doesn't store a reference to the plugin; keeps the reload ability. ++ // Why not clean on reload?: Effort. ++ private final ConcurrentMap nagRecords = new ConcurrentHashMap<>(64); ++ ++ public SysoutCatcher() { ++ System.setOut(new WrappedOutStream(System.out, Level.INFO, "[STDOUT] ")); ++ System.setErr(new WrappedOutStream(System.err, Level.SEVERE, "[STDERR] ")); ++ } ++ ++ private final class WrappedOutStream extends PrintStream { ++ private static final StackWalker STACK_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); ++ private final Level level; ++ private final String prefix; ++ ++ public WrappedOutStream(@NotNull final OutputStream out, final Level level, final String prefix) { ++ super(out); ++ this.level = level; ++ this.prefix = prefix; ++ } ++ ++ @Override ++ public void println(@Nullable final String line) { ++ final Class clazz = STACK_WALKER.getCallerClass(); ++ try { ++ final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(clazz); ++ ++ // Instead of just printing the message, send it to the plugin's logger ++ plugin.getLogger().log(this.level, this.prefix + line); ++ ++ if (SysoutCatcher.SUPPRESS_NAGS) { ++ return; ++ } ++ if (SysoutCatcher.NAG_INTERVAL > 0 || SysoutCatcher.NAG_TIMEOUT > 0) { ++ final PluginNag nagRecord = SysoutCatcher.this.nagRecords.computeIfAbsent(plugin.getName(), k -> new PluginNag()); ++ final boolean hasTimePassed = SysoutCatcher.NAG_TIMEOUT > 0 ++ && (nagRecord.lastNagTimestamp == Long.MIN_VALUE ++ || nagRecord.lastNagTimestamp + SysoutCatcher.NAG_TIMEOUT <= System.nanoTime()); ++ final boolean hasMessagesPassed = SysoutCatcher.NAG_INTERVAL > 0 ++ && (nagRecord.messagesSinceNag == Long.MIN_VALUE ++ || ++nagRecord.messagesSinceNag >= SysoutCatcher.NAG_INTERVAL); ++ if (!hasMessagesPassed && !hasTimePassed) { ++ return; ++ } ++ nagRecord.lastNagTimestamp = System.nanoTime(); ++ nagRecord.messagesSinceNag = 0; ++ } ++ Bukkit.getLogger().warning( ++ String.format("Nag author(s): '%s' of '%s' about their usage of System.out/err.print. " ++ + "Please use your plugin's logger instead (JavaPlugin#getLogger).", ++ plugin.getPluginMeta().getAuthors(), ++ plugin.getPluginMeta().getDisplayName()) ++ ); ++ } catch (final IllegalArgumentException | IllegalStateException e) { ++ // If anything happens, the calling class doesn't exist, there is no JavaPlugin that "owns" the calling class, etc ++ // Just print out normally, with some added information ++ Bukkit.getLogger().log(this.level, String.format("%s[%s] %s", this.prefix, clazz.getName(), line)); ++ } ++ } ++ } ++ ++ private static class PluginNag { ++ private long lastNagTimestamp = Long.MIN_VALUE; ++ private long messagesSinceNag = Long.MIN_VALUE; ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 236e73eed6caac7f98236ca418185a143f78c5fa..51337b1b2e74a67ad54c5d594004b649cb6af4ed 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -305,6 +305,7 @@ public final class CraftServer implements Server { + public int reloadCount; + private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper + public static Exception excessiveVelEx; // Paper - Velocity warnings ++ private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper + + static { + ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); diff --git a/patches/server/0601-Add-System.out-err-catcher.patch b/patches/server/0601-Add-System.out-err-catcher.patch deleted file mode 100644 index 56f9ba7be563..000000000000 --- a/patches/server/0601-Add-System.out-err-catcher.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: underscore11code -Date: Fri, 23 Jul 2021 23:01:42 -0700 -Subject: [PATCH] Add System.out/err catcher - - -diff --git a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a8e813ca89b033f061e695288b3383bdcf128531 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java -@@ -0,0 +1,94 @@ -+package io.papermc.paper.logging; -+ -+import org.bukkit.Bukkit; -+import org.bukkit.plugin.java.JavaPlugin; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.io.OutputStream; -+import java.io.PrintStream; -+import java.util.Objects; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.ConcurrentMap; -+import java.util.concurrent.TimeUnit; -+import java.util.logging.Level; -+ -+public final class SysoutCatcher { -+ private static final boolean SUPPRESS_NAGS = Boolean.getBoolean("io.papermc.paper.suppress.sout.nags"); -+ // Nanoseconds between nag at most; if interval is caught first, this is reset. -+ // <= 0 for disabling. -+ private static final long NAG_TIMEOUT = TimeUnit.MILLISECONDS.toNanos( -+ Long.getLong("io.papermc.paper.sout.nags.timeout", TimeUnit.MINUTES.toMillis(5L))); -+ // Count since last nag; if timeout is first, this is reset. -+ // <= 0 for disabling. -+ private static final long NAG_INTERVAL = Long.getLong("io.papermc.paper.sout.nags.interval", 200L); -+ -+ // We don't particularly care about how correct this is at any given moment; let's do it on a best attempt basis. -+ // The records are also pretty small, so let's just go for a size of 64 to start... -+ // -+ // Content: Plugin name => nag object -+ // Why plugin name?: This doesn't store a reference to the plugin; keeps the reload ability. -+ // Why not clean on reload?: Effort. -+ private final ConcurrentMap nagRecords = new ConcurrentHashMap<>(64); -+ -+ public SysoutCatcher() { -+ System.setOut(new WrappedOutStream(System.out, Level.INFO, "[STDOUT] ")); -+ System.setErr(new WrappedOutStream(System.err, Level.SEVERE, "[STDERR] ")); -+ } -+ -+ private final class WrappedOutStream extends PrintStream { -+ private static final StackWalker STACK_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); -+ private final Level level; -+ private final String prefix; -+ -+ public WrappedOutStream(@NotNull final OutputStream out, final Level level, final String prefix) { -+ super(out); -+ this.level = level; -+ this.prefix = prefix; -+ } -+ -+ @Override -+ public void println(@Nullable final String line) { -+ final Class clazz = STACK_WALKER.getCallerClass(); -+ try { -+ final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(clazz); -+ -+ // Instead of just printing the message, send it to the plugin's logger -+ plugin.getLogger().log(this.level, this.prefix + line); -+ -+ if (SysoutCatcher.SUPPRESS_NAGS) { -+ return; -+ } -+ if (SysoutCatcher.NAG_INTERVAL > 0 || SysoutCatcher.NAG_TIMEOUT > 0) { -+ final PluginNag nagRecord = SysoutCatcher.this.nagRecords.computeIfAbsent(plugin.getName(), k -> new PluginNag()); -+ final boolean hasTimePassed = SysoutCatcher.NAG_TIMEOUT > 0 -+ && (nagRecord.lastNagTimestamp == Long.MIN_VALUE -+ || nagRecord.lastNagTimestamp + SysoutCatcher.NAG_TIMEOUT <= System.nanoTime()); -+ final boolean hasMessagesPassed = SysoutCatcher.NAG_INTERVAL > 0 -+ && (nagRecord.messagesSinceNag == Long.MIN_VALUE -+ || ++nagRecord.messagesSinceNag >= SysoutCatcher.NAG_INTERVAL); -+ if (!hasMessagesPassed && !hasTimePassed) { -+ return; -+ } -+ nagRecord.lastNagTimestamp = System.nanoTime(); -+ nagRecord.messagesSinceNag = 0; -+ } -+ Bukkit.getLogger().warning( -+ String.format("Nag author(s): '%s' of '%s' about their usage of System.out/err.print. " -+ + "Please use your plugin's logger instead (JavaPlugin#getLogger).", -+ plugin.getPluginMeta().getAuthors(), -+ plugin.getPluginMeta().getDisplayName()) -+ ); -+ } catch (final IllegalArgumentException | IllegalStateException e) { -+ // If anything happens, the calling class doesn't exist, there is no JavaPlugin that "owns" the calling class, etc -+ // Just print out normally, with some added information -+ Bukkit.getLogger().log(this.level, String.format("%s[%s] %s", this.prefix, clazz.getName(), line)); -+ } -+ } -+ } -+ -+ private static class PluginNag { -+ private long lastNagTimestamp = Long.MIN_VALUE; -+ private long messagesSinceNag = Long.MIN_VALUE; -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 5bf8d8de30f69e2eed92400bc75c36231a4631fe..3c29d2a8ccac5ca50d3df41262e9e767daf7035b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -303,6 +303,7 @@ public final class CraftServer implements Server { - public int reloadCount; - private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper - public static Exception excessiveVelEx; // Paper - Velocity warnings -+ private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper - - static { - ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); diff --git a/patches/server/0602-Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch b/patches/server/0601-Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch similarity index 100% rename from patches/server/0602-Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch rename to patches/server/0601-Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch diff --git a/patches/server/0602-Prevent-AFK-kick-while-watching-end-credits.patch b/patches/server/0602-Prevent-AFK-kick-while-watching-end-credits.patch new file mode 100644 index 000000000000..0abb2b65fdaa --- /dev/null +++ b/patches/server/0602-Prevent-AFK-kick-while-watching-end-credits.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Sat, 24 Jul 2021 16:54:11 +0200 +Subject: [PATCH] Prevent AFK kick while watching end credits + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 8b6f37463e35162d4b228b732a0283d57dc2f1b9..0fef0dda0586b70dc140406b55dba1d5e23c3c97 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -389,7 +389,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + --this.dropSpamTickCount; + } + +- if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { ++ if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits + this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 + this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause + } diff --git a/patches/server/0604-Allow-skipping-writing-of-comments-to-server.propert.patch b/patches/server/0603-Allow-skipping-writing-of-comments-to-server.propert.patch similarity index 100% rename from patches/server/0604-Allow-skipping-writing-of-comments-to-server.propert.patch rename to patches/server/0603-Allow-skipping-writing-of-comments-to-server.propert.patch diff --git a/patches/server/0603-Prevent-AFK-kick-while-watching-end-credits.patch b/patches/server/0603-Prevent-AFK-kick-while-watching-end-credits.patch deleted file mode 100644 index 7cd213831e59..000000000000 --- a/patches/server/0603-Prevent-AFK-kick-while-watching-end-credits.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Sat, 24 Jul 2021 16:54:11 +0200 -Subject: [PATCH] Prevent AFK kick while watching end credits - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index a28141f7a4b59d98e1eb9fbd8c431fa3eedaf53e..5689d048b74e7608119f2e5db0022ba9b6180e5b 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -389,7 +389,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - --this.dropSpamTickCount; - } - -- if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { -+ if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits - this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 - this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause - } diff --git a/patches/server/0604-Add-PlayerSetSpawnEvent.patch b/patches/server/0604-Add-PlayerSetSpawnEvent.patch new file mode 100644 index 000000000000..ca1c60c1158d --- /dev/null +++ b/patches/server/0604-Add-PlayerSetSpawnEvent.patch @@ -0,0 +1,204 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 19 May 2021 18:59:10 -0700 +Subject: [PATCH] Add PlayerSetSpawnEvent + + +diff --git a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java +index a2d0699e8427b2262a2396495111125eccafbb66..15db9368227dbc29d07d74e85bd126b345b526b6 100644 +--- a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java ++++ b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java +@@ -38,24 +38,34 @@ public class SetSpawnCommand { + ResourceKey resourcekey = source.getLevel().dimension(); + Iterator iterator = targets.iterator(); + ++ final Collection actualTargets = new java.util.ArrayList<>(); // Paper - Add PlayerSetSpawnEvent + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + +- entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.COMMAND); // CraftBukkit ++ // Paper start - Add PlayerSetSpawnEvent ++ if (entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.COMMAND)) { ++ actualTargets.add(entityplayer); ++ } ++ // Paper end - Add PlayerSetSpawnEvent + } ++ // Paper start - Add PlayerSetSpawnEvent ++ if (actualTargets.isEmpty()) { ++ return 0; ++ } ++ // Paper end - Add PlayerSetSpawnEvent + + String s = resourcekey.location().toString(); + +- if (targets.size() == 1) { ++ if (actualTargets.size() == 1) { // Paper - Add PlayerSetSpawnEvent + source.sendSuccess(() -> { +- return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) targets.iterator().next()).getDisplayName()); ++ return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) actualTargets.iterator().next()).getDisplayName()); // Paper - Add PlayerSetSpawnEvent + }, true); + } else { + source.sendSuccess(() -> { +- return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, targets.size()); ++ return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, actualTargets.size()); // Paper - Add PlayerSetSpawnEvent + }, true); + } + +- return targets.size(); ++ return actualTargets.size(); // Paper - Add PlayerSetSpawnEvent + } + } +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 751216261df86402c23d3f0d73944ae51e849caa..fb86824192f1fc850a55905757c65cafec1edb6a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1307,7 +1307,7 @@ public class ServerPlayer extends Player { + } else if (this.bedBlocked(blockposition, enumdirection)) { + return Either.left(Player.BedSleepingProblem.OBSTRUCTED); + } else { +- this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, PlayerSpawnChangeEvent.Cause.BED); // CraftBukkit ++ this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.BED); // Paper - Add PlayerSetSpawnEvent + if (this.level().isDay()) { + return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_NOW); + } else { +@@ -2262,44 +2262,50 @@ public class ServerPlayer extends Player { + return this.respawnForced; + } + ++ @Deprecated // Paper - Add PlayerSetSpawnEvent + public void setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage) { +- // CraftBukkit start +- this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, PlayerSpawnChangeEvent.Cause.UNKNOWN); +- } +- +- public void setRespawnPosition(ResourceKey resourcekey, @Nullable BlockPos blockposition, float f, boolean flag, boolean flag1, PlayerSpawnChangeEvent.Cause cause) { +- ServerLevel newWorld = this.server.getLevel(resourcekey); +- Location newSpawn = (blockposition != null) ? CraftLocation.toBukkit(blockposition, newWorld.getWorld(), f, 0) : null; +- +- PlayerSpawnChangeEvent event = new PlayerSpawnChangeEvent(this.getBukkitEntity(), newSpawn, flag, cause); +- Bukkit.getServer().getPluginManager().callEvent(event); +- if (event.isCancelled()) { +- return; +- } +- newSpawn = event.getNewSpawn(); +- flag = event.isForced(); +- +- if (newSpawn != null) { +- resourcekey = ((CraftWorld) newSpawn.getWorld()).getHandle().dimension(); +- blockposition = BlockPos.containing(newSpawn.getX(), newSpawn.getY(), newSpawn.getZ()); +- f = newSpawn.getYaw(); +- } else { +- resourcekey = Level.OVERWORLD; +- blockposition = null; +- f = 0.0F; ++ // Paper start - Add PlayerSetSpawnEvent ++ this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.UNKNOWN); ++ } ++ @Deprecated ++ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, PlayerSpawnChangeEvent.Cause cause) { ++ return this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, cause == PlayerSpawnChangeEvent.Cause.RESET ? ++ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN : com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.valueOf(cause.name())); ++ } ++ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause cause) { ++ Location spawnLoc = null; ++ boolean willNotify = false; ++ if (pos != null) { ++ boolean flag2 = pos.equals(this.respawnPosition) && dimension.equals(this.respawnDimension); ++ spawnLoc = io.papermc.paper.util.MCUtil.toLocation(this.getServer().getLevel(dimension), pos); ++ spawnLoc.setYaw(angle); ++ willNotify = sendMessage && !flag2; ++ } ++ ++ PlayerSpawnChangeEvent dumbEvent = new PlayerSpawnChangeEvent(this.getBukkitEntity(), spawnLoc, forced, ++ cause == com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN ? PlayerSpawnChangeEvent.Cause.RESET : PlayerSpawnChangeEvent.Cause.valueOf(cause.name())); ++ dumbEvent.callEvent(); ++ ++ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent event = new com.destroystokyo.paper.event.player.PlayerSetSpawnEvent(this.getBukkitEntity(), cause, dumbEvent.getNewSpawn(), dumbEvent.isForced(), willNotify, willNotify ? net.kyori.adventure.text.Component.translatable("block.minecraft.set_spawn") : null); ++ event.setCancelled(dumbEvent.isCancelled()); ++ if (!event.callEvent()) { ++ return false; + } +- // CraftBukkit end +- if (blockposition != null) { +- boolean flag2 = blockposition.equals(this.respawnPosition) && resourcekey.equals(this.respawnDimension); ++ if (event.getLocation() != null) { ++ dimension = event.getLocation().getWorld() != null ? ((CraftWorld) event.getLocation().getWorld()).getHandle().dimension() : dimension; ++ pos = io.papermc.paper.util.MCUtil.toBlockPosition(event.getLocation()); ++ angle = event.getLocation().getYaw(); ++ forced = event.isForced(); ++ // Paper end - Add PlayerSetSpawnEvent + +- if (flag1 && !flag2) { +- this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn")); ++ if (event.willNotifyPlayer() && event.getNotification() != null) { // Paper - Add PlayerSetSpawnEvent ++ this.sendSystemMessage(PaperAdventure.asVanilla(event.getNotification())); // Paper - Add PlayerSetSpawnEvent + } + +- this.respawnPosition = blockposition; +- this.respawnDimension = resourcekey; +- this.respawnAngle = f; +- this.respawnForced = flag; ++ this.respawnPosition = pos; ++ this.respawnDimension = dimension; ++ this.respawnAngle = angle; ++ this.respawnForced = forced; + } else { + this.respawnPosition = null; + this.respawnDimension = Level.OVERWORLD; +@@ -2307,6 +2313,7 @@ public class ServerPlayer extends Player { + this.respawnForced = false; + } + ++ return true; // Paper - Add PlayerSetSpawnEvent + } + + public SectionPos getLastSectionPos() { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 569d1f1682b9c785701fbb04683fea880504c94c..75e3d7f81e21caaffd79d095022c4196507a9059 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -870,7 +870,7 @@ public abstract class PlayerList { + location = CraftLocation.toBukkit(vec3d, worldserver1.getWorld(), f1, 0.0F); + } else if (blockposition != null) { + entityplayer1.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F)); +- entityplayer1.setRespawnPosition(null, null, 0f, false, false, PlayerSpawnChangeEvent.Cause.RESET); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed ++ entityplayer1.setRespawnPosition(null, null, 0f, false, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed // Paper - Add PlayerSetSpawnEvent + } + } + +diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java +index ecaa7f0b2bf795f16187f11fa27f6d5d435ccbfe..acd5ec218b8d4c096f44ae2eec1379eeaf30ddc5 100644 +--- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java +@@ -87,9 +87,14 @@ public class RespawnAnchorBlock extends Block { + ServerPlayer entityplayer = (ServerPlayer) player; + + if (entityplayer.getRespawnDimension() != world.dimension() || !pos.equals(entityplayer.getRespawnPosition())) { +- entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.RESPAWN_ANCHOR); // CraftBukkit ++ if (entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.RESPAWN_ANCHOR)) { // Paper - Add PlayerSetSpawnEvent + world.playSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, SoundEvents.RESPAWN_ANCHOR_SET_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F); + return InteractionResult.SUCCESS; ++ // Paper start - Add PlayerSetSpawnEvent ++ } else { ++ return InteractionResult.FAIL; ++ } ++ // Paper end - Add PlayerSetSpawnEvent + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 5ff0081fa3cdd34698b4d995a0845709bb5b397f..8b89f46aad11628a50d9f6c65caf52a558b9ee18 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1321,9 +1321,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + @Override + public void setRespawnLocation(Location location, boolean override) { + if (location == null) { +- this.getHandle().setRespawnPosition(null, null, 0.0F, override, false, PlayerSpawnChangeEvent.Cause.PLUGIN); ++ this.getHandle().setRespawnPosition(null, null, 0.0F, override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - Add PlayerSetSpawnEvent + } else { +- this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), CraftLocation.toBlockPosition(location), location.getYaw(), override, false, PlayerSpawnChangeEvent.Cause.PLUGIN); ++ this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), CraftLocation.toBlockPosition(location), location.getYaw(), override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - Add PlayerSetSpawnEvent + } + } + diff --git a/patches/server/0605-Add-PlayerSetSpawnEvent.patch b/patches/server/0605-Add-PlayerSetSpawnEvent.patch deleted file mode 100644 index af0338e82fd6..000000000000 --- a/patches/server/0605-Add-PlayerSetSpawnEvent.patch +++ /dev/null @@ -1,204 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 19 May 2021 18:59:10 -0700 -Subject: [PATCH] Add PlayerSetSpawnEvent - - -diff --git a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java -index a2d0699e8427b2262a2396495111125eccafbb66..15db9368227dbc29d07d74e85bd126b345b526b6 100644 ---- a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java -+++ b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java -@@ -38,24 +38,34 @@ public class SetSpawnCommand { - ResourceKey resourcekey = source.getLevel().dimension(); - Iterator iterator = targets.iterator(); - -+ final Collection actualTargets = new java.util.ArrayList<>(); // Paper - Add PlayerSetSpawnEvent - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); - -- entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.COMMAND); // CraftBukkit -+ // Paper start - Add PlayerSetSpawnEvent -+ if (entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.COMMAND)) { -+ actualTargets.add(entityplayer); -+ } -+ // Paper end - Add PlayerSetSpawnEvent - } -+ // Paper start - Add PlayerSetSpawnEvent -+ if (actualTargets.isEmpty()) { -+ return 0; -+ } -+ // Paper end - Add PlayerSetSpawnEvent - - String s = resourcekey.location().toString(); - -- if (targets.size() == 1) { -+ if (actualTargets.size() == 1) { // Paper - Add PlayerSetSpawnEvent - source.sendSuccess(() -> { -- return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) targets.iterator().next()).getDisplayName()); -+ return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) actualTargets.iterator().next()).getDisplayName()); // Paper - Add PlayerSetSpawnEvent - }, true); - } else { - source.sendSuccess(() -> { -- return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, targets.size()); -+ return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, actualTargets.size()); // Paper - Add PlayerSetSpawnEvent - }, true); - } - -- return targets.size(); -+ return actualTargets.size(); // Paper - Add PlayerSetSpawnEvent - } - } -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 974874a6e72b65f956ebb5605547e5d1be74d2e8..5fff2d791b924e402a1b861c3cff8989c19d5e3b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1307,7 +1307,7 @@ public class ServerPlayer extends Player { - } else if (this.bedBlocked(blockposition, enumdirection)) { - return Either.left(Player.BedSleepingProblem.OBSTRUCTED); - } else { -- this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, PlayerSpawnChangeEvent.Cause.BED); // CraftBukkit -+ this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.BED); // Paper - Add PlayerSetSpawnEvent - if (this.level().isDay()) { - return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_NOW); - } else { -@@ -2262,44 +2262,50 @@ public class ServerPlayer extends Player { - return this.respawnForced; - } - -+ @Deprecated // Paper - Add PlayerSetSpawnEvent - public void setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage) { -- // CraftBukkit start -- this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, PlayerSpawnChangeEvent.Cause.UNKNOWN); -- } -- -- public void setRespawnPosition(ResourceKey resourcekey, @Nullable BlockPos blockposition, float f, boolean flag, boolean flag1, PlayerSpawnChangeEvent.Cause cause) { -- ServerLevel newWorld = this.server.getLevel(resourcekey); -- Location newSpawn = (blockposition != null) ? CraftLocation.toBukkit(blockposition, newWorld.getWorld(), f, 0) : null; -- -- PlayerSpawnChangeEvent event = new PlayerSpawnChangeEvent(this.getBukkitEntity(), newSpawn, flag, cause); -- Bukkit.getServer().getPluginManager().callEvent(event); -- if (event.isCancelled()) { -- return; -- } -- newSpawn = event.getNewSpawn(); -- flag = event.isForced(); -- -- if (newSpawn != null) { -- resourcekey = ((CraftWorld) newSpawn.getWorld()).getHandle().dimension(); -- blockposition = BlockPos.containing(newSpawn.getX(), newSpawn.getY(), newSpawn.getZ()); -- f = newSpawn.getYaw(); -- } else { -- resourcekey = Level.OVERWORLD; -- blockposition = null; -- f = 0.0F; -+ // Paper start - Add PlayerSetSpawnEvent -+ this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.UNKNOWN); -+ } -+ @Deprecated -+ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, PlayerSpawnChangeEvent.Cause cause) { -+ return this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, cause == PlayerSpawnChangeEvent.Cause.RESET ? -+ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN : com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.valueOf(cause.name())); -+ } -+ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause cause) { -+ Location spawnLoc = null; -+ boolean willNotify = false; -+ if (pos != null) { -+ boolean flag2 = pos.equals(this.respawnPosition) && dimension.equals(this.respawnDimension); -+ spawnLoc = io.papermc.paper.util.MCUtil.toLocation(this.getServer().getLevel(dimension), pos); -+ spawnLoc.setYaw(angle); -+ willNotify = sendMessage && !flag2; -+ } -+ -+ PlayerSpawnChangeEvent dumbEvent = new PlayerSpawnChangeEvent(this.getBukkitEntity(), spawnLoc, forced, -+ cause == com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN ? PlayerSpawnChangeEvent.Cause.RESET : PlayerSpawnChangeEvent.Cause.valueOf(cause.name())); -+ dumbEvent.callEvent(); -+ -+ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent event = new com.destroystokyo.paper.event.player.PlayerSetSpawnEvent(this.getBukkitEntity(), cause, dumbEvent.getNewSpawn(), dumbEvent.isForced(), willNotify, willNotify ? net.kyori.adventure.text.Component.translatable("block.minecraft.set_spawn") : null); -+ event.setCancelled(dumbEvent.isCancelled()); -+ if (!event.callEvent()) { -+ return false; - } -- // CraftBukkit end -- if (blockposition != null) { -- boolean flag2 = blockposition.equals(this.respawnPosition) && resourcekey.equals(this.respawnDimension); -+ if (event.getLocation() != null) { -+ dimension = event.getLocation().getWorld() != null ? ((CraftWorld) event.getLocation().getWorld()).getHandle().dimension() : dimension; -+ pos = io.papermc.paper.util.MCUtil.toBlockPosition(event.getLocation()); -+ angle = event.getLocation().getYaw(); -+ forced = event.isForced(); -+ // Paper end - Add PlayerSetSpawnEvent - -- if (flag1 && !flag2) { -- this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn")); -+ if (event.willNotifyPlayer() && event.getNotification() != null) { // Paper - Add PlayerSetSpawnEvent -+ this.sendSystemMessage(PaperAdventure.asVanilla(event.getNotification())); // Paper - Add PlayerSetSpawnEvent - } - -- this.respawnPosition = blockposition; -- this.respawnDimension = resourcekey; -- this.respawnAngle = f; -- this.respawnForced = flag; -+ this.respawnPosition = pos; -+ this.respawnDimension = dimension; -+ this.respawnAngle = angle; -+ this.respawnForced = forced; - } else { - this.respawnPosition = null; - this.respawnDimension = Level.OVERWORLD; -@@ -2307,6 +2313,7 @@ public class ServerPlayer extends Player { - this.respawnForced = false; - } - -+ return true; // Paper - Add PlayerSetSpawnEvent - } - - public SectionPos getLastSectionPos() { -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 9e9d4b5cc32beef41aa6d3b0ad8dbbd158b1f148..3df033f99ccc5b803eca2fe6d4f1e60399c4bee1 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -870,7 +870,7 @@ public abstract class PlayerList { - location = CraftLocation.toBukkit(vec3d, worldserver1.getWorld(), f1, 0.0F); - } else if (blockposition != null) { - entityplayer1.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F)); -- entityplayer1.setRespawnPosition(null, null, 0f, false, false, PlayerSpawnChangeEvent.Cause.RESET); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed -+ entityplayer1.setRespawnPosition(null, null, 0f, false, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed // Paper - Add PlayerSetSpawnEvent - } - } - -diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -index ecaa7f0b2bf795f16187f11fa27f6d5d435ccbfe..acd5ec218b8d4c096f44ae2eec1379eeaf30ddc5 100644 ---- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -@@ -87,9 +87,14 @@ public class RespawnAnchorBlock extends Block { - ServerPlayer entityplayer = (ServerPlayer) player; - - if (entityplayer.getRespawnDimension() != world.dimension() || !pos.equals(entityplayer.getRespawnPosition())) { -- entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.RESPAWN_ANCHOR); // CraftBukkit -+ if (entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.RESPAWN_ANCHOR)) { // Paper - Add PlayerSetSpawnEvent - world.playSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, SoundEvents.RESPAWN_ANCHOR_SET_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F); - return InteractionResult.SUCCESS; -+ // Paper start - Add PlayerSetSpawnEvent -+ } else { -+ return InteractionResult.FAIL; -+ } -+ // Paper end - Add PlayerSetSpawnEvent - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 8b54fe6ee1c07a70f9823f1a2a13887620a6dfda..ff85623db8fc9cbf9b056eec9774ddcd3106c194 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1291,9 +1291,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - @Override - public void setRespawnLocation(Location location, boolean override) { - if (location == null) { -- this.getHandle().setRespawnPosition(null, null, 0.0F, override, false, PlayerSpawnChangeEvent.Cause.PLUGIN); -+ this.getHandle().setRespawnPosition(null, null, 0.0F, override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - Add PlayerSetSpawnEvent - } else { -- this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), CraftLocation.toBlockPosition(location), location.getYaw(), override, false, PlayerSpawnChangeEvent.Cause.PLUGIN); -+ this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), CraftLocation.toBlockPosition(location), location.getYaw(), override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - Add PlayerSetSpawnEvent - } - } - diff --git a/patches/server/0606-Make-hoppers-respect-inventory-max-stack-size.patch b/patches/server/0605-Make-hoppers-respect-inventory-max-stack-size.patch similarity index 100% rename from patches/server/0606-Make-hoppers-respect-inventory-max-stack-size.patch rename to patches/server/0605-Make-hoppers-respect-inventory-max-stack-size.patch diff --git a/patches/server/0607-Optimize-entity-tracker-passenger-checks.patch b/patches/server/0606-Optimize-entity-tracker-passenger-checks.patch similarity index 100% rename from patches/server/0607-Optimize-entity-tracker-passenger-checks.patch rename to patches/server/0606-Optimize-entity-tracker-passenger-checks.patch diff --git a/patches/server/0608-Config-option-for-Piglins-guarding-chests.patch b/patches/server/0607-Config-option-for-Piglins-guarding-chests.patch similarity index 100% rename from patches/server/0608-Config-option-for-Piglins-guarding-chests.patch rename to patches/server/0607-Config-option-for-Piglins-guarding-chests.patch diff --git a/patches/server/0608-Add-EntityDamageItemEvent.patch b/patches/server/0608-Add-EntityDamageItemEvent.patch new file mode 100644 index 000000000000..99df36aea012 --- /dev/null +++ b/patches/server/0608-Add-EntityDamageItemEvent.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 22 Dec 2020 13:52:48 -0800 +Subject: [PATCH] Add EntityDamageItemEvent + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 9d2d305a5e66b9f3d94f6464736f5bb40adae591..d5ee83e6538fbd067388272fa9895e17859be642 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -624,7 +624,7 @@ public final class ItemStack { + return this.getItem().getMaxDamage(); + } + +- public boolean hurt(int amount, RandomSource random, @Nullable ServerPlayer player) { ++ public boolean hurt(int amount, RandomSource random, @Nullable LivingEntity player) { // Paper - Add EntityDamageItemEvent + if (!this.isDamageableItem()) { + return false; + } else { +@@ -642,8 +642,8 @@ public final class ItemStack { + + amount -= k; + // CraftBukkit start +- if (player != null) { +- PlayerItemDamageEvent event = new PlayerItemDamageEvent(player.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); ++ if (player instanceof ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent ++ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); // Paper - Add EntityDamageItemEvent + event.getPlayer().getServer().getPluginManager().callEvent(event); + + if (amount != event.getDamage() || event.isCancelled()) { +@@ -654,6 +654,14 @@ public final class ItemStack { + } + + amount = event.getDamage(); ++ // Paper start - Add EntityDamageItemEvent ++ } else if (player != null) { ++ io.papermc.paper.event.entity.EntityDamageItemEvent event = new io.papermc.paper.event.entity.EntityDamageItemEvent(player.getBukkitLivingEntity(), CraftItemStack.asCraftMirror(this), amount); ++ if (!event.callEvent()) { ++ return false; ++ } ++ amount = event.getDamage(); ++ // Paper end - Add EntityDamageItemEvent + } + // CraftBukkit end + if (amount <= 0) { +@@ -661,8 +669,8 @@ public final class ItemStack { + } + } + +- if (player != null && amount != 0) { +- CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, this.getDamageValue() + amount); ++ if (player instanceof ServerPlayer serverPlayer && amount != 0) { // Paper - Add EntityDamageItemEvent ++ CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, this, this.getDamageValue() + amount); // Paper - Add EntityDamageItemEvent + } + + j = this.getDamageValue() + amount; +@@ -674,7 +682,7 @@ public final class ItemStack { + public void hurtAndBreak(int amount, T entity, Consumer breakCallback) { + if (!entity.level().isClientSide && (!(entity instanceof net.minecraft.world.entity.player.Player) || !((net.minecraft.world.entity.player.Player) entity).getAbilities().instabuild)) { + if (this.isDamageableItem()) { +- if (this.hurt(amount, entity.getRandom(), entity instanceof ServerPlayer ? (ServerPlayer) entity : null)) { ++ if (this.hurt(amount, entity.getRandom(), entity /*instanceof ServerPlayer ? (ServerPlayer) entity : null*/)) { // Paper - Add EntityDamageItemEvent + breakCallback.accept(entity); + Item item = this.getItem(); + // CraftBukkit start - Check for item breaking diff --git a/patches/server/0609-Add-EntityDamageItemEvent.patch b/patches/server/0609-Add-EntityDamageItemEvent.patch deleted file mode 100644 index 66964564a322..000000000000 --- a/patches/server/0609-Add-EntityDamageItemEvent.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 22 Dec 2020 13:52:48 -0800 -Subject: [PATCH] Add EntityDamageItemEvent - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index b4f2b75960674e81c8189dc908523c56ae2e5079..4624fc8eb4a5e71d7874d25ca0a25975f65a6919 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -622,7 +622,7 @@ public final class ItemStack { - return this.getItem().getMaxDamage(); - } - -- public boolean hurt(int amount, RandomSource random, @Nullable ServerPlayer player) { -+ public boolean hurt(int amount, RandomSource random, @Nullable LivingEntity player) { // Paper - Add EntityDamageItemEvent - if (!this.isDamageableItem()) { - return false; - } else { -@@ -640,8 +640,8 @@ public final class ItemStack { - - amount -= k; - // CraftBukkit start -- if (player != null) { -- PlayerItemDamageEvent event = new PlayerItemDamageEvent(player.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); -+ if (player instanceof ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent -+ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); // Paper - Add EntityDamageItemEvent - event.getPlayer().getServer().getPluginManager().callEvent(event); - - if (amount != event.getDamage() || event.isCancelled()) { -@@ -652,6 +652,14 @@ public final class ItemStack { - } - - amount = event.getDamage(); -+ // Paper start - Add EntityDamageItemEvent -+ } else if (player != null) { -+ io.papermc.paper.event.entity.EntityDamageItemEvent event = new io.papermc.paper.event.entity.EntityDamageItemEvent(player.getBukkitLivingEntity(), CraftItemStack.asCraftMirror(this), amount); -+ if (!event.callEvent()) { -+ return false; -+ } -+ amount = event.getDamage(); -+ // Paper end - Add EntityDamageItemEvent - } - // CraftBukkit end - if (amount <= 0) { -@@ -659,8 +667,8 @@ public final class ItemStack { - } - } - -- if (player != null && amount != 0) { -- CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, this.getDamageValue() + amount); -+ if (player instanceof ServerPlayer serverPlayer && amount != 0) { // Paper - Add EntityDamageItemEvent -+ CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, this, this.getDamageValue() + amount); // Paper - Add EntityDamageItemEvent - } - - j = this.getDamageValue() + amount; -@@ -672,7 +680,7 @@ public final class ItemStack { - public void hurtAndBreak(int amount, T entity, Consumer breakCallback) { - if (!entity.level().isClientSide && (!(entity instanceof net.minecraft.world.entity.player.Player) || !((net.minecraft.world.entity.player.Player) entity).getAbilities().instabuild)) { - if (this.isDamageableItem()) { -- if (this.hurt(amount, entity.getRandom(), entity instanceof ServerPlayer ? (ServerPlayer) entity : null)) { -+ if (this.hurt(amount, entity.getRandom(), entity /*instanceof ServerPlayer ? (ServerPlayer) entity : null*/)) { // Paper - Add EntityDamageItemEvent - breakCallback.accept(entity); - Item item = this.getItem(); - // CraftBukkit start - Check for item breaking diff --git a/patches/server/0609-Optimize-indirect-passenger-iteration.patch b/patches/server/0609-Optimize-indirect-passenger-iteration.patch new file mode 100644 index 000000000000..27bb816c057d --- /dev/null +++ b/patches/server/0609-Optimize-indirect-passenger-iteration.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 9 Aug 2021 00:38:37 -0400 +Subject: [PATCH] Optimize indirect passenger iteration + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index e4ba2914184072835e4447511b70a94f1ebc9eea..2d3d27840dc8435381c415dfe34325499e5638c3 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3826,20 +3826,34 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + private Stream getIndirectPassengersStream() { ++ if (this.passengers.isEmpty()) { return Stream.of(); } // Paper - Optimize indirect passenger iteration + return this.passengers.stream().flatMap(Entity::getSelfAndPassengers); + } + + @Override + public Stream getSelfAndPassengers() { ++ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration + return Stream.concat(Stream.of(this), this.getIndirectPassengersStream()); + } + + @Override + public Stream getPassengersAndSelf() { ++ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration + return Stream.concat(this.passengers.stream().flatMap(Entity::getPassengersAndSelf), Stream.of(this)); + } + + public Iterable getIndirectPassengers() { ++ // Paper start - Optimize indirect passenger iteration ++ if (this.passengers.isEmpty()) { return ImmutableList.of(); } ++ ImmutableList.Builder indirectPassengers = ImmutableList.builder(); ++ for (Entity passenger : this.passengers) { ++ indirectPassengers.add(passenger); ++ indirectPassengers.addAll(passenger.getIndirectPassengers()); ++ } ++ return indirectPassengers.build(); ++ } ++ private Iterable getIndirectPassengers_old() { ++ // Paper end - Optimize indirect passenger iteration + return () -> { + return this.getIndirectPassengersStream().iterator(); + }; +@@ -3852,6 +3866,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + public boolean hasExactlyOnePlayerPassenger() { ++ if (this.passengers.isEmpty()) { return false; } // Paper - Optimize indirect passenger iteration + return this.countPlayerPassengers() == 1; + } + diff --git a/patches/server/0611-Configurable-item-frame-map-cursor-update-interval.patch b/patches/server/0610-Configurable-item-frame-map-cursor-update-interval.patch similarity index 100% rename from patches/server/0611-Configurable-item-frame-map-cursor-update-interval.patch rename to patches/server/0610-Configurable-item-frame-map-cursor-update-interval.patch diff --git a/patches/server/0610-Optimize-indirect-passenger-iteration.patch b/patches/server/0610-Optimize-indirect-passenger-iteration.patch deleted file mode 100644 index 787e9411e795..000000000000 --- a/patches/server/0610-Optimize-indirect-passenger-iteration.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Mon, 9 Aug 2021 00:38:37 -0400 -Subject: [PATCH] Optimize indirect passenger iteration - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 50ebb94f4403bdf532af423d5204364d538667ee..fb085b7c72896bc6e5223eb2d87d1e6b435114dc 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3823,20 +3823,34 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - private Stream getIndirectPassengersStream() { -+ if (this.passengers.isEmpty()) { return Stream.of(); } // Paper - Optimize indirect passenger iteration - return this.passengers.stream().flatMap(Entity::getSelfAndPassengers); - } - - @Override - public Stream getSelfAndPassengers() { -+ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration - return Stream.concat(Stream.of(this), this.getIndirectPassengersStream()); - } - - @Override - public Stream getPassengersAndSelf() { -+ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration - return Stream.concat(this.passengers.stream().flatMap(Entity::getPassengersAndSelf), Stream.of(this)); - } - - public Iterable getIndirectPassengers() { -+ // Paper start - Optimize indirect passenger iteration -+ if (this.passengers.isEmpty()) { return ImmutableList.of(); } -+ ImmutableList.Builder indirectPassengers = ImmutableList.builder(); -+ for (Entity passenger : this.passengers) { -+ indirectPassengers.add(passenger); -+ indirectPassengers.addAll(passenger.getIndirectPassengers()); -+ } -+ return indirectPassengers.build(); -+ } -+ private Iterable getIndirectPassengers_old() { -+ // Paper end - Optimize indirect passenger iteration - return () -> { - return this.getIndirectPassengersStream().iterator(); - }; -@@ -3849,6 +3863,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - public boolean hasExactlyOnePlayerPassenger() { -+ if (this.passengers.isEmpty()) { return false; } // Paper - Optimize indirect passenger iteration - return this.countPlayerPassengers() == 1; - } - diff --git a/patches/server/0612-Clear-bucket-NBT-after-dispense.patch b/patches/server/0611-Clear-bucket-NBT-after-dispense.patch similarity index 100% rename from patches/server/0612-Clear-bucket-NBT-after-dispense.patch rename to patches/server/0611-Clear-bucket-NBT-after-dispense.patch diff --git a/patches/server/0613-Change-EnderEye-target-without-changing-other-things.patch b/patches/server/0612-Change-EnderEye-target-without-changing-other-things.patch similarity index 100% rename from patches/server/0613-Change-EnderEye-target-without-changing-other-things.patch rename to patches/server/0612-Change-EnderEye-target-without-changing-other-things.patch diff --git a/patches/server/0614-Add-BlockBreakBlockEvent.patch b/patches/server/0613-Add-BlockBreakBlockEvent.patch similarity index 100% rename from patches/server/0614-Add-BlockBreakBlockEvent.patch rename to patches/server/0613-Add-BlockBreakBlockEvent.patch diff --git a/patches/server/0615-Option-to-prevent-NBT-copy-in-smithing-recipes.patch b/patches/server/0614-Option-to-prevent-NBT-copy-in-smithing-recipes.patch similarity index 100% rename from patches/server/0615-Option-to-prevent-NBT-copy-in-smithing-recipes.patch rename to patches/server/0614-Option-to-prevent-NBT-copy-in-smithing-recipes.patch diff --git a/patches/server/0616-More-CommandBlock-API.patch b/patches/server/0615-More-CommandBlock-API.patch similarity index 100% rename from patches/server/0616-More-CommandBlock-API.patch rename to patches/server/0615-More-CommandBlock-API.patch diff --git a/patches/server/0617-Add-missing-team-sidebar-display-slots.patch b/patches/server/0616-Add-missing-team-sidebar-display-slots.patch similarity index 100% rename from patches/server/0617-Add-missing-team-sidebar-display-slots.patch rename to patches/server/0616-Add-missing-team-sidebar-display-slots.patch diff --git a/patches/server/0617-Add-back-EntityPortalExitEvent.patch b/patches/server/0617-Add-back-EntityPortalExitEvent.patch new file mode 100644 index 000000000000..2228e9d709ee --- /dev/null +++ b/patches/server/0617-Add-back-EntityPortalExitEvent.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 16 May 2021 09:39:46 -0700 +Subject: [PATCH] Add back EntityPortalExitEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 2d3d27840dc8435381c415dfe34325499e5638c3..7295dfa7010297e019aba979bda9ff133d05a3c1 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3275,6 +3275,28 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } else { + // CraftBukkit start + worldserver = shapedetectorshape.world; ++ // Paper start - Call EntityPortalExitEvent ++ Vec3 position = shapedetectorshape.pos; ++ float yaw = shapedetectorshape.yRot; ++ float pitch = this.getXRot(); // Keep entity pitch as per moveTo line below ++ Vec3 velocity = shapedetectorshape.speed; ++ CraftEntity bukkitEntity = this.getBukkitEntity(); ++ org.bukkit.event.entity.EntityPortalExitEvent event = new org.bukkit.event.entity.EntityPortalExitEvent(bukkitEntity, ++ bukkitEntity.getLocation(), new Location(worldserver.getWorld(), position.x, position.y, position.z, yaw, pitch), ++ bukkitEntity.getVelocity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(shapedetectorshape.speed)); ++ event.callEvent(); ++ if (this.isRemoved()) { ++ return null; ++ } ++ ++ if (!event.isCancelled() && event.getTo() != null) { ++ worldserver = ((CraftWorld) event.getTo().getWorld()).getHandle(); ++ position = CraftLocation.toVec3D(event.getTo()); ++ yaw = event.getTo().getYaw(); ++ pitch = event.getTo().getPitch(); ++ velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter()); ++ } ++ // Paper end - Call EntityPortalExitEvent + if (worldserver == this.level) { + // SPIGOT-6782: Just move the entity if a plugin changed the world to the one the entity is already in + this.moveTo(shapedetectorshape.pos.x, shapedetectorshape.pos.y, shapedetectorshape.pos.z, shapedetectorshape.yRot, shapedetectorshape.xRot); +@@ -3294,8 +3316,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + + if (entity != null) { + entity.restoreFrom(this); +- entity.moveTo(shapedetectorshape.pos.x, shapedetectorshape.pos.y, shapedetectorshape.pos.z, shapedetectorshape.yRot, entity.getXRot()); +- entity.setDeltaMovement(shapedetectorshape.speed); ++ entity.moveTo(position.x, position.y, position.z, yaw, pitch); // Paper - EntityPortalExitEvent ++ entity.setDeltaMovement(velocity); // Paper - EntityPortalExitEvent + // CraftBukkit start - Don't spawn the new entity if the current entity isn't spawned + if (this.inWorld) { + worldserver.addDuringTeleport(entity); diff --git a/patches/server/0618-Add-back-EntityPortalExitEvent.patch b/patches/server/0618-Add-back-EntityPortalExitEvent.patch deleted file mode 100644 index 0460615abdf4..000000000000 --- a/patches/server/0618-Add-back-EntityPortalExitEvent.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 16 May 2021 09:39:46 -0700 -Subject: [PATCH] Add back EntityPortalExitEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index fb085b7c72896bc6e5223eb2d87d1e6b435114dc..207c97bf5ad773ca80151284796432a055095631 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3272,6 +3272,28 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } else { - // CraftBukkit start - worldserver = shapedetectorshape.world; -+ // Paper start - Call EntityPortalExitEvent -+ Vec3 position = shapedetectorshape.pos; -+ float yaw = shapedetectorshape.yRot; -+ float pitch = this.getXRot(); // Keep entity pitch as per moveTo line below -+ Vec3 velocity = shapedetectorshape.speed; -+ CraftEntity bukkitEntity = this.getBukkitEntity(); -+ org.bukkit.event.entity.EntityPortalExitEvent event = new org.bukkit.event.entity.EntityPortalExitEvent(bukkitEntity, -+ bukkitEntity.getLocation(), new Location(worldserver.getWorld(), position.x, position.y, position.z, yaw, pitch), -+ bukkitEntity.getVelocity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(shapedetectorshape.speed)); -+ event.callEvent(); -+ if (this.isRemoved()) { -+ return null; -+ } -+ -+ if (!event.isCancelled() && event.getTo() != null) { -+ worldserver = ((CraftWorld) event.getTo().getWorld()).getHandle(); -+ position = CraftLocation.toVec3D(event.getTo()); -+ yaw = event.getTo().getYaw(); -+ pitch = event.getTo().getPitch(); -+ velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter()); -+ } -+ // Paper end - Call EntityPortalExitEvent - if (worldserver == this.level) { - // SPIGOT-6782: Just move the entity if a plugin changed the world to the one the entity is already in - this.moveTo(shapedetectorshape.pos.x, shapedetectorshape.pos.y, shapedetectorshape.pos.z, shapedetectorshape.yRot, shapedetectorshape.xRot); -@@ -3291,8 +3313,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - - if (entity != null) { - entity.restoreFrom(this); -- entity.moveTo(shapedetectorshape.pos.x, shapedetectorshape.pos.y, shapedetectorshape.pos.z, shapedetectorshape.yRot, entity.getXRot()); -- entity.setDeltaMovement(shapedetectorshape.speed); -+ entity.moveTo(position.x, position.y, position.z, yaw, pitch); // Paper - EntityPortalExitEvent -+ entity.setDeltaMovement(velocity); // Paper - EntityPortalExitEvent - // CraftBukkit start - Don't spawn the new entity if the current entity isn't spawned - if (this.inWorld) { - worldserver.addDuringTeleport(entity); diff --git a/patches/server/0618-Add-methods-to-find-targets-for-lightning-strikes.patch b/patches/server/0618-Add-methods-to-find-targets-for-lightning-strikes.patch new file mode 100644 index 000000000000..bd7a6b51d2b8 --- /dev/null +++ b/patches/server/0618-Add-methods-to-find-targets-for-lightning-strikes.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jakub Zacek +Date: Mon, 4 Oct 2021 10:16:44 +0200 +Subject: [PATCH] Add methods to find targets for lightning strikes + +== AT == +public net.minecraft.server.level.ServerLevel findLightningRod(Lnet/minecraft/core/BlockPos;)Ljava/util/Optional; + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index af51a014156ade4d9c1b874a4c57a6de8849aea1..c83f2be16953b5cc009ddef81fd082295f3b2f71 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -757,6 +757,11 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + + protected BlockPos findLightningTargetAround(BlockPos pos) { ++ // Paper start - Add methods to find targets for lightning strikes ++ return this.findLightningTargetAround(pos, false); ++ } ++ public BlockPos findLightningTargetAround(BlockPos pos, boolean returnNullWhenNoTarget) { ++ // Paper end - Add methods to find targets for lightning strikes + BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); + Optional optional = this.findLightningRod(blockposition1); + +@@ -771,6 +776,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + if (!list.isEmpty()) { + return ((LivingEntity) list.get(this.random.nextInt(list.size()))).blockPosition(); + } else { ++ if (returnNullWhenNoTarget) return null; // Paper - Add methods to find targets for lightning strikes + if (blockposition1.getY() == this.getMinBuildHeight() - 1) { + blockposition1 = blockposition1.above(2); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index fce62b18da2d6ee8d10688107ca3179abfa3781b..ca68faef4232859e833adfd86a0ce13f7c2ad00e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -667,6 +667,23 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return (LightningStrike) lightning.getBukkitEntity(); + } + ++ // Paper start - Add methods to find targets for lightning strikes ++ @Override ++ public Location findLightningRod(Location location) { ++ return this.world.findLightningRod(io.papermc.paper.util.MCUtil.toBlockPosition(location)) ++ .map(blockPos -> io.papermc.paper.util.MCUtil.toLocation(this.world, blockPos) ++ // get the actual rod pos ++ .subtract(0, 1, 0)) ++ .orElse(null); ++ } ++ ++ @Override ++ public Location findLightningTarget(Location location) { ++ final BlockPos pos = this.world.findLightningTargetAround(io.papermc.paper.util.MCUtil.toBlockPosition(location), true); ++ return pos == null ? null : io.papermc.paper.util.MCUtil.toLocation(this.world, pos); ++ } ++ // Paper end - Add methods to find targets for lightning strikes ++ + @Override + public boolean generateTree(Location loc, TreeType type) { + return this.generateTree(loc, CraftWorld.rand, type); diff --git a/patches/server/0619-Add-methods-to-find-targets-for-lightning-strikes.patch b/patches/server/0619-Add-methods-to-find-targets-for-lightning-strikes.patch deleted file mode 100644 index f442052cf5dc..000000000000 --- a/patches/server/0619-Add-methods-to-find-targets-for-lightning-strikes.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jakub Zacek -Date: Mon, 4 Oct 2021 10:16:44 +0200 -Subject: [PATCH] Add methods to find targets for lightning strikes - -== AT == -public net.minecraft.server.level.ServerLevel findLightningRod(Lnet/minecraft/core/BlockPos;)Ljava/util/Optional; - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 0a7f98900cf45fdb11e64c2ed7139fcad940f0d5..dd3e5ee62da7b37b51e07796f6a1188e207d49e2 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -757,6 +757,11 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - protected BlockPos findLightningTargetAround(BlockPos pos) { -+ // Paper start - Add methods to find targets for lightning strikes -+ return this.findLightningTargetAround(pos, false); -+ } -+ public BlockPos findLightningTargetAround(BlockPos pos, boolean returnNullWhenNoTarget) { -+ // Paper end - Add methods to find targets for lightning strikes - BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); - Optional optional = this.findLightningRod(blockposition1); - -@@ -771,6 +776,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - if (!list.isEmpty()) { - return ((LivingEntity) list.get(this.random.nextInt(list.size()))).blockPosition(); - } else { -+ if (returnNullWhenNoTarget) return null; // Paper - Add methods to find targets for lightning strikes - if (blockposition1.getY() == this.getMinBuildHeight() - 1) { - blockposition1 = blockposition1.above(2); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index eb915b7e07666ef1cad55dc32882ecc962ab2ae3..938b3147040a43601f425b056dc4a83ccf2564be 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -661,6 +661,23 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return (LightningStrike) lightning.getBukkitEntity(); - } - -+ // Paper start - Add methods to find targets for lightning strikes -+ @Override -+ public Location findLightningRod(Location location) { -+ return this.world.findLightningRod(io.papermc.paper.util.MCUtil.toBlockPosition(location)) -+ .map(blockPos -> io.papermc.paper.util.MCUtil.toLocation(this.world, blockPos) -+ // get the actual rod pos -+ .subtract(0, 1, 0)) -+ .orElse(null); -+ } -+ -+ @Override -+ public Location findLightningTarget(Location location) { -+ final BlockPos pos = this.world.findLightningTargetAround(io.papermc.paper.util.MCUtil.toBlockPosition(location), true); -+ return pos == null ? null : io.papermc.paper.util.MCUtil.toLocation(this.world, pos); -+ } -+ // Paper end - Add methods to find targets for lightning strikes -+ - @Override - public boolean generateTree(Location loc, TreeType type) { - return this.generateTree(loc, CraftWorld.rand, type); diff --git a/patches/server/0619-Get-entity-default-attributes.patch b/patches/server/0619-Get-entity-default-attributes.patch new file mode 100644 index 000000000000..a157e222b155 --- /dev/null +++ b/patches/server/0619-Get-entity-default-attributes.patch @@ -0,0 +1,150 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 20 Aug 2021 13:03:21 -0700 +Subject: [PATCH] Get entity default attributes + +== AT == +public net.minecraft.world.entity.ai.attributes.AttributeSupplier getAttributeInstance(Lnet/minecraft/world/entity/ai/attributes/Attribute;)Lnet/minecraft/world/entity/ai/attributes/AttributeInstance; + +diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java +new file mode 100644 +index 0000000000000000000000000000000000000000..12135ffeacd648f6bc4d7d327059ea1a7e8c79c4 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java +@@ -0,0 +1,30 @@ ++package io.papermc.paper.attribute; ++ ++import net.minecraft.world.entity.ai.attributes.AttributeInstance; ++import org.bukkit.attribute.Attribute; ++import org.bukkit.attribute.AttributeModifier; ++import org.bukkit.craftbukkit.attribute.CraftAttributeInstance; ++ ++import java.util.Collection; ++ ++public class UnmodifiableAttributeInstance extends CraftAttributeInstance { ++ ++ public UnmodifiableAttributeInstance(AttributeInstance handle, Attribute attribute) { ++ super(handle, attribute); ++ } ++ ++ @Override ++ public void setBaseValue(double d) { ++ throw new UnsupportedOperationException("Cannot modify default attributes"); ++ } ++ ++ @Override ++ public void addModifier(AttributeModifier modifier) { ++ throw new UnsupportedOperationException("Cannot modify default attributes"); ++ } ++ ++ @Override ++ public void removeModifier(AttributeModifier modifier) { ++ throw new UnsupportedOperationException("Cannot modify default attributes"); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..68044b8439c302114240d0ae4da93ab3e0789cd2 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java +@@ -0,0 +1,32 @@ ++package io.papermc.paper.attribute; ++ ++import net.minecraft.world.entity.ai.attributes.AttributeSupplier; ++import org.bukkit.attribute.Attributable; ++import org.bukkit.attribute.Attribute; ++import org.bukkit.attribute.AttributeInstance; ++import org.bukkit.craftbukkit.attribute.CraftAttribute; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++public class UnmodifiableAttributeMap implements Attributable { ++ ++ private final AttributeSupplier handle; ++ ++ public UnmodifiableAttributeMap(@NotNull AttributeSupplier handle) { ++ this.handle = handle; ++ } ++ ++ @Override ++ public @Nullable AttributeInstance getAttribute(@NotNull Attribute attribute) { ++ net.minecraft.world.entity.ai.attributes.Attribute nmsAttribute = CraftAttribute.bukkitToMinecraft(attribute); ++ if (!this.handle.hasAttribute(nmsAttribute)) { ++ return null; ++ } ++ return new UnmodifiableAttributeInstance(this.handle.getAttributeInstance(nmsAttribute), attribute); ++ } ++ ++ @Override ++ public void registerAttribute(@NotNull Attribute attribute) { ++ throw new UnsupportedOperationException("Cannot register new attributes here"); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index b89171cb89e3d38f3260ead8549cccde904db7c4..efa904eac6e3c2995f5a03fca44340d083e73b77 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -576,6 +576,18 @@ public final class CraftMagicNumbers implements UnsafeValues { + public int getProtocolVersion() { + return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); + } ++ ++ @Override ++ public boolean hasDefaultEntityAttributes(NamespacedKey bukkitEntityKey) { ++ return net.minecraft.world.entity.ai.attributes.DefaultAttributes.hasSupplier(net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); ++ } ++ ++ @Override ++ public org.bukkit.attribute.Attributable getDefaultEntityAttributes(NamespacedKey bukkitEntityKey) { ++ Preconditions.checkArgument(hasDefaultEntityAttributes(bukkitEntityKey), bukkitEntityKey + " doesn't have default attributes"); ++ var supplier = net.minecraft.world.entity.ai.attributes.DefaultAttributes.getSupplier((net.minecraft.world.entity.EntityType) net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); ++ return new io.papermc.paper.attribute.UnmodifiableAttributeMap(supplier); ++ } + // Paper end + + /** +diff --git a/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e8cdfa385230d3de202122e4df5e07f61f80ce75 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java +@@ -0,0 +1,39 @@ ++package io.papermc.paper.attribute; ++ ++import org.bukkit.attribute.Attributable; ++import org.bukkit.attribute.Attribute; ++import org.bukkit.attribute.AttributeInstance; ++import org.bukkit.attribute.AttributeModifier; ++import org.bukkit.entity.EntityType; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertFalse; ++import static org.junit.jupiter.api.Assertions.assertNotNull; ++import static org.junit.jupiter.api.Assertions.assertThrows; ++import static org.junit.jupiter.api.Assertions.assertTrue; ++ ++public class EntityTypeAttributesTest extends AbstractTestingBase { ++ ++ @Test ++ public void testIllegalEntity() { ++ assertFalse(EntityType.EGG.hasDefaultAttributes()); ++ assertThrows(IllegalArgumentException.class, () -> EntityType.EGG.getDefaultAttributes()); ++ } ++ ++ @Test ++ public void testLegalEntity() { ++ assertTrue(EntityType.ZOMBIE.hasDefaultAttributes()); ++ EntityType.ZOMBIE.getDefaultAttributes(); ++ } ++ ++ @Test ++ public void testUnmodifiabilityOfAttributable() { ++ Attributable attributable = EntityType.ZOMBIE.getDefaultAttributes(); ++ assertThrows(UnsupportedOperationException.class, () -> attributable.registerAttribute(Attribute.GENERIC_ATTACK_DAMAGE)); ++ AttributeInstance instance = attributable.getAttribute(Attribute.GENERIC_FOLLOW_RANGE); ++ assertNotNull(instance); ++ assertThrows(UnsupportedOperationException.class, () -> instance.addModifier(new AttributeModifier("test", 3, AttributeModifier.Operation.ADD_NUMBER))); ++ assertThrows(UnsupportedOperationException.class, () -> instance.setBaseValue(3.2)); ++ } ++} diff --git a/patches/server/0620-Get-entity-default-attributes.patch b/patches/server/0620-Get-entity-default-attributes.patch deleted file mode 100644 index 3fffa9139468..000000000000 --- a/patches/server/0620-Get-entity-default-attributes.patch +++ /dev/null @@ -1,150 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 20 Aug 2021 13:03:21 -0700 -Subject: [PATCH] Get entity default attributes - -== AT == -public net.minecraft.world.entity.ai.attributes.AttributeSupplier getAttributeInstance(Lnet/minecraft/world/entity/ai/attributes/Attribute;)Lnet/minecraft/world/entity/ai/attributes/AttributeInstance; - -diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java -new file mode 100644 -index 0000000000000000000000000000000000000000..12135ffeacd648f6bc4d7d327059ea1a7e8c79c4 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java -@@ -0,0 +1,30 @@ -+package io.papermc.paper.attribute; -+ -+import net.minecraft.world.entity.ai.attributes.AttributeInstance; -+import org.bukkit.attribute.Attribute; -+import org.bukkit.attribute.AttributeModifier; -+import org.bukkit.craftbukkit.attribute.CraftAttributeInstance; -+ -+import java.util.Collection; -+ -+public class UnmodifiableAttributeInstance extends CraftAttributeInstance { -+ -+ public UnmodifiableAttributeInstance(AttributeInstance handle, Attribute attribute) { -+ super(handle, attribute); -+ } -+ -+ @Override -+ public void setBaseValue(double d) { -+ throw new UnsupportedOperationException("Cannot modify default attributes"); -+ } -+ -+ @Override -+ public void addModifier(AttributeModifier modifier) { -+ throw new UnsupportedOperationException("Cannot modify default attributes"); -+ } -+ -+ @Override -+ public void removeModifier(AttributeModifier modifier) { -+ throw new UnsupportedOperationException("Cannot modify default attributes"); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..68044b8439c302114240d0ae4da93ab3e0789cd2 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java -@@ -0,0 +1,32 @@ -+package io.papermc.paper.attribute; -+ -+import net.minecraft.world.entity.ai.attributes.AttributeSupplier; -+import org.bukkit.attribute.Attributable; -+import org.bukkit.attribute.Attribute; -+import org.bukkit.attribute.AttributeInstance; -+import org.bukkit.craftbukkit.attribute.CraftAttribute; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+public class UnmodifiableAttributeMap implements Attributable { -+ -+ private final AttributeSupplier handle; -+ -+ public UnmodifiableAttributeMap(@NotNull AttributeSupplier handle) { -+ this.handle = handle; -+ } -+ -+ @Override -+ public @Nullable AttributeInstance getAttribute(@NotNull Attribute attribute) { -+ net.minecraft.world.entity.ai.attributes.Attribute nmsAttribute = CraftAttribute.bukkitToMinecraft(attribute); -+ if (!this.handle.hasAttribute(nmsAttribute)) { -+ return null; -+ } -+ return new UnmodifiableAttributeInstance(this.handle.getAttributeInstance(nmsAttribute), attribute); -+ } -+ -+ @Override -+ public void registerAttribute(@NotNull Attribute attribute) { -+ throw new UnsupportedOperationException("Cannot register new attributes here"); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index fd35c5102aa4e14f5eb707884be64120c2b13276..c9be954c94adbc982e2ef9d406be4290e75bc892 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -561,6 +561,18 @@ public final class CraftMagicNumbers implements UnsafeValues { - public int getProtocolVersion() { - return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); - } -+ -+ @Override -+ public boolean hasDefaultEntityAttributes(NamespacedKey bukkitEntityKey) { -+ return net.minecraft.world.entity.ai.attributes.DefaultAttributes.hasSupplier(net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); -+ } -+ -+ @Override -+ public org.bukkit.attribute.Attributable getDefaultEntityAttributes(NamespacedKey bukkitEntityKey) { -+ Preconditions.checkArgument(hasDefaultEntityAttributes(bukkitEntityKey), bukkitEntityKey + " doesn't have default attributes"); -+ var supplier = net.minecraft.world.entity.ai.attributes.DefaultAttributes.getSupplier((net.minecraft.world.entity.EntityType) net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); -+ return new io.papermc.paper.attribute.UnmodifiableAttributeMap(supplier); -+ } - // Paper end - - /** -diff --git a/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e8cdfa385230d3de202122e4df5e07f61f80ce75 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java -@@ -0,0 +1,39 @@ -+package io.papermc.paper.attribute; -+ -+import org.bukkit.attribute.Attributable; -+import org.bukkit.attribute.Attribute; -+import org.bukkit.attribute.AttributeInstance; -+import org.bukkit.attribute.AttributeModifier; -+import org.bukkit.entity.EntityType; -+import org.bukkit.support.AbstractTestingBase; -+import org.junit.jupiter.api.Test; -+ -+import static org.junit.jupiter.api.Assertions.assertFalse; -+import static org.junit.jupiter.api.Assertions.assertNotNull; -+import static org.junit.jupiter.api.Assertions.assertThrows; -+import static org.junit.jupiter.api.Assertions.assertTrue; -+ -+public class EntityTypeAttributesTest extends AbstractTestingBase { -+ -+ @Test -+ public void testIllegalEntity() { -+ assertFalse(EntityType.EGG.hasDefaultAttributes()); -+ assertThrows(IllegalArgumentException.class, () -> EntityType.EGG.getDefaultAttributes()); -+ } -+ -+ @Test -+ public void testLegalEntity() { -+ assertTrue(EntityType.ZOMBIE.hasDefaultAttributes()); -+ EntityType.ZOMBIE.getDefaultAttributes(); -+ } -+ -+ @Test -+ public void testUnmodifiabilityOfAttributable() { -+ Attributable attributable = EntityType.ZOMBIE.getDefaultAttributes(); -+ assertThrows(UnsupportedOperationException.class, () -> attributable.registerAttribute(Attribute.GENERIC_ATTACK_DAMAGE)); -+ AttributeInstance instance = attributable.getAttribute(Attribute.GENERIC_FOLLOW_RANGE); -+ assertNotNull(instance); -+ assertThrows(UnsupportedOperationException.class, () -> instance.addModifier(new AttributeModifier("test", 3, AttributeModifier.Operation.ADD_NUMBER))); -+ assertThrows(UnsupportedOperationException.class, () -> instance.setBaseValue(3.2)); -+ } -+} diff --git a/patches/server/0621-Left-handed-API.patch b/patches/server/0620-Left-handed-API.patch similarity index 100% rename from patches/server/0621-Left-handed-API.patch rename to patches/server/0620-Left-handed-API.patch diff --git a/patches/server/0622-Add-more-advancement-API.patch b/patches/server/0621-Add-more-advancement-API.patch similarity index 100% rename from patches/server/0622-Add-more-advancement-API.patch rename to patches/server/0621-Add-more-advancement-API.patch diff --git a/patches/server/0623-Add-ItemFactory-getSpawnEgg-API.patch b/patches/server/0622-Add-ItemFactory-getSpawnEgg-API.patch similarity index 100% rename from patches/server/0623-Add-ItemFactory-getSpawnEgg-API.patch rename to patches/server/0622-Add-ItemFactory-getSpawnEgg-API.patch diff --git a/patches/server/0623-Add-critical-damage-API.patch b/patches/server/0623-Add-critical-damage-API.patch new file mode 100644 index 000000000000..55b032eb4100 --- /dev/null +++ b/patches/server/0623-Add-critical-damage-API.patch @@ -0,0 +1,111 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dodison +Date: Mon, 26 Jul 2021 17:32:36 +0200 +Subject: [PATCH] Add critical damage API + + +diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java +index e055106c50944c9a23bc59fe23f58a62c5deb7e4..8187feffe52efa5c887f1910e581a37c6e439401 100644 +--- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java ++++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java +@@ -234,4 +234,18 @@ public class DamageSource { + public Holder typeHolder() { + return this.type; + } ++ ++ // Paper start - add critical damage API ++ private boolean critical; ++ public boolean isCritical() { ++ return this.critical; ++ } ++ public DamageSource critical() { ++ return this.critical(true); ++ } ++ public DamageSource critical(boolean critical) { ++ this.critical = critical; ++ return this; ++ } ++ // Paper end - add critical damage API + } +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 bc205c48460f6b90fbdc83f979f7bf029c1e0f9d..e4cb4a12c8623d19ccceccaceeca528edf3848e4 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1256,7 +1256,7 @@ public abstract class Player extends LivingEntity { + flag1 = true; + } + +- boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround() && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity; ++ boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround() && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity; // Paper - Add critical damage API; diff on change + + flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits + flag2 = flag2 && !this.isSprinting(); +@@ -1296,7 +1296,7 @@ public abstract class Player extends LivingEntity { + } + + Vec3 vec3d = target.getDeltaMovement(); +- boolean flag5 = target.hurt(this.damageSources().playerAttack(this), f); ++ boolean flag5 = target.hurt(this.damageSources().playerAttack(this).critical(flag2), f); // Paper - add critical damage API + + if (flag5) { + if (i > 0) { +@@ -1324,7 +1324,7 @@ public abstract class Player extends LivingEntity { + + if (entityliving != this && entityliving != target && !this.isAlliedTo((Entity) entityliving) && (!(entityliving instanceof ArmorStand) || !((ArmorStand) entityliving).isMarker()) && this.distanceToSqr((Entity) entityliving) < 9.0D) { + // CraftBukkit start - Only apply knockback if the damage hits +- if (entityliving.hurt(this.damageSources().playerAttack(this).sweep(), f4)) { ++ if (entityliving.hurt(this.damageSources().playerAttack(this).sweep().critical(flag2), f4)) { // Paper - add critical damage API + entityliving.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this, EntityKnockbackEvent.KnockbackCause.SWEEP_ATTACK); // CraftBukkit + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index 0e1d4bd6f70e439b33eca57bf06e9e090825f58a..5f75e54cde19614461dd8375ded1d6b3c395f674 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -379,6 +379,7 @@ public abstract class AbstractArrow extends Projectile { + } + } + ++ if (this.isCritArrow()) damagesource = damagesource.critical(); // Paper - add critical damage API + boolean flag = entity.getType() == EntityType.ENDERMAN; + int k = entity.getRemainingFireTicks(); + boolean flag1 = entity.getType().is(EntityTypeTags.DEFLECTS_ARROWS); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index e57bafa1d071a2fefe44a150bc5754e76d78cdd9..cbac5fa3864e7b2298f75a44f6ef3f0000c9e765 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1061,7 +1061,7 @@ public class CraftEventFactory { + return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), entity, DamageCause.BLOCK_EXPLOSION, bukkitDamageSource, modifiers, modifierFunctions, cancelled); + } + DamageCause damageCause = (damager.getBukkitEntity() instanceof org.bukkit.entity.TNTPrimed) ? DamageCause.BLOCK_EXPLOSION : DamageCause.ENTITY_EXPLOSION; +- return CraftEventFactory.callEntityDamageEvent(damager, entity, damageCause, bukkitDamageSource, modifiers, modifierFunctions, cancelled); ++ return CraftEventFactory.callEntityDamageEvent(damager, entity, damageCause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API + } else if (damager != null || source.getDirectEntity() != null) { + DamageCause cause = (source.isSweep()) ? DamageCause.ENTITY_SWEEP_ATTACK : DamageCause.ENTITY_ATTACK; + +@@ -1091,7 +1091,7 @@ public class CraftEventFactory { + cause = DamageCause.MAGIC; + } + +- return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled); ++ return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API + } else if (source.is(DamageTypes.FELL_OUT_OF_WORLD)) { + return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), entity, DamageCause.VOID, bukkitDamageSource, modifiers, modifierFunctions, cancelled); + } else if (source.is(DamageTypes.LAVA)) { +@@ -1149,13 +1149,13 @@ public class CraftEventFactory { + cause = DamageCause.CUSTOM; + } + +- return CraftEventFactory.callEntityDamageEvent((Entity) null, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled); ++ return CraftEventFactory.callEntityDamageEvent((Entity) null, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API + } + +- private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, org.bukkit.damage.DamageSource bukkitDamageSource, Map modifiers, Map> modifierFunctions, boolean cancelled) { ++ private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, org.bukkit.damage.DamageSource bukkitDamageSource, Map modifiers, Map> modifierFunctions, boolean cancelled, boolean critical) { // Paper - add critical damage API + EntityDamageEvent event; + if (damager != null) { +- event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions); ++ event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions, critical); + } else { + event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions); + } diff --git a/patches/server/0624-Add-critical-damage-API.patch b/patches/server/0624-Add-critical-damage-API.patch deleted file mode 100644 index cc3685c6ae19..000000000000 --- a/patches/server/0624-Add-critical-damage-API.patch +++ /dev/null @@ -1,134 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: dodison -Date: Mon, 26 Jul 2021 17:32:36 +0200 -Subject: [PATCH] Add critical damage API - - -diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -index df8c88bfa749e02f633350446101dcce05db7ac1..ed1277fad60992344b94f8a939febaca3edd9702 100644 ---- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java -+++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -@@ -191,4 +191,18 @@ public class DamageSource { - public Holder typeHolder() { - return this.type; - } -+ -+ // Paper start - add critical damage API -+ private boolean critical; -+ public boolean isCritical() { -+ return this.critical; -+ } -+ public DamageSource critical() { -+ return this.critical(true); -+ } -+ public DamageSource critical(boolean critical) { -+ this.critical = critical; -+ return this; -+ } -+ // Paper end - add critical damage API - } -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 00477c81dc3f5d8289b08881b119b699552e5722..89c1928b1bf6f3a291794c5582b5e1efb4b74327 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1255,7 +1255,7 @@ public abstract class Player extends LivingEntity { - flag1 = true; - } - -- boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround() && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity; -+ boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround() && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity; // Paper - Add critical damage API; diff on change - - flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits - flag2 = flag2 && !this.isSprinting(); -@@ -1295,7 +1295,7 @@ public abstract class Player extends LivingEntity { - } - - Vec3 vec3d = target.getDeltaMovement(); -- boolean flag5 = target.hurt(this.damageSources().playerAttack(this), f); -+ boolean flag5 = target.hurt(this.damageSources().playerAttack(this).critical(flag2), f); // Paper - add critical damage API - - if (flag5) { - if (i > 0) { -@@ -1323,7 +1323,7 @@ public abstract class Player extends LivingEntity { - - if (entityliving != this && entityliving != target && !this.isAlliedTo((Entity) entityliving) && (!(entityliving instanceof ArmorStand) || !((ArmorStand) entityliving).isMarker()) && this.distanceToSqr((Entity) entityliving) < 9.0D) { - // CraftBukkit start - Only apply knockback if the damage hits -- if (entityliving.hurt(this.damageSources().playerAttack(this).sweep(), f4)) { -+ if (entityliving.hurt(this.damageSources().playerAttack(this).sweep().critical(flag2), f4)) { // Paper - add critical damage API - entityliving.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this); // Paper - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index 0e1d4bd6f70e439b33eca57bf06e9e090825f58a..5f75e54cde19614461dd8375ded1d6b3c395f674 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -379,6 +379,7 @@ public abstract class AbstractArrow extends Projectile { - } - } - -+ if (this.isCritArrow()) damagesource = damagesource.critical(); // Paper - add critical damage API - boolean flag = entity.getType() == EntityType.ENDERMAN; - int k = entity.getRemainingFireTicks(); - boolean flag1 = entity.getType().is(EntityTypeTags.DEFLECTS_ARROWS); -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 833011c1f1746e000adc72ab092295fd4fab2ab8..bc5966ced62aeeed784077517658d7f28550c449 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1069,7 +1069,7 @@ public class CraftEventFactory { - } else { - damageCause = DamageCause.ENTITY_EXPLOSION; - } -- event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), entity.getBukkitEntity(), damageCause, modifiers, modifierFunctions); -+ event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), entity.getBukkitEntity(), damageCause, modifiers, modifierFunctions, source.isCritical()); // Paper - add critical damage API - } - event.setCancelled(cancelled); - -@@ -1101,7 +1101,7 @@ public class CraftEventFactory { - cause = DamageCause.SONIC_BOOM; - } - -- return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, modifiers, modifierFunctions, cancelled); -+ return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API - } else if (source.is(DamageTypes.FELL_OUT_OF_WORLD)) { - EntityDamageEvent event = new EntityDamageByBlockEvent(null, entity.getBukkitEntity(), DamageCause.VOID, modifiers, modifierFunctions); - event.setCancelled(cancelled); -@@ -1171,7 +1171,7 @@ public class CraftEventFactory { - } else { - throw new IllegalStateException(String.format("Unhandled damage of %s by %s from %s", entity, damager.getHandle(), source.getMsgId())); - } -- EntityDamageEvent event = new EntityDamageByEntityEvent(damager, entity.getBukkitEntity(), cause, modifiers, modifierFunctions); -+ EntityDamageEvent event = new EntityDamageByEntityEvent(damager, entity.getBukkitEntity(), cause, modifiers, modifierFunctions, source.isCritical()); // Paper - add critical damage API - event.setCancelled(cancelled); - CraftEventFactory.callEvent(event); - if (!event.isCancelled()) { -@@ -1220,20 +1220,28 @@ public class CraftEventFactory { - } - - if (cause != null) { -- return CraftEventFactory.callEntityDamageEvent(null, entity, cause, modifiers, modifierFunctions, cancelled); -+ return CraftEventFactory.callEntityDamageEvent(null, entity, cause, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API - } - - throw new IllegalStateException(String.format("Unhandled damage of %s from %s", entity, source.getMsgId())); - } - -+ @Deprecated // Paper - Add critical damage API - private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, Map modifiers, Map> modifierFunctions) { - return CraftEventFactory.callEntityDamageEvent(damager, damagee, cause, modifiers, modifierFunctions, false); - } - -+ // Paper start - Add critical damage API -+ @Deprecated - private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, Map modifiers, Map> modifierFunctions, boolean cancelled) { -+ return CraftEventFactory.callEntityDamageEvent(damager, damagee, cause, modifiers, modifierFunctions, cancelled, false); -+ } -+ -+ private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, Map modifiers, Map> modifierFunctions, boolean cancelled, boolean critical) { -+ // Paper end - EntityDamageEvent event; - if (damager != null) { -- event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); -+ event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions, critical); // Paper - add critical damage API - } else { - event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); - } diff --git a/patches/server/0625-Fix-issues-with-mob-conversion.patch b/patches/server/0624-Fix-issues-with-mob-conversion.patch similarity index 100% rename from patches/server/0625-Fix-issues-with-mob-conversion.patch rename to patches/server/0624-Fix-issues-with-mob-conversion.patch diff --git a/patches/server/0625-Add-isCollidable-methods-to-various-places.patch b/patches/server/0625-Add-isCollidable-methods-to-various-places.patch new file mode 100644 index 000000000000..ce6e50fe3c49 --- /dev/null +++ b/patches/server/0625-Add-isCollidable-methods-to-various-places.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 4 Nov 2021 11:50:40 -0700 +Subject: [PATCH] Add isCollidable methods to various places + +== AT == +public net.minecraft.world.level.block.state.BlockBehaviour hasCollision + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index ce297420f695404356655b1df2847a32fb98ec59..068b3735b6c50a7a2053c7dc39856f728fb7218a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -457,6 +457,11 @@ public class CraftBlock implements Block { + public boolean isSolid() { + return this.getNMS().blocksMotion(); + } ++ ++ @Override ++ public boolean isCollidable() { ++ return getNMS().getBlock().hasCollision; ++ } + // Paper end + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +index 928a301627134b49915b0ceaeabb7dc350605dc2..08716e757b2e100f7bc47a046f02db664d882aba 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +@@ -331,4 +331,11 @@ public class CraftBlockState implements BlockState { + public CraftBlockState copy() { + return new CraftBlockState(this); + } ++ ++ // Paper start ++ @Override ++ public boolean isCollidable() { ++ return this.data.getBlock().hasCollision; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index efa904eac6e3c2995f5a03fca44340d083e73b77..609b103cb9af3b0554bf1116306874fe98c8534c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -588,6 +588,12 @@ public final class CraftMagicNumbers implements UnsafeValues { + var supplier = net.minecraft.world.entity.ai.attributes.DefaultAttributes.getSupplier((net.minecraft.world.entity.EntityType) net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); + return new io.papermc.paper.attribute.UnmodifiableAttributeMap(supplier); + } ++ ++ @Override ++ public boolean isCollidable(Material material) { ++ Preconditions.checkArgument(material.isBlock(), material + " is not a block"); ++ return getBlock(material).hasCollision; ++ } + // Paper end + + /** diff --git a/patches/server/0626-Add-isCollidable-methods-to-various-places.patch b/patches/server/0626-Add-isCollidable-methods-to-various-places.patch deleted file mode 100644 index fa5484bbdc1c..000000000000 --- a/patches/server/0626-Add-isCollidable-methods-to-various-places.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 4 Nov 2021 11:50:40 -0700 -Subject: [PATCH] Add isCollidable methods to various places - -== AT == -public net.minecraft.world.level.block.state.BlockBehaviour hasCollision - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index ce297420f695404356655b1df2847a32fb98ec59..068b3735b6c50a7a2053c7dc39856f728fb7218a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -457,6 +457,11 @@ public class CraftBlock implements Block { - public boolean isSolid() { - return this.getNMS().blocksMotion(); - } -+ -+ @Override -+ public boolean isCollidable() { -+ return getNMS().getBlock().hasCollision; -+ } - // Paper end - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -index 928a301627134b49915b0ceaeabb7dc350605dc2..08716e757b2e100f7bc47a046f02db664d882aba 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -@@ -331,4 +331,11 @@ public class CraftBlockState implements BlockState { - public CraftBlockState copy() { - return new CraftBlockState(this); - } -+ -+ // Paper start -+ @Override -+ public boolean isCollidable() { -+ return this.data.getBlock().hasCollision; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index c9be954c94adbc982e2ef9d406be4290e75bc892..35bc0c3bfda51e3eec2ee1fc68d207ddba4239ef 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -573,6 +573,12 @@ public final class CraftMagicNumbers implements UnsafeValues { - var supplier = net.minecraft.world.entity.ai.attributes.DefaultAttributes.getSupplier((net.minecraft.world.entity.EntityType) net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); - return new io.papermc.paper.attribute.UnmodifiableAttributeMap(supplier); - } -+ -+ @Override -+ public boolean isCollidable(Material material) { -+ Preconditions.checkArgument(material.isBlock(), material + " is not a block"); -+ return getBlock(material).hasCollision; -+ } - // Paper end - - /** diff --git a/patches/server/0627-Goat-ram-API.patch b/patches/server/0626-Goat-ram-API.patch similarity index 100% rename from patches/server/0627-Goat-ram-API.patch rename to patches/server/0626-Goat-ram-API.patch diff --git a/patches/server/0628-Add-API-for-resetting-a-single-score.patch b/patches/server/0627-Add-API-for-resetting-a-single-score.patch similarity index 100% rename from patches/server/0628-Add-API-for-resetting-a-single-score.patch rename to patches/server/0627-Add-API-for-resetting-a-single-score.patch diff --git a/patches/server/0628-Add-Raw-Byte-Entity-Serialization.patch b/patches/server/0628-Add-Raw-Byte-Entity-Serialization.patch new file mode 100644 index 000000000000..ca05abcafd72 --- /dev/null +++ b/patches/server/0628-Add-Raw-Byte-Entity-Serialization.patch @@ -0,0 +1,85 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sun, 24 Oct 2021 16:20:31 -0400 +Subject: [PATCH] Add Raw Byte Entity Serialization + +== AT == +public net.minecraft.world.entity.Entity setLevel(Lnet/minecraft/world/level/Level;)V + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7295dfa7010297e019aba979bda9ff133d05a3c1..0d68d8016a033ae2745e27d6a39d9b529bb2f675 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2065,6 +2065,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + } + ++ // Paper start - Entity serialization api ++ public boolean serializeEntity(CompoundTag compound) { ++ List pass = new java.util.ArrayList<>(this.getPassengers()); ++ this.passengers = ImmutableList.of(); ++ boolean result = save(compound); ++ this.passengers = ImmutableList.copyOf(pass); ++ return result; ++ } ++ // Paper end - Entity serialization api + public boolean save(CompoundTag nbt) { + return this.isPassenger() ? false : this.saveAsPassenger(nbt); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index a6492b6a9f66d8bcda8928fadf0a5920ff7f0dab..71845027ba1b755ead76cf75d011547d9910597a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1076,5 +1076,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + return set; + } ++ ++ @Override ++ public boolean spawnAt(Location location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ Preconditions.checkNotNull(location, "location cannot be null"); ++ Preconditions.checkNotNull(reason, "reason cannot be null"); ++ this.entity.setLevel(((CraftWorld) location.getWorld()).getHandle()); ++ this.entity.setPos(location.getX(), location.getY(), location.getZ()); ++ this.entity.setRot(location.getYaw(), location.getPitch()); ++ return !this.entity.valid && this.entity.level().addFreshEntity(this.entity, reason); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 609b103cb9af3b0554bf1116306874fe98c8534c..3f582c5653e13875cce4ef8ecd279d8a3d2b2dc2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -499,6 +499,32 @@ public final class CraftMagicNumbers implements UnsafeValues { + return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of(compound)); + } + ++ @Override ++ public byte[] serializeEntity(org.bukkit.entity.Entity entity) { ++ Preconditions.checkNotNull(entity, "null cannot be serialized"); ++ Preconditions.checkArgument(entity instanceof org.bukkit.craftbukkit.entity.CraftEntity, "only CraftEntities can be serialized"); ++ ++ CompoundTag compound = new CompoundTag(); ++ ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().serializeEntity(compound); ++ return serializeNbtToBytes(compound); ++ } ++ ++ @Override ++ public org.bukkit.entity.Entity deserializeEntity(byte[] data, org.bukkit.World world, boolean preserveUUID) { ++ Preconditions.checkNotNull(data, "null cannot be deserialized"); ++ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); ++ ++ CompoundTag compound = deserializeNbtFromBytes(data); ++ int dataVersion = compound.getInt("DataVersion"); ++ compound = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ENTITY, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue(); ++ if (!preserveUUID) { ++ // Generate a new UUID so we don't have to worry about deserializing the same entity twice ++ compound.remove("UUID"); ++ } ++ return net.minecraft.world.entity.EntityType.create(compound, ((org.bukkit.craftbukkit.CraftWorld) world).getHandle()) ++ .orElseThrow(() -> new IllegalArgumentException("An ID was not found for the data. Did you downgrade?")).getBukkitEntity(); ++ } ++ + private byte[] serializeNbtToBytes(CompoundTag compound) { + compound.putInt("DataVersion", getDataVersion()); + java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); diff --git a/patches/server/0629-Add-Raw-Byte-Entity-Serialization.patch b/patches/server/0629-Add-Raw-Byte-Entity-Serialization.patch deleted file mode 100644 index 77fee154d0f3..000000000000 --- a/patches/server/0629-Add-Raw-Byte-Entity-Serialization.patch +++ /dev/null @@ -1,85 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Sun, 24 Oct 2021 16:20:31 -0400 -Subject: [PATCH] Add Raw Byte Entity Serialization - -== AT == -public net.minecraft.world.entity.Entity setLevel(Lnet/minecraft/world/level/Level;)V - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 8825afb72b3d85840a15b02dbd3d914de06a62a8..c19cc2af8bf0d2f5a778cdd05560340fad6847e1 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2061,6 +2061,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - } - -+ // Paper start - Entity serialization api -+ public boolean serializeEntity(CompoundTag compound) { -+ List pass = new java.util.ArrayList<>(this.getPassengers()); -+ this.passengers = ImmutableList.of(); -+ boolean result = save(compound); -+ this.passengers = ImmutableList.copyOf(pass); -+ return result; -+ } -+ // Paper end - Entity serialization api - public boolean save(CompoundTag nbt) { - return this.isPassenger() ? false : this.saveAsPassenger(nbt); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index a6492b6a9f66d8bcda8928fadf0a5920ff7f0dab..71845027ba1b755ead76cf75d011547d9910597a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1076,5 +1076,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - } - return set; - } -+ -+ @Override -+ public boolean spawnAt(Location location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { -+ Preconditions.checkNotNull(location, "location cannot be null"); -+ Preconditions.checkNotNull(reason, "reason cannot be null"); -+ this.entity.setLevel(((CraftWorld) location.getWorld()).getHandle()); -+ this.entity.setPos(location.getX(), location.getY(), location.getZ()); -+ this.entity.setRot(location.getYaw(), location.getPitch()); -+ return !this.entity.valid && this.entity.level().addFreshEntity(this.entity, reason); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 35bc0c3bfda51e3eec2ee1fc68d207ddba4239ef..34c19a422de27cd6aa08159186a0180215c0837d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -484,6 +484,32 @@ public final class CraftMagicNumbers implements UnsafeValues { - return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of(compound)); - } - -+ @Override -+ public byte[] serializeEntity(org.bukkit.entity.Entity entity) { -+ Preconditions.checkNotNull(entity, "null cannot be serialized"); -+ Preconditions.checkArgument(entity instanceof org.bukkit.craftbukkit.entity.CraftEntity, "only CraftEntities can be serialized"); -+ -+ CompoundTag compound = new CompoundTag(); -+ ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().serializeEntity(compound); -+ return serializeNbtToBytes(compound); -+ } -+ -+ @Override -+ public org.bukkit.entity.Entity deserializeEntity(byte[] data, org.bukkit.World world, boolean preserveUUID) { -+ Preconditions.checkNotNull(data, "null cannot be deserialized"); -+ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); -+ -+ CompoundTag compound = deserializeNbtFromBytes(data); -+ int dataVersion = compound.getInt("DataVersion"); -+ compound = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ENTITY, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue(); -+ if (!preserveUUID) { -+ // Generate a new UUID so we don't have to worry about deserializing the same entity twice -+ compound.remove("UUID"); -+ } -+ return net.minecraft.world.entity.EntityType.create(compound, ((org.bukkit.craftbukkit.CraftWorld) world).getHandle()) -+ .orElseThrow(() -> new IllegalArgumentException("An ID was not found for the data. Did you downgrade?")).getBukkitEntity(); -+ } -+ - private byte[] serializeNbtToBytes(CompoundTag compound) { - compound.putInt("DataVersion", getDataVersion()); - java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); diff --git a/patches/server/0630-Vanilla-command-permission-fixes.patch b/patches/server/0629-Vanilla-command-permission-fixes.patch similarity index 100% rename from patches/server/0630-Vanilla-command-permission-fixes.patch rename to patches/server/0629-Vanilla-command-permission-fixes.patch diff --git a/patches/server/0630-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch b/patches/server/0630-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch new file mode 100644 index 000000000000..d33cd40a43f8 --- /dev/null +++ b/patches/server/0630-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 11 Mar 2021 03:03:32 -0800 +Subject: [PATCH] Do not run close logic for inventories on chunk unload + +Still call the event and change the active container though. We +want to avoid close logic because it's possible to load the +chunk through it. This should also be OK from a leak prevention/ +state desync POV because the TE is getting unloaded anyways. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index c83f2be16953b5cc009ddef81fd082295f3b2f71..f2015ffc83696b7ab6a552d4bb6fec1d8f6c6031 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1270,9 +1270,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + // Spigot Start + for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) { + if (tileentity instanceof net.minecraft.world.Container) { ++ // Paper start - this area looks like it can load chunks, change the behavior ++ // chests for example can apply physics to the world ++ // so instead we just change the active container and call the event + for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { +- h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason ++ ((org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason + } ++ // Paper end - this area looks like it can load chunks, change the behavior + } + } + // Spigot End +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index fb86824192f1fc850a55905757c65cafec1edb6a..48edd0bf7a6f24aaf582a96ee7cb4c29c9e3598a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1581,6 +1581,18 @@ public class ServerPlayer extends Player { + this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); + this.doCloseContainer(); + } ++ // Paper start - special close for unloaded inventory ++ @Override ++ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // copied from above ++ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit ++ // Paper end ++ // copied from below ++ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); ++ this.containerMenu = this.inventoryMenu; ++ // do not run close logic ++ } ++ // Paper end - special close for unloaded inventory + + @Override + public void doCloseContainer() { +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 e4cb4a12c8623d19ccceccaceeca528edf3848e4..1a2083e5887dc8ba0ad908cc961dd090cbc165f5 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -509,6 +509,11 @@ public abstract class Player extends LivingEntity { + this.containerMenu = this.inventoryMenu; + } + // Paper end - Inventory close reason ++ // Paper start - special close for unloaded inventory ++ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ this.containerMenu = this.inventoryMenu; ++ } ++ // Paper end - special close for unloaded inventory + + public void closeContainer() { + this.containerMenu = this.inventoryMenu; diff --git a/patches/server/0631-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch b/patches/server/0631-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch deleted file mode 100644 index f2c8fc9b3353..000000000000 --- a/patches/server/0631-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 11 Mar 2021 03:03:32 -0800 -Subject: [PATCH] Do not run close logic for inventories on chunk unload - -Still call the event and change the active container though. We -want to avoid close logic because it's possible to load the -chunk through it. This should also be OK from a leak prevention/ -state desync POV because the TE is getting unloaded anyways. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index c83f2be16953b5cc009ddef81fd082295f3b2f71..f2015ffc83696b7ab6a552d4bb6fec1d8f6c6031 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1270,9 +1270,13 @@ public class ServerLevel extends Level implements WorldGenLevel { - // Spigot Start - for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) { - if (tileentity instanceof net.minecraft.world.Container) { -+ // Paper start - this area looks like it can load chunks, change the behavior -+ // chests for example can apply physics to the world -+ // so instead we just change the active container and call the event - for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { -- h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason -+ ((org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason - } -+ // Paper end - this area looks like it can load chunks, change the behavior - } - } - // Spigot End -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 5fff2d791b924e402a1b861c3cff8989c19d5e3b..b04b31e599c4954d4d4176f9d99f29bf0c1fadd7 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1581,6 +1581,18 @@ public class ServerPlayer extends Player { - this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); - this.doCloseContainer(); - } -+ // Paper start - special close for unloaded inventory -+ @Override -+ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ // copied from above -+ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit -+ // Paper end -+ // copied from below -+ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); -+ this.containerMenu = this.inventoryMenu; -+ // do not run close logic -+ } -+ // Paper end - special close for unloaded inventory - - @Override - public void doCloseContainer() { -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 89c1928b1bf6f3a291794c5582b5e1efb4b74327..865b5a43d108e87dc93d1d678371efaacb65507a 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -508,6 +508,11 @@ public abstract class Player extends LivingEntity { - this.containerMenu = this.inventoryMenu; - } - // Paper end - Inventory close reason -+ // Paper start - special close for unloaded inventory -+ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ this.containerMenu = this.inventoryMenu; -+ } -+ // Paper end - special close for unloaded inventory - - public void closeContainer() { - this.containerMenu = this.inventoryMenu; diff --git a/patches/server/0632-Fix-GameProfileCache-concurrency.patch b/patches/server/0631-Fix-GameProfileCache-concurrency.patch similarity index 100% rename from patches/server/0632-Fix-GameProfileCache-concurrency.patch rename to patches/server/0631-Fix-GameProfileCache-concurrency.patch diff --git a/patches/server/0632-Improve-and-expand-AsyncCatcher.patch b/patches/server/0632-Improve-and-expand-AsyncCatcher.patch new file mode 100644 index 000000000000..b488b6b5934d --- /dev/null +++ b/patches/server/0632-Improve-and-expand-AsyncCatcher.patch @@ -0,0 +1,227 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 25 Aug 2021 20:17:12 -0700 +Subject: [PATCH] Improve and expand AsyncCatcher + +Log when the async catcher is tripped + The chunk system can swallow the exception given it's all + built with completablefuture, so ensure it is at least printed. + +Add/move several async catchers + +Async catch modifications to critical entity state + These used to be here from Spigot, but were dropped with 1.17. + Now in 1.17, this state is _even more_ critical than it was before, + so these must exist to catch stupid plugins. + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 0fef0dda0586b70dc140406b55dba1d5e23c3c97..b0df4c822ad01f1ed0f8b858e4ca012a823c7f47 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1570,6 +1570,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { // Paper ++ org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper + // Paper start - Prevent teleporting dead entities + if (player.isRemoved()) { + LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index fadbb788bff1dc1c643ffbb28774d20ba6d55ce5..16d8cc391f384bee57550f507484d60f344de76e 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1118,7 +1118,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) { +- org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot ++ // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API + if (this.isTickingEffects) { + this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause)); + return true; +diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +index bbbf6dd8e566ecdca8794e3b03765fe7e426a2bd..66ab901956ca394c251c420338643d39817a1b7e 100644 +--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -77,6 +77,7 @@ public class PersistentEntitySectionManager implements A + } + + private boolean addEntityUuid(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity add by UUID"); // Paper + if (!this.knownUuids.add(entity.getUUID())) { + PersistentEntitySectionManager.LOGGER.warn("UUID of added entity already exists: {}", entity); + return false; +@@ -90,6 +91,7 @@ public class PersistentEntitySectionManager implements A + } + + private boolean addEntity(T entity, boolean existing) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper + // Paper start - chunk system hooks + if (existing) { + // I don't want to know why this is a generic type. +@@ -145,19 +147,23 @@ public class PersistentEntitySectionManager implements A + } + + void startTicking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity start ticking"); // Paper + this.callbacks.onTickingStart(entity); + } + + void stopTicking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity stop ticking"); // Paper + this.callbacks.onTickingEnd(entity); + } + + void startTracking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity start tracking"); // Paper + this.visibleEntityStorage.add(entity); + this.callbacks.onTrackingStart(entity); + } + + void stopTracking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity stop tracking"); // Paper + this.callbacks.onTrackingEnd(entity); + this.visibleEntityStorage.remove(entity); + } +@@ -169,6 +175,7 @@ public class PersistentEntitySectionManager implements A + } + + public void updateChunkStatus(ChunkPos chunkPos, Visibility trackingStatus) { ++ org.spigotmc.AsyncCatcher.catchOp("Update chunk status"); // Paper + long i = chunkPos.toLong(); + + if (trackingStatus == Visibility.HIDDEN) { +@@ -213,6 +220,7 @@ public class PersistentEntitySectionManager implements A + } + + public void ensureChunkQueuedForLoad(long chunkPos) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk save"); // Paper + PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(chunkPos); + + if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) { +@@ -257,6 +265,7 @@ public class PersistentEntitySectionManager implements A + } + + private void requestChunkLoad(long chunkPos) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk load request"); // Paper + this.chunkLoadStatuses.put(chunkPos, PersistentEntitySectionManager.ChunkLoadStatus.PENDING); + ChunkPos chunkcoordintpair = new ChunkPos(chunkPos); + CompletableFuture completablefuture = this.permanentStorage.loadEntities(chunkcoordintpair); +@@ -270,6 +279,7 @@ public class PersistentEntitySectionManager implements A + } + + private boolean processChunkUnload(long chunkPos) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk unload process"); // Paper + boolean flag = this.storeChunkSections(chunkPos, (entityaccess) -> { + entityaccess.getPassengersAndSelf().forEach(this::unloadEntity); + }, true); // CraftBukkit - add boolean for event call +@@ -294,6 +304,7 @@ public class PersistentEntitySectionManager implements A + } + + private void processPendingLoads() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk process pending loads"); // Paper + ChunkEntities chunkentities; // CraftBukkit - decompile error + + while ((chunkentities = (ChunkEntities) this.loadingInbox.poll()) != null) { +@@ -310,6 +321,7 @@ public class PersistentEntitySectionManager implements A + } + + public void tick() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity manager tick"); // Paper + this.processPendingLoads(); + this.processUnloads(); + } +@@ -330,6 +342,7 @@ public class PersistentEntitySectionManager implements A + } + + public void autoSave() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity manager autosave"); // Paper + this.getAllChunksToSave().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error + boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN; + +@@ -344,6 +357,7 @@ public class PersistentEntitySectionManager implements A + } + + public void saveAll() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity manager save"); // Paper + LongSet longset = this.getAllChunksToSave(); + + while (!longset.isEmpty()) { +@@ -451,6 +465,7 @@ public class PersistentEntitySectionManager implements A + long i = SectionPos.asLong(blockposition); + + if (i != this.currentSectionKey) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity move"); // Paper + Visibility visibility = this.currentSection.getStatus(); + + if (!this.currentSection.remove(this.entity)) { +@@ -505,6 +520,7 @@ public class PersistentEntitySectionManager implements A + + @Override + public void onRemove(Entity.RemovalReason reason) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper + if (!this.currentSection.remove(this.entity)) { + PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason}); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index ca68faef4232859e833adfd86a0ce13f7c2ad00e..df57157317fc6c84f69751fd8a120761837429af 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1737,6 +1737,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { ++ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + if (loc == null || sound == null || category == null) return; + + double x = loc.getX(); +@@ -1748,6 +1749,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void playSound(Location loc, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { ++ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + if (loc == null || sound == null || category == null) return; + + double x = loc.getX(); +@@ -1780,6 +1782,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void playSound(Entity entity, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { ++ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; + + ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(CraftSound.bukkitToMinecraftHolder(sound), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); +@@ -1791,6 +1794,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void playSound(Entity entity, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { ++ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; + + ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(Holder.direct(SoundEvent.createVariableRangeEvent(new ResourceLocation(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index f288efe62c7280189359bba749a2dc3ec3f6ef49..60230ddeec41485e1e1b83614a6256d9ae2cb242 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -501,6 +501,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + + @Override + public boolean addPotionEffect(PotionEffect effect, boolean force) { ++ org.spigotmc.AsyncCatcher.catchOp("effect add"); // Paper + this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon + return true; + } +diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java +index 78669fa035b7537ff7e533cf32aaf2995625424f..e8e3cc48cf1c58bd8151d1f28df28781859cd0e3 100644 +--- a/src/main/java/org/spigotmc/AsyncCatcher.java ++++ b/src/main/java/org/spigotmc/AsyncCatcher.java +@@ -11,6 +11,7 @@ public class AsyncCatcher + { + if ( (AsyncCatcher.enabled || io.papermc.paper.util.TickThread.STRICT_THREAD_CHECKS) && Thread.currentThread() != MinecraftServer.getServer().serverThread ) // Paper + { ++ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper + throw new IllegalStateException( "Asynchronous " + reason + "!" ); + } + } diff --git a/patches/server/0633-Add-paper-mobcaps-and-paper-playermobcaps.patch b/patches/server/0633-Add-paper-mobcaps-and-paper-playermobcaps.patch new file mode 100644 index 000000000000..5de369549887 --- /dev/null +++ b/patches/server/0633-Add-paper-mobcaps-and-paper-playermobcaps.patch @@ -0,0 +1,341 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Mon, 16 Aug 2021 01:31:54 -0500 +Subject: [PATCH] Add '/paper mobcaps' and '/paper playermobcaps' + +Add commands to get the mobcaps for a world, as well as the mobcaps for +each player when per-player mob spawning is enabled. + +Also has a hover text on each mob category listing what entity types are +in said category + +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 27775df10a490ff75ca377e8373931738f1b817c..c9bb2df0d884227576ed8d2e72219bbbd7ba827e 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -40,6 +40,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("dumpplugins"), new DumpPluginsCommand()); + commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); + commands.put(Set.of("dumpitem"), new DumpItemCommand()); ++ commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d3b39d88a72ca25057fd8574d32f28db0d420818 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +@@ -0,0 +1,229 @@ ++package io.papermc.paper.command.subcommands; ++ ++import com.google.common.collect.ImmutableMap; ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.Map; ++import java.util.function.ToIntFunction; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.ComponentLike; ++import net.kyori.adventure.text.JoinConfiguration; ++import net.kyori.adventure.text.TextComponent; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextColor; ++import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.entity.MobCategory; ++import net.minecraft.world.level.NaturalSpawner; ++import org.bukkit.Bukkit; ++import org.bukkit.World; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class MobcapsCommand implements PaperSubcommand { ++ static final Map MOB_CATEGORY_COLORS = ImmutableMap.builder() ++ .put(MobCategory.MONSTER, NamedTextColor.RED) ++ .put(MobCategory.CREATURE, NamedTextColor.GREEN) ++ .put(MobCategory.AMBIENT, NamedTextColor.GRAY) ++ .put(MobCategory.AXOLOTLS, TextColor.color(0x7324FF)) ++ .put(MobCategory.UNDERGROUND_WATER_CREATURE, TextColor.color(0x3541E6)) ++ .put(MobCategory.WATER_CREATURE, TextColor.color(0x006EFF)) ++ .put(MobCategory.WATER_AMBIENT, TextColor.color(0x00B3FF)) ++ .put(MobCategory.MISC, TextColor.color(0x636363)) ++ .build(); ++ ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ switch (subCommand) { ++ case "mobcaps" -> this.printMobcaps(sender, args); ++ case "playermobcaps" -> this.printPlayerMobcaps(sender, args); ++ } ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ return switch (subCommand) { ++ case "mobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestMobcaps(args)); ++ case "playermobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestPlayerMobcaps(sender, args)); ++ default -> throw new IllegalArgumentException(); ++ }; ++ } ++ ++ private List suggestMobcaps(final String[] args) { ++ if (args.length == 1) { ++ final List worlds = new ArrayList<>(Bukkit.getWorlds().stream().map(World::getName).toList()); ++ worlds.add("*"); ++ return worlds; ++ } ++ ++ return Collections.emptyList(); ++ } ++ ++ private List suggestPlayerMobcaps(final CommandSender sender, final String[] args) { ++ if (args.length == 1) { ++ final List list = new ArrayList<>(); ++ for (final Player player : Bukkit.getOnlinePlayers()) { ++ if (!(sender instanceof Player senderPlayer) || senderPlayer.canSee(player)) { ++ list.add(player.getName()); ++ } ++ } ++ return list; ++ } ++ ++ return Collections.emptyList(); ++ } ++ ++ private void printMobcaps(final CommandSender sender, final String[] args) { ++ final List worlds; ++ if (args.length == 0) { ++ if (sender instanceof Player player) { ++ worlds = List.of(player.getWorld()); ++ } else { ++ sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps world'", NamedTextColor.RED)); ++ return; ++ } ++ } else if (args.length == 1) { ++ final String input = args[0]; ++ if (input.equals("*")) { ++ worlds = Bukkit.getWorlds(); ++ } else { ++ final @Nullable World world = Bukkit.getWorld(input); ++ if (world == null) { ++ sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED)); ++ return; ++ } else { ++ worlds = List.of(world); ++ } ++ } ++ } else { ++ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); ++ return; ++ } ++ ++ for (final World world : worlds) { ++ final ServerLevel level = ((CraftWorld) world).getHandle(); ++ final NaturalSpawner.@Nullable SpawnState state = level.getChunkSource().getLastSpawnState(); ++ ++ final int chunks; ++ if (state == null) { ++ chunks = 0; ++ } else { ++ chunks = state.getSpawnableChunkCount(); ++ } ++ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), ++ Component.text("Mobcaps for world: "), ++ Component.text(world.getName(), NamedTextColor.AQUA), ++ Component.text(" (" + chunks + " spawnable chunks)") ++ )); ++ ++ sender.sendMessage(createMobcapsComponent( ++ category -> { ++ if (state == null) { ++ return 0; ++ } else { ++ return state.getMobCategoryCounts().getOrDefault(category, 0); ++ } ++ }, ++ category -> NaturalSpawner.globalLimitForCategory(level, category, chunks) ++ )); ++ } ++ } ++ ++ private void printPlayerMobcaps(final CommandSender sender, final String[] args) { ++ final @Nullable Player player; ++ if (args.length == 0) { ++ if (sender instanceof Player pl) { ++ player = pl; ++ } else { ++ sender.sendMessage(Component.text("Must specify a player! ex: '/paper playermobcount playerName'", NamedTextColor.RED)); ++ return; ++ } ++ } else if (args.length == 1) { ++ final String input = args[0]; ++ player = Bukkit.getPlayerExact(input); ++ if (player == null) { ++ sender.sendMessage(Component.text("Could not find player named '" + input + "'", NamedTextColor.RED)); ++ return; ++ } ++ } else { ++ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); ++ return; ++ } ++ ++ final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); ++ final ServerLevel level = serverPlayer.serverLevel(); ++ ++ if (!level.paperConfig().entities.spawning.perPlayerMobSpawns) { ++ sender.sendMessage(Component.text("Use '/paper mobcaps' for worlds where per-player mob spawning is disabled.", NamedTextColor.RED)); ++ return; ++ } ++ ++ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), Component.text("Mobcaps for player: "), Component.text(player.getName(), NamedTextColor.GREEN))); ++ sender.sendMessage(createMobcapsComponent( ++ category -> level.chunkSource.chunkMap.getMobCountNear(serverPlayer, category), ++ category -> level.getWorld().getSpawnLimitUnsafe(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category)) ++ )); ++ } ++ ++ private static Component createMobcapsComponent(final ToIntFunction countGetter, final ToIntFunction limitGetter) { ++ return MOB_CATEGORY_COLORS.entrySet().stream() ++ .map(entry -> { ++ final MobCategory category = entry.getKey(); ++ final TextColor color = entry.getValue(); ++ ++ final Component categoryHover = Component.join(JoinConfiguration.noSeparators(), ++ Component.text("Entity types in category ", TextColor.color(0xE0E0E0)), ++ Component.text(category.getName(), color), ++ Component.text(':', NamedTextColor.GRAY), ++ Component.newline(), ++ Component.newline(), ++ BuiltInRegistries.ENTITY_TYPE.entrySet().stream() ++ .filter(it -> it.getValue().getCategory() == category) ++ .map(it -> Component.translatable(it.getValue().getDescriptionId())) ++ .collect(Component.toComponent(Component.text(", ", NamedTextColor.GRAY))) ++ ); ++ ++ final Component categoryComponent = Component.text() ++ .content(" " + category.getName()) ++ .color(color) ++ .hoverEvent(categoryHover) ++ .build(); ++ ++ final TextComponent.Builder builder = Component.text() ++ .append( ++ categoryComponent, ++ Component.text(": ", NamedTextColor.GRAY) ++ ); ++ final int limit = limitGetter.applyAsInt(category); ++ if (limit != -1) { ++ builder.append( ++ Component.text(countGetter.applyAsInt(category)), ++ Component.text("/", NamedTextColor.GRAY), ++ Component.text(limit) ++ ); ++ } else { ++ builder.append(Component.text() ++ .append( ++ Component.text('n'), ++ Component.text("/", NamedTextColor.GRAY), ++ Component.text('a') ++ ) ++ .hoverEvent(Component.text("This category does not naturally spawn."))); ++ } ++ return builder; ++ }) ++ .map(ComponentLike::asComponent) ++ .collect(Component.toComponent(Component.newline())); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index 5247782edc426107fb4b3ade5d92f148c0b6e681..c44c10e15564af6ba0f6d60a1b5f38c6e874a43a 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -157,6 +157,16 @@ public final class NaturalSpawner { + world.getProfiler().pop(); + } + ++ // Paper start - Add mobcaps commands ++ public static int globalLimitForCategory(final ServerLevel level, final MobCategory category, final int spawnableChunkCount) { ++ final int categoryLimit = level.getWorld().getSpawnLimitUnsafe(CraftSpawnCategory.toBukkit(category)); ++ if (categoryLimit < 1) { ++ return categoryLimit; ++ } ++ return categoryLimit * spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; ++ } ++ // Paper end - Add mobcaps commands ++ + public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { + BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 51337b1b2e74a67ad54c5d594004b649cb6af4ed..dc37989ab5e0971a144a8248152169b4fd868067 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2286,6 +2286,11 @@ public final class CraftServer implements Server { + + @Override + public int getSpawnLimit(SpawnCategory spawnCategory) { ++ // Paper start - Add mobcaps commands ++ return this.getSpawnLimitUnsafe(spawnCategory); ++ } ++ public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { ++ // Paper end - Add mobcaps commands + return this.spawnCategoryLimit.getOrDefault(spawnCategory, -1); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index df57157317fc6c84f69751fd8a120761837429af..df74b47477553bdee0b5faaf40835b94c41316dd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1695,9 +1695,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { + Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); + Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory); + ++ // Paper start - Add mobcaps commands ++ return this.getSpawnLimitUnsafe(spawnCategory); ++ } ++ public final int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { + int limit = this.spawnCategoryLimit.getOrDefault(spawnCategory, -1); + if (limit < 0) { +- limit = this.server.getSpawnLimit(spawnCategory); ++ limit = this.server.getSpawnLimitUnsafe(spawnCategory); ++ // Paper end - Add mobcaps commands + } + return limit; + } +diff --git a/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fd238eacee24ebf0d0ce82b96107e093ca4866b0 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java +@@ -0,0 +1,20 @@ ++package io.papermc.paper.command.subcommands; ++ ++import java.util.HashSet; ++import java.util.Set; ++import net.minecraft.world.entity.MobCategory; ++import org.junit.jupiter.api.Assertions; ++import org.junit.jupiter.api.Test; ++ ++public class MobcapsCommandTest { ++ @Test ++ public void testMobCategoryColors() { ++ final Set missing = new HashSet<>(); ++ for (final MobCategory value : MobCategory.values()) { ++ if (!MobcapsCommand.MOB_CATEGORY_COLORS.containsKey(value)) { ++ missing.add(value.getName()); ++ } ++ } ++ Assertions.assertTrue(missing.isEmpty(), "MobcapsCommand.MOB_CATEGORY_COLORS map missing TextColors for [" + String.join(", ", missing + "]")); ++ } ++} diff --git a/patches/server/0633-Improve-and-expand-AsyncCatcher.patch b/patches/server/0633-Improve-and-expand-AsyncCatcher.patch deleted file mode 100644 index e9e03b906da4..000000000000 --- a/patches/server/0633-Improve-and-expand-AsyncCatcher.patch +++ /dev/null @@ -1,227 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 25 Aug 2021 20:17:12 -0700 -Subject: [PATCH] Improve and expand AsyncCatcher - -Log when the async catcher is tripped - The chunk system can swallow the exception given it's all - built with completablefuture, so ensure it is at least printed. - -Add/move several async catchers - -Async catch modifications to critical entity state - These used to be here from Spigot, but were dropped with 1.17. - Now in 1.17, this state is _even more_ critical than it was before, - so these must exist to catch stupid plugins. - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 935cb7d0529134115fdda73f1bcaf538b7dc7c94..4ab3f9321f37956420549e63ce65b955ca9c3b94 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1570,6 +1570,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { // Paper -+ org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper - // Paper start - Prevent teleporting dead entities - if (player.isRemoved()) { - LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 8dcebc2b6e8baf4ed5f269a1b9cec9e5cd754047..f1ac4256878bd5737ded1f7d3e8b51416887f545 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1117,7 +1117,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) { -- org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot -+ // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API - if (this.isTickingEffects) { - this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause)); - return true; -diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index bbbf6dd8e566ecdca8794e3b03765fe7e426a2bd..66ab901956ca394c251c420338643d39817a1b7e 100644 ---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -@@ -77,6 +77,7 @@ public class PersistentEntitySectionManager implements A - } - - private boolean addEntityUuid(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity add by UUID"); // Paper - if (!this.knownUuids.add(entity.getUUID())) { - PersistentEntitySectionManager.LOGGER.warn("UUID of added entity already exists: {}", entity); - return false; -@@ -90,6 +91,7 @@ public class PersistentEntitySectionManager implements A - } - - private boolean addEntity(T entity, boolean existing) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper - // Paper start - chunk system hooks - if (existing) { - // I don't want to know why this is a generic type. -@@ -145,19 +147,23 @@ public class PersistentEntitySectionManager implements A - } - - void startTicking(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity start ticking"); // Paper - this.callbacks.onTickingStart(entity); - } - - void stopTicking(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity stop ticking"); // Paper - this.callbacks.onTickingEnd(entity); - } - - void startTracking(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity start tracking"); // Paper - this.visibleEntityStorage.add(entity); - this.callbacks.onTrackingStart(entity); - } - - void stopTracking(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity stop tracking"); // Paper - this.callbacks.onTrackingEnd(entity); - this.visibleEntityStorage.remove(entity); - } -@@ -169,6 +175,7 @@ public class PersistentEntitySectionManager implements A - } - - public void updateChunkStatus(ChunkPos chunkPos, Visibility trackingStatus) { -+ org.spigotmc.AsyncCatcher.catchOp("Update chunk status"); // Paper - long i = chunkPos.toLong(); - - if (trackingStatus == Visibility.HIDDEN) { -@@ -213,6 +220,7 @@ public class PersistentEntitySectionManager implements A - } - - public void ensureChunkQueuedForLoad(long chunkPos) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity chunk save"); // Paper - PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(chunkPos); - - if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) { -@@ -257,6 +265,7 @@ public class PersistentEntitySectionManager implements A - } - - private void requestChunkLoad(long chunkPos) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity chunk load request"); // Paper - this.chunkLoadStatuses.put(chunkPos, PersistentEntitySectionManager.ChunkLoadStatus.PENDING); - ChunkPos chunkcoordintpair = new ChunkPos(chunkPos); - CompletableFuture completablefuture = this.permanentStorage.loadEntities(chunkcoordintpair); -@@ -270,6 +279,7 @@ public class PersistentEntitySectionManager implements A - } - - private boolean processChunkUnload(long chunkPos) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity chunk unload process"); // Paper - boolean flag = this.storeChunkSections(chunkPos, (entityaccess) -> { - entityaccess.getPassengersAndSelf().forEach(this::unloadEntity); - }, true); // CraftBukkit - add boolean for event call -@@ -294,6 +304,7 @@ public class PersistentEntitySectionManager implements A - } - - private void processPendingLoads() { -+ org.spigotmc.AsyncCatcher.catchOp("Entity chunk process pending loads"); // Paper - ChunkEntities chunkentities; // CraftBukkit - decompile error - - while ((chunkentities = (ChunkEntities) this.loadingInbox.poll()) != null) { -@@ -310,6 +321,7 @@ public class PersistentEntitySectionManager implements A - } - - public void tick() { -+ org.spigotmc.AsyncCatcher.catchOp("Entity manager tick"); // Paper - this.processPendingLoads(); - this.processUnloads(); - } -@@ -330,6 +342,7 @@ public class PersistentEntitySectionManager implements A - } - - public void autoSave() { -+ org.spigotmc.AsyncCatcher.catchOp("Entity manager autosave"); // Paper - this.getAllChunksToSave().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error - boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN; - -@@ -344,6 +357,7 @@ public class PersistentEntitySectionManager implements A - } - - public void saveAll() { -+ org.spigotmc.AsyncCatcher.catchOp("Entity manager save"); // Paper - LongSet longset = this.getAllChunksToSave(); - - while (!longset.isEmpty()) { -@@ -451,6 +465,7 @@ public class PersistentEntitySectionManager implements A - long i = SectionPos.asLong(blockposition); - - if (i != this.currentSectionKey) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity move"); // Paper - Visibility visibility = this.currentSection.getStatus(); - - if (!this.currentSection.remove(this.entity)) { -@@ -505,6 +520,7 @@ public class PersistentEntitySectionManager implements A - - @Override - public void onRemove(Entity.RemovalReason reason) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper - if (!this.currentSection.remove(this.entity)) { - PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason}); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 8a37dd71f1551ce27c1a729eab2a11c465b684e3..b58788161b758eee5fbaee3280c8551116e82566 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1724,6 +1724,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { -+ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper - if (loc == null || sound == null || category == null) return; - - double x = loc.getX(); -@@ -1735,6 +1736,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void playSound(Location loc, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { -+ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper - if (loc == null || sound == null || category == null) return; - - double x = loc.getX(); -@@ -1767,6 +1769,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void playSound(Entity entity, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { -+ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper - if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; - - ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(CraftSound.bukkitToMinecraftHolder(sound), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); -@@ -1778,6 +1781,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void playSound(Entity entity, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { -+ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper - if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; - - ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(Holder.direct(SoundEvent.createVariableRangeEvent(new ResourceLocation(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 1df7c8c94e3ed371a4cda5d72a3ceade59e454cc..cf0151ea68738783a5e8c2e49a001b3a41d0c91a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -488,6 +488,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - - @Override - public boolean addPotionEffect(PotionEffect effect, boolean force) { -+ org.spigotmc.AsyncCatcher.catchOp("effect add"); // Paper - this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon - return true; - } -diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java -index 78669fa035b7537ff7e533cf32aaf2995625424f..e8e3cc48cf1c58bd8151d1f28df28781859cd0e3 100644 ---- a/src/main/java/org/spigotmc/AsyncCatcher.java -+++ b/src/main/java/org/spigotmc/AsyncCatcher.java -@@ -11,6 +11,7 @@ public class AsyncCatcher - { - if ( (AsyncCatcher.enabled || io.papermc.paper.util.TickThread.STRICT_THREAD_CHECKS) && Thread.currentThread() != MinecraftServer.getServer().serverThread ) // Paper - { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper - throw new IllegalStateException( "Asynchronous " + reason + "!" ); - } - } diff --git a/patches/server/0634-Add-paper-mobcaps-and-paper-playermobcaps.patch b/patches/server/0634-Add-paper-mobcaps-and-paper-playermobcaps.patch deleted file mode 100644 index d90f1f9ab897..000000000000 --- a/patches/server/0634-Add-paper-mobcaps-and-paper-playermobcaps.patch +++ /dev/null @@ -1,341 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Mon, 16 Aug 2021 01:31:54 -0500 -Subject: [PATCH] Add '/paper mobcaps' and '/paper playermobcaps' - -Add commands to get the mobcaps for a world, as well as the mobcaps for -each player when per-player mob spawning is enabled. - -Also has a hover text on each mob category listing what entity types are -in said category - -diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index 27775df10a490ff75ca377e8373931738f1b817c..c9bb2df0d884227576ed8d2e72219bbbd7ba827e 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -40,6 +40,7 @@ public final class PaperCommand extends Command { - commands.put(Set.of("dumpplugins"), new DumpPluginsCommand()); - commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); - commands.put(Set.of("dumpitem"), new DumpItemCommand()); -+ commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); - - return commands.entrySet().stream() - .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) -diff --git a/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d3b39d88a72ca25057fd8574d32f28db0d420818 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java -@@ -0,0 +1,229 @@ -+package io.papermc.paper.command.subcommands; -+ -+import com.google.common.collect.ImmutableMap; -+import io.papermc.paper.command.CommandUtil; -+import io.papermc.paper.command.PaperSubcommand; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.List; -+import java.util.Map; -+import java.util.function.ToIntFunction; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.ComponentLike; -+import net.kyori.adventure.text.JoinConfiguration; -+import net.kyori.adventure.text.TextComponent; -+import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.format.TextColor; -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.MobCategory; -+import net.minecraft.world.level.NaturalSpawner; -+import org.bukkit.Bukkit; -+import org.bukkit.World; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.entity.Player; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public final class MobcapsCommand implements PaperSubcommand { -+ static final Map MOB_CATEGORY_COLORS = ImmutableMap.builder() -+ .put(MobCategory.MONSTER, NamedTextColor.RED) -+ .put(MobCategory.CREATURE, NamedTextColor.GREEN) -+ .put(MobCategory.AMBIENT, NamedTextColor.GRAY) -+ .put(MobCategory.AXOLOTLS, TextColor.color(0x7324FF)) -+ .put(MobCategory.UNDERGROUND_WATER_CREATURE, TextColor.color(0x3541E6)) -+ .put(MobCategory.WATER_CREATURE, TextColor.color(0x006EFF)) -+ .put(MobCategory.WATER_AMBIENT, TextColor.color(0x00B3FF)) -+ .put(MobCategory.MISC, TextColor.color(0x636363)) -+ .build(); -+ -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ switch (subCommand) { -+ case "mobcaps" -> this.printMobcaps(sender, args); -+ case "playermobcaps" -> this.printPlayerMobcaps(sender, args); -+ } -+ return true; -+ } -+ -+ @Override -+ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { -+ return switch (subCommand) { -+ case "mobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestMobcaps(args)); -+ case "playermobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestPlayerMobcaps(sender, args)); -+ default -> throw new IllegalArgumentException(); -+ }; -+ } -+ -+ private List suggestMobcaps(final String[] args) { -+ if (args.length == 1) { -+ final List worlds = new ArrayList<>(Bukkit.getWorlds().stream().map(World::getName).toList()); -+ worlds.add("*"); -+ return worlds; -+ } -+ -+ return Collections.emptyList(); -+ } -+ -+ private List suggestPlayerMobcaps(final CommandSender sender, final String[] args) { -+ if (args.length == 1) { -+ final List list = new ArrayList<>(); -+ for (final Player player : Bukkit.getOnlinePlayers()) { -+ if (!(sender instanceof Player senderPlayer) || senderPlayer.canSee(player)) { -+ list.add(player.getName()); -+ } -+ } -+ return list; -+ } -+ -+ return Collections.emptyList(); -+ } -+ -+ private void printMobcaps(final CommandSender sender, final String[] args) { -+ final List worlds; -+ if (args.length == 0) { -+ if (sender instanceof Player player) { -+ worlds = List.of(player.getWorld()); -+ } else { -+ sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps world'", NamedTextColor.RED)); -+ return; -+ } -+ } else if (args.length == 1) { -+ final String input = args[0]; -+ if (input.equals("*")) { -+ worlds = Bukkit.getWorlds(); -+ } else { -+ final @Nullable World world = Bukkit.getWorld(input); -+ if (world == null) { -+ sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED)); -+ return; -+ } else { -+ worlds = List.of(world); -+ } -+ } -+ } else { -+ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); -+ return; -+ } -+ -+ for (final World world : worlds) { -+ final ServerLevel level = ((CraftWorld) world).getHandle(); -+ final NaturalSpawner.@Nullable SpawnState state = level.getChunkSource().getLastSpawnState(); -+ -+ final int chunks; -+ if (state == null) { -+ chunks = 0; -+ } else { -+ chunks = state.getSpawnableChunkCount(); -+ } -+ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), -+ Component.text("Mobcaps for world: "), -+ Component.text(world.getName(), NamedTextColor.AQUA), -+ Component.text(" (" + chunks + " spawnable chunks)") -+ )); -+ -+ sender.sendMessage(createMobcapsComponent( -+ category -> { -+ if (state == null) { -+ return 0; -+ } else { -+ return state.getMobCategoryCounts().getOrDefault(category, 0); -+ } -+ }, -+ category -> NaturalSpawner.globalLimitForCategory(level, category, chunks) -+ )); -+ } -+ } -+ -+ private void printPlayerMobcaps(final CommandSender sender, final String[] args) { -+ final @Nullable Player player; -+ if (args.length == 0) { -+ if (sender instanceof Player pl) { -+ player = pl; -+ } else { -+ sender.sendMessage(Component.text("Must specify a player! ex: '/paper playermobcount playerName'", NamedTextColor.RED)); -+ return; -+ } -+ } else if (args.length == 1) { -+ final String input = args[0]; -+ player = Bukkit.getPlayerExact(input); -+ if (player == null) { -+ sender.sendMessage(Component.text("Could not find player named '" + input + "'", NamedTextColor.RED)); -+ return; -+ } -+ } else { -+ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); -+ return; -+ } -+ -+ final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); -+ final ServerLevel level = serverPlayer.serverLevel(); -+ -+ if (!level.paperConfig().entities.spawning.perPlayerMobSpawns) { -+ sender.sendMessage(Component.text("Use '/paper mobcaps' for worlds where per-player mob spawning is disabled.", NamedTextColor.RED)); -+ return; -+ } -+ -+ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), Component.text("Mobcaps for player: "), Component.text(player.getName(), NamedTextColor.GREEN))); -+ sender.sendMessage(createMobcapsComponent( -+ category -> level.chunkSource.chunkMap.getMobCountNear(serverPlayer, category), -+ category -> level.getWorld().getSpawnLimitUnsafe(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category)) -+ )); -+ } -+ -+ private static Component createMobcapsComponent(final ToIntFunction countGetter, final ToIntFunction limitGetter) { -+ return MOB_CATEGORY_COLORS.entrySet().stream() -+ .map(entry -> { -+ final MobCategory category = entry.getKey(); -+ final TextColor color = entry.getValue(); -+ -+ final Component categoryHover = Component.join(JoinConfiguration.noSeparators(), -+ Component.text("Entity types in category ", TextColor.color(0xE0E0E0)), -+ Component.text(category.getName(), color), -+ Component.text(':', NamedTextColor.GRAY), -+ Component.newline(), -+ Component.newline(), -+ BuiltInRegistries.ENTITY_TYPE.entrySet().stream() -+ .filter(it -> it.getValue().getCategory() == category) -+ .map(it -> Component.translatable(it.getValue().getDescriptionId())) -+ .collect(Component.toComponent(Component.text(", ", NamedTextColor.GRAY))) -+ ); -+ -+ final Component categoryComponent = Component.text() -+ .content(" " + category.getName()) -+ .color(color) -+ .hoverEvent(categoryHover) -+ .build(); -+ -+ final TextComponent.Builder builder = Component.text() -+ .append( -+ categoryComponent, -+ Component.text(": ", NamedTextColor.GRAY) -+ ); -+ final int limit = limitGetter.applyAsInt(category); -+ if (limit != -1) { -+ builder.append( -+ Component.text(countGetter.applyAsInt(category)), -+ Component.text("/", NamedTextColor.GRAY), -+ Component.text(limit) -+ ); -+ } else { -+ builder.append(Component.text() -+ .append( -+ Component.text('n'), -+ Component.text("/", NamedTextColor.GRAY), -+ Component.text('a') -+ ) -+ .hoverEvent(Component.text("This category does not naturally spawn."))); -+ } -+ return builder; -+ }) -+ .map(ComponentLike::asComponent) -+ .collect(Component.toComponent(Component.newline())); -+ } -+} -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 4ad3a4403f497f4b437209a9e63445f0d29b09f1..28ec1cc4dec6d12627761a58d635fd51dbc398b3 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -157,6 +157,16 @@ public final class NaturalSpawner { - world.getProfiler().pop(); - } - -+ // Paper start - Add mobcaps commands -+ public static int globalLimitForCategory(final ServerLevel level, final MobCategory category, final int spawnableChunkCount) { -+ final int categoryLimit = level.getWorld().getSpawnLimitUnsafe(CraftSpawnCategory.toBukkit(category)); -+ if (categoryLimit < 1) { -+ return categoryLimit; -+ } -+ return categoryLimit * spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; -+ } -+ // Paper end - Add mobcaps commands -+ - public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { - BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk); - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 3c29d2a8ccac5ca50d3df41262e9e767daf7035b..20b2fd911c2d8bc530c533e883f334b5b329fa9b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2274,6 +2274,11 @@ public final class CraftServer implements Server { - - @Override - public int getSpawnLimit(SpawnCategory spawnCategory) { -+ // Paper start - Add mobcaps commands -+ return this.getSpawnLimitUnsafe(spawnCategory); -+ } -+ public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { -+ // Paper end - Add mobcaps commands - return this.spawnCategoryLimit.getOrDefault(spawnCategory, -1); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index b58788161b758eee5fbaee3280c8551116e82566..b3e17c14152204e9ccbe70ee8dfab4b20583d2d6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1682,9 +1682,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { - Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); - Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory); - -+ // Paper start - Add mobcaps commands -+ return this.getSpawnLimitUnsafe(spawnCategory); -+ } -+ public final int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { - int limit = this.spawnCategoryLimit.getOrDefault(spawnCategory, -1); - if (limit < 0) { -- limit = this.server.getSpawnLimit(spawnCategory); -+ limit = this.server.getSpawnLimitUnsafe(spawnCategory); -+ // Paper end - Add mobcaps commands - } - return limit; - } -diff --git a/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fd238eacee24ebf0d0ce82b96107e093ca4866b0 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java -@@ -0,0 +1,20 @@ -+package io.papermc.paper.command.subcommands; -+ -+import java.util.HashSet; -+import java.util.Set; -+import net.minecraft.world.entity.MobCategory; -+import org.junit.jupiter.api.Assertions; -+import org.junit.jupiter.api.Test; -+ -+public class MobcapsCommandTest { -+ @Test -+ public void testMobCategoryColors() { -+ final Set missing = new HashSet<>(); -+ for (final MobCategory value : MobCategory.values()) { -+ if (!MobcapsCommand.MOB_CATEGORY_COLORS.containsKey(value)) { -+ missing.add(value.getName()); -+ } -+ } -+ Assertions.assertTrue(missing.isEmpty(), "MobcapsCommand.MOB_CATEGORY_COLORS map missing TextColors for [" + String.join(", ", missing + "]")); -+ } -+} diff --git a/patches/server/0635-Sanitize-ResourceLocation-error-logging.patch b/patches/server/0634-Sanitize-ResourceLocation-error-logging.patch similarity index 100% rename from patches/server/0635-Sanitize-ResourceLocation-error-logging.patch rename to patches/server/0634-Sanitize-ResourceLocation-error-logging.patch diff --git a/patches/server/0636-Manually-inline-methods-in-BlockPosition.patch b/patches/server/0635-Manually-inline-methods-in-BlockPosition.patch similarity index 100% rename from patches/server/0636-Manually-inline-methods-in-BlockPosition.patch rename to patches/server/0635-Manually-inline-methods-in-BlockPosition.patch diff --git a/patches/server/0637-Name-craft-scheduler-threads-according-to-the-plugin.patch b/patches/server/0636-Name-craft-scheduler-threads-according-to-the-plugin.patch similarity index 100% rename from patches/server/0637-Name-craft-scheduler-threads-according-to-the-plugin.patch rename to patches/server/0636-Name-craft-scheduler-threads-according-to-the-plugin.patch diff --git a/patches/server/0638-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch b/patches/server/0637-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch similarity index 100% rename from patches/server/0638-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch rename to patches/server/0637-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch diff --git a/patches/server/0638-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch b/patches/server/0638-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch new file mode 100644 index 000000000000..538ac5666525 --- /dev/null +++ b/patches/server/0638-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 11 Apr 2021 02:58:48 -0700 +Subject: [PATCH] Don't read neighbour chunk data off disk when converting + chunks + +Lighting is purged on update anyways, so let's not add more +into the conversion process + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index eaf978d15618b80d23c443acbd42db926d570d01..6743dca44e6552ad39aca757a24f3c4df400d83d 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -44,6 +44,7 @@ public class ChunkStorage implements AutoCloseable { + + // CraftBukkit start + private boolean check(ServerChunkCache cps, int x, int z) { ++ if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full" + ChunkPos pos = new ChunkPos(x, z); + if (cps != null) { + com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); diff --git a/patches/server/0640-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch b/patches/server/0639-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch similarity index 100% rename from patches/server/0640-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch rename to patches/server/0639-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch diff --git a/patches/server/0639-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch b/patches/server/0639-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch deleted file mode 100644 index c878df90414b..000000000000 --- a/patches/server/0639-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 11 Apr 2021 02:58:48 -0700 -Subject: [PATCH] Don't read neighbour chunk data off disk when converting - chunks - -Lighting is purged on update anyways, so let's not add more -into the conversion process - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -index ab6b4491d4278b3839b7363d9890bd12a757e4cc..eebaf98bc0fa4696af59b2a79563beb73501a554 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -44,6 +44,7 @@ public class ChunkStorage implements AutoCloseable { - - // CraftBukkit start - private boolean check(ServerChunkCache cps, int x, int z) { -+ if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full" - ChunkPos pos = new ChunkPos(x, z); - if (cps != null) { - com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); diff --git a/patches/server/0641-Time-scoreboard-search.patch b/patches/server/0640-Time-scoreboard-search.patch similarity index 100% rename from patches/server/0641-Time-scoreboard-search.patch rename to patches/server/0640-Time-scoreboard-search.patch diff --git a/patches/server/0641-Oprimise-map-impl-for-tracked-players.patch b/patches/server/0641-Oprimise-map-impl-for-tracked-players.patch new file mode 100644 index 000000000000..274e6f709ea8 --- /dev/null +++ b/patches/server/0641-Oprimise-map-impl-for-tracked-players.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 19 Feb 2021 22:51:52 -0800 +Subject: [PATCH] Oprimise map impl for tracked players + +Reference2BooleanOpenHashMap is going to have +better lookups than HashMap. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 5ef08156aa2e93e42eed586a4014c6208ddb20c1..63e7f41eaed3f22c1bc0191790ff0ad313dc4ffd 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1677,7 +1677,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + final Entity entity; + private final int range; + SectionPos lastSectionPos; +- public final Set seenBy = Sets.newIdentityHashSet(); ++ public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl + + public TrackedEntity(Entity entity, int i, int j, boolean flag) { + this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit diff --git a/patches/server/0643-Add-missing-InventoryType.patch b/patches/server/0642-Add-missing-InventoryType.patch similarity index 100% rename from patches/server/0643-Add-missing-InventoryType.patch rename to patches/server/0642-Add-missing-InventoryType.patch diff --git a/patches/server/0642-Oprimise-map-impl-for-tracked-players.patch b/patches/server/0642-Oprimise-map-impl-for-tracked-players.patch deleted file mode 100644 index c74b649a5cfe..000000000000 --- a/patches/server/0642-Oprimise-map-impl-for-tracked-players.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 19 Feb 2021 22:51:52 -0800 -Subject: [PATCH] Oprimise map impl for tracked players - -Reference2BooleanOpenHashMap is going to have -better lookups than HashMap. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 7f61b2945e5174f89936041c334d4cb2e5cdb130..7c13ef020ac183253465d691adebb0e40f24ee8a 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1677,7 +1677,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - final Entity entity; - private final int range; - SectionPos lastSectionPos; -- public final Set seenBy = Sets.newIdentityHashSet(); -+ public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl - - public TrackedEntity(Entity entity, int i, int j, boolean flag) { - this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit diff --git a/patches/server/0644-Optimise-BlockSoil-nearby-water-lookup.patch b/patches/server/0643-Optimise-BlockSoil-nearby-water-lookup.patch similarity index 100% rename from patches/server/0644-Optimise-BlockSoil-nearby-water-lookup.patch rename to patches/server/0643-Optimise-BlockSoil-nearby-water-lookup.patch diff --git a/patches/server/0645-Fix-merchant-inventory-not-closing-on-entity-removal.patch b/patches/server/0644-Fix-merchant-inventory-not-closing-on-entity-removal.patch similarity index 100% rename from patches/server/0645-Fix-merchant-inventory-not-closing-on-entity-removal.patch rename to patches/server/0644-Fix-merchant-inventory-not-closing-on-entity-removal.patch diff --git a/patches/server/0647-Check-requirement-before-suggesting-root-nodes.patch b/patches/server/0645-Check-requirement-before-suggesting-root-nodes.patch similarity index 100% rename from patches/server/0647-Check-requirement-before-suggesting-root-nodes.patch rename to patches/server/0645-Check-requirement-before-suggesting-root-nodes.patch diff --git a/patches/server/0646-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch b/patches/server/0646-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch new file mode 100644 index 000000000000..e21f5ec679f2 --- /dev/null +++ b/patches/server/0646-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: stonar96 +Date: Sun, 12 Sep 2021 00:14:21 +0200 +Subject: [PATCH] Don't respond to ServerboundCommandSuggestionPacket when + tab-complete is disabled + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index b0df4c822ad01f1ed0f8b858e4ca012a823c7f47..bf790227e9716a9f678bea1914430c1c65027468 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -725,6 +725,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + return; + } + // CraftBukkit end ++ // Paper start - Don't suggest if tab-complete is disabled ++ if (org.spigotmc.SpigotConfig.tabComplete < 0) { ++ return; ++ } ++ // Paper end - Don't suggest if tab-complete is disabled + // Paper start - AsyncTabCompleteEvent + TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet)); + } diff --git a/patches/server/0646-Use-correct-max-stack-size-in-crafter.patch b/patches/server/0646-Use-correct-max-stack-size-in-crafter.patch deleted file mode 100644 index 0ee54e0bc865..000000000000 --- a/patches/server/0646-Use-correct-max-stack-size-in-crafter.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 19 Dec 2023 13:52:21 -0800 -Subject: [PATCH] Use correct max stack size in crafter - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/CrafterBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CrafterBlockEntity.java -index c832aad3312ecd4d8027e4f78c12f640dec56bf9..0801e09d41223c65eee37256c5cfb3c6dce1e44e 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/CrafterBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/CrafterBlockEntity.java -@@ -40,7 +40,7 @@ public class CrafterBlockEntity extends RandomizableContainerBlockEntity impleme - protected final ContainerData containerData; - // CraftBukkit start - add fields and methods - public List transaction = new java.util.ArrayList<>(); -- private int maxStack = 1; -+ private int maxStack = CraftingContainer.LARGE_MAX_STACK_SIZE; // Paper - use correct stack size - - @Override - public List getContents() { diff --git a/patches/server/0647-Add-packet-limiter-config.patch b/patches/server/0647-Add-packet-limiter-config.patch new file mode 100644 index 000000000000..dd045eca7512 --- /dev/null +++ b/patches/server/0647-Add-packet-limiter-config.patch @@ -0,0 +1,108 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 30 Oct 2020 22:37:16 -0700 +Subject: [PATCH] Add packet limiter config + +Example config: +packet-limiter: + kick-message: '&cSent too many packets' + limits: + all: + interval: 7.0 + max-packet-rate: 500.0 + ServerboundPlaceRecipePacket: + interval: 4.0 + max-packet-rate: 5.0 + action: DROP + +all section refers to all incoming packets, the action for all is +hard coded to KICK. + +For specific limits, the section name is the class's name, +and an action can be defined: DROP or KICK + +If interval or rate are less-than 0, the limit is ignored + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 37b16918451859c22f92bcbcbce05c16b8beff75..22a7f17180b76b6c3548d3b54ae8218a469401a8 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -128,6 +128,22 @@ public class Connection extends SimpleChannelInboundHandler> { + return null; + } + // Paper end - add utility methods ++ // Paper start - packet limiter ++ protected final Object PACKET_LIMIT_LOCK = new Object(); ++ protected final @Nullable io.papermc.paper.util.IntervalledCounter allPacketCounts = io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.isEnabled() ? new io.papermc.paper.util.IntervalledCounter( ++ (long)(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.interval() * 1.0e9) ++ ) : null; ++ protected final java.util.Map>, io.papermc.paper.util.IntervalledCounter> packetSpecificLimits = new java.util.HashMap<>(); ++ ++ private boolean stopReadingPackets; ++ private void killForPacketSpam() { ++ this.sendPacket(new ClientboundDisconnectPacket(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage)), PacketSendListener.thenRun(() -> { ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage)); ++ }), true); ++ this.setReadOnly(); ++ this.stopReadingPackets = true; ++ } ++ // Paper end - packet limiter + + public Connection(PacketFlow side) { + this.receiving = side; +@@ -220,6 +236,55 @@ public class Connection extends SimpleChannelInboundHandler> { + if (packetlistener == null) { + throw new IllegalStateException("Received a packet before the packet listener was initialized"); + } else { ++ // Paper start - packet limiter ++ if (this.stopReadingPackets) { ++ return; ++ } ++ if (this.allPacketCounts != null || ++ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.containsKey(packet.getClass())) { ++ long time = System.nanoTime(); ++ synchronized (PACKET_LIMIT_LOCK) { ++ if (this.allPacketCounts != null) { ++ this.allPacketCounts.updateAndAdd(1, time); ++ if (this.allPacketCounts.getRate() >= io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.maxPacketRate()) { ++ this.killForPacketSpam(); ++ return; ++ } ++ } ++ ++ for (Class check = packet.getClass(); check != Object.class; check = check.getSuperclass()) { ++ io.papermc.paper.configuration.GlobalConfiguration.PacketLimiter.PacketLimit packetSpecificLimit = ++ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.get(check); ++ if (packetSpecificLimit == null || !packetSpecificLimit.isEnabled()) { ++ continue; ++ } ++ io.papermc.paper.util.IntervalledCounter counter = this.packetSpecificLimits.computeIfAbsent((Class)check, (clazz) -> { ++ return new io.papermc.paper.util.IntervalledCounter((long)(packetSpecificLimit.interval() * 1.0e9)); ++ }); ++ counter.updateAndAdd(1, time); ++ if (counter.getRate() >= packetSpecificLimit.maxPacketRate()) { ++ switch (packetSpecificLimit.action()) { ++ case DROP: ++ return; ++ case KICK: ++ String deobfedPacketName = io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(check.getName()); ++ ++ String playerName; ++ if (this.packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl impl) { ++ playerName = impl.getOwner().getName(); ++ } else { ++ playerName = this.getLoggableAddress(net.minecraft.server.MinecraftServer.getServer().logIPs()); ++ } ++ ++ Connection.LOGGER.warn("{} kicked for packet spamming: {}", playerName, deobfedPacketName.substring(deobfedPacketName.lastIndexOf(".") + 1)); ++ this.killForPacketSpam(); ++ return; ++ } ++ } ++ } ++ } ++ } ++ // Paper end - packet limiter + if (packetlistener.shouldHandleMessage(packet)) { + try { + Connection.genericsFtw(packet, packetlistener); diff --git a/patches/server/0648-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch b/patches/server/0648-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch deleted file mode 100644 index 0a1bfe0a242b..000000000000 --- a/patches/server/0648-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: stonar96 -Date: Sun, 12 Sep 2021 00:14:21 +0200 -Subject: [PATCH] Don't respond to ServerboundCommandSuggestionPacket when - tab-complete is disabled - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 4ab3f9321f37956420549e63ce65b955ca9c3b94..fe83e4b592e13bf325e76d051df6901f22221484 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -725,6 +725,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - return; - } - // CraftBukkit end -+ // Paper start - Don't suggest if tab-complete is disabled -+ if (org.spigotmc.SpigotConfig.tabComplete < 0) { -+ return; -+ } -+ // Paper end - Don't suggest if tab-complete is disabled - // Paper start - AsyncTabCompleteEvent - TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet)); - } diff --git a/patches/server/0650-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch b/patches/server/0648-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch similarity index 100% rename from patches/server/0650-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch rename to patches/server/0648-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch diff --git a/patches/server/0649-Add-packet-limiter-config.patch b/patches/server/0649-Add-packet-limiter-config.patch deleted file mode 100644 index 38146c83421d..000000000000 --- a/patches/server/0649-Add-packet-limiter-config.patch +++ /dev/null @@ -1,108 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 30 Oct 2020 22:37:16 -0700 -Subject: [PATCH] Add packet limiter config - -Example config: -packet-limiter: - kick-message: '&cSent too many packets' - limits: - all: - interval: 7.0 - max-packet-rate: 500.0 - ServerboundPlaceRecipePacket: - interval: 4.0 - max-packet-rate: 5.0 - action: DROP - -all section refers to all incoming packets, the action for all is -hard coded to KICK. - -For specific limits, the section name is the class's name, -and an action can be defined: DROP or KICK - -If interval or rate are less-than 0, the limit is ignored - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index c7e4d38f67a196b6334e0cc2b9ce9bd96fdc5b0a..e6a8f36fa07561b69b9d869022234182bdd62da0 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -128,6 +128,22 @@ public class Connection extends SimpleChannelInboundHandler> { - return null; - } - // Paper end - add utility methods -+ // Paper start - packet limiter -+ protected final Object PACKET_LIMIT_LOCK = new Object(); -+ protected final @Nullable io.papermc.paper.util.IntervalledCounter allPacketCounts = io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.isEnabled() ? new io.papermc.paper.util.IntervalledCounter( -+ (long)(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.interval() * 1.0e9) -+ ) : null; -+ protected final java.util.Map>, io.papermc.paper.util.IntervalledCounter> packetSpecificLimits = new java.util.HashMap<>(); -+ -+ private boolean stopReadingPackets; -+ private void killForPacketSpam() { -+ this.sendPacket(new ClientboundDisconnectPacket(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage)), PacketSendListener.thenRun(() -> { -+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage)); -+ }), true); -+ this.setReadOnly(); -+ this.stopReadingPackets = true; -+ } -+ // Paper end - packet limiter - - public Connection(PacketFlow side) { - this.receiving = side; -@@ -220,6 +236,55 @@ public class Connection extends SimpleChannelInboundHandler> { - if (packetlistener == null) { - throw new IllegalStateException("Received a packet before the packet listener was initialized"); - } else { -+ // Paper start - packet limiter -+ if (this.stopReadingPackets) { -+ return; -+ } -+ if (this.allPacketCounts != null || -+ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.containsKey(packet.getClass())) { -+ long time = System.nanoTime(); -+ synchronized (PACKET_LIMIT_LOCK) { -+ if (this.allPacketCounts != null) { -+ this.allPacketCounts.updateAndAdd(1, time); -+ if (this.allPacketCounts.getRate() >= io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.maxPacketRate()) { -+ this.killForPacketSpam(); -+ return; -+ } -+ } -+ -+ for (Class check = packet.getClass(); check != Object.class; check = check.getSuperclass()) { -+ io.papermc.paper.configuration.GlobalConfiguration.PacketLimiter.PacketLimit packetSpecificLimit = -+ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.get(check); -+ if (packetSpecificLimit == null || !packetSpecificLimit.isEnabled()) { -+ continue; -+ } -+ io.papermc.paper.util.IntervalledCounter counter = this.packetSpecificLimits.computeIfAbsent((Class)check, (clazz) -> { -+ return new io.papermc.paper.util.IntervalledCounter((long)(packetSpecificLimit.interval() * 1.0e9)); -+ }); -+ counter.updateAndAdd(1, time); -+ if (counter.getRate() >= packetSpecificLimit.maxPacketRate()) { -+ switch (packetSpecificLimit.action()) { -+ case DROP: -+ return; -+ case KICK: -+ String deobfedPacketName = io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(check.getName()); -+ -+ String playerName; -+ if (this.packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl impl) { -+ playerName = impl.getOwner().getName(); -+ } else { -+ playerName = this.getLoggableAddress(net.minecraft.server.MinecraftServer.getServer().logIPs()); -+ } -+ -+ Connection.LOGGER.warn("{} kicked for packet spamming: {}", playerName, deobfedPacketName.substring(deobfedPacketName.lastIndexOf(".") + 1)); -+ this.killForPacketSpam(); -+ return; -+ } -+ } -+ } -+ } -+ } -+ // Paper end - packet limiter - if (packetlistener.shouldHandleMessage(packet)) { - try { - Connection.genericsFtw(packet, packetlistener); diff --git a/patches/server/0649-Ensure-valid-vehicle-status.patch b/patches/server/0649-Ensure-valid-vehicle-status.patch new file mode 100644 index 000000000000..24e12fde1c1a --- /dev/null +++ b/patches/server/0649-Ensure-valid-vehicle-status.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Tue, 28 Sep 2021 09:47:47 +0200 +Subject: [PATCH] Ensure valid vehicle status + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 48edd0bf7a6f24aaf582a96ee7cb4c29c9e3598a..be8c6c48287b73693ead9ae22071f2b4af7eed32 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -527,7 +527,7 @@ public class ServerPlayer extends Player { + } + } + +- if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger()) { ++ if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger() && !entity.isRemoved()) { // Paper - Ensure valid vehicle status + // CraftBukkit end + CompoundTag nbttagcompound2 = new CompoundTag(); + CompoundTag nbttagcompound3 = new CompoundTag(); diff --git a/patches/server/0650-Prevent-softlocked-end-exit-portal-generation.patch b/patches/server/0650-Prevent-softlocked-end-exit-portal-generation.patch new file mode 100644 index 000000000000..6a1c04bc8af2 --- /dev/null +++ b/patches/server/0650-Prevent-softlocked-end-exit-portal-generation.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Mon, 30 Aug 2021 15:22:18 +0200 +Subject: [PATCH] Prevent softlocked end exit portal generation + + +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 1d5edcad4c5bfe48711cfce7c46a9c4606196ae3..59f6c3109b34719a7ed487ada5a8ce33ec458e87 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 +@@ -466,6 +466,11 @@ public class EndDragonFight { + } + } + ++ // Paper start - Prevent "softlocked" exit portal generation ++ if (this.portalLocation.getY() <= this.level.getMinBuildHeight()) { ++ this.portalLocation = this.portalLocation.atY(this.level.getMinBuildHeight() + 1); ++ } ++ // Paper end - Prevent "softlocked" exit portal generation + if (worldgenendtrophy.place(FeatureConfiguration.NONE, this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), this.portalLocation)) { + int i = Mth.positiveCeilDiv(4, 16); + diff --git a/patches/server/0651-Ensure-valid-vehicle-status.patch b/patches/server/0651-Ensure-valid-vehicle-status.patch deleted file mode 100644 index ed5c4e0c7837..000000000000 --- a/patches/server/0651-Ensure-valid-vehicle-status.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Tue, 28 Sep 2021 09:47:47 +0200 -Subject: [PATCH] Ensure valid vehicle status - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index b04b31e599c4954d4d4176f9d99f29bf0c1fadd7..e418778297b89edd3cdf4ce9917dcb4d4d130023 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -527,7 +527,7 @@ public class ServerPlayer extends Player { - } - } - -- if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger()) { -+ if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger() && !entity.isRemoved()) { // Paper - Ensure valid vehicle status - // CraftBukkit end - CompoundTag nbttagcompound2 = new CompoundTag(); - CompoundTag nbttagcompound3 = new CompoundTag(); diff --git a/patches/server/0653-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch b/patches/server/0651-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch similarity index 100% rename from patches/server/0653-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch rename to patches/server/0651-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch diff --git a/patches/server/0654-Don-t-log-debug-logging-being-disabled.patch b/patches/server/0652-Don-t-log-debug-logging-being-disabled.patch similarity index 100% rename from patches/server/0654-Don-t-log-debug-logging-being-disabled.patch rename to patches/server/0652-Don-t-log-debug-logging-being-disabled.patch diff --git a/patches/server/0652-Prevent-softlocked-end-exit-portal-generation.patch b/patches/server/0652-Prevent-softlocked-end-exit-portal-generation.patch deleted file mode 100644 index 6c29d863247f..000000000000 --- a/patches/server/0652-Prevent-softlocked-end-exit-portal-generation.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Mon, 30 Aug 2021 15:22:18 +0200 -Subject: [PATCH] Prevent softlocked end exit portal generation - - -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 5b14d63e7c354cd51d67ddc045cc86a0f7b36811..2d74efff30c93ba664cf8dbf20e47dcdbd767d3f 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 -@@ -466,6 +466,11 @@ public class EndDragonFight { - } - } - -+ // Paper start - Prevent "softlocked" exit portal generation -+ if (this.portalLocation.getY() <= this.level.getMinBuildHeight()) { -+ this.portalLocation = this.portalLocation.atY(this.level.getMinBuildHeight() + 1); -+ } -+ // Paper end - Prevent "softlocked" exit portal generation - if (worldgenendtrophy.place(FeatureConfiguration.NONE, this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), this.portalLocation)) { - int i = Mth.positiveCeilDiv(4, 16); - diff --git a/patches/server/0655-fix-various-menus-with-empty-level-accesses.patch b/patches/server/0653-fix-various-menus-with-empty-level-accesses.patch similarity index 100% rename from patches/server/0655-fix-various-menus-with-empty-level-accesses.patch rename to patches/server/0653-fix-various-menus-with-empty-level-accesses.patch diff --git a/patches/server/0656-Preserve-overstacked-loot.patch b/patches/server/0654-Preserve-overstacked-loot.patch similarity index 100% rename from patches/server/0656-Preserve-overstacked-loot.patch rename to patches/server/0654-Preserve-overstacked-loot.patch diff --git a/patches/server/0655-Update-head-rotation-in-missing-places.patch b/patches/server/0655-Update-head-rotation-in-missing-places.patch new file mode 100644 index 000000000000..26bfd657fea0 --- /dev/null +++ b/patches/server/0655-Update-head-rotation-in-missing-places.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 21 Jun 2021 21:55:23 -0400 +Subject: [PATCH] Update head rotation in missing places + +In certain areas the player's head rotation could be desynced when teleported/moved. +This is because bukkit uses a separate head rotation field for yaw. +This issue only applies to players. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index ac09bfed823524b3b11b8ad622198a62c70f7e69..3916da425bc0a2043070823f940c3a2bd6f3faf1 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1777,6 +1777,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + this.setXRot(Mth.clamp(pitch, -90.0F, 90.0F) % 360.0F); + this.yRotO = this.getYRot(); + this.xRotO = this.getXRot(); ++ this.setYHeadRot(yaw); // Paper - Update head rotation + } + + public void absMoveTo(double x, double y, double z) { +@@ -1815,6 +1816,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + this.setXRot(pitch); + this.setOldPosAndRot(); + this.reapplyPosition(); ++ this.setYHeadRot(yaw); // Paper - Update head rotation + } + + public final void setOldPosAndRot() { diff --git a/patches/server/0658-prevent-unintended-light-block-manipulation.patch b/patches/server/0656-prevent-unintended-light-block-manipulation.patch similarity index 100% rename from patches/server/0658-prevent-unintended-light-block-manipulation.patch rename to patches/server/0656-prevent-unintended-light-block-manipulation.patch diff --git a/patches/server/0659-Fix-CraftCriteria-defaults-map.patch b/patches/server/0657-Fix-CraftCriteria-defaults-map.patch similarity index 100% rename from patches/server/0659-Fix-CraftCriteria-defaults-map.patch rename to patches/server/0657-Fix-CraftCriteria-defaults-map.patch diff --git a/patches/server/0657-Update-head-rotation-in-missing-places.patch b/patches/server/0657-Update-head-rotation-in-missing-places.patch deleted file mode 100644 index ac63ef112997..000000000000 --- a/patches/server/0657-Update-head-rotation-in-missing-places.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Mon, 21 Jun 2021 21:55:23 -0400 -Subject: [PATCH] Update head rotation in missing places - -In certain areas the player's head rotation could be desynced when teleported/moved. -This is because bukkit uses a separate head rotation field for yaw. -This issue only applies to players. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index c19cc2af8bf0d2f5a778cdd05560340fad6847e1..48b1648b749f884d29abe8d48865860791eda9dc 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1778,6 +1778,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - this.setXRot(Mth.clamp(pitch, -90.0F, 90.0F) % 360.0F); - this.yRotO = this.getYRot(); - this.xRotO = this.getXRot(); -+ this.setYHeadRot(yaw); // Paper - Update head rotation - } - - public void absMoveTo(double x, double y, double z) { -@@ -1816,6 +1817,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - this.setXRot(pitch); - this.setOldPosAndRot(); - this.reapplyPosition(); -+ this.setYHeadRot(yaw); // Paper - Update head rotation - } - - public final void setOldPosAndRot() { diff --git a/patches/server/0660-Fix-upstreams-block-state-factories.patch b/patches/server/0658-Fix-upstreams-block-state-factories.patch similarity index 100% rename from patches/server/0660-Fix-upstreams-block-state-factories.patch rename to patches/server/0658-Fix-upstreams-block-state-factories.patch diff --git a/patches/server/0661-Configurable-feature-seeds.patch b/patches/server/0659-Configurable-feature-seeds.patch similarity index 100% rename from patches/server/0661-Configurable-feature-seeds.patch rename to patches/server/0659-Configurable-feature-seeds.patch diff --git a/patches/server/0660-Add-root-admin-user-detection.patch b/patches/server/0660-Add-root-admin-user-detection.patch new file mode 100644 index 000000000000..74f125cf61c0 --- /dev/null +++ b/patches/server/0660-Add-root-admin-user-detection.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: egg82 +Date: Sat, 11 Sep 2021 22:55:14 +0200 +Subject: [PATCH] Add root/admin user detection + +This patch detects whether or not the server is currently executing as a privileged user and spits out a warning. +The warning serves as a sort-of PSA for newer server admins who don't understand the risks of running as root. +We've seen plenty of bad/malicious plugins hit markets, and there's been a few close-calls with exploits in the past. +Hopefully this helps mitigate some potential damage to servers, even if it is just a warning. + +Co-authored-by: Noah van der Aa + +diff --git a/src/main/java/io/papermc/paper/util/ServerEnvironment.java b/src/main/java/io/papermc/paper/util/ServerEnvironment.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6bd0afddbcc461149dfe9a5c7a86fff6ea13a5f1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/ServerEnvironment.java +@@ -0,0 +1,40 @@ ++package io.papermc.paper.util; ++ ++import com.sun.security.auth.module.NTSystem; ++import com.sun.security.auth.module.UnixSystem; ++import org.apache.commons.lang.SystemUtils; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.util.Set; ++ ++public class ServerEnvironment { ++ private static final boolean RUNNING_AS_ROOT_OR_ADMIN; ++ private static final String WINDOWS_HIGH_INTEGRITY_LEVEL = "S-1-16-12288"; ++ ++ static { ++ if (SystemUtils.IS_OS_WINDOWS) { ++ RUNNING_AS_ROOT_OR_ADMIN = Set.of(new NTSystem().getGroupIDs()).contains(WINDOWS_HIGH_INTEGRITY_LEVEL); ++ } else { ++ boolean isRunningAsRoot = false; ++ if (new UnixSystem().getUid() == 0) { ++ // Due to an OpenJDK bug (https://bugs.openjdk.java.net/browse/JDK-8274721), UnixSystem#getUid incorrectly ++ // returns 0 when the user doesn't have a username. Because of this, we'll have to double-check if the user ID is ++ // actually 0 by running the id -u command. ++ try { ++ Process process = new ProcessBuilder("id", "-u").start(); ++ process.waitFor(); ++ InputStream inputStream = process.getInputStream(); ++ isRunningAsRoot = new String(inputStream.readAllBytes()).trim().equals("0"); ++ } catch (InterruptedException | IOException ignored) { ++ isRunningAsRoot = false; ++ } ++ } ++ RUNNING_AS_ROOT_OR_ADMIN = isRunningAsRoot; ++ } ++ } ++ ++ public static boolean userIsRootOrAdmin() { ++ return RUNNING_AS_ROOT_OR_ADMIN; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 6b4c9ef02931491dd048646ead494892f06504c5..608d860b940dee870a3df3d52efaed5e9eab17cf 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -179,6 +179,16 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); + } + ++ // Paper start - detect running as root ++ if (io.papermc.paper.util.ServerEnvironment.userIsRootOrAdmin()) { ++ DedicatedServer.LOGGER.warn("****************************"); ++ DedicatedServer.LOGGER.warn("YOU ARE RUNNING THIS SERVER AS AN ADMINISTRATIVE OR ROOT USER. THIS IS NOT ADVISED."); ++ DedicatedServer.LOGGER.warn("YOU ARE OPENING YOURSELF UP TO POTENTIAL RISKS WHEN DOING THIS."); ++ DedicatedServer.LOGGER.warn("FOR MORE INFORMATION, SEE https://madelinemiller.dev/blog/root-minecraft-server/"); ++ DedicatedServer.LOGGER.warn("****************************"); ++ } ++ // Paper end - detect running as root ++ + DedicatedServer.LOGGER.info("Loading properties"); + DedicatedServerProperties dedicatedserverproperties = this.settings.getProperties(); + diff --git a/patches/server/0663-Always-allow-item-changing-in-Fireball.patch b/patches/server/0661-Always-allow-item-changing-in-Fireball.patch similarity index 100% rename from patches/server/0663-Always-allow-item-changing-in-Fireball.patch rename to patches/server/0661-Always-allow-item-changing-in-Fireball.patch diff --git a/patches/server/0662-Add-root-admin-user-detection.patch b/patches/server/0662-Add-root-admin-user-detection.patch deleted file mode 100644 index 5bb88a6088a3..000000000000 --- a/patches/server/0662-Add-root-admin-user-detection.patch +++ /dev/null @@ -1,79 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: egg82 -Date: Sat, 11 Sep 2021 22:55:14 +0200 -Subject: [PATCH] Add root/admin user detection - -This patch detects whether or not the server is currently executing as a privileged user and spits out a warning. -The warning serves as a sort-of PSA for newer server admins who don't understand the risks of running as root. -We've seen plenty of bad/malicious plugins hit markets, and there's been a few close-calls with exploits in the past. -Hopefully this helps mitigate some potential damage to servers, even if it is just a warning. - -Co-authored-by: Noah van der Aa - -diff --git a/src/main/java/io/papermc/paper/util/ServerEnvironment.java b/src/main/java/io/papermc/paper/util/ServerEnvironment.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6bd0afddbcc461149dfe9a5c7a86fff6ea13a5f1 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/ServerEnvironment.java -@@ -0,0 +1,40 @@ -+package io.papermc.paper.util; -+ -+import com.sun.security.auth.module.NTSystem; -+import com.sun.security.auth.module.UnixSystem; -+import org.apache.commons.lang.SystemUtils; -+ -+import java.io.IOException; -+import java.io.InputStream; -+import java.util.Set; -+ -+public class ServerEnvironment { -+ private static final boolean RUNNING_AS_ROOT_OR_ADMIN; -+ private static final String WINDOWS_HIGH_INTEGRITY_LEVEL = "S-1-16-12288"; -+ -+ static { -+ if (SystemUtils.IS_OS_WINDOWS) { -+ RUNNING_AS_ROOT_OR_ADMIN = Set.of(new NTSystem().getGroupIDs()).contains(WINDOWS_HIGH_INTEGRITY_LEVEL); -+ } else { -+ boolean isRunningAsRoot = false; -+ if (new UnixSystem().getUid() == 0) { -+ // Due to an OpenJDK bug (https://bugs.openjdk.java.net/browse/JDK-8274721), UnixSystem#getUid incorrectly -+ // returns 0 when the user doesn't have a username. Because of this, we'll have to double-check if the user ID is -+ // actually 0 by running the id -u command. -+ try { -+ Process process = new ProcessBuilder("id", "-u").start(); -+ process.waitFor(); -+ InputStream inputStream = process.getInputStream(); -+ isRunningAsRoot = new String(inputStream.readAllBytes()).trim().equals("0"); -+ } catch (InterruptedException | IOException ignored) { -+ isRunningAsRoot = false; -+ } -+ } -+ RUNNING_AS_ROOT_OR_ADMIN = isRunningAsRoot; -+ } -+ } -+ -+ public static boolean userIsRootOrAdmin() { -+ return RUNNING_AS_ROOT_OR_ADMIN; -+ } -+} -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 212ce0957d623776a11779c4a476c76bc7c1c0bd..c39b19bee3aca093a2087e19875a50ff21cf1ab3 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -179,6 +179,16 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); - } - -+ // Paper start - detect running as root -+ if (io.papermc.paper.util.ServerEnvironment.userIsRootOrAdmin()) { -+ DedicatedServer.LOGGER.warn("****************************"); -+ DedicatedServer.LOGGER.warn("YOU ARE RUNNING THIS SERVER AS AN ADMINISTRATIVE OR ROOT USER. THIS IS NOT ADVISED."); -+ DedicatedServer.LOGGER.warn("YOU ARE OPENING YOURSELF UP TO POTENTIAL RISKS WHEN DOING THIS."); -+ DedicatedServer.LOGGER.warn("FOR MORE INFORMATION, SEE https://madelinemiller.dev/blog/root-minecraft-server/"); -+ DedicatedServer.LOGGER.warn("****************************"); -+ } -+ // Paper end - detect running as root -+ - DedicatedServer.LOGGER.info("Loading properties"); - DedicatedServerProperties dedicatedserverproperties = this.settings.getProperties(); - diff --git a/patches/server/0662-don-t-attempt-to-teleport-dead-entities.patch b/patches/server/0662-don-t-attempt-to-teleport-dead-entities.patch new file mode 100644 index 000000000000..a06cf03b5f5d --- /dev/null +++ b/patches/server/0662-don-t-attempt-to-teleport-dead-entities.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: sulu5890 +Date: Sun, 24 Oct 2021 22:48:14 -0500 +Subject: [PATCH] don't attempt to teleport dead entities + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 3916da425bc0a2043070823f940c3a2bd6f3faf1..9f3ae962b800021faad0ae9ce064b364002a2135 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -705,7 +705,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + // CraftBukkit start + public void postTick() { + // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle +- if (!(this instanceof ServerPlayer)) { ++ if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities + this.handleNetherPortal(); + } + } diff --git a/patches/server/0663-Prevent-excessive-velocity-through-repeated-crits.patch b/patches/server/0663-Prevent-excessive-velocity-through-repeated-crits.patch new file mode 100644 index 000000000000..0379cedd9196 --- /dev/null +++ b/patches/server/0663-Prevent-excessive-velocity-through-repeated-crits.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 25 Nov 2021 10:25:09 +0100 +Subject: [PATCH] Prevent excessive velocity through repeated crits + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 16d8cc391f384bee57550f507484d60f344de76e..291cfb86f85eb617d1156f64ea72e6143b94a4b2 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2689,13 +2689,26 @@ public abstract class LivingEntity extends Entity implements Attackable { + return this.hasEffect(MobEffects.JUMP) ? 0.1F * ((float) this.getEffect(MobEffects.JUMP).getAmplifier() + 1.0F) : 0.0F; + } + ++ protected long lastJumpTime = 0L; // Paper - Prevent excessive velocity through repeated crits + protected void jumpFromGround() { + Vec3 vec3d = this.getDeltaMovement(); ++ // Paper start - Prevent excessive velocity through repeated crits ++ long time = System.nanoTime(); ++ boolean canCrit = true; ++ if (this instanceof net.minecraft.world.entity.player.Player) { ++ canCrit = false; ++ if (time - this.lastJumpTime > (long)(0.250e9)) { ++ this.lastJumpTime = time; ++ canCrit = true; ++ } ++ } ++ // Paper end - Prevent excessive velocity through repeated crits + + this.setDeltaMovement(vec3d.x, (double) this.getJumpPower(), vec3d.z); + if (this.isSprinting()) { + float f = this.getYRot() * 0.017453292F; + ++ if (canCrit) // Paper - Prevent excessive velocity through repeated crits + this.setDeltaMovement(this.getDeltaMovement().add((double) (-Mth.sin(f) * 0.2F), 0.0D, (double) (Mth.cos(f) * 0.2F))); + } + diff --git a/patches/server/0666-Remove-client-side-code-using-deprecated-for-removal.patch b/patches/server/0664-Remove-client-side-code-using-deprecated-for-removal.patch similarity index 100% rename from patches/server/0666-Remove-client-side-code-using-deprecated-for-removal.patch rename to patches/server/0664-Remove-client-side-code-using-deprecated-for-removal.patch diff --git a/patches/server/0664-don-t-attempt-to-teleport-dead-entities.patch b/patches/server/0664-don-t-attempt-to-teleport-dead-entities.patch deleted file mode 100644 index 77871ece3dd9..000000000000 --- a/patches/server/0664-don-t-attempt-to-teleport-dead-entities.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: sulu5890 -Date: Sun, 24 Oct 2021 22:48:14 -0500 -Subject: [PATCH] don't attempt to teleport dead entities - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 48b1648b749f884d29abe8d48865860791eda9dc..021d3803ffbf4fb0a6de92c13406c5316e09c4c1 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -705,7 +705,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - // CraftBukkit start - public void postTick() { - // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle -- if (!(this instanceof ServerPlayer)) { -+ if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities - this.handleNetherPortal(); - } - } diff --git a/patches/server/0667-Fix-removing-recipes-from-RecipeIterator.patch b/patches/server/0665-Fix-removing-recipes-from-RecipeIterator.patch similarity index 100% rename from patches/server/0667-Fix-removing-recipes-from-RecipeIterator.patch rename to patches/server/0665-Fix-removing-recipes-from-RecipeIterator.patch diff --git a/patches/server/0665-Prevent-excessive-velocity-through-repeated-crits.patch b/patches/server/0665-Prevent-excessive-velocity-through-repeated-crits.patch deleted file mode 100644 index cc55e32c588d..000000000000 --- a/patches/server/0665-Prevent-excessive-velocity-through-repeated-crits.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Thu, 25 Nov 2021 10:25:09 +0100 -Subject: [PATCH] Prevent excessive velocity through repeated crits - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 61c8d108122e27f063d677e1d3130a4d3eeecb94..24a7ded2966b5431bae938b4ac903683fa2a3da7 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -2676,13 +2676,26 @@ public abstract class LivingEntity extends Entity implements Attackable { - return this.hasEffect(MobEffects.JUMP) ? 0.1F * ((float) this.getEffect(MobEffects.JUMP).getAmplifier() + 1.0F) : 0.0F; - } - -+ protected long lastJumpTime = 0L; // Paper - Prevent excessive velocity through repeated crits - protected void jumpFromGround() { - Vec3 vec3d = this.getDeltaMovement(); -+ // Paper start - Prevent excessive velocity through repeated crits -+ long time = System.nanoTime(); -+ boolean canCrit = true; -+ if (this instanceof net.minecraft.world.entity.player.Player) { -+ canCrit = false; -+ if (time - this.lastJumpTime > (long)(0.250e9)) { -+ this.lastJumpTime = time; -+ canCrit = true; -+ } -+ } -+ // Paper end - Prevent excessive velocity through repeated crits - - this.setDeltaMovement(vec3d.x, (double) this.getJumpPower(), vec3d.z); - if (this.isSprinting()) { - float f = this.getYRot() * 0.017453292F; - -+ if (canCrit) // Paper - Prevent excessive velocity through repeated crits - this.setDeltaMovement(this.getDeltaMovement().add((double) (-Mth.sin(f) * 0.2F), 0.0D, (double) (Mth.cos(f) * 0.2F))); - } - diff --git a/patches/server/0666-Prevent-sending-oversized-item-data-in-equipment-and.patch b/patches/server/0666-Prevent-sending-oversized-item-data-in-equipment-and.patch new file mode 100644 index 000000000000..7cd3ceec2b3d --- /dev/null +++ b/patches/server/0666-Prevent-sending-oversized-item-data-in-equipment-and.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Wed, 1 Dec 2021 12:36:25 +0100 +Subject: [PATCH] Prevent sending oversized item data in equipment and metadata + + +diff --git a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java +index f9e15908143f4453c2a5817b412e8a13554553f0..06498788c169133bd563c5a87192b71802c4d4df 100644 +--- a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java ++++ b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java +@@ -42,7 +42,7 @@ public class EntityDataSerializers { + public static final EntityDataSerializer ITEM_STACK = new EntityDataSerializer() { + @Override + public void write(FriendlyByteBuf buf, ItemStack value) { +- buf.writeItem(value); ++ buf.writeItem(net.minecraft.world.entity.LivingEntity.sanitizeItemStack(value, true)); // Paper - prevent oversized data + } + + @Override +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index a83b4d9b61230ecf5f776269cab228cf49c5f546..86f6e5bad325dd3d817b643388b196121624b8c7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -335,7 +335,10 @@ public class ServerEntity { + ItemStack itemstack = ((LivingEntity) this.entity).getItemBySlot(enumitemslot); + + if (!itemstack.isEmpty()) { +- list.add(Pair.of(enumitemslot, itemstack.copy())); ++ // Paper start - prevent oversized data ++ final ItemStack sanitized = LivingEntity.sanitizeItemStack(itemstack.copy(), false); ++ list.add(Pair.of(enumitemslot, sanitized)); ++ // Paper end - prevent oversized data + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 291cfb86f85eb617d1156f64ea72e6143b94a4b2..fa11c0b3125ac74538848018fe6d8c88b78bfb2e 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3200,7 +3200,10 @@ public abstract class LivingEntity extends Entity implements Attackable { + equipmentChanges.forEach((enumitemslot, itemstack) -> { + ItemStack itemstack1 = itemstack.copy(); + +- list.add(Pair.of(enumitemslot, itemstack1)); ++ // Paper start - prevent oversized data ++ ItemStack toSend = sanitizeItemStack(itemstack1, true); ++ list.add(Pair.of(enumitemslot, toSend)); ++ // Paper end - prevent oversized data + switch (enumitemslot.getType()) { + case HAND: + this.setLastHandItem(enumitemslot, itemstack1); +@@ -3213,6 +3216,34 @@ public abstract class LivingEntity extends Entity implements Attackable { + ((ServerLevel) this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list)); + } + ++ // Paper start - prevent oversized data ++ public static ItemStack sanitizeItemStack(final ItemStack itemStack, final boolean copyItemStack) { ++ if (itemStack.isEmpty() || !itemStack.hasTag()) { ++ return itemStack; ++ } ++ ++ final ItemStack copy = copyItemStack ? itemStack.copy() : itemStack; ++ final CompoundTag tag = copy.getTag(); ++ if (copy.is(Items.BUNDLE) && tag.get("Items") instanceof ListTag oldItems && !oldItems.isEmpty()) { ++ // Bundles change their texture based on their fullness. ++ org.bukkit.inventory.meta.BundleMeta bundleMeta = (org.bukkit.inventory.meta.BundleMeta) copy.asBukkitMirror().getItemMeta(); ++ int sizeUsed = 0; ++ for (org.bukkit.inventory.ItemStack item : bundleMeta.getItems()) { ++ int scale = 64 / item.getMaxStackSize(); ++ sizeUsed += scale * item.getAmount(); ++ } ++ // Now we add a single fake item that uses the same amount of slots as all other items. ++ ListTag items = new ListTag(); ++ items.add(new ItemStack(Items.PAPER, sizeUsed).save(new CompoundTag())); ++ tag.put("Items", items); ++ } ++ if (tag.get("BlockEntityTag") instanceof CompoundTag blockEntityTag) { ++ blockEntityTag.remove("Items"); ++ } ++ return copy; ++ } ++ // Paper end - prevent oversized data ++ + private ItemStack getLastArmorItem(EquipmentSlot slot) { + return (ItemStack) this.lastArmorItemStacks.get(slot.getIndex()); + } diff --git a/patches/server/0667-Hide-unnecessary-itemmeta-from-clients.patch b/patches/server/0667-Hide-unnecessary-itemmeta-from-clients.patch new file mode 100644 index 000000000000..daa573e1f012 --- /dev/null +++ b/patches/server/0667-Hide-unnecessary-itemmeta-from-clients.patch @@ -0,0 +1,125 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Tue, 3 Aug 2021 17:28:27 +0200 +Subject: [PATCH] Hide unnecessary itemmeta from clients + + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 86f6e5bad325dd3d817b643388b196121624b8c7..062225ac8b5fbc44290352d78b215640691f3c23 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -337,7 +337,7 @@ public class ServerEntity { + if (!itemstack.isEmpty()) { + // Paper start - prevent oversized data + final ItemStack sanitized = LivingEntity.sanitizeItemStack(itemstack.copy(), false); +- list.add(Pair.of(enumitemslot, sanitized)); ++ list.add(Pair.of(enumitemslot, ((LivingEntity) this.entity).stripMeta(sanitized, false))); // Paper - Hide unnecessary item meta + // Paper end - prevent oversized data + } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index bf790227e9716a9f678bea1914430c1c65027468..1fb3e89d6f7f9d048f083e80b1259480d4e67ef9 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2561,8 +2561,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + // Refresh the current entity metadata + entity.getEntityData().refresh(ServerGamePacketListenerImpl.this.player); + // SPIGOT-7136 - Allays +- if (entity instanceof Allay) { +- ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) entity).getItemBySlot(slot).copy())).collect(Collectors.toList()))); ++ if (entity instanceof Allay allay) { // Paper - Hide unnecessary item meta ++ ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, allay.stripMeta(allay.getItemBySlot(slot), true))).collect(Collectors.toList()))); // Paper - Hide unnecessary item meta + ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); + } + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index fa11c0b3125ac74538848018fe6d8c88b78bfb2e..4e781939e7c980d095a4b0fdd11736ec48b4ecfa 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3202,7 +3202,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + + // Paper start - prevent oversized data + ItemStack toSend = sanitizeItemStack(itemstack1, true); +- list.add(Pair.of(enumitemslot, toSend)); ++ list.add(Pair.of(enumitemslot, stripMeta(toSend, toSend == itemstack1))); // Paper - Hide unnecessary item meta + // Paper end - prevent oversized data + switch (enumitemslot.getType()) { + case HAND: +@@ -3216,6 +3216,77 @@ public abstract class LivingEntity extends Entity implements Attackable { + ((ServerLevel) this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list)); + } + ++ // Paper start - Hide unnecessary item meta ++ public ItemStack stripMeta(final ItemStack itemStack, final boolean copyItemStack) { ++ if (itemStack.isEmpty() || (!itemStack.hasTag() && itemStack.getCount() < 2)) { ++ return itemStack; ++ } ++ ++ final ItemStack copy = copyItemStack ? itemStack.copy() : itemStack; ++ if (this.level().paperConfig().anticheat.obfuscation.items.hideDurability) { ++ // Only show damage values for elytra's, since they show a different texture when broken. ++ if (!copy.is(Items.ELYTRA) || copy.getDamageValue() < copy.getMaxDamage() - 1) { ++ copy.setDamageValue(0); ++ } ++ } ++ ++ final CompoundTag tag = copy.getTag(); ++ if (this.level().paperConfig().anticheat.obfuscation.items.hideItemmeta) { ++ // Some resource packs show different textures when there is more than one item. Since this shouldn't provide a big advantage, ++ // we'll tell the client if there's one or (more than) two items. ++ copy.setCount(copy.getCount() > 1 ? 2 : 1); ++ // We can't just strip out display, leather helmets still use the display.color tag. ++ if (tag != null) { ++ if (tag.get("display") instanceof CompoundTag displayTag) { ++ displayTag.remove("Lore"); ++ displayTag.remove("Name"); ++ } ++ ++ if (tag.get("Enchantments") instanceof ListTag enchantmentsTag && !enchantmentsTag.isEmpty()) { ++ // The client still renders items with the enchantment glow if the enchantments tag contains at least one (empty) child. ++ ListTag enchantments = new ListTag(); ++ CompoundTag fakeEnchantment = new CompoundTag(); ++ // Soul speed boots generate client side particles. ++ if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SOUL_SPEED, itemStack) > 0) { ++ fakeEnchantment.putString("id", org.bukkit.enchantments.Enchantment.SOUL_SPEED.getKey().asString()); ++ fakeEnchantment.putInt("lvl", 1); ++ } ++ enchantments.add(fakeEnchantment); ++ tag.put("Enchantments", enchantments); ++ } ++ tag.remove("AttributeModifiers"); ++ tag.remove("Unbreakable"); ++ tag.remove("PublicBukkitValues"); // Persistent data container1 ++ ++ // Books ++ tag.remove("author"); ++ tag.remove("filtered_title"); ++ tag.remove("pages"); ++ tag.remove("filtered_pages"); ++ tag.remove("title"); ++ tag.remove("generation"); ++ ++ // Filled maps ++ tag.remove("map"); ++ tag.remove("map_scale_direction"); ++ tag.remove("map_to_lock"); ++ } ++ } ++ ++ if (this.level().paperConfig().anticheat.obfuscation.items.hideItemmetaWithVisualEffects && tag != null) { ++ // Lodestone compasses ++ tag.remove("LodestonePos"); ++ if (tag.contains("LodestoneDimension")) { ++ // The client shows the glint if either the position or the dimension is present, so we just wipe ++ // the position and fake the dimension ++ tag.putString("LodestoneDimension", "paper:paper"); ++ } ++ } ++ ++ return copy; ++ } ++ // Paper end - Hide unnecessary item meta ++ + // Paper start - prevent oversized data + public static ItemStack sanitizeItemStack(final ItemStack itemStack, final boolean copyItemStack) { + if (itemStack.isEmpty() || !itemStack.hasTag()) { diff --git a/patches/server/0670-Fix-Spigot-growth-modifiers.patch b/patches/server/0668-Fix-Spigot-growth-modifiers.patch similarity index 100% rename from patches/server/0670-Fix-Spigot-growth-modifiers.patch rename to patches/server/0668-Fix-Spigot-growth-modifiers.patch diff --git a/patches/server/0668-Prevent-sending-oversized-item-data-in-equipment-and.patch b/patches/server/0668-Prevent-sending-oversized-item-data-in-equipment-and.patch deleted file mode 100644 index 4afd14619c51..000000000000 --- a/patches/server/0668-Prevent-sending-oversized-item-data-in-equipment-and.patch +++ /dev/null @@ -1,86 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Wed, 1 Dec 2021 12:36:25 +0100 -Subject: [PATCH] Prevent sending oversized item data in equipment and metadata - - -diff --git a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java -index f9e15908143f4453c2a5817b412e8a13554553f0..06498788c169133bd563c5a87192b71802c4d4df 100644 ---- a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java -+++ b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java -@@ -42,7 +42,7 @@ public class EntityDataSerializers { - public static final EntityDataSerializer ITEM_STACK = new EntityDataSerializer() { - @Override - public void write(FriendlyByteBuf buf, ItemStack value) { -- buf.writeItem(value); -+ buf.writeItem(net.minecraft.world.entity.LivingEntity.sanitizeItemStack(value, true)); // Paper - prevent oversized data - } - - @Override -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 75b722ebc6c1fc6a45a0d3a3e57b5f131f2cf815..0a86e72e50d4aab7d19f588c3f11d7465c6fe817 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -335,7 +335,10 @@ public class ServerEntity { - ItemStack itemstack = ((LivingEntity) this.entity).getItemBySlot(enumitemslot); - - if (!itemstack.isEmpty()) { -- list.add(Pair.of(enumitemslot, itemstack.copy())); -+ // Paper start - prevent oversized data -+ final ItemStack sanitized = LivingEntity.sanitizeItemStack(itemstack.copy(), false); -+ list.add(Pair.of(enumitemslot, sanitized)); -+ // Paper end - prevent oversized data - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 24a7ded2966b5431bae938b4ac903683fa2a3da7..d43ac664cae11d2f7d0c6965a57234430086f8ca 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3187,7 +3187,10 @@ public abstract class LivingEntity extends Entity implements Attackable { - equipmentChanges.forEach((enumitemslot, itemstack) -> { - ItemStack itemstack1 = itemstack.copy(); - -- list.add(Pair.of(enumitemslot, itemstack1)); -+ // Paper start - prevent oversized data -+ ItemStack toSend = sanitizeItemStack(itemstack1, true); -+ list.add(Pair.of(enumitemslot, toSend)); -+ // Paper end - prevent oversized data - switch (enumitemslot.getType()) { - case HAND: - this.setLastHandItem(enumitemslot, itemstack1); -@@ -3200,6 +3203,34 @@ public abstract class LivingEntity extends Entity implements Attackable { - ((ServerLevel) this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list)); - } - -+ // Paper start - prevent oversized data -+ public static ItemStack sanitizeItemStack(final ItemStack itemStack, final boolean copyItemStack) { -+ if (itemStack.isEmpty() || !itemStack.hasTag()) { -+ return itemStack; -+ } -+ -+ final ItemStack copy = copyItemStack ? itemStack.copy() : itemStack; -+ final CompoundTag tag = copy.getTag(); -+ if (copy.is(Items.BUNDLE) && tag.get("Items") instanceof ListTag oldItems && !oldItems.isEmpty()) { -+ // Bundles change their texture based on their fullness. -+ org.bukkit.inventory.meta.BundleMeta bundleMeta = (org.bukkit.inventory.meta.BundleMeta) copy.asBukkitMirror().getItemMeta(); -+ int sizeUsed = 0; -+ for (org.bukkit.inventory.ItemStack item : bundleMeta.getItems()) { -+ int scale = 64 / item.getMaxStackSize(); -+ sizeUsed += scale * item.getAmount(); -+ } -+ // Now we add a single fake item that uses the same amount of slots as all other items. -+ ListTag items = new ListTag(); -+ items.add(new ItemStack(Items.PAPER, sizeUsed).save(new CompoundTag())); -+ tag.put("Items", items); -+ } -+ if (tag.get("BlockEntityTag") instanceof CompoundTag blockEntityTag) { -+ blockEntityTag.remove("Items"); -+ } -+ return copy; -+ } -+ // Paper end - prevent oversized data -+ - private ItemStack getLastArmorItem(EquipmentSlot slot) { - return (ItemStack) this.lastArmorItemStacks.get(slot.getIndex()); - } diff --git a/patches/server/0669-Hide-unnecessary-itemmeta-from-clients.patch b/patches/server/0669-Hide-unnecessary-itemmeta-from-clients.patch deleted file mode 100644 index 31a1c523e9ae..000000000000 --- a/patches/server/0669-Hide-unnecessary-itemmeta-from-clients.patch +++ /dev/null @@ -1,125 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Tue, 3 Aug 2021 17:28:27 +0200 -Subject: [PATCH] Hide unnecessary itemmeta from clients - - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 86f6e5bad325dd3d817b643388b196121624b8c7..062225ac8b5fbc44290352d78b215640691f3c23 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -337,7 +337,7 @@ public class ServerEntity { - if (!itemstack.isEmpty()) { - // Paper start - prevent oversized data - final ItemStack sanitized = LivingEntity.sanitizeItemStack(itemstack.copy(), false); -- list.add(Pair.of(enumitemslot, sanitized)); -+ list.add(Pair.of(enumitemslot, ((LivingEntity) this.entity).stripMeta(sanitized, false))); // Paper - Hide unnecessary item meta - // Paper end - prevent oversized data - } - } -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index fe83e4b592e13bf325e76d051df6901f22221484..b07fbf0121c6f147ff6f59d13c2d976db9cedad1 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2561,8 +2561,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - // Refresh the current entity metadata - entity.getEntityData().refresh(ServerGamePacketListenerImpl.this.player); - // SPIGOT-7136 - Allays -- if (entity instanceof Allay) { -- ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) entity).getItemBySlot(slot).copy())).collect(Collectors.toList()))); -+ if (entity instanceof Allay allay) { // Paper - Hide unnecessary item meta -+ ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, allay.stripMeta(allay.getItemBySlot(slot), true))).collect(Collectors.toList()))); // Paper - Hide unnecessary item meta - ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); - } - } -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 8623d6a59445ab1cc0a04ca87ee7b3e3567e1ddc..37f35a576c4bdf22daf6f47b412d9147980762b0 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3189,7 +3189,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - // Paper start - prevent oversized data - ItemStack toSend = sanitizeItemStack(itemstack1, true); -- list.add(Pair.of(enumitemslot, toSend)); -+ list.add(Pair.of(enumitemslot, stripMeta(toSend, toSend == itemstack1))); // Paper - Hide unnecessary item meta - // Paper end - prevent oversized data - switch (enumitemslot.getType()) { - case HAND: -@@ -3203,6 +3203,77 @@ public abstract class LivingEntity extends Entity implements Attackable { - ((ServerLevel) this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list)); - } - -+ // Paper start - Hide unnecessary item meta -+ public ItemStack stripMeta(final ItemStack itemStack, final boolean copyItemStack) { -+ if (itemStack.isEmpty() || (!itemStack.hasTag() && itemStack.getCount() < 2)) { -+ return itemStack; -+ } -+ -+ final ItemStack copy = copyItemStack ? itemStack.copy() : itemStack; -+ if (this.level().paperConfig().anticheat.obfuscation.items.hideDurability) { -+ // Only show damage values for elytra's, since they show a different texture when broken. -+ if (!copy.is(Items.ELYTRA) || copy.getDamageValue() < copy.getMaxDamage() - 1) { -+ copy.setDamageValue(0); -+ } -+ } -+ -+ final CompoundTag tag = copy.getTag(); -+ if (this.level().paperConfig().anticheat.obfuscation.items.hideItemmeta) { -+ // Some resource packs show different textures when there is more than one item. Since this shouldn't provide a big advantage, -+ // we'll tell the client if there's one or (more than) two items. -+ copy.setCount(copy.getCount() > 1 ? 2 : 1); -+ // We can't just strip out display, leather helmets still use the display.color tag. -+ if (tag != null) { -+ if (tag.get("display") instanceof CompoundTag displayTag) { -+ displayTag.remove("Lore"); -+ displayTag.remove("Name"); -+ } -+ -+ if (tag.get("Enchantments") instanceof ListTag enchantmentsTag && !enchantmentsTag.isEmpty()) { -+ // The client still renders items with the enchantment glow if the enchantments tag contains at least one (empty) child. -+ ListTag enchantments = new ListTag(); -+ CompoundTag fakeEnchantment = new CompoundTag(); -+ // Soul speed boots generate client side particles. -+ if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SOUL_SPEED, itemStack) > 0) { -+ fakeEnchantment.putString("id", org.bukkit.enchantments.Enchantment.SOUL_SPEED.getKey().asString()); -+ fakeEnchantment.putInt("lvl", 1); -+ } -+ enchantments.add(fakeEnchantment); -+ tag.put("Enchantments", enchantments); -+ } -+ tag.remove("AttributeModifiers"); -+ tag.remove("Unbreakable"); -+ tag.remove("PublicBukkitValues"); // Persistent data container1 -+ -+ // Books -+ tag.remove("author"); -+ tag.remove("filtered_title"); -+ tag.remove("pages"); -+ tag.remove("filtered_pages"); -+ tag.remove("title"); -+ tag.remove("generation"); -+ -+ // Filled maps -+ tag.remove("map"); -+ tag.remove("map_scale_direction"); -+ tag.remove("map_to_lock"); -+ } -+ } -+ -+ if (this.level().paperConfig().anticheat.obfuscation.items.hideItemmetaWithVisualEffects && tag != null) { -+ // Lodestone compasses -+ tag.remove("LodestonePos"); -+ if (tag.contains("LodestoneDimension")) { -+ // The client shows the glint if either the position or the dimension is present, so we just wipe -+ // the position and fake the dimension -+ tag.putString("LodestoneDimension", "paper:paper"); -+ } -+ } -+ -+ return copy; -+ } -+ // Paper end - Hide unnecessary item meta -+ - // Paper start - prevent oversized data - public static ItemStack sanitizeItemStack(final ItemStack itemStack, final boolean copyItemStack) { - if (itemStack.isEmpty() || !itemStack.hasTag()) { diff --git a/patches/server/0671-Prevent-ContainerOpenersCounter-openCount-from-going.patch b/patches/server/0669-Prevent-ContainerOpenersCounter-openCount-from-going.patch similarity index 100% rename from patches/server/0671-Prevent-ContainerOpenersCounter-openCount-from-going.patch rename to patches/server/0669-Prevent-ContainerOpenersCounter-openCount-from-going.patch diff --git a/patches/server/0672-Add-PlayerItemFrameChangeEvent.patch b/patches/server/0670-Add-PlayerItemFrameChangeEvent.patch similarity index 100% rename from patches/server/0672-Add-PlayerItemFrameChangeEvent.patch rename to patches/server/0670-Add-PlayerItemFrameChangeEvent.patch diff --git a/patches/server/0673-Optimize-HashMapPalette.patch b/patches/server/0671-Optimize-HashMapPalette.patch similarity index 100% rename from patches/server/0673-Optimize-HashMapPalette.patch rename to patches/server/0671-Optimize-HashMapPalette.patch diff --git a/patches/server/0672-Allow-delegation-to-vanilla-chunk-gen.patch b/patches/server/0672-Allow-delegation-to-vanilla-chunk-gen.patch new file mode 100644 index 000000000000..7c8b0a2c4a3f --- /dev/null +++ b/patches/server/0672-Allow-delegation-to-vanilla-chunk-gen.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Wed, 29 Apr 2020 02:10:32 +0200 +Subject: [PATCH] Allow delegation to vanilla chunk gen + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index dc37989ab5e0971a144a8248152169b4fd868067..c366992f3e48f9a3a69ba48491d678ba73e81d61 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2466,6 +2466,88 @@ public final class CraftServer implements Server { + return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(Registries.BIOME)); + } + ++ // Paper start - Allow delegation to vanilla chunk gen ++ private static final List VANILLA_GEN_STATUSES = List.of( ++ net.minecraft.world.level.chunk.ChunkStatus.EMPTY, ++ net.minecraft.world.level.chunk.ChunkStatus.STRUCTURE_STARTS, ++ net.minecraft.world.level.chunk.ChunkStatus.STRUCTURE_REFERENCES, ++ net.minecraft.world.level.chunk.ChunkStatus.BIOMES, ++ net.minecraft.world.level.chunk.ChunkStatus.NOISE, ++ net.minecraft.world.level.chunk.ChunkStatus.SURFACE, ++ net.minecraft.world.level.chunk.ChunkStatus.CARVERS, ++ net.minecraft.world.level.chunk.ChunkStatus.FEATURES, ++ net.minecraft.world.level.chunk.ChunkStatus.INITIALIZE_LIGHT, ++ net.minecraft.world.level.chunk.ChunkStatus.LIGHT ++ ); ++ ++ @Override ++ @Deprecated(forRemoval = true) ++ public ChunkGenerator.ChunkData createVanillaChunkData(World world, int x, int z) { ++ // do bunch of vanilla shit ++ final net.minecraft.server.level.ServerLevel serverLevel = ((CraftWorld) world).getHandle(); ++ final net.minecraft.core.Registry biomeRegistry = serverLevel.getServer().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME); ++ final net.minecraft.world.level.chunk.ProtoChunk protoChunk = new net.minecraft.world.level.chunk.ProtoChunk( ++ new net.minecraft.world.level.ChunkPos(x, z), ++ net.minecraft.world.level.chunk.UpgradeData.EMPTY, ++ serverLevel, ++ biomeRegistry, ++ null ++ ); ++ ++ final net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator; ++ if (serverLevel.chunkSource.getGenerator() instanceof org.bukkit.craftbukkit.generator.CustomChunkGenerator bukkit) { ++ chunkGenerator = bukkit.getDelegate(); ++ } else { ++ chunkGenerator = serverLevel.chunkSource.getGenerator(); ++ } ++ ++ final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(x, z); ++ final net.minecraft.util.thread.ProcessorMailbox mailbox = net.minecraft.util.thread.ProcessorMailbox.create( ++ net.minecraft.Util.backgroundExecutor(), ++ "CraftServer#createVanillaChunkData(worldName='" + world.getName() + "', x='" + x + "', z='" + z + "')" ++ ); ++ for (final net.minecraft.world.level.chunk.ChunkStatus chunkStatus : VANILLA_GEN_STATUSES) { ++ final List chunks = Lists.newArrayList(); ++ final int statusRange = Math.max(1, chunkStatus.getRange()); ++ ++ for (int zz = chunkPos.z - statusRange; zz <= chunkPos.z + statusRange; ++zz) { ++ for (int xx = chunkPos.x - statusRange; xx <= chunkPos.x + statusRange; ++xx) { ++ if (xx == chunkPos.x && zz == chunkPos.z) { ++ chunks.add(protoChunk); ++ } else { ++ final net.minecraft.core.Holder biomeHolder = serverLevel.registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME).getHolderOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS); ++ final net.minecraft.world.level.chunk.ChunkAccess chunk = new net.minecraft.world.level.chunk.EmptyLevelChunk(serverLevel, new net.minecraft.world.level.ChunkPos(xx, zz), biomeHolder); ++ chunks.add(chunk); ++ } ++ } ++ } ++ ++ chunkStatus.generate( ++ mailbox::tell, ++ serverLevel, ++ chunkGenerator, ++ serverLevel.getStructureManager(), ++ serverLevel.chunkSource.getLightEngine(), ++ chunk -> { ++ throw new UnsupportedOperationException("Not creating full chunks here"); ++ }, ++ chunks ++ ).thenAccept(either -> { ++ if (chunkStatus == net.minecraft.world.level.chunk.ChunkStatus.NOISE) { ++ either.left().ifPresent(chunk -> net.minecraft.world.level.levelgen.Heightmap.primeHeightmaps(chunk, net.minecraft.world.level.chunk.ChunkStatus.POST_FEATURES)); ++ } ++ }).join(); ++ } ++ ++ // get empty object ++ OldCraftChunkData data = (OldCraftChunkData) this.createChunkData(world); ++ // copy over generated sections ++ data.setRawChunkData(protoChunk.getSections()); ++ // hooray! ++ return data; ++ } ++ // Paper end - Allow delegation to vanilla chunk gen ++ + @Override + public BossBar createBossBar(String title, BarColor color, BarStyle style, BarFlag... flags) { + return new CraftBossBar(title, color, style, flags); +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java +index e7f7a246e9c03e676dadfee59de87b8b2ac55ba3..9b640705f2c810160aa7fea5006429ec41d0c858 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java +@@ -23,7 +23,7 @@ import org.bukkit.material.MaterialData; + public final class OldCraftChunkData implements ChunkGenerator.ChunkData { + private final int minHeight; + private final int maxHeight; +- private final LevelChunkSection[] sections; ++ private LevelChunkSection[] sections; // Paper - Allow delegation to vanilla chunk gen + private final Registry biomes; + private Set tiles; + private final Set lights = new HashSet<>(); +@@ -189,7 +189,13 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData { + return this.tiles; + } + +- Set getLights() { ++ public Set getLights() { // Paper - Allow delegation to vanilla chunk gen + return this.lights; + } ++ ++ // Paper start - Allow delegation to vanilla chunk gen ++ public void setRawChunkData(LevelChunkSection[] sections) { ++ this.sections = sections; ++ } ++ // Paper end - Allow delegation to vanilla chunk gen + } diff --git a/patches/server/0673-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch b/patches/server/0673-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch new file mode 100644 index 000000000000..f451e896f880 --- /dev/null +++ b/patches/server/0673-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Thu, 9 Dec 2021 00:08:11 -0800 +Subject: [PATCH] Fix ChunkSnapshot#isSectionEmpty(int) and optimize + PalettedContainer copying by not using codecs + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index bfde7573acb6d84accfd3f7fee877bbfb3b0852f..260ca4a9c170567b27488466f802c91212997f91 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -337,13 +337,17 @@ public class CraftChunk implements Chunk { + PalettedContainerRO>[] biome = (includeBiome || includeBiomeTempRain) ? new PalettedContainer[cs.length] : null; + + Registry iregistry = this.worldServer.registryAccess().registryOrThrow(Registries.BIOME); +- Codec>> biomeCodec = PalettedContainer.codecRO(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS)); + + for (int i = 0; i < cs.length; i++) { +- CompoundTag data = new CompoundTag(); + +- data.put("block_states", ChunkSerializer.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, cs[i].getStates()).get().left().get()); +- sectionBlockIDs[i] = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, data.getCompound("block_states")).get().left().get(); ++ // Paper start - Fix ChunkSnapshot#isSectionEmpty(int); and remove codec usage ++ sectionEmpty[i] = cs[i].hasOnlyAir(); // fix sectionEmpty array not being filled ++ if (!sectionEmpty[i]) { ++ sectionBlockIDs[i] = cs[i].getStates().copy(); // use copy instead of round tripping with codecs ++ } else { ++ sectionBlockIDs[i] = CraftChunk.emptyBlockIDs; // use cached instance for empty block sections ++ } ++ // Paper end - Fix ChunkSnapshot#isSectionEmpty(int) + + LevelLightEngine lightengine = this.worldServer.getLightEngine(); + DataLayer skyLightArray = lightengine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(this.x, chunk.getSectionYFromSectionIndex(i), this.z)); // SPIGOT-7498: Convert section index +@@ -362,8 +366,7 @@ public class CraftChunk implements Chunk { + } + + if (biome != null) { +- data.put("biomes", biomeCodec.encodeStart(NbtOps.INSTANCE, cs[i].getBiomes()).get().left().get()); +- biome[i] = biomeCodec.parse(NbtOps.INSTANCE, data.getCompound("biomes")).get().left().get(); ++ biome[i] = ((PalettedContainer>) cs[i].getBiomes()).copy(); // Paper - Perf: use copy instead of round tripping with codecs + } + } + diff --git a/patches/server/0676-Add-more-Campfire-API.patch b/patches/server/0674-Add-more-Campfire-API.patch similarity index 100% rename from patches/server/0676-Add-more-Campfire-API.patch rename to patches/server/0674-Add-more-Campfire-API.patch diff --git a/patches/server/0674-Allow-delegation-to-vanilla-chunk-gen.patch b/patches/server/0674-Allow-delegation-to-vanilla-chunk-gen.patch deleted file mode 100644 index 59db41b993ef..000000000000 --- a/patches/server/0674-Allow-delegation-to-vanilla-chunk-gen.patch +++ /dev/null @@ -1,127 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MiniDigger -Date: Wed, 29 Apr 2020 02:10:32 +0200 -Subject: [PATCH] Allow delegation to vanilla chunk gen - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 20b2fd911c2d8bc530c533e883f334b5b329fa9b..7a5fd6fb4ddcf1ba555725f1bb956333a0450d7c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2454,6 +2454,88 @@ public final class CraftServer implements Server { - return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(Registries.BIOME)); - } - -+ // Paper start - Allow delegation to vanilla chunk gen -+ private static final List VANILLA_GEN_STATUSES = List.of( -+ net.minecraft.world.level.chunk.ChunkStatus.EMPTY, -+ net.minecraft.world.level.chunk.ChunkStatus.STRUCTURE_STARTS, -+ net.minecraft.world.level.chunk.ChunkStatus.STRUCTURE_REFERENCES, -+ net.minecraft.world.level.chunk.ChunkStatus.BIOMES, -+ net.minecraft.world.level.chunk.ChunkStatus.NOISE, -+ net.minecraft.world.level.chunk.ChunkStatus.SURFACE, -+ net.minecraft.world.level.chunk.ChunkStatus.CARVERS, -+ net.minecraft.world.level.chunk.ChunkStatus.FEATURES, -+ net.minecraft.world.level.chunk.ChunkStatus.INITIALIZE_LIGHT, -+ net.minecraft.world.level.chunk.ChunkStatus.LIGHT -+ ); -+ -+ @Override -+ @Deprecated(forRemoval = true) -+ public ChunkGenerator.ChunkData createVanillaChunkData(World world, int x, int z) { -+ // do bunch of vanilla shit -+ final net.minecraft.server.level.ServerLevel serverLevel = ((CraftWorld) world).getHandle(); -+ final net.minecraft.core.Registry biomeRegistry = serverLevel.getServer().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME); -+ final net.minecraft.world.level.chunk.ProtoChunk protoChunk = new net.minecraft.world.level.chunk.ProtoChunk( -+ new net.minecraft.world.level.ChunkPos(x, z), -+ net.minecraft.world.level.chunk.UpgradeData.EMPTY, -+ serverLevel, -+ biomeRegistry, -+ null -+ ); -+ -+ final net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator; -+ if (serverLevel.chunkSource.getGenerator() instanceof org.bukkit.craftbukkit.generator.CustomChunkGenerator bukkit) { -+ chunkGenerator = bukkit.getDelegate(); -+ } else { -+ chunkGenerator = serverLevel.chunkSource.getGenerator(); -+ } -+ -+ final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(x, z); -+ final net.minecraft.util.thread.ProcessorMailbox mailbox = net.minecraft.util.thread.ProcessorMailbox.create( -+ net.minecraft.Util.backgroundExecutor(), -+ "CraftServer#createVanillaChunkData(worldName='" + world.getName() + "', x='" + x + "', z='" + z + "')" -+ ); -+ for (final net.minecraft.world.level.chunk.ChunkStatus chunkStatus : VANILLA_GEN_STATUSES) { -+ final List chunks = Lists.newArrayList(); -+ final int statusRange = Math.max(1, chunkStatus.getRange()); -+ -+ for (int zz = chunkPos.z - statusRange; zz <= chunkPos.z + statusRange; ++zz) { -+ for (int xx = chunkPos.x - statusRange; xx <= chunkPos.x + statusRange; ++xx) { -+ if (xx == chunkPos.x && zz == chunkPos.z) { -+ chunks.add(protoChunk); -+ } else { -+ final net.minecraft.core.Holder biomeHolder = serverLevel.registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME).getHolderOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS); -+ final net.minecraft.world.level.chunk.ChunkAccess chunk = new net.minecraft.world.level.chunk.EmptyLevelChunk(serverLevel, new net.minecraft.world.level.ChunkPos(xx, zz), biomeHolder); -+ chunks.add(chunk); -+ } -+ } -+ } -+ -+ chunkStatus.generate( -+ mailbox::tell, -+ serverLevel, -+ chunkGenerator, -+ serverLevel.getStructureManager(), -+ serverLevel.chunkSource.getLightEngine(), -+ chunk -> { -+ throw new UnsupportedOperationException("Not creating full chunks here"); -+ }, -+ chunks -+ ).thenAccept(either -> { -+ if (chunkStatus == net.minecraft.world.level.chunk.ChunkStatus.NOISE) { -+ either.left().ifPresent(chunk -> net.minecraft.world.level.levelgen.Heightmap.primeHeightmaps(chunk, net.minecraft.world.level.chunk.ChunkStatus.POST_FEATURES)); -+ } -+ }).join(); -+ } -+ -+ // get empty object -+ OldCraftChunkData data = (OldCraftChunkData) this.createChunkData(world); -+ // copy over generated sections -+ data.setRawChunkData(protoChunk.getSections()); -+ // hooray! -+ return data; -+ } -+ // Paper end - Allow delegation to vanilla chunk gen -+ - @Override - public BossBar createBossBar(String title, BarColor color, BarStyle style, BarFlag... flags) { - return new CraftBossBar(title, color, style, flags); -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java -index e7f7a246e9c03e676dadfee59de87b8b2ac55ba3..9b640705f2c810160aa7fea5006429ec41d0c858 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java -@@ -23,7 +23,7 @@ import org.bukkit.material.MaterialData; - public final class OldCraftChunkData implements ChunkGenerator.ChunkData { - private final int minHeight; - private final int maxHeight; -- private final LevelChunkSection[] sections; -+ private LevelChunkSection[] sections; // Paper - Allow delegation to vanilla chunk gen - private final Registry biomes; - private Set tiles; - private final Set lights = new HashSet<>(); -@@ -189,7 +189,13 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData { - return this.tiles; - } - -- Set getLights() { -+ public Set getLights() { // Paper - Allow delegation to vanilla chunk gen - return this.lights; - } -+ -+ // Paper start - Allow delegation to vanilla chunk gen -+ public void setRawChunkData(LevelChunkSection[] sections) { -+ this.sections = sections; -+ } -+ // Paper end - Allow delegation to vanilla chunk gen - } diff --git a/patches/server/0675-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch b/patches/server/0675-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch deleted file mode 100644 index 6a39da753c5d..000000000000 --- a/patches/server/0675-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Thu, 9 Dec 2021 00:08:11 -0800 -Subject: [PATCH] Fix ChunkSnapshot#isSectionEmpty(int) and optimize - PalettedContainer copying by not using codecs - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index e38643853220cabeac6c30912a9ae4bc1c018d5f..1eff5e4800ad3b628a42113fb3ba67458e56a40d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -335,13 +335,17 @@ public class CraftChunk implements Chunk { - PalettedContainerRO>[] biome = (includeBiome || includeBiomeTempRain) ? new PalettedContainer[cs.length] : null; - - Registry iregistry = this.worldServer.registryAccess().registryOrThrow(Registries.BIOME); -- Codec>> biomeCodec = PalettedContainer.codecRO(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS)); - - for (int i = 0; i < cs.length; i++) { -- CompoundTag data = new CompoundTag(); - -- data.put("block_states", ChunkSerializer.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, cs[i].getStates()).get().left().get()); -- sectionBlockIDs[i] = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, data.getCompound("block_states")).get().left().get(); -+ // Paper start - Fix ChunkSnapshot#isSectionEmpty(int); and remove codec usage -+ sectionEmpty[i] = cs[i].hasOnlyAir(); // fix sectionEmpty array not being filled -+ if (!sectionEmpty[i]) { -+ sectionBlockIDs[i] = cs[i].getStates().copy(); // use copy instead of round tripping with codecs -+ } else { -+ sectionBlockIDs[i] = CraftChunk.emptyBlockIDs; // use cached instance for empty block sections -+ } -+ // Paper end - Fix ChunkSnapshot#isSectionEmpty(int) - - LevelLightEngine lightengine = this.worldServer.getLightEngine(); - DataLayer skyLightArray = lightengine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(this.x, chunk.getSectionYFromSectionIndex(i), this.z)); // SPIGOT-7498: Convert section index -@@ -360,8 +364,7 @@ public class CraftChunk implements Chunk { - } - - if (biome != null) { -- data.put("biomes", biomeCodec.encodeStart(NbtOps.INSTANCE, cs[i].getBiomes()).get().left().get()); -- biome[i] = biomeCodec.parse(NbtOps.INSTANCE, data.getCompound("biomes")).get().left().get(); -+ biome[i] = ((PalettedContainer>) cs[i].getBiomes()).copy(); // Paper - Perf: use copy instead of round tripping with codecs - } - } - diff --git a/patches/server/0677-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch b/patches/server/0675-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch similarity index 100% rename from patches/server/0677-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch rename to patches/server/0675-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch diff --git a/patches/server/0676-Forward-CraftEntity-in-teleport-command.patch b/patches/server/0676-Forward-CraftEntity-in-teleport-command.patch new file mode 100644 index 000000000000..2319e020df70 --- /dev/null +++ b/patches/server/0676-Forward-CraftEntity-in-teleport-command.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 4 Dec 2021 17:04:47 -0800 +Subject: [PATCH] Forward CraftEntity in teleport command + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 885cef0698c7578bfd8bf7698c69a8e0006b0f8b..e4b9431f15de834b8616f4797fa1748bbd65b233 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3247,6 +3247,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + public void restoreFrom(Entity original) { ++ // Paper start - Forward CraftEntity in teleport command ++ CraftEntity bukkitEntity = original.bukkitEntity; ++ if (bukkitEntity != null) { ++ bukkitEntity.setHandle(this); ++ this.bukkitEntity = bukkitEntity; ++ } ++ // Paper end - Forward CraftEntity in teleport command + CompoundTag nbttagcompound = original.saveWithoutId(new CompoundTag()); + + nbttagcompound.remove("Dimension"); +@@ -3337,10 +3344,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + } + // CraftBukkit end +- // CraftBukkit start - Forward the CraftEntity to the new entity +- this.getBukkitEntity().setHandle(entity); +- entity.bukkitEntity = this.getBukkitEntity(); +- // CraftBukkit end ++ // // CraftBukkit start - Forward the CraftEntity to the new entity // Paper - Forward CraftEntity in teleport command; moved to Entity#restoreFrom ++ // this.getBukkitEntity().setHandle(entity); ++ // entity.bukkitEntity = this.getBukkitEntity(); ++ // // CraftBukkit end + } + + this.removeAfterChangingDimensions(); diff --git a/patches/server/0679-Improve-scoreboard-entries.patch b/patches/server/0677-Improve-scoreboard-entries.patch similarity index 100% rename from patches/server/0679-Improve-scoreboard-entries.patch rename to patches/server/0677-Improve-scoreboard-entries.patch diff --git a/patches/server/0680-Entity-powdered-snow-API.patch b/patches/server/0678-Entity-powdered-snow-API.patch similarity index 100% rename from patches/server/0680-Entity-powdered-snow-API.patch rename to patches/server/0678-Entity-powdered-snow-API.patch diff --git a/patches/server/0678-Forward-CraftEntity-in-teleport-command.patch b/patches/server/0678-Forward-CraftEntity-in-teleport-command.patch deleted file mode 100644 index bc1ee77e6649..000000000000 --- a/patches/server/0678-Forward-CraftEntity-in-teleport-command.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 4 Dec 2021 17:04:47 -0800 -Subject: [PATCH] Forward CraftEntity in teleport command - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 021d3803ffbf4fb0a6de92c13406c5316e09c4c1..f946e0194197febaf8a13ac1f26999ec70456e0b 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3244,6 +3244,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - public void restoreFrom(Entity original) { -+ // Paper start - Forward CraftEntity in teleport command -+ CraftEntity bukkitEntity = original.bukkitEntity; -+ if (bukkitEntity != null) { -+ bukkitEntity.setHandle(this); -+ this.bukkitEntity = bukkitEntity; -+ } -+ // Paper end - Forward CraftEntity in teleport command - CompoundTag nbttagcompound = original.saveWithoutId(new CompoundTag()); - - nbttagcompound.remove("Dimension"); -@@ -3334,10 +3341,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - } - // CraftBukkit end -- // CraftBukkit start - Forward the CraftEntity to the new entity -- this.getBukkitEntity().setHandle(entity); -- entity.bukkitEntity = this.getBukkitEntity(); -- // CraftBukkit end -+ // // CraftBukkit start - Forward the CraftEntity to the new entity // Paper - Forward CraftEntity in teleport command; moved to Entity#restoreFrom -+ // this.getBukkitEntity().setHandle(entity); -+ // entity.bukkitEntity = this.getBukkitEntity(); -+ // // CraftBukkit end - } - - this.removeAfterChangingDimensions(); diff --git a/patches/server/0681-Add-API-for-item-entity-health.patch b/patches/server/0679-Add-API-for-item-entity-health.patch similarity index 100% rename from patches/server/0681-Add-API-for-item-entity-health.patch rename to patches/server/0679-Add-API-for-item-entity-health.patch diff --git a/patches/server/0682-Configurable-max-block-light-for-monster-spawning.patch b/patches/server/0680-Configurable-max-block-light-for-monster-spawning.patch similarity index 100% rename from patches/server/0682-Configurable-max-block-light-for-monster-spawning.patch rename to patches/server/0680-Configurable-max-block-light-for-monster-spawning.patch diff --git a/patches/server/0683-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch b/patches/server/0681-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch similarity index 100% rename from patches/server/0683-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch rename to patches/server/0681-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch diff --git a/patches/server/0684-Load-effect-amplifiers-greater-than-127-correctly.patch b/patches/server/0682-Load-effect-amplifiers-greater-than-127-correctly.patch similarity index 100% rename from patches/server/0684-Load-effect-amplifiers-greater-than-127-correctly.patch rename to patches/server/0682-Load-effect-amplifiers-greater-than-127-correctly.patch diff --git a/patches/server/0685-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch b/patches/server/0683-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch similarity index 100% rename from patches/server/0685-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch rename to patches/server/0683-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch diff --git a/patches/server/0686-Fix-bees-aging-inside-hives.patch b/patches/server/0684-Fix-bees-aging-inside-hives.patch similarity index 100% rename from patches/server/0686-Fix-bees-aging-inside-hives.patch rename to patches/server/0684-Fix-bees-aging-inside-hives.patch diff --git a/patches/server/0687-Bucketable-API.patch b/patches/server/0685-Bucketable-API.patch similarity index 100% rename from patches/server/0687-Bucketable-API.patch rename to patches/server/0685-Bucketable-API.patch diff --git a/patches/server/0686-Validate-usernames.patch b/patches/server/0686-Validate-usernames.patch new file mode 100644 index 000000000000..4ed9d0f86f19 --- /dev/null +++ b/patches/server/0686-Validate-usernames.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 1 Jan 2022 05:19:37 -0800 +Subject: [PATCH] Validate usernames + + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index a7da99ac31bbcb8b6f1814a2d5509c7067aafb08..fb582acfe9e5cb68314ee39e1d54a550d6700e76 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -63,6 +63,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + public GameProfile authenticatedProfile; // Paper - public + private final String serverId; + private ServerPlayer player; // CraftBukkit ++ public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding + + public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection) { + this.state = ServerLoginPacketListenerImpl.State.HELLO; +@@ -137,7 +138,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + @Override + public void handleHello(ServerboundHelloPacket packet) { + Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]); +- Validate.validState(Player.isValidUsername(packet.name()), "Invalid characters in username", 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 + this.requestedUsername = packet.name(); + GameProfile gameprofile = this.server.getSingleplayerProfile(); + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 75e3d7f81e21caaffd79d095022c4196507a9059..584b1627cf18e6aee9338ade0f94d983e2dda412 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -677,7 +677,7 @@ public abstract class PlayerList { + + for (int i = 0; i < this.players.size(); ++i) { + entityplayer = (ServerPlayer) this.players.get(i); +- if (entityplayer.getUUID().equals(uuid)) { ++ if (entityplayer.getUUID().equals(uuid) || (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && entityplayer.getGameProfile().getName().equalsIgnoreCase(gameprofile.getName()))) { // Paper - validate usernames + list.add(entityplayer); + } + } +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 1a2083e5887dc8ba0ad908cc961dd090cbc165f5..8ca4f45f1db35a08a335142aa888e98e63cec348 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -2333,9 +2333,23 @@ public abstract class Player extends LivingEntity { + } + + public static boolean isValidUsername(String name) { +- return name.length() > 16 ? false : name.chars().filter((i) -> { +- return i <= 32 || i >= 127; +- }).findAny().isEmpty(); ++ // Paper start - username validation overriding ++ if (name == null || name.isEmpty() || name.length() > 16) { ++ return false; ++ } ++ ++ for (int i = 0, len = name.length(); i < len; ++i) { ++ char c = name.charAt(i); ++ ++ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_' || c == '.')) { ++ continue; ++ } ++ ++ return false; ++ } ++ ++ return true; ++ // Paper end - username validation overriding + } + + public static float getPickRange(boolean creative) { diff --git a/patches/server/0689-Make-water-animal-spawn-height-configurable.patch b/patches/server/0687-Make-water-animal-spawn-height-configurable.patch similarity index 100% rename from patches/server/0689-Make-water-animal-spawn-height-configurable.patch rename to patches/server/0687-Make-water-animal-spawn-height-configurable.patch diff --git a/patches/server/0688-Expose-vanilla-BiomeProvider-from-WorldInfo.patch b/patches/server/0688-Expose-vanilla-BiomeProvider-from-WorldInfo.patch new file mode 100644 index 000000000000..2d40d8b66883 --- /dev/null +++ b/patches/server/0688-Expose-vanilla-BiomeProvider-from-WorldInfo.patch @@ -0,0 +1,137 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Thu, 6 Jan 2022 15:59:06 -0800 +Subject: [PATCH] Expose vanilla BiomeProvider from WorldInfo + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 7ee46b9f98794d1fec0a8feea71fd495f9199dd0..d19f2ca702ffa32c616ec821219079e2b784a460 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -597,7 +597,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver)); + LevelStem worlddimension = (LevelStem) dimensions.get(dimensionKey); + +- org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value()); ++ org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value(), worlddimension.generator(), this.registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo + if (biomeProvider == null && gen != null) { + biomeProvider = gen.getDefaultBiomeProvider(worldInfo); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index c366992f3e48f9a3a69ba48491d678ba73e81d61..b3e1f987d31ef5a8f90bc366e248de86e93b9634 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1272,7 +1272,7 @@ public final class CraftServer implements Server { + List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); + LevelStem worlddimension = iregistry.get(actualDimension); + +- WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value()); ++ WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value(), worlddimension.generator(), this.getHandle().getServer().registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo + if (biomeProvider == null && generator != null) { + biomeProvider = generator.getDefaultBiomeProvider(worldInfo); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index df74b47477553bdee0b5faaf40835b94c41316dd..c787fbcef295f2108a6f843524c00cdea4660f27 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -211,6 +211,29 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public int getPlayerCount() { + return world.players().size(); + } ++ ++ @Override ++ public BiomeProvider vanillaBiomeProvider() { ++ net.minecraft.server.level.ServerChunkCache serverCache = this.getHandle().chunkSource; ++ ++ final net.minecraft.world.level.biome.BiomeSource biomeSource = serverCache.getGenerator().getBiomeSource(); ++ final net.minecraft.world.level.biome.Climate.Sampler sampler = serverCache.randomState().sampler(); ++ ++ final List possibleBiomes = biomeSource.possibleBiomes().stream() ++ .map(biome -> org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit(biome)) ++ .toList(); ++ return new BiomeProvider() { ++ @Override ++ public Biome getBiome(final org.bukkit.generator.WorldInfo worldInfo, final int x, final int y, final int z) { ++ return org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit(biomeSource.getNoiseBiome(x >> 2, y >> 2, z >> 2, sampler)); ++ } ++ ++ @Override ++ public List getBiomes(final org.bukkit.generator.WorldInfo worldInfo) { ++ return possibleBiomes; ++ } ++ }; ++ } + // Paper end + + private static final Random rand = new Random(); +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java +index 5d655d6cd3e23e0287069f8bdf77601487e862fd..cf57c6e9ce63f7b1d95d91ead2453409a31a5c52 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java +@@ -17,8 +17,14 @@ public class CraftWorldInfo implements WorldInfo { + private final long seed; + private final int minHeight; + private final int maxHeight; ++ // Paper start ++ private final net.minecraft.world.level.chunk.ChunkGenerator vanillaChunkGenerator; ++ private final net.minecraft.core.RegistryAccess.Frozen registryAccess; + +- public CraftWorldInfo(ServerLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager) { ++ public CraftWorldInfo(ServerLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager, net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator, net.minecraft.core.RegistryAccess.Frozen registryAccess) { ++ this.registryAccess = registryAccess; ++ this.vanillaChunkGenerator = chunkGenerator; ++ // Paper end + this.name = worldDataServer.getLevelName(); + this.uuid = WorldUUID.getUUID(session.levelDirectory.path().toFile()); + this.environment = environment; +@@ -27,15 +33,6 @@ public class CraftWorldInfo implements WorldInfo { + this.maxHeight = dimensionManager.minY() + dimensionManager.height(); + } + +- public CraftWorldInfo(String name, UUID uuid, World.Environment environment, long seed, int minHeight, int maxHeight) { +- this.name = name; +- this.uuid = uuid; +- this.environment = environment; +- this.seed = seed; +- this.minHeight = minHeight; +- this.maxHeight = maxHeight; +- } +- + @Override + public String getName() { + return this.name; +@@ -65,4 +62,34 @@ public class CraftWorldInfo implements WorldInfo { + public int getMaxHeight() { + return this.maxHeight; + } ++ ++ // Paper start ++ @Override ++ public org.bukkit.generator.BiomeProvider vanillaBiomeProvider() { ++ final net.minecraft.world.level.levelgen.RandomState randomState; ++ if (vanillaChunkGenerator instanceof net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator noiseBasedChunkGenerator) { ++ randomState = net.minecraft.world.level.levelgen.RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(), ++ registryAccess.lookupOrThrow(net.minecraft.core.registries.Registries.NOISE), getSeed()); ++ } else { ++ randomState = net.minecraft.world.level.levelgen.RandomState.create(net.minecraft.world.level.levelgen.NoiseGeneratorSettings.dummy(), ++ registryAccess.lookupOrThrow(net.minecraft.core.registries.Registries.NOISE), getSeed()); ++ } ++ ++ final java.util.List possibleBiomes = CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().possibleBiomes().stream() ++ .map(biome -> org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit(biome)) ++ .toList(); ++ return new org.bukkit.generator.BiomeProvider() { ++ @Override ++ public org.bukkit.block.Biome getBiome(final WorldInfo worldInfo, final int x, final int y, final int z) { ++ return org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit( ++ CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().getNoiseBiome(x >> 2, y >> 2, z >> 2, randomState.sampler())); ++ } ++ ++ @Override ++ public java.util.List getBiomes(final org.bukkit.generator.WorldInfo worldInfo) { ++ return possibleBiomes; ++ } ++ }; ++ } ++ // Paper end + } diff --git a/patches/server/0688-Validate-usernames.patch b/patches/server/0688-Validate-usernames.patch deleted file mode 100644 index 0b5be768a286..000000000000 --- a/patches/server/0688-Validate-usernames.patch +++ /dev/null @@ -1,71 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 1 Jan 2022 05:19:37 -0800 -Subject: [PATCH] Validate usernames - - -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index a7da99ac31bbcb8b6f1814a2d5509c7067aafb08..fb582acfe9e5cb68314ee39e1d54a550d6700e76 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -63,6 +63,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - public GameProfile authenticatedProfile; // Paper - public - private final String serverId; - private ServerPlayer player; // CraftBukkit -+ public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding - - public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection) { - this.state = ServerLoginPacketListenerImpl.State.HELLO; -@@ -137,7 +138,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - @Override - public void handleHello(ServerboundHelloPacket packet) { - Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]); -- Validate.validState(Player.isValidUsername(packet.name()), "Invalid characters in username", 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 - this.requestedUsername = packet.name(); - GameProfile gameprofile = this.server.getSingleplayerProfile(); - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 75e3d7f81e21caaffd79d095022c4196507a9059..584b1627cf18e6aee9338ade0f94d983e2dda412 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -677,7 +677,7 @@ public abstract class PlayerList { - - for (int i = 0; i < this.players.size(); ++i) { - entityplayer = (ServerPlayer) this.players.get(i); -- if (entityplayer.getUUID().equals(uuid)) { -+ if (entityplayer.getUUID().equals(uuid) || (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && entityplayer.getGameProfile().getName().equalsIgnoreCase(gameprofile.getName()))) { // Paper - validate usernames - list.add(entityplayer); - } - } -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 865b5a43d108e87dc93d1d678371efaacb65507a..addbfbf1cae0a9c38ee1daabdb74e9af324f3a94 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -2332,9 +2332,23 @@ public abstract class Player extends LivingEntity { - } - - public static boolean isValidUsername(String name) { -- return name.length() > 16 ? false : name.chars().filter((i) -> { -- return i <= 32 || i >= 127; -- }).findAny().isEmpty(); -+ // Paper start - username validation overriding -+ if (name == null || name.isEmpty() || name.length() > 16) { -+ return false; -+ } -+ -+ for (int i = 0, len = name.length(); i < len; ++i) { -+ char c = name.charAt(i); -+ -+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_' || c == '.')) { -+ continue; -+ } -+ -+ return false; -+ } -+ -+ return true; -+ // Paper end - username validation overriding - } - - public static float getPickRange(boolean creative) { diff --git a/patches/server/0691-Add-config-option-for-worlds-affected-by-time-cmd.patch b/patches/server/0689-Add-config-option-for-worlds-affected-by-time-cmd.patch similarity index 100% rename from patches/server/0691-Add-config-option-for-worlds-affected-by-time-cmd.patch rename to patches/server/0689-Add-config-option-for-worlds-affected-by-time-cmd.patch diff --git a/patches/server/0692-Add-missing-IAE-check-for-PersistentDataContainer-ha.patch b/patches/server/0690-Add-missing-IAE-check-for-PersistentDataContainer-ha.patch similarity index 100% rename from patches/server/0692-Add-missing-IAE-check-for-PersistentDataContainer-ha.patch rename to patches/server/0690-Add-missing-IAE-check-for-PersistentDataContainer-ha.patch diff --git a/patches/server/0690-Expose-vanilla-BiomeProvider-from-WorldInfo.patch b/patches/server/0690-Expose-vanilla-BiomeProvider-from-WorldInfo.patch deleted file mode 100644 index c7af2ee4215b..000000000000 --- a/patches/server/0690-Expose-vanilla-BiomeProvider-from-WorldInfo.patch +++ /dev/null @@ -1,137 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Thu, 6 Jan 2022 15:59:06 -0800 -Subject: [PATCH] Expose vanilla BiomeProvider from WorldInfo - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 2535f33e1ee5f1bac3247231138966fe070615cd..1df847164796c0992e677e0db45a9be223c8dc4a 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -597,7 +597,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver)); - LevelStem worlddimension = (LevelStem) dimensions.get(dimensionKey); - -- org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value()); -+ org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value(), worlddimension.generator(), this.registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo - if (biomeProvider == null && gen != null) { - biomeProvider = gen.getDefaultBiomeProvider(worldInfo); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index d8f4a433d5072e849eb883eb8e54831325c4c5ef..dd62f8ccca6a6c08876af1595bc274c9151055b8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1260,7 +1260,7 @@ public final class CraftServer implements Server { - List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); - LevelStem worlddimension = iregistry.get(actualDimension); - -- WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value()); -+ WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value(), worlddimension.generator(), this.getHandle().getServer().registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo - if (biomeProvider == null && generator != null) { - biomeProvider = generator.getDefaultBiomeProvider(worldInfo); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index b3e17c14152204e9ccbe70ee8dfab4b20583d2d6..391f546e44080293fd94ff240d7809e3f471bb14 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -205,6 +205,29 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public int getPlayerCount() { - return world.players().size(); - } -+ -+ @Override -+ public BiomeProvider vanillaBiomeProvider() { -+ net.minecraft.server.level.ServerChunkCache serverCache = this.getHandle().chunkSource; -+ -+ final net.minecraft.world.level.biome.BiomeSource biomeSource = serverCache.getGenerator().getBiomeSource(); -+ final net.minecraft.world.level.biome.Climate.Sampler sampler = serverCache.randomState().sampler(); -+ -+ final List possibleBiomes = biomeSource.possibleBiomes().stream() -+ .map(biome -> org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit(biome)) -+ .toList(); -+ return new BiomeProvider() { -+ @Override -+ public Biome getBiome(final org.bukkit.generator.WorldInfo worldInfo, final int x, final int y, final int z) { -+ return org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit(biomeSource.getNoiseBiome(x >> 2, y >> 2, z >> 2, sampler)); -+ } -+ -+ @Override -+ public List getBiomes(final org.bukkit.generator.WorldInfo worldInfo) { -+ return possibleBiomes; -+ } -+ }; -+ } - // Paper end - - private static final Random rand = new Random(); -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java -index 5d655d6cd3e23e0287069f8bdf77601487e862fd..cf57c6e9ce63f7b1d95d91ead2453409a31a5c52 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java -@@ -17,8 +17,14 @@ public class CraftWorldInfo implements WorldInfo { - private final long seed; - private final int minHeight; - private final int maxHeight; -+ // Paper start -+ private final net.minecraft.world.level.chunk.ChunkGenerator vanillaChunkGenerator; -+ private final net.minecraft.core.RegistryAccess.Frozen registryAccess; - -- public CraftWorldInfo(ServerLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager) { -+ public CraftWorldInfo(ServerLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager, net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator, net.minecraft.core.RegistryAccess.Frozen registryAccess) { -+ this.registryAccess = registryAccess; -+ this.vanillaChunkGenerator = chunkGenerator; -+ // Paper end - this.name = worldDataServer.getLevelName(); - this.uuid = WorldUUID.getUUID(session.levelDirectory.path().toFile()); - this.environment = environment; -@@ -27,15 +33,6 @@ public class CraftWorldInfo implements WorldInfo { - this.maxHeight = dimensionManager.minY() + dimensionManager.height(); - } - -- public CraftWorldInfo(String name, UUID uuid, World.Environment environment, long seed, int minHeight, int maxHeight) { -- this.name = name; -- this.uuid = uuid; -- this.environment = environment; -- this.seed = seed; -- this.minHeight = minHeight; -- this.maxHeight = maxHeight; -- } -- - @Override - public String getName() { - return this.name; -@@ -65,4 +62,34 @@ public class CraftWorldInfo implements WorldInfo { - public int getMaxHeight() { - return this.maxHeight; - } -+ -+ // Paper start -+ @Override -+ public org.bukkit.generator.BiomeProvider vanillaBiomeProvider() { -+ final net.minecraft.world.level.levelgen.RandomState randomState; -+ if (vanillaChunkGenerator instanceof net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator noiseBasedChunkGenerator) { -+ randomState = net.minecraft.world.level.levelgen.RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(), -+ registryAccess.lookupOrThrow(net.minecraft.core.registries.Registries.NOISE), getSeed()); -+ } else { -+ randomState = net.minecraft.world.level.levelgen.RandomState.create(net.minecraft.world.level.levelgen.NoiseGeneratorSettings.dummy(), -+ registryAccess.lookupOrThrow(net.minecraft.core.registries.Registries.NOISE), getSeed()); -+ } -+ -+ final java.util.List possibleBiomes = CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().possibleBiomes().stream() -+ .map(biome -> org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit(biome)) -+ .toList(); -+ return new org.bukkit.generator.BiomeProvider() { -+ @Override -+ public org.bukkit.block.Biome getBiome(final WorldInfo worldInfo, final int x, final int y, final int z) { -+ return org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit( -+ CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().getNoiseBiome(x >> 2, y >> 2, z >> 2, randomState.sampler())); -+ } -+ -+ @Override -+ public java.util.List getBiomes(final org.bukkit.generator.WorldInfo worldInfo) { -+ return possibleBiomes; -+ } -+ }; -+ } -+ // Paper end - } diff --git a/patches/server/0693-Multiple-Entries-with-Scoreboards.patch b/patches/server/0691-Multiple-Entries-with-Scoreboards.patch similarity index 100% rename from patches/server/0693-Multiple-Entries-with-Scoreboards.patch rename to patches/server/0691-Multiple-Entries-with-Scoreboards.patch diff --git a/patches/server/0694-Reset-placed-block-on-exception.patch b/patches/server/0692-Reset-placed-block-on-exception.patch similarity index 100% rename from patches/server/0694-Reset-placed-block-on-exception.patch rename to patches/server/0692-Reset-placed-block-on-exception.patch diff --git a/patches/server/0695-Add-configurable-height-for-slime-spawn.patch b/patches/server/0693-Add-configurable-height-for-slime-spawn.patch similarity index 100% rename from patches/server/0695-Add-configurable-height-for-slime-spawn.patch rename to patches/server/0693-Add-configurable-height-for-slime-spawn.patch diff --git a/patches/server/0696-Fix-xp-reward-for-baby-zombies.patch b/patches/server/0694-Fix-xp-reward-for-baby-zombies.patch similarity index 100% rename from patches/server/0696-Fix-xp-reward-for-baby-zombies.patch rename to patches/server/0694-Fix-xp-reward-for-baby-zombies.patch diff --git a/patches/server/0695-Multi-Block-Change-API-Implementation.patch b/patches/server/0695-Multi-Block-Change-API-Implementation.patch new file mode 100644 index 000000000000..13e338d236c2 --- /dev/null +++ b/patches/server/0695-Multi-Block-Change-API-Implementation.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brody Beckwith +Date: Fri, 14 Jan 2022 00:41:11 -0500 +Subject: [PATCH] Multi Block Change API Implementation + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java +index f96d61bdeb556665d6e6e5023f9d77fd82204e89..ccdc2345465313991f065e1176b58fb7d5e8722f 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java +@@ -59,6 +59,14 @@ public class ClientboundSectionBlocksUpdatePacket implements Packet blockChanges) { ++ this.sectionPos = sectionPos; ++ this.positions = blockChanges.keySet().toShortArray(); ++ this.states = blockChanges.values().toArray(new BlockState[0]); ++ } ++ // Paper end - Multi Block Change API ++ + @Override + public void write(FriendlyByteBuf buf) { + buf.writeLong(this.sectionPos.asLong()); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 8b89f46aad11628a50d9f6c65caf52a558b9ee18..87b241a8b78b6fe0144e0d318e04da69a3101bc7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -850,6 +850,32 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.getHandle().connection.send(packet); + } + ++ // Paper start ++ @Override ++ public void sendMultiBlockChange(final Map blockChanges) { ++ if (this.getHandle().connection == null) return; ++ ++ Map> sectionMap = new HashMap<>(); ++ ++ for (Map.Entry entry : blockChanges.entrySet()) { ++ BlockData blockData = entry.getValue(); ++ BlockPos blockPos = io.papermc.paper.util.MCUtil.toBlockPos(entry.getKey()); ++ SectionPos sectionPos = SectionPos.of(blockPos); ++ ++ it.unimi.dsi.fastutil.shorts.Short2ObjectMap sectionData = sectionMap.computeIfAbsent(sectionPos, key -> new it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap<>()); ++ sectionData.put(SectionPos.sectionRelativePos(blockPos), ((CraftBlockData) blockData).getState()); ++ } ++ ++ for (Map.Entry> entry : sectionMap.entrySet()) { ++ SectionPos sectionPos = entry.getKey(); ++ it.unimi.dsi.fastutil.shorts.Short2ObjectMap blockData = entry.getValue(); ++ ++ net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket packet = new net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket(sectionPos, blockData); ++ this.getHandle().connection.send(packet); ++ } ++ } ++ // Paper end ++ + @Override + public void sendBlockChanges(Collection blocks) { + Preconditions.checkArgument(blocks != null, "blocks must not be null"); diff --git a/patches/server/0698-Fix-NotePlayEvent.patch b/patches/server/0696-Fix-NotePlayEvent.patch similarity index 100% rename from patches/server/0698-Fix-NotePlayEvent.patch rename to patches/server/0696-Fix-NotePlayEvent.patch diff --git a/patches/server/0697-Freeze-Tick-Lock-API.patch b/patches/server/0697-Freeze-Tick-Lock-API.patch new file mode 100644 index 000000000000..7baa8a0bcf74 --- /dev/null +++ b/patches/server/0697-Freeze-Tick-Lock-API.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 26 Dec 2021 20:27:43 -0500 +Subject: [PATCH] Freeze Tick Lock API + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index e4b9431f15de834b8616f4797fa1748bbd65b233..1a51c0ed22f892a233ce82fe53e72056685c9eba 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -404,6 +404,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + private org.bukkit.util.Vector origin; + @javax.annotation.Nullable + private UUID originWorld; ++ public boolean freezeLocked = false; // Paper - Freeze Tick Lock API + + public void setOrigin(@javax.annotation.Nonnull Location location) { + this.origin = location.toVector(); +@@ -751,7 +752,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + this.setRemainingFireTicks(this.remainingFireTicks - 1); + } + +- if (this.getTicksFrozen() > 0) { ++ if (this.getTicksFrozen() > 0 && !freezeLocked) { // Paper - Freeze Tick Lock API + this.setTicksFrozen(0); + this.level().levelEvent((Player) null, 1009, this.blockPosition, 1); + } +@@ -2230,6 +2231,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + if (fromNetherPortal) { + nbttagcompound.putBoolean("Paper.FromNetherPortal", true); + } ++ if (freezeLocked) { ++ nbttagcompound.putBoolean("Paper.FreezeLock", true); ++ } + // Paper end + return nbttagcompound; + } catch (Throwable throwable) { +@@ -2374,6 +2378,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + if (spawnReason == null) { + spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; + } ++ if (nbt.contains("Paper.FreezeLock")) { ++ freezeLocked = nbt.getBoolean("Paper.FreezeLock"); ++ } + // Paper end + + } catch (Throwable throwable) { +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 4e781939e7c980d095a4b0fdd11736ec48b4ecfa..5adfa199835f8ee6b4db16c1bf262bb43324e654 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3464,7 +3464,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + + this.level().getProfiler().pop(); + this.level().getProfiler().push("freezing"); +- if (!this.level().isClientSide && !this.isDeadOrDying()) { ++ if (!this.level().isClientSide && !this.isDeadOrDying() && !this.freezeLocked) { // Paper - Freeze Tick Lock API + int i = this.getTicksFrozen(); + + if (this.isInPowderSnow && this.canFreeze()) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 47be73dbb0dd1b6cef0113042be6a80cb6209252..b7df71633527dce2e4f954caee249e3b31b82226 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -320,6 +320,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return this.getHandle().isFullyFrozen(); + } + ++ // Paper start - Freeze Tick Lock API ++ @Override ++ public boolean isFreezeTickingLocked() { ++ return this.entity.freezeLocked; ++ } ++ ++ @Override ++ public void lockFreezeTicks(boolean locked) { ++ this.entity.freezeLocked = locked; ++ } ++ // Paper end - Freeze Tick Lock API + @Override + public void remove() { + this.entity.pluginRemoved = true; diff --git a/patches/server/0697-Multi-Block-Change-API-Implementation.patch b/patches/server/0697-Multi-Block-Change-API-Implementation.patch deleted file mode 100644 index be7d80087808..000000000000 --- a/patches/server/0697-Multi-Block-Change-API-Implementation.patch +++ /dev/null @@ -1,62 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Brody Beckwith -Date: Fri, 14 Jan 2022 00:41:11 -0500 -Subject: [PATCH] Multi Block Change API Implementation - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java -index f96d61bdeb556665d6e6e5023f9d77fd82204e89..ccdc2345465313991f065e1176b58fb7d5e8722f 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java -@@ -59,6 +59,14 @@ public class ClientboundSectionBlocksUpdatePacket implements Packet blockChanges) { -+ this.sectionPos = sectionPos; -+ this.positions = blockChanges.keySet().toShortArray(); -+ this.states = blockChanges.values().toArray(new BlockState[0]); -+ } -+ // Paper end - Multi Block Change API -+ - @Override - public void write(FriendlyByteBuf buf) { - buf.writeLong(this.sectionPos.asLong()); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 85dd644ca86895e91ba639ba4974a05290e4e061..1fe839581edc496826885937ab0e10ec0a056212 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -844,6 +844,32 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.getHandle().connection.send(packet); - } - -+ // Paper start -+ @Override -+ public void sendMultiBlockChange(final Map blockChanges) { -+ if (this.getHandle().connection == null) return; -+ -+ Map> sectionMap = new HashMap<>(); -+ -+ for (Map.Entry entry : blockChanges.entrySet()) { -+ BlockData blockData = entry.getValue(); -+ BlockPos blockPos = io.papermc.paper.util.MCUtil.toBlockPos(entry.getKey()); -+ SectionPos sectionPos = SectionPos.of(blockPos); -+ -+ it.unimi.dsi.fastutil.shorts.Short2ObjectMap sectionData = sectionMap.computeIfAbsent(sectionPos, key -> new it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap<>()); -+ sectionData.put(SectionPos.sectionRelativePos(blockPos), ((CraftBlockData) blockData).getState()); -+ } -+ -+ for (Map.Entry> entry : sectionMap.entrySet()) { -+ SectionPos sectionPos = entry.getKey(); -+ it.unimi.dsi.fastutil.shorts.Short2ObjectMap blockData = entry.getValue(); -+ -+ net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket packet = new net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket(sectionPos, blockData); -+ this.getHandle().connection.send(packet); -+ } -+ } -+ // Paper end -+ - @Override - public void sendBlockChanges(Collection blocks) { - Preconditions.checkArgument(blocks != null, "blocks must not be null"); diff --git a/patches/server/0700-More-PotionEffectType-API.patch b/patches/server/0698-More-PotionEffectType-API.patch similarity index 100% rename from patches/server/0700-More-PotionEffectType-API.patch rename to patches/server/0698-More-PotionEffectType-API.patch diff --git a/patches/server/0699-Freeze-Tick-Lock-API.patch b/patches/server/0699-Freeze-Tick-Lock-API.patch deleted file mode 100644 index c665c14b5162..000000000000 --- a/patches/server/0699-Freeze-Tick-Lock-API.patch +++ /dev/null @@ -1,82 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 26 Dec 2021 20:27:43 -0500 -Subject: [PATCH] Freeze Tick Lock API - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index f946e0194197febaf8a13ac1f26999ec70456e0b..b0c97b12d316463af8c581f5f15c5c5e567eb403 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -404,6 +404,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - private org.bukkit.util.Vector origin; - @javax.annotation.Nullable - private UUID originWorld; -+ public boolean freezeLocked = false; // Paper - Freeze Tick Lock API - - public void setOrigin(@javax.annotation.Nonnull Location location) { - this.origin = location.toVector(); -@@ -751,7 +752,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - this.setRemainingFireTicks(this.remainingFireTicks - 1); - } - -- if (this.getTicksFrozen() > 0) { -+ if (this.getTicksFrozen() > 0 && !freezeLocked) { // Paper - Freeze Tick Lock API - this.setTicksFrozen(0); - this.level().levelEvent((Player) null, 1009, this.blockPosition, 1); - } -@@ -2226,6 +2227,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - if (fromNetherPortal) { - nbttagcompound.putBoolean("Paper.FromNetherPortal", true); - } -+ if (freezeLocked) { -+ nbttagcompound.putBoolean("Paper.FreezeLock", true); -+ } - // Paper end - return nbttagcompound; - } catch (Throwable throwable) { -@@ -2370,6 +2374,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - if (spawnReason == null) { - spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; - } -+ if (nbt.contains("Paper.FreezeLock")) { -+ freezeLocked = nbt.getBoolean("Paper.FreezeLock"); -+ } - // Paper end - - } catch (Throwable throwable) { -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 45ad7703da01bc2bac56bb9447925a9b2bb9ea81..fbbddf856a0fbb0b88bdbf00fed79ebeb9f85a33 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3451,7 +3451,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - this.level().getProfiler().pop(); - this.level().getProfiler().push("freezing"); -- if (!this.level().isClientSide && !this.isDeadOrDying()) { -+ if (!this.level().isClientSide && !this.isDeadOrDying() && !this.freezeLocked) { // Paper - Freeze Tick Lock API - int i = this.getTicksFrozen(); - - if (this.isInPowderSnow && this.canFreeze()) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 47be73dbb0dd1b6cef0113042be6a80cb6209252..b7df71633527dce2e4f954caee249e3b31b82226 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -320,6 +320,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return this.getHandle().isFullyFrozen(); - } - -+ // Paper start - Freeze Tick Lock API -+ @Override -+ public boolean isFreezeTickingLocked() { -+ return this.entity.freezeLocked; -+ } -+ -+ @Override -+ public void lockFreezeTicks(boolean locked) { -+ this.entity.freezeLocked = locked; -+ } -+ // Paper end - Freeze Tick Lock API - @Override - public void remove() { - this.entity.pluginRemoved = true; diff --git a/patches/server/0701-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch b/patches/server/0699-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch similarity index 100% rename from patches/server/0701-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch rename to patches/server/0699-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch diff --git a/patches/server/0700-API-for-creating-command-sender-which-forwards-feedb.patch b/patches/server/0700-API-for-creating-command-sender-which-forwards-feedb.patch new file mode 100644 index 000000000000..cceecc25eaf0 --- /dev/null +++ b/patches/server/0700-API-for-creating-command-sender-which-forwards-feedb.patch @@ -0,0 +1,170 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Tue, 1 Feb 2022 15:51:55 -0700 +Subject: [PATCH] API for creating command sender which forwards feedback + + +diff --git a/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e3a5f1ec376319bdfda87fa27ae217bff3914292 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java +@@ -0,0 +1,111 @@ ++package io.papermc.paper.commands; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import java.util.UUID; ++import java.util.function.Consumer; ++import net.kyori.adventure.audience.MessageType; ++import net.kyori.adventure.identity.Identity; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; ++import net.minecraft.commands.CommandSource; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.phys.Vec2; ++import net.minecraft.world.phys.Vec3; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.command.ServerCommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class FeedbackForwardingSender extends ServerCommandSender { ++ private final Consumer feedback; ++ private final CraftServer server; ++ ++ public FeedbackForwardingSender(final Consumer feedback, final CraftServer server) { ++ super(((ServerCommandSender) server.getConsoleSender()).perm); ++ this.server = server; ++ this.feedback = feedback; ++ } ++ ++ @Override ++ public void sendMessage(final String message) { ++ this.sendMessage(LegacyComponentSerializer.legacySection().deserialize(message)); ++ } ++ ++ @Override ++ public void sendMessage(final String... messages) { ++ for (final String message : messages) { ++ this.sendMessage(message); ++ } ++ } ++ ++ @Override ++ public void sendMessage(final Identity identity, final Component message, final MessageType type) { ++ this.feedback.accept(message); ++ } ++ ++ @Override ++ public String getName() { ++ return "FeedbackForwardingSender"; ++ } ++ ++ @Override ++ public Component name() { ++ return Component.text(this.getName()); ++ } ++ ++ @Override ++ public boolean isOp() { ++ return true; ++ } ++ ++ @Override ++ public void setOp(final boolean value) { ++ throw new UnsupportedOperationException("Cannot change operator status of " + this.getClass().getName()); ++ } ++ ++ public CommandSourceStack asVanilla() { ++ final @Nullable ServerLevel overworld = this.server.getServer().overworld(); ++ return new CommandSourceStack( ++ new Source(this), ++ overworld == null ? Vec3.ZERO : Vec3.atLowerCornerOf(overworld.getSharedSpawnPos()), ++ Vec2.ZERO, ++ overworld, ++ 4, ++ this.getName(), ++ net.minecraft.network.chat.Component.literal(this.getName()), ++ this.server.getServer(), ++ null ++ ); ++ } ++ ++ private record Source(FeedbackForwardingSender sender) implements CommandSource { ++ @Override ++ public void sendSystemMessage(final net.minecraft.network.chat.Component message) { ++ this.sender.sendMessage(Identity.nil(), PaperAdventure.asAdventure(message)); ++ } ++ ++ @Override ++ public boolean acceptsSuccess() { ++ return true; ++ } ++ ++ @Override ++ public boolean acceptsFailure() { ++ return true; ++ } ++ ++ @Override ++ public boolean shouldInformAdmins() { ++ return false; ++ } ++ ++ @Override ++ public CommandSender getBukkitSender(final CommandSourceStack stack) { ++ return this.sender; ++ } ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index b3e1f987d31ef5a8f90bc366e248de86e93b9634..76b9f6c0c754be69c1369aa3c13f7ca1f56e9e13 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2117,6 +2117,13 @@ public final class CraftServer implements Server { + return this.console.console; + } + ++ // Paper start ++ @Override ++ public CommandSender createCommandSender(final java.util.function.Consumer feedback) { ++ return new io.papermc.paper.commands.FeedbackForwardingSender(feedback, this); ++ } ++ // Paper end ++ + public EntityMetadataStore getEntityMetadata() { + return this.entityMetadata; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java +index 7f22950ae61436e91a59cd29a345809c42bbe739..1e3091687735b461d3b6a313ab8761127981d3e8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java +@@ -12,7 +12,7 @@ import org.bukkit.permissions.PermissionAttachmentInfo; + import org.bukkit.plugin.Plugin; + + public abstract class ServerCommandSender implements CommandSender { +- private final PermissibleBase perm; ++ public final PermissibleBase perm; // Paper + private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers + + protected ServerCommandSender() { +diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +index bda9a0b99184adce28bb7851612ed7f4e324826d..61115db85b81e627d11a0de21691a2ca69aafe2c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +@@ -81,6 +81,11 @@ public final class VanillaCommandWrapper extends BukkitCommand { + if (sender instanceof ProxiedCommandSender) { + return ((ProxiedNativeCommandSender) sender).getHandle(); + } ++ // Paper start ++ if (sender instanceof io.papermc.paper.commands.FeedbackForwardingSender feedback) { ++ return feedback.asVanilla(); ++ } ++ // Paper end + + throw new IllegalArgumentException("Cannot make " + sender + " a vanilla command listener"); + } diff --git a/patches/server/0703-Add-missing-structure-set-seed-configs.patch b/patches/server/0701-Add-missing-structure-set-seed-configs.patch similarity index 100% rename from patches/server/0703-Add-missing-structure-set-seed-configs.patch rename to patches/server/0701-Add-missing-structure-set-seed-configs.patch diff --git a/patches/server/0702-API-for-creating-command-sender-which-forwards-feedb.patch b/patches/server/0702-API-for-creating-command-sender-which-forwards-feedb.patch deleted file mode 100644 index dddb5edd476a..000000000000 --- a/patches/server/0702-API-for-creating-command-sender-which-forwards-feedb.patch +++ /dev/null @@ -1,170 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Tue, 1 Feb 2022 15:51:55 -0700 -Subject: [PATCH] API for creating command sender which forwards feedback - - -diff --git a/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e3a5f1ec376319bdfda87fa27ae217bff3914292 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java -@@ -0,0 +1,111 @@ -+package io.papermc.paper.commands; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import java.util.UUID; -+import java.util.function.Consumer; -+import net.kyori.adventure.audience.MessageType; -+import net.kyori.adventure.identity.Identity; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -+import net.minecraft.commands.CommandSource; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.phys.Vec2; -+import net.minecraft.world.phys.Vec3; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftServer; -+import org.bukkit.craftbukkit.command.ServerCommandSender; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public final class FeedbackForwardingSender extends ServerCommandSender { -+ private final Consumer feedback; -+ private final CraftServer server; -+ -+ public FeedbackForwardingSender(final Consumer feedback, final CraftServer server) { -+ super(((ServerCommandSender) server.getConsoleSender()).perm); -+ this.server = server; -+ this.feedback = feedback; -+ } -+ -+ @Override -+ public void sendMessage(final String message) { -+ this.sendMessage(LegacyComponentSerializer.legacySection().deserialize(message)); -+ } -+ -+ @Override -+ public void sendMessage(final String... messages) { -+ for (final String message : messages) { -+ this.sendMessage(message); -+ } -+ } -+ -+ @Override -+ public void sendMessage(final Identity identity, final Component message, final MessageType type) { -+ this.feedback.accept(message); -+ } -+ -+ @Override -+ public String getName() { -+ return "FeedbackForwardingSender"; -+ } -+ -+ @Override -+ public Component name() { -+ return Component.text(this.getName()); -+ } -+ -+ @Override -+ public boolean isOp() { -+ return true; -+ } -+ -+ @Override -+ public void setOp(final boolean value) { -+ throw new UnsupportedOperationException("Cannot change operator status of " + this.getClass().getName()); -+ } -+ -+ public CommandSourceStack asVanilla() { -+ final @Nullable ServerLevel overworld = this.server.getServer().overworld(); -+ return new CommandSourceStack( -+ new Source(this), -+ overworld == null ? Vec3.ZERO : Vec3.atLowerCornerOf(overworld.getSharedSpawnPos()), -+ Vec2.ZERO, -+ overworld, -+ 4, -+ this.getName(), -+ net.minecraft.network.chat.Component.literal(this.getName()), -+ this.server.getServer(), -+ null -+ ); -+ } -+ -+ private record Source(FeedbackForwardingSender sender) implements CommandSource { -+ @Override -+ public void sendSystemMessage(final net.minecraft.network.chat.Component message) { -+ this.sender.sendMessage(Identity.nil(), PaperAdventure.asAdventure(message)); -+ } -+ -+ @Override -+ public boolean acceptsSuccess() { -+ return true; -+ } -+ -+ @Override -+ public boolean acceptsFailure() { -+ return true; -+ } -+ -+ @Override -+ public boolean shouldInformAdmins() { -+ return false; -+ } -+ -+ @Override -+ public CommandSender getBukkitSender(final CommandSourceStack stack) { -+ return this.sender; -+ } -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 5161f6d2fdd7cbf30b52089ecfda56b3d480ecda..e9d6f40de8ce89bc6541dcb3cd847b208ee67a85 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2105,6 +2105,13 @@ public final class CraftServer implements Server { - return this.console.console; - } - -+ // Paper start -+ @Override -+ public CommandSender createCommandSender(final java.util.function.Consumer feedback) { -+ return new io.papermc.paper.commands.FeedbackForwardingSender(feedback, this); -+ } -+ // Paper end -+ - public EntityMetadataStore getEntityMetadata() { - return this.entityMetadata; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java -index 7f22950ae61436e91a59cd29a345809c42bbe739..1e3091687735b461d3b6a313ab8761127981d3e8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java -@@ -12,7 +12,7 @@ import org.bukkit.permissions.PermissionAttachmentInfo; - import org.bukkit.plugin.Plugin; - - public abstract class ServerCommandSender implements CommandSender { -- private final PermissibleBase perm; -+ public final PermissibleBase perm; // Paper - private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers - - protected ServerCommandSender() { -diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -index bda9a0b99184adce28bb7851612ed7f4e324826d..61115db85b81e627d11a0de21691a2ca69aafe2c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -@@ -81,6 +81,11 @@ public final class VanillaCommandWrapper extends BukkitCommand { - if (sender instanceof ProxiedCommandSender) { - return ((ProxiedNativeCommandSender) sender).getHandle(); - } -+ // Paper start -+ if (sender instanceof io.papermc.paper.commands.FeedbackForwardingSender feedback) { -+ return feedback.asVanilla(); -+ } -+ // Paper end - - throw new IllegalArgumentException("Cannot make " + sender + " a vanilla command listener"); - } diff --git a/patches/server/0702-Implement-regenerateChunk.patch b/patches/server/0702-Implement-regenerateChunk.patch new file mode 100644 index 000000000000..b65be58c1ebc --- /dev/null +++ b/patches/server/0702-Implement-regenerateChunk.patch @@ -0,0 +1,102 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Mon, 31 Jan 2022 11:21:50 +0100 +Subject: [PATCH] Implement regenerateChunk + +Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index c787fbcef295f2108a6f843524c00cdea4660f27..7bb43181e4de4cf3a3944aea88c45e54e476433c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -150,6 +150,7 @@ import org.jetbrains.annotations.Nullable; + public class CraftWorld extends CraftRegionAccessor implements World { + public static final int CUSTOM_DIMENSION_OFFSET = 10; + private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); ++ private static final ChunkStatus[] REGEN_CHUNK_STATUSES = {ChunkStatus.BIOMES, ChunkStatus.NOISE, ChunkStatus.SURFACE, ChunkStatus.CARVERS, ChunkStatus.FEATURES, ChunkStatus.INITIALIZE_LIGHT}; // Paper - implement regenerate chunk method + + private final ServerLevel world; + private WorldBorder worldBorder; +@@ -420,27 +421,68 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public boolean regenerateChunk(int x, int z) { + org.spigotmc.AsyncCatcher.catchOp("chunk regenerate"); // Spigot +- throw new UnsupportedOperationException("Not supported in this Minecraft version! Unless you can fix it, this is not a bug :)"); +- /* +- if (!unloadChunk0(x, z, false)) { +- return false; ++ // Paper start - implement regenerateChunk method ++ final ServerLevel serverLevel = this.world; ++ final net.minecraft.server.level.ServerChunkCache serverChunkCache = serverLevel.getChunkSource(); ++ final ChunkPos chunkPos = new ChunkPos(x, z); ++ final net.minecraft.world.level.chunk.LevelChunk levelChunk = serverChunkCache.getChunk(chunkPos.x, chunkPos.z, true); ++ for (final BlockPos blockPos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), serverLevel.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ())) { ++ levelChunk.removeBlockEntity(blockPos); ++ serverLevel.setBlock(blockPos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(), 16); ++ } ++ ++ for (final ChunkStatus chunkStatus : REGEN_CHUNK_STATUSES) { ++ final List list = new ArrayList<>(); ++ final int range = Math.max(1, chunkStatus.getRange()); ++ for (int chunkX = chunkPos.z - range; chunkX <= chunkPos.z + range; chunkX++) { ++ for (int chunkZ = chunkPos.x - range; chunkZ <= chunkPos.x + range; chunkZ++) { ++ ChunkAccess chunkAccess = serverChunkCache.getChunk(chunkZ, chunkX, chunkStatus.getParent(), true); ++ if (chunkAccess instanceof ImposterProtoChunk accessProtoChunk) { ++ chunkAccess = new ImposterProtoChunk(accessProtoChunk.getWrapped(), true); ++ } else if (chunkAccess instanceof net.minecraft.world.level.chunk.LevelChunk accessLevelChunk) { ++ chunkAccess = new ImposterProtoChunk(accessLevelChunk, true); ++ } ++ list.add(chunkAccess); ++ } ++ } ++ ++ final java.util.concurrent.CompletableFuture> future = chunkStatus.generate( ++ Runnable::run, ++ serverLevel, ++ serverChunkCache.getGenerator(), ++ serverLevel.getStructureManager(), ++ serverChunkCache.getLightEngine(), ++ chunk -> { ++ throw new UnsupportedOperationException("Not creating full chunks here"); ++ }, ++ list ++ ); ++ serverChunkCache.mainThreadProcessor.managedBlock(future::isDone); ++ if (chunkStatus == ChunkStatus.NOISE) { ++ future.join().left().ifPresent(chunk -> net.minecraft.world.level.levelgen.Heightmap.primeHeightmaps(chunk, ChunkStatus.POST_FEATURES)); ++ } + } + +- final long chunkKey = ChunkCoordIntPair.pair(x, z); +- world.getChunkProvider().unloadQueue.remove(chunkKey); ++ for (final BlockPos blockPos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), serverLevel.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ())) { ++ serverChunkCache.blockChanged(blockPos); ++ } + +- net.minecraft.server.Chunk chunk = world.getChunkProvider().generateChunk(x, z); +- PlayerChunk playerChunk = world.getPlayerChunkMap().getChunk(x, z); +- if (playerChunk != null) { +- playerChunk.chunk = chunk; ++ final Set chunksToRelight = new HashSet<>(9); ++ for (int chunkX = chunkPos.x - 1; chunkX <= chunkPos.x + 1 ; chunkX++) { ++ for (int chunkZ = chunkPos.z - 1; chunkZ <= chunkPos.z + 1 ; chunkZ++) { ++ chunksToRelight.add(new ChunkPos(chunkX, chunkZ)); ++ } + } + +- if (chunk != null) { +- refreshChunk(x, z); ++ for (final ChunkPos pos : chunksToRelight) { ++ final ChunkAccess chunk = serverChunkCache.getChunk(pos.x, pos.z, false); ++ if (chunk != null) { ++ serverChunkCache.getLightEngine().lightChunk(chunk, false); ++ } + } + +- return chunk != null; +- */ ++ return true; ++ // Paper end - implement regenerate chunk method + } + + @Override diff --git a/patches/server/0703-Fix-cancelled-powdered-snow-bucket-placement.patch b/patches/server/0703-Fix-cancelled-powdered-snow-bucket-placement.patch new file mode 100644 index 000000000000..80c4ffe9f222 --- /dev/null +++ b/patches/server/0703-Fix-cancelled-powdered-snow-bucket-placement.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 8 Oct 2021 13:12:58 -0700 +Subject: [PATCH] Fix cancelled powdered snow bucket placement + +Cancelling the placement of powdered snow from the powdered +snow bucket didn't revert grass that became snowy because of the +placement. + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index d5ee83e6538fbd067388272fa9895e17859be642..a8f1953da3a5684079fb0cdb88cb3caf72d43646 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -380,7 +380,7 @@ public final class ItemStack { + int oldCount = this.getCount(); + ServerLevel world = (ServerLevel) context.getLevel(); + +- if (!(item instanceof BucketItem || item instanceof SolidBucketItem)) { // if not bucket ++ if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement + world.captureBlockStates = true; + // special case bonemeal + if (item == Items.BONE_MEAL) { +@@ -440,7 +440,7 @@ public final class ItemStack { + world.capturedBlockStates.clear(); + if (blocks.size() > 1) { + placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ()); +- } else if (blocks.size() == 1) { ++ } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement + placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, enumhand, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ()); + } + diff --git a/patches/server/0704-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch b/patches/server/0704-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch new file mode 100644 index 000000000000..971d368008c3 --- /dev/null +++ b/patches/server/0704-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Sat, 12 Feb 2022 12:40:50 -0700 +Subject: [PATCH] Add missing Validate calls to CraftServer#getSpawnLimit + +Copies appropriate checks from CraftWorld#getSpawnLimit + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 76b9f6c0c754be69c1369aa3c13f7ca1f56e9e13..a64109dfa8f33812c243ad50c673a5ef043559c0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2294,6 +2294,8 @@ public final class CraftServer implements Server { + @Override + public int getSpawnLimit(SpawnCategory spawnCategory) { + // Paper start - Add mobcaps commands ++ Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); ++ Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory." + spawnCategory + " does not have a spawn limit."); + return this.getSpawnLimitUnsafe(spawnCategory); + } + public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { diff --git a/patches/server/0704-Implement-regenerateChunk.patch b/patches/server/0704-Implement-regenerateChunk.patch deleted file mode 100644 index 20bcc8001bdd..000000000000 --- a/patches/server/0704-Implement-regenerateChunk.patch +++ /dev/null @@ -1,102 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Mon, 31 Jan 2022 11:21:50 +0100 -Subject: [PATCH] Implement regenerateChunk - -Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 391f546e44080293fd94ff240d7809e3f471bb14..ef942e75ed1aefbc9175f99113cdfeada58e8a50 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -144,6 +144,7 @@ import org.jetbrains.annotations.NotNull; - public class CraftWorld extends CraftRegionAccessor implements World { - public static final int CUSTOM_DIMENSION_OFFSET = 10; - private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); -+ private static final ChunkStatus[] REGEN_CHUNK_STATUSES = {ChunkStatus.BIOMES, ChunkStatus.NOISE, ChunkStatus.SURFACE, ChunkStatus.CARVERS, ChunkStatus.FEATURES, ChunkStatus.INITIALIZE_LIGHT}; // Paper - implement regenerate chunk method - - private final ServerLevel world; - private WorldBorder worldBorder; -@@ -414,27 +415,68 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @Override - public boolean regenerateChunk(int x, int z) { - org.spigotmc.AsyncCatcher.catchOp("chunk regenerate"); // Spigot -- throw new UnsupportedOperationException("Not supported in this Minecraft version! Unless you can fix it, this is not a bug :)"); -- /* -- if (!unloadChunk0(x, z, false)) { -- return false; -+ // Paper start - implement regenerateChunk method -+ final ServerLevel serverLevel = this.world; -+ final net.minecraft.server.level.ServerChunkCache serverChunkCache = serverLevel.getChunkSource(); -+ final ChunkPos chunkPos = new ChunkPos(x, z); -+ final net.minecraft.world.level.chunk.LevelChunk levelChunk = serverChunkCache.getChunk(chunkPos.x, chunkPos.z, true); -+ for (final BlockPos blockPos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), serverLevel.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ())) { -+ levelChunk.removeBlockEntity(blockPos); -+ serverLevel.setBlock(blockPos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(), 16); -+ } -+ -+ for (final ChunkStatus chunkStatus : REGEN_CHUNK_STATUSES) { -+ final List list = new ArrayList<>(); -+ final int range = Math.max(1, chunkStatus.getRange()); -+ for (int chunkX = chunkPos.z - range; chunkX <= chunkPos.z + range; chunkX++) { -+ for (int chunkZ = chunkPos.x - range; chunkZ <= chunkPos.x + range; chunkZ++) { -+ ChunkAccess chunkAccess = serverChunkCache.getChunk(chunkZ, chunkX, chunkStatus.getParent(), true); -+ if (chunkAccess instanceof ImposterProtoChunk accessProtoChunk) { -+ chunkAccess = new ImposterProtoChunk(accessProtoChunk.getWrapped(), true); -+ } else if (chunkAccess instanceof net.minecraft.world.level.chunk.LevelChunk accessLevelChunk) { -+ chunkAccess = new ImposterProtoChunk(accessLevelChunk, true); -+ } -+ list.add(chunkAccess); -+ } -+ } -+ -+ final java.util.concurrent.CompletableFuture> future = chunkStatus.generate( -+ Runnable::run, -+ serverLevel, -+ serverChunkCache.getGenerator(), -+ serverLevel.getStructureManager(), -+ serverChunkCache.getLightEngine(), -+ chunk -> { -+ throw new UnsupportedOperationException("Not creating full chunks here"); -+ }, -+ list -+ ); -+ serverChunkCache.mainThreadProcessor.managedBlock(future::isDone); -+ if (chunkStatus == ChunkStatus.NOISE) { -+ future.join().left().ifPresent(chunk -> net.minecraft.world.level.levelgen.Heightmap.primeHeightmaps(chunk, ChunkStatus.POST_FEATURES)); -+ } - } - -- final long chunkKey = ChunkCoordIntPair.pair(x, z); -- world.getChunkProvider().unloadQueue.remove(chunkKey); -+ for (final BlockPos blockPos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), serverLevel.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ())) { -+ serverChunkCache.blockChanged(blockPos); -+ } - -- net.minecraft.server.Chunk chunk = world.getChunkProvider().generateChunk(x, z); -- PlayerChunk playerChunk = world.getPlayerChunkMap().getChunk(x, z); -- if (playerChunk != null) { -- playerChunk.chunk = chunk; -+ final Set chunksToRelight = new HashSet<>(9); -+ for (int chunkX = chunkPos.x - 1; chunkX <= chunkPos.x + 1 ; chunkX++) { -+ for (int chunkZ = chunkPos.z - 1; chunkZ <= chunkPos.z + 1 ; chunkZ++) { -+ chunksToRelight.add(new ChunkPos(chunkX, chunkZ)); -+ } - } - -- if (chunk != null) { -- refreshChunk(x, z); -+ for (final ChunkPos pos : chunksToRelight) { -+ final ChunkAccess chunk = serverChunkCache.getChunk(pos.x, pos.z, false); -+ if (chunk != null) { -+ serverChunkCache.getLightEngine().lightChunk(chunk, false); -+ } - } - -- return chunk != null; -- */ -+ return true; -+ // Paper end - implement regenerate chunk method - } - - @Override diff --git a/patches/server/0705-Add-GameEvent-tags.patch b/patches/server/0705-Add-GameEvent-tags.patch new file mode 100644 index 000000000000..0a4355d93ac3 --- /dev/null +++ b/patches/server/0705-Add-GameEvent-tags.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 20:03:35 -0800 +Subject: [PATCH] Add GameEvent tags + + +diff --git a/src/main/java/io/papermc/paper/CraftGameEventTag.java b/src/main/java/io/papermc/paper/CraftGameEventTag.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e7d9fd2702a1ce96596580fff8f5ee4fd3d22b5b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/CraftGameEventTag.java +@@ -0,0 +1,35 @@ ++package io.papermc.paper; ++ ++import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.tags.TagKey; ++import org.bukkit.GameEvent; ++import org.bukkit.craftbukkit.tag.CraftTag; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.Collections; ++import java.util.IdentityHashMap; ++import java.util.Map; ++import java.util.Objects; ++import java.util.Set; ++import java.util.stream.Collectors; ++ ++public class CraftGameEventTag extends CraftTag { ++ ++ public CraftGameEventTag(net.minecraft.core.Registry registry, TagKey tag) { ++ super(registry, tag); ++ } ++ ++ private static final Map> KEY_CACHE = Collections.synchronizedMap(new IdentityHashMap<>()); ++ @Override ++ public boolean isTagged(@NotNull GameEvent gameEvent) { ++ return registry.getHolderOrThrow(KEY_CACHE.computeIfAbsent(gameEvent, event -> ResourceKey.create(Registries.GAME_EVENT, CraftNamespacedKey.toMinecraft(event.getKey())))).is(tag); ++ } ++ ++ @Override ++ public @NotNull Set getValues() { ++ return getHandle().stream().map((nms) -> Objects.requireNonNull(GameEvent.getByKey(CraftNamespacedKey.fromMinecraft(BuiltInRegistries.GAME_EVENT.getKey(nms.value()))), nms + " is not a recognized game event")).collect(Collectors.toUnmodifiableSet()); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index a64109dfa8f33812c243ad50c673a5ef043559c0..ddf3ce680bc8c69647a89ee46fd1547e566a61b8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2715,6 +2715,15 @@ public final class CraftServer implements Server { + return (org.bukkit.Tag) new CraftEntityTag(BuiltInRegistries.ENTITY_TYPE, entityTagKey); + } + } ++ // Paper start ++ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> { ++ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class, "Game Event namespace must have GameEvent type"); ++ TagKey gameEventTagKey = TagKey.create(net.minecraft.core.registries.Registries.GAME_EVENT, key); ++ if (net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.getTag(gameEventTagKey).isPresent()) { ++ return (org.bukkit.Tag) new io.papermc.paper.CraftGameEventTag(net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT, gameEventTagKey); ++ } ++ } ++ // Paper end + default -> throw new IllegalArgumentException(); + } + +@@ -2747,6 +2756,13 @@ public final class CraftServer implements Server { + net.minecraft.core.Registry> entityTags = BuiltInRegistries.ENTITY_TYPE; + return entityTags.getTags().map(pair -> (org.bukkit.Tag) new CraftEntityTag(entityTags, pair.getFirst())).collect(ImmutableList.toImmutableList()); + } ++ // Paper start ++ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> { ++ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class); ++ net.minecraft.core.Registry gameEvents = net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT; ++ return gameEvents.getTags().map(pair -> (org.bukkit.Tag) new io.papermc.paper.CraftGameEventTag(gameEvents, pair.getFirst())).collect(ImmutableList.toImmutableList()); ++ } ++ // Paper end + default -> throw new IllegalArgumentException(); + } + } diff --git a/patches/server/0705-Fix-cancelled-powdered-snow-bucket-placement.patch b/patches/server/0705-Fix-cancelled-powdered-snow-bucket-placement.patch deleted file mode 100644 index 872f00bd934b..000000000000 --- a/patches/server/0705-Fix-cancelled-powdered-snow-bucket-placement.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 8 Oct 2021 13:12:58 -0700 -Subject: [PATCH] Fix cancelled powdered snow bucket placement - -Cancelling the placement of powdered snow from the powdered -snow bucket didn't revert grass that became snowy because of the -placement. - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 4624fc8eb4a5e71d7874d25ca0a25975f65a6919..750ffb60c6136fe9e1d56e402578e2a9978d2703 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -379,7 +379,7 @@ public final class ItemStack { - int oldCount = this.getCount(); - ServerLevel world = (ServerLevel) context.getLevel(); - -- if (!(item instanceof BucketItem || item instanceof SolidBucketItem)) { // if not bucket -+ if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement - world.captureBlockStates = true; - // special case bonemeal - if (item == Items.BONE_MEAL) { -@@ -438,7 +438,7 @@ public final class ItemStack { - world.capturedBlockStates.clear(); - if (blocks.size() > 1) { - placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ()); -- } else if (blocks.size() == 1) { -+ } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement - placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, enumhand, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ()); - } - diff --git a/patches/server/0706-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch b/patches/server/0706-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch deleted file mode 100644 index d90b3e803457..000000000000 --- a/patches/server/0706-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sat, 12 Feb 2022 12:40:50 -0700 -Subject: [PATCH] Add missing Validate calls to CraftServer#getSpawnLimit - -Copies appropriate checks from CraftWorld#getSpawnLimit - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index e9d6f40de8ce89bc6541dcb3cd847b208ee67a85..4e64f5b74a26c16325b9778a4646f87936c994a4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2282,6 +2282,8 @@ public final class CraftServer implements Server { - @Override - public int getSpawnLimit(SpawnCategory spawnCategory) { - // Paper start - Add mobcaps commands -+ Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); -+ Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory." + spawnCategory + " does not have a spawn limit."); - return this.getSpawnLimitUnsafe(spawnCategory); - } - public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { diff --git a/patches/server/0706-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch b/patches/server/0706-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch new file mode 100644 index 000000000000..06c9d9f99afc --- /dev/null +++ b/patches/server/0706-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 28 Dec 2021 07:19:01 -0800 +Subject: [PATCH] Execute chunk tasks fairly for worlds while waiting for next + tick + +Currently, only the first world would have had tasks executed. +This might result in chunks loading far slower in the nether, +for example. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index d19f2ca702ffa32c616ec821219079e2b784a460..1c1d111eba7b625bd36b95fbec83d8c5f8272b97 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1296,6 +1296,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Sun, 3 Jan 2021 20:03:35 -0800 -Subject: [PATCH] Add GameEvent tags - - -diff --git a/src/main/java/io/papermc/paper/CraftGameEventTag.java b/src/main/java/io/papermc/paper/CraftGameEventTag.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e7d9fd2702a1ce96596580fff8f5ee4fd3d22b5b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/CraftGameEventTag.java -@@ -0,0 +1,35 @@ -+package io.papermc.paper; -+ -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.core.registries.Registries; -+import net.minecraft.resources.ResourceKey; -+import net.minecraft.tags.TagKey; -+import org.bukkit.GameEvent; -+import org.bukkit.craftbukkit.tag.CraftTag; -+import org.bukkit.craftbukkit.util.CraftNamespacedKey; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.Collections; -+import java.util.IdentityHashMap; -+import java.util.Map; -+import java.util.Objects; -+import java.util.Set; -+import java.util.stream.Collectors; -+ -+public class CraftGameEventTag extends CraftTag { -+ -+ public CraftGameEventTag(net.minecraft.core.Registry registry, TagKey tag) { -+ super(registry, tag); -+ } -+ -+ private static final Map> KEY_CACHE = Collections.synchronizedMap(new IdentityHashMap<>()); -+ @Override -+ public boolean isTagged(@NotNull GameEvent gameEvent) { -+ return registry.getHolderOrThrow(KEY_CACHE.computeIfAbsent(gameEvent, event -> ResourceKey.create(Registries.GAME_EVENT, CraftNamespacedKey.toMinecraft(event.getKey())))).is(tag); -+ } -+ -+ @Override -+ public @NotNull Set getValues() { -+ return getHandle().stream().map((nms) -> Objects.requireNonNull(GameEvent.getByKey(CraftNamespacedKey.fromMinecraft(BuiltInRegistries.GAME_EVENT.getKey(nms.value()))), nms + " is not a recognized game event")).collect(Collectors.toUnmodifiableSet()); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 4e64f5b74a26c16325b9778a4646f87936c994a4..9cfc4422aa772889db06a3bb450d18c7aa0b4dcb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2703,6 +2703,15 @@ public final class CraftServer implements Server { - return (org.bukkit.Tag) new CraftEntityTag(BuiltInRegistries.ENTITY_TYPE, entityTagKey); - } - } -+ // Paper start -+ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> { -+ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class, "Game Event namespace must have GameEvent type"); -+ TagKey gameEventTagKey = TagKey.create(net.minecraft.core.registries.Registries.GAME_EVENT, key); -+ if (net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.getTag(gameEventTagKey).isPresent()) { -+ return (org.bukkit.Tag) new io.papermc.paper.CraftGameEventTag(net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT, gameEventTagKey); -+ } -+ } -+ // Paper end - default -> throw new IllegalArgumentException(); - } - -@@ -2735,6 +2744,13 @@ public final class CraftServer implements Server { - net.minecraft.core.Registry> entityTags = BuiltInRegistries.ENTITY_TYPE; - return entityTags.getTags().map(pair -> (org.bukkit.Tag) new CraftEntityTag(entityTags, pair.getFirst())).collect(ImmutableList.toImmutableList()); - } -+ // Paper start -+ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> { -+ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class); -+ net.minecraft.core.Registry gameEvents = net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT; -+ return gameEvents.getTags().map(pair -> (org.bukkit.Tag) new io.papermc.paper.CraftGameEventTag(gameEvents, pair.getFirst())).collect(ImmutableList.toImmutableList()); -+ } -+ // Paper end - default -> throw new IllegalArgumentException(); - } - } diff --git a/patches/server/0709-Furnace-RecipesUsed-API.patch b/patches/server/0707-Furnace-RecipesUsed-API.patch similarity index 100% rename from patches/server/0709-Furnace-RecipesUsed-API.patch rename to patches/server/0707-Furnace-RecipesUsed-API.patch diff --git a/patches/server/0710-Configurable-sculk-sensor-listener-range.patch b/patches/server/0708-Configurable-sculk-sensor-listener-range.patch similarity index 100% rename from patches/server/0710-Configurable-sculk-sensor-listener-range.patch rename to patches/server/0708-Configurable-sculk-sensor-listener-range.patch diff --git a/patches/server/0708-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch b/patches/server/0708-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch deleted file mode 100644 index f4cd07d2d716..000000000000 --- a/patches/server/0708-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 28 Dec 2021 07:19:01 -0800 -Subject: [PATCH] Execute chunk tasks fairly for worlds while waiting for next - tick - -Currently, only the first world would have had tasks executed. -This might result in chunks loading far slower in the nether, -for example. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 1df847164796c0992e677e0db45a9be223c8dc4a..6a321f5d8dd8278d01e154f64a1685403255978c 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1296,6 +1296,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sat, 19 Feb 2022 20:15:41 -0800 +Subject: [PATCH] Option to have default CustomSpawners in custom worlds + +By default, only LevelStem's that specifically match the ResourceKey for +OVERWORLD will have the 5 (currently) impls of CustomSpawner (for +phantoms, wandering traders, etc.). This adds an option to instead of +just looking at the LevelStem key, look at the DimensionType key which +is one level below that. Defaults to off to keep vanilla behavior. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 1c1d111eba7b625bd36b95fbec83d8c5f8272b97..239b1616ddd4cb64a0565d0f8f3bef7bbaaf23c9 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -617,7 +617,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop spawners; ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryAccess().registryOrThrow(Registries.DIMENSION_TYPE).getResourceKey(worlddimension.type().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) { ++ spawners = list; ++ } else { ++ spawners = Collections.emptyList(); ++ } ++ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider); ++ // Paper end - option to use the dimension_type to check if spawners should be added + } + + worlddata.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified()); diff --git a/patches/server/0711-Put-world-into-worldlist-before-initing-the-world.patch b/patches/server/0711-Put-world-into-worldlist-before-initing-the-world.patch new file mode 100644 index 000000000000..998c2caea6fb --- /dev/null +++ b/patches/server/0711-Put-world-into-worldlist-before-initing-the-world.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 22 Feb 2022 14:21:35 -0800 +Subject: [PATCH] Put world into worldlist before initing the world + +Some parts of legacy conversion will need the overworld +to get the legacy structure data storage + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 239b1616ddd4cb64a0565d0f8f3bef7bbaaf23c9..39735cce3c32d9905130dca2d7e34aff144364ef 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -629,9 +629,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Thu, 23 Dec 2021 23:59:12 -0500 +Subject: [PATCH] Fix Entity Position Desync + +If entities were teleported in the first tick it would not be send to the client. + +This excludes hanging entities, as this fix caused problematic behavior due to them having their own +position field. + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 062225ac8b5fbc44290352d78b215640691f3c23..3608257fabec6ad7edb056def8a6f36c50b4871e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -171,7 +171,7 @@ public class ServerEntity { + boolean flag4 = false; + boolean flag5 = false; + +- if (this.tickCount > 0 || this.entity instanceof AbstractArrow) { ++ if (!(this.entity instanceof net.minecraft.world.entity.decoration.HangingEntity) || this.tickCount > 0 || this.entity instanceof AbstractArrow) { // Paper - Always update position to fix first-tick teleports + long k = this.positionCodec.encodeX(vec3d); + long l = this.positionCodec.encodeY(vec3d); + long i1 = this.positionCodec.encodeZ(vec3d); diff --git a/patches/server/0712-Option-to-have-default-CustomSpawners-in-custom-worl.patch b/patches/server/0712-Option-to-have-default-CustomSpawners-in-custom-worl.patch deleted file mode 100644 index 07a86c606606..000000000000 --- a/patches/server/0712-Option-to-have-default-CustomSpawners-in-custom-worl.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 19 Feb 2022 20:15:41 -0800 -Subject: [PATCH] Option to have default CustomSpawners in custom worlds - -By default, only LevelStem's that specifically match the ResourceKey for -OVERWORLD will have the 5 (currently) impls of CustomSpawner (for -phantoms, wandering traders, etc.). This adds an option to instead of -just looking at the LevelStem key, look at the DimensionType key which -is one level below that. Defaults to off to keep vanilla behavior. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 6a321f5d8dd8278d01e154f64a1685403255978c..0190a51f4b63bb98dda55b6acbea002fc91d3bbe 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -617,7 +617,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop spawners; -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryAccess().registryOrThrow(Registries.DIMENSION_TYPE).getResourceKey(worlddimension.type().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) { -+ spawners = list; -+ } else { -+ spawners = Collections.emptyList(); -+ } -+ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider); -+ // Paper end - option to use the dimension_type to check if spawners should be added - } - - worlddata.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified()); diff --git a/patches/server/0713-Custom-Potion-Mixes.patch b/patches/server/0713-Custom-Potion-Mixes.patch new file mode 100644 index 000000000000..edb4c6d4f4a3 --- /dev/null +++ b/patches/server/0713-Custom-Potion-Mixes.patch @@ -0,0 +1,247 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 7 Oct 2021 14:34:55 -0700 +Subject: [PATCH] Custom Potion Mixes + + +diff --git a/src/main/java/io/papermc/paper/potion/PaperPotionMix.java b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7ea357ac2f3a93db4ebdf24b5072be7d1cad3e33 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java +@@ -0,0 +1,21 @@ ++package io.papermc.paper.potion; ++ ++import java.util.function.Predicate; ++import net.minecraft.world.item.ItemStack; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++import org.bukkit.inventory.RecipeChoice; ++ ++public record PaperPotionMix(ItemStack result, Predicate input, Predicate ingredient) { ++ ++ public PaperPotionMix(PotionMix potionMix) { ++ this(CraftItemStack.asNMSCopy(potionMix.getResult()), convert(potionMix.getInput()), convert(potionMix.getIngredient())); ++ } ++ ++ static Predicate convert(final RecipeChoice choice) { ++ if (choice instanceof PredicateRecipeChoice predicateRecipeChoice) { ++ return stack -> predicateRecipeChoice.test(CraftItemStack.asBukkitCopy(stack)); ++ } ++ return CraftRecipe.toIngredient(choice, true); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 39735cce3c32d9905130dca2d7e34aff144364ef..8080eee9babe02660724eee756700a4d5e69014f 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2081,6 +2081,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> POTION_MIXES = Lists.newArrayList(); + private static final List> CONTAINER_MIXES = Lists.newArrayList(); ++ private static final it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap CUSTOM_MIXES = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // Paper - Custom Potion Mixes + private static final List ALLOWED_CONTAINERS = Lists.newArrayList(); + private static final Predicate ALLOWED_CONTAINER = (stack) -> { + for(Ingredient ingredient : ALLOWED_CONTAINERS) { +@@ -26,7 +27,7 @@ public class PotionBrewing { + }; + + public static boolean isIngredient(ItemStack stack) { +- return isContainerIngredient(stack) || isPotionIngredient(stack); ++ return isContainerIngredient(stack) || isPotionIngredient(stack) || isCustomIngredient(stack); // Paper - Custom Potion Mixes + } + + protected static boolean isContainerIngredient(ItemStack stack) { +@@ -60,6 +61,11 @@ public class PotionBrewing { + } + + public static boolean hasMix(ItemStack input, ItemStack ingredient) { ++ // Paper start - Custom Potion Mixes ++ if (hasCustomMix(input, ingredient)) { ++ return true; ++ } ++ // Paper end - Custom Potion Mixes + if (!ALLOWED_CONTAINER.test(input)) { + return false; + } else { +@@ -93,6 +99,13 @@ public class PotionBrewing { + + public static ItemStack mix(ItemStack ingredient, ItemStack input) { + if (!input.isEmpty()) { ++ // Paper start - Custom Potion Mixes ++ for (var mix : CUSTOM_MIXES.values()) { ++ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { ++ return mix.result().copy(); ++ } ++ } ++ // Paper end - Custom Potion Mixes + Potion potion = PotionUtils.getPotion(input); + Item item = input.getItem(); + +@@ -112,6 +125,54 @@ public class PotionBrewing { + return input; + } + ++ // Paper start - Custom Potion Mixes ++ public static boolean isCustomIngredient(ItemStack stack) { ++ for (var mix : CUSTOM_MIXES.values()) { ++ if (mix.ingredient().test(stack)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ public static boolean isCustomInput(ItemStack stack) { ++ for (var mix : CUSTOM_MIXES.values()) { ++ if (mix.input().test(stack)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ private static boolean hasCustomMix(ItemStack input, ItemStack ingredient) { ++ for (var mix : CUSTOM_MIXES.values()) { ++ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ public static void addPotionMix(io.papermc.paper.potion.PotionMix mix) { ++ if (CUSTOM_MIXES.containsKey(mix.getKey())) { ++ throw new IllegalArgumentException("Duplicate recipe ignored with ID " + mix.getKey()); ++ } ++ CUSTOM_MIXES.putAndMoveToFirst(mix.getKey(), new io.papermc.paper.potion.PaperPotionMix(mix)); ++ } ++ ++ public static boolean removePotionMix(org.bukkit.NamespacedKey key) { ++ return CUSTOM_MIXES.remove(key) != null; ++ } ++ ++ public static void reload() { ++ POTION_MIXES.clear(); ++ CONTAINER_MIXES.clear(); ++ ALLOWED_CONTAINERS.clear(); ++ CUSTOM_MIXES.clear(); ++ bootStrap(); ++ } ++ // Paper end - Custom Potion Mixes ++ + public static void bootStrap() { + addContainer(Items.POTION); + addContainer(Items.SPLASH_POTION); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java +index b31d7053abf1d2432b4887dab2bd8f8cc5308554..9bb542ce3a8c52e1688bb1f66fc916dd23a5fd10 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java +@@ -341,7 +341,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements + + @Override + public boolean canPlaceItem(int slot, ItemStack stack) { +- return slot == 3 ? PotionBrewing.isIngredient(stack) : (slot == 4 ? stack.is(Items.BLAZE_POWDER) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE)) && this.getItem(slot).isEmpty()); ++ return slot == 3 ? PotionBrewing.isIngredient(stack) : (slot == 4 ? stack.is(Items.BLAZE_POWDER) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || PotionBrewing.isCustomInput(stack)) && this.getItem(slot).isEmpty()); // Paper - Custom Potion Mixes + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index f629991fa998d4dab89ed5c3d26b1b7a4f85b5cb..d8bec75e7f5c034051839818f51cdae71863608c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -306,6 +306,7 @@ public final class CraftServer implements Server { + private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper + public static Exception excessiveVelEx; // Paper - Velocity warnings + 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 + + static { + ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); +@@ -332,7 +333,7 @@ public final class CraftServer implements Server { + + CraftRegistry.setMinecraftRegistry(console.registryAccess()); + +- Potion.setPotionBrewer(new CraftPotionBrewer()); ++ Potion.setPotionBrewer(potionBrewer); // Paper - Custom Potion Mixes + // Ugly hack :( + + if (!Main.useConsole) { +@@ -3079,5 +3080,10 @@ public final class CraftServer implements Server { + return datapackManager; + } + ++ @Override ++ public CraftPotionBrewer getPotionBrewer() { ++ return this.potionBrewer; ++ } ++ + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +index 139dff90561ac6c51954c6289918a07aeea13a1b..6ba29875d78ede4aa7978ff689e588f7fed11528 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +@@ -15,6 +15,11 @@ public interface CraftRecipe extends Recipe { + void addToCraftingManager(); + + default Ingredient toNMS(RecipeChoice bukkit, boolean requireNotEmpty) { ++ // Paper start ++ return toIngredient(bukkit, requireNotEmpty); ++ } ++ static Ingredient toIngredient(RecipeChoice bukkit, boolean requireNotEmpty) { ++ // Paper end + Ingredient stack; + + if (bukkit == null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java +index 09ac71b6b41c757832792d9ea8ac9288f8a7404f..2909a2736a0c9d863c7ab01e0ec259f7952080cc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java ++++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java +@@ -28,4 +28,21 @@ public class CraftPotionBrewer implements PotionBrewer { + public PotionEffect createEffect(PotionEffectType potion, int duration, int amplifier) { + return new PotionEffect(potion, potion.isInstant() ? 1 : (int) (duration * potion.getDurationModifier()), amplifier); + } ++ ++ // Paper start ++ @Override ++ public void addPotionMix(io.papermc.paper.potion.PotionMix potionMix) { ++ net.minecraft.world.item.alchemy.PotionBrewing.addPotionMix(potionMix); ++ } ++ ++ @Override ++ public void removePotionMix(org.bukkit.NamespacedKey key) { ++ net.minecraft.world.item.alchemy.PotionBrewing.removePotionMix(key); ++ } ++ ++ @Override ++ public void resetPotionMixes() { ++ net.minecraft.world.item.alchemy.PotionBrewing.reload(); ++ } ++ // Paper end + } diff --git a/patches/server/0713-Put-world-into-worldlist-before-initing-the-world.patch b/patches/server/0713-Put-world-into-worldlist-before-initing-the-world.patch deleted file mode 100644 index 299eb0a886fa..000000000000 --- a/patches/server/0713-Put-world-into-worldlist-before-initing-the-world.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 22 Feb 2022 14:21:35 -0800 -Subject: [PATCH] Put world into worldlist before initing the world - -Some parts of legacy conversion will need the overworld -to get the legacy structure data storage - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 0190a51f4b63bb98dda55b6acbea002fc91d3bbe..831594402c2c7d6aa72b303fb73fde62160c95e3 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -629,9 +629,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Thu, 23 Dec 2021 23:59:12 -0500 -Subject: [PATCH] Fix Entity Position Desync - -If entities were teleported in the first tick it would not be send to the client. - -This excludes hanging entities, as this fix caused problematic behavior due to them having their own -position field. - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index c881d6ea6acbcbd1414a0b7d6b5a26076244e34e..b52f86e23a35f8727d827155ebb20f847108d673 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -171,7 +171,7 @@ public class ServerEntity { - boolean flag4 = false; - boolean flag5 = false; - -- if (this.tickCount > 0 || this.entity instanceof AbstractArrow) { -+ if (!(this.entity instanceof net.minecraft.world.entity.decoration.HangingEntity) || this.tickCount > 0 || this.entity instanceof AbstractArrow) { // Paper - Always update position to fix first-tick teleports - long k = this.positionCodec.encodeX(vec3d); - long l = this.positionCodec.encodeY(vec3d); - long i1 = this.positionCodec.encodeZ(vec3d); diff --git a/patches/server/0714-Force-close-world-loading-screen.patch b/patches/server/0714-Force-close-world-loading-screen.patch new file mode 100644 index 000000000000..848ad8fa5bfd --- /dev/null +++ b/patches/server/0714-Force-close-world-loading-screen.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Wed, 2 Mar 2022 09:45:56 +0100 +Subject: [PATCH] Force close world loading screen + +Dead players would be stuck in the world loading screen and other players may +miss messages and similar sent in the join event if chunk loading is slow. +Paper already circumvents falling through the world before chunks are loaded, +so we do not need that. The client only needs the chunk it is currently in to +be loaded to close the loading screen, so we just send an empty one. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 584b1627cf18e6aee9338ade0f94d983e2dda412..0c77457e1b9360ef578b4a781bb2ec1fb7b582db 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -398,6 +398,16 @@ public abstract class PlayerList { + + // Paper start - Fire PlayerJoinEvent when Player is actually ready; move vehicle into method so it can be called above - short circuit around that code + onPlayerJoinFinish(player, worldserver1, s1); ++ // Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead ++ 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) ++ ); ++ } ++ // Paper end - Send empty chunk + } + private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, CompoundTag nbttagcompound) { + // Paper end - Fire PlayerJoinEvent when Player is actually ready diff --git a/patches/server/0715-Custom-Potion-Mixes.patch b/patches/server/0715-Custom-Potion-Mixes.patch deleted file mode 100644 index 4468cf31b76b..000000000000 --- a/patches/server/0715-Custom-Potion-Mixes.patch +++ /dev/null @@ -1,247 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 7 Oct 2021 14:34:55 -0700 -Subject: [PATCH] Custom Potion Mixes - - -diff --git a/src/main/java/io/papermc/paper/potion/PaperPotionMix.java b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7ea357ac2f3a93db4ebdf24b5072be7d1cad3e33 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java -@@ -0,0 +1,21 @@ -+package io.papermc.paper.potion; -+ -+import java.util.function.Predicate; -+import net.minecraft.world.item.ItemStack; -+import org.bukkit.craftbukkit.inventory.CraftItemStack; -+import org.bukkit.craftbukkit.inventory.CraftRecipe; -+import org.bukkit.inventory.RecipeChoice; -+ -+public record PaperPotionMix(ItemStack result, Predicate input, Predicate ingredient) { -+ -+ public PaperPotionMix(PotionMix potionMix) { -+ this(CraftItemStack.asNMSCopy(potionMix.getResult()), convert(potionMix.getInput()), convert(potionMix.getIngredient())); -+ } -+ -+ static Predicate convert(final RecipeChoice choice) { -+ if (choice instanceof PredicateRecipeChoice predicateRecipeChoice) { -+ return stack -> predicateRecipeChoice.test(CraftItemStack.asBukkitCopy(stack)); -+ } -+ return CraftRecipe.toIngredient(choice, true); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index d81fa8c7049bb6284865f249cc808caac7d5f65e..011d688fc0be45a8d1f65ebedcc5291bb109def2 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2081,6 +2081,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> POTION_MIXES = Lists.newArrayList(); - private static final List> CONTAINER_MIXES = Lists.newArrayList(); -+ private static final it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap CUSTOM_MIXES = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // Paper - Custom Potion Mixes - private static final List ALLOWED_CONTAINERS = Lists.newArrayList(); - private static final Predicate ALLOWED_CONTAINER = (stack) -> { - for(Ingredient ingredient : ALLOWED_CONTAINERS) { -@@ -26,7 +27,7 @@ public class PotionBrewing { - }; - - public static boolean isIngredient(ItemStack stack) { -- return isContainerIngredient(stack) || isPotionIngredient(stack); -+ return isContainerIngredient(stack) || isPotionIngredient(stack) || isCustomIngredient(stack); // Paper - Custom Potion Mixes - } - - protected static boolean isContainerIngredient(ItemStack stack) { -@@ -60,6 +61,11 @@ public class PotionBrewing { - } - - public static boolean hasMix(ItemStack input, ItemStack ingredient) { -+ // Paper start - Custom Potion Mixes -+ if (hasCustomMix(input, ingredient)) { -+ return true; -+ } -+ // Paper end - Custom Potion Mixes - if (!ALLOWED_CONTAINER.test(input)) { - return false; - } else { -@@ -93,6 +99,13 @@ public class PotionBrewing { - - public static ItemStack mix(ItemStack ingredient, ItemStack input) { - if (!input.isEmpty()) { -+ // Paper start - Custom Potion Mixes -+ for (var mix : CUSTOM_MIXES.values()) { -+ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { -+ return mix.result().copy(); -+ } -+ } -+ // Paper end - Custom Potion Mixes - Potion potion = PotionUtils.getPotion(input); - Item item = input.getItem(); - -@@ -112,6 +125,54 @@ public class PotionBrewing { - return input; - } - -+ // Paper start - Custom Potion Mixes -+ public static boolean isCustomIngredient(ItemStack stack) { -+ for (var mix : CUSTOM_MIXES.values()) { -+ if (mix.ingredient().test(stack)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ public static boolean isCustomInput(ItemStack stack) { -+ for (var mix : CUSTOM_MIXES.values()) { -+ if (mix.input().test(stack)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ private static boolean hasCustomMix(ItemStack input, ItemStack ingredient) { -+ for (var mix : CUSTOM_MIXES.values()) { -+ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ public static void addPotionMix(io.papermc.paper.potion.PotionMix mix) { -+ if (CUSTOM_MIXES.containsKey(mix.getKey())) { -+ throw new IllegalArgumentException("Duplicate recipe ignored with ID " + mix.getKey()); -+ } -+ CUSTOM_MIXES.putAndMoveToFirst(mix.getKey(), new io.papermc.paper.potion.PaperPotionMix(mix)); -+ } -+ -+ public static boolean removePotionMix(org.bukkit.NamespacedKey key) { -+ return CUSTOM_MIXES.remove(key) != null; -+ } -+ -+ public static void reload() { -+ POTION_MIXES.clear(); -+ CONTAINER_MIXES.clear(); -+ ALLOWED_CONTAINERS.clear(); -+ CUSTOM_MIXES.clear(); -+ bootStrap(); -+ } -+ // Paper end - Custom Potion Mixes -+ - public static void bootStrap() { - addContainer(Items.POTION); - addContainer(Items.SPLASH_POTION); -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java -index 424406d2692856cfd82b6f3b7b6228fa3bd20c2f..bc01481ac5990ad1cfd1def5a16dd0ed2f9de8c9 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java -@@ -341,7 +341,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements - - @Override - public boolean canPlaceItem(int slot, ItemStack stack) { -- return slot == 3 ? PotionBrewing.isIngredient(stack) : (slot == 4 ? stack.is(Items.BLAZE_POWDER) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE)) && this.getItem(slot).isEmpty()); -+ return slot == 3 ? PotionBrewing.isIngredient(stack) : (slot == 4 ? stack.is(Items.BLAZE_POWDER) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || PotionBrewing.isCustomInput(stack)) && this.getItem(slot).isEmpty()); // Paper - Custom Potion Mixes - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index ab5e98c3b7c7b3d5b2108be87e6465cc7a6f3d2c..2bc4cfb1abd8a82228d1958fde2fd92e10c199b1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -304,6 +304,7 @@ public final class CraftServer implements Server { - private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper - public static Exception excessiveVelEx; // Paper - Velocity warnings - 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 - - static { - ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); -@@ -330,7 +331,7 @@ public final class CraftServer implements Server { - - CraftRegistry.setMinecraftRegistry(console.registryAccess()); - -- Potion.setPotionBrewer(new CraftPotionBrewer()); -+ Potion.setPotionBrewer(potionBrewer); // Paper - Custom Potion Mixes - // Ugly hack :( - - if (!Main.useConsole) { -@@ -3067,5 +3068,10 @@ public final class CraftServer implements Server { - return datapackManager; - } - -+ @Override -+ public CraftPotionBrewer getPotionBrewer() { -+ return this.potionBrewer; -+ } -+ - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -index 139dff90561ac6c51954c6289918a07aeea13a1b..6ba29875d78ede4aa7978ff689e588f7fed11528 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -@@ -15,6 +15,11 @@ public interface CraftRecipe extends Recipe { - void addToCraftingManager(); - - default Ingredient toNMS(RecipeChoice bukkit, boolean requireNotEmpty) { -+ // Paper start -+ return toIngredient(bukkit, requireNotEmpty); -+ } -+ static Ingredient toIngredient(RecipeChoice bukkit, boolean requireNotEmpty) { -+ // Paper end - Ingredient stack; - - if (bukkit == null) { -diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java -index 09ac71b6b41c757832792d9ea8ac9288f8a7404f..2909a2736a0c9d863c7ab01e0ec259f7952080cc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java -+++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java -@@ -28,4 +28,21 @@ public class CraftPotionBrewer implements PotionBrewer { - public PotionEffect createEffect(PotionEffectType potion, int duration, int amplifier) { - return new PotionEffect(potion, potion.isInstant() ? 1 : (int) (duration * potion.getDurationModifier()), amplifier); - } -+ -+ // Paper start -+ @Override -+ public void addPotionMix(io.papermc.paper.potion.PotionMix potionMix) { -+ net.minecraft.world.item.alchemy.PotionBrewing.addPotionMix(potionMix); -+ } -+ -+ @Override -+ public void removePotionMix(org.bukkit.NamespacedKey key) { -+ net.minecraft.world.item.alchemy.PotionBrewing.removePotionMix(key); -+ } -+ -+ @Override -+ public void resetPotionMixes() { -+ net.minecraft.world.item.alchemy.PotionBrewing.reload(); -+ } -+ // Paper end - } diff --git a/patches/server/0715-Fix-falling-block-spawn-methods.patch b/patches/server/0715-Fix-falling-block-spawn-methods.patch new file mode 100644 index 000000000000..5181c49b2862 --- /dev/null +++ b/patches/server/0715-Fix-falling-block-spawn-methods.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Fri, 4 Mar 2022 20:35:19 +0100 +Subject: [PATCH] Fix falling block spawn methods + +Restores the API behavior from previous versions of the server +- Do not call API events +- Do not replace the existing block in the world + +== AT == +public net.minecraft.world.entity.item.FallingBlockEntity (Lnet/minecraft/world/level/Level;DDDLnet/minecraft/world/level/block/state/BlockState;)V + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 7bb43181e4de4cf3a3944aea88c45e54e476433c..97ebf2e2e08469cacc66c4f38bd2edfc2107fe6a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1394,7 +1394,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + Preconditions.checkArgument(material != null, "Material cannot be null"); + Preconditions.checkArgument(material.isBlock(), "Material.%s must be a block", material); + +- FallingBlockEntity entity = FallingBlockEntity.fall(this.world, BlockPos.containing(location.getX(), location.getY(), location.getZ()), CraftBlockType.bukkitToMinecraft(material).defaultBlockState(), SpawnReason.CUSTOM); ++ // Paper start - restore API behavior for spawning falling blocks ++ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), CraftBlockType.bukkitToMinecraft(material).defaultBlockState()); // Paper ++ entity.time = 1; ++ ++ this.world.addFreshEntity(entity, SpawnReason.CUSTOM); ++ // Paper end - restore API behavior for spawning falling blocks + return (FallingBlock) entity.getBukkitEntity(); + } + +@@ -1403,7 +1408,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + Preconditions.checkArgument(location != null, "Location cannot be null"); + Preconditions.checkArgument(data != null, "BlockData cannot be null"); + +- FallingBlockEntity entity = FallingBlockEntity.fall(this.world, BlockPos.containing(location.getX(), location.getY(), location.getZ()), ((CraftBlockData) data).getState(), SpawnReason.CUSTOM); ++ // Paper start - restore API behavior for spawning falling blocks ++ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), ((CraftBlockData) data).getState()); ++ entity.time = 1; ++ ++ this.world.addFreshEntity(entity, SpawnReason.CUSTOM); ++ // Paper end - restore API behavior for spawning falling blocks + return (FallingBlock) entity.getBukkitEntity(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +index 0aa6cd6da72c4a48d8bb48adeccc2bb49939fabd..08316493785e0cf1f93f07dda8016ca5956216f8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java +@@ -378,7 +378,7 @@ public final class CraftEntityTypes { + register(new EntityTypeData<>(EntityType.PRIMED_TNT, TNTPrimed.class, CraftTNTPrimed::new, spawnData -> new PrimedTnt(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), null))); + register(new EntityTypeData<>(EntityType.FALLING_BLOCK, FallingBlock.class, CraftFallingBlock::new, spawnData -> { + BlockPos pos = BlockPos.containing(spawnData.x(), spawnData.y(), spawnData.z()); +- return FallingBlockEntity.fall(spawnData.minecraftWorld(), pos, spawnData.world().getBlockState(pos)); ++ return new FallingBlockEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), spawnData.world().getBlockState(pos)); // Paper - create falling block entities correctly + })); + register(new EntityTypeData<>(EntityType.FIREWORK, Firework.class, CraftFirework::new, spawnData -> new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), net.minecraft.world.item.ItemStack.EMPTY))); + register(new EntityTypeData<>(EntityType.EVOKER_FANGS, EvokerFangs.class, CraftEvokerFangs::new, spawnData -> new net.minecraft.world.entity.projectile.EvokerFangs(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), (float) Math.toRadians(spawnData.yaw()), 0, null))); diff --git a/patches/server/0718-Expose-furnace-minecart-push-values.patch b/patches/server/0716-Expose-furnace-minecart-push-values.patch similarity index 100% rename from patches/server/0718-Expose-furnace-minecart-push-values.patch rename to patches/server/0716-Expose-furnace-minecart-push-values.patch diff --git a/patches/server/0716-Force-close-world-loading-screen.patch b/patches/server/0716-Force-close-world-loading-screen.patch deleted file mode 100644 index 0ab0559a4f05..000000000000 --- a/patches/server/0716-Force-close-world-loading-screen.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Wed, 2 Mar 2022 09:45:56 +0100 -Subject: [PATCH] Force close world loading screen - -Dead players would be stuck in the world loading screen and other players may -miss messages and similar sent in the join event if chunk loading is slow. -Paper already circumvents falling through the world before chunks are loaded, -so we do not need that. The client only needs the chunk it is currently in to -be loaded to close the loading screen, so we just send an empty one. - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index f66f7e8070cbe7628a6e4ae69418fb863f684fae..a75e343a03627f1155361c98a45ed8fb66b0c4ec 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -398,6 +398,16 @@ public abstract class PlayerList { - - // Paper start - Fire PlayerJoinEvent when Player is actually ready; move vehicle into method so it can be called above - short circuit around that code - onPlayerJoinFinish(player, worldserver1, s1); -+ // Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead -+ 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) -+ ); -+ } -+ // Paper end - Send empty chunk - } - private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, CompoundTag nbttagcompound) { - // Paper end - Fire PlayerJoinEvent when Player is actually ready diff --git a/patches/server/0719-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch b/patches/server/0717-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch similarity index 100% rename from patches/server/0719-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch rename to patches/server/0717-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch diff --git a/patches/server/0717-Fix-falling-block-spawn-methods.patch b/patches/server/0717-Fix-falling-block-spawn-methods.patch deleted file mode 100644 index a05c6e641f9f..000000000000 --- a/patches/server/0717-Fix-falling-block-spawn-methods.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Fri, 4 Mar 2022 20:35:19 +0100 -Subject: [PATCH] Fix falling block spawn methods - -Restores the API behavior from previous versions of the server -- Do not call API events -- Do not replace the existing block in the world - -== AT == -public net.minecraft.world.entity.item.FallingBlockEntity (Lnet/minecraft/world/level/Level;DDDLnet/minecraft/world/level/block/state/BlockState;)V - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index ef942e75ed1aefbc9175f99113cdfeada58e8a50..5e2a1a4bd047b6eb1581ce6e6d9b0f78be64c448 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1381,7 +1381,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - Preconditions.checkArgument(material != null, "Material cannot be null"); - Preconditions.checkArgument(material.isBlock(), "Material.%s must be a block", material); - -- FallingBlockEntity entity = FallingBlockEntity.fall(this.world, BlockPos.containing(location.getX(), location.getY(), location.getZ()), CraftBlockType.bukkitToMinecraft(material).defaultBlockState(), SpawnReason.CUSTOM); -+ // Paper start - restore API behavior for spawning falling blocks -+ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), CraftBlockType.bukkitToMinecraft(material).defaultBlockState()); // Paper -+ entity.time = 1; -+ -+ this.world.addFreshEntity(entity, SpawnReason.CUSTOM); -+ // Paper end - restore API behavior for spawning falling blocks - return (FallingBlock) entity.getBukkitEntity(); - } - -@@ -1390,7 +1395,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - Preconditions.checkArgument(location != null, "Location cannot be null"); - Preconditions.checkArgument(data != null, "BlockData cannot be null"); - -- FallingBlockEntity entity = FallingBlockEntity.fall(this.world, BlockPos.containing(location.getX(), location.getY(), location.getZ()), ((CraftBlockData) data).getState(), SpawnReason.CUSTOM); -+ // Paper start - restore API behavior for spawning falling blocks -+ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), ((CraftBlockData) data).getState()); -+ entity.time = 1; -+ -+ this.world.addFreshEntity(entity, SpawnReason.CUSTOM); -+ // Paper end - restore API behavior for spawning falling blocks - return (FallingBlock) entity.getBukkitEntity(); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -index 0aa6cd6da72c4a48d8bb48adeccc2bb49939fabd..08316493785e0cf1f93f07dda8016ca5956216f8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java -@@ -378,7 +378,7 @@ public final class CraftEntityTypes { - register(new EntityTypeData<>(EntityType.PRIMED_TNT, TNTPrimed.class, CraftTNTPrimed::new, spawnData -> new PrimedTnt(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), null))); - register(new EntityTypeData<>(EntityType.FALLING_BLOCK, FallingBlock.class, CraftFallingBlock::new, spawnData -> { - BlockPos pos = BlockPos.containing(spawnData.x(), spawnData.y(), spawnData.z()); -- return FallingBlockEntity.fall(spawnData.minecraftWorld(), pos, spawnData.world().getBlockState(pos)); -+ return new FallingBlockEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), spawnData.world().getBlockState(pos)); // Paper - create falling block entities correctly - })); - register(new EntityTypeData<>(EntityType.FIREWORK, Firework.class, CraftFirework::new, spawnData -> new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), net.minecraft.world.item.ItemStack.EMPTY))); - register(new EntityTypeData<>(EntityType.EVOKER_FANGS, EvokerFangs.class, CraftEvokerFangs::new, spawnData -> new net.minecraft.world.entity.projectile.EvokerFangs(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), (float) Math.toRadians(spawnData.yaw()), 0, null))); diff --git a/patches/server/0718-More-Projectile-API.patch b/patches/server/0718-More-Projectile-API.patch new file mode 100644 index 000000000000..222fc0e15df4 --- /dev/null +++ b/patches/server/0718-More-Projectile-API.patch @@ -0,0 +1,588 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Tue, 22 Jun 2021 23:41:11 -0400 +Subject: [PATCH] More Projectile API + +== AT == +public net.minecraft.world.entity.projectile.FishingHook timeUntilLured +public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaX +public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaY +public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaZ +public net.minecraft.world.entity.projectile.ShulkerBullet currentMoveDirection +public net.minecraft.world.entity.projectile.ShulkerBullet flightSteps +public net.minecraft.world.entity.projectile.AbstractArrow soundEvent +public net.minecraft.world.entity.projectile.ThrownTrident dealtDamage +public net.minecraft.world.entity.projectile.Projectile hasBeenShot +public net.minecraft.world.entity.projectile.Projectile leftOwner +public net.minecraft.world.entity.projectile.Projectile preOnHit(Lnet/minecraft/world/phys/HitResult;)V +public net.minecraft.world.entity.projectile.Projectile canHitEntity(Lnet/minecraft/world/entity/Entity;)Z + +Co-authored-by: Nassim Jahnke + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +index 77235314f4ccc28255b98f2bb52f553fe93313f3..5f316b09fb914ccb0782efe521ad85727f5dd02f 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +@@ -100,6 +100,11 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + @Override + protected void onHit(HitResult hitResult) { + super.onHit(hitResult); ++ // Paper start - More projectile API ++ this.splash(hitResult); ++ } ++ public void splash(@Nullable HitResult hitResult) { ++ // Paper end - More projectile API + if (!this.level().isClientSide) { + ItemStack itemstack = this.getItem(); + Potion potionregistry = PotionUtils.getPotion(itemstack); +@@ -113,7 +118,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + if (this.isLingering()) { + showParticles = this.makeAreaOfEffectCloud(itemstack, potionregistry, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper + } else { +- showParticles = this.applySplash(list, hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper ++ showParticles = this.applySplash(list, hitResult != null && hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API + } + } + +@@ -175,7 +180,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + + } + +- private boolean applySplash(List list, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events ++ private boolean applySplash(List list, @Nullable Entity entity, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events & More projectile API + AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); + List list1 = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb); + Map affected = new HashMap(); // CraftBukkit +@@ -252,7 +257,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + + } + +- private boolean makeAreaOfEffectCloud(ItemStack itemstack, Potion potionregistry, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - return boolean ++ private boolean makeAreaOfEffectCloud(ItemStack itemstack, Potion potionregistry, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - return boolean & More projectile API + AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ()); + Entity entity = this.getOwner(); + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java +index 91c2d0b40d3fca86938cd454e1415a4eea3df7c7..c1c52f4fc5f900fac4098e5e37c52dfc4e82b8bb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java +@@ -17,4 +17,65 @@ public abstract class AbstractProjectile extends CraftEntity implements Projecti + @Override + public void setBounce(boolean doesBounce) {} + ++ // Paper start - More projectile API ++ @Override ++ public boolean hasLeftShooter() { ++ return this.getHandle().leftOwner; ++ } ++ ++ @Override ++ public void setHasLeftShooter(boolean leftShooter) { ++ this.getHandle().leftOwner = leftShooter; ++ } ++ ++ @Override ++ public boolean hasBeenShot() { ++ return this.getHandle().hasBeenShot; ++ } ++ ++ @Override ++ public void setHasBeenShot(boolean beenShot) { ++ this.getHandle().hasBeenShot = beenShot; ++ } ++ ++ @Override ++ public boolean canHitEntity(org.bukkit.entity.Entity entity) { ++ return this.getHandle().canHitEntity(((CraftEntity) entity).getHandle()); ++ } ++ ++ @Override ++ public void hitEntity(org.bukkit.entity.Entity entity) { ++ this.getHandle().preOnHit(new net.minecraft.world.phys.EntityHitResult(((CraftEntity) entity).getHandle())); ++ } ++ ++ @Override ++ public void hitEntity(org.bukkit.entity.Entity entity, org.bukkit.util.Vector vector) { ++ this.getHandle().preOnHit(new net.minecraft.world.phys.EntityHitResult(((CraftEntity) entity).getHandle(), new net.minecraft.world.phys.Vec3(vector.getX(), vector.getY(), vector.getZ()))); ++ } ++ ++ @Override ++ public net.minecraft.world.entity.projectile.Projectile getHandle() { ++ return (net.minecraft.world.entity.projectile.Projectile) entity; ++ } ++ ++ @Override ++ public final org.bukkit.projectiles.ProjectileSource getShooter() { ++ return this.getHandle().projectileSource; ++ } ++ ++ @Override ++ public final void setShooter(org.bukkit.projectiles.ProjectileSource shooter) { ++ if (shooter instanceof CraftEntity craftEntity) { ++ this.getHandle().setOwner(craftEntity.getHandle()); ++ } else { ++ this.getHandle().setOwner(null); ++ } ++ this.getHandle().projectileSource = shooter; ++ } ++ ++ @Override ++ public java.util.UUID getOwnerUniqueId() { ++ return this.getHandle().ownerUUID; ++ } ++ // Paper end - More projectile API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java +index 762c395e45a681a11f3fe9d10e7f0ba310786e80..6d2fe30742f8b41d53dd2cbff120fcc042ea0e0c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java +@@ -58,20 +58,7 @@ public class CraftArrow extends AbstractProjectile implements AbstractArrow { + this.getHandle().setCritArrow(critical); + } + +- @Override +- public ProjectileSource getShooter() { +- return this.getHandle().projectileSource; +- } +- +- @Override +- public void setShooter(ProjectileSource shooter) { +- if (shooter instanceof Entity) { +- this.getHandle().setOwner(((CraftEntity) shooter).getHandle()); +- } else { +- this.getHandle().setOwner(null); +- } +- this.getHandle().projectileSource = shooter; +- } ++ // Paper - moved to AbstractProjectile + + @Override + public boolean isInBlock() { +@@ -105,6 +92,27 @@ public class CraftArrow extends AbstractProjectile implements AbstractArrow { + return org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(getHandle().getPickupItem()); + } + ++ @Override ++ public void setLifetimeTicks(int ticks) { ++ this.getHandle().life = ticks; ++ } ++ ++ @Override ++ public int getLifetimeTicks() { ++ return this.getHandle().life; ++ } ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public org.bukkit.Sound getHitSound() { ++ return org.bukkit.craftbukkit.CraftSound.minecraftToBukkit(this.getHandle().soundEvent); ++ } ++ ++ @Override ++ public void setHitSound(@org.jetbrains.annotations.NotNull org.bukkit.Sound sound) { ++ this.getHandle().setSoundEvent(org.bukkit.craftbukkit.CraftSound.bukkitToMinecraft(sound)); ++ } ++ + @Override + public void setNoPhysics(boolean noPhysics) { + this.getHandle().setNoPhysics(noPhysics); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java +index 73cb7aa01af3eed71b05b1a539f082b26dcd8d60..2783e218d5e5c24787429237974e196761f4d02b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java +@@ -32,20 +32,7 @@ public class CraftFireball extends AbstractProjectile implements Fireball { + this.getHandle().bukkitYield = yield; + } + +- @Override +- public ProjectileSource getShooter() { +- return this.getHandle().projectileSource; +- } +- +- @Override +- public void setShooter(ProjectileSource shooter) { +- if (shooter instanceof CraftLivingEntity) { +- this.getHandle().setOwner(((CraftLivingEntity) shooter).getHandle()); +- } else { +- this.getHandle().setOwner(null); +- } +- this.getHandle().projectileSource = shooter; +- } ++ // Paper - moved to AbstractProjectile + + @Override + public Vector getDirection() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +index c9e15a9d82dee935293b2e7e233f5b9b2d822448..fedbfbac02b73382aacc69f8a1e5a3e746c55ea2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +@@ -15,24 +15,26 @@ import org.bukkit.inventory.meta.FireworkMeta; + public class CraftFirework extends CraftProjectile implements Firework { + + private final Random random = new Random(); +- private final CraftItemStack item; ++ //private CraftItemStack item; // Paper - Remove usage, not accurate representation of current item. + + public CraftFirework(CraftServer server, FireworkRocketEntity entity) { + super(server, entity); + +- ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM); +- +- if (item.isEmpty()) { +- item = new ItemStack(Items.FIREWORK_ROCKET); +- this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); +- } +- +- this.item = CraftItemStack.asCraftMirror(item); +- +- // Ensure the item is a firework... +- if (this.item.getType() != Material.FIREWORK_ROCKET) { +- this.item.setType(Material.FIREWORK_ROCKET); +- } ++// Paper start - Expose firework item directly ++// ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM); ++// ++// if (item.isEmpty()) { ++// item = new ItemStack(Items.FIREWORK_ROCKET); ++// this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); ++// } ++// ++// this.item = CraftItemStack.asCraftMirror(item); ++// ++// // Ensure the item is a firework... ++// if (this.item.getType() != Material.FIREWORK_ROCKET) { ++// this.item.setType(Material.FIREWORK_ROCKET); ++// } ++ // Paper end - Expose firework item directly + } + + @Override +@@ -47,12 +49,12 @@ public class CraftFirework extends CraftProjectile implements Firework { + + @Override + public FireworkMeta getFireworkMeta() { +- return (FireworkMeta) this.item.getItemMeta(); ++ return (FireworkMeta) CraftItemStack.getItemMeta(this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM), Material.FIREWORK_ROCKET); // Paper - Expose firework item directly + } + + @Override + public void setFireworkMeta(FireworkMeta meta) { +- this.item.setItemMeta(meta); ++ applyFireworkEffect(meta); // Paper - Expose firework item directly + + // Copied from EntityFireworks constructor, update firework lifetime/power + this.getHandle().lifetime = 10 * (1 + meta.getPower()) + this.random.nextInt(6) + this.random.nextInt(7); +@@ -136,4 +138,46 @@ public class CraftFirework extends CraftProjectile implements Firework { + return getHandle().spawningEntity; + } + // Paper end ++ // Paper start - Expose firework item directly + manually setting flight ++ @Override ++ public org.bukkit.inventory.ItemStack getItem() { ++ return CraftItemStack.asBukkitCopy(this.getHandle().getItem()); ++ } ++ ++ @Override ++ public void setItem(org.bukkit.inventory.ItemStack itemStack) { ++ FireworkMeta meta = getFireworkMeta(); ++ ItemStack nmsItem = itemStack == null ? ItemStack.EMPTY : CraftItemStack.asNMSCopy(itemStack); ++ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, nmsItem); ++ ++ applyFireworkEffect(meta); ++ } ++ ++ @Override ++ public int getTicksFlown() { ++ return this.getHandle().life; ++ } ++ ++ @Override ++ public void setTicksFlown(int ticks) { ++ this.getHandle().life = ticks; ++ } ++ ++ @Override ++ public int getTicksToDetonate() { ++ return this.getHandle().lifetime; ++ } ++ ++ @Override ++ public void setTicksToDetonate(int ticks) { ++ this.getHandle().lifetime = ticks; ++ } ++ ++ void applyFireworkEffect(FireworkMeta meta) { ++ ItemStack item = this.getHandle().getItem(); ++ CraftItemStack.applyMetaToItem(item, meta); ++ ++ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); ++ } ++ // Paper end - Expose firework item directly + manually setting flight + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java +index 6e2f91423371ead9890095cf4b1e2299c4dcba28..ad1aeea80877f2cdb9e8ad9c5b46f95dd76b3335 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java +@@ -196,4 +196,15 @@ public class CraftFishHook extends CraftProjectile implements FishHook { + public HookState getState() { + return HookState.values()[this.getHandle().currentState.ordinal()]; + } ++ // Paper start - More FishHook API ++ @Override ++ public int getWaitTime() { ++ return this.getHandle().timeUntilLured; ++ } ++ ++ @Override ++ public void setWaitTime(int ticks) { ++ this.getHandle().timeUntilLured = ticks; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java +index 70cbc6c668c60e9d608ca7013b72f9b916c05c2d..47633f05b4fab1dcabc2117e7645fe6d6949622a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java +@@ -20,13 +20,5 @@ public class CraftLlamaSpit extends AbstractProjectile implements LlamaSpit { + return "CraftLlamaSpit"; + } + +- @Override +- public ProjectileSource getShooter() { +- return (this.getHandle().getOwner() != null) ? (ProjectileSource) this.getHandle().getOwner().getBukkitEntity() : null; +- } +- +- @Override +- public void setShooter(ProjectileSource source) { +- this.getHandle().setOwner((source != null) ? ((CraftLivingEntity) source).getHandle() : null); +- } ++ // Paper - moved to AbstractProjectile + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java +index 696fdfa723aa896a67946f862d7c6890f7f7ab17..4f1fa7dec78970bdfc184d3c1f1632dc9d75a574 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java +@@ -10,20 +10,7 @@ public abstract class CraftProjectile extends AbstractProjectile implements Proj + super(server, entity); + } + +- @Override +- public ProjectileSource getShooter() { +- return this.getHandle().projectileSource; +- } +- +- @Override +- public void setShooter(ProjectileSource shooter) { +- if (shooter instanceof CraftLivingEntity) { +- this.getHandle().setOwner((LivingEntity) ((CraftLivingEntity) shooter).entity); +- } else { +- this.getHandle().setOwner(null); +- } +- this.getHandle().projectileSource = shooter; +- } ++ // Paper - moved to AbstractProjectile + + @Override + public net.minecraft.world.entity.projectile.Projectile getHandle() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java +index d685d09cae5f862c0004f148298c800736d2139e..636c4481e3afdf20197e502cf221f5d34d18f101 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java +@@ -12,20 +12,7 @@ public class CraftShulkerBullet extends AbstractProjectile implements ShulkerBul + super(server, entity); + } + +- @Override +- public ProjectileSource getShooter() { +- return this.getHandle().projectileSource; +- } +- +- @Override +- public void setShooter(ProjectileSource shooter) { +- if (shooter instanceof Entity) { +- this.getHandle().setOwner(((CraftEntity) shooter).getHandle()); +- } else { +- this.getHandle().setOwner(null); +- } +- this.getHandle().projectileSource = shooter; +- } ++ // Paper - moved to AbstractProjectile + + @Override + public org.bukkit.entity.Entity getTarget() { +@@ -39,6 +26,40 @@ public class CraftShulkerBullet extends AbstractProjectile implements ShulkerBul + this.getHandle().setTarget(target == null ? null : ((CraftEntity) target).getHandle()); + } + ++ @Override ++ public org.bukkit.util.Vector getTargetDelta() { ++ net.minecraft.world.entity.projectile.ShulkerBullet bullet = this.getHandle(); ++ return new org.bukkit.util.Vector(bullet.targetDeltaX, bullet.targetDeltaY, bullet.targetDeltaZ); ++ } ++ ++ @Override ++ public void setTargetDelta(org.bukkit.util.Vector vector) { ++ net.minecraft.world.entity.projectile.ShulkerBullet bullet = this.getHandle(); ++ bullet.targetDeltaX = vector.getX(); ++ bullet.targetDeltaY = vector.getY(); ++ bullet.targetDeltaZ = vector.getZ(); ++ } ++ ++ @Override ++ public org.bukkit.block.BlockFace getCurrentMovementDirection() { ++ return org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(this.getHandle().currentMoveDirection); ++ } ++ ++ @Override ++ public void setCurrentMovementDirection(org.bukkit.block.BlockFace movementDirection) { ++ this.getHandle().currentMoveDirection = org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(movementDirection); ++ } ++ ++ @Override ++ public int getFlightSteps() { ++ return this.getHandle().flightSteps; ++ } ++ ++ @Override ++ public void setFlightSteps(int steps) { ++ this.getHandle().flightSteps = steps; ++ } ++ + @Override + public String toString() { + return "CraftShulkerBullet"; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java +index 269af20a6d0d100909a0aea0bdba307ea0658f3e..d5f1681a476c8fe2ae128a84910f4bf04063b75a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java +@@ -35,11 +35,31 @@ public class CraftThrownPotion extends CraftThrowableProjectile implements Throw + @Override + public void setItem(ItemStack item) { + Preconditions.checkArgument(item != null, "ItemStack cannot be null"); +- Preconditions.checkArgument(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack material must be Material.LINGERING_POTION or Material.SPLASH_POTION but was Material.%s", item.getType()); ++ // Preconditions.checkArgument(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack material must be Material.LINGERING_POTION or Material.SPLASH_POTION but was Material.%s", item.getType()); // Paper - Projectile API ++ org.bukkit.inventory.meta.PotionMeta meta = (item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION) ? null : this.getPotionMeta(); // Paper - Projectile API + + this.getHandle().setItem(CraftItemStack.asNMSCopy(item)); ++ if (meta != null) this.setPotionMeta(meta); // Paper - Projectile API + } + ++ // Paper start - Projectile API ++ @Override ++ public org.bukkit.inventory.meta.PotionMeta getPotionMeta() { ++ return (org.bukkit.inventory.meta.PotionMeta) CraftItemStack.getItemMeta(this.getHandle().getItemRaw(), Material.SPLASH_POTION); ++ } ++ ++ @Override ++ public void setPotionMeta(org.bukkit.inventory.meta.PotionMeta meta) { ++ net.minecraft.world.item.ItemStack item = this.getHandle().getItem(); ++ CraftItemStack.applyMetaToItem(item, meta); ++ this.getHandle().setItem(item); // Reset item ++ } ++ ++ @Override ++ public void splash() { ++ this.getHandle().splash(null); ++ } ++ // Paper end + @Override + public net.minecraft.world.entity.projectile.ThrownPotion getHandle() { + return (net.minecraft.world.entity.projectile.ThrownPotion) this.entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java +index 20f9735c7cb76024e15dbdca7684f5c560876175..8a6af0db8e0aa0cffbf19584be747076c2c8ee44 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java +@@ -53,5 +53,15 @@ public class CraftTrident extends CraftArrow implements Trident { + com.google.common.base.Preconditions.checkArgument(loyaltyLevel >= 0 && loyaltyLevel <= 127, "The loyalty level has to be between 0 and 127"); + this.getHandle().setLoyalty((byte) loyaltyLevel); + } ++ ++ @Override ++ public boolean hasDealtDamage() { ++ return this.getHandle().dealtDamage; ++ } ++ ++ @Override ++ public void setHasDealtDamage(boolean hasDealtDamage) { ++ this.getHandle().dealtDamage = hasDealtDamage; ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index cbac5fa3864e7b2298f75a44f6ef3f0000c9e765..a30626e25272769caf06caa69d32e2d3f9211710 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -831,19 +831,19 @@ public class CraftEventFactory { + /** + * PotionSplashEvent + */ +- public static PotionSplashEvent callPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, HitResult position, Map affectedEntities) { ++ public static PotionSplashEvent callPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult position, Map affectedEntities) { // Paper - nullable hitResult + ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); + + Block hitBlock = null; + BlockFace hitFace = null; +- if (position.getType() == HitResult.Type.BLOCK) { ++ if (position != null && position.getType() == HitResult.Type.BLOCK) { // Paper - nullable hitResult + BlockHitResult positionBlock = (BlockHitResult) position; + hitBlock = CraftBlock.at(potion.level(), positionBlock.getBlockPos()); + hitFace = CraftBlock.notchToBlockFace(positionBlock.getDirection()); + } + + org.bukkit.entity.Entity hitEntity = null; +- if (position.getType() == HitResult.Type.ENTITY) { ++ if (position != null && position.getType() == HitResult.Type.ENTITY) { // Paper - nullable hitResult + hitEntity = ((EntityHitResult) position).getEntity().getBukkitEntity(); + } + +@@ -852,20 +852,20 @@ public class CraftEventFactory { + return event; + } + +- public static LingeringPotionSplashEvent callLingeringPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, HitResult position, net.minecraft.world.entity.AreaEffectCloud cloud) { ++ public static LingeringPotionSplashEvent callLingeringPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult position, net.minecraft.world.entity.AreaEffectCloud cloud) { // Paper - nullable hitResult + ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); + AreaEffectCloud effectCloud = (AreaEffectCloud) cloud.getBukkitEntity(); + + Block hitBlock = null; + BlockFace hitFace = null; +- if (position.getType() == HitResult.Type.BLOCK) { ++ if (position != null && position.getType() == HitResult.Type.BLOCK) { // Paper + BlockHitResult positionBlock = (BlockHitResult) position; + hitBlock = CraftBlock.at(potion.level(), positionBlock.getBlockPos()); + hitFace = CraftBlock.notchToBlockFace(positionBlock.getDirection()); + } + + org.bukkit.entity.Entity hitEntity = null; +- if (position.getType() == HitResult.Type.ENTITY) { ++ if (position != null && position.getType() == HitResult.Type.ENTITY) { // Paper + hitEntity = ((EntityHitResult) position).getEntity().getBukkitEntity(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 7ce48473222516aefda3c5ad40e5e3fd23502e3d..6725c0824b986885c8aade846f6e159986ffbe59 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -294,12 +294,20 @@ public final class CraftItemStack extends ItemStack { + public ItemMeta getItemMeta() { + return CraftItemStack.getItemMeta(this.handle); + } ++ // Paper start ++ public static void applyMetaToItem(net.minecraft.world.item.ItemStack itemStack, ItemMeta meta) { ++ ((org.bukkit.craftbukkit.inventory.CraftMetaItem) meta).applyToItem(itemStack.getOrCreateTag()); ++ } + + public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item) { ++ return getItemMeta(item, CraftItemStack.getType(item)); ++ } ++ public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item, Material material) { ++ // Paper end + if (!CraftItemStack.hasItemMeta(item)) { +- return CraftItemFactory.instance().getItemMeta(CraftItemStack.getType(item)); ++ return CraftItemFactory.instance().getItemMeta(material); // Paper + } +- switch (CraftItemStack.getType(item)) { ++ switch (material) { // Paper + case WRITTEN_BOOK: + return new CraftMetaBookSigned(item.getTag()); + case WRITABLE_BOOK: diff --git a/patches/server/0721-Fix-swamp-hut-cat-generation-deadlock.patch b/patches/server/0719-Fix-swamp-hut-cat-generation-deadlock.patch similarity index 100% rename from patches/server/0721-Fix-swamp-hut-cat-generation-deadlock.patch rename to patches/server/0719-Fix-swamp-hut-cat-generation-deadlock.patch diff --git a/patches/server/0720-Don-t-allow-vehicle-movement-from-players-while-tele.patch b/patches/server/0720-Don-t-allow-vehicle-movement-from-players-while-tele.patch new file mode 100644 index 000000000000..8d3a98a3689f --- /dev/null +++ b/patches/server/0720-Don-t-allow-vehicle-movement-from-players-while-tele.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 14 Mar 2022 12:35:37 -0700 +Subject: [PATCH] Don't allow vehicle movement from players while teleporting + +Bring the vehicle move packet behavior in line with the +regular player move packet. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 1fb3e89d6f7f9d048f083e80b1259480d4e67ef9..cd79aa53ec49c80ee3ddf79b7161637e66b688fd 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -464,6 +464,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause + } else { + Entity entity = this.player.getRootVehicle(); ++ // Paper start - Don't allow vehicle movement from players while teleporting ++ if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) { ++ return; ++ } ++ // Paper end - Don't allow vehicle movement from players while teleporting + + if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) { + ServerLevel worldserver = this.player.serverLevel(); diff --git a/patches/server/0720-More-Projectile-API.patch b/patches/server/0720-More-Projectile-API.patch deleted file mode 100644 index e2ba7b8b135b..000000000000 --- a/patches/server/0720-More-Projectile-API.patch +++ /dev/null @@ -1,588 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Tue, 22 Jun 2021 23:41:11 -0400 -Subject: [PATCH] More Projectile API - -== AT == -public net.minecraft.world.entity.projectile.FishingHook timeUntilLured -public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaX -public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaY -public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaZ -public net.minecraft.world.entity.projectile.ShulkerBullet currentMoveDirection -public net.minecraft.world.entity.projectile.ShulkerBullet flightSteps -public net.minecraft.world.entity.projectile.AbstractArrow soundEvent -public net.minecraft.world.entity.projectile.ThrownTrident dealtDamage -public net.minecraft.world.entity.projectile.Projectile hasBeenShot -public net.minecraft.world.entity.projectile.Projectile leftOwner -public net.minecraft.world.entity.projectile.Projectile preOnHit(Lnet/minecraft/world/phys/HitResult;)V -public net.minecraft.world.entity.projectile.Projectile canHitEntity(Lnet/minecraft/world/entity/Entity;)Z - -Co-authored-by: Nassim Jahnke - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -index 77235314f4ccc28255b98f2bb52f553fe93313f3..5f316b09fb914ccb0782efe521ad85727f5dd02f 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -@@ -100,6 +100,11 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - @Override - protected void onHit(HitResult hitResult) { - super.onHit(hitResult); -+ // Paper start - More projectile API -+ this.splash(hitResult); -+ } -+ public void splash(@Nullable HitResult hitResult) { -+ // Paper end - More projectile API - if (!this.level().isClientSide) { - ItemStack itemstack = this.getItem(); - Potion potionregistry = PotionUtils.getPotion(itemstack); -@@ -113,7 +118,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - if (this.isLingering()) { - showParticles = this.makeAreaOfEffectCloud(itemstack, potionregistry, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper - } else { -- showParticles = this.applySplash(list, hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper -+ showParticles = this.applySplash(list, hitResult != null && hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API - } - } - -@@ -175,7 +180,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - - } - -- private boolean applySplash(List list, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events -+ private boolean applySplash(List list, @Nullable Entity entity, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events & More projectile API - AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); - List list1 = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb); - Map affected = new HashMap(); // CraftBukkit -@@ -252,7 +257,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - - } - -- private boolean makeAreaOfEffectCloud(ItemStack itemstack, Potion potionregistry, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - return boolean -+ private boolean makeAreaOfEffectCloud(ItemStack itemstack, Potion potionregistry, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - return boolean & More projectile API - AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ()); - Entity entity = this.getOwner(); - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -index 91c2d0b40d3fca86938cd454e1415a4eea3df7c7..c1c52f4fc5f900fac4098e5e37c52dfc4e82b8bb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -@@ -17,4 +17,65 @@ public abstract class AbstractProjectile extends CraftEntity implements Projecti - @Override - public void setBounce(boolean doesBounce) {} - -+ // Paper start - More projectile API -+ @Override -+ public boolean hasLeftShooter() { -+ return this.getHandle().leftOwner; -+ } -+ -+ @Override -+ public void setHasLeftShooter(boolean leftShooter) { -+ this.getHandle().leftOwner = leftShooter; -+ } -+ -+ @Override -+ public boolean hasBeenShot() { -+ return this.getHandle().hasBeenShot; -+ } -+ -+ @Override -+ public void setHasBeenShot(boolean beenShot) { -+ this.getHandle().hasBeenShot = beenShot; -+ } -+ -+ @Override -+ public boolean canHitEntity(org.bukkit.entity.Entity entity) { -+ return this.getHandle().canHitEntity(((CraftEntity) entity).getHandle()); -+ } -+ -+ @Override -+ public void hitEntity(org.bukkit.entity.Entity entity) { -+ this.getHandle().preOnHit(new net.minecraft.world.phys.EntityHitResult(((CraftEntity) entity).getHandle())); -+ } -+ -+ @Override -+ public void hitEntity(org.bukkit.entity.Entity entity, org.bukkit.util.Vector vector) { -+ this.getHandle().preOnHit(new net.minecraft.world.phys.EntityHitResult(((CraftEntity) entity).getHandle(), new net.minecraft.world.phys.Vec3(vector.getX(), vector.getY(), vector.getZ()))); -+ } -+ -+ @Override -+ public net.minecraft.world.entity.projectile.Projectile getHandle() { -+ return (net.minecraft.world.entity.projectile.Projectile) entity; -+ } -+ -+ @Override -+ public final org.bukkit.projectiles.ProjectileSource getShooter() { -+ return this.getHandle().projectileSource; -+ } -+ -+ @Override -+ public final void setShooter(org.bukkit.projectiles.ProjectileSource shooter) { -+ if (shooter instanceof CraftEntity craftEntity) { -+ this.getHandle().setOwner(craftEntity.getHandle()); -+ } else { -+ this.getHandle().setOwner(null); -+ } -+ this.getHandle().projectileSource = shooter; -+ } -+ -+ @Override -+ public java.util.UUID getOwnerUniqueId() { -+ return this.getHandle().ownerUUID; -+ } -+ // Paper end - More projectile API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java -index 762c395e45a681a11f3fe9d10e7f0ba310786e80..6d2fe30742f8b41d53dd2cbff120fcc042ea0e0c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java -@@ -58,20 +58,7 @@ public class CraftArrow extends AbstractProjectile implements AbstractArrow { - this.getHandle().setCritArrow(critical); - } - -- @Override -- public ProjectileSource getShooter() { -- return this.getHandle().projectileSource; -- } -- -- @Override -- public void setShooter(ProjectileSource shooter) { -- if (shooter instanceof Entity) { -- this.getHandle().setOwner(((CraftEntity) shooter).getHandle()); -- } else { -- this.getHandle().setOwner(null); -- } -- this.getHandle().projectileSource = shooter; -- } -+ // Paper - moved to AbstractProjectile - - @Override - public boolean isInBlock() { -@@ -105,6 +92,27 @@ public class CraftArrow extends AbstractProjectile implements AbstractArrow { - return org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(getHandle().getPickupItem()); - } - -+ @Override -+ public void setLifetimeTicks(int ticks) { -+ this.getHandle().life = ticks; -+ } -+ -+ @Override -+ public int getLifetimeTicks() { -+ return this.getHandle().life; -+ } -+ -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public org.bukkit.Sound getHitSound() { -+ return org.bukkit.craftbukkit.CraftSound.minecraftToBukkit(this.getHandle().soundEvent); -+ } -+ -+ @Override -+ public void setHitSound(@org.jetbrains.annotations.NotNull org.bukkit.Sound sound) { -+ this.getHandle().setSoundEvent(org.bukkit.craftbukkit.CraftSound.bukkitToMinecraft(sound)); -+ } -+ - @Override - public void setNoPhysics(boolean noPhysics) { - this.getHandle().setNoPhysics(noPhysics); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java -index 73cb7aa01af3eed71b05b1a539f082b26dcd8d60..2783e218d5e5c24787429237974e196761f4d02b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java -@@ -32,20 +32,7 @@ public class CraftFireball extends AbstractProjectile implements Fireball { - this.getHandle().bukkitYield = yield; - } - -- @Override -- public ProjectileSource getShooter() { -- return this.getHandle().projectileSource; -- } -- -- @Override -- public void setShooter(ProjectileSource shooter) { -- if (shooter instanceof CraftLivingEntity) { -- this.getHandle().setOwner(((CraftLivingEntity) shooter).getHandle()); -- } else { -- this.getHandle().setOwner(null); -- } -- this.getHandle().projectileSource = shooter; -- } -+ // Paper - moved to AbstractProjectile - - @Override - public Vector getDirection() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -index c9e15a9d82dee935293b2e7e233f5b9b2d822448..fedbfbac02b73382aacc69f8a1e5a3e746c55ea2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -@@ -15,24 +15,26 @@ import org.bukkit.inventory.meta.FireworkMeta; - public class CraftFirework extends CraftProjectile implements Firework { - - private final Random random = new Random(); -- private final CraftItemStack item; -+ //private CraftItemStack item; // Paper - Remove usage, not accurate representation of current item. - - public CraftFirework(CraftServer server, FireworkRocketEntity entity) { - super(server, entity); - -- ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM); -- -- if (item.isEmpty()) { -- item = new ItemStack(Items.FIREWORK_ROCKET); -- this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); -- } -- -- this.item = CraftItemStack.asCraftMirror(item); -- -- // Ensure the item is a firework... -- if (this.item.getType() != Material.FIREWORK_ROCKET) { -- this.item.setType(Material.FIREWORK_ROCKET); -- } -+// Paper start - Expose firework item directly -+// ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM); -+// -+// if (item.isEmpty()) { -+// item = new ItemStack(Items.FIREWORK_ROCKET); -+// this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); -+// } -+// -+// this.item = CraftItemStack.asCraftMirror(item); -+// -+// // Ensure the item is a firework... -+// if (this.item.getType() != Material.FIREWORK_ROCKET) { -+// this.item.setType(Material.FIREWORK_ROCKET); -+// } -+ // Paper end - Expose firework item directly - } - - @Override -@@ -47,12 +49,12 @@ public class CraftFirework extends CraftProjectile implements Firework { - - @Override - public FireworkMeta getFireworkMeta() { -- return (FireworkMeta) this.item.getItemMeta(); -+ return (FireworkMeta) CraftItemStack.getItemMeta(this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM), Material.FIREWORK_ROCKET); // Paper - Expose firework item directly - } - - @Override - public void setFireworkMeta(FireworkMeta meta) { -- this.item.setItemMeta(meta); -+ applyFireworkEffect(meta); // Paper - Expose firework item directly - - // Copied from EntityFireworks constructor, update firework lifetime/power - this.getHandle().lifetime = 10 * (1 + meta.getPower()) + this.random.nextInt(6) + this.random.nextInt(7); -@@ -136,4 +138,46 @@ public class CraftFirework extends CraftProjectile implements Firework { - return getHandle().spawningEntity; - } - // Paper end -+ // Paper start - Expose firework item directly + manually setting flight -+ @Override -+ public org.bukkit.inventory.ItemStack getItem() { -+ return CraftItemStack.asBukkitCopy(this.getHandle().getItem()); -+ } -+ -+ @Override -+ public void setItem(org.bukkit.inventory.ItemStack itemStack) { -+ FireworkMeta meta = getFireworkMeta(); -+ ItemStack nmsItem = itemStack == null ? ItemStack.EMPTY : CraftItemStack.asNMSCopy(itemStack); -+ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, nmsItem); -+ -+ applyFireworkEffect(meta); -+ } -+ -+ @Override -+ public int getTicksFlown() { -+ return this.getHandle().life; -+ } -+ -+ @Override -+ public void setTicksFlown(int ticks) { -+ this.getHandle().life = ticks; -+ } -+ -+ @Override -+ public int getTicksToDetonate() { -+ return this.getHandle().lifetime; -+ } -+ -+ @Override -+ public void setTicksToDetonate(int ticks) { -+ this.getHandle().lifetime = ticks; -+ } -+ -+ void applyFireworkEffect(FireworkMeta meta) { -+ ItemStack item = this.getHandle().getItem(); -+ CraftItemStack.applyMetaToItem(item, meta); -+ -+ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); -+ } -+ // Paper end - Expose firework item directly + manually setting flight - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java -index 6e2f91423371ead9890095cf4b1e2299c4dcba28..ad1aeea80877f2cdb9e8ad9c5b46f95dd76b3335 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java -@@ -196,4 +196,15 @@ public class CraftFishHook extends CraftProjectile implements FishHook { - public HookState getState() { - return HookState.values()[this.getHandle().currentState.ordinal()]; - } -+ // Paper start - More FishHook API -+ @Override -+ public int getWaitTime() { -+ return this.getHandle().timeUntilLured; -+ } -+ -+ @Override -+ public void setWaitTime(int ticks) { -+ this.getHandle().timeUntilLured = ticks; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java -index 70cbc6c668c60e9d608ca7013b72f9b916c05c2d..47633f05b4fab1dcabc2117e7645fe6d6949622a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java -@@ -20,13 +20,5 @@ public class CraftLlamaSpit extends AbstractProjectile implements LlamaSpit { - return "CraftLlamaSpit"; - } - -- @Override -- public ProjectileSource getShooter() { -- return (this.getHandle().getOwner() != null) ? (ProjectileSource) this.getHandle().getOwner().getBukkitEntity() : null; -- } -- -- @Override -- public void setShooter(ProjectileSource source) { -- this.getHandle().setOwner((source != null) ? ((CraftLivingEntity) source).getHandle() : null); -- } -+ // Paper - moved to AbstractProjectile - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java -index 696fdfa723aa896a67946f862d7c6890f7f7ab17..4f1fa7dec78970bdfc184d3c1f1632dc9d75a574 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java -@@ -10,20 +10,7 @@ public abstract class CraftProjectile extends AbstractProjectile implements Proj - super(server, entity); - } - -- @Override -- public ProjectileSource getShooter() { -- return this.getHandle().projectileSource; -- } -- -- @Override -- public void setShooter(ProjectileSource shooter) { -- if (shooter instanceof CraftLivingEntity) { -- this.getHandle().setOwner((LivingEntity) ((CraftLivingEntity) shooter).entity); -- } else { -- this.getHandle().setOwner(null); -- } -- this.getHandle().projectileSource = shooter; -- } -+ // Paper - moved to AbstractProjectile - - @Override - public net.minecraft.world.entity.projectile.Projectile getHandle() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java -index d685d09cae5f862c0004f148298c800736d2139e..636c4481e3afdf20197e502cf221f5d34d18f101 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java -@@ -12,20 +12,7 @@ public class CraftShulkerBullet extends AbstractProjectile implements ShulkerBul - super(server, entity); - } - -- @Override -- public ProjectileSource getShooter() { -- return this.getHandle().projectileSource; -- } -- -- @Override -- public void setShooter(ProjectileSource shooter) { -- if (shooter instanceof Entity) { -- this.getHandle().setOwner(((CraftEntity) shooter).getHandle()); -- } else { -- this.getHandle().setOwner(null); -- } -- this.getHandle().projectileSource = shooter; -- } -+ // Paper - moved to AbstractProjectile - - @Override - public org.bukkit.entity.Entity getTarget() { -@@ -39,6 +26,40 @@ public class CraftShulkerBullet extends AbstractProjectile implements ShulkerBul - this.getHandle().setTarget(target == null ? null : ((CraftEntity) target).getHandle()); - } - -+ @Override -+ public org.bukkit.util.Vector getTargetDelta() { -+ net.minecraft.world.entity.projectile.ShulkerBullet bullet = this.getHandle(); -+ return new org.bukkit.util.Vector(bullet.targetDeltaX, bullet.targetDeltaY, bullet.targetDeltaZ); -+ } -+ -+ @Override -+ public void setTargetDelta(org.bukkit.util.Vector vector) { -+ net.minecraft.world.entity.projectile.ShulkerBullet bullet = this.getHandle(); -+ bullet.targetDeltaX = vector.getX(); -+ bullet.targetDeltaY = vector.getY(); -+ bullet.targetDeltaZ = vector.getZ(); -+ } -+ -+ @Override -+ public org.bukkit.block.BlockFace getCurrentMovementDirection() { -+ return org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(this.getHandle().currentMoveDirection); -+ } -+ -+ @Override -+ public void setCurrentMovementDirection(org.bukkit.block.BlockFace movementDirection) { -+ this.getHandle().currentMoveDirection = org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(movementDirection); -+ } -+ -+ @Override -+ public int getFlightSteps() { -+ return this.getHandle().flightSteps; -+ } -+ -+ @Override -+ public void setFlightSteps(int steps) { -+ this.getHandle().flightSteps = steps; -+ } -+ - @Override - public String toString() { - return "CraftShulkerBullet"; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -index 269af20a6d0d100909a0aea0bdba307ea0658f3e..d5f1681a476c8fe2ae128a84910f4bf04063b75a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -@@ -35,11 +35,31 @@ public class CraftThrownPotion extends CraftThrowableProjectile implements Throw - @Override - public void setItem(ItemStack item) { - Preconditions.checkArgument(item != null, "ItemStack cannot be null"); -- Preconditions.checkArgument(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack material must be Material.LINGERING_POTION or Material.SPLASH_POTION but was Material.%s", item.getType()); -+ // Preconditions.checkArgument(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack material must be Material.LINGERING_POTION or Material.SPLASH_POTION but was Material.%s", item.getType()); // Paper - Projectile API -+ org.bukkit.inventory.meta.PotionMeta meta = (item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION) ? null : this.getPotionMeta(); // Paper - Projectile API - - this.getHandle().setItem(CraftItemStack.asNMSCopy(item)); -+ if (meta != null) this.setPotionMeta(meta); // Paper - Projectile API - } - -+ // Paper start - Projectile API -+ @Override -+ public org.bukkit.inventory.meta.PotionMeta getPotionMeta() { -+ return (org.bukkit.inventory.meta.PotionMeta) CraftItemStack.getItemMeta(this.getHandle().getItemRaw(), Material.SPLASH_POTION); -+ } -+ -+ @Override -+ public void setPotionMeta(org.bukkit.inventory.meta.PotionMeta meta) { -+ net.minecraft.world.item.ItemStack item = this.getHandle().getItem(); -+ CraftItemStack.applyMetaToItem(item, meta); -+ this.getHandle().setItem(item); // Reset item -+ } -+ -+ @Override -+ public void splash() { -+ this.getHandle().splash(null); -+ } -+ // Paper end - @Override - public net.minecraft.world.entity.projectile.ThrownPotion getHandle() { - return (net.minecraft.world.entity.projectile.ThrownPotion) this.entity; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -index 20f9735c7cb76024e15dbdca7684f5c560876175..8a6af0db8e0aa0cffbf19584be747076c2c8ee44 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -@@ -53,5 +53,15 @@ public class CraftTrident extends CraftArrow implements Trident { - com.google.common.base.Preconditions.checkArgument(loyaltyLevel >= 0 && loyaltyLevel <= 127, "The loyalty level has to be between 0 and 127"); - this.getHandle().setLoyalty((byte) loyaltyLevel); - } -+ -+ @Override -+ public boolean hasDealtDamage() { -+ return this.getHandle().dealtDamage; -+ } -+ -+ @Override -+ public void setHasDealtDamage(boolean hasDealtDamage) { -+ this.getHandle().dealtDamage = hasDealtDamage; -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index bc5966ced62aeeed784077517658d7f28550c449..1514eb294a2e191abb3afe7374c24b351a202c04 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -831,19 +831,19 @@ public class CraftEventFactory { - /** - * PotionSplashEvent - */ -- public static PotionSplashEvent callPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, HitResult position, Map affectedEntities) { -+ public static PotionSplashEvent callPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult position, Map affectedEntities) { // Paper - nullable hitResult - ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); - - Block hitBlock = null; - BlockFace hitFace = null; -- if (position.getType() == HitResult.Type.BLOCK) { -+ if (position != null && position.getType() == HitResult.Type.BLOCK) { // Paper - nullable hitResult - BlockHitResult positionBlock = (BlockHitResult) position; - hitBlock = CraftBlock.at(potion.level(), positionBlock.getBlockPos()); - hitFace = CraftBlock.notchToBlockFace(positionBlock.getDirection()); - } - - org.bukkit.entity.Entity hitEntity = null; -- if (position.getType() == HitResult.Type.ENTITY) { -+ if (position != null && position.getType() == HitResult.Type.ENTITY) { // Paper - nullable hitResult - hitEntity = ((EntityHitResult) position).getEntity().getBukkitEntity(); - } - -@@ -852,20 +852,20 @@ public class CraftEventFactory { - return event; - } - -- public static LingeringPotionSplashEvent callLingeringPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, HitResult position, net.minecraft.world.entity.AreaEffectCloud cloud) { -+ public static LingeringPotionSplashEvent callLingeringPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult position, net.minecraft.world.entity.AreaEffectCloud cloud) { // Paper - nullable hitResult - ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity(); - AreaEffectCloud effectCloud = (AreaEffectCloud) cloud.getBukkitEntity(); - - Block hitBlock = null; - BlockFace hitFace = null; -- if (position.getType() == HitResult.Type.BLOCK) { -+ if (position != null && position.getType() == HitResult.Type.BLOCK) { // Paper - BlockHitResult positionBlock = (BlockHitResult) position; - hitBlock = CraftBlock.at(potion.level(), positionBlock.getBlockPos()); - hitFace = CraftBlock.notchToBlockFace(positionBlock.getDirection()); - } - - org.bukkit.entity.Entity hitEntity = null; -- if (position.getType() == HitResult.Type.ENTITY) { -+ if (position != null && position.getType() == HitResult.Type.ENTITY) { // Paper - hitEntity = ((EntityHitResult) position).getEntity().getBukkitEntity(); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index 7ce48473222516aefda3c5ad40e5e3fd23502e3d..6725c0824b986885c8aade846f6e159986ffbe59 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -294,12 +294,20 @@ public final class CraftItemStack extends ItemStack { - public ItemMeta getItemMeta() { - return CraftItemStack.getItemMeta(this.handle); - } -+ // Paper start -+ public static void applyMetaToItem(net.minecraft.world.item.ItemStack itemStack, ItemMeta meta) { -+ ((org.bukkit.craftbukkit.inventory.CraftMetaItem) meta).applyToItem(itemStack.getOrCreateTag()); -+ } - - public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item) { -+ return getItemMeta(item, CraftItemStack.getType(item)); -+ } -+ public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item, Material material) { -+ // Paper end - if (!CraftItemStack.hasItemMeta(item)) { -- return CraftItemFactory.instance().getItemMeta(CraftItemStack.getType(item)); -+ return CraftItemFactory.instance().getItemMeta(material); // Paper - } -- switch (CraftItemStack.getType(item)) { -+ switch (material) { // Paper - case WRITTEN_BOOK: - return new CraftMetaBookSigned(item.getTag()); - case WRITABLE_BOOK: diff --git a/patches/server/0723-Implement-getComputedBiome-API.patch b/patches/server/0721-Implement-getComputedBiome-API.patch similarity index 100% rename from patches/server/0723-Implement-getComputedBiome-API.patch rename to patches/server/0721-Implement-getComputedBiome-API.patch diff --git a/patches/server/0722-Don-t-allow-vehicle-movement-from-players-while-tele.patch b/patches/server/0722-Don-t-allow-vehicle-movement-from-players-while-tele.patch deleted file mode 100644 index 3d79f799dd96..000000000000 --- a/patches/server/0722-Don-t-allow-vehicle-movement-from-players-while-tele.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 14 Mar 2022 12:35:37 -0700 -Subject: [PATCH] Don't allow vehicle movement from players while teleporting - -Bring the vehicle move packet behavior in line with the -regular player move packet. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 737bf4600b1565684a78d973e785b881d7ddd4ea..469b98f56124583fc9be37cfdac13edb89894814 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -464,6 +464,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause - } else { - Entity entity = this.player.getRootVehicle(); -+ // Paper start - Don't allow vehicle movement from players while teleporting -+ if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) { -+ return; -+ } -+ // Paper end - Don't allow vehicle movement from players while teleporting - - if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) { - ServerLevel worldserver = this.player.serverLevel(); diff --git a/patches/server/0724-Make-some-itemstacks-nonnull.patch b/patches/server/0722-Make-some-itemstacks-nonnull.patch similarity index 100% rename from patches/server/0724-Make-some-itemstacks-nonnull.patch rename to patches/server/0722-Make-some-itemstacks-nonnull.patch diff --git a/patches/server/0725-Implement-enchantWithLevels-API.patch b/patches/server/0723-Implement-enchantWithLevels-API.patch similarity index 100% rename from patches/server/0725-Implement-enchantWithLevels-API.patch rename to patches/server/0723-Implement-enchantWithLevels-API.patch diff --git a/patches/server/0724-Fix-saving-in-unloadWorld.patch b/patches/server/0724-Fix-saving-in-unloadWorld.patch new file mode 100644 index 000000000000..a844d1cd8e37 --- /dev/null +++ b/patches/server/0724-Fix-saving-in-unloadWorld.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Philip Kelley +Date: Wed, 16 Mar 2022 12:05:59 +0000 +Subject: [PATCH] Fix saving in unloadWorld + +Change savingDisabled to false to ensure ServerLevel's saving logic gets called when unloadWorld is called with save = true + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index d8bec75e7f5c034051839818f51cdae71863608c..713d36b8bcb3f39f7f0cff61532199416970bcf4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1343,7 +1343,7 @@ public final class CraftServer implements Server { + + try { + if (save) { +- handle.save(null, true, true); ++ handle.save(null, true, false); // Paper - Fix saving in unloadWorld + } + + handle.getChunkSource().close(save); diff --git a/patches/server/0727-Buffer-OOB-setBlock-calls.patch b/patches/server/0725-Buffer-OOB-setBlock-calls.patch similarity index 100% rename from patches/server/0727-Buffer-OOB-setBlock-calls.patch rename to patches/server/0725-Buffer-OOB-setBlock-calls.patch diff --git a/patches/server/0728-Add-TameableDeathMessageEvent.patch b/patches/server/0726-Add-TameableDeathMessageEvent.patch similarity index 100% rename from patches/server/0728-Add-TameableDeathMessageEvent.patch rename to patches/server/0726-Add-TameableDeathMessageEvent.patch diff --git a/patches/server/0726-Fix-saving-in-unloadWorld.patch b/patches/server/0726-Fix-saving-in-unloadWorld.patch deleted file mode 100644 index 5ab088fe94e4..000000000000 --- a/patches/server/0726-Fix-saving-in-unloadWorld.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Philip Kelley -Date: Wed, 16 Mar 2022 12:05:59 +0000 -Subject: [PATCH] Fix saving in unloadWorld - -Change savingDisabled to false to ensure ServerLevel's saving logic gets called when unloadWorld is called with save = true - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index a2eb5ca693d0239e9cf43f6bda78d9fd9e653e98..001afa881d8c84bfdb7329037d877bd88430b11b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1331,7 +1331,7 @@ public final class CraftServer implements Server { - - try { - if (save) { -- handle.save(null, true, true); -+ handle.save(null, true, false); // Paper - Fix saving in unloadWorld - } - - handle.getChunkSource().close(save); diff --git a/patches/server/0727-Fix-new-block-data-for-EntityChangeBlockEvent.patch b/patches/server/0727-Fix-new-block-data-for-EntityChangeBlockEvent.patch new file mode 100644 index 000000000000..ad1116447d56 --- /dev/null +++ b/patches/server/0727-Fix-new-block-data-for-EntityChangeBlockEvent.patch @@ -0,0 +1,215 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SoSeDiK +Date: Mon, 21 Mar 2022 20:00:53 +0200 +Subject: [PATCH] Fix new block data for EntityChangeBlockEvent + +Also standardizes how to handle EntityChangeBlockEvent before a removeBlock or destroyBlock +call. Always use 'state.getFluidState().createLegacyBlock()' to get the new state instead of +just using the 'air' state. + +Also fixes the new block data for EntityBreakDoorEvent (a sub-event from +EntityChangeBlockEvent) + +Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java +index b7abd8309a7d9744d3b3df9be8cad54f8909cc15..d3a2a6dee2d83b3df0ddc521c080f7d72b709461 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java +@@ -107,7 +107,7 @@ public class HarvestFarmland extends Behavior { + Block block1 = world.getBlockState(this.aboveFarmlandPos.below()).getBlock(); + + if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata)) { +- if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, Blocks.AIR.defaultBlockState())) { // CraftBukkit ++ if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state + world.destroyBlock(this.aboveFarmlandPos, true, entity); + } // CraftBukkit + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +index 4253b3b1263a7ae5a2f5f3a34674dfea615a81ea..784a894688f98f9d0368a36d456c5c94e1ee3695 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +@@ -72,7 +72,7 @@ public class BreakDoorGoal extends DoorInteractGoal { + + if (this.breakTime == this.getDoorBreakTime() && this.isValidDifficulty(this.mob.level().getDifficulty())) { + // CraftBukkit start +- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.mob, this.doorPos).isCancelled()) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.mob, this.doorPos, this.mob.level().getFluidState(this.doorPos).createLegacyBlock()).isCancelled()) { // Paper - fix wrong block state + this.start(); + return; + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +index f4bc556e245179d0a4710e5255dd289aaafdceb7..d802985f1431be4332c07f0dab88feebedea4ce2 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +@@ -67,8 +67,9 @@ public class EatBlockGoal extends Goal { + if (this.eatAnimationTick == this.adjustedTickDelay(4)) { + BlockPos blockposition = this.mob.blockPosition(); + +- if (EatBlockGoal.IS_TALL_GRASS.test(this.level.getBlockState(blockposition))) { +- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, Blocks.AIR.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit ++ final BlockState blockState = this.level.getBlockState(blockposition); // Paper - fix wrong block state ++ if (EatBlockGoal.IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state ++ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state + this.level.destroyBlock(blockposition, false); + } + +@@ -77,7 +78,7 @@ public class EatBlockGoal extends Goal { + BlockPos blockposition1 = blockposition.below(); + + if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) { +- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.AIR.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit ++ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state + this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState())); + this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2); + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java +index 6e1c67ad757e466d122badd547ee3f8421eba9ba..cf4859814a60468f683e3afe285b4934d35e9704 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java +@@ -580,7 +580,7 @@ public class Rabbit extends Animal implements VariantHolder { + + if (i == 0) { + // CraftBukkit start +- if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, Blocks.AIR.defaultBlockState())) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + return; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index 0a2c2b847dc516abf31870116056dbdbb22f31d9..45906d273e6d6ec20cf44b4d07efdac68752ee9b 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -374,7 +374,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob + + if (WitherBoss.canDestroy(iblockdata)) { + // CraftBukkit start +- if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, Blocks.AIR.defaultBlockState())) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + continue; + } + // CraftBukkit end +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 c360135b923aa8d1ed2c7caf97ede981cb605cf2..f33c03e81b7ff643741f56eea055e6af260de618 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +@@ -580,7 +580,7 @@ public class EnderMan extends Monster implements NeutralMob { + boolean flag = movingobjectpositionblock.getBlockPos().equals(blockposition); + + if (iblockdata.is(BlockTags.ENDERMAN_HOLDABLE) && flag) { +- if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, Blocks.AIR.defaultBlockState())) { // CraftBukkit - Place event ++ if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit - Place event // Paper - fix wrong block state + world.removeBlock(blockposition, false); + world.gameEvent(GameEvent.BLOCK_DESTROY, blockposition, GameEvent.Context.of(this.enderman, iblockdata)); + this.enderman.setCarriedBlock(iblockdata.getBlock().defaultBlockState()); +diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +index aac60e85cd6dba7d87f4a1663c2c62952521d112..151acc43c96b4545ce92d3d559d8e1591874b4b5 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +@@ -158,7 +158,7 @@ public class Ravager extends Raider { + + if (block instanceof LeavesBlock) { + // CraftBukkit start +- if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + continue; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java +index 6f452605e9dc9ebd9980eae9fdeea34417a37a88..2c60a3765d22909e73b660492410ab8456304b68 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java +@@ -181,7 +181,8 @@ public class Silverfish extends Monster { + + if (block instanceof InfestedBlock) { + // CraftBukkit start +- if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) { ++ BlockState afterState = world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? iblockdata.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)); // Paper - fix wrong block state ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, afterState)) { // Paper - fix wrong block state + continue; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +index 5f316b09fb914ccb0782efe521ad85727f5dd02f..f191b0e303a97b5406133454374ff9448aee1a5a 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +@@ -306,7 +306,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + + if (iblockdata.is(BlockTags.FIRE)) { + // CraftBukkit start +- if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, Blocks.AIR.defaultBlockState())) { ++ if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + this.level().destroyBlock(pos, false, this); + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java b/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java +index 87c66bf8fee56e77b25498d9b2524fe2b6fd6549..0ab1bbd7c8dc8e45f754434357898d8fc990a021 100644 +--- a/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java +@@ -284,7 +284,7 @@ public class ChorusFlowerBlock extends Block { + + if (!world.isClientSide && projectile.mayInteract(world, blockposition) && projectile.mayBreak(world)) { + // CraftBukkit +- if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState())) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + return; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java +index 998e43e8dc6bd6b741bdcb77d2b75df8ab2feefc..d0e679745a794228bf62a9aa59422776760f3867 100644 +--- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java +@@ -133,7 +133,7 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate + + if (projectile.mayInteract(world, blockposition) && projectile.mayBreak(world) && projectile instanceof ThrownTrident && projectile.getDeltaMovement().length() > 0.6D) { + // CraftBukkit start +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState())) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + return; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/level/block/TntBlock.java b/src/main/java/net/minecraft/world/level/block/TntBlock.java +index 4edd2e7bb62df65d6da8c8a623cf03e7e947bf75..5fa5e8c838720eb1491aea73d462f4bc7d779956 100644 +--- a/src/main/java/net/minecraft/world/level/block/TntBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TntBlock.java +@@ -163,7 +163,7 @@ public class TntBlock extends Block { + + if (projectile.isOnFire() && projectile.mayInteract(world, blockposition)) { + // CraftBukkit start +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState()) || !CraftEventFactory.callTNTPrimeEvent(world, blockposition, PrimeCause.PROJECTILE, projectile, null)) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock()) || !CraftEventFactory.callTNTPrimeEvent(world, blockposition, PrimeCause.PROJECTILE, projectile, null)) { // Paper - fix wrong block state + return; + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java +index 61abbcfe97e3d3e3da5ee658672549d14594ad17..05e14322e519d1399e87beb532e1cc4a95f689aa 100644 +--- a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java +@@ -37,7 +37,7 @@ public class WaterlilyBlock extends BushBlock { + if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (world instanceof ServerLevel && entity instanceof Boat) { + // CraftBukkit start +- if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState())) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state + return; + } + // CraftBukkit end +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index a30626e25272769caf06caa69d32e2d3f9211710..489576ddb53766e8aa463772e9260ee996ce974a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1359,11 +1359,11 @@ public class CraftEventFactory { + return event; + } + +- public static EntityBreakDoorEvent callEntityBreakDoorEvent(Entity entity, BlockPos pos) { ++ public static EntityBreakDoorEvent callEntityBreakDoorEvent(Entity entity, BlockPos pos, net.minecraft.world.level.block.state.BlockState newState) { // Paper + org.bukkit.entity.Entity entity1 = entity.getBukkitEntity(); + Block block = CraftBlock.at(entity.level(), pos); + +- EntityBreakDoorEvent event = new EntityBreakDoorEvent((LivingEntity) entity1, block); ++ EntityBreakDoorEvent event = new EntityBreakDoorEvent((LivingEntity) entity1, block, newState.createCraftBlockData()); // Paper + entity1.getServer().getPluginManager().callEvent(event); + + return event; diff --git a/patches/server/0728-fix-player-loottables-running-when-mob-loot-gamerule.patch b/patches/server/0728-fix-player-loottables-running-when-mob-loot-gamerule.patch new file mode 100644 index 000000000000..185fdf44370f --- /dev/null +++ b/patches/server/0728-fix-player-loottables-running-when-mob-loot-gamerule.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 22 Mar 2022 09:50:40 -0700 +Subject: [PATCH] fix player loottables running when mob loot gamerule is false + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index be8c6c48287b73693ead9ae22071f2b4af7eed32..0db3f5b06b6c93882761450ea77ba4ee09869c61 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -875,12 +875,14 @@ public class ServerPlayer extends Player { + } + } + } ++ if (this.shouldDropLoot() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false + // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) + this.dropFromLootTable(damageSource, this.lastHurtByPlayerTime > 0); + for (org.bukkit.inventory.ItemStack item : this.drops) { + loot.add(item); + } + this.drops.clear(); // SPIGOT-5188: make sure to clear ++ } // Paper - fix player loottables running when mob loot gamerule is false + + Component defaultMessage = this.getCombatTracker().getDeathMessage(); + diff --git a/patches/server/0729-Ensure-entity-passenger-world-matches-ridden-entity.patch b/patches/server/0729-Ensure-entity-passenger-world-matches-ridden-entity.patch new file mode 100644 index 000000000000..1996d367ed43 --- /dev/null +++ b/patches/server/0729-Ensure-entity-passenger-world-matches-ridden-entity.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 31 Mar 2022 05:11:37 -0700 +Subject: [PATCH] Ensure entity passenger world matches ridden entity + +Bad plugins doing this would cause some obvious problems... + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 1a51c0ed22f892a233ce82fe53e72056685c9eba..c678e9b313bf1cb4a0849ca993fa006ebf182f3d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2564,7 +2564,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + public boolean startRiding(Entity entity, boolean force) { +- if (entity == this.vehicle) { ++ if (entity == this.vehicle || entity.level != this.level) { // Paper - Ensure entity passenger world matches ridden entity (bad plugins) + return false; + } else if (!entity.couldAcceptPassenger()) { + return false; diff --git a/patches/server/0729-Fix-new-block-data-for-EntityChangeBlockEvent.patch b/patches/server/0729-Fix-new-block-data-for-EntityChangeBlockEvent.patch deleted file mode 100644 index 92bc2d010f13..000000000000 --- a/patches/server/0729-Fix-new-block-data-for-EntityChangeBlockEvent.patch +++ /dev/null @@ -1,215 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SoSeDiK -Date: Mon, 21 Mar 2022 20:00:53 +0200 -Subject: [PATCH] Fix new block data for EntityChangeBlockEvent - -Also standardizes how to handle EntityChangeBlockEvent before a removeBlock or destroyBlock -call. Always use 'state.getFluidState().createLegacyBlock()' to get the new state instead of -just using the 'air' state. - -Also fixes the new block data for EntityBreakDoorEvent (a sub-event from -EntityChangeBlockEvent) - -Co-authored-by: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java -index b7abd8309a7d9744d3b3df9be8cad54f8909cc15..d3a2a6dee2d83b3df0ddc521c080f7d72b709461 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java -@@ -107,7 +107,7 @@ public class HarvestFarmland extends Behavior { - Block block1 = world.getBlockState(this.aboveFarmlandPos.below()).getBlock(); - - if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata)) { -- if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, Blocks.AIR.defaultBlockState())) { // CraftBukkit -+ if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state - world.destroyBlock(this.aboveFarmlandPos, true, entity); - } // CraftBukkit - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -index 4253b3b1263a7ae5a2f5f3a34674dfea615a81ea..784a894688f98f9d0368a36d456c5c94e1ee3695 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -@@ -72,7 +72,7 @@ public class BreakDoorGoal extends DoorInteractGoal { - - if (this.breakTime == this.getDoorBreakTime() && this.isValidDifficulty(this.mob.level().getDifficulty())) { - // CraftBukkit start -- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.mob, this.doorPos).isCancelled()) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.mob, this.doorPos, this.mob.level().getFluidState(this.doorPos).createLegacyBlock()).isCancelled()) { // Paper - fix wrong block state - this.start(); - return; - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -index f4bc556e245179d0a4710e5255dd289aaafdceb7..d802985f1431be4332c07f0dab88feebedea4ce2 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -@@ -67,8 +67,9 @@ public class EatBlockGoal extends Goal { - if (this.eatAnimationTick == this.adjustedTickDelay(4)) { - BlockPos blockposition = this.mob.blockPosition(); - -- if (EatBlockGoal.IS_TALL_GRASS.test(this.level.getBlockState(blockposition))) { -- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, Blocks.AIR.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit -+ final BlockState blockState = this.level.getBlockState(blockposition); // Paper - fix wrong block state -+ if (EatBlockGoal.IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state -+ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state - this.level.destroyBlock(blockposition, false); - } - -@@ -77,7 +78,7 @@ public class EatBlockGoal extends Goal { - BlockPos blockposition1 = blockposition.below(); - - if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) { -- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.AIR.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit -+ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state - this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState())); - this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2); - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -index 6e1c67ad757e466d122badd547ee3f8421eba9ba..cf4859814a60468f683e3afe285b4934d35e9704 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -@@ -580,7 +580,7 @@ public class Rabbit extends Animal implements VariantHolder { - - if (i == 0) { - // CraftBukkit start -- if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, Blocks.AIR.defaultBlockState())) { -+ if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - return; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 0a2c2b847dc516abf31870116056dbdbb22f31d9..45906d273e6d6ec20cf44b4d07efdac68752ee9b 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -374,7 +374,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - - if (WitherBoss.canDestroy(iblockdata)) { - // CraftBukkit start -- if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, Blocks.AIR.defaultBlockState())) { -+ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - continue; - } - // CraftBukkit end -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 c360135b923aa8d1ed2c7caf97ede981cb605cf2..f33c03e81b7ff643741f56eea055e6af260de618 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -@@ -580,7 +580,7 @@ public class EnderMan extends Monster implements NeutralMob { - boolean flag = movingobjectpositionblock.getBlockPos().equals(blockposition); - - if (iblockdata.is(BlockTags.ENDERMAN_HOLDABLE) && flag) { -- if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, Blocks.AIR.defaultBlockState())) { // CraftBukkit - Place event -+ if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit - Place event // Paper - fix wrong block state - world.removeBlock(blockposition, false); - world.gameEvent(GameEvent.BLOCK_DESTROY, blockposition, GameEvent.Context.of(this.enderman, iblockdata)); - this.enderman.setCarriedBlock(iblockdata.getBlock().defaultBlockState()); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -index aac60e85cd6dba7d87f4a1663c2c62952521d112..151acc43c96b4545ce92d3d559d8e1591874b4b5 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -@@ -158,7 +158,7 @@ public class Ravager extends Raider { - - if (block instanceof LeavesBlock) { - // CraftBukkit start -- if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) { -+ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - continue; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -index 6f452605e9dc9ebd9980eae9fdeea34417a37a88..2c60a3765d22909e73b660492410ab8456304b68 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java -@@ -181,7 +181,8 @@ public class Silverfish extends Monster { - - if (block instanceof InfestedBlock) { - // CraftBukkit start -- if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) { -+ BlockState afterState = world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? iblockdata.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)); // Paper - fix wrong block state -+ if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, afterState)) { // Paper - fix wrong block state - continue; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -index aa523ba36c975e6ac92544ee14d7904f35789bec..efe2095063fd402ed645d711611d85bc3753a5b8 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -@@ -306,7 +306,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - - if (iblockdata.is(BlockTags.FIRE)) { - // CraftBukkit start -- if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, Blocks.AIR.defaultBlockState())) { -+ if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - this.level().destroyBlock(pos, false, this); - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java b/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java -index 87c66bf8fee56e77b25498d9b2524fe2b6fd6549..0ab1bbd7c8dc8e45f754434357898d8fc990a021 100644 ---- a/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java -@@ -284,7 +284,7 @@ public class ChorusFlowerBlock extends Block { - - if (!world.isClientSide && projectile.mayInteract(world, blockposition) && projectile.mayBreak(world)) { - // CraftBukkit -- if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState())) { -+ if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - return; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -index bf0e05be3db1952b311e3eb9ab4881b12ee9dfd7..e59f9b83606da83f15924477ea2a2c4b74e7d892 100644 ---- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -@@ -137,7 +137,7 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate - - if (projectile.mayInteract(world, blockposition) && projectile.mayBreak(world) && projectile instanceof ThrownTrident && projectile.getDeltaMovement().length() > 0.6D) { - // CraftBukkit start -- if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState())) { -+ if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - return; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/TntBlock.java b/src/main/java/net/minecraft/world/level/block/TntBlock.java -index ab53b42eec6eb57b373487e50066967042d6eb40..f8cff1f2825a8e428d8a99d0b1b58606a74a918a 100644 ---- a/src/main/java/net/minecraft/world/level/block/TntBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TntBlock.java -@@ -163,7 +163,7 @@ public class TntBlock extends Block { - - if (projectile.isOnFire() && projectile.mayInteract(world, blockposition)) { - // CraftBukkit start -- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState()) || !CraftEventFactory.callTNTPrimeEvent(world, blockposition, PrimeCause.PROJECTILE, projectile, null)) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock()) || !CraftEventFactory.callTNTPrimeEvent(world, blockposition, PrimeCause.PROJECTILE, projectile, null)) { // Paper - fix wrong block state - return; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -index 61abbcfe97e3d3e3da5ee658672549d14594ad17..05e14322e519d1399e87beb532e1cc4a95f689aa 100644 ---- a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -@@ -37,7 +37,7 @@ public class WaterlilyBlock extends BushBlock { - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (world instanceof ServerLevel && entity instanceof Boat) { - // CraftBukkit start -- if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState())) { -+ if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - return; - } - // CraftBukkit end -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index fb39bae1a76739cdb1da2b73173c1eb3ce3cd16b..2af7ba566697681f71e5b733591709bc28dfc8ce 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1436,11 +1436,11 @@ public class CraftEventFactory { - return event; - } - -- public static EntityBreakDoorEvent callEntityBreakDoorEvent(Entity entity, BlockPos pos) { -+ public static EntityBreakDoorEvent callEntityBreakDoorEvent(Entity entity, BlockPos pos, net.minecraft.world.level.block.state.BlockState newState) { // Paper - org.bukkit.entity.Entity entity1 = entity.getBukkitEntity(); - Block block = CraftBlock.at(entity.level(), pos); - -- EntityBreakDoorEvent event = new EntityBreakDoorEvent((LivingEntity) entity1, block); -+ EntityBreakDoorEvent event = new EntityBreakDoorEvent((LivingEntity) entity1, block, newState.createCraftBlockData()); // Paper - entity1.getServer().getPluginManager().callEvent(event); - - return event; diff --git a/patches/server/0732-cache-resource-keys.patch b/patches/server/0730-cache-resource-keys.patch similarity index 100% rename from patches/server/0732-cache-resource-keys.patch rename to patches/server/0730-cache-resource-keys.patch diff --git a/patches/server/0730-fix-player-loottables-running-when-mob-loot-gamerule.patch b/patches/server/0730-fix-player-loottables-running-when-mob-loot-gamerule.patch deleted file mode 100644 index 0eb3cafa241a..000000000000 --- a/patches/server/0730-fix-player-loottables-running-when-mob-loot-gamerule.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 22 Mar 2022 09:50:40 -0700 -Subject: [PATCH] fix player loottables running when mob loot gamerule is false - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index e418778297b89edd3cdf4ce9917dcb4d4d130023..6d1093972ffcee52c9f94c97c4f842b66726647c 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -875,12 +875,14 @@ public class ServerPlayer extends Player { - } - } - } -+ if (this.shouldDropLoot() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false - // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) - this.dropFromLootTable(damageSource, this.lastHurtByPlayerTime > 0); - for (org.bukkit.inventory.ItemStack item : this.drops) { - loot.add(item); - } - this.drops.clear(); // SPIGOT-5188: make sure to clear -+ } // Paper - fix player loottables running when mob loot gamerule is false - - Component defaultMessage = this.getCombatTracker().getDeathMessage(); - diff --git a/patches/server/0731-Allow-changing-the-EnderDragon-podium.patch b/patches/server/0731-Allow-changing-the-EnderDragon-podium.patch new file mode 100644 index 000000000000..301eb8f9fe7a --- /dev/null +++ b/patches/server/0731-Allow-changing-the-EnderDragon-podium.patch @@ -0,0 +1,151 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Doc +Date: Sun, 3 Apr 2022 11:31:42 -0400 +Subject: [PATCH] Allow changing the EnderDragon podium + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index 623a4cc921442dbba4f80df3be06762b4b1289ae..d0de3ef6c78785a047ecdf2412df082d53fb985b 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -103,6 +103,10 @@ public class EnderDragon extends Mob implements Enemy { + private final int[] nodeAdjacency; + private final BinaryHeap openSet; + private final Explosion explosionSource; // CraftBukkit - reusable source for CraftTNTPrimed.getSource() ++ // Paper start - Allow changing the EnderDragon podium ++ @Nullable ++ private BlockPos podium; ++ // Paper end - Allow changing the EnderDragon podium + + public EnderDragon(EntityType entitytypes, Level world) { + super(EntityType.ENDER_DRAGON, world); +@@ -143,6 +147,19 @@ public class EnderDragon extends Mob implements Enemy { + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D); + } + ++ // Paper start - Allow changing the EnderDragon podium ++ public BlockPos getPodium() { ++ if (this.podium == null) { ++ return EndPodiumFeature.getLocation(this.getFightOrigin()); ++ } ++ return this.podium; ++ } ++ ++ public void setPodium(@Nullable BlockPos blockPos) { ++ this.podium = blockPos; ++ } ++ // Paper end - Allow changing the EnderDragon podium ++ + @Override + public boolean isFlapping() { + float f = Mth.cos(this.flapTime * 6.2831855F); +@@ -994,7 +1011,7 @@ public class EnderDragon extends Mob implements Enemy { + d0 = segment2[1] - segment1[1]; + } + } else { +- BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin)); ++ BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium + double d1 = Math.max(Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0D, 1.0D); + + d0 = (double) segmentOffset / d1; +@@ -1021,7 +1038,7 @@ public class EnderDragon extends Mob implements Enemy { + vec3d = this.getViewVector(tickDelta); + } + } else { +- BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin)); ++ BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium + + f1 = Math.max((float) Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0F, 1.0F); + float f3 = 6.0F / f1; +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java +index f4028b6890fd094360a33403728588380111204d..d6ec0583dbaca95eb6a6444923cc1311a9753825 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java +@@ -32,7 +32,7 @@ public class DragonDeathPhase extends AbstractDragonPhaseInstance { + public void doServerTick() { + ++this.time; + if (this.targetLocation == null) { +- BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + this.targetLocation = Vec3.atBottomCenterOf(blockPos); + } + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java +index 00228e49c07eeed13b726192d5f9b8f2fc55bb75..bd7be8a5a24f1328dde28ae4a66823c12110fa02 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java +@@ -54,7 +54,7 @@ public class DragonHoldingPatternPhase extends AbstractDragonPhaseInstance { + + private void findNewTarget() { + if (this.currentPath != null && this.currentPath.isDone()) { +- BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, new BlockPos(EndPodiumFeature.getLocation(this.dragon.getFightOrigin()))); ++ BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + int i = this.dragon.getDragonFight() == null ? 0 : this.dragon.getDragonFight().getCrystalsAlive(); + if (this.dragon.getRandom().nextInt(i + 3) == 0) { + this.dragon.getPhaseManager().setPhase(EnderDragonPhase.LANDING_APPROACH); +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java +index 80647b1f5192e6f2b660a31413db1fa45fb92f2c..e161bfb06beeada4987272d01a0f140069b37951 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java +@@ -52,7 +52,7 @@ public class DragonLandingApproachPhase extends AbstractDragonPhaseInstance { + private void findNewTarget() { + if (this.currentPath == null || this.currentPath.isDone()) { + int i = this.dragon.findClosestNode(); +- BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + Player player = this.dragon.level().getNearestPlayer(NEAR_EGG_TARGETING, this.dragon, (double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ()); + int j; + if (player != null) { +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java +index 48ebc2ab8ebbdc2292af10b955384bf7d722ade2..24db7fafd31277eb1b82eac4a97b2f979c1d3816 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java +@@ -39,7 +39,7 @@ public class DragonLandingPhase extends AbstractDragonPhaseInstance { + @Override + public void doServerTick() { + if (this.targetLocation == null) { +- this.targetLocation = Vec3.atBottomCenterOf(this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()))); ++ this.targetLocation = Vec3.atBottomCenterOf(this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium())); // Paper - Allow changing the EnderDragon podium + } + + if (this.targetLocation.distanceToSqr(this.dragon.getX(), this.dragon.getY(), this.dragon.getZ()) < 1.0D) { +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java +index 1290090f855840bf64bf3a7ba93e3cb036630dcc..792ff77090fa582a7ffcb7b8c394badfa7586126 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java +@@ -24,7 +24,7 @@ public class DragonTakeoffPhase extends AbstractDragonPhaseInstance { + @Override + public void doServerTick() { + if (!this.firstTick && this.currentPath != null) { +- BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); ++ BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium + if (!blockPos.closerToCenterThan(this.dragon.position(), 10.0D)) { + this.dragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java +index 25b3d889a1742c347e60725df8d6f6c1cee264c7..7b7b89e67d53ed70efae714192c5fa32977f3d9c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java +@@ -73,4 +73,22 @@ public class CraftEnderDragon extends CraftMob implements EnderDragon, CraftEnem + public int getDeathAnimationTicks() { + return this.getHandle().dragonDeathTime; + } ++ ++ // Paper start - Allow changing the EnderDragon podium ++ @Override ++ public org.bukkit.Location getPodium() { ++ net.minecraft.core.BlockPos blockPosOrigin = this.getHandle().getPodium(); ++ return new org.bukkit.Location(getWorld(), blockPosOrigin.getX(), blockPosOrigin.getY(), blockPosOrigin.getZ()); ++ } ++ ++ @Override ++ public void setPodium(org.bukkit.Location location) { ++ if (location == null) { ++ this.getHandle().setPodium(null); ++ } else { ++ org.apache.commons.lang.Validate.isTrue(location.getWorld() == null || location.getWorld().equals(getWorld()), "You cannot set a podium in a different world to where the dragon is"); ++ this.getHandle().setPodium(io.papermc.paper.util.MCUtil.toBlockPos(location)); ++ } ++ } ++ // Paper end - Allow changing the EnderDragon podium + } diff --git a/patches/server/0731-Ensure-entity-passenger-world-matches-ridden-entity.patch b/patches/server/0731-Ensure-entity-passenger-world-matches-ridden-entity.patch deleted file mode 100644 index 00ce9a109985..000000000000 --- a/patches/server/0731-Ensure-entity-passenger-world-matches-ridden-entity.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 31 Mar 2022 05:11:37 -0700 -Subject: [PATCH] Ensure entity passenger world matches ridden entity - -Bad plugins doing this would cause some obvious problems... - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index b0c97b12d316463af8c581f5f15c5c5e567eb403..b580bb939f3c128ed68e02f2c75764aed49427c6 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2560,7 +2560,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - public boolean startRiding(Entity entity, boolean force) { -- if (entity == this.vehicle) { -+ if (entity == this.vehicle || entity.level != this.level) { // Paper - Ensure entity passenger world matches ridden entity (bad plugins) - return false; - } else if (!entity.couldAcceptPassenger()) { - return false; diff --git a/patches/server/0734-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch b/patches/server/0732-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch similarity index 100% rename from patches/server/0734-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch rename to patches/server/0732-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch diff --git a/patches/server/0733-Allow-changing-the-EnderDragon-podium.patch b/patches/server/0733-Allow-changing-the-EnderDragon-podium.patch deleted file mode 100644 index 68a861520446..000000000000 --- a/patches/server/0733-Allow-changing-the-EnderDragon-podium.patch +++ /dev/null @@ -1,151 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Doc -Date: Sun, 3 Apr 2022 11:31:42 -0400 -Subject: [PATCH] Allow changing the EnderDragon podium - - -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index 290d97437e9c593816b4f4c4e738c268be616dc0..955d0ddb69c29de723b85c65f42b0274813aadac 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -103,6 +103,10 @@ public class EnderDragon extends Mob implements Enemy { - private final int[] nodeAdjacency; - private final BinaryHeap openSet; - private final Explosion explosionSource; // CraftBukkit - reusable source for CraftTNTPrimed.getSource() -+ // Paper start - Allow changing the EnderDragon podium -+ @Nullable -+ private BlockPos podium; -+ // Paper end - Allow changing the EnderDragon podium - - public EnderDragon(EntityType entitytypes, Level world) { - super(EntityType.ENDER_DRAGON, world); -@@ -143,6 +147,19 @@ public class EnderDragon extends Mob implements Enemy { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D); - } - -+ // Paper start - Allow changing the EnderDragon podium -+ public BlockPos getPodium() { -+ if (this.podium == null) { -+ return EndPodiumFeature.getLocation(this.getFightOrigin()); -+ } -+ return this.podium; -+ } -+ -+ public void setPodium(@Nullable BlockPos blockPos) { -+ this.podium = blockPos; -+ } -+ // Paper end - Allow changing the EnderDragon podium -+ - @Override - public boolean isFlapping() { - float f = Mth.cos(this.flapTime * 6.2831855F); -@@ -994,7 +1011,7 @@ public class EnderDragon extends Mob implements Enemy { - d0 = segment2[1] - segment1[1]; - } - } else { -- BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin)); -+ BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium - double d1 = Math.max(Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0D, 1.0D); - - d0 = (double) segmentOffset / d1; -@@ -1021,7 +1038,7 @@ public class EnderDragon extends Mob implements Enemy { - vec3d = this.getViewVector(tickDelta); - } - } else { -- BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin)); -+ BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium - - f1 = Math.max((float) Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0F, 1.0F); - float f3 = 6.0F / f1; -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java -index f4028b6890fd094360a33403728588380111204d..d6ec0583dbaca95eb6a6444923cc1311a9753825 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java -@@ -32,7 +32,7 @@ public class DragonDeathPhase extends AbstractDragonPhaseInstance { - public void doServerTick() { - ++this.time; - if (this.targetLocation == null) { -- BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); -+ BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium - this.targetLocation = Vec3.atBottomCenterOf(blockPos); - } - -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java -index 00228e49c07eeed13b726192d5f9b8f2fc55bb75..bd7be8a5a24f1328dde28ae4a66823c12110fa02 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java -@@ -54,7 +54,7 @@ public class DragonHoldingPatternPhase extends AbstractDragonPhaseInstance { - - private void findNewTarget() { - if (this.currentPath != null && this.currentPath.isDone()) { -- BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, new BlockPos(EndPodiumFeature.getLocation(this.dragon.getFightOrigin()))); -+ BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium - int i = this.dragon.getDragonFight() == null ? 0 : this.dragon.getDragonFight().getCrystalsAlive(); - if (this.dragon.getRandom().nextInt(i + 3) == 0) { - this.dragon.getPhaseManager().setPhase(EnderDragonPhase.LANDING_APPROACH); -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java -index 80647b1f5192e6f2b660a31413db1fa45fb92f2c..e161bfb06beeada4987272d01a0f140069b37951 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java -@@ -52,7 +52,7 @@ public class DragonLandingApproachPhase extends AbstractDragonPhaseInstance { - private void findNewTarget() { - if (this.currentPath == null || this.currentPath.isDone()) { - int i = this.dragon.findClosestNode(); -- BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); -+ BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium - Player player = this.dragon.level().getNearestPlayer(NEAR_EGG_TARGETING, this.dragon, (double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ()); - int j; - if (player != null) { -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java -index 48ebc2ab8ebbdc2292af10b955384bf7d722ade2..24db7fafd31277eb1b82eac4a97b2f979c1d3816 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java -@@ -39,7 +39,7 @@ public class DragonLandingPhase extends AbstractDragonPhaseInstance { - @Override - public void doServerTick() { - if (this.targetLocation == null) { -- this.targetLocation = Vec3.atBottomCenterOf(this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()))); -+ this.targetLocation = Vec3.atBottomCenterOf(this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium())); // Paper - Allow changing the EnderDragon podium - } - - if (this.targetLocation.distanceToSqr(this.dragon.getX(), this.dragon.getY(), this.dragon.getZ()) < 1.0D) { -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java -index 1290090f855840bf64bf3a7ba93e3cb036630dcc..792ff77090fa582a7ffcb7b8c394badfa7586126 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java -@@ -24,7 +24,7 @@ public class DragonTakeoffPhase extends AbstractDragonPhaseInstance { - @Override - public void doServerTick() { - if (!this.firstTick && this.currentPath != null) { -- BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin())); -+ BlockPos blockPos = this.dragon.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium - if (!blockPos.closerToCenterThan(this.dragon.position(), 10.0D)) { - this.dragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java -index 25b3d889a1742c347e60725df8d6f6c1cee264c7..7b7b89e67d53ed70efae714192c5fa32977f3d9c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java -@@ -73,4 +73,22 @@ public class CraftEnderDragon extends CraftMob implements EnderDragon, CraftEnem - public int getDeathAnimationTicks() { - return this.getHandle().dragonDeathTime; - } -+ -+ // Paper start - Allow changing the EnderDragon podium -+ @Override -+ public org.bukkit.Location getPodium() { -+ net.minecraft.core.BlockPos blockPosOrigin = this.getHandle().getPodium(); -+ return new org.bukkit.Location(getWorld(), blockPosOrigin.getX(), blockPosOrigin.getY(), blockPosOrigin.getZ()); -+ } -+ -+ @Override -+ public void setPodium(org.bukkit.Location location) { -+ if (location == null) { -+ this.getHandle().setPodium(null); -+ } else { -+ org.apache.commons.lang.Validate.isTrue(location.getWorld() == null || location.getWorld().equals(getWorld()), "You cannot set a podium in a different world to where the dragon is"); -+ this.getHandle().setPodium(io.papermc.paper.util.MCUtil.toBlockPos(location)); -+ } -+ } -+ // Paper end - Allow changing the EnderDragon podium - } diff --git a/patches/server/0733-Prevent-tile-entity-copies-loading-chunks.patch b/patches/server/0733-Prevent-tile-entity-copies-loading-chunks.patch new file mode 100644 index 000000000000..a24fd47f060e --- /dev/null +++ b/patches/server/0733-Prevent-tile-entity-copies-loading-chunks.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Wed, 13 Apr 2022 08:25:42 +0100 +Subject: [PATCH] Prevent tile entity copies loading chunks + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index cd79aa53ec49c80ee3ddf79b7161637e66b688fd..819894dabc8e28a499a6ab5420299e14ab072ff8 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3107,7 +3107,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound); + + if (this.player.level().isLoaded(blockposition)) { +- BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); ++ // Paper start - Prevent tile entity copies loading chunks ++ BlockEntity tileentity = null; ++ if (this.player.distanceToSqr(blockposition.getX(), blockposition.getY(), blockposition.getZ()) < 32 * 32 && this.player.serverLevel().isLoadedAndInBounds(blockposition)) { ++ tileentity = this.player.level().getBlockEntity(blockposition); ++ } ++ // Paper end - Prevent tile entity copies loading chunks + + if (tileentity != null) { + tileentity.saveToItem(itemstack); diff --git a/patches/server/0734-Use-username-instead-of-display-name-in-PlayerList-g.patch b/patches/server/0734-Use-username-instead-of-display-name-in-PlayerList-g.patch new file mode 100644 index 000000000000..a5282aa96aee --- /dev/null +++ b/patches/server/0734-Use-username-instead-of-display-name-in-PlayerList-g.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Doc +Date: Fri, 15 Apr 2022 17:40:30 -0400 +Subject: [PATCH] Use username instead of display name in + PlayerList#getPlayerStats + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 0c77457e1b9360ef578b4a781bb2ec1fb7b582db..a8a57819993f3657d6525a98527ec47631174bed 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1434,7 +1434,7 @@ public abstract class PlayerList { + // CraftBukkit start + public ServerStatsCounter getPlayerStats(ServerPlayer entityhuman) { + ServerStatsCounter serverstatisticmanager = entityhuman.getStats(); +- return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getDisplayName().getString()) : serverstatisticmanager; ++ return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getGameProfile().getName()) : serverstatisticmanager; // Paper - use username and not display name + } + + public ServerStatsCounter getPlayerStats(UUID uuid, String displayName) { diff --git a/patches/server/0735-Expand-PlayerItemDamageEvent.patch b/patches/server/0735-Expand-PlayerItemDamageEvent.patch new file mode 100644 index 000000000000..bb173a36a5d5 --- /dev/null +++ b/patches/server/0735-Expand-PlayerItemDamageEvent.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Sun, 10 Apr 2022 06:26:32 +0100 +Subject: [PATCH] Expand PlayerItemDamageEvent + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index a8f1953da3a5684079fb0cdb88cb3caf72d43646..59c4550b4cb8b0317f5256efc9376265f4583b60 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -640,10 +640,11 @@ public final class ItemStack { + } + } + ++ int originalDamage = amount; // Paper - Expand PlayerItemDamageEvent + amount -= k; + // CraftBukkit start + if (player instanceof ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent +- PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); // Paper - Add EntityDamageItemEvent ++ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount, originalDamage); // Paper - Add EntityDamageItemEvent & Expand PlayerItemDamageEvent + event.getPlayer().getServer().getPluginManager().callEvent(event); + + if (amount != event.getDamage() || event.isCancelled()) { diff --git a/patches/server/0735-Fix-StructureGrowEvent-species-for-RED_MUSHROOM.patch b/patches/server/0735-Fix-StructureGrowEvent-species-for-RED_MUSHROOM.patch deleted file mode 100644 index dfa3c3ac08e8..000000000000 --- a/patches/server/0735-Fix-StructureGrowEvent-species-for-RED_MUSHROOM.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Tue, 12 Apr 2022 16:36:15 -0700 -Subject: [PATCH] Fix StructureGrowEvent species for RED_MUSHROOM - - -diff --git a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java -index c04629ba46e9018967b98ca4438b4f21e7dafb52..5889cb1cdb64875f0d7a7c681808b45cdc661d8e 100644 ---- a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java -@@ -104,7 +104,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { - return false; - } else { - world.removeBlock(pos, false); -- SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.BROWN_MUSHROOM; // CraftBukkit -+ SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.RED_MUSHROOM; // CraftBukkit // Paper - if (((ConfiguredFeature) ((Holder) optional.get()).value()).place(world, world.getChunkSource().getGenerator(), random, pos)) { - return true; - } else { diff --git a/patches/server/0736-Prevent-tile-entity-copies-loading-chunks.patch b/patches/server/0736-Prevent-tile-entity-copies-loading-chunks.patch deleted file mode 100644 index 6df0fd17cc4e..000000000000 --- a/patches/server/0736-Prevent-tile-entity-copies-loading-chunks.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Wed, 13 Apr 2022 08:25:42 +0100 -Subject: [PATCH] Prevent tile entity copies loading chunks - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0017de198711446f82edecf6ac6afe064f315bd9..a62f10f672fb74b250de39ce3b8b3cd867d73869 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3107,7 +3107,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound); - - if (this.player.level().isLoaded(blockposition)) { -- BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); -+ // Paper start - Prevent tile entity copies loading chunks -+ BlockEntity tileentity = null; -+ if (this.player.distanceToSqr(blockposition.getX(), blockposition.getY(), blockposition.getZ()) < 32 * 32 && this.player.serverLevel().isLoadedAndInBounds(blockposition)) { -+ tileentity = this.player.level().getBlockEntity(blockposition); -+ } -+ // Paper end - Prevent tile entity copies loading chunks - - if (tileentity != null) { - tileentity.saveToItem(itemstack); diff --git a/patches/server/0736-WorldCreator-keepSpawnLoaded.patch b/patches/server/0736-WorldCreator-keepSpawnLoaded.patch new file mode 100644 index 000000000000..97cbe705e894 --- /dev/null +++ b/patches/server/0736-WorldCreator-keepSpawnLoaded.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 3 Jul 2021 21:18:28 +0100 +Subject: [PATCH] WorldCreator#keepSpawnLoaded + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 713d36b8bcb3f39f7f0cff61532199416970bcf4..f3b461baac5a5de61f6b1c00251961bd25eb4773 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1290,7 +1290,7 @@ public final class CraftServer implements Server { + + ServerLevel internal = (ServerLevel) new ServerLevel(this.console, this.console.executor, worldSession, worlddata, worldKey, worlddimension, this.getServer().progressListenerFactory.create(11), + worlddata.isDebugWorld(), j, creator.environment() == Environment.NORMAL ? list : ImmutableList.of(), true, this.console.overworld().getRandomSequences(), creator.environment(), generator, biomeProvider); +- internal.keepSpawnInMemory = creator.keepSpawnInMemory(); ++ // internal.keepSpawnInMemory = creator.keepSpawnInMemory(); // Paper - replace + + if (!(this.worlds.containsKey(name.toLowerCase(java.util.Locale.ENGLISH)))) { + return null; +@@ -1302,6 +1302,7 @@ public final class CraftServer implements Server { + internal.setSpawnSettings(true, true); + // Paper - Put world into worldlist before initing the world; move up + ++ internal.keepSpawnInMemory = creator.keepSpawnLoaded().toBooleanOrElse(internal.getWorld().getKeepSpawnInMemory()); // Paper + this.getServer().prepareLevels(internal.getChunkSource().chunkMap.progressListener, internal); + internal.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API + diff --git a/patches/server/0741-Fix-CME-in-CraftPersistentDataTypeRegistry.patch b/patches/server/0737-Fix-CME-in-CraftPersistentDataTypeRegistry.patch similarity index 100% rename from patches/server/0741-Fix-CME-in-CraftPersistentDataTypeRegistry.patch rename to patches/server/0737-Fix-CME-in-CraftPersistentDataTypeRegistry.patch diff --git a/patches/server/0737-Use-username-instead-of-display-name-in-PlayerList-g.patch b/patches/server/0737-Use-username-instead-of-display-name-in-PlayerList-g.patch deleted file mode 100644 index 149673f045fd..000000000000 --- a/patches/server/0737-Use-username-instead-of-display-name-in-PlayerList-g.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Doc -Date: Fri, 15 Apr 2022 17:40:30 -0400 -Subject: [PATCH] Use username instead of display name in - PlayerList#getPlayerStats - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 4b8062c04df707de93046c19633048d674d809b6..b40ad88b42dd8fcbf6138cd4f1933360cce0ad28 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1434,7 +1434,7 @@ public abstract class PlayerList { - // CraftBukkit start - public ServerStatsCounter getPlayerStats(ServerPlayer entityhuman) { - ServerStatsCounter serverstatisticmanager = entityhuman.getStats(); -- return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getDisplayName().getString()) : serverstatisticmanager; -+ return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getGameProfile().getName()) : serverstatisticmanager; // Paper - use username and not display name - } - - public ServerStatsCounter getPlayerStats(UUID uuid, String displayName) { diff --git a/patches/server/0738-Pass-ServerLevel-for-gamerule-callbacks.patch b/patches/server/0738-Pass-ServerLevel-for-gamerule-callbacks.patch deleted file mode 100644 index 546e668462e8..000000000000 --- a/patches/server/0738-Pass-ServerLevel-for-gamerule-callbacks.patch +++ /dev/null @@ -1,190 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 27 Mar 2022 13:51:09 -0400 -Subject: [PATCH] Pass ServerLevel for gamerule callbacks - - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index c39b19bee3aca093a2087e19875a50ff21cf1ab3..8d7d5cadbd65833d46dce71609e938290c81f772 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -304,7 +304,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - - DedicatedServer.LOGGER.info("Done ({})! For help, type \"help\"", s); - if (dedicatedserverproperties.announcePlayerAchievements != null) { -- ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this); -+ ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, null); // Paper - Pass ServerLevel for gamerule callbacks - } - - if (dedicatedserverproperties.enableQuery) { -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index a62f10f672fb74b250de39ce3b8b3cd867d73869..e06d09ba19df5b7bf2b5179a07c39e2197cc0863 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2671,7 +2671,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.player = this.server.getPlayerList().respawn(this.player, false, RespawnReason.DEATH); - if (this.server.isHardcore()) { - this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper - Expand PlayerGameModeChangeEvent -- ((GameRules.BooleanValue) this.player.level().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.server); -+ ((GameRules.BooleanValue) this.player.level().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.player.serverLevel()); // Paper - Pass ServerLevel for gamerule callbacks - } - } - break; -diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java -index f3cdf1fa7731eb7bb1cb89aa6a37204d81257cb0..0112ef51815c4fab38b95d5e917d335eeaaa21cd 100644 ---- a/src/main/java/net/minecraft/world/level/GameRules.java -+++ b/src/main/java/net/minecraft/world/level/GameRules.java -@@ -52,7 +52,7 @@ public class GameRules { - public static final GameRules.Key RULE_SENDCOMMANDFEEDBACK = GameRules.register("sendCommandFeedback", GameRules.Category.CHAT, GameRules.BooleanValue.create(true)); - public static final GameRules.Key RULE_REDUCEDDEBUGINFO = GameRules.register("reducedDebugInfo", GameRules.Category.MISC, GameRules.BooleanValue.create(false, (minecraftserver, gamerules_gameruleboolean) -> { - int i = gamerules_gameruleboolean.get() ? 22 : 23; -- Iterator iterator = minecraftserver.getPlayerList().getPlayers().iterator(); -+ Iterator iterator = minecraftserver.players().iterator(); // Paper - Pass ServerLevel for gamerule callbacks - - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -@@ -67,7 +67,7 @@ public class GameRules { - public static final GameRules.Key RULE_MAX_ENTITY_CRAMMING = GameRules.register("maxEntityCramming", GameRules.Category.MOBS, GameRules.IntegerValue.create(24)); - public static final GameRules.Key RULE_WEATHER_CYCLE = GameRules.register("doWeatherCycle", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true)); - public static final GameRules.Key RULE_LIMITED_CRAFTING = GameRules.register("doLimitedCrafting", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (minecraftserver, gamerules_gameruleboolean) -> { -- Iterator iterator = minecraftserver.getPlayerList().getPlayers().iterator(); -+ Iterator iterator = minecraftserver.players().iterator(); // Paper - Pass ServerLevel for gamerule callbacks - - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -@@ -83,7 +83,7 @@ public class GameRules { - public static final GameRules.Key RULE_DISABLE_RAIDS = GameRules.register("disableRaids", GameRules.Category.MOBS, GameRules.BooleanValue.create(false)); - public static final GameRules.Key RULE_DOINSOMNIA = GameRules.register("doInsomnia", GameRules.Category.SPAWNING, GameRules.BooleanValue.create(true)); - public static final GameRules.Key RULE_DO_IMMEDIATE_RESPAWN = GameRules.register("doImmediateRespawn", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (minecraftserver, gamerules_gameruleboolean) -> { -- Iterator iterator = minecraftserver.getPlayerList().getPlayers().iterator(); -+ Iterator iterator = minecraftserver.players().iterator(); // Paper - Pass ServerLevel for gamerule callbacks - - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -@@ -180,13 +180,13 @@ public class GameRules { - ((GameRules.Type) type).callVisitor(consumer, (GameRules.Key) key); // CraftBukkit - decompile error - } - -- public void assignFrom(GameRules rules, @Nullable MinecraftServer server) { -+ public void assignFrom(GameRules rules, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper - Pass ServerLevel for gamerule callbacks - rules.rules.keySet().forEach((gamerules_gamerulekey) -> { - this.assignCap(gamerules_gamerulekey, rules, server); - }); - } - -- private > void assignCap(GameRules.Key key, GameRules rules, @Nullable MinecraftServer server) { -+ private > void assignCap(GameRules.Key key, GameRules rules, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper - Pass ServerLevel for gamerule callbacks - T t0 = rules.getRule(key); - - this.getRule(key).setFrom(t0, server); -@@ -254,10 +254,10 @@ public class GameRules { - - private final Supplier> argument; - private final Function, T> constructor; -- final BiConsumer callback; -+ final BiConsumer callback; // Paper - Pass ServerLevel for gamerule callbacks - private final GameRules.VisitorCaller visitorCaller; - -- Type(Supplier> argumentType, Function, T> ruleFactory, BiConsumer changeCallback, GameRules.VisitorCaller ruleAcceptor) { -+ Type(Supplier> argumentType, Function, T> ruleFactory, BiConsumer changeCallback, GameRules.VisitorCaller ruleAcceptor) { // Paper - Pass ServerLevel for gamerule callbacks - this.argument = argumentType; - this.constructor = ruleFactory; - this.callback = changeCallback; -@@ -289,10 +289,10 @@ public class GameRules { - - public void setFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper - Add WorldGameRuleChangeEvent - this.updateFromArgument(context, name, gameRuleKey); // Paper - Add WorldGameRuleChangeEvent -- this.onChanged(((CommandSourceStack) context.getSource()).getServer()); -+ this.onChanged(((CommandSourceStack) context.getSource()).getLevel()); // Paper - Pass ServerLevel for gamerule callbacks - } - -- public void onChanged(@Nullable MinecraftServer server) { -+ public void onChanged(@Nullable net.minecraft.server.level.ServerLevel server) { // Paper - Pass ServerLevel for gamerule callbacks - if (server != null) { - this.type.callback.accept(server, this.getSelf()); - } -@@ -313,7 +313,7 @@ public class GameRules { - - protected abstract T copy(); - -- public abstract void setFrom(T rule, @Nullable MinecraftServer server); -+ public abstract void setFrom(T rule, @Nullable net.minecraft.server.level.ServerLevel level); // Paper - Pass ServerLevel for gamerule callbacks - } - - public interface GameRuleTypeVisitor { -@@ -329,7 +329,7 @@ public class GameRules { - - private boolean value; - -- static GameRules.Type create(boolean initialValue, BiConsumer changeCallback) { -+ static GameRules.Type create(boolean initialValue, BiConsumer changeCallback) { // Paper - Pass ServerLevel for gamerule callbacks - return new GameRules.Type<>(BoolArgumentType::bool, (gamerules_gameruledefinition) -> { - return new GameRules.BooleanValue(gamerules_gameruledefinition, initialValue); - }, changeCallback, GameRules.GameRuleTypeVisitor::visitBoolean); -@@ -357,7 +357,7 @@ public class GameRules { - return this.value; - } - -- public void set(boolean value, @Nullable MinecraftServer server) { -+ public void set(boolean value, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper - Pass ServerLevel for gamerule callbacks - this.value = value; - this.onChanged(server); - } -@@ -387,7 +387,7 @@ public class GameRules { - return new GameRules.BooleanValue(this.type, this.value); - } - -- public void setFrom(GameRules.BooleanValue rule, @Nullable MinecraftServer server) { -+ public void setFrom(GameRules.BooleanValue rule, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper - Pass ServerLevel for gamerule callbacks - this.value = rule.value; - this.onChanged(server); - } -@@ -397,7 +397,7 @@ public class GameRules { - - private int value; - -- private static GameRules.Type create(int initialValue, BiConsumer changeCallback) { -+ private static GameRules.Type create(int initialValue, BiConsumer changeCallback) { // Paper - Pass ServerLevel for gamerule callbacks - return new GameRules.Type<>(IntegerArgumentType::integer, (gamerules_gameruledefinition) -> { - return new GameRules.IntegerValue(gamerules_gameruledefinition, initialValue); - }, changeCallback, GameRules.GameRuleTypeVisitor::visitInteger); -@@ -425,7 +425,7 @@ public class GameRules { - return this.value; - } - -- public void set(int value, @Nullable MinecraftServer server) { -+ public void set(int value, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper - Pass ServerLevel for gamerule callbacks - this.value = value; - this.onChanged(server); - } -@@ -476,7 +476,7 @@ public class GameRules { - return new GameRules.IntegerValue(this.type, this.value); - } - -- public void setFrom(GameRules.IntegerValue rule, @Nullable MinecraftServer server) { -+ public void setFrom(GameRules.IntegerValue rule, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper - Pass ServerLevel for gamerule callbacks - this.value = rule.value; - this.onChanged(server); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 5e2a1a4bd047b6eb1581ce6e6d9b0f78be64c448..12013237e7b00f47d2a8660fd09ee3d52fdf084c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1966,7 +1966,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - // Paper end - Add WorldGameRuleChangeEvent - GameRules.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule)); - handle.deserialize(event.getValue()); // Paper - Add WorldGameRuleChangeEvent -- handle.onChanged(this.getHandle().getServer()); -+ handle.onChanged(this.getHandle()); // Paper - Pass ServerLevel for gamerule callbacks - return true; - } - -@@ -2007,7 +2007,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - // Paper end - Add WorldGameRuleChangeEvent - GameRules.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule.getName())); - handle.deserialize(event.getValue()); // Paper - Add WorldGameRuleChangeEvent -- handle.onChanged(this.getHandle().getServer()); -+ handle.onChanged(this.getHandle()); // Paper - Pass ServerLevel for gamerule callbacks - return true; - } - diff --git a/patches/server/0738-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch b/patches/server/0738-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch new file mode 100644 index 000000000000..9606b571bede --- /dev/null +++ b/patches/server/0738-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Feb 2022 13:50:06 -0800 +Subject: [PATCH] Trigger bee_nest_destroyed trigger in the correct place + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 4fe571915b247ec612b2376dce57991e441f63c2..8f4c9b99b638cfce8cc7c55f6369f62e757f4e48 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -421,12 +421,16 @@ public class ServerPlayerGameMode { + block.destroy(this.level, pos, iblockdata1); + } + ++ ItemStack mainHandStack = null; // Paper - Trigger bee_nest_destroyed trigger in the correct place ++ boolean isCorrectTool = false; // Paper - Trigger bee_nest_destroyed trigger in the correct place + if (this.isCreative()) { + // return true; // CraftBukkit + } else { + ItemStack itemstack = this.player.getMainHandItem(); + ItemStack itemstack1 = itemstack.copy(); + boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1); ++ mainHandStack = itemstack1; // Paper - Trigger bee_nest_destroyed trigger in the correct place ++ isCorrectTool = flag1; // Paper - Trigger bee_nest_destroyed trigger in the correct place + + itemstack.mineBlock(this.level, iblockdata1, pos, this.player); + if (flag && flag1 && event.isDropItems()) { // CraftBukkit - Check if block should drop items +@@ -447,6 +451,13 @@ public class ServerPlayerGameMode { + if (flag && event != null) { + iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper + } ++ // Paper start - Trigger bee_nest_destroyed trigger in the correct place (check impls of block#playerDestroy) ++ if (mainHandStack != null) { ++ if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && tileentity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above ++ CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, iblockdata, mainHandStack, beehiveBlockEntity.getOccupantCount()); ++ } ++ } ++ // Paper end - Trigger bee_nest_destroyed trigger in the correct place + + return true; + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +index 27f1c1ac12251f1438ee8bf14f4afb5fe601138f..799e44ae5a7c3d6994653d43d455c39f3e30b012 100644 +--- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +@@ -95,7 +95,7 @@ public class BeehiveBlock extends BaseEntityBlock { + this.angerNearbyBees(world, pos); + } + +- CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount()); ++ // CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount()); // Paper - Trigger bee_nest_destroyed trigger in the correct place; moved until after items are dropped + } + + } diff --git a/patches/server/0743-Add-EntityDyeEvent-and-CollarColorable-interface.patch b/patches/server/0739-Add-EntityDyeEvent-and-CollarColorable-interface.patch similarity index 100% rename from patches/server/0743-Add-EntityDyeEvent-and-CollarColorable-interface.patch rename to patches/server/0739-Add-EntityDyeEvent-and-CollarColorable-interface.patch diff --git a/patches/server/0739-Expand-PlayerItemDamageEvent.patch b/patches/server/0739-Expand-PlayerItemDamageEvent.patch deleted file mode 100644 index a549b4189cdf..000000000000 --- a/patches/server/0739-Expand-PlayerItemDamageEvent.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HexedHero <6012891+HexedHero@users.noreply.github.com> -Date: Sun, 10 Apr 2022 06:26:32 +0100 -Subject: [PATCH] Expand PlayerItemDamageEvent - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 750ffb60c6136fe9e1d56e402578e2a9978d2703..23fec59b51c99c1f0ac19ffd6c84ffa8fc3caaac 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -638,10 +638,11 @@ public final class ItemStack { - } - } - -+ int originalDamage = amount; // Paper - Expand PlayerItemDamageEvent - amount -= k; - // CraftBukkit start - if (player instanceof ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent -- PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); // Paper - Add EntityDamageItemEvent -+ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount, originalDamage); // Paper - Add EntityDamageItemEvent & Expand PlayerItemDamageEvent - event.getPlayer().getServer().getPluginManager().callEvent(event); - - if (amount != event.getDamage() || event.isCancelled()) { diff --git a/patches/server/0744-Fire-CauldronLevelChange-on-initial-fill.patch b/patches/server/0740-Fire-CauldronLevelChange-on-initial-fill.patch similarity index 100% rename from patches/server/0744-Fire-CauldronLevelChange-on-initial-fill.patch rename to patches/server/0740-Fire-CauldronLevelChange-on-initial-fill.patch diff --git a/patches/server/0740-WorldCreator-keepSpawnLoaded.patch b/patches/server/0740-WorldCreator-keepSpawnLoaded.patch deleted file mode 100644 index 6ce0b724a148..000000000000 --- a/patches/server/0740-WorldCreator-keepSpawnLoaded.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sat, 3 Jul 2021 21:18:28 +0100 -Subject: [PATCH] WorldCreator#keepSpawnLoaded - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 001afa881d8c84bfdb7329037d877bd88430b11b..62cecfbc3bf8f52078e18273d7c1ca940cff4105 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1278,7 +1278,7 @@ public final class CraftServer implements Server { - - ServerLevel internal = (ServerLevel) new ServerLevel(this.console, this.console.executor, worldSession, worlddata, worldKey, worlddimension, this.getServer().progressListenerFactory.create(11), - worlddata.isDebugWorld(), j, creator.environment() == Environment.NORMAL ? list : ImmutableList.of(), true, this.console.overworld().getRandomSequences(), creator.environment(), generator, biomeProvider); -- internal.keepSpawnInMemory = creator.keepSpawnInMemory(); -+ // internal.keepSpawnInMemory = creator.keepSpawnInMemory(); // Paper - replace - - if (!(this.worlds.containsKey(name.toLowerCase(java.util.Locale.ENGLISH)))) { - return null; -@@ -1290,6 +1290,7 @@ public final class CraftServer implements Server { - internal.setSpawnSettings(true, true); - // Paper - Put world into worldlist before initing the world; move up - -+ internal.keepSpawnInMemory = creator.keepSpawnLoaded().toBooleanOrElse(internal.getWorld().getKeepSpawnInMemory()); // Paper - this.getServer().prepareLevels(internal.getChunkSource().chunkMap.progressListener, internal); - internal.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API - diff --git a/patches/server/0745-fix-powder-snow-cauldrons-not-turning-to-water.patch b/patches/server/0741-fix-powder-snow-cauldrons-not-turning-to-water.patch similarity index 100% rename from patches/server/0745-fix-powder-snow-cauldrons-not-turning-to-water.patch rename to patches/server/0741-fix-powder-snow-cauldrons-not-turning-to-water.patch diff --git a/patches/server/0742-Add-PlayerStopUsingItemEvent.patch b/patches/server/0742-Add-PlayerStopUsingItemEvent.patch new file mode 100644 index 000000000000..93eab4e15b53 --- /dev/null +++ b/patches/server/0742-Add-PlayerStopUsingItemEvent.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: u9g +Date: Tue, 3 May 2022 20:41:37 -0400 +Subject: [PATCH] Add PlayerStopUsingItemEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 5adfa199835f8ee6b4db16c1bf262bb43324e654..e3b19c6e51596b854b6d2acbc67f6fb109d62a92 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -4037,6 +4037,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + + public void releaseUsingItem() { + if (!this.useItem.isEmpty()) { ++ if (this instanceof ServerPlayer) new io.papermc.paper.event.player.PlayerStopUsingItemEvent((Player) getBukkitEntity(), useItem.asBukkitMirror(), getTicksUsingItem()).callEvent(); // Paper - Add PlayerStopUsingItemEvent + this.useItem.releaseUsing(this.level(), this, this.getUseItemRemainingTicks()); + if (this.useItem.useOnRelease()) { + this.updatingUsingItem(); diff --git a/patches/server/0742-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch b/patches/server/0742-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch deleted file mode 100644 index 88ff6e154663..000000000000 --- a/patches/server/0742-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 2 Feb 2022 13:50:06 -0800 -Subject: [PATCH] Trigger bee_nest_destroyed trigger in the correct place - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 37513cf5714afda6c552219c2eca8134c054d2bb..be25ea71cb3a3bd324935754604c9f7473a88d0a 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -421,12 +421,16 @@ public class ServerPlayerGameMode { - block.destroy(this.level, pos, iblockdata1); - } - -+ ItemStack mainHandStack = null; // Paper - Trigger bee_nest_destroyed trigger in the correct place -+ boolean isCorrectTool = false; // Paper - Trigger bee_nest_destroyed trigger in the correct place - if (this.isCreative()) { - // return true; // CraftBukkit - } else { - ItemStack itemstack = this.player.getMainHandItem(); - ItemStack itemstack1 = itemstack.copy(); - boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1); -+ mainHandStack = itemstack1; // Paper - Trigger bee_nest_destroyed trigger in the correct place -+ isCorrectTool = flag1; // Paper - Trigger bee_nest_destroyed trigger in the correct place - - itemstack.mineBlock(this.level, iblockdata1, pos, this.player); - if (flag && flag1 && event.isDropItems()) { // CraftBukkit - Check if block should drop items -@@ -447,6 +451,13 @@ public class ServerPlayerGameMode { - if (flag && event != null) { - iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper - } -+ // Paper start - Trigger bee_nest_destroyed trigger in the correct place (check impls of block#playerDestroy) -+ if (mainHandStack != null) { -+ if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && tileentity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above -+ CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, iblockdata, mainHandStack, beehiveBlockEntity.getOccupantCount()); -+ } -+ } -+ // Paper end - Trigger bee_nest_destroyed trigger in the correct place - - return true; - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -index 65d3311cfa251b0d3246413d0714acff7d31e844..a0ab721a01faccf216259c46e6d6f638426732c2 100644 ---- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -@@ -95,7 +95,7 @@ public class BeehiveBlock extends BaseEntityBlock { - this.angerNearbyBees(world, pos); - } - -- CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount()); -+ // CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount()); // Paper - Trigger bee_nest_destroyed trigger in the correct place; moved until after items are dropped - } - - } diff --git a/patches/server/0747-Don-t-tick-markers.patch b/patches/server/0743-Don-t-tick-markers.patch similarity index 100% rename from patches/server/0747-Don-t-tick-markers.patch rename to patches/server/0743-Don-t-tick-markers.patch diff --git a/patches/server/0744-Expand-FallingBlock-API.patch b/patches/server/0744-Expand-FallingBlock-API.patch new file mode 100644 index 000000000000..b70e2528225d --- /dev/null +++ b/patches/server/0744-Expand-FallingBlock-API.patch @@ -0,0 +1,107 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 5 Dec 2021 14:58:17 -0500 +Subject: [PATCH] Expand FallingBlock API + +- add auto expire setting +- add setter for block data +- add accessors for block state + +== AT == +public net.minecraft.world.entity.item.FallingBlockEntity blockState + +Co-authored-by: Lukas Planz + +diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +index 67875e854d66b62c36fcca455f02f5abf3ebfdb3..05b77bf1af82397c542fde19b54ee545448ce12e 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -66,6 +66,7 @@ public class FallingBlockEntity extends Entity { + @Nullable + public CompoundTag blockData; + protected static final EntityDataAccessor DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS); ++ public boolean autoExpire = true; // Paper - Expand FallingBlock API + + public FallingBlockEntity(EntityType type, Level world) { + super(type, world); +@@ -178,7 +179,7 @@ public class FallingBlockEntity extends Entity { + } + + if (!this.onGround() && !flag1) { +- if (!this.level().isClientSide && (this.time > 100 && (blockposition.getY() <= this.level().getMinBuildHeight() || blockposition.getY() > this.level().getMaxBuildHeight()) || this.time > 600)) { ++ if (!this.level().isClientSide && ((this.time > 100 && autoExpire) && (blockposition.getY() <= this.level().getMinBuildHeight() || blockposition.getY() > this.level().getMaxBuildHeight()) || (this.time > 600 && autoExpire))) { // Paper - Expand FallingBlock API + if (this.dropItem && this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { + this.spawnAtLocation((ItemLike) block); + } +@@ -324,6 +325,7 @@ public class FallingBlockEntity extends Entity { + } + + nbt.putBoolean("CancelDrop", this.cancelDrop); ++ if (!autoExpire) {nbt.putBoolean("Paper.AutoExpire", false);} // Paper - Expand FallingBlock API + } + + @Override +@@ -351,6 +353,11 @@ public class FallingBlockEntity extends Entity { + this.blockState = Blocks.SAND.defaultBlockState(); + } + ++ // Paper start - Expand FallingBlock API ++ if (nbt.contains("Paper.AutoExpire")) { ++ this.autoExpire = nbt.getBoolean("Paper.AutoExpire"); ++ } ++ // Paper end - Expand FallingBlock API + } + + public void setHurtsEntities(float fallHurtAmount, int fallHurtMax) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +index a7a3f74b846112d752fe04162b30805961457b11..1359d25a32b4a5d5e8e68ce737bd19f7b5afaf69 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +@@ -33,6 +33,31 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { + public BlockData getBlockData() { + return CraftBlockData.fromData(this.getHandle().getBlockState()); + } ++ // Paper start - Expand FallingBlock API ++ @Override ++ public void setBlockData(final BlockData blockData) { ++ Preconditions.checkArgument(blockData != null, "blockData"); ++ final net.minecraft.world.level.block.state.BlockState oldState = this.getHandle().blockState, newState = ((CraftBlockData) blockData).getState(); ++ this.getHandle().blockState = newState; ++ this.getHandle().blockData = null; ++ ++ if (oldState != newState) this.update(); ++ } ++ ++ @Override ++ public org.bukkit.block.BlockState getBlockState() { ++ return org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.getHandle().blockState, this.getHandle().blockData); ++ } ++ ++ @Override ++ public void setBlockState(final org.bukkit.block.BlockState blockState) { ++ Preconditions.checkArgument(blockState != null, "blockState"); ++ // Calls #update if needed, the block data compound tag is not synced with the client and hence can be mutated after the sync with clients. ++ // The call also clears any potential old block data. ++ this.setBlockData(blockState.getBlockData()); ++ if (blockState instanceof final org.bukkit.craftbukkit.block.CraftBlockEntityState tileEntity) this.getHandle().blockData = tileEntity.getSnapshotNBT(); ++ } ++ // Paper end - Expand FallingBlock API + + @Override + public boolean getDropItem() { +@@ -101,4 +126,15 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { + this.setHurtEntities(true); + } + } ++ // Paper start - Expand FallingBlock API ++ @Override ++ public boolean doesAutoExpire() { ++ return this.getHandle().autoExpire; ++ } ++ ++ @Override ++ public void shouldAutoExpire(boolean autoExpires) { ++ this.getHandle().autoExpire = autoExpires; ++ } ++ // Paper end - Expand FallingBlock API + } diff --git a/patches/server/0749-Add-support-for-Proxy-Protocol.patch b/patches/server/0745-Add-support-for-Proxy-Protocol.patch similarity index 100% rename from patches/server/0749-Add-support-for-Proxy-Protocol.patch rename to patches/server/0745-Add-support-for-Proxy-Protocol.patch diff --git a/patches/server/0746-Add-PlayerStopUsingItemEvent.patch b/patches/server/0746-Add-PlayerStopUsingItemEvent.patch deleted file mode 100644 index dda50b38461e..000000000000 --- a/patches/server/0746-Add-PlayerStopUsingItemEvent.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: u9g -Date: Tue, 3 May 2022 20:41:37 -0400 -Subject: [PATCH] Add PlayerStopUsingItemEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index fbbddf856a0fbb0b88bdbf00fed79ebeb9f85a33..f5bed73a079e022eeb3b05e4c49532044852fd22 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -4024,6 +4024,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - public void releaseUsingItem() { - if (!this.useItem.isEmpty()) { -+ if (this instanceof ServerPlayer) new io.papermc.paper.event.player.PlayerStopUsingItemEvent((Player) getBukkitEntity(), useItem.asBukkitMirror(), getTicksUsingItem()).callEvent(); // Paper - Add PlayerStopUsingItemEvent - this.useItem.releaseUsing(this.level(), this, this.getUseItemRemainingTicks()); - if (this.useItem.useOnRelease()) { - this.updatingUsingItem(); diff --git a/patches/server/0750-Fix-OfflinePlayer-getBedSpawnLocation.patch b/patches/server/0746-Fix-OfflinePlayer-getBedSpawnLocation.patch similarity index 100% rename from patches/server/0750-Fix-OfflinePlayer-getBedSpawnLocation.patch rename to patches/server/0746-Fix-OfflinePlayer-getBedSpawnLocation.patch diff --git a/patches/server/0751-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch b/patches/server/0747-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch similarity index 100% rename from patches/server/0751-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch rename to patches/server/0747-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch diff --git a/patches/server/0748-Expand-FallingBlock-API.patch b/patches/server/0748-Expand-FallingBlock-API.patch deleted file mode 100644 index 16de3bcc75d9..000000000000 --- a/patches/server/0748-Expand-FallingBlock-API.patch +++ /dev/null @@ -1,107 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 5 Dec 2021 14:58:17 -0500 -Subject: [PATCH] Expand FallingBlock API - -- add auto expire setting -- add setter for block data -- add accessors for block state - -== AT == -public net.minecraft.world.entity.item.FallingBlockEntity blockState - -Co-authored-by: Lukas Planz - -diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -index 2452c7f0a3ed1faf9b90351bea3389382c677d05..9daf8aa557d9f4fdbcc138a47892ea5a061dd877 100644 ---- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -@@ -66,6 +66,7 @@ public class FallingBlockEntity extends Entity { - @Nullable - public CompoundTag blockData; - protected static final EntityDataAccessor DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS); -+ public boolean autoExpire = true; // Paper - Expand FallingBlock API - - public FallingBlockEntity(EntityType type, Level world) { - super(type, world); -@@ -178,7 +179,7 @@ public class FallingBlockEntity extends Entity { - } - - if (!this.onGround() && !flag1) { -- if (!this.level().isClientSide && (this.time > 100 && (blockposition.getY() <= this.level().getMinBuildHeight() || blockposition.getY() > this.level().getMaxBuildHeight()) || this.time > 600)) { -+ if (!this.level().isClientSide && ((this.time > 100 && autoExpire) && (blockposition.getY() <= this.level().getMinBuildHeight() || blockposition.getY() > this.level().getMaxBuildHeight()) || (this.time > 600 && autoExpire))) { // Paper - Expand FallingBlock API - if (this.dropItem && this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { - this.spawnAtLocation((ItemLike) block); - } -@@ -326,6 +327,7 @@ public class FallingBlockEntity extends Entity { - } - - nbt.putBoolean("CancelDrop", this.cancelDrop); -+ if (!autoExpire) {nbt.putBoolean("Paper.AutoExpire", false);} // Paper - Expand FallingBlock API - } - - @Override -@@ -353,6 +355,11 @@ public class FallingBlockEntity extends Entity { - this.blockState = Blocks.SAND.defaultBlockState(); - } - -+ // Paper start - Expand FallingBlock API -+ if (nbt.contains("Paper.AutoExpire")) { -+ this.autoExpire = nbt.getBoolean("Paper.AutoExpire"); -+ } -+ // Paper end - Expand FallingBlock API - } - - public void setHurtsEntities(float fallHurtAmount, int fallHurtMax) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java -index a7a3f74b846112d752fe04162b30805961457b11..1359d25a32b4a5d5e8e68ce737bd19f7b5afaf69 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java -@@ -33,6 +33,31 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { - public BlockData getBlockData() { - return CraftBlockData.fromData(this.getHandle().getBlockState()); - } -+ // Paper start - Expand FallingBlock API -+ @Override -+ public void setBlockData(final BlockData blockData) { -+ Preconditions.checkArgument(blockData != null, "blockData"); -+ final net.minecraft.world.level.block.state.BlockState oldState = this.getHandle().blockState, newState = ((CraftBlockData) blockData).getState(); -+ this.getHandle().blockState = newState; -+ this.getHandle().blockData = null; -+ -+ if (oldState != newState) this.update(); -+ } -+ -+ @Override -+ public org.bukkit.block.BlockState getBlockState() { -+ return org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.getHandle().blockState, this.getHandle().blockData); -+ } -+ -+ @Override -+ public void setBlockState(final org.bukkit.block.BlockState blockState) { -+ Preconditions.checkArgument(blockState != null, "blockState"); -+ // Calls #update if needed, the block data compound tag is not synced with the client and hence can be mutated after the sync with clients. -+ // The call also clears any potential old block data. -+ this.setBlockData(blockState.getBlockData()); -+ if (blockState instanceof final org.bukkit.craftbukkit.block.CraftBlockEntityState tileEntity) this.getHandle().blockData = tileEntity.getSnapshotNBT(); -+ } -+ // Paper end - Expand FallingBlock API - - @Override - public boolean getDropItem() { -@@ -101,4 +126,15 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { - this.setHurtEntities(true); - } - } -+ // Paper start - Expand FallingBlock API -+ @Override -+ public boolean doesAutoExpire() { -+ return this.getHandle().autoExpire; -+ } -+ -+ @Override -+ public void shouldAutoExpire(boolean autoExpires) { -+ this.getHandle().autoExpire = autoExpires; -+ } -+ // Paper end - Expand FallingBlock API - } diff --git a/patches/server/0748-properly-read-and-store-sus-effect-duration.patch b/patches/server/0748-properly-read-and-store-sus-effect-duration.patch new file mode 100644 index 000000000000..94589408f489 --- /dev/null +++ b/patches/server/0748-properly-read-and-store-sus-effect-duration.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 18 Dec 2023 20:05:50 -0800 +Subject: [PATCH] properly read and store sus effect duration + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java +index e13146b71552ab3f9ae867110650fe3977563dfb..2c3b9f76067088efdc2250cdb5070df86e2dc0f5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java +@@ -49,7 +49,14 @@ public class CraftMetaSuspiciousStew extends CraftMetaItem implements Suspicious + if (type == null) { + continue; + } +- int duration = effect.getInt(CraftMetaSuspiciousStew.DURATION.NBT); ++ // Paper start - default duration is 160 ++ final int duration; ++ if (effect.contains(CraftMetaSuspiciousStew.DURATION.NBT)) { ++ duration = effect.getInt(CraftMetaSuspiciousStew.DURATION.NBT); ++ } else { ++ duration = net.minecraft.world.item.SuspiciousStewItem.DEFAULT_DURATION; ++ } ++ // Paper end start - default duration is 160 + this.customEffects.add(new PotionEffect(type, duration, 0)); + } + } +@@ -80,7 +87,7 @@ public class CraftMetaSuspiciousStew extends CraftMetaItem implements Suspicious + for (PotionEffect effect : this.customEffects) { + CompoundTag effectData = new CompoundTag(); + effectData.putString(CraftMetaSuspiciousStew.ID.NBT, effect.getType().getKey().toString()); +- effectData.putInt(CraftMetaSuspiciousStew.DURATION.NBT, effect.getDuration()); ++ if (effect.getDuration() != net.minecraft.world.item.SuspiciousStewItem.DEFAULT_DURATION) effectData.putInt(CraftMetaSuspiciousStew.DURATION.NBT, effect.getDuration()); // Paper - don't save duration if it's the default value + effectList.add(effectData); + } + } diff --git a/patches/server/0753-Sanitize-sent-BlockEntity-NBT.patch b/patches/server/0749-Sanitize-sent-BlockEntity-NBT.patch similarity index 100% rename from patches/server/0753-Sanitize-sent-BlockEntity-NBT.patch rename to patches/server/0749-Sanitize-sent-BlockEntity-NBT.patch diff --git a/patches/server/0754-Disable-component-selector-resolving-in-books-by-def.patch b/patches/server/0750-Disable-component-selector-resolving-in-books-by-def.patch similarity index 100% rename from patches/server/0754-Disable-component-selector-resolving-in-books-by-def.patch rename to patches/server/0750-Disable-component-selector-resolving-in-books-by-def.patch diff --git a/patches/server/0751-Prevent-entity-loading-causing-async-lookups.patch b/patches/server/0751-Prevent-entity-loading-causing-async-lookups.patch new file mode 100644 index 000000000000..5a3cc46ff7aa --- /dev/null +++ b/patches/server/0751-Prevent-entity-loading-causing-async-lookups.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 6 Mar 2022 11:09:09 -0500 +Subject: [PATCH] Prevent entity loading causing async lookups + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 5d70e5fe31d0f1f18351d63e060973adc9a49f46..291c65d9b1af48c677360a9da1bd10c1fedbb686 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -714,6 +714,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + + public void baseTick() { + this.level().getProfiler().push("entityBaseTick"); ++ if (firstTick && this instanceof net.minecraft.world.entity.NeutralMob neutralMob) neutralMob.tickInitialPersistentAnger(level); // Paper - Prevent entity loading causing async lookups + this.feetBlockState = null; + if (this.isPassenger() && this.getVehicle().isRemoved()) { + this.stopRiding(); +diff --git a/src/main/java/net/minecraft/world/entity/NeutralMob.java b/src/main/java/net/minecraft/world/entity/NeutralMob.java +index fa64c7baa7587f2cfe80b78ed83be011239618cf..47d5a0928f3c86d71f851738bfe7beedc98cfbb3 100644 +--- a/src/main/java/net/minecraft/world/entity/NeutralMob.java ++++ b/src/main/java/net/minecraft/world/entity/NeutralMob.java +@@ -42,18 +42,11 @@ public interface NeutralMob { + UUID uuid = nbt.getUUID("AngryAt"); + + this.setPersistentAngerTarget(uuid); +- Entity entity = ((ServerLevel) world).getEntity(uuid); +- +- if (entity != null) { +- if (entity instanceof Mob) { +- this.setLastHurtByMob((Mob) entity); +- } +- +- if (entity.getType() == EntityType.PLAYER) { +- this.setLastHurtByPlayer((Player) entity); +- } +- +- } ++ // Paper - Prevent entity loading causing async lookups; Moved diff to separate method ++ // If this entity already survived its first tick, e.g. is loaded and ticked in sync, actively ++ // tick the initial persistent anger. ++ // If not, let the first tick on the baseTick call the method later down the line. ++ if (this instanceof Entity entity && !entity.firstTick) this.tickInitialPersistentAnger(world); + } + } + } +@@ -127,4 +120,27 @@ public interface NeutralMob { + + @Nullable + LivingEntity getTarget(); ++ ++ // Paper start - Prevent entity loading causing async lookups ++ // Update last hurt when ticking ++ default void tickInitialPersistentAnger(Level level) { ++ UUID target = getPersistentAngerTarget(); ++ if (target == null) { ++ return; ++ } ++ ++ Entity entity = ((ServerLevel) level).getEntity(target); ++ ++ if (entity != null) { ++ if (entity instanceof Mob) { ++ this.setLastHurtByMob((Mob) entity); ++ } ++ ++ if (entity.getType() == EntityType.PLAYER) { ++ this.setLastHurtByPlayer((Player) entity); ++ } ++ ++ } ++ } ++ // Paper end - Prevent entity loading causing async lookups + } diff --git a/patches/server/0752-Throw-exception-on-world-create-while-being-ticked.patch b/patches/server/0752-Throw-exception-on-world-create-while-being-ticked.patch new file mode 100644 index 000000000000..34fcf49e254e --- /dev/null +++ b/patches/server/0752-Throw-exception-on-world-create-while-being-ticked.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 22 Mar 2022 12:44:30 -0700 +Subject: [PATCH] Throw exception on world create while being ticked + +There are no plans to support creating worlds while worlds are +being ticked themselvess. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 8080eee9babe02660724eee756700a4d5e69014f..ab1f4e62b2ffed99b47ae23cae172f20ed586b27 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -307,6 +307,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { + AtomicReference atomicreference = new AtomicReference(); +@@ -1501,7 +1502,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent +@@ -1571,6 +1574,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop(this.worlds.values()); + } + ++ @Override ++ public boolean isTickingWorlds() { ++ return console.isIteratingOverLevels; ++ } ++ + public DedicatedPlayerList getHandle() { + return this.playerList; + } +@@ -1145,6 +1150,7 @@ public final class CraftServer implements Server { + @Override + public World createWorld(WorldCreator creator) { + Preconditions.checkState(this.console.getAllLevels().iterator().hasNext(), "Cannot create additional worlds on STARTUP"); ++ //Preconditions.checkState(!this.console.isIteratingOverLevels, "Cannot create a world while worlds are being ticked"); // Paper - Cat - Temp disable. We'll see how this goes. + Preconditions.checkArgument(creator != null, "WorldCreator cannot be null"); + + String name = creator.name(); +@@ -1317,6 +1323,7 @@ public final class CraftServer implements Server { + + @Override + public boolean unloadWorld(World world, boolean save) { ++ //Preconditions.checkState(!this.console.isIteratingOverLevels, "Cannot unload a world while worlds are being ticked"); // Paper - Cat - Temp disable. We'll see how this goes. + if (world == null) { + return false; + } diff --git a/patches/server/0752-properly-read-and-store-sus-effect-duration.patch b/patches/server/0752-properly-read-and-store-sus-effect-duration.patch deleted file mode 100644 index 20754fe9a1a4..000000000000 --- a/patches/server/0752-properly-read-and-store-sus-effect-duration.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 18 Dec 2023 20:05:50 -0800 -Subject: [PATCH] properly read and store sus effect duration - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java -index df1df2ad759622b5b1355fae322cbc0333c932fb..adb804f6f81f35ceeb1dc36157c70267136a1e81 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSuspiciousStew.java -@@ -49,7 +49,14 @@ public class CraftMetaSuspiciousStew extends CraftMetaItem implements Suspicious - if (type == null) { - continue; - } -- int duration = effect.getInt(CraftMetaSuspiciousStew.DURATION.NBT); -+ // Paper start - default duration is 160 -+ final int duration; -+ if (effect.contains(CraftMetaSuspiciousStew.DURATION.NBT)) { -+ duration = effect.getInt(CraftMetaSuspiciousStew.DURATION.NBT); -+ } else { -+ duration = net.minecraft.world.item.SuspiciousStewItem.DEFAULT_DURATION; -+ } -+ // Paper end start - default duration is 160 - this.customEffects.add(new PotionEffect(type, duration, 0)); - } - } -@@ -80,7 +87,7 @@ public class CraftMetaSuspiciousStew extends CraftMetaItem implements Suspicious - for (PotionEffect effect : this.customEffects) { - CompoundTag effectData = new CompoundTag(); - effectData.putString(CraftMetaSuspiciousStew.ID.NBT, effect.getType().getKey().toString()); -- effectData.putInt(CraftMetaSuspiciousStew.DURATION.NBT, effect.getDuration()); -+ if (effect.getDuration() != net.minecraft.world.item.SuspiciousStewItem.DEFAULT_DURATION) effectData.putInt(CraftMetaSuspiciousStew.DURATION.NBT, effect.getDuration()); // Paper - don't save duration if it's the default value - effectList.add(effectData); - } - } diff --git a/patches/server/0757-Dont-resent-entity-on-art-update.patch b/patches/server/0753-Dont-resent-entity-on-art-update.patch similarity index 100% rename from patches/server/0757-Dont-resent-entity-on-art-update.patch rename to patches/server/0753-Dont-resent-entity-on-art-update.patch diff --git a/patches/server/0758-Add-WardenAngerChangeEvent.patch b/patches/server/0754-Add-WardenAngerChangeEvent.patch similarity index 100% rename from patches/server/0758-Add-WardenAngerChangeEvent.patch rename to patches/server/0754-Add-WardenAngerChangeEvent.patch diff --git a/patches/server/0755-Add-option-for-strict-advancement-dimension-checks.patch b/patches/server/0755-Add-option-for-strict-advancement-dimension-checks.patch new file mode 100644 index 000000000000..3dd4e6d49002 --- /dev/null +++ b/patches/server/0755-Add-option-for-strict-advancement-dimension-checks.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 12 Jun 2022 11:47:24 -0700 +Subject: [PATCH] Add option for strict advancement dimension checks + +Craftbukkit attempts to translate worlds that use the +same generation as the Overworld, The Nether, or The End +to use those dimensions when checking the `changed_dimension` +criteria trigger, or whether to trigger the `NETHER_TRAVEL` +distance trigger. This adds a config option to ignore that +and use the exact dimension key of the worlds involved. + +diff --git a/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java b/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java +index 5f9cb2c7a2874e423087d04d3360af0364692b5c..428e0afef2cac9f2a19d8cfe8f2504ddd8e50887 100644 +--- a/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java ++++ b/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java +@@ -25,7 +25,7 @@ public record LocationPredicate(Optional po + public boolean matches(ServerLevel world, double x, double y, double z) { + if (this.position.isPresent() && !this.position.get().matches(x, y, z)) { + return false; +- } else if (this.dimension.isPresent() && this.dimension.get() != world.dimension()) { ++ } else if (this.dimension.isPresent() && this.dimension.get() != (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck ? world.dimension() : org.bukkit.craftbukkit.util.CraftDimensionUtil.getMainDimensionKey(world))) { // Paper - Add option for strict advancement dimension checks + return false; + } else { + BlockPos blockPos = BlockPos.containing(x, y, z); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 0db3f5b06b6c93882761450ea77ba4ee09869c61..bc95420dcf5d23d028e5df6595899a8712550be8 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1272,6 +1272,12 @@ public class ServerPlayer extends Player { + ResourceKey maindimensionkey = CraftDimensionUtil.getMainDimensionKey(origin); + ResourceKey maindimensionkey1 = CraftDimensionUtil.getMainDimensionKey(this.level()); + ++ // Paper start - Add option for strict advancement dimension checks ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck) { ++ maindimensionkey = resourcekey; ++ maindimensionkey1 = resourcekey1; ++ } ++ // Paper end - Add option for strict advancement dimension checks + CriteriaTriggers.CHANGED_DIMENSION.trigger(this, maindimensionkey, maindimensionkey1); + if (maindimensionkey != resourcekey || maindimensionkey1 != resourcekey1) { + CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourcekey, resourcekey1); diff --git a/patches/server/0755-Prevent-entity-loading-causing-async-lookups.patch b/patches/server/0755-Prevent-entity-loading-causing-async-lookups.patch deleted file mode 100644 index c3dc6168549c..000000000000 --- a/patches/server/0755-Prevent-entity-loading-causing-async-lookups.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 6 Mar 2022 11:09:09 -0500 -Subject: [PATCH] Prevent entity loading causing async lookups - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index b580bb939f3c128ed68e02f2c75764aed49427c6..467f269107f314ebc3af1c4a1852f239906c0ba4 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -714,6 +714,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - - public void baseTick() { - this.level().getProfiler().push("entityBaseTick"); -+ if (firstTick && this instanceof net.minecraft.world.entity.NeutralMob neutralMob) neutralMob.tickInitialPersistentAnger(level); // Paper - Prevent entity loading causing async lookups - this.feetBlockState = null; - if (this.isPassenger() && this.getVehicle().isRemoved()) { - this.stopRiding(); -diff --git a/src/main/java/net/minecraft/world/entity/NeutralMob.java b/src/main/java/net/minecraft/world/entity/NeutralMob.java -index fa64c7baa7587f2cfe80b78ed83be011239618cf..47d5a0928f3c86d71f851738bfe7beedc98cfbb3 100644 ---- a/src/main/java/net/minecraft/world/entity/NeutralMob.java -+++ b/src/main/java/net/minecraft/world/entity/NeutralMob.java -@@ -42,18 +42,11 @@ public interface NeutralMob { - UUID uuid = nbt.getUUID("AngryAt"); - - this.setPersistentAngerTarget(uuid); -- Entity entity = ((ServerLevel) world).getEntity(uuid); -- -- if (entity != null) { -- if (entity instanceof Mob) { -- this.setLastHurtByMob((Mob) entity); -- } -- -- if (entity.getType() == EntityType.PLAYER) { -- this.setLastHurtByPlayer((Player) entity); -- } -- -- } -+ // Paper - Prevent entity loading causing async lookups; Moved diff to separate method -+ // If this entity already survived its first tick, e.g. is loaded and ticked in sync, actively -+ // tick the initial persistent anger. -+ // If not, let the first tick on the baseTick call the method later down the line. -+ if (this instanceof Entity entity && !entity.firstTick) this.tickInitialPersistentAnger(world); - } - } - } -@@ -127,4 +120,27 @@ public interface NeutralMob { - - @Nullable - LivingEntity getTarget(); -+ -+ // Paper start - Prevent entity loading causing async lookups -+ // Update last hurt when ticking -+ default void tickInitialPersistentAnger(Level level) { -+ UUID target = getPersistentAngerTarget(); -+ if (target == null) { -+ return; -+ } -+ -+ Entity entity = ((ServerLevel) level).getEntity(target); -+ -+ if (entity != null) { -+ if (entity instanceof Mob) { -+ this.setLastHurtByMob((Mob) entity); -+ } -+ -+ if (entity.getType() == EntityType.PLAYER) { -+ this.setLastHurtByPlayer((Player) entity); -+ } -+ -+ } -+ } -+ // Paper end - Prevent entity loading causing async lookups - } diff --git a/patches/server/0760-Add-missing-important-BlockStateListPopulator-method.patch b/patches/server/0756-Add-missing-important-BlockStateListPopulator-method.patch similarity index 100% rename from patches/server/0760-Add-missing-important-BlockStateListPopulator-method.patch rename to patches/server/0756-Add-missing-important-BlockStateListPopulator-method.patch diff --git a/patches/server/0756-Throw-exception-on-world-create-while-being-ticked.patch b/patches/server/0756-Throw-exception-on-world-create-while-being-ticked.patch deleted file mode 100644 index 2b8a3df3d081..000000000000 --- a/patches/server/0756-Throw-exception-on-world-create-while-being-ticked.patch +++ /dev/null @@ -1,78 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 22 Mar 2022 12:44:30 -0700 -Subject: [PATCH] Throw exception on world create while being ticked - -There are no plans to support creating worlds while worlds are -being ticked themselvess. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 8080eee9babe02660724eee756700a4d5e69014f..ab1f4e62b2ffed99b47ae23cae172f20ed586b27 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -307,6 +307,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { - AtomicReference atomicreference = new AtomicReference(); -@@ -1501,7 +1502,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent -@@ -1571,6 +1574,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop(this.worlds.values()); - } - -+ @Override -+ public boolean isTickingWorlds() { -+ return console.isIteratingOverLevels; -+ } -+ - public DedicatedPlayerList getHandle() { - return this.playerList; - } -@@ -1133,6 +1138,7 @@ public final class CraftServer implements Server { - @Override - public World createWorld(WorldCreator creator) { - Preconditions.checkState(this.console.getAllLevels().iterator().hasNext(), "Cannot create additional worlds on STARTUP"); -+ //Preconditions.checkState(!this.console.isIteratingOverLevels, "Cannot create a world while worlds are being ticked"); // Paper - Cat - Temp disable. We'll see how this goes. - Preconditions.checkArgument(creator != null, "WorldCreator cannot be null"); - - String name = creator.name(); -@@ -1305,6 +1311,7 @@ public final class CraftServer implements Server { - - @Override - public boolean unloadWorld(World world, boolean save) { -+ //Preconditions.checkState(!this.console.isIteratingOverLevels, "Cannot unload a world while worlds are being ticked"); // Paper - Cat - Temp disable. We'll see how this goes. - if (world == null) { - return false; - } diff --git a/patches/server/0761-Nameable-Banner-API.patch b/patches/server/0757-Nameable-Banner-API.patch similarity index 100% rename from patches/server/0761-Nameable-Banner-API.patch rename to patches/server/0757-Nameable-Banner-API.patch diff --git a/patches/server/0758-Don-t-broadcast-messages-to-command-blocks.patch b/patches/server/0758-Don-t-broadcast-messages-to-command-blocks.patch new file mode 100644 index 000000000000..fa6488648ddb --- /dev/null +++ b/patches/server/0758-Don-t-broadcast-messages-to-command-blocks.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 16 Jun 2022 14:22:56 -0700 +Subject: [PATCH] Don't broadcast messages to command blocks + +Previously the broadcast method would update the last output +in command blocks, and if called asynchronously, would throw +an error + +diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +index c56f5173fda6b38c2dcaea196217f2f5a7d7c641..524b0f1086c01888fe0b76e180c40915d16a1eb9 100644 +--- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java ++++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +@@ -172,6 +172,7 @@ public abstract class BaseCommandBlock implements CommandSource { + @Override + public void sendSystemMessage(Component message) { + if (this.trackOutput) { ++ org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper - Don't broadcast messages to command blocks + SimpleDateFormat simpledateformat = BaseCommandBlock.TIME_FORMAT; + Date date = new Date(); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 562953515e21adc1cffadfe3ffb803c2d5059df0..e849e706660bf0b0ef1748dfee3ba75e5aff9863 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1880,7 +1880,7 @@ public final class CraftServer implements Server { + // Paper end + Set recipients = new HashSet<>(); + for (Permissible permissible : this.getPluginManager().getPermissionSubscriptions(permission)) { +- if (permissible instanceof CommandSender && permissible.hasPermission(permission)) { ++ if (permissible instanceof CommandSender && !(permissible instanceof org.bukkit.command.BlockCommandSender) && permissible.hasPermission(permission)) { // Paper - Don't broadcast messages to command blocks + recipients.add((CommandSender) permissible); + } + } diff --git a/patches/server/0759-Add-option-for-strict-advancement-dimension-checks.patch b/patches/server/0759-Add-option-for-strict-advancement-dimension-checks.patch deleted file mode 100644 index c99f644dd897..000000000000 --- a/patches/server/0759-Add-option-for-strict-advancement-dimension-checks.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 12 Jun 2022 11:47:24 -0700 -Subject: [PATCH] Add option for strict advancement dimension checks - -Craftbukkit attempts to translate worlds that use the -same generation as the Overworld, The Nether, or The End -to use those dimensions when checking the `changed_dimension` -criteria trigger, or whether to trigger the `NETHER_TRAVEL` -distance trigger. This adds a config option to ignore that -and use the exact dimension key of the worlds involved. - -diff --git a/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java b/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java -index 5f9cb2c7a2874e423087d04d3360af0364692b5c..428e0afef2cac9f2a19d8cfe8f2504ddd8e50887 100644 ---- a/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java -+++ b/src/main/java/net/minecraft/advancements/critereon/LocationPredicate.java -@@ -25,7 +25,7 @@ public record LocationPredicate(Optional po - public boolean matches(ServerLevel world, double x, double y, double z) { - if (this.position.isPresent() && !this.position.get().matches(x, y, z)) { - return false; -- } else if (this.dimension.isPresent() && this.dimension.get() != world.dimension()) { -+ } else if (this.dimension.isPresent() && this.dimension.get() != (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck ? world.dimension() : org.bukkit.craftbukkit.util.CraftDimensionUtil.getMainDimensionKey(world))) { // Paper - Add option for strict advancement dimension checks - return false; - } else { - BlockPos blockPos = BlockPos.containing(x, y, z); -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 6d1093972ffcee52c9f94c97c4f842b66726647c..efd95132301f50cd159339444e48cd4988accb1b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1272,6 +1272,12 @@ public class ServerPlayer extends Player { - ResourceKey maindimensionkey = CraftDimensionUtil.getMainDimensionKey(origin); - ResourceKey maindimensionkey1 = CraftDimensionUtil.getMainDimensionKey(this.level()); - -+ // Paper start - Add option for strict advancement dimension checks -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck) { -+ maindimensionkey = resourcekey; -+ maindimensionkey1 = resourcekey1; -+ } -+ // Paper end - Add option for strict advancement dimension checks - CriteriaTriggers.CHANGED_DIMENSION.trigger(this, maindimensionkey, maindimensionkey1); - if (maindimensionkey != resourcekey || maindimensionkey1 != resourcekey1) { - CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourcekey, resourcekey1); diff --git a/patches/server/0763-Prevent-empty-items-from-being-added-to-world.patch b/patches/server/0759-Prevent-empty-items-from-being-added-to-world.patch similarity index 100% rename from patches/server/0763-Prevent-empty-items-from-being-added-to-world.patch rename to patches/server/0759-Prevent-empty-items-from-being-added-to-world.patch diff --git a/patches/server/0764-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch b/patches/server/0760-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch similarity index 100% rename from patches/server/0764-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch rename to patches/server/0760-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch diff --git a/patches/server/0765-Add-Player-getFishHook.patch b/patches/server/0761-Add-Player-getFishHook.patch similarity index 100% rename from patches/server/0765-Add-Player-getFishHook.patch rename to patches/server/0761-Add-Player-getFishHook.patch diff --git a/patches/server/0766-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch b/patches/server/0762-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch similarity index 100% rename from patches/server/0766-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch rename to patches/server/0762-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch diff --git a/patches/server/0762-Don-t-broadcast-messages-to-command-blocks.patch b/patches/server/0762-Don-t-broadcast-messages-to-command-blocks.patch deleted file mode 100644 index 2e33539ca86f..000000000000 --- a/patches/server/0762-Don-t-broadcast-messages-to-command-blocks.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 16 Jun 2022 14:22:56 -0700 -Subject: [PATCH] Don't broadcast messages to command blocks - -Previously the broadcast method would update the last output -in command blocks, and if called asynchronously, would throw -an error - -diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -index c56f5173fda6b38c2dcaea196217f2f5a7d7c641..524b0f1086c01888fe0b76e180c40915d16a1eb9 100644 ---- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -+++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -@@ -172,6 +172,7 @@ public abstract class BaseCommandBlock implements CommandSource { - @Override - public void sendSystemMessage(Component message) { - if (this.trackOutput) { -+ org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper - Don't broadcast messages to command blocks - SimpleDateFormat simpledateformat = BaseCommandBlock.TIME_FORMAT; - Date date = new Date(); - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 3261c8715aee966d6e7234e60074ad59a841294f..4c343b5b811b87b2c4f40efaaf376368ce73fcdb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1868,7 +1868,7 @@ public final class CraftServer implements Server { - // Paper end - Set recipients = new HashSet<>(); - for (Permissible permissible : this.getPluginManager().getPermissionSubscriptions(permission)) { -- if (permissible instanceof CommandSender && permissible.hasPermission(permission)) { -+ if (permissible instanceof CommandSender && !(permissible instanceof org.bukkit.command.BlockCommandSender) && permissible.hasPermission(permission)) { // Paper - Don't broadcast messages to command blocks - recipients.add((CommandSender) permissible); - } - } diff --git a/patches/server/0763-Add-various-missing-EntityDropItemEvent-calls.patch b/patches/server/0763-Add-various-missing-EntityDropItemEvent-calls.patch new file mode 100644 index 000000000000..957be8bd0563 --- /dev/null +++ b/patches/server/0763-Add-various-missing-EntityDropItemEvent-calls.patch @@ -0,0 +1,88 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 20 Jul 2021 21:35:47 -0700 +Subject: [PATCH] Add various missing EntityDropItemEvent calls + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 824c2f05791e793a1420f9baf743288a05d03cf1..ef1afa30b2fe56fc2f3d9c94c6d673d4c132ef92 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2475,6 +2475,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe + + entityitem.setDefaultPickUpDelay(); ++ // Paper start - Call EntityDropItemEvent ++ return this.spawnAtLocation(entityitem); ++ } ++ } ++ @Nullable ++ public ItemEntity spawnAtLocation(ItemEntity entityitem) { ++ { ++ // Paper end - Call EntityDropItemEvent + // CraftBukkit start + EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); + Bukkit.getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +index fb5e200499a1a73fd40c8b7c81185f48381f49e4..178e1e75fcd0e60a1dd2729a894df08cf4129526 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +@@ -592,7 +592,7 @@ public class Dolphin extends WaterAnimal { + float f2 = 0.02F * Dolphin.this.random.nextFloat(); + + entityitem.setDeltaMovement((double) (0.3F * -Mth.sin(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.cos(f1) * f2), (double) (0.3F * Mth.sin(Dolphin.this.getXRot() * 0.017453292F) * 1.5F), (double) (0.3F * Mth.cos(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.sin(f1) * f2)); +- Dolphin.this.level().addFreshEntity(entityitem); ++ Dolphin.this.spawnAtLocation(entityitem); // Paper - Call EntityDropItemEvent + } + } + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java +index 950e4b476a03fb5c26351a3b4d1578975a0b0ea8..fa0a7e18fed41653b1625cfb12a78d9ee502f2be 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java +@@ -509,14 +509,14 @@ public class Fox extends Animal implements VariantHolder { + entityitem.setPickUpDelay(40); + entityitem.setThrower(this); + this.playSound(SoundEvents.FOX_SPIT, 1.0F, 1.0F); +- this.level().addFreshEntity(entityitem); ++ this.spawnAtLocation(entityitem); // Paper - Call EntityDropItemEvent + } + } + + private void dropItemStack(ItemStack stack) { + ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), stack); + +- this.level().addFreshEntity(entityitem); ++ this.spawnAtLocation(entityitem); // Paper - Call EntityDropItemEvent + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +index ee1b2c1fec4b76a821e1d52fbb07e1f302b2efa1..110dd5418b0512a2f27f0c4d5a5f1812356a6a12 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +@@ -357,8 +357,7 @@ public class Goat extends Animal { + double d2 = (double) Mth.randomBetween(this.random, -0.2F, 0.2F); + ItemEntity entityitem = new ItemEntity(this.level(), vec3d.x(), vec3d.y(), vec3d.z(), itemstack, d0, d1, d2); + +- this.level().addFreshEntity(entityitem); +- return true; ++ return this.spawnAtLocation(entityitem) != null; // Paper - Call EntityDropItemEvent + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java +index 1d9913cbc7fd0c6e29278b02c38703b52af1245b..0e85e3ab58d848b119212fa7d2eb4f92d3efe29b 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java ++++ b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java +@@ -352,8 +352,9 @@ public class Sniffer extends Animal { + + entityitem.setDefaultPickUpDelay(); + this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null); ++ if (this.spawnAtLocation(entityitem) != null) { // Paper - Call EntityDropItemEvent + this.playSound(SoundEvents.SNIFFER_EGG_PLOP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 0.5F); +- world.addFreshEntity(entityitem); ++ } // Paper - Call EntityDropItemEvent + } + + @Override diff --git a/patches/server/0768-Fix-Bee-flower-NPE.patch b/patches/server/0764-Fix-Bee-flower-NPE.patch similarity index 100% rename from patches/server/0768-Fix-Bee-flower-NPE.patch rename to patches/server/0764-Fix-Bee-flower-NPE.patch diff --git a/patches/server/0765-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch b/patches/server/0765-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch new file mode 100644 index 000000000000..2d342dd0a182 --- /dev/null +++ b/patches/server/0765-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Doc +Date: Sun, 17 Jul 2022 11:49:43 -0400 +Subject: [PATCH] Fix Spigot Config not using commands.spam-exclusions + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 819894dabc8e28a499a6ab5420299e14ab072ff8..2f566e05dc100cde49d36e3392a86c7e20b32473 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2299,7 +2299,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + // Spigot end + // this.chatSpamTickCount += 20; +- if (this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { ++ if (counted && this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper - exclude from SpigotConfig.spamExclusions + // CraftBukkit end + this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause + } diff --git a/patches/server/0766-More-Teleport-API.patch b/patches/server/0766-More-Teleport-API.patch new file mode 100644 index 000000000000..1474f1fb9e2b --- /dev/null +++ b/patches/server/0766-More-Teleport-API.patch @@ -0,0 +1,229 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 5 Sep 2021 12:15:59 -0400 +Subject: [PATCH] More Teleport API + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 2f566e05dc100cde49d36e3392a86c7e20b32473..7a3293e1a911de494ac32b946d8f709a52d98cbe 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1558,11 +1558,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + return false; // CraftBukkit - Return event status + } + +- PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause); ++ // Paper start - Teleport API ++ Set relativeFlags = java.util.EnumSet.noneOf(io.papermc.paper.entity.TeleportFlag.Relative.class); ++ for (RelativeMovement relativeArgument : set) { ++ relativeFlags.add(org.bukkit.craftbukkit.entity.CraftPlayer.toApiRelativeFlag(relativeArgument)); ++ } ++ PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause, java.util.Set.copyOf(relativeFlags)); ++ // Paper end - Teleport API + this.cserver.getPluginManager().callEvent(event); + + if (event.isCancelled() || !to.equals(event.getTo())) { +- set.clear(); // Can't relative teleport ++ // set.clear(); // Can't relative teleport // Paper - Teleport API; Now you can! + to = event.isCancelled() ? event.getFrom() : event.getTo(); + d0 = to.getX(); + d1 = to.getY(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index b7df71633527dce2e4f954caee249e3b31b82226..ee1dc74b2f48bf8d684562de895ab631cf792a30 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -218,15 +218,36 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + + @Override + public boolean teleport(Location location, TeleportCause cause) { ++ // Paper start - Teleport passenger API ++ return teleport(location, cause, new io.papermc.paper.entity.TeleportFlag[0]); ++ } ++ ++ @Override ++ public boolean teleport(Location location, TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { ++ // Paper end + Preconditions.checkArgument(location != null, "location cannot be null"); + location.checkFinite(); ++ // Paper start - Teleport passenger API ++ Set flagSet = Set.of(flags); ++ boolean dismount = !flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_VEHICLE); ++ boolean ignorePassengers = flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS); ++ // Don't allow teleporting between worlds while keeping passengers ++ if (flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS) && this.entity.isVehicle() && location.getWorld() != this.getWorld()) { ++ return false; ++ } ++ ++ // Don't allow to teleport between worlds if remaining on vehicle ++ if (!dismount && this.entity.isPassenger() && location.getWorld() != this.getWorld()) { ++ return false; ++ } ++ // Paper end + +- if (this.entity.isVehicle() || this.entity.isRemoved()) { ++ if ((!ignorePassengers && this.entity.isVehicle()) || this.entity.isRemoved()) { // Paper - Teleport passenger API + return false; + } + + // If this entity is riding another entity, we must dismount before teleporting. +- this.entity.stopRiding(); ++ if (dismount) this.entity.stopRiding(); // Paper - Teleport passenger API + + // Let the server handle cross world teleports + if (location.getWorld() != null && !location.getWorld().equals(this.getWorld())) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 87b241a8b78b6fe0144e0d318e04da69a3101bc7..0e5076529693f1b9f971600909ae353e021f0671 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1195,13 +1195,101 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void setRotation(float yaw, float pitch) { +- throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead."); ++ // Paper start - Teleport API ++ Location targetLocation = this.getEyeLocation(); ++ targetLocation.setYaw(yaw); ++ targetLocation.setPitch(pitch); ++ ++ org.bukkit.util.Vector direction = targetLocation.getDirection(); ++ direction.multiply(9999999); // We need to move the target block.. FAR out ++ targetLocation.add(direction); ++ this.lookAt(targetLocation, io.papermc.paper.entity.LookAnchor.EYES); ++ // Paper end + } + + @Override + public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { ++ // Paper start - Teleport API ++ return this.teleport(location, cause, new io.papermc.paper.entity.TeleportFlag[0]); ++ } ++ ++ @Override ++ public void lookAt(@NotNull org.bukkit.entity.Entity entity, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor, @NotNull io.papermc.paper.entity.LookAnchor entityAnchor) { ++ this.getHandle().lookAt(toNmsAnchor(playerAnchor), ((CraftEntity) entity).getHandle(), toNmsAnchor(entityAnchor)); ++ } ++ ++ @Override ++ public void lookAt(double x, double y, double z, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor) { ++ this.getHandle().lookAt(toNmsAnchor(playerAnchor), new Vec3(x, y, z)); ++ } ++ ++ public static net.minecraft.commands.arguments.EntityAnchorArgument.Anchor toNmsAnchor(io.papermc.paper.entity.LookAnchor nmsAnchor) { ++ return switch (nmsAnchor) { ++ case EYES -> net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.EYES; ++ case FEET -> net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.FEET; ++ }; ++ } ++ ++ public static io.papermc.paper.entity.LookAnchor toApiAnchor(net.minecraft.commands.arguments.EntityAnchorArgument.Anchor playerAnchor) { ++ return switch (playerAnchor) { ++ case EYES -> io.papermc.paper.entity.LookAnchor.EYES; ++ case FEET -> io.papermc.paper.entity.LookAnchor.FEET; ++ }; ++ } ++ ++ public static net.minecraft.world.entity.RelativeMovement toNmsRelativeFlag(io.papermc.paper.entity.TeleportFlag.Relative apiFlag) { ++ return switch (apiFlag) { ++ case X -> net.minecraft.world.entity.RelativeMovement.X; ++ case Y -> net.minecraft.world.entity.RelativeMovement.Y; ++ case Z -> net.minecraft.world.entity.RelativeMovement.Z; ++ case PITCH -> net.minecraft.world.entity.RelativeMovement.X_ROT; ++ case YAW -> net.minecraft.world.entity.RelativeMovement.Y_ROT; ++ }; ++ } ++ ++ public static io.papermc.paper.entity.TeleportFlag.Relative toApiRelativeFlag(net.minecraft.world.entity.RelativeMovement nmsFlag) { ++ return switch (nmsFlag) { ++ case X -> io.papermc.paper.entity.TeleportFlag.Relative.X; ++ case Y -> io.papermc.paper.entity.TeleportFlag.Relative.Y; ++ case Z -> io.papermc.paper.entity.TeleportFlag.Relative.Z; ++ case X_ROT -> io.papermc.paper.entity.TeleportFlag.Relative.PITCH; ++ case Y_ROT -> io.papermc.paper.entity.TeleportFlag.Relative.YAW; ++ }; ++ } ++ ++ @Override ++ public boolean teleport(Location location, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { ++ Set relativeArguments; ++ Set allFlags; ++ if (flags.length == 0) { ++ relativeArguments = Set.of(); ++ allFlags = Set.of(); ++ } else { ++ relativeArguments = java.util.EnumSet.noneOf(io.papermc.paper.entity.TeleportFlag.Relative.class); ++ allFlags = new HashSet<>(); ++ for (io.papermc.paper.entity.TeleportFlag flag : flags) { ++ if (flag instanceof final io.papermc.paper.entity.TeleportFlag.Relative relativeTeleportFlag) { ++ relativeArguments.add(relativeTeleportFlag); ++ } ++ allFlags.add(flag); ++ } ++ } ++ boolean dismount = !allFlags.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_VEHICLE); ++ boolean ignorePassengers = allFlags.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS); ++ // Paper end - Teleport API + Preconditions.checkArgument(location != null, "location"); + Preconditions.checkArgument(location.getWorld() != null, "location.world"); ++ // Paper start - Teleport passenger API ++ // Don't allow teleporting between worlds while keeping passengers ++ if (ignorePassengers && entity.isVehicle() && location.getWorld() != this.getWorld()) { ++ return false; ++ } ++ ++ // Don't allow to teleport between worlds if remaining on vehicle ++ if (!dismount && entity.isPassenger() && location.getWorld() != this.getWorld()) { ++ return false; ++ } ++ // Paper end + location.checkFinite(); + + ServerPlayer entity = this.getHandle(); +@@ -1214,7 +1302,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return false; + } + +- if (entity.isVehicle()) { ++ if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API + return false; + } + +@@ -1223,7 +1311,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + // To = Players new Location if Teleport is Successful + Location to = location; + // Create & Call the Teleport Event. +- PlayerTeleportEvent event = new PlayerTeleportEvent(this, from, to, cause); ++ PlayerTeleportEvent event = new PlayerTeleportEvent(this, from, to, cause, Set.copyOf(relativeArguments)); // Paper - Teleport API + this.server.getPluginManager().callEvent(event); + + // Return False to inform the Plugin that the Teleport was unsuccessful/cancelled. +@@ -1232,7 +1320,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + // If this player is riding another entity, we must dismount before teleporting. +- entity.stopRiding(); ++ if (dismount) entity.stopRiding(); // Paper - Teleport API + + // SPIGOT-5509: Wakeup, similar to riding + if (this.isSleeping()) { +@@ -1248,13 +1336,19 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + ServerLevel toWorld = ((CraftWorld) to.getWorld()).getHandle(); + + // Close any foreign inventory +- if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { ++ if (this.getHandle().containerMenu != this.getHandle().inventoryMenu && !allFlags.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_OPEN_INVENTORY)) { // Paper + this.getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); // Paper - Inventory close reason + } + + // Check if the fromWorld and toWorld are the same. + if (fromWorld == toWorld) { +- entity.connection.teleport(to); ++ // Paper start - Teleport API ++ final Set nms = java.util.EnumSet.noneOf(net.minecraft.world.entity.RelativeMovement.class); ++ for (final io.papermc.paper.entity.TeleportFlag.Relative bukkit : relativeArguments) { ++ nms.add(toNmsRelativeFlag(bukkit)); ++ } ++ entity.connection.internalTeleport(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch(), nms); ++ // Paper end - Teleport API + } else { + // The respawn reason should never be used if the passed location is non null. + this.server.getHandle().respawn(entity, toWorld, true, to, !toWorld.paperConfig().environment.disableTeleportationSuffocationCheck, null); // Paper diff --git a/patches/server/0767-Add-EntityPortalReadyEvent.patch b/patches/server/0767-Add-EntityPortalReadyEvent.patch new file mode 100644 index 000000000000..176fcf19402a --- /dev/null +++ b/patches/server/0767-Add-EntityPortalReadyEvent.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 12 May 2021 04:30:42 -0700 +Subject: [PATCH] Add EntityPortalReadyEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index ef1afa30b2fe56fc2f3d9c94c6d673d4c132ef92..7d600e90ad8c7a020988a18e2926872180fb4a1e 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2816,6 +2816,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + if (true && !this.isPassenger() && this.portalTime++ >= i) { // CraftBukkit + this.level().getProfiler().push("portal"); + this.portalTime = i; ++ // Paper start - Add EntityPortalReadyEvent ++ io.papermc.paper.event.entity.EntityPortalReadyEvent event = new io.papermc.paper.event.entity.EntityPortalReadyEvent(this.getBukkitEntity(), worldserver1 == null ? null : worldserver1.getWorld(), org.bukkit.PortalType.NETHER); ++ if (!event.callEvent()) { ++ this.portalTime = 0; ++ } else { ++ worldserver1 = event.getTargetWorld() == null ? null : ((CraftWorld) event.getTargetWorld()).getHandle(); ++ // Paper end - Add EntityPortalReadyEvent + this.setPortalCooldown(); + // CraftBukkit start + if (this instanceof ServerPlayer) { +@@ -2823,6 +2830,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } else { + this.changeDimension(worldserver1); + } ++ } // Paper - Add EntityPortalReadyEvent + // CraftBukkit end + this.level().getProfiler().pop(); + } diff --git a/patches/server/0767-Add-various-missing-EntityDropItemEvent-calls.patch b/patches/server/0767-Add-various-missing-EntityDropItemEvent-calls.patch deleted file mode 100644 index 68f6361dc9ba..000000000000 --- a/patches/server/0767-Add-various-missing-EntityDropItemEvent-calls.patch +++ /dev/null @@ -1,88 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 20 Jul 2021 21:35:47 -0700 -Subject: [PATCH] Add various missing EntityDropItemEvent calls - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 467f269107f314ebc3af1c4a1852f239906c0ba4..efc7287ce869d8bb6833d075738d3bfab47d3909 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2471,6 +2471,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe - - entityitem.setDefaultPickUpDelay(); -+ // Paper start - Call EntityDropItemEvent -+ return this.spawnAtLocation(entityitem); -+ } -+ } -+ @Nullable -+ public ItemEntity spawnAtLocation(ItemEntity entityitem) { -+ { -+ // Paper end - Call EntityDropItemEvent - // CraftBukkit start - EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); - Bukkit.getPluginManager().callEvent(event); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -index fb5e200499a1a73fd40c8b7c81185f48381f49e4..178e1e75fcd0e60a1dd2729a894df08cf4129526 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -@@ -592,7 +592,7 @@ public class Dolphin extends WaterAnimal { - float f2 = 0.02F * Dolphin.this.random.nextFloat(); - - entityitem.setDeltaMovement((double) (0.3F * -Mth.sin(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.cos(f1) * f2), (double) (0.3F * Mth.sin(Dolphin.this.getXRot() * 0.017453292F) * 1.5F), (double) (0.3F * Mth.cos(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.sin(f1) * f2)); -- Dolphin.this.level().addFreshEntity(entityitem); -+ Dolphin.this.spawnAtLocation(entityitem); // Paper - Call EntityDropItemEvent - } - } - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java -index 6124209f50300eeaab45b66c2f1a5b2944119450..94bb69a7f5795e0fbee171433632b5c3bca3b902 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java -@@ -509,14 +509,14 @@ public class Fox extends Animal implements VariantHolder { - entityitem.setPickUpDelay(40); - entityitem.setThrower(this); - this.playSound(SoundEvents.FOX_SPIT, 1.0F, 1.0F); -- this.level().addFreshEntity(entityitem); -+ this.spawnAtLocation(entityitem); // Paper - Call EntityDropItemEvent - } - } - - private void dropItemStack(ItemStack stack) { - ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), stack); - -- this.level().addFreshEntity(entityitem); -+ this.spawnAtLocation(entityitem); // Paper - Call EntityDropItemEvent - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -index ee1b2c1fec4b76a821e1d52fbb07e1f302b2efa1..110dd5418b0512a2f27f0c4d5a5f1812356a6a12 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -@@ -357,8 +357,7 @@ public class Goat extends Animal { - double d2 = (double) Mth.randomBetween(this.random, -0.2F, 0.2F); - ItemEntity entityitem = new ItemEntity(this.level(), vec3d.x(), vec3d.y(), vec3d.z(), itemstack, d0, d1, d2); - -- this.level().addFreshEntity(entityitem); -- return true; -+ return this.spawnAtLocation(entityitem) != null; // Paper - Call EntityDropItemEvent - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -index 1d9913cbc7fd0c6e29278b02c38703b52af1245b..0e85e3ab58d848b119212fa7d2eb4f92d3efe29b 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -+++ b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -@@ -352,8 +352,9 @@ public class Sniffer extends Animal { - - entityitem.setDefaultPickUpDelay(); - this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null); -+ if (this.spawnAtLocation(entityitem) != null) { // Paper - Call EntityDropItemEvent - this.playSound(SoundEvents.SNIFFER_EGG_PLOP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 0.5F); -- world.addFreshEntity(entityitem); -+ } // Paper - Call EntityDropItemEvent - } - - @Override diff --git a/patches/server/0772-Don-t-use-level-random-in-entity-constructors.patch b/patches/server/0768-Don-t-use-level-random-in-entity-constructors.patch similarity index 100% rename from patches/server/0772-Don-t-use-level-random-in-entity-constructors.patch rename to patches/server/0768-Don-t-use-level-random-in-entity-constructors.patch diff --git a/patches/server/0769-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch b/patches/server/0769-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch deleted file mode 100644 index ccaed9e8fab9..000000000000 --- a/patches/server/0769-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Doc -Date: Sun, 17 Jul 2022 11:49:43 -0400 -Subject: [PATCH] Fix Spigot Config not using commands.spam-exclusions - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index e06d09ba19df5b7bf2b5179a07c39e2197cc0863..141645f5ab844bd18d04e7d36beddcfa402dc8e9 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2299,7 +2299,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - // Spigot end - // this.chatSpamTickCount += 20; -- if (this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { -+ if (counted && this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper - exclude from SpigotConfig.spamExclusions - // CraftBukkit end - this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause - } diff --git a/patches/server/0769-Send-block-entities-after-destroy-prediction.patch b/patches/server/0769-Send-block-entities-after-destroy-prediction.patch new file mode 100644 index 000000000000..5acf3fa9d7e0 --- /dev/null +++ b/patches/server/0769-Send-block-entities-after-destroy-prediction.patch @@ -0,0 +1,91 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sat, 25 Jun 2022 19:45:20 -0400 +Subject: [PATCH] Send block entities after destroy prediction + +Minecraft's prediction system does not handle block entities, so if we are manually sending block entities during +block breaking we need to set it after the prediction is finished. This fixes block entities not showing when cancelling the BlockBreakEvent. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 8f4c9b99b638cfce8cc7c55f6369f62e757f4e48..f3389dc345d8b6e5389ae37848d9b268d4bbad83 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -62,6 +62,8 @@ public class ServerPlayerGameMode { + private BlockPos delayedDestroyPos; + private int delayedTickStart; + private int lastSentState; ++ public boolean captureSentBlockEntities = false; // Paper - Send block entities after destroy prediction ++ public boolean capturedBlockEntity = false; // Paper - Send block entities after destroy prediction + + public ServerPlayerGameMode(ServerPlayer player) { + this.gameModeForPlayer = GameType.DEFAULT_MODE; +@@ -188,10 +190,7 @@ public class ServerPlayerGameMode { + this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); + this.debugLogging(pos, false, sequence, "may not interact"); + // Update any tile entity data for this block +- BlockEntity tileentity = this.level.getBlockEntity(pos); +- if (tileentity != null) { +- this.player.connection.send(tileentity.getUpdatePacket()); +- } ++ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction + // CraftBukkit end + return; + } +@@ -202,10 +201,7 @@ public class ServerPlayerGameMode { + // Let the client know the block still exists + this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); + // Update any tile entity data for this block +- BlockEntity tileentity = this.level.getBlockEntity(pos); +- if (tileentity != null) { +- this.player.connection.send(tileentity.getUpdatePacket()); +- } ++ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction + return; + } + // CraftBukkit end +@@ -387,10 +383,12 @@ public class ServerPlayerGameMode { + } + + // Update any tile entity data for this block ++ if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction + BlockEntity tileentity = this.level.getBlockEntity(pos); + if (tileentity != null) { + this.player.connection.send(tileentity.getUpdatePacket()); + } ++ } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction + return false; + } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 7a3293e1a911de494ac32b946d8f709a52d98cbe..1eae23b70659b729aaec9d1b5b4dedbefb30a29f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1704,8 +1704,28 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + return; + } + // Paper end - Don't allow digging into unloaded chunks ++ // Paper start - Send block entities after destroy prediction ++ this.player.gameMode.capturedBlockEntity = false; ++ this.player.gameMode.captureSentBlockEntities = true; ++ // Paper end - Send block entities after destroy prediction + this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level().getMaxBuildHeight(), packet.getSequence()); + this.player.connection.ackBlockChangesUpTo(packet.getSequence()); ++ // Paper start - Send block entities after destroy prediction ++ this.player.gameMode.captureSentBlockEntities = false; ++ // If a block entity was modified speedup the block change ack to avoid the block entity ++ // being overriden. ++ if (this.player.gameMode.capturedBlockEntity) { ++ // manually tick ++ this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); ++ this.player.connection.ackBlockChangesUpTo = -1; ++ ++ this.player.gameMode.capturedBlockEntity = false; ++ BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); ++ if (tileentity != null) { ++ this.player.connection.send(tileentity.getUpdatePacket()); ++ } ++ } ++ // Paper end - Send block entities after destroy prediction + return; + default: + throw new IllegalArgumentException("Invalid player action"); diff --git a/patches/server/0770-More-Teleport-API.patch b/patches/server/0770-More-Teleport-API.patch deleted file mode 100644 index 1641436dbf02..000000000000 --- a/patches/server/0770-More-Teleport-API.patch +++ /dev/null @@ -1,229 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 5 Sep 2021 12:15:59 -0400 -Subject: [PATCH] More Teleport API - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 141645f5ab844bd18d04e7d36beddcfa402dc8e9..229c472bbd8c99086e0b63dbb4c1777e145d5130 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1558,11 +1558,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - return false; // CraftBukkit - Return event status - } - -- PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause); -+ // Paper start - Teleport API -+ Set relativeFlags = java.util.EnumSet.noneOf(io.papermc.paper.entity.TeleportFlag.Relative.class); -+ for (RelativeMovement relativeArgument : set) { -+ relativeFlags.add(org.bukkit.craftbukkit.entity.CraftPlayer.toApiRelativeFlag(relativeArgument)); -+ } -+ PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause, java.util.Set.copyOf(relativeFlags)); -+ // Paper end - Teleport API - this.cserver.getPluginManager().callEvent(event); - - if (event.isCancelled() || !to.equals(event.getTo())) { -- set.clear(); // Can't relative teleport -+ // set.clear(); // Can't relative teleport // Paper - Teleport API; Now you can! - to = event.isCancelled() ? event.getFrom() : event.getTo(); - d0 = to.getX(); - d1 = to.getY(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index b7df71633527dce2e4f954caee249e3b31b82226..ee1dc74b2f48bf8d684562de895ab631cf792a30 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -218,15 +218,36 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - - @Override - public boolean teleport(Location location, TeleportCause cause) { -+ // Paper start - Teleport passenger API -+ return teleport(location, cause, new io.papermc.paper.entity.TeleportFlag[0]); -+ } -+ -+ @Override -+ public boolean teleport(Location location, TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { -+ // Paper end - Preconditions.checkArgument(location != null, "location cannot be null"); - location.checkFinite(); -+ // Paper start - Teleport passenger API -+ Set flagSet = Set.of(flags); -+ boolean dismount = !flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_VEHICLE); -+ boolean ignorePassengers = flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS); -+ // Don't allow teleporting between worlds while keeping passengers -+ if (flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS) && this.entity.isVehicle() && location.getWorld() != this.getWorld()) { -+ return false; -+ } -+ -+ // Don't allow to teleport between worlds if remaining on vehicle -+ if (!dismount && this.entity.isPassenger() && location.getWorld() != this.getWorld()) { -+ return false; -+ } -+ // Paper end - -- if (this.entity.isVehicle() || this.entity.isRemoved()) { -+ if ((!ignorePassengers && this.entity.isVehicle()) || this.entity.isRemoved()) { // Paper - Teleport passenger API - return false; - } - - // If this entity is riding another entity, we must dismount before teleporting. -- this.entity.stopRiding(); -+ if (dismount) this.entity.stopRiding(); // Paper - Teleport passenger API - - // Let the server handle cross world teleports - if (location.getWorld() != null && !location.getWorld().equals(this.getWorld())) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 1551924d052087b8a5dc9e5342fd0fbc8022b405..71337c5163b53fc4728380c24e926bccd736538c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1165,13 +1165,101 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void setRotation(float yaw, float pitch) { -- throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead."); -+ // Paper start - Teleport API -+ Location targetLocation = this.getEyeLocation(); -+ targetLocation.setYaw(yaw); -+ targetLocation.setPitch(pitch); -+ -+ org.bukkit.util.Vector direction = targetLocation.getDirection(); -+ direction.multiply(9999999); // We need to move the target block.. FAR out -+ targetLocation.add(direction); -+ this.lookAt(targetLocation, io.papermc.paper.entity.LookAnchor.EYES); -+ // Paper end - } - - @Override - public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { -+ // Paper start - Teleport API -+ return this.teleport(location, cause, new io.papermc.paper.entity.TeleportFlag[0]); -+ } -+ -+ @Override -+ public void lookAt(@NotNull org.bukkit.entity.Entity entity, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor, @NotNull io.papermc.paper.entity.LookAnchor entityAnchor) { -+ this.getHandle().lookAt(toNmsAnchor(playerAnchor), ((CraftEntity) entity).getHandle(), toNmsAnchor(entityAnchor)); -+ } -+ -+ @Override -+ public void lookAt(double x, double y, double z, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor) { -+ this.getHandle().lookAt(toNmsAnchor(playerAnchor), new Vec3(x, y, z)); -+ } -+ -+ public static net.minecraft.commands.arguments.EntityAnchorArgument.Anchor toNmsAnchor(io.papermc.paper.entity.LookAnchor nmsAnchor) { -+ return switch (nmsAnchor) { -+ case EYES -> net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.EYES; -+ case FEET -> net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.FEET; -+ }; -+ } -+ -+ public static io.papermc.paper.entity.LookAnchor toApiAnchor(net.minecraft.commands.arguments.EntityAnchorArgument.Anchor playerAnchor) { -+ return switch (playerAnchor) { -+ case EYES -> io.papermc.paper.entity.LookAnchor.EYES; -+ case FEET -> io.papermc.paper.entity.LookAnchor.FEET; -+ }; -+ } -+ -+ public static net.minecraft.world.entity.RelativeMovement toNmsRelativeFlag(io.papermc.paper.entity.TeleportFlag.Relative apiFlag) { -+ return switch (apiFlag) { -+ case X -> net.minecraft.world.entity.RelativeMovement.X; -+ case Y -> net.minecraft.world.entity.RelativeMovement.Y; -+ case Z -> net.minecraft.world.entity.RelativeMovement.Z; -+ case PITCH -> net.minecraft.world.entity.RelativeMovement.X_ROT; -+ case YAW -> net.minecraft.world.entity.RelativeMovement.Y_ROT; -+ }; -+ } -+ -+ public static io.papermc.paper.entity.TeleportFlag.Relative toApiRelativeFlag(net.minecraft.world.entity.RelativeMovement nmsFlag) { -+ return switch (nmsFlag) { -+ case X -> io.papermc.paper.entity.TeleportFlag.Relative.X; -+ case Y -> io.papermc.paper.entity.TeleportFlag.Relative.Y; -+ case Z -> io.papermc.paper.entity.TeleportFlag.Relative.Z; -+ case X_ROT -> io.papermc.paper.entity.TeleportFlag.Relative.PITCH; -+ case Y_ROT -> io.papermc.paper.entity.TeleportFlag.Relative.YAW; -+ }; -+ } -+ -+ @Override -+ public boolean teleport(Location location, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { -+ Set relativeArguments; -+ Set allFlags; -+ if (flags.length == 0) { -+ relativeArguments = Set.of(); -+ allFlags = Set.of(); -+ } else { -+ relativeArguments = java.util.EnumSet.noneOf(io.papermc.paper.entity.TeleportFlag.Relative.class); -+ allFlags = new HashSet<>(); -+ for (io.papermc.paper.entity.TeleportFlag flag : flags) { -+ if (flag instanceof final io.papermc.paper.entity.TeleportFlag.Relative relativeTeleportFlag) { -+ relativeArguments.add(relativeTeleportFlag); -+ } -+ allFlags.add(flag); -+ } -+ } -+ boolean dismount = !allFlags.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_VEHICLE); -+ boolean ignorePassengers = allFlags.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS); -+ // Paper end - Teleport API - Preconditions.checkArgument(location != null, "location"); - Preconditions.checkArgument(location.getWorld() != null, "location.world"); -+ // Paper start - Teleport passenger API -+ // Don't allow teleporting between worlds while keeping passengers -+ if (ignorePassengers && entity.isVehicle() && location.getWorld() != this.getWorld()) { -+ return false; -+ } -+ -+ // Don't allow to teleport between worlds if remaining on vehicle -+ if (!dismount && entity.isPassenger() && location.getWorld() != this.getWorld()) { -+ return false; -+ } -+ // Paper end - location.checkFinite(); - - ServerPlayer entity = this.getHandle(); -@@ -1184,7 +1272,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - return false; - } - -- if (entity.isVehicle()) { -+ if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API - return false; - } - -@@ -1193,7 +1281,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - // To = Players new Location if Teleport is Successful - Location to = location; - // Create & Call the Teleport Event. -- PlayerTeleportEvent event = new PlayerTeleportEvent(this, from, to, cause); -+ PlayerTeleportEvent event = new PlayerTeleportEvent(this, from, to, cause, Set.copyOf(relativeArguments)); // Paper - Teleport API - this.server.getPluginManager().callEvent(event); - - // Return False to inform the Plugin that the Teleport was unsuccessful/cancelled. -@@ -1202,7 +1290,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - // If this player is riding another entity, we must dismount before teleporting. -- entity.stopRiding(); -+ if (dismount) entity.stopRiding(); // Paper - Teleport API - - // SPIGOT-5509: Wakeup, similar to riding - if (this.isSleeping()) { -@@ -1218,13 +1306,19 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - ServerLevel toWorld = ((CraftWorld) to.getWorld()).getHandle(); - - // Close any foreign inventory -- if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { -+ if (this.getHandle().containerMenu != this.getHandle().inventoryMenu && !allFlags.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_OPEN_INVENTORY)) { // Paper - this.getHandle().closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); // Paper - Inventory close reason - } - - // Check if the fromWorld and toWorld are the same. - if (fromWorld == toWorld) { -- entity.connection.teleport(to); -+ // Paper start - Teleport API -+ final Set nms = java.util.EnumSet.noneOf(net.minecraft.world.entity.RelativeMovement.class); -+ for (final io.papermc.paper.entity.TeleportFlag.Relative bukkit : relativeArguments) { -+ nms.add(toNmsRelativeFlag(bukkit)); -+ } -+ entity.connection.internalTeleport(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch(), nms); -+ // Paper end - Teleport API - } else { - // The respawn reason should never be used if the passed location is non null. - this.server.getHandle().respawn(entity, toWorld, true, to, !toWorld.paperConfig().environment.disableTeleportationSuffocationCheck, null); // Paper diff --git a/patches/server/0770-Warn-on-plugins-accessing-faraway-chunks.patch b/patches/server/0770-Warn-on-plugins-accessing-faraway-chunks.patch new file mode 100644 index 000000000000..81ca2e88f6ae --- /dev/null +++ b/patches/server/0770-Warn-on-plugins-accessing-faraway-chunks.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Fri, 29 Jul 2022 12:35:19 -0400 +Subject: [PATCH] Warn on plugins accessing faraway chunks + + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index c49ff35c92874273233ec53ed63aaff9c79edcd0..76deb93a1a1d3f93498176f32ef291e1299c588d 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -345,7 +345,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + + private static boolean isInWorldBoundsHorizontal(BlockPos pos) { +- return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; ++ return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; // Diff on change warnUnsafeChunk() + } + + private static boolean isOutsideSpawnableHeight(int y) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 97ebf2e2e08469cacc66c4f38bd2edfc2107fe6a..54cdb4b6a97250c1e15e2fce355e3699c9189948 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -318,9 +318,24 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public boolean setSpawnLocation(int x, int y, int z) { + return this.setSpawnLocation(x, y, z, 0.0F); + } ++ // Paper start ++ private static void warnUnsafeChunk(String reason, int x, int z) { ++ // if any chunk coord is outside of 30 million blocks ++ if (x > 1875000 || z > 1875000 || x < -1875000 || z < -1875000) { ++ Plugin plugin = io.papermc.paper.util.StackWalkerUtil.getFirstPluginCaller(); ++ if (plugin != null) { ++ plugin.getLogger().warning("Plugin is %s at (%s, %s), this might cause issues.".formatted(reason, x, z)); ++ } ++ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) { ++ io.papermc.paper.util.TraceUtil.dumpTraceForThread("Dangerous chunk retrieval"); ++ } ++ } ++ } ++ // Paper end + + @Override + public Chunk getChunkAt(int x, int z) { ++ warnUnsafeChunk("getting a faraway chunk", x, z); // Paper + // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it + net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); + if (chunk == null) { +@@ -421,6 +436,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public boolean regenerateChunk(int x, int z) { + org.spigotmc.AsyncCatcher.catchOp("chunk regenerate"); // Spigot ++ warnUnsafeChunk("regenerating a faraway chunk", x, z); // Paper + // Paper start - implement regenerateChunk method + final ServerLevel serverLevel = this.world; + final net.minecraft.server.level.ServerChunkCache serverChunkCache = serverLevel.getChunkSource(); +@@ -515,6 +531,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public boolean loadChunk(int x, int z, boolean generate) { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot ++ warnUnsafeChunk("loading a faraway chunk", x, z); // Paper + ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper + + // If generate = false, but the chunk already exists, we will get this back. +@@ -547,6 +564,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean addPluginChunkTicket(int x, int z, Plugin plugin) { ++ warnUnsafeChunk("adding a faraway chunk ticket", x, z); // Paper + Preconditions.checkArgument(plugin != null, "null plugin"); + Preconditions.checkArgument(plugin.isEnabled(), "plugin is not enabled"); + +@@ -628,6 +646,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setChunkForceLoaded(int x, int z, boolean forced) { ++ warnUnsafeChunk("forceloading a faraway chunk", x, z); // Paper + this.getHandle().setChunkForced(x, z, forced); + } + +@@ -947,6 +966,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { ++ warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper + // Transient load for this tick + return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); + } +@@ -2380,6 +2400,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + // Spigot end + // Paper start + public java.util.concurrent.CompletableFuture getChunkAtAsync(int x, int z, boolean gen, boolean urgent) { ++ warnUnsafeChunk("getting a faraway chunk async", x, z); // Paper + if (Bukkit.isPrimaryThread()) { + net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); + if (immediate != null) { diff --git a/patches/server/0771-Add-EntityPortalReadyEvent.patch b/patches/server/0771-Add-EntityPortalReadyEvent.patch deleted file mode 100644 index 3ce3a5c8bff0..000000000000 --- a/patches/server/0771-Add-EntityPortalReadyEvent.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 12 May 2021 04:30:42 -0700 -Subject: [PATCH] Add EntityPortalReadyEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index efc7287ce869d8bb6833d075738d3bfab47d3909..e6739e436f709fd24315932ad6386ed6534721c2 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2812,6 +2812,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - if (true && !this.isPassenger() && this.portalTime++ >= i) { // CraftBukkit - this.level().getProfiler().push("portal"); - this.portalTime = i; -+ // Paper start - Add EntityPortalReadyEvent -+ io.papermc.paper.event.entity.EntityPortalReadyEvent event = new io.papermc.paper.event.entity.EntityPortalReadyEvent(this.getBukkitEntity(), worldserver1 == null ? null : worldserver1.getWorld(), org.bukkit.PortalType.NETHER); -+ if (!event.callEvent()) { -+ this.portalTime = 0; -+ } else { -+ worldserver1 = event.getTargetWorld() == null ? null : ((CraftWorld) event.getTargetWorld()).getHandle(); -+ // Paper end - Add EntityPortalReadyEvent - this.setPortalCooldown(); - // CraftBukkit start - if (this instanceof ServerPlayer) { -@@ -2819,6 +2826,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } else { - this.changeDimension(worldserver1); - } -+ } // Paper - Add EntityPortalReadyEvent - // CraftBukkit end - this.level().getProfiler().pop(); - } diff --git a/patches/server/0771-Custom-Chat-Completion-Suggestions-API.patch b/patches/server/0771-Custom-Chat-Completion-Suggestions-API.patch new file mode 100644 index 000000000000..fa918c4450d4 --- /dev/null +++ b/patches/server/0771-Custom-Chat-Completion-Suggestions-API.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sat, 30 Jul 2022 11:23:05 -0400 +Subject: [PATCH] Custom Chat Completion Suggestions API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 0e5076529693f1b9f971600909ae353e021f0671..d866ad7fd91c64fd0fe6680a417678e3ef5ca41d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -611,6 +611,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + // Paper end - Add sendOpLevel API + ++ // Paper start - custom chat completions API ++ @Override ++ public void addAdditionalChatCompletions(@NotNull Collection completions) { ++ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket( ++ net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket.Action.ADD, ++ new ArrayList<>(completions) ++ )); ++ } ++ ++ @Override ++ public void removeAdditionalChatCompletions(@NotNull Collection completions) { ++ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket( ++ net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket.Action.REMOVE, ++ new ArrayList<>(completions) ++ )); ++ } ++ // Paper end - custom chat completions API ++ + @Override + public void setCompassTarget(Location loc) { + Preconditions.checkArgument(loc != null, "Location cannot be null"); diff --git a/patches/server/0776-Add-and-fix-missing-BlockFadeEvents.patch b/patches/server/0772-Add-and-fix-missing-BlockFadeEvents.patch similarity index 100% rename from patches/server/0776-Add-and-fix-missing-BlockFadeEvents.patch rename to patches/server/0772-Add-and-fix-missing-BlockFadeEvents.patch diff --git a/patches/server/0777-Collision-API.patch b/patches/server/0773-Collision-API.patch similarity index 100% rename from patches/server/0777-Collision-API.patch rename to patches/server/0773-Collision-API.patch diff --git a/patches/server/0773-Send-block-entities-after-destroy-prediction.patch b/patches/server/0773-Send-block-entities-after-destroy-prediction.patch deleted file mode 100644 index a6b883a2d648..000000000000 --- a/patches/server/0773-Send-block-entities-after-destroy-prediction.patch +++ /dev/null @@ -1,91 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sat, 25 Jun 2022 19:45:20 -0400 -Subject: [PATCH] Send block entities after destroy prediction - -Minecraft's prediction system does not handle block entities, so if we are manually sending block entities during -block breaking we need to set it after the prediction is finished. This fixes block entities not showing when cancelling the BlockBreakEvent. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 8f4c9b99b638cfce8cc7c55f6369f62e757f4e48..f3389dc345d8b6e5389ae37848d9b268d4bbad83 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -62,6 +62,8 @@ public class ServerPlayerGameMode { - private BlockPos delayedDestroyPos; - private int delayedTickStart; - private int lastSentState; -+ public boolean captureSentBlockEntities = false; // Paper - Send block entities after destroy prediction -+ public boolean capturedBlockEntity = false; // Paper - Send block entities after destroy prediction - - public ServerPlayerGameMode(ServerPlayer player) { - this.gameModeForPlayer = GameType.DEFAULT_MODE; -@@ -188,10 +190,7 @@ public class ServerPlayerGameMode { - this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); - this.debugLogging(pos, false, sequence, "may not interact"); - // Update any tile entity data for this block -- BlockEntity tileentity = this.level.getBlockEntity(pos); -- if (tileentity != null) { -- this.player.connection.send(tileentity.getUpdatePacket()); -- } -+ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction - // CraftBukkit end - return; - } -@@ -202,10 +201,7 @@ public class ServerPlayerGameMode { - // Let the client know the block still exists - this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); - // Update any tile entity data for this block -- BlockEntity tileentity = this.level.getBlockEntity(pos); -- if (tileentity != null) { -- this.player.connection.send(tileentity.getUpdatePacket()); -- } -+ capturedBlockEntity = true; // Paper - Send block entities after destroy prediction - return; - } - // CraftBukkit end -@@ -387,10 +383,12 @@ public class ServerPlayerGameMode { - } - - // Update any tile entity data for this block -+ if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction - BlockEntity tileentity = this.level.getBlockEntity(pos); - if (tileentity != null) { - this.player.connection.send(tileentity.getUpdatePacket()); - } -+ } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction - return false; - } - } -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 229c472bbd8c99086e0b63dbb4c1777e145d5130..17c059020eae2c125971b4f6ffe90db46fb85cd2 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1704,8 +1704,28 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - return; - } - // Paper end - Don't allow digging into unloaded chunks -+ // Paper start - Send block entities after destroy prediction -+ this.player.gameMode.capturedBlockEntity = false; -+ this.player.gameMode.captureSentBlockEntities = true; -+ // Paper end - Send block entities after destroy prediction - this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level().getMaxBuildHeight(), packet.getSequence()); - this.player.connection.ackBlockChangesUpTo(packet.getSequence()); -+ // Paper start - Send block entities after destroy prediction -+ this.player.gameMode.captureSentBlockEntities = false; -+ // If a block entity was modified speedup the block change ack to avoid the block entity -+ // being overriden. -+ if (this.player.gameMode.capturedBlockEntity) { -+ // manually tick -+ this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); -+ this.player.connection.ackBlockChangesUpTo = -1; -+ -+ this.player.gameMode.capturedBlockEntity = false; -+ BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); -+ if (tileentity != null) { -+ this.player.connection.send(tileentity.getUpdatePacket()); -+ } -+ } -+ // Paper end - Send block entities after destroy prediction - return; - default: - throw new IllegalArgumentException("Invalid player action"); diff --git a/patches/server/0778-Fix-suggest-command-message-for-brigadier-syntax-exc.patch b/patches/server/0774-Fix-suggest-command-message-for-brigadier-syntax-exc.patch similarity index 100% rename from patches/server/0778-Fix-suggest-command-message-for-brigadier-syntax-exc.patch rename to patches/server/0774-Fix-suggest-command-message-for-brigadier-syntax-exc.patch diff --git a/patches/server/0774-Warn-on-plugins-accessing-faraway-chunks.patch b/patches/server/0774-Warn-on-plugins-accessing-faraway-chunks.patch deleted file mode 100644 index 6df9b3dc9486..000000000000 --- a/patches/server/0774-Warn-on-plugins-accessing-faraway-chunks.patch +++ /dev/null @@ -1,96 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Fri, 29 Jul 2022 12:35:19 -0400 -Subject: [PATCH] Warn on plugins accessing faraway chunks - - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index c49ff35c92874273233ec53ed63aaff9c79edcd0..76deb93a1a1d3f93498176f32ef291e1299c588d 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -345,7 +345,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - - private static boolean isInWorldBoundsHorizontal(BlockPos pos) { -- return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; -+ return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; // Diff on change warnUnsafeChunk() - } - - private static boolean isOutsideSpawnableHeight(int y) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 12013237e7b00f47d2a8660fd09ee3d52fdf084c..caca37e0febbfaa2012820c8a6f0e6adbaf2451b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -312,9 +312,24 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public boolean setSpawnLocation(int x, int y, int z) { - return this.setSpawnLocation(x, y, z, 0.0F); - } -+ // Paper start -+ private static void warnUnsafeChunk(String reason, int x, int z) { -+ // if any chunk coord is outside of 30 million blocks -+ if (x > 1875000 || z > 1875000 || x < -1875000 || z < -1875000) { -+ Plugin plugin = io.papermc.paper.util.StackWalkerUtil.getFirstPluginCaller(); -+ if (plugin != null) { -+ plugin.getLogger().warning("Plugin is %s at (%s, %s), this might cause issues.".formatted(reason, x, z)); -+ } -+ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) { -+ io.papermc.paper.util.TraceUtil.dumpTraceForThread("Dangerous chunk retrieval"); -+ } -+ } -+ } -+ // Paper end - - @Override - public Chunk getChunkAt(int x, int z) { -+ warnUnsafeChunk("getting a faraway chunk", x, z); // Paper - // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it - net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); - if (chunk == null) { -@@ -415,6 +430,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @Override - public boolean regenerateChunk(int x, int z) { - org.spigotmc.AsyncCatcher.catchOp("chunk regenerate"); // Spigot -+ warnUnsafeChunk("regenerating a faraway chunk", x, z); // Paper - // Paper start - implement regenerateChunk method - final ServerLevel serverLevel = this.world; - final net.minecraft.server.level.ServerChunkCache serverChunkCache = serverLevel.getChunkSource(); -@@ -509,6 +525,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @Override - public boolean loadChunk(int x, int z, boolean generate) { - org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot -+ warnUnsafeChunk("loading a faraway chunk", x, z); // Paper - ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper - - // If generate = false, but the chunk already exists, we will get this back. -@@ -541,6 +558,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public boolean addPluginChunkTicket(int x, int z, Plugin plugin) { -+ warnUnsafeChunk("adding a faraway chunk ticket", x, z); // Paper - Preconditions.checkArgument(plugin != null, "null plugin"); - Preconditions.checkArgument(plugin.isEnabled(), "plugin is not enabled"); - -@@ -622,6 +640,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void setChunkForceLoaded(int x, int z, boolean forced) { -+ warnUnsafeChunk("forceloading a faraway chunk", x, z); // Paper - this.getHandle().setChunkForced(x, z, forced); - } - -@@ -934,6 +953,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { -+ warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper - // Transient load for this tick - return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); - } -@@ -2343,6 +2363,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - // Spigot end - // Paper start - public java.util.concurrent.CompletableFuture getChunkAtAsync(int x, int z, boolean gen, boolean urgent) { -+ warnUnsafeChunk("getting a faraway chunk async", x, z); // Paper - if (Bukkit.isPrimaryThread()) { - net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); - if (immediate != null) { diff --git a/patches/server/0779-Block-Ticking-API.patch b/patches/server/0775-Block-Ticking-API.patch similarity index 100% rename from patches/server/0779-Block-Ticking-API.patch rename to patches/server/0775-Block-Ticking-API.patch diff --git a/patches/server/0775-Custom-Chat-Completion-Suggestions-API.patch b/patches/server/0775-Custom-Chat-Completion-Suggestions-API.patch deleted file mode 100644 index 58069f387562..000000000000 --- a/patches/server/0775-Custom-Chat-Completion-Suggestions-API.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sat, 30 Jul 2022 11:23:05 -0400 -Subject: [PATCH] Custom Chat Completion Suggestions API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 451ef858cfc4504efe52f30d449d632b92569bd9..93fb3ec75009ccbeba796146ffb6236cba2d8bd5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -605,6 +605,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - // Paper end - Add sendOpLevel API - -+ // Paper start - custom chat completions API -+ @Override -+ public void addAdditionalChatCompletions(@NotNull Collection completions) { -+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket( -+ net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket.Action.ADD, -+ new ArrayList<>(completions) -+ )); -+ } -+ -+ @Override -+ public void removeAdditionalChatCompletions(@NotNull Collection completions) { -+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket( -+ net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket.Action.REMOVE, -+ new ArrayList<>(completions) -+ )); -+ } -+ // Paper end - custom chat completions API -+ - @Override - public void setCompassTarget(Location loc) { - Preconditions.checkArgument(loc != null, "Location cannot be null"); diff --git a/patches/server/0776-Add-Velocity-IP-Forwarding-Support.patch b/patches/server/0776-Add-Velocity-IP-Forwarding-Support.patch new file mode 100644 index 000000000000..64983350dabc --- /dev/null +++ b/patches/server/0776-Add-Velocity-IP-Forwarding-Support.patch @@ -0,0 +1,242 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 8 Oct 2018 14:36:14 -0400 +Subject: [PATCH] Add Velocity IP Forwarding Support + +While Velocity supports BungeeCord-style IP forwarding, it is not secure. Users +have a lot of problems setting up firewalls or setting up plugins like IPWhitelist. +Further, the BungeeCord IP forwarding protocol still retains essentially its original +form, when there is brand new support for custom login plugin messages in 1.13. + +Velocity's modern IP forwarding uses an HMAC-SHA256 code to ensure authenticity +of messages, is packed into a binary format that is smaller than BungeeCord's +forwarding, and is integrated into the Minecraft login process by using the 1.13 +login plugin message packet. + +diff --git a/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3c31ff3330c2e925e205c0c9ff4f0b832682b576 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java +@@ -0,0 +1,86 @@ ++package com.destroystokyo.paper.proxy; ++ ++import io.papermc.paper.configuration.GlobalConfiguration; ++import com.google.common.net.InetAddresses; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.properties.Property; ++import java.net.InetAddress; ++import java.security.InvalidKeyException; ++import java.security.MessageDigest; ++import java.security.NoSuchAlgorithmException; ++import java.util.UUID; ++import javax.crypto.Mac; ++import javax.crypto.spec.SecretKeySpec; ++import net.minecraft.network.FriendlyByteBuf; ++import net.minecraft.network.protocol.login.custom.CustomQueryPayload; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.entity.player.ProfilePublicKey; ++ ++/** ++ * While Velocity supports BungeeCord-style IP forwarding, it is not secure. Users ++ * have a lot of problems setting up firewalls or setting up plugins like IPWhitelist. ++ * Further, the BungeeCord IP forwarding protocol still retains essentially its original ++ * form, when there is brand-new support for custom login plugin messages in 1.13. ++ *

    ++ * Velocity's modern IP forwarding uses an HMAC-SHA256 code to ensure authenticity ++ * of messages, is packed into a binary format that is smaller than BungeeCord's ++ * forwarding, and is integrated into the Minecraft login process by using the 1.13 ++ * login plugin message packet. ++ */ ++public class VelocityProxy { ++ private static final int SUPPORTED_FORWARDING_VERSION = 1; ++ public static final int MODERN_FORWARDING_WITH_KEY = 2; ++ public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3; ++ public static final int MODERN_LAZY_SESSION = 4; ++ public static final byte MAX_SUPPORTED_FORWARDING_VERSION = MODERN_LAZY_SESSION; ++ public static final ResourceLocation PLAYER_INFO_CHANNEL = new ResourceLocation("velocity", "player_info"); ++ ++ public static boolean checkIntegrity(final FriendlyByteBuf buf) { ++ final byte[] signature = new byte[32]; ++ buf.readBytes(signature); ++ ++ final byte[] data = new byte[buf.readableBytes()]; ++ buf.getBytes(buf.readerIndex(), data); ++ ++ try { ++ final Mac mac = Mac.getInstance("HmacSHA256"); ++ mac.init(new SecretKeySpec(GlobalConfiguration.get().proxies.velocity.secret.getBytes(java.nio.charset.StandardCharsets.UTF_8), "HmacSHA256")); ++ final byte[] mySignature = mac.doFinal(data); ++ if (!MessageDigest.isEqual(signature, mySignature)) { ++ return false; ++ } ++ } catch (final InvalidKeyException | NoSuchAlgorithmException e) { ++ throw new AssertionError(e); ++ } ++ ++ return true; ++ } ++ ++ public static InetAddress readAddress(final FriendlyByteBuf buf) { ++ return InetAddresses.forString(buf.readUtf(Short.MAX_VALUE)); ++ } ++ ++ public static GameProfile createProfile(final FriendlyByteBuf buf) { ++ final GameProfile profile = new GameProfile(buf.readUUID(), buf.readUtf(16)); ++ readProperties(buf, profile); ++ return profile; ++ } ++ ++ private static void readProperties(final FriendlyByteBuf buf, final GameProfile profile) { ++ final int properties = buf.readVarInt(); ++ for (int i1 = 0; i1 < properties; i1++) { ++ final String name = buf.readUtf(Short.MAX_VALUE); ++ final String value = buf.readUtf(Short.MAX_VALUE); ++ final String signature = buf.readBoolean() ? buf.readUtf(Short.MAX_VALUE) : null; ++ profile.getProperties().put(name, new Property(name, value, signature)); ++ } ++ } ++ ++ public static ProfilePublicKey.Data readForwardedKey(FriendlyByteBuf buf) { ++ return new ProfilePublicKey.Data(buf); ++ } ++ ++ public static UUID readSignerUuidOrElse(FriendlyByteBuf buf, UUID orElse) { ++ return buf.readBoolean() ? buf.readUUID() : orElse; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 608d860b940dee870a3df3d52efaed5e9eab17cf..5675931fe3ea896027a510944fc484f41f5ef555 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -274,13 +274,20 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.STARTUP); + // CraftBukkit end + ++ // Paper start - Add Velocity IP Forwarding Support ++ boolean usingProxy = org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled; ++ String proxyFlavor = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "Velocity" : "BungeeCord"; ++ String proxyLink = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "https://docs.papermc.io/velocity/security" : "http://www.spigotmc.org/wiki/firewall-guide/"; ++ // Paper end - Add Velocity IP Forwarding Support + if (!this.usesAuthentication()) { + DedicatedServer.LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!"); + DedicatedServer.LOGGER.warn("The server will make no attempt to authenticate usernames. Beware."); + // Spigot start +- if (org.spigotmc.SpigotConfig.bungee) { +- DedicatedServer.LOGGER.warn("Whilst this makes it possible to use BungeeCord, unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose."); +- DedicatedServer.LOGGER.warn("Please see http://www.spigotmc.org/wiki/firewall-guide/ for further information."); ++ // Paper start - Add Velocity IP Forwarding Support ++ if (usingProxy) { ++ DedicatedServer.LOGGER.warn("Whilst this makes it possible to use " + proxyFlavor + ", unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose."); ++ DedicatedServer.LOGGER.warn("Please see " + proxyLink + " for further information."); ++ // Paper end - Add Velocity IP Forwarding Support + } else { + DedicatedServer.LOGGER.warn("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose."); + } +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index fb582acfe9e5cb68314ee39e1d54a550d6700e76..b8d8f14c30786321949901ca5184c43bee716355 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -64,6 +64,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + private final String serverId; + private ServerPlayer player; // CraftBukkit + public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding ++ private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support + + public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection) { + this.state = ServerLoginPacketListenerImpl.State.HELLO; +@@ -149,6 +150,16 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + this.state = ServerLoginPacketListenerImpl.State.KEY; + this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.challenge)); + } else { ++ // Paper start - Add Velocity IP Forwarding Support ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { ++ this.velocityLoginMessageId = java.util.concurrent.ThreadLocalRandom.current().nextInt(); ++ net.minecraft.network.FriendlyByteBuf buf = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.buffer()); ++ buf.writeByte(com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); ++ net.minecraft.network.protocol.login.ClientboundCustomQueryPacket packet1 = new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket(this.velocityLoginMessageId, new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket.PlayerInfoChannelPayload(com.destroystokyo.paper.proxy.VelocityProxy.PLAYER_INFO_CHANNEL, buf)); ++ this.connection.send(packet1); ++ return; ++ } ++ // Paper end - Add Velocity IP Forwarding Support + // CraftBukkit start + // Paper start - Cache authenticator threads + authenticatorPool.execute(new Runnable() { +@@ -289,6 +300,12 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + + // CraftBukkit start + private GameProfile callPlayerPreLoginEvents(GameProfile gameprofile) throws Exception { // Paper - Add more fields to AsyncPlayerPreLoginEvent ++ // Paper start - Add Velocity IP Forwarding Support ++ if (ServerLoginPacketListenerImpl.this.velocityLoginMessageId == -1 && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { ++ disconnect("This server requires you to connect with Velocity."); ++ return gameprofile; ++ } ++ // Paper end - Add Velocity IP Forwarding Support + String playerName = gameprofile.getName(); + java.net.InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); + java.util.UUID uniqueId = gameprofile.getId(); +@@ -334,6 +351,51 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + + @Override + public void handleCustomQueryPacket(ServerboundCustomQueryAnswerPacket packet) { ++ // Paper start - Add Velocity IP Forwarding Support ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && packet.transactionId() == this.velocityLoginMessageId) { ++ ServerboundCustomQueryAnswerPacket.QueryAnswerPayload payload = (ServerboundCustomQueryAnswerPacket.QueryAnswerPayload)packet.payload(); ++ if (payload == null) { ++ this.disconnect("This server requires you to connect with Velocity."); ++ return; ++ } ++ ++ net.minecraft.network.FriendlyByteBuf buf = payload.buffer; ++ ++ if (!com.destroystokyo.paper.proxy.VelocityProxy.checkIntegrity(buf)) { ++ this.disconnect("Unable to verify player details"); ++ return; ++ } ++ ++ int version = buf.readVarInt(); ++ if (version > com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION) { ++ throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted upto " + com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); ++ } ++ ++ java.net.SocketAddress listening = this.connection.getRemoteAddress(); ++ int port = 0; ++ if (listening instanceof java.net.InetSocketAddress) { ++ port = ((java.net.InetSocketAddress) listening).getPort(); ++ } ++ this.connection.address = new java.net.InetSocketAddress(com.destroystokyo.paper.proxy.VelocityProxy.readAddress(buf), port); ++ ++ this.authenticatedProfile = com.destroystokyo.paper.proxy.VelocityProxy.createProfile(buf); ++ ++ //TODO Update handling for lazy sessions, might not even have to do anything? ++ ++ // Proceed with login ++ authenticatorPool.execute(() -> { ++ try { ++ final GameProfile gameprofile = this.callPlayerPreLoginEvents(this.authenticatedProfile); ++ ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId()); ++ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); ++ } catch (Exception ex) { ++ disconnect("Failed to verify username!"); ++ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + this.authenticatedProfile.getName(), ex); ++ } ++ }); ++ return; ++ } ++ // Paper end - Add Velocity IP Forwarding Support + this.disconnect(ServerLoginPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index e849e706660bf0b0ef1748dfee3ba75e5aff9863..10450a2bb6973a647fb6969d3c142e7a6a1011a4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -815,7 +815,7 @@ public final class CraftServer implements Server { + @Override + public long getConnectionThrottle() { + // Spigot Start - Automatically set connection throttle for bungee configurations +- if (org.spigotmc.SpigotConfig.bungee) { ++ if (org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { // Paper - Add Velocity IP Forwarding Support + return -1; + } else { + return this.configuration.getInt("settings.connection-throttle"); diff --git a/patches/server/0777-Add-NamespacedKey-biome-methods.patch b/patches/server/0777-Add-NamespacedKey-biome-methods.patch new file mode 100644 index 000000000000..bf854146f460 --- /dev/null +++ b/patches/server/0777-Add-NamespacedKey-biome-methods.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Josh Roy <10731363+JRoy@users.noreply.github.com> +Date: Sun, 14 Aug 2022 12:23:11 -0400 +Subject: [PATCH] Add NamespacedKey biome methods + +Co-authored-by: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 3f582c5653e13875cce4ef8ecd279d8a3d2b2dc2..5f93c5a6e1c381898c50332099cc98063a108b4e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -620,6 +620,19 @@ public final class CraftMagicNumbers implements UnsafeValues { + Preconditions.checkArgument(material.isBlock(), material + " is not a block"); + return getBlock(material).hasCollision; + } ++ ++ @Override ++ public org.bukkit.NamespacedKey getBiomeKey(org.bukkit.RegionAccessor accessor, int x, int y, int z) { ++ org.bukkit.craftbukkit.CraftRegionAccessor cra = (org.bukkit.craftbukkit.CraftRegionAccessor) accessor; ++ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(cra.getHandle().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME).getKey(cra.getHandle().getBiome(new net.minecraft.core.BlockPos(x, y, z)).value())); ++ } ++ ++ @Override ++ public void setBiomeKey(org.bukkit.RegionAccessor accessor, int x, int y, int z, org.bukkit.NamespacedKey biomeKey) { ++ org.bukkit.craftbukkit.CraftRegionAccessor cra = (org.bukkit.craftbukkit.CraftRegionAccessor) accessor; ++ net.minecraft.core.Holder biomeBase = cra.getHandle().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME).getHolderOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.BIOME, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(biomeKey))); ++ cra.setBiome(x, y, z, biomeBase); ++ } + // Paper end + + /** diff --git a/patches/server/0778-Fix-plugin-loggers-on-server-shutdown.patch b/patches/server/0778-Fix-plugin-loggers-on-server-shutdown.patch new file mode 100644 index 000000000000..f27e838474ef --- /dev/null +++ b/patches/server/0778-Fix-plugin-loggers-on-server-shutdown.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?= +Date: Sat, 5 Jun 2021 13:45:15 +0200 +Subject: [PATCH] Fix plugin loggers on server shutdown + + +diff --git a/src/main/java/io/papermc/paper/log/CustomLogManager.java b/src/main/java/io/papermc/paper/log/CustomLogManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c1d3bac79bb8b4796c013ff4472f75dcd79602dc +--- /dev/null ++++ b/src/main/java/io/papermc/paper/log/CustomLogManager.java +@@ -0,0 +1,26 @@ ++package io.papermc.paper.log; ++ ++import java.util.logging.LogManager; ++ ++public class CustomLogManager extends LogManager { ++ private static CustomLogManager instance; ++ ++ public CustomLogManager() { ++ instance = this; ++ } ++ ++ @Override ++ public void reset() { ++ // Ignore calls to this method ++ } ++ ++ private void superReset() { ++ super.reset(); ++ } ++ ++ public static void forceReset() { ++ if (instance != null) { ++ instance.superReset(); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index ab1f4e62b2ffed99b47ae23cae172f20ed586b27..97dbe5a44d2791c6dee830654c3935f4ac54aad4 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1223,6 +1223,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Wed, 24 Aug 2022 09:54:11 -0400 +Subject: [PATCH] Stop large look changes from crashing the server + +Co-authored-by: Jaren Knodel + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index e3b19c6e51596b854b6d2acbc67f6fb109d62a92..a33c5870874674c29bf5e50ea58357965d24e54a 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3068,37 +3068,15 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.level().getProfiler().pop(); + this.level().getProfiler().push("rangeChecks"); + +- while (this.getYRot() - this.yRotO < -180.0F) { +- this.yRotO -= 360.0F; +- } +- +- while (this.getYRot() - this.yRotO >= 180.0F) { +- this.yRotO += 360.0F; +- } ++ // Paper start - stop large pitch and yaw changes from crashing the server ++ this.yRotO += Math.round((this.getYRot() - this.yRotO) / 360.0F) * 360.0F; + +- while (this.yBodyRot - this.yBodyRotO < -180.0F) { +- this.yBodyRotO -= 360.0F; +- } ++ this.yBodyRotO += Math.round((this.yBodyRot - this.yBodyRotO) / 360.0F) * 360.0F; + +- while (this.yBodyRot - this.yBodyRotO >= 180.0F) { +- this.yBodyRotO += 360.0F; +- } +- +- while (this.getXRot() - this.xRotO < -180.0F) { +- this.xRotO -= 360.0F; +- } ++ this.xRotO += Math.round((this.getXRot() - this.xRotO) / 360.0F) * 360.0F; + +- while (this.getXRot() - this.xRotO >= 180.0F) { +- this.xRotO += 360.0F; +- } +- +- while (this.yHeadRot - this.yHeadRotO < -180.0F) { +- this.yHeadRotO -= 360.0F; +- } +- +- while (this.yHeadRot - this.yHeadRotO >= 180.0F) { +- this.yHeadRotO += 360.0F; +- } ++ this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F; ++ // Paper end + + this.level().getProfiler().pop(); + this.animStep += f2; +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 20842ed5b730dda88efd0cda9292a37f879a4017..0a207f3f2e4c0790e784fb4b0c3c2dfa49c39724 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -259,13 +259,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { + } + + protected static float lerpRotation(float prevRot, float newRot) { +- while (newRot - prevRot < -180.0F) { +- prevRot -= 360.0F; +- } +- +- while (newRot - prevRot >= 180.0F) { +- prevRot += 360.0F; +- } ++ prevRot += Math.round((newRot - prevRot) / 360.0F) * 360.0F; // Paper - stop large look changes from crashing the server + + return Mth.lerp(0.2F, prevRot, newRot); + } diff --git a/patches/server/0780-Add-Velocity-IP-Forwarding-Support.patch b/patches/server/0780-Add-Velocity-IP-Forwarding-Support.patch deleted file mode 100644 index 844fc51f4e51..000000000000 --- a/patches/server/0780-Add-Velocity-IP-Forwarding-Support.patch +++ /dev/null @@ -1,242 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Mon, 8 Oct 2018 14:36:14 -0400 -Subject: [PATCH] Add Velocity IP Forwarding Support - -While Velocity supports BungeeCord-style IP forwarding, it is not secure. Users -have a lot of problems setting up firewalls or setting up plugins like IPWhitelist. -Further, the BungeeCord IP forwarding protocol still retains essentially its original -form, when there is brand new support for custom login plugin messages in 1.13. - -Velocity's modern IP forwarding uses an HMAC-SHA256 code to ensure authenticity -of messages, is packed into a binary format that is smaller than BungeeCord's -forwarding, and is integrated into the Minecraft login process by using the 1.13 -login plugin message packet. - -diff --git a/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3c31ff3330c2e925e205c0c9ff4f0b832682b576 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java -@@ -0,0 +1,86 @@ -+package com.destroystokyo.paper.proxy; -+ -+import io.papermc.paper.configuration.GlobalConfiguration; -+import com.google.common.net.InetAddresses; -+import com.mojang.authlib.GameProfile; -+import com.mojang.authlib.properties.Property; -+import java.net.InetAddress; -+import java.security.InvalidKeyException; -+import java.security.MessageDigest; -+import java.security.NoSuchAlgorithmException; -+import java.util.UUID; -+import javax.crypto.Mac; -+import javax.crypto.spec.SecretKeySpec; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.protocol.login.custom.CustomQueryPayload; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.entity.player.ProfilePublicKey; -+ -+/** -+ * While Velocity supports BungeeCord-style IP forwarding, it is not secure. Users -+ * have a lot of problems setting up firewalls or setting up plugins like IPWhitelist. -+ * Further, the BungeeCord IP forwarding protocol still retains essentially its original -+ * form, when there is brand-new support for custom login plugin messages in 1.13. -+ *

    -+ * Velocity's modern IP forwarding uses an HMAC-SHA256 code to ensure authenticity -+ * of messages, is packed into a binary format that is smaller than BungeeCord's -+ * forwarding, and is integrated into the Minecraft login process by using the 1.13 -+ * login plugin message packet. -+ */ -+public class VelocityProxy { -+ private static final int SUPPORTED_FORWARDING_VERSION = 1; -+ public static final int MODERN_FORWARDING_WITH_KEY = 2; -+ public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3; -+ public static final int MODERN_LAZY_SESSION = 4; -+ public static final byte MAX_SUPPORTED_FORWARDING_VERSION = MODERN_LAZY_SESSION; -+ public static final ResourceLocation PLAYER_INFO_CHANNEL = new ResourceLocation("velocity", "player_info"); -+ -+ public static boolean checkIntegrity(final FriendlyByteBuf buf) { -+ final byte[] signature = new byte[32]; -+ buf.readBytes(signature); -+ -+ final byte[] data = new byte[buf.readableBytes()]; -+ buf.getBytes(buf.readerIndex(), data); -+ -+ try { -+ final Mac mac = Mac.getInstance("HmacSHA256"); -+ mac.init(new SecretKeySpec(GlobalConfiguration.get().proxies.velocity.secret.getBytes(java.nio.charset.StandardCharsets.UTF_8), "HmacSHA256")); -+ final byte[] mySignature = mac.doFinal(data); -+ if (!MessageDigest.isEqual(signature, mySignature)) { -+ return false; -+ } -+ } catch (final InvalidKeyException | NoSuchAlgorithmException e) { -+ throw new AssertionError(e); -+ } -+ -+ return true; -+ } -+ -+ public static InetAddress readAddress(final FriendlyByteBuf buf) { -+ return InetAddresses.forString(buf.readUtf(Short.MAX_VALUE)); -+ } -+ -+ public static GameProfile createProfile(final FriendlyByteBuf buf) { -+ final GameProfile profile = new GameProfile(buf.readUUID(), buf.readUtf(16)); -+ readProperties(buf, profile); -+ return profile; -+ } -+ -+ private static void readProperties(final FriendlyByteBuf buf, final GameProfile profile) { -+ final int properties = buf.readVarInt(); -+ for (int i1 = 0; i1 < properties; i1++) { -+ final String name = buf.readUtf(Short.MAX_VALUE); -+ final String value = buf.readUtf(Short.MAX_VALUE); -+ final String signature = buf.readBoolean() ? buf.readUtf(Short.MAX_VALUE) : null; -+ profile.getProperties().put(name, new Property(name, value, signature)); -+ } -+ } -+ -+ public static ProfilePublicKey.Data readForwardedKey(FriendlyByteBuf buf) { -+ return new ProfilePublicKey.Data(buf); -+ } -+ -+ public static UUID readSignerUuidOrElse(FriendlyByteBuf buf, UUID orElse) { -+ return buf.readBoolean() ? buf.readUUID() : orElse; -+ } -+} -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 8d7d5cadbd65833d46dce71609e938290c81f772..c666bcd29d39ee7bca05edac348b7fa0325e80ab 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -274,13 +274,20 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.STARTUP); - // CraftBukkit end - -+ // Paper start - Add Velocity IP Forwarding Support -+ boolean usingProxy = org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled; -+ String proxyFlavor = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "Velocity" : "BungeeCord"; -+ String proxyLink = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "https://docs.papermc.io/velocity/security" : "http://www.spigotmc.org/wiki/firewall-guide/"; -+ // Paper end - Add Velocity IP Forwarding Support - if (!this.usesAuthentication()) { - DedicatedServer.LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!"); - DedicatedServer.LOGGER.warn("The server will make no attempt to authenticate usernames. Beware."); - // Spigot start -- if (org.spigotmc.SpigotConfig.bungee) { -- DedicatedServer.LOGGER.warn("Whilst this makes it possible to use BungeeCord, unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose."); -- DedicatedServer.LOGGER.warn("Please see http://www.spigotmc.org/wiki/firewall-guide/ for further information."); -+ // Paper start - Add Velocity IP Forwarding Support -+ if (usingProxy) { -+ DedicatedServer.LOGGER.warn("Whilst this makes it possible to use " + proxyFlavor + ", unless access to your server is properly restricted, it also opens up the ability for hackers to connect with any username they choose."); -+ DedicatedServer.LOGGER.warn("Please see " + proxyLink + " for further information."); -+ // Paper end - Add Velocity IP Forwarding Support - } else { - DedicatedServer.LOGGER.warn("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose."); - } -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index ebda34825bbd10145a81d54c345e31c2a4fb5de4..42ae62bdbe11fdfbacebf621d64e7c4985bbd1c7 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -64,6 +64,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - private final String serverId; - private ServerPlayer player; // CraftBukkit - public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding -+ private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support - - public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection) { - this.state = ServerLoginPacketListenerImpl.State.HELLO; -@@ -149,6 +150,16 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - this.state = ServerLoginPacketListenerImpl.State.KEY; - this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.challenge)); - } else { -+ // Paper start - Add Velocity IP Forwarding Support -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { -+ this.velocityLoginMessageId = java.util.concurrent.ThreadLocalRandom.current().nextInt(); -+ net.minecraft.network.FriendlyByteBuf buf = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.buffer()); -+ buf.writeByte(com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); -+ net.minecraft.network.protocol.login.ClientboundCustomQueryPacket packet1 = new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket(this.velocityLoginMessageId, new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket.PlayerInfoChannelPayload(com.destroystokyo.paper.proxy.VelocityProxy.PLAYER_INFO_CHANNEL, buf)); -+ this.connection.send(packet1); -+ return; -+ } -+ // Paper end - Add Velocity IP Forwarding Support - // CraftBukkit start - // Paper start - Cache authenticator threads - authenticatorPool.execute(new Runnable() { -@@ -289,6 +300,12 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - - // CraftBukkit start - private GameProfile callPlayerPreLoginEvents(GameProfile gameprofile) throws Exception { // Paper - Add more fields to AsyncPlayerPreLoginEvent -+ // Paper start - Add Velocity IP Forwarding Support -+ if (ServerLoginPacketListenerImpl.this.velocityLoginMessageId == -1 && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { -+ disconnect("This server requires you to connect with Velocity."); -+ return gameprofile; -+ } -+ // Paper end - Add Velocity IP Forwarding Support - String playerName = gameprofile.getName(); - java.net.InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); - java.util.UUID uniqueId = gameprofile.getId(); -@@ -334,6 +351,51 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - - @Override - public void handleCustomQueryPacket(ServerboundCustomQueryAnswerPacket packet) { -+ // Paper start - Add Velocity IP Forwarding Support -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && packet.transactionId() == this.velocityLoginMessageId) { -+ ServerboundCustomQueryAnswerPacket.QueryAnswerPayload payload = (ServerboundCustomQueryAnswerPacket.QueryAnswerPayload)packet.payload(); -+ if (payload == null) { -+ this.disconnect("This server requires you to connect with Velocity."); -+ return; -+ } -+ -+ net.minecraft.network.FriendlyByteBuf buf = payload.buffer; -+ -+ if (!com.destroystokyo.paper.proxy.VelocityProxy.checkIntegrity(buf)) { -+ this.disconnect("Unable to verify player details"); -+ return; -+ } -+ -+ int version = buf.readVarInt(); -+ if (version > com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION) { -+ throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted upto " + com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); -+ } -+ -+ java.net.SocketAddress listening = this.connection.getRemoteAddress(); -+ int port = 0; -+ if (listening instanceof java.net.InetSocketAddress) { -+ port = ((java.net.InetSocketAddress) listening).getPort(); -+ } -+ this.connection.address = new java.net.InetSocketAddress(com.destroystokyo.paper.proxy.VelocityProxy.readAddress(buf), port); -+ -+ this.authenticatedProfile = com.destroystokyo.paper.proxy.VelocityProxy.createProfile(buf); -+ -+ //TODO Update handling for lazy sessions, might not even have to do anything? -+ -+ // Proceed with login -+ authenticatorPool.execute(() -> { -+ try { -+ final GameProfile gameprofile = this.callPlayerPreLoginEvents(this.authenticatedProfile); -+ ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId()); -+ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); -+ } catch (Exception ex) { -+ disconnect("Failed to verify username!"); -+ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + this.authenticatedProfile.getName(), ex); -+ } -+ }); -+ return; -+ } -+ // Paper end - Add Velocity IP Forwarding Support - this.disconnect(ServerLoginPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 4c343b5b811b87b2c4f40efaaf376368ce73fcdb..f5bd1a68502be87e03923934b25fb3e982762be7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -803,7 +803,7 @@ public final class CraftServer implements Server { - @Override - public long getConnectionThrottle() { - // Spigot Start - Automatically set connection throttle for bungee configurations -- if (org.spigotmc.SpigotConfig.bungee) { -+ if (org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { // Paper - Add Velocity IP Forwarding Support - return -1; - } else { - return this.configuration.getInt("settings.connection-throttle"); diff --git a/patches/server/0780-Fire-EntityChangeBlockEvent-in-more-places.patch b/patches/server/0780-Fire-EntityChangeBlockEvent-in-more-places.patch new file mode 100644 index 000000000000..b829dfe19849 --- /dev/null +++ b/patches/server/0780-Fire-EntityChangeBlockEvent-in-more-places.patch @@ -0,0 +1,315 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 9 Aug 2021 20:45:46 -0700 +Subject: [PATCH] Fire EntityChangeBlockEvent in more places + +Co-authored-by: ChristopheG <61288881+chrisgdt@users.noreply.github.com> + +diff --git a/src/main/java/net/minecraft/world/entity/LightningBolt.java b/src/main/java/net/minecraft/world/entity/LightningBolt.java +index 09c981e2573305bd7f5c3cbcc8f240fa8705a9f2..0db0d67f9ac15372becc1166c37f7f0aede4a4da 100644 +--- a/src/main/java/net/minecraft/world/entity/LightningBolt.java ++++ b/src/main/java/net/minecraft/world/entity/LightningBolt.java +@@ -97,7 +97,7 @@ public class LightningBolt extends Entity { + } + + this.powerLightningRod(); +- LightningBolt.clearCopperOnLightningStrike(this.level(), this.getStrikePosition()); ++ LightningBolt.clearCopperOnLightningStrike(this.level(), this.getStrikePosition(), this); // Paper - Call EntityChangeBlockEvent + this.gameEvent(GameEvent.LIGHTNING_STRIKE); + } + } +@@ -191,7 +191,7 @@ public class LightningBolt extends Entity { + } + } + +- private static void clearCopperOnLightningStrike(Level world, BlockPos pos) { ++ private static void clearCopperOnLightningStrike(Level world, BlockPos pos, Entity lightning) { // Paper - Call EntityChangeBlockEvent + BlockState iblockdata = world.getBlockState(pos); + BlockPos blockposition1; + BlockState iblockdata1; +@@ -205,24 +205,29 @@ public class LightningBolt extends Entity { + } + + if (iblockdata1.getBlock() instanceof WeatheringCopper) { +- world.setBlockAndUpdate(blockposition1, WeatheringCopper.getFirst(world.getBlockState(blockposition1))); ++ // Paper start - Call EntityChangeBlockEvent ++ BlockState newBlock = WeatheringCopper.getFirst(world.getBlockState(blockposition1)); ++ if (CraftEventFactory.callEntityChangeBlockEvent(lightning, blockposition1, newBlock)) { ++ world.setBlockAndUpdate(blockposition1, newBlock); ++ } ++ // Paper end - Call EntityChangeBlockEvent + BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); + int i = world.random.nextInt(3) + 3; + + for (int j = 0; j < i; ++j) { + int k = world.random.nextInt(8) + 1; + +- LightningBolt.randomWalkCleaningCopper(world, blockposition1, blockposition_mutableblockposition, k); ++ LightningBolt.randomWalkCleaningCopper(world, blockposition1, blockposition_mutableblockposition, k, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent + } + + } + } + +- private static void randomWalkCleaningCopper(Level world, BlockPos pos, BlockPos.MutableBlockPos mutablePos, int count) { ++ private static void randomWalkCleaningCopper(Level world, BlockPos pos, BlockPos.MutableBlockPos mutablePos, int count, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent + mutablePos.set(pos); + + for (int j = 0; j < count; ++j) { +- Optional optional = LightningBolt.randomStepCleaningCopper(world, mutablePos); ++ Optional optional = LightningBolt.randomStepCleaningCopper(world, mutablePos, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent + + if (optional.isEmpty()) { + break; +@@ -233,7 +238,7 @@ public class LightningBolt extends Entity { + + } + +- private static Optional randomStepCleaningCopper(Level world, BlockPos pos) { ++ private static Optional randomStepCleaningCopper(Level world, BlockPos pos, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent + Iterator iterator = BlockPos.randomInCube(world.random, 10, pos, 1).iterator(); + + BlockPos blockposition1; +@@ -250,6 +255,7 @@ public class LightningBolt extends Entity { + + BlockPos blockposition1Final = blockposition1; // CraftBukkit - decompile error + WeatheringCopper.getPrevious(iblockdata).ifPresent((iblockdata1) -> { ++ if (CraftEventFactory.callEntityChangeBlockEvent(lightning, blockposition1Final, iblockdata1)) // Paper - call EntityChangeBlockEvent + world.setBlockAndUpdate(blockposition1Final, iblockdata1); // CraftBukkit - decompile error + }); + world.levelEvent(3002, blockposition1, -1); +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java b/src/main/java/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java +index 2c443b421e342ebfbdf941a431ba20560521920b..91b68ee3605afdb845405e455c869e48a7fc9aab 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java +@@ -27,6 +27,12 @@ public class TryLaySpawnOnWaterNearLand { + BlockPos blockPos3 = blockPos2.above(); + if (world.getBlockState(blockPos3).isAir()) { + BlockState blockState = frogSpawn.defaultBlockState(); ++ // Paper start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockPos3, blockState)) { ++ isPregnant.erase(); // forgot pregnant memory ++ return true; ++ } ++ // Paper end + world.setBlock(blockPos3, blockState, 3); + world.gameEvent(GameEvent.BLOCK_PLACE, blockPos3, GameEvent.Context.of(entity, blockState)); + world.playSound((Player)null, entity, SoundEvents.FROG_LAY_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F); +diff --git a/src/main/java/net/minecraft/world/item/AxeItem.java b/src/main/java/net/minecraft/world/item/AxeItem.java +index db507638a97b5a33df712c54daff35b21922c0dd..2e75fd06e9e379eb95ebfe55086ffc327706ab2f 100644 +--- a/src/main/java/net/minecraft/world/item/AxeItem.java ++++ b/src/main/java/net/minecraft/world/item/AxeItem.java +@@ -38,6 +38,11 @@ public class AxeItem extends DiggerItem { + return InteractionResult.PASS; + } else { + ItemStack itemStack = context.getItemInHand(); ++ // Paper start - EntityChangeBlockEvent ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, optional.get())) { ++ return InteractionResult.PASS; ++ } ++ // Paper end + if (player instanceof ServerPlayer) { + CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); + } +diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java +index 1977e702f6af39ebf100c1f2f2edc2d1c4d003b0..cfcd1778b5ae66395400221879dde3575591b23d 100644 +--- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java ++++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java +@@ -43,6 +43,11 @@ public class EnderEyeItem extends Item { + return InteractionResult.SUCCESS; + } else { + BlockState iblockdata1 = (BlockState) iblockdata.setValue(EndPortalFrameBlock.HAS_EYE, true); ++ // Paper start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), blockposition, iblockdata1)) { ++ return InteractionResult.PASS; ++ } ++ // Paper end + + Block.pushEntitiesUp(iblockdata, iblockdata1, world, blockposition); + world.setBlock(blockposition, iblockdata1, 2); +diff --git a/src/main/java/net/minecraft/world/item/HoneycombItem.java b/src/main/java/net/minecraft/world/item/HoneycombItem.java +index 78bdf7c0a058e84cafcd831c6d6f5123c0f168b0..e0cae3b6848af74fefc37a1e3183c501155c4710 100644 +--- a/src/main/java/net/minecraft/world/item/HoneycombItem.java ++++ b/src/main/java/net/minecraft/world/item/HoneycombItem.java +@@ -39,6 +39,14 @@ public class HoneycombItem extends Item implements SignApplicator { + return getWaxed(blockState).map((state) -> { + Player player = context.getPlayer(); + ItemStack itemStack = context.getItemInHand(); ++ // Paper start - EntityChangeBlockEvent ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, state)) { ++ if (!player.isCreative()) { ++ player.containerMenu.sendAllDataToRemote(); ++ } ++ return InteractionResult.PASS; ++ } ++ // Paper end + if (player instanceof ServerPlayer) { + CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); + } +diff --git a/src/main/java/net/minecraft/world/item/PotionItem.java b/src/main/java/net/minecraft/world/item/PotionItem.java +index 5c62741e3a3854a7f674bfec49758f837f3bb9a0..b93ed2896ebeb8ec75eb3cfb717a740c97dd9622 100644 +--- a/src/main/java/net/minecraft/world/item/PotionItem.java ++++ b/src/main/java/net/minecraft/world/item/PotionItem.java +@@ -107,6 +107,12 @@ public class PotionItem extends Item { + BlockState iblockdata = world.getBlockState(blockposition); + + if (context.getClickedFace() != Direction.DOWN && iblockdata.is(BlockTags.CONVERTABLE_TO_MUD) && PotionUtils.getPotion(itemstack) == Potions.WATER) { ++ // Paper start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entityhuman, blockposition, Blocks.MUD.defaultBlockState())) { ++ entityhuman.containerMenu.sendAllDataToRemote(); ++ return InteractionResult.PASS; ++ } ++ // Paper end + world.playSound((Player) null, blockposition, SoundEvents.GENERIC_SPLASH, SoundSource.BLOCKS, 1.0F, 1.0F); + entityhuman.setItemInHand(context.getHand(), ItemUtils.createFilledResult(itemstack, entityhuman, new ItemStack(Items.GLASS_BOTTLE))); + entityhuman.awardStat(Stats.ITEM_USED.get(itemstack.getItem())); +diff --git a/src/main/java/net/minecraft/world/item/ShovelItem.java b/src/main/java/net/minecraft/world/item/ShovelItem.java +index 32995cb5efdad0bc34ecacacb78cccd21220ba8d..21212462e6b415e96536a27b2c009d1562f18946 100644 +--- a/src/main/java/net/minecraft/world/item/ShovelItem.java ++++ b/src/main/java/net/minecraft/world/item/ShovelItem.java +@@ -36,20 +36,29 @@ public class ShovelItem extends DiggerItem { + Player player = context.getPlayer(); + BlockState blockState2 = FLATTENABLES.get(blockState.getBlock()); + BlockState blockState3 = null; ++ Runnable afterAction = null; // Paper + if (blockState2 != null && level.getBlockState(blockPos.above()).isAir()) { +- level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); ++ afterAction = () -> level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); // Paper + blockState3 = blockState2; + } else if (blockState.getBlock() instanceof CampfireBlock && blockState.getValue(CampfireBlock.LIT)) { ++ afterAction = () -> { // Paper + if (!level.isClientSide()) { + level.levelEvent((Player)null, 1009, blockPos, 0); + } + + CampfireBlock.dowse(context.getPlayer(), level, blockPos, blockState); ++ }; // Paper + blockState3 = blockState.setValue(CampfireBlock.LIT, Boolean.valueOf(false)); + } + + if (blockState3 != null) { + if (!level.isClientSide) { ++ // Paper start ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), blockPos, blockState3)) { ++ return InteractionResult.PASS; ++ } ++ afterAction.run(); ++ // Paper end + level.setBlock(blockPos, blockState3, 11); + level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, blockState3)); + if (player != null) { +diff --git a/src/main/java/net/minecraft/world/level/block/CakeBlock.java b/src/main/java/net/minecraft/world/level/block/CakeBlock.java +index 49fe91a8eaeb2580c8ad0166e72540168af605f6..ca1ccedb5a551328ebfad907f39594b220efaefe 100644 +--- a/src/main/java/net/minecraft/world/level/block/CakeBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CakeBlock.java +@@ -62,6 +62,12 @@ public class CakeBlock extends Block { + Block block = Block.byItem(item); + + if (block instanceof CandleBlock) { ++ // Paper start - call change block event ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, CandleCakeBlock.byCandle(block))) { ++ player.containerMenu.sendAllDataToRemote(); // update inv because candle could decrease ++ return InteractionResult.PASS; ++ } ++ // Paper end - call change block event + if (!player.isCreative()) { + itemstack.shrink(1); + } +@@ -91,6 +97,14 @@ public class CakeBlock extends Block { + if (!player.canEat(false)) { + return InteractionResult.PASS; + } else { ++ // Paper start - call change block event ++ int i = state.getValue(CakeBlock.BITES); ++ final BlockState newState = i < MAX_BITES ? state.setValue(CakeBlock.BITES, i + 1) : world.getFluidState(pos).createLegacyBlock(); ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, newState)) { ++ ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().sendHealthUpdate(); ++ return InteractionResult.PASS; // return a non-consume result to cake blocks don't drop their candles ++ } ++ // Paper end - call change block event + player.awardStat(Stats.EAT_CAKE_SLICE); + // CraftBukkit start + // entityhuman.getFoodData().eat(2, 0.1F); +@@ -104,7 +118,7 @@ public class CakeBlock extends Block { + + ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().sendHealthUpdate(); + // CraftBukkit end +- int i = (Integer) state.getValue(CakeBlock.BITES); ++ // Paper - move up + + world.gameEvent((Entity) player, GameEvent.EAT, pos); + if (i < 6) { +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 6cccdd1d19488275ff3fe90838cf1c31e844d517..413b307acaad5823b9e06f49fa2faf561f5f7b9a 100644 +--- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java +@@ -238,6 +238,11 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + if (i < 8 && ComposterBlock.COMPOSTABLES.containsKey(itemstack.getItem())) { + if (i < 7 && !world.isClientSide) { + BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, itemstack); ++ // Paper start - handle cancelled events ++ if (iblockdata1 == null) { ++ return InteractionResult.PASS; ++ } ++ // Paper end + + world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); + player.awardStat(Stats.ITEM_USED.get(itemstack.getItem())); +@@ -261,11 +266,16 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + if (i < 7 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) { + // CraftBukkit start + double rand = world.getRandom().nextDouble(); +- BlockState iblockdata1 = ComposterBlock.addItem(user, state, DummyGeneratorAccess.INSTANCE, pos, stack, rand); +- if (state == iblockdata1 || !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(user, pos, iblockdata1)) { ++ BlockState iblockdata1 = null; // Paper ++ if (false && (state == iblockdata1 || !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(user, pos, iblockdata1))) { // Paper - move event call into addItem + return state; + } + iblockdata1 = ComposterBlock.addItem(user, state, world, pos, stack, rand); ++ // Paper start - handle cancelled events ++ if (iblockdata1 == null) { ++ return state; ++ } ++ // Paper end + // CraftBukkit end + + stack.shrink(1); +@@ -306,11 +316,13 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + return iblockdata1; + } + ++ @Nullable // Paper + static BlockState addItem(@Nullable Entity user, BlockState state, LevelAccessor world, BlockPos pos, ItemStack stack) { + // CraftBukkit start + return ComposterBlock.addItem(user, state, world, pos, stack, world.getRandom().nextDouble()); + } + ++ @Nullable // Paper - make it nullable + static BlockState addItem(@Nullable Entity entity, BlockState iblockdata, LevelAccessor generatoraccess, BlockPos blockposition, ItemStack itemstack, double rand) { + // CraftBukkit end + int i = (Integer) iblockdata.getValue(ComposterBlock.LEVEL); +@@ -321,6 +333,11 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { + } else { + int j = i + 1; + BlockState iblockdata1 = (BlockState) iblockdata.setValue(ComposterBlock.LEVEL, j); ++ // Paper start - move the EntityChangeBlockEvent here to avoid conflict later for the compost events ++ if (entity != null && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, iblockdata1)) { ++ return null; ++ } ++ // Paper end + + generatoraccess.setBlock(blockposition, iblockdata1, 3); + generatoraccess.gameEvent(GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entity, iblockdata1)); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +index 67c9009b735429e887e706baf50a6023d572a46c..7956002e2d4d583c27e277562312d27ea6871557 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +@@ -120,7 +120,7 @@ public class DummyGeneratorAccess implements WorldGenLevel { + + @Override + public void gameEvent(GameEvent event, Vec3 emitterPos, GameEvent.Context emitter) { +- // Used by BlockComposter ++ // Used by ComposterBlock + } + + @Override diff --git a/patches/server/0781-Add-NamespacedKey-biome-methods.patch b/patches/server/0781-Add-NamespacedKey-biome-methods.patch deleted file mode 100644 index 1656e9d9f230..000000000000 --- a/patches/server/0781-Add-NamespacedKey-biome-methods.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Josh Roy <10731363+JRoy@users.noreply.github.com> -Date: Sun, 14 Aug 2022 12:23:11 -0400 -Subject: [PATCH] Add NamespacedKey biome methods - -Co-authored-by: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 34c19a422de27cd6aa08159186a0180215c0837d..3bf1c2a5273879a64e81bcd8c107e7bc82cf679c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -605,6 +605,19 @@ public final class CraftMagicNumbers implements UnsafeValues { - Preconditions.checkArgument(material.isBlock(), material + " is not a block"); - return getBlock(material).hasCollision; - } -+ -+ @Override -+ public org.bukkit.NamespacedKey getBiomeKey(org.bukkit.RegionAccessor accessor, int x, int y, int z) { -+ org.bukkit.craftbukkit.CraftRegionAccessor cra = (org.bukkit.craftbukkit.CraftRegionAccessor) accessor; -+ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(cra.getHandle().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME).getKey(cra.getHandle().getBiome(new net.minecraft.core.BlockPos(x, y, z)).value())); -+ } -+ -+ @Override -+ public void setBiomeKey(org.bukkit.RegionAccessor accessor, int x, int y, int z, org.bukkit.NamespacedKey biomeKey) { -+ org.bukkit.craftbukkit.CraftRegionAccessor cra = (org.bukkit.craftbukkit.CraftRegionAccessor) accessor; -+ net.minecraft.core.Holder biomeBase = cra.getHandle().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME).getHolderOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.BIOME, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(biomeKey))); -+ cra.setBiome(x, y, z, biomeBase); -+ } - // Paper end - - /** diff --git a/patches/server/0781-Missing-eating-regain-reason.patch b/patches/server/0781-Missing-eating-regain-reason.patch new file mode 100644 index 000000000000..ae0d770acd25 --- /dev/null +++ b/patches/server/0781-Missing-eating-regain-reason.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Fri, 5 Aug 2022 12:16:51 +0200 +Subject: [PATCH] Missing eating regain reason + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java +index 1d0c424be2b67cad0f8bca85070a9c46a6b283da..f760ce7d9df79ef58f8963de3e901cba3e12fcaa 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Cat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java +@@ -387,7 +387,7 @@ public class Cat extends TamableAnimal implements VariantHolder { + if (!(item instanceof DyeItem)) { + if (item.isEdible() && this.isFood(itemstack) && this.getHealth() < this.getMaxHealth()) { + this.usePlayerItem(player, hand, itemstack); +- this.heal((float) item.getFoodProperties().getNutrition()); ++ this.heal((float) item.getFoodProperties().getNutrition(), org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason + return InteractionResult.CONSUME; + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java +index 869f60e9407ed1c5bee536ef91a21f4d11f8f964..16a5e1247a160a7ae3eba2bab9fde42dff5d62c6 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java ++++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java +@@ -384,7 +384,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl + } else { + boolean bl = this.getHealth() < this.getMaxHealth(); + if (bl) { +- this.heal(2.0F); ++ this.heal(2.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason + } + + boolean bl2 = this.isTamed() && this.getAge() == 0 && this.canFallInLove(); +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +index 91fb62807b3c5600c83d4dc8d3fadf36e94e2133..9b5b894d43f25566ab9c3698705e978ab823a0d2 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +@@ -196,7 +196,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder 0.0F) { +- this.heal(f); ++ this.heal(f, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason + flag = true; + } + diff --git a/patches/server/0782-Fix-plugin-loggers-on-server-shutdown.patch b/patches/server/0782-Fix-plugin-loggers-on-server-shutdown.patch deleted file mode 100644 index 68314fc16724..000000000000 --- a/patches/server/0782-Fix-plugin-loggers-on-server-shutdown.patch +++ /dev/null @@ -1,67 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?= -Date: Sat, 5 Jun 2021 13:45:15 +0200 -Subject: [PATCH] Fix plugin loggers on server shutdown - - -diff --git a/src/main/java/io/papermc/paper/log/CustomLogManager.java b/src/main/java/io/papermc/paper/log/CustomLogManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c1d3bac79bb8b4796c013ff4472f75dcd79602dc ---- /dev/null -+++ b/src/main/java/io/papermc/paper/log/CustomLogManager.java -@@ -0,0 +1,26 @@ -+package io.papermc.paper.log; -+ -+import java.util.logging.LogManager; -+ -+public class CustomLogManager extends LogManager { -+ private static CustomLogManager instance; -+ -+ public CustomLogManager() { -+ instance = this; -+ } -+ -+ @Override -+ public void reset() { -+ // Ignore calls to this method -+ } -+ -+ private void superReset() { -+ super.reset(); -+ } -+ -+ public static void forceReset() { -+ if (instance != null) { -+ instance.superReset(); -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 6e7e8aa26a60d774d51148bc8dca5e5c901f81e7..aaf49ff8339e360461dedfe940449b4bf9be1b66 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1223,6 +1223,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Wed, 24 Aug 2022 09:54:11 -0400 -Subject: [PATCH] Stop large look changes from crashing the server - -Co-authored-by: Jaren Knodel - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index f5bed73a079e022eeb3b05e4c49532044852fd22..39d9f33e49cc8099e0f6dc9822e6d471b46d6e28 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3055,37 +3055,15 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.level().getProfiler().pop(); - this.level().getProfiler().push("rangeChecks"); - -- while (this.getYRot() - this.yRotO < -180.0F) { -- this.yRotO -= 360.0F; -- } -- -- while (this.getYRot() - this.yRotO >= 180.0F) { -- this.yRotO += 360.0F; -- } -+ // Paper start - stop large pitch and yaw changes from crashing the server -+ this.yRotO += Math.round((this.getYRot() - this.yRotO) / 360.0F) * 360.0F; - -- while (this.yBodyRot - this.yBodyRotO < -180.0F) { -- this.yBodyRotO -= 360.0F; -- } -+ this.yBodyRotO += Math.round((this.yBodyRot - this.yBodyRotO) / 360.0F) * 360.0F; - -- while (this.yBodyRot - this.yBodyRotO >= 180.0F) { -- this.yBodyRotO += 360.0F; -- } -- -- while (this.getXRot() - this.xRotO < -180.0F) { -- this.xRotO -= 360.0F; -- } -+ this.xRotO += Math.round((this.getXRot() - this.xRotO) / 360.0F) * 360.0F; - -- while (this.getXRot() - this.xRotO >= 180.0F) { -- this.xRotO += 360.0F; -- } -- -- while (this.yHeadRot - this.yHeadRotO < -180.0F) { -- this.yHeadRotO -= 360.0F; -- } -- -- while (this.yHeadRot - this.yHeadRotO >= 180.0F) { -- this.yHeadRotO += 360.0F; -- } -+ this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F; -+ // Paper end - - this.level().getProfiler().pop(); - this.animStep += f2; -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 20842ed5b730dda88efd0cda9292a37f879a4017..0a207f3f2e4c0790e784fb4b0c3c2dfa49c39724 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -259,13 +259,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { - } - - protected static float lerpRotation(float prevRot, float newRot) { -- while (newRot - prevRot < -180.0F) { -- prevRot -= 360.0F; -- } -- -- while (newRot - prevRot >= 180.0F) { -- prevRot += 360.0F; -- } -+ prevRot += Math.round((newRot - prevRot) / 360.0F) * 360.0F; // Paper - stop large look changes from crashing the server - - return Mth.lerp(0.2F, prevRot, newRot); - } diff --git a/patches/server/0784-Add-a-consumer-parameter-to-ProjectileSource-launchP.patch b/patches/server/0784-Add-a-consumer-parameter-to-ProjectileSource-launchP.patch new file mode 100644 index 000000000000..d7fcd122edac --- /dev/null +++ b/patches/server/0784-Add-a-consumer-parameter-to-ProjectileSource-launchP.patch @@ -0,0 +1,69 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MelnCat +Date: Mon, 19 Sep 2022 14:16:10 -0700 +Subject: [PATCH] Add a consumer parameter to ProjectileSource#launchProjectile + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 60230ddeec41485e1e1b83614a6256d9ae2cb242..1aa4f09d93f36f523923281a6fb17dc184dbed86 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -546,8 +546,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } + + @Override +- @SuppressWarnings("unchecked") + public T launchProjectile(Class projectile, Vector velocity) { ++ // Paper start - launchProjectile consumer ++ return this.launchProjectile(projectile, velocity, null); ++ } ++ ++ @Override ++ @SuppressWarnings("unchecked") ++ public T launchProjectile(Class projectile, Vector velocity, java.util.function.Consumer function) { ++ // Paper end - launchProjectile consumer + Preconditions.checkState(!this.getHandle().generation, "Cannot launch projectile during world generation"); + + net.minecraft.world.level.Level world = ((CraftWorld) this.getWorld()).getHandle(); +@@ -634,6 +641,11 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + if (velocity != null) { + ((T) launch.getBukkitEntity()).setVelocity(velocity); + } ++ // Paper start - launchProjectile consumer ++ if (function != null) { ++ function.accept((T) launch.getBukkitEntity()); ++ } ++ // Paper end - launchProjectile consumer + + world.addFreshEntity(launch); + return (T) launch.getBukkitEntity(); +diff --git a/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java b/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java +index faab60e735aca230cb4c0b7db21e6a9a237daf6e..89401fc942c151941790b1152a35357aa51fcdba 100644 +--- a/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java ++++ b/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java +@@ -55,6 +55,13 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { + + @Override + public T launchProjectile(Class projectile, Vector velocity) { ++ // Paper start - launchProjectile consumer ++ return this.launchProjectile(projectile, velocity, null); ++ } ++ ++ @Override ++ public T launchProjectile(Class projectile, Vector velocity, java.util.function.Consumer function) { ++ // Paper end - launchProjectile consumer + Preconditions.checkArgument(this.getBlock().getType() == Material.DISPENSER, "Block is no longer dispenser"); + // Copied from BlockDispenser.dispense() + BlockSource sourceblock = new BlockSource((ServerLevel) this.dispenserBlock.getLevel(), this.dispenserBlock.getBlockPos(), this.dispenserBlock.getBlockState(), this.dispenserBlock); +@@ -145,6 +152,11 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { + if (velocity != null) { + ((T) launch.getBukkitEntity()).setVelocity(velocity); + } ++ // Paper start ++ if (function != null) { ++ function.accept((T) launch.getBukkitEntity()); ++ } ++ // Paper end + + world.addFreshEntity(launch); + return (T) launch.getBukkitEntity(); diff --git a/patches/server/0784-Fire-EntityChangeBlockEvent-in-more-places.patch b/patches/server/0784-Fire-EntityChangeBlockEvent-in-more-places.patch deleted file mode 100644 index 168322884aa5..000000000000 --- a/patches/server/0784-Fire-EntityChangeBlockEvent-in-more-places.patch +++ /dev/null @@ -1,315 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 9 Aug 2021 20:45:46 -0700 -Subject: [PATCH] Fire EntityChangeBlockEvent in more places - -Co-authored-by: ChristopheG <61288881+chrisgdt@users.noreply.github.com> - -diff --git a/src/main/java/net/minecraft/world/entity/LightningBolt.java b/src/main/java/net/minecraft/world/entity/LightningBolt.java -index 09c981e2573305bd7f5c3cbcc8f240fa8705a9f2..0db0d67f9ac15372becc1166c37f7f0aede4a4da 100644 ---- a/src/main/java/net/minecraft/world/entity/LightningBolt.java -+++ b/src/main/java/net/minecraft/world/entity/LightningBolt.java -@@ -97,7 +97,7 @@ public class LightningBolt extends Entity { - } - - this.powerLightningRod(); -- LightningBolt.clearCopperOnLightningStrike(this.level(), this.getStrikePosition()); -+ LightningBolt.clearCopperOnLightningStrike(this.level(), this.getStrikePosition(), this); // Paper - Call EntityChangeBlockEvent - this.gameEvent(GameEvent.LIGHTNING_STRIKE); - } - } -@@ -191,7 +191,7 @@ public class LightningBolt extends Entity { - } - } - -- private static void clearCopperOnLightningStrike(Level world, BlockPos pos) { -+ private static void clearCopperOnLightningStrike(Level world, BlockPos pos, Entity lightning) { // Paper - Call EntityChangeBlockEvent - BlockState iblockdata = world.getBlockState(pos); - BlockPos blockposition1; - BlockState iblockdata1; -@@ -205,24 +205,29 @@ public class LightningBolt extends Entity { - } - - if (iblockdata1.getBlock() instanceof WeatheringCopper) { -- world.setBlockAndUpdate(blockposition1, WeatheringCopper.getFirst(world.getBlockState(blockposition1))); -+ // Paper start - Call EntityChangeBlockEvent -+ BlockState newBlock = WeatheringCopper.getFirst(world.getBlockState(blockposition1)); -+ if (CraftEventFactory.callEntityChangeBlockEvent(lightning, blockposition1, newBlock)) { -+ world.setBlockAndUpdate(blockposition1, newBlock); -+ } -+ // Paper end - Call EntityChangeBlockEvent - BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); - int i = world.random.nextInt(3) + 3; - - for (int j = 0; j < i; ++j) { - int k = world.random.nextInt(8) + 1; - -- LightningBolt.randomWalkCleaningCopper(world, blockposition1, blockposition_mutableblockposition, k); -+ LightningBolt.randomWalkCleaningCopper(world, blockposition1, blockposition_mutableblockposition, k, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent - } - - } - } - -- private static void randomWalkCleaningCopper(Level world, BlockPos pos, BlockPos.MutableBlockPos mutablePos, int count) { -+ private static void randomWalkCleaningCopper(Level world, BlockPos pos, BlockPos.MutableBlockPos mutablePos, int count, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent - mutablePos.set(pos); - - for (int j = 0; j < count; ++j) { -- Optional optional = LightningBolt.randomStepCleaningCopper(world, mutablePos); -+ Optional optional = LightningBolt.randomStepCleaningCopper(world, mutablePos, lightning); // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent - - if (optional.isEmpty()) { - break; -@@ -233,7 +238,7 @@ public class LightningBolt extends Entity { - - } - -- private static Optional randomStepCleaningCopper(Level world, BlockPos pos) { -+ private static Optional randomStepCleaningCopper(Level world, BlockPos pos, Entity lightning) { // Paper - transmit LightningBolt instance to call EntityChangeBlockEvent - Iterator iterator = BlockPos.randomInCube(world.random, 10, pos, 1).iterator(); - - BlockPos blockposition1; -@@ -250,6 +255,7 @@ public class LightningBolt extends Entity { - - BlockPos blockposition1Final = blockposition1; // CraftBukkit - decompile error - WeatheringCopper.getPrevious(iblockdata).ifPresent((iblockdata1) -> { -+ if (CraftEventFactory.callEntityChangeBlockEvent(lightning, blockposition1Final, iblockdata1)) // Paper - call EntityChangeBlockEvent - world.setBlockAndUpdate(blockposition1Final, iblockdata1); // CraftBukkit - decompile error - }); - world.levelEvent(3002, blockposition1, -1); -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java b/src/main/java/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java -index 2c443b421e342ebfbdf941a431ba20560521920b..91b68ee3605afdb845405e455c869e48a7fc9aab 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/TryLaySpawnOnWaterNearLand.java -@@ -27,6 +27,12 @@ public class TryLaySpawnOnWaterNearLand { - BlockPos blockPos3 = blockPos2.above(); - if (world.getBlockState(blockPos3).isAir()) { - BlockState blockState = frogSpawn.defaultBlockState(); -+ // Paper start -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockPos3, blockState)) { -+ isPregnant.erase(); // forgot pregnant memory -+ return true; -+ } -+ // Paper end - world.setBlock(blockPos3, blockState, 3); - world.gameEvent(GameEvent.BLOCK_PLACE, blockPos3, GameEvent.Context.of(entity, blockState)); - world.playSound((Player)null, entity, SoundEvents.FROG_LAY_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F); -diff --git a/src/main/java/net/minecraft/world/item/AxeItem.java b/src/main/java/net/minecraft/world/item/AxeItem.java -index db507638a97b5a33df712c54daff35b21922c0dd..2e75fd06e9e379eb95ebfe55086ffc327706ab2f 100644 ---- a/src/main/java/net/minecraft/world/item/AxeItem.java -+++ b/src/main/java/net/minecraft/world/item/AxeItem.java -@@ -38,6 +38,11 @@ public class AxeItem extends DiggerItem { - return InteractionResult.PASS; - } else { - ItemStack itemStack = context.getItemInHand(); -+ // Paper start - EntityChangeBlockEvent -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, optional.get())) { -+ return InteractionResult.PASS; -+ } -+ // Paper end - if (player instanceof ServerPlayer) { - CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); - } -diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java -index 1977e702f6af39ebf100c1f2f2edc2d1c4d003b0..cfcd1778b5ae66395400221879dde3575591b23d 100644 ---- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java -+++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java -@@ -43,6 +43,11 @@ public class EnderEyeItem extends Item { - return InteractionResult.SUCCESS; - } else { - BlockState iblockdata1 = (BlockState) iblockdata.setValue(EndPortalFrameBlock.HAS_EYE, true); -+ // Paper start -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), blockposition, iblockdata1)) { -+ return InteractionResult.PASS; -+ } -+ // Paper end - - Block.pushEntitiesUp(iblockdata, iblockdata1, world, blockposition); - world.setBlock(blockposition, iblockdata1, 2); -diff --git a/src/main/java/net/minecraft/world/item/HoneycombItem.java b/src/main/java/net/minecraft/world/item/HoneycombItem.java -index 78bdf7c0a058e84cafcd831c6d6f5123c0f168b0..e0cae3b6848af74fefc37a1e3183c501155c4710 100644 ---- a/src/main/java/net/minecraft/world/item/HoneycombItem.java -+++ b/src/main/java/net/minecraft/world/item/HoneycombItem.java -@@ -39,6 +39,14 @@ public class HoneycombItem extends Item implements SignApplicator { - return getWaxed(blockState).map((state) -> { - Player player = context.getPlayer(); - ItemStack itemStack = context.getItemInHand(); -+ // Paper start - EntityChangeBlockEvent -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, state)) { -+ if (!player.isCreative()) { -+ player.containerMenu.sendAllDataToRemote(); -+ } -+ return InteractionResult.PASS; -+ } -+ // Paper end - if (player instanceof ServerPlayer) { - CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); - } -diff --git a/src/main/java/net/minecraft/world/item/PotionItem.java b/src/main/java/net/minecraft/world/item/PotionItem.java -index 5c62741e3a3854a7f674bfec49758f837f3bb9a0..b93ed2896ebeb8ec75eb3cfb717a740c97dd9622 100644 ---- a/src/main/java/net/minecraft/world/item/PotionItem.java -+++ b/src/main/java/net/minecraft/world/item/PotionItem.java -@@ -107,6 +107,12 @@ public class PotionItem extends Item { - BlockState iblockdata = world.getBlockState(blockposition); - - if (context.getClickedFace() != Direction.DOWN && iblockdata.is(BlockTags.CONVERTABLE_TO_MUD) && PotionUtils.getPotion(itemstack) == Potions.WATER) { -+ // Paper start -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entityhuman, blockposition, Blocks.MUD.defaultBlockState())) { -+ entityhuman.containerMenu.sendAllDataToRemote(); -+ return InteractionResult.PASS; -+ } -+ // Paper end - world.playSound((Player) null, blockposition, SoundEvents.GENERIC_SPLASH, SoundSource.BLOCKS, 1.0F, 1.0F); - entityhuman.setItemInHand(context.getHand(), ItemUtils.createFilledResult(itemstack, entityhuman, new ItemStack(Items.GLASS_BOTTLE))); - entityhuman.awardStat(Stats.ITEM_USED.get(itemstack.getItem())); -diff --git a/src/main/java/net/minecraft/world/item/ShovelItem.java b/src/main/java/net/minecraft/world/item/ShovelItem.java -index 32995cb5efdad0bc34ecacacb78cccd21220ba8d..21212462e6b415e96536a27b2c009d1562f18946 100644 ---- a/src/main/java/net/minecraft/world/item/ShovelItem.java -+++ b/src/main/java/net/minecraft/world/item/ShovelItem.java -@@ -36,20 +36,29 @@ public class ShovelItem extends DiggerItem { - Player player = context.getPlayer(); - BlockState blockState2 = FLATTENABLES.get(blockState.getBlock()); - BlockState blockState3 = null; -+ Runnable afterAction = null; // Paper - if (blockState2 != null && level.getBlockState(blockPos.above()).isAir()) { -- level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); -+ afterAction = () -> level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); // Paper - blockState3 = blockState2; - } else if (blockState.getBlock() instanceof CampfireBlock && blockState.getValue(CampfireBlock.LIT)) { -+ afterAction = () -> { // Paper - if (!level.isClientSide()) { - level.levelEvent((Player)null, 1009, blockPos, 0); - } - - CampfireBlock.dowse(context.getPlayer(), level, blockPos, blockState); -+ }; // Paper - blockState3 = blockState.setValue(CampfireBlock.LIT, Boolean.valueOf(false)); - } - - if (blockState3 != null) { - if (!level.isClientSide) { -+ // Paper start -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(context.getPlayer(), blockPos, blockState3)) { -+ return InteractionResult.PASS; -+ } -+ afterAction.run(); -+ // Paper end - level.setBlock(blockPos, blockState3, 11); - level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, blockState3)); - if (player != null) { -diff --git a/src/main/java/net/minecraft/world/level/block/CakeBlock.java b/src/main/java/net/minecraft/world/level/block/CakeBlock.java -index 49fe91a8eaeb2580c8ad0166e72540168af605f6..ca1ccedb5a551328ebfad907f39594b220efaefe 100644 ---- a/src/main/java/net/minecraft/world/level/block/CakeBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CakeBlock.java -@@ -62,6 +62,12 @@ public class CakeBlock extends Block { - Block block = Block.byItem(item); - - if (block instanceof CandleBlock) { -+ // Paper start - call change block event -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, CandleCakeBlock.byCandle(block))) { -+ player.containerMenu.sendAllDataToRemote(); // update inv because candle could decrease -+ return InteractionResult.PASS; -+ } -+ // Paper end - call change block event - if (!player.isCreative()) { - itemstack.shrink(1); - } -@@ -91,6 +97,14 @@ public class CakeBlock extends Block { - if (!player.canEat(false)) { - return InteractionResult.PASS; - } else { -+ // Paper start - call change block event -+ int i = state.getValue(CakeBlock.BITES); -+ final BlockState newState = i < MAX_BITES ? state.setValue(CakeBlock.BITES, i + 1) : world.getFluidState(pos).createLegacyBlock(); -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, newState)) { -+ ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().sendHealthUpdate(); -+ return InteractionResult.PASS; // return a non-consume result to cake blocks don't drop their candles -+ } -+ // Paper end - call change block event - player.awardStat(Stats.EAT_CAKE_SLICE); - // CraftBukkit start - // entityhuman.getFoodData().eat(2, 0.1F); -@@ -104,7 +118,7 @@ public class CakeBlock extends Block { - - ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().sendHealthUpdate(); - // CraftBukkit end -- int i = (Integer) state.getValue(CakeBlock.BITES); -+ // Paper - move up - - world.gameEvent((Entity) player, GameEvent.EAT, pos); - if (i < 6) { -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 6cccdd1d19488275ff3fe90838cf1c31e844d517..413b307acaad5823b9e06f49fa2faf561f5f7b9a 100644 ---- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -@@ -238,6 +238,11 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - if (i < 8 && ComposterBlock.COMPOSTABLES.containsKey(itemstack.getItem())) { - if (i < 7 && !world.isClientSide) { - BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, itemstack); -+ // Paper start - handle cancelled events -+ if (iblockdata1 == null) { -+ return InteractionResult.PASS; -+ } -+ // Paper end - - world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); - player.awardStat(Stats.ITEM_USED.get(itemstack.getItem())); -@@ -261,11 +266,16 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - if (i < 7 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) { - // CraftBukkit start - double rand = world.getRandom().nextDouble(); -- BlockState iblockdata1 = ComposterBlock.addItem(user, state, DummyGeneratorAccess.INSTANCE, pos, stack, rand); -- if (state == iblockdata1 || !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(user, pos, iblockdata1)) { -+ BlockState iblockdata1 = null; // Paper -+ if (false && (state == iblockdata1 || !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(user, pos, iblockdata1))) { // Paper - move event call into addItem - return state; - } - iblockdata1 = ComposterBlock.addItem(user, state, world, pos, stack, rand); -+ // Paper start - handle cancelled events -+ if (iblockdata1 == null) { -+ return state; -+ } -+ // Paper end - // CraftBukkit end - - stack.shrink(1); -@@ -306,11 +316,13 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - return iblockdata1; - } - -+ @Nullable // Paper - static BlockState addItem(@Nullable Entity user, BlockState state, LevelAccessor world, BlockPos pos, ItemStack stack) { - // CraftBukkit start - return ComposterBlock.addItem(user, state, world, pos, stack, world.getRandom().nextDouble()); - } - -+ @Nullable // Paper - make it nullable - static BlockState addItem(@Nullable Entity entity, BlockState iblockdata, LevelAccessor generatoraccess, BlockPos blockposition, ItemStack itemstack, double rand) { - // CraftBukkit end - int i = (Integer) iblockdata.getValue(ComposterBlock.LEVEL); -@@ -321,6 +333,11 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - } else { - int j = i + 1; - BlockState iblockdata1 = (BlockState) iblockdata.setValue(ComposterBlock.LEVEL, j); -+ // Paper start - move the EntityChangeBlockEvent here to avoid conflict later for the compost events -+ if (entity != null && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, iblockdata1)) { -+ return null; -+ } -+ // Paper end - - generatoraccess.setBlock(blockposition, iblockdata1, 3); - generatoraccess.gameEvent(GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entity, iblockdata1)); -diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -index 32594b4ebe8ab4c820e588573f5e01b08c57984f..a5e34c25e00e7f770bcb6e15ed0bbfe8f369a68a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -@@ -120,7 +120,7 @@ public class DummyGeneratorAccess implements WorldGenLevel { - - @Override - public void gameEvent(GameEvent event, Vec3 emitterPos, GameEvent.Context emitter) { -- // Used by BlockComposter -+ // Used by ComposterBlock - } - - @Override diff --git a/patches/server/0789-Call-BlockPhysicsEvent-more-often.patch b/patches/server/0785-Call-BlockPhysicsEvent-more-often.patch similarity index 100% rename from patches/server/0789-Call-BlockPhysicsEvent-more-often.patch rename to patches/server/0785-Call-BlockPhysicsEvent-more-often.patch diff --git a/patches/server/0785-Missing-eating-regain-reason.patch b/patches/server/0785-Missing-eating-regain-reason.patch deleted file mode 100644 index cbd8900d95ff..000000000000 --- a/patches/server/0785-Missing-eating-regain-reason.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Fri, 5 Aug 2022 12:16:51 +0200 -Subject: [PATCH] Missing eating regain reason - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java -index 1d0c424be2b67cad0f8bca85070a9c46a6b283da..f760ce7d9df79ef58f8963de3e901cba3e12fcaa 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java -@@ -387,7 +387,7 @@ public class Cat extends TamableAnimal implements VariantHolder { - if (!(item instanceof DyeItem)) { - if (item.isEdible() && this.isFood(itemstack) && this.getHealth() < this.getMaxHealth()) { - this.usePlayerItem(player, hand, itemstack); -- this.heal((float) item.getFoodProperties().getNutrition()); -+ this.heal((float) item.getFoodProperties().getNutrition(), org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason - return InteractionResult.CONSUME; - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -index 3aa98f7c282cb4884589cb83b1546b924e66f096..570b39592e7e3a24828c233ec2a2f113b9ef5868 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -+++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -@@ -384,7 +384,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl - } else { - boolean bl = this.getHealth() < this.getMaxHealth(); - if (bl) { -- this.heal(2.0F); -+ this.heal(2.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason - } - - boolean bl2 = this.isTamed() && this.getAge() == 0 && this.canFallInLove(); -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -index 91fb62807b3c5600c83d4dc8d3fadf36e94e2133..9b5b894d43f25566ab9c3698705e978ab823a0d2 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -@@ -196,7 +196,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder 0.0F) { -- this.heal(f); -+ this.heal(f, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.EATING); // Paper - Add missing regain reason - flag = true; - } - diff --git a/patches/server/0786-Configurable-chat-thread-limit.patch b/patches/server/0786-Configurable-chat-thread-limit.patch new file mode 100644 index 000000000000..9296a47439cb --- /dev/null +++ b/patches/server/0786-Configurable-chat-thread-limit.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 18 Sep 2022 06:33:17 +0100 +Subject: [PATCH] Configurable chat thread limit + +By default, spigot shifts chat over to an unbounded thread pool, +on a normal server, this really offers no gains, the creation of a thread +on submitting to the pool on these servers eats more time vs just running it in +the netty pipeline, however, on servers using plugins which do work in here, there +could be some overall benefits to moving this stuff outside of the pipeline. + +In general, this patch does two things: +1) Exposes the core size for the pool, this allows for ensuring that a number of threads +sit around in the pool, mitigating the need for creating new threads; This IS however +caveated, the ThreadPoolExecutor will ONLY create core threads as they're needed, it +just won't allow for us to dip back under the # of core threads, this can potentially +be mitigated by calling prestartCoreThread, however, I'm not sure if there is much justification +for this +2) Exposes a max size for the pool, as stated, by default this is unbounded, for most +servers limiting the size of the pool is going to have 0 effects given how fast chat +is actually processed, this is honestly really just exposed for the misnomers or people +who just wanna ensure that this won't grow over a specific size if chat gets stupidly active + +diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +index 57e6e857c84234bf0c137058ae35e6fb0e9b124f..ffd52f6871161cd1f2d23040ed4493434a29b834 100644 +--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +@@ -283,7 +283,18 @@ public class GlobalConfiguration extends ConfigurationPart { + + @PostProcess + private void postProcess() { +- // TODO: fill in separate patch ++ //noinspection ConstantConditions ++ if (net.minecraft.server.MinecraftServer.getServer() == null) return; // In testing env, this will be null here ++ int _chatExecutorMaxSize = (this.chatExecutorMaxSize <= 0) ? Integer.MAX_VALUE : this.chatExecutorMaxSize; // This is somewhat dumb, but, this is the default, do we cap this?; ++ int _chatExecutorCoreSize = Math.max(this.chatExecutorCoreSize, 0); ++ ++ if (_chatExecutorMaxSize < _chatExecutorCoreSize) { ++ _chatExecutorMaxSize = _chatExecutorCoreSize; ++ } ++ ++ java.util.concurrent.ThreadPoolExecutor executor = (java.util.concurrent.ThreadPoolExecutor) net.minecraft.server.MinecraftServer.getServer().chatExecutor; ++ executor.setCorePoolSize(_chatExecutorCoreSize); ++ executor.setMaximumPoolSize(_chatExecutorMaxSize); + } + } + public int maxJoinsPerTick = 5; diff --git a/patches/server/0791-Mitigate-effects-of-WorldCreator-keepSpawnLoaded-ret.patch b/patches/server/0787-Mitigate-effects-of-WorldCreator-keepSpawnLoaded-ret.patch similarity index 100% rename from patches/server/0791-Mitigate-effects-of-WorldCreator-keepSpawnLoaded-ret.patch rename to patches/server/0787-Mitigate-effects-of-WorldCreator-keepSpawnLoaded-ret.patch diff --git a/patches/server/0788-Add-a-consumer-parameter-to-ProjectileSource-launchP.patch b/patches/server/0788-Add-a-consumer-parameter-to-ProjectileSource-launchP.patch deleted file mode 100644 index 01a5e6880ca5..000000000000 --- a/patches/server/0788-Add-a-consumer-parameter-to-ProjectileSource-launchP.patch +++ /dev/null @@ -1,69 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MelnCat -Date: Mon, 19 Sep 2022 14:16:10 -0700 -Subject: [PATCH] Add a consumer parameter to ProjectileSource#launchProjectile - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index cf0151ea68738783a5e8c2e49a001b3a41d0c91a..b7aee6c64ffc74924a1b211e097a15e8b27f18f7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -533,8 +533,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } - - @Override -- @SuppressWarnings("unchecked") - public T launchProjectile(Class projectile, Vector velocity) { -+ // Paper start - launchProjectile consumer -+ return this.launchProjectile(projectile, velocity, null); -+ } -+ -+ @Override -+ @SuppressWarnings("unchecked") -+ public T launchProjectile(Class projectile, Vector velocity, java.util.function.Consumer function) { -+ // Paper end - launchProjectile consumer - Preconditions.checkState(!this.getHandle().generation, "Cannot launch projectile during world generation"); - - net.minecraft.world.level.Level world = ((CraftWorld) this.getWorld()).getHandle(); -@@ -621,6 +628,11 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - if (velocity != null) { - ((T) launch.getBukkitEntity()).setVelocity(velocity); - } -+ // Paper start - launchProjectile consumer -+ if (function != null) { -+ function.accept((T) launch.getBukkitEntity()); -+ } -+ // Paper end - launchProjectile consumer - - world.addFreshEntity(launch); - return (T) launch.getBukkitEntity(); -diff --git a/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java b/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java -index faab60e735aca230cb4c0b7db21e6a9a237daf6e..89401fc942c151941790b1152a35357aa51fcdba 100644 ---- a/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java -+++ b/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java -@@ -55,6 +55,13 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { - - @Override - public T launchProjectile(Class projectile, Vector velocity) { -+ // Paper start - launchProjectile consumer -+ return this.launchProjectile(projectile, velocity, null); -+ } -+ -+ @Override -+ public T launchProjectile(Class projectile, Vector velocity, java.util.function.Consumer function) { -+ // Paper end - launchProjectile consumer - Preconditions.checkArgument(this.getBlock().getType() == Material.DISPENSER, "Block is no longer dispenser"); - // Copied from BlockDispenser.dispense() - BlockSource sourceblock = new BlockSource((ServerLevel) this.dispenserBlock.getLevel(), this.dispenserBlock.getBlockPos(), this.dispenserBlock.getBlockState(), this.dispenserBlock); -@@ -145,6 +152,11 @@ public class CraftBlockProjectileSource implements BlockProjectileSource { - if (velocity != null) { - ((T) launch.getBukkitEntity()).setVelocity(velocity); - } -+ // Paper start -+ if (function != null) { -+ function.accept((T) launch.getBukkitEntity()); -+ } -+ // Paper end - - world.addFreshEntity(launch); - return (T) launch.getBukkitEntity(); diff --git a/patches/server/0792-fix-Jigsaw-block-kicking-user.patch b/patches/server/0788-fix-Jigsaw-block-kicking-user.patch similarity index 100% rename from patches/server/0792-fix-Jigsaw-block-kicking-user.patch rename to patches/server/0788-fix-Jigsaw-block-kicking-user.patch diff --git a/patches/server/0789-use-BlockFormEvent-for-mud-converting-into-clay.patch b/patches/server/0789-use-BlockFormEvent-for-mud-converting-into-clay.patch new file mode 100644 index 000000000000..3344be8e0020 --- /dev/null +++ b/patches/server/0789-use-BlockFormEvent-for-mud-converting-into-clay.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Legitimoose +Date: Thu, 29 Sep 2022 16:25:50 -0700 +Subject: [PATCH] use BlockFormEvent for mud converting into clay + + +diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java +index 998e43e8dc6bd6b741bdcb77d2b75df8ab2feefc..06a9dd14c7f7954bf9327427de76965be8fe30f4 100644 +--- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java +@@ -214,10 +214,13 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate + if (((PointedDripstoneBlock.FluidInfo) optional.get()).sourceState.is(Blocks.MUD) && fluidtype == Fluids.WATER) { + BlockState iblockdata1 = Blocks.CLAY.defaultBlockState(); + +- world.setBlockAndUpdate(((PointedDripstoneBlock.FluidInfo) optional.get()).pos, iblockdata1); ++ // Paper start - Call BlockFormEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos, iblockdata1)) { + Block.pushEntitiesUp(((PointedDripstoneBlock.FluidInfo) optional.get()).sourceState, iblockdata1, world, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos); + world.gameEvent(GameEvent.BLOCK_CHANGE, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos, GameEvent.Context.of(iblockdata1)); + world.levelEvent(1504, blockposition1, 0); ++ } ++ // Paper end - Call BlockFormEvent + } else { + BlockPos blockposition2 = PointedDripstoneBlock.findFillableCauldronBelowStalactiteTip(world, blockposition1, fluidtype); + diff --git a/patches/server/0794-Add-getDrops-to-BlockState.patch b/patches/server/0790-Add-getDrops-to-BlockState.patch similarity index 100% rename from patches/server/0794-Add-getDrops-to-BlockState.patch rename to patches/server/0790-Add-getDrops-to-BlockState.patch diff --git a/patches/server/0790-Configurable-chat-thread-limit.patch b/patches/server/0790-Configurable-chat-thread-limit.patch deleted file mode 100644 index 8f0a8571fd71..000000000000 --- a/patches/server/0790-Configurable-chat-thread-limit.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sun, 18 Sep 2022 06:33:17 +0100 -Subject: [PATCH] Configurable chat thread limit - -By default, spigot shifts chat over to an unbounded thread pool, -on a normal server, this really offers no gains, the creation of a thread -on submitting to the pool on these servers eats more time vs just running it in -the netty pipeline, however, on servers using plugins which do work in here, there -could be some overall benefits to moving this stuff outside of the pipeline. - -In general, this patch does two things: -1) Exposes the core size for the pool, this allows for ensuring that a number of threads -sit around in the pool, mitigating the need for creating new threads; This IS however -caveated, the ThreadPoolExecutor will ONLY create core threads as they're needed, it -just won't allow for us to dip back under the # of core threads, this can potentially -be mitigated by calling prestartCoreThread, however, I'm not sure if there is much justification -for this -2) Exposes a max size for the pool, as stated, by default this is unbounded, for most -servers limiting the size of the pool is going to have 0 effects given how fast chat -is actually processed, this is honestly really just exposed for the misnomers or people -who just wanna ensure that this won't grow over a specific size if chat gets stupidly active - -diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index 73a34b4e378e6012a01c8ac8b092248298be6648..0f4d5e44d3f4fb2498b555bdafe5263810ed0c77 100644 ---- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -283,7 +283,18 @@ public class GlobalConfiguration extends ConfigurationPart { - - @PostProcess - private void postProcess() { -- // TODO: fill in separate patch -+ //noinspection ConstantConditions -+ if (net.minecraft.server.MinecraftServer.getServer() == null) return; // In testing env, this will be null here -+ int _chatExecutorMaxSize = (this.chatExecutorMaxSize <= 0) ? Integer.MAX_VALUE : this.chatExecutorMaxSize; // This is somewhat dumb, but, this is the default, do we cap this?; -+ int _chatExecutorCoreSize = Math.max(this.chatExecutorCoreSize, 0); -+ -+ if (_chatExecutorMaxSize < _chatExecutorCoreSize) { -+ _chatExecutorMaxSize = _chatExecutorCoreSize; -+ } -+ -+ java.util.concurrent.ThreadPoolExecutor executor = (java.util.concurrent.ThreadPoolExecutor) net.minecraft.server.MinecraftServer.getServer().chatExecutor; -+ executor.setCorePoolSize(_chatExecutorCoreSize); -+ executor.setMaximumPoolSize(_chatExecutorMaxSize); - } - } - public int maxJoinsPerTick = 5; diff --git a/patches/server/0791-Fix-a-bunch-of-vanilla-bugs.patch b/patches/server/0791-Fix-a-bunch-of-vanilla-bugs.patch new file mode 100644 index 000000000000..abd36984a13e --- /dev/null +++ b/patches/server/0791-Fix-a-bunch-of-vanilla-bugs.patch @@ -0,0 +1,491 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 11 Jul 2022 11:56:41 -0700 +Subject: [PATCH] Fix a bunch of vanilla bugs + +https://bugs.mojang.com/browse/MC-253884 + show raid entity event to all tracking players + +https://bugs.mojang.com/browse/MC-253721 + wrong msg for opping multiple players + +https://bugs.mojang.com/browse/MC-248588 + respect mob griefing gamerule for draining water cauldrons + +https://bugs.mojang.com/browse/MC-244739 + play goat eating sound for last item in stack + +https://bugs.mojang.com/browse/MC-243057 + ignore furnace fuel slot in recipe book click + +https://bugs.mojang.com/browse/MC-147659 + Some witch huts spawn the incorrect cat + Note: Marked as Won't Fix, makes 0 sense + +https://bugs.mojang.com/browse/MC-179072 + Creepers do not defuse when switching from Survival to Creative/Spectator + +https://bugs.mojang.com/browse/MC-191591 + Fix items equipped on AbstractHorse losing NBT + +https://bugs.mojang.com/browse/MC-259571 + Fix changeGameModeForPlayer to use gameModeForPlayer + +https://bugs.mojang.com/browse/MC-262422 + Fix lightning being able to hit spectators + +https://bugs.mojang.com/browse/MC-224454 + Fix mobs attempting to pathfind through azalea blocks + +https://bugs.mojang.com/browse/MC-263999 + Fix mobs breaking doors not spawning block break particles + +https://bugs.mojang.com/browse/MC-210802 + Fixes sheep eating blocks outside of ticking range + +https://bugs.mojang.com/browse/MC-123848 + Fixes item frames dropping items above when pointing down + +https://bugs.mojang.com/browse/MC-84789 + Fix wild wolves not considering bones interesting + +https://bugs.mojang.com/browse/MC-225381 + Fix overfilled bundles duplicating items / being filled with air + +https://bugs.mojang.com/browse/MC-173303 + Fix leashed pets teleporting to owner when loaded + +https://bugs.mojang.com/browse/MC-174630 + Fix secondary beacon effect remaining after switching effect + +https://bugs.mojang.com/browse/MC-153086 + Fix the beacon deactivation sound always playing when broken + +https://bugs.mojang.com/browse/MC-200092 + Fix yaw being ignored for a player's first spawn pos + +https://bugs.mojang.com/browse/MC-158900 + Fix error when joining after tempban expired + +== AT == +public net/minecraft/world/entity/Mob leashInfoTag + +Co-authored-by: William Blake Galbreath +Co-authored-by: Spottedleaf + +diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +index 829c72333664da0c06ce04af93ea39bb90ce0b67..b4f7e3ae855cbf48925f0db916501adf513ae08c 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -351,7 +351,7 @@ public interface DispenseItemBehavior { + } + } + // CraftBukkit end +- ((Saddleable) list.get(0)).equipSaddle(SoundSource.BLOCKS); ++ ((Saddleable) list.get(0)).equipSaddle(SoundSource.BLOCKS, CraftItemStack.asNMSCopy(event.getItem())); // Paper - Fix saddles losing nbt data - MC-191591 + // itemstack.shrink(1); // CraftBukkit - handled above + this.setSuccess(true); + return stack; +diff --git a/src/main/java/net/minecraft/server/commands/DeOpCommands.java b/src/main/java/net/minecraft/server/commands/DeOpCommands.java +index 40490d10649e92cf622849f1bb87538102b130c7..797efca662dcc0fe7f4cf0b4b7baa235ea044f1f 100644 +--- a/src/main/java/net/minecraft/server/commands/DeOpCommands.java ++++ b/src/main/java/net/minecraft/server/commands/DeOpCommands.java +@@ -34,7 +34,7 @@ public class DeOpCommands { + playerList.deop(gameProfile); + ++i; + source.sendSuccess(() -> { +- return Component.translatable("commands.deop.success", targets.iterator().next().getName()); ++ return Component.translatable("commands.deop.success", gameProfile.getName()); // Paper - fixes MC-253721 + }, true); + } + } +diff --git a/src/main/java/net/minecraft/server/commands/OpCommand.java b/src/main/java/net/minecraft/server/commands/OpCommand.java +index 6cd6d69a20e95e344fc18ab67dc300824537a59b..2e2a7c2cf3081187da817479a9da3eb10f662a6d 100644 +--- a/src/main/java/net/minecraft/server/commands/OpCommand.java ++++ b/src/main/java/net/minecraft/server/commands/OpCommand.java +@@ -39,7 +39,7 @@ public class OpCommand { + playerList.op(gameProfile); + ++i; + source.sendSuccess(() -> { +- return Component.translatable("commands.op.success", targets.iterator().next().getName()); ++ return Component.translatable("commands.op.success", gameProfile.getName()); // Paper - fixes MC-253721 + }, true); + } + } +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 63e7f41eaed3f22c1bc0191790ff0ad313dc4ffd..7c5c9efe4d6037c4c5444d108d76af241144d6b5 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1233,7 +1233,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // CraftBukkit end + } + +- boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { ++ public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { // Paper - public + // Spigot start + return this.anyPlayerCloseEnoughForSpawning(pos, false); + } +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index de822b99df2de08371f3a7a29bf8f3275650887c..62feb9e2f8e0072bb180c65b4946281494a7dc1d 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -770,7 +770,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + } else { + AABB axisalignedbb = AABB.encapsulatingFullBlocks(blockposition1, new BlockPos(blockposition1.atY(this.getMaxBuildHeight()))).inflate(3.0D); + List list = this.getEntitiesOfClass(LivingEntity.class, axisalignedbb, (entityliving) -> { +- return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition()); ++ return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition()) && !entityliving.isSpectator(); // Paper - Fix lightning being able to hit spectators (MC-262422) + }); + + if (!list.isEmpty()) { +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index bc95420dcf5d23d028e5df6595899a8712550be8..84792f21cbb727bdb8f72b3574b39c4dad631612 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -419,14 +419,14 @@ public class ServerPlayer extends Player { + BlockPos blockposition1 = PlayerRespawnLogic.getOverworldRespawnPos(world, blockposition.getX() + j2 - i, blockposition.getZ() + k2 - i); + + if (blockposition1 != null) { +- this.moveTo(blockposition1, 0.0F, 0.0F); ++ this.moveTo(blockposition1, world.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored + if (world.noCollision((Entity) this)) { + break; + } + } + } + } else { +- this.moveTo(blockposition, 0.0F, 0.0F); ++ this.moveTo(blockposition, world.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored + + while (!world.noCollision((Entity) this) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { + this.setPos(this.getX(), this.getY() + 1.0D, this.getZ()); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index f3389dc345d8b6e5389ae37848d9b268d4bbad83..5d1b8bae981dc538ee1fe4fb993e44f227168233 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -92,7 +92,7 @@ public class ServerPlayerGameMode { + return event; // Paper - Expand PlayerGameModeChangeEvent + } + // CraftBukkit end +- this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer); ++ this.setGameModeForPlayer(gameMode, this.gameModeForPlayer); // Paper - Fix MC-259571 + this.player.onUpdateAbilities(); + this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit + this.level.updateSleepingPlayerList(); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index a8a57819993f3657d6525a98527ec47631174bed..598dce073d7887d44a6630820c7e5746ce1c6dcc 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -709,8 +709,10 @@ public abstract class PlayerList { + Player player = entity.getBukkitEntity(); + PlayerLoginEvent event = new PlayerLoginEvent(player, loginlistener.connection.hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.channel.remoteAddress()).getAddress()); + +- if (this.getBans().isBanned(gameprofile) && !this.getBans().get(gameprofile).hasExpired()) { +- UserBanListEntry gameprofilebanentry = (UserBanListEntry) this.bans.get(gameprofile); ++ // Paper start - Fix MC-158900 ++ UserBanListEntry gameprofilebanentry; ++ if (getBans().isBanned(gameprofile) && (gameprofilebanentry = getBans().get(gameprofile)) != null) { ++ // Paper end - Fix MC-158900 + + ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned.reason", gameprofilebanentry.getReason()); + if (gameprofilebanentry.getExpires() != null) { +diff --git a/src/main/java/net/minecraft/world/entity/Saddleable.java b/src/main/java/net/minecraft/world/entity/Saddleable.java +index effe4c4fb37fe13aece70cdef4966047d4719af9..7152674d3f3fb98198585cb5ece2bb88877345f9 100644 +--- a/src/main/java/net/minecraft/world/entity/Saddleable.java ++++ b/src/main/java/net/minecraft/world/entity/Saddleable.java +@@ -9,6 +9,11 @@ public interface Saddleable { + boolean isSaddleable(); + + void equipSaddle(@Nullable SoundSource sound); ++ // Paper start - Fix saddles losing nbt data - MC-191591 ++ default void equipSaddle(final @Nullable SoundSource sound, final @Nullable net.minecraft.world.item.ItemStack stack) { ++ this.equipSaddle(sound); ++ } ++ // Paper end + + default SoundEvent getSaddleSoundEvent() { + return SoundEvents.HORSE_SADDLE; +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BegGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BegGoal.java +index 9a46007245399481fb6e749d95e9e4791e799250..2afca5652541c9166278f8f2590ddb81003ae579 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/BegGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/BegGoal.java +@@ -65,7 +65,7 @@ public class BegGoal extends Goal { + private boolean playerHoldingInteresting(Player player) { + for(InteractionHand interactionHand : InteractionHand.values()) { + ItemStack itemStack = player.getItemInHand(interactionHand); +- if (this.wolf.isTame() && itemStack.is(Items.BONE)) { ++ if (!this.wolf.isTame() && itemStack.is(Items.BONE)) { // Paper - Fix MC-84789 + return true; + } + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +index 784a894688f98f9d0368a36d456c5c94e1ee3695..a85885ee51df585fa11ae9f8fcd67ff2a71c5a18 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +@@ -77,9 +77,10 @@ public class BreakDoorGoal extends DoorInteractGoal { + return; + } + // CraftBukkit end ++ final net.minecraft.world.level.block.state.BlockState oldState = this.mob.level().getBlockState(this.doorPos); // Paper - fix MC-263999 + this.mob.level().removeBlock(this.doorPos, false); + this.mob.level().levelEvent(1021, this.doorPos, 0); +- this.mob.level().levelEvent(2001, this.doorPos, Block.getId(this.mob.level().getBlockState(this.doorPos))); ++ this.mob.level().levelEvent(2001, this.doorPos, Block.getId(oldState)); // Paper - fix MC-263999 + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +index d802985f1431be4332c07f0dab88feebedea4ce2..4e2c23ccdf4e4a4d65b291dbe20952bae1838bff 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +@@ -31,6 +31,11 @@ public class EatBlockGoal extends Goal { + + @Override + public boolean canUse() { ++ // Paper start - Fix MC-210802 ++ if (!((net.minecraft.server.level.ServerLevel) this.level).chunkSource.chunkMap.anyPlayerCloseEnoughForSpawning(this.mob.chunkPosition())) { ++ return false; ++ } ++ // Paper end + if (this.mob.getRandom().nextInt(this.mob.isBaby() ? 50 : 1000) != 0) { + return false; + } else { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java +index 11cc26954b4e97114b59df35a4f9b75a09132e20..0a3f7dcc0e205a85dbaa6dee1fc9ae2c7fa9e02d 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java +@@ -74,7 +74,7 @@ public class FollowOwnerGoal extends Goal { + } + + private boolean unableToMove() { +- return this.tamable.isOrderedToSit() || this.tamable.isPassenger() || this.tamable.isLeashed(); ++ return this.tamable.isOrderedToSit() || this.tamable.isPassenger() || this.tamable.isLeashed() || this.tamable.leashInfoTag != null; // Paper - Fix MC-173303 + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java +index 19540fd4a7f992888fadb6501d0c8a5a7e71fcf6..e241ae250f4f04a17ef2c583d00b065a4ca56a4c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java +@@ -21,6 +21,13 @@ public class SwellGoal extends Goal { + return this.creeper.getSwellDir() > 0 || livingEntity != null && this.creeper.distanceToSqr(livingEntity) < 9.0D; + } + ++ // Paper start - Fix MC-179072 ++ @Override ++ public boolean canContinueToUse() { ++ return !net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(this.creeper.getTarget()) && canUse(); ++ } ++ // Paper end ++ + @Override + public void start() { + this.creeper.getNavigation().stop(); +diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +index 110dd5418b0512a2f27f0c4d5a5f1812356a6a12..5d247ac38fe8a61603b3d934f3000bcda773142b 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +@@ -239,9 +239,10 @@ public class Goat extends Animal { + player.setItemInHand(hand, itemstack1); + return InteractionResult.sidedSuccess(this.level().isClientSide); + } else { ++ boolean isFood = this.isFood(itemstack); // Paper - track before stack is possibly decreased to 0 (Fixes MC-244739) + InteractionResult enuminteractionresult = super.mobInteract(player, hand); + +- if (enuminteractionresult.consumesAction() && this.isFood(itemstack)) { ++ if (enuminteractionresult.consumesAction() && isFood) { // Paper + this.level().playSound((Player) null, (Entity) this, this.getEatingSound(itemstack), SoundSource.NEUTRAL, 1.0F, Mth.randomBetween(this.level().random, 0.8F, 1.2F)); + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +index 56cc6ecf7f95687db7c7c062b4ee979bfe49844b..94dd97662ba07689fbfa16ef5c7d99fe12ce83de 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +@@ -252,7 +252,13 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + + @Override + public void equipSaddle(@Nullable SoundSource sound) { +- this.inventory.setItem(0, new ItemStack(Items.SADDLE)); ++ // Paper start - Fix saddles losing nbt data - MC-191591 ++ this.equipSaddle(sound, null); ++ } ++ @Override ++ public void equipSaddle(@Nullable SoundSource sound, @Nullable ItemStack stack) { ++ this.inventory.setItem(0, stack != null ? stack : new ItemStack(Items.SADDLE)); ++ // Paper end + } + + public void equipArmor(Player player, ItemStack stack) { +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +index 3ff1ae5ae705cd8d5c8529e1dcdd5ccaed908830..0c952bfc01b367a297e81768cab436c5474830f1 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +@@ -282,6 +282,14 @@ public class ItemFrame extends HangingEntity { + } + } + ++ // Paper start - Fix MC-123848 (spawn item frame drops above block) ++ @Nullable ++ @Override ++ public net.minecraft.world.entity.item.ItemEntity spawnAtLocation(ItemStack stack) { ++ return this.spawnAtLocation(stack, getDirection().equals(Direction.DOWN) ? -0.6F : 0.0F); ++ } ++ // Paper end ++ + private void removeFramedMap(ItemStack itemstack) { + this.getFramedMapId().ifPresent((i) -> { + MapItemSavedData worldmap = MapItem.getSavedData(i, this.level()); +diff --git a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java +index f174094febfdfdc309f1b50877be60bae8a98156..5f407535298a31a34cfe114dd863fd6a9b977707 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java ++++ b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java +@@ -87,8 +87,8 @@ public class CatSpawner implements CustomSpawner { + if (cat == null) { + return 0; + } else { ++ cat.moveTo(pos, 0.0F, 0.0F); // Paper - move up - Fix MC-147659 + cat.finalizeSpawn(world, world.getCurrentDifficultyAt(pos), MobSpawnType.NATURAL, (SpawnGroupData)null, (CompoundTag)null); +- cat.moveTo(pos, 0.0F, 0.0F); + world.addFreshEntityWithPassengers(cat); + return 1; + } +diff --git a/src/main/java/net/minecraft/world/entity/raid/Raids.java b/src/main/java/net/minecraft/world/entity/raid/Raids.java +index 4a0f4c83228187a2082ad029680056b1801f77bd..31831811ce16265e9828fa34d9e67d8ac195d723 100644 +--- a/src/main/java/net/minecraft/world/entity/raid/Raids.java ++++ b/src/main/java/net/minecraft/world/entity/raid/Raids.java +@@ -134,7 +134,7 @@ public class Raids extends SavedData { + // CraftBukkit end + } else { + player.removeEffect(MobEffects.BAD_OMEN); +- player.connection.send(new ClientboundEntityEventPacket(player, (byte) 43)); ++ this.level.broadcastEntityEvent(player, net.minecraft.world.entity.EntityEvent.BAD_OMEN_TRIGGERED /* (byte) 43 */); // Paper - Fix MC-253884 + } + + if (flag) { +@@ -149,7 +149,7 @@ public class Raids extends SavedData { + } + // CraftBukkit end + raid.absorbBadOmen(player); +- player.connection.send(new ClientboundEntityEventPacket(player, (byte) 43)); ++ this.level.broadcastEntityEvent(player, net.minecraft.world.entity.EntityEvent.BAD_OMEN_TRIGGERED /* (byte) 43 */); // Paper - Fix MC-253884 + if (!raid.hasFirstWaveSpawned()) { + player.awardStat(Stats.RAID_TRIGGER); + CriteriaTriggers.BAD_OMEN.trigger(player); +diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +index b670c0cb3886c99d38a91b5c13aa2cefaae702cf..9599a5f96601030bf7f7cbd3392861d626959f9d 100644 +--- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +@@ -177,6 +177,11 @@ public class BeaconMenu extends AbstractContainerMenu { + // Paper end - Add PlayerChangeBeaconEffectEvent + + public void updateEffects(Optional primary, Optional secondary) { ++ // Paper start - fix MC-174630 - validate secondary power ++ if (secondary.isPresent() && secondary.get() != net.minecraft.world.effect.MobEffects.REGENERATION && (primary.isPresent() && secondary.get() != primary.get())) { ++ secondary = Optional.empty(); ++ } ++ // Paper end + if (this.paymentSlot.hasItem()) { + // Paper start - Add PlayerChangeBeaconEffectEvent + io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent event = new io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), convert(primary), convert(secondary), this.access.getLocation().getBlock()); +diff --git a/src/main/java/net/minecraft/world/item/BundleItem.java b/src/main/java/net/minecraft/world/item/BundleItem.java +index 10b0720ce7eed58fa3cd8c8051efa6225f7d73e1..ac0bc87f60c4e1562d1301522183e449558d42f8 100644 +--- a/src/main/java/net/minecraft/world/item/BundleItem.java ++++ b/src/main/java/net/minecraft/world/item/BundleItem.java +@@ -52,7 +52,7 @@ public class BundleItem extends Item { + }); + } else if (itemStack.getItem().canFitInsideContainerItems()) { + int i = (64 - getContentWeight(stack)) / getWeight(itemStack); +- int j = add(stack, slot.safeTake(itemStack.getCount(), i, player)); ++ int j = add(stack, slot.safeTake(itemStack.getCount(), Math.max(0, i), player)); // Paper - prevent item addition on overfilled bundles - safeTake will yield EMPTY for amount == 0. + if (j > 0) { + this.playInsertSound(player); + } +@@ -121,7 +121,7 @@ public class BundleItem extends Item { + int i = getContentWeight(bundle); + int j = getWeight(stack); + int k = Math.min(stack.getCount(), (64 - i) / j); +- if (k == 0) { ++ if (k <= 0) { // Paper - prevent item addition on overfilled bundles + return 0; + } else { + ListTag listTag = compoundTag.getList("Items", 10); +diff --git a/src/main/java/net/minecraft/world/item/SaddleItem.java b/src/main/java/net/minecraft/world/item/SaddleItem.java +index ca6a2b9840c9ade87ec8effab01d4f184fe876b7..43129ecefcc8beccbcf2978f262b1ce8cf49ca43 100644 +--- a/src/main/java/net/minecraft/world/item/SaddleItem.java ++++ b/src/main/java/net/minecraft/world/item/SaddleItem.java +@@ -18,7 +18,7 @@ public class SaddleItem extends Item { + if (entity instanceof Saddleable saddleable) { + if (entity.isAlive() && !saddleable.isSaddled() && saddleable.isSaddleable()) { + if (!user.level().isClientSide) { +- saddleable.equipSaddle(SoundSource.NEUTRAL); ++ saddleable.equipSaddle(SoundSource.NEUTRAL, stack.copyWithCount(1)); // Paper - Fix saddles losing nbt data - MC-191591 + entity.level().gameEvent(entity, GameEvent.EQUIP, entity.position()); + stack.shrink(1); + } +diff --git a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java +index 6e45582f8ea7dd2a46f58369c5581764538bff0d..3ecc92439fc85d224ff52f41c5e34079e042a5e6 100644 +--- a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java +@@ -51,4 +51,11 @@ public class AzaleaBlock extends BushBlock implements BonemealableBlock { + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + TreeGrower.AZALEA.growTree(world, world.getChunkSource().getGenerator(), pos, state, random); + } ++ ++ // Paper start - Fix MC-224454 ++ @Override ++ public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, net.minecraft.world.level.pathfinder.PathComputationType type) { ++ return false; ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +index 42c1e3dfec23d6a7d832bf73d47ecae1212ec2c9..a4857675772d4fe849ba85fc21a369decca42fc0 100644 +--- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +@@ -68,7 +68,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { + if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent + if (!world.isClientSide && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { + // CraftBukkit start +- if (entity.mayInteract(world, pos)) { ++ if ((entity instanceof net.minecraft.world.entity.player.Player || world.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING)) && entity.mayInteract(world, pos)) { // Paper - Fixes MC-248588 + if (!this.handleEntityOnFireInsideWithEvent(state, world, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities + return; + } +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 c7ba7ac1a3869e4db1ef6b0350b3cab7f31a94c4..d7beeac4a8e4a16221809663a5aa03389a759742 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 +@@ -675,13 +675,10 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + + @Override + public void fillStackedContents(StackedContents finder) { +- Iterator iterator = this.items.iterator(); +- +- while (iterator.hasNext()) { +- ItemStack itemstack = (ItemStack) iterator.next(); +- +- finder.accountStack(itemstack); +- } ++ // Paper start - don't account fuel stack (fixes MC-243057) ++ finder.accountStack(this.items.get(SLOT_INPUT)); ++ finder.accountStack(this.items.get(SLOT_RESULT)); ++ // Paper end + + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index 61a618f09af475407a78343eecb4352052b1df1e..247f24c7fadc203ee0f6a6f85122c91ab4c82f80 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -291,7 +291,11 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); + new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); + // Paper end - beacon activation/deactivation events ++ // Paper start - fix MC-153086 ++ if (this.levels > 0 && !this.beamSections.isEmpty()) { + BeaconBlockEntity.playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE); ++ } ++ // Paper end + super.setRemoved(); + } + diff --git a/patches/server/0796-Remove-unnecessary-onTrackingStart-during-navigation.patch b/patches/server/0792-Remove-unnecessary-onTrackingStart-during-navigation.patch similarity index 100% rename from patches/server/0796-Remove-unnecessary-onTrackingStart-during-navigation.patch rename to patches/server/0792-Remove-unnecessary-onTrackingStart-during-navigation.patch diff --git a/patches/server/0797-Fix-custom-piglin-loved-items.patch b/patches/server/0793-Fix-custom-piglin-loved-items.patch similarity index 100% rename from patches/server/0797-Fix-custom-piglin-loved-items.patch rename to patches/server/0793-Fix-custom-piglin-loved-items.patch diff --git a/patches/server/0793-use-BlockFormEvent-for-mud-converting-into-clay.patch b/patches/server/0793-use-BlockFormEvent-for-mud-converting-into-clay.patch deleted file mode 100644 index 5d836bf94830..000000000000 --- a/patches/server/0793-use-BlockFormEvent-for-mud-converting-into-clay.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Legitimoose -Date: Thu, 29 Sep 2022 16:25:50 -0700 -Subject: [PATCH] use BlockFormEvent for mud converting into clay - - -diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -index e59f9b83606da83f15924477ea2a2c4b74e7d892..e310abaca79615caaa75d92183a57242734266a3 100644 ---- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -@@ -220,10 +220,13 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate - if (((PointedDripstoneBlock.FluidInfo) optional.get()).sourceState.is(Blocks.MUD) && fluidtype == Fluids.WATER) { - BlockState iblockdata1 = Blocks.CLAY.defaultBlockState(); - -- world.setBlockAndUpdate(((PointedDripstoneBlock.FluidInfo) optional.get()).pos, iblockdata1); -+ // Paper start - Call BlockFormEvent -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(world, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos, iblockdata1)) { - Block.pushEntitiesUp(((PointedDripstoneBlock.FluidInfo) optional.get()).sourceState, iblockdata1, world, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos); - world.gameEvent(GameEvent.BLOCK_CHANGE, ((PointedDripstoneBlock.FluidInfo) optional.get()).pos, GameEvent.Context.of(iblockdata1)); - world.levelEvent(1504, blockposition1, 0); -+ } -+ // Paper end - Call BlockFormEvent - } else { - BlockPos blockposition2 = PointedDripstoneBlock.findFillableCauldronBelowStalactiteTip(world, blockposition1, fluidtype); - diff --git a/patches/server/0798-EntityPickupItemEvent-fixes.patch b/patches/server/0794-EntityPickupItemEvent-fixes.patch similarity index 100% rename from patches/server/0798-EntityPickupItemEvent-fixes.patch rename to patches/server/0794-EntityPickupItemEvent-fixes.patch diff --git a/patches/server/0795-Correctly-handle-interactions-with-items-on-cooldown.patch b/patches/server/0795-Correctly-handle-interactions-with-items-on-cooldown.patch new file mode 100644 index 000000000000..ca32db1eca33 --- /dev/null +++ b/patches/server/0795-Correctly-handle-interactions-with-items-on-cooldown.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 16 Jun 2022 21:57:02 -0700 +Subject: [PATCH] Correctly handle interactions with items on cooldown + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 5d1b8bae981dc538ee1fe4fb993e44f227168233..3621770701c6fb1da75c69a41297684493380e37 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -514,6 +514,7 @@ public class ServerPlayerGameMode { + BlockState iblockdata = world.getBlockState(blockposition); + InteractionResult enuminteractionresult = InteractionResult.PASS; + boolean cancelledBlock = false; ++ boolean cancelledItem = false; // Paper - correctly handle items on cooldown + + if (!iblockdata.getBlock().isEnabled(world.enabledFeatures())) { + return InteractionResult.FAIL; +@@ -523,10 +524,10 @@ public class ServerPlayerGameMode { + } + + if (player.getCooldowns().isOnCooldown(stack.getItem())) { +- cancelledBlock = true; ++ cancelledItem = true; // Paper - correctly handle items on cooldown + } + +- PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, hand, hitResult.getLocation()); ++ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, cancelledItem, hand, hitResult.getLocation()); // Paper - correctly handle items on cooldown + this.firedInteract = true; + this.interactResult = event.useItemInHand() == Event.Result.DENY; + this.interactPosition = blockposition.immutable(); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 489576ddb53766e8aa463772e9260ee996ce974a..46445366327eb7868ff844bfa2299a3f261aef42 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -544,6 +544,12 @@ public class CraftEventFactory { + } + + public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, InteractionHand hand, Vec3 targetPos) { ++ // Paper start - cancelledItem param ++ return CraftEventFactory.callPlayerInteractEvent(who, action, position, direction, itemstack, cancelledBlock, false, hand, targetPos); ++ } ++ ++ public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, boolean cancelledItem, InteractionHand hand, Vec3 targetPos) { ++ // Paper end - cancelledItem param + Player player = (who == null) ? null : (Player) who.getBukkitEntity(); + CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); + +@@ -578,6 +584,11 @@ public class CraftEventFactory { + if (cancelledBlock) { + event.setUseInteractedBlock(Event.Result.DENY); + } ++ // Paper start ++ if (cancelledItem) { ++ event.setUseItemInHand(Result.DENY); ++ } ++ // Paper end + craftServer.getPluginManager().callEvent(event); + + return event; diff --git a/patches/server/0795-Fix-a-bunch-of-vanilla-bugs.patch b/patches/server/0795-Fix-a-bunch-of-vanilla-bugs.patch deleted file mode 100644 index 6f0017dc2038..000000000000 --- a/patches/server/0795-Fix-a-bunch-of-vanilla-bugs.patch +++ /dev/null @@ -1,491 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 11 Jul 2022 11:56:41 -0700 -Subject: [PATCH] Fix a bunch of vanilla bugs - -https://bugs.mojang.com/browse/MC-253884 - show raid entity event to all tracking players - -https://bugs.mojang.com/browse/MC-253721 - wrong msg for opping multiple players - -https://bugs.mojang.com/browse/MC-248588 - respect mob griefing gamerule for draining water cauldrons - -https://bugs.mojang.com/browse/MC-244739 - play goat eating sound for last item in stack - -https://bugs.mojang.com/browse/MC-243057 - ignore furnace fuel slot in recipe book click - -https://bugs.mojang.com/browse/MC-147659 - Some witch huts spawn the incorrect cat - Note: Marked as Won't Fix, makes 0 sense - -https://bugs.mojang.com/browse/MC-179072 - Creepers do not defuse when switching from Survival to Creative/Spectator - -https://bugs.mojang.com/browse/MC-191591 - Fix items equipped on AbstractHorse losing NBT - -https://bugs.mojang.com/browse/MC-259571 - Fix changeGameModeForPlayer to use gameModeForPlayer - -https://bugs.mojang.com/browse/MC-262422 - Fix lightning being able to hit spectators - -https://bugs.mojang.com/browse/MC-224454 - Fix mobs attempting to pathfind through azalea blocks - -https://bugs.mojang.com/browse/MC-263999 - Fix mobs breaking doors not spawning block break particles - -https://bugs.mojang.com/browse/MC-210802 - Fixes sheep eating blocks outside of ticking range - -https://bugs.mojang.com/browse/MC-123848 - Fixes item frames dropping items above when pointing down - -https://bugs.mojang.com/browse/MC-84789 - Fix wild wolves not considering bones interesting - -https://bugs.mojang.com/browse/MC-225381 - Fix overfilled bundles duplicating items / being filled with air - -https://bugs.mojang.com/browse/MC-173303 - Fix leashed pets teleporting to owner when loaded - -https://bugs.mojang.com/browse/MC-174630 - Fix secondary beacon effect remaining after switching effect - -https://bugs.mojang.com/browse/MC-153086 - Fix the beacon deactivation sound always playing when broken - -https://bugs.mojang.com/browse/MC-200092 - Fix yaw being ignored for a player's first spawn pos - -https://bugs.mojang.com/browse/MC-158900 - Fix error when joining after tempban expired - -== AT == -public net/minecraft/world/entity/Mob leashInfoTag - -Co-authored-by: William Blake Galbreath -Co-authored-by: Spottedleaf - -diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -index 829c72333664da0c06ce04af93ea39bb90ce0b67..b4f7e3ae855cbf48925f0db916501adf513ae08c 100644 ---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -351,7 +351,7 @@ public interface DispenseItemBehavior { - } - } - // CraftBukkit end -- ((Saddleable) list.get(0)).equipSaddle(SoundSource.BLOCKS); -+ ((Saddleable) list.get(0)).equipSaddle(SoundSource.BLOCKS, CraftItemStack.asNMSCopy(event.getItem())); // Paper - Fix saddles losing nbt data - MC-191591 - // itemstack.shrink(1); // CraftBukkit - handled above - this.setSuccess(true); - return stack; -diff --git a/src/main/java/net/minecraft/server/commands/DeOpCommands.java b/src/main/java/net/minecraft/server/commands/DeOpCommands.java -index 40490d10649e92cf622849f1bb87538102b130c7..797efca662dcc0fe7f4cf0b4b7baa235ea044f1f 100644 ---- a/src/main/java/net/minecraft/server/commands/DeOpCommands.java -+++ b/src/main/java/net/minecraft/server/commands/DeOpCommands.java -@@ -34,7 +34,7 @@ public class DeOpCommands { - playerList.deop(gameProfile); - ++i; - source.sendSuccess(() -> { -- return Component.translatable("commands.deop.success", targets.iterator().next().getName()); -+ return Component.translatable("commands.deop.success", gameProfile.getName()); // Paper - fixes MC-253721 - }, true); - } - } -diff --git a/src/main/java/net/minecraft/server/commands/OpCommand.java b/src/main/java/net/minecraft/server/commands/OpCommand.java -index 6cd6d69a20e95e344fc18ab67dc300824537a59b..2e2a7c2cf3081187da817479a9da3eb10f662a6d 100644 ---- a/src/main/java/net/minecraft/server/commands/OpCommand.java -+++ b/src/main/java/net/minecraft/server/commands/OpCommand.java -@@ -39,7 +39,7 @@ public class OpCommand { - playerList.op(gameProfile); - ++i; - source.sendSuccess(() -> { -- return Component.translatable("commands.op.success", targets.iterator().next().getName()); -+ return Component.translatable("commands.op.success", gameProfile.getName()); // Paper - fixes MC-253721 - }, true); - } - } -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 63e7f41eaed3f22c1bc0191790ff0ad313dc4ffd..7c5c9efe4d6037c4c5444d108d76af241144d6b5 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1233,7 +1233,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // CraftBukkit end - } - -- boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { -+ public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { // Paper - public - // Spigot start - return this.anyPlayerCloseEnoughForSpawning(pos, false); - } -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index de822b99df2de08371f3a7a29bf8f3275650887c..62feb9e2f8e0072bb180c65b4946281494a7dc1d 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -770,7 +770,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } else { - AABB axisalignedbb = AABB.encapsulatingFullBlocks(blockposition1, new BlockPos(blockposition1.atY(this.getMaxBuildHeight()))).inflate(3.0D); - List list = this.getEntitiesOfClass(LivingEntity.class, axisalignedbb, (entityliving) -> { -- return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition()); -+ return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition()) && !entityliving.isSpectator(); // Paper - Fix lightning being able to hit spectators (MC-262422) - }); - - if (!list.isEmpty()) { -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index efd95132301f50cd159339444e48cd4988accb1b..2fd04f7d39c4d06aa85133a3f53caa6d741d35ae 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -419,14 +419,14 @@ public class ServerPlayer extends Player { - BlockPos blockposition1 = PlayerRespawnLogic.getOverworldRespawnPos(world, blockposition.getX() + j2 - i, blockposition.getZ() + k2 - i); - - if (blockposition1 != null) { -- this.moveTo(blockposition1, 0.0F, 0.0F); -+ this.moveTo(blockposition1, world.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored - if (world.noCollision((Entity) this)) { - break; - } - } - } - } else { -- this.moveTo(blockposition, 0.0F, 0.0F); -+ this.moveTo(blockposition, world.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored - - while (!world.noCollision((Entity) this) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { - this.setPos(this.getX(), this.getY() + 1.0D, this.getZ()); -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index f3389dc345d8b6e5389ae37848d9b268d4bbad83..5d1b8bae981dc538ee1fe4fb993e44f227168233 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -92,7 +92,7 @@ public class ServerPlayerGameMode { - return event; // Paper - Expand PlayerGameModeChangeEvent - } - // CraftBukkit end -- this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer); -+ this.setGameModeForPlayer(gameMode, this.gameModeForPlayer); // Paper - Fix MC-259571 - this.player.onUpdateAbilities(); - this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit - this.level.updateSleepingPlayerList(); -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index a8a57819993f3657d6525a98527ec47631174bed..598dce073d7887d44a6630820c7e5746ce1c6dcc 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -709,8 +709,10 @@ public abstract class PlayerList { - Player player = entity.getBukkitEntity(); - PlayerLoginEvent event = new PlayerLoginEvent(player, loginlistener.connection.hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.channel.remoteAddress()).getAddress()); - -- if (this.getBans().isBanned(gameprofile) && !this.getBans().get(gameprofile).hasExpired()) { -- UserBanListEntry gameprofilebanentry = (UserBanListEntry) this.bans.get(gameprofile); -+ // Paper start - Fix MC-158900 -+ UserBanListEntry gameprofilebanentry; -+ if (getBans().isBanned(gameprofile) && (gameprofilebanentry = getBans().get(gameprofile)) != null) { -+ // Paper end - Fix MC-158900 - - ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned.reason", gameprofilebanentry.getReason()); - if (gameprofilebanentry.getExpires() != null) { -diff --git a/src/main/java/net/minecraft/world/entity/Saddleable.java b/src/main/java/net/minecraft/world/entity/Saddleable.java -index effe4c4fb37fe13aece70cdef4966047d4719af9..7152674d3f3fb98198585cb5ece2bb88877345f9 100644 ---- a/src/main/java/net/minecraft/world/entity/Saddleable.java -+++ b/src/main/java/net/minecraft/world/entity/Saddleable.java -@@ -9,6 +9,11 @@ public interface Saddleable { - boolean isSaddleable(); - - void equipSaddle(@Nullable SoundSource sound); -+ // Paper start - Fix saddles losing nbt data - MC-191591 -+ default void equipSaddle(final @Nullable SoundSource sound, final @Nullable net.minecraft.world.item.ItemStack stack) { -+ this.equipSaddle(sound); -+ } -+ // Paper end - - default SoundEvent getSaddleSoundEvent() { - return SoundEvents.HORSE_SADDLE; -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BegGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BegGoal.java -index 9a46007245399481fb6e749d95e9e4791e799250..2afca5652541c9166278f8f2590ddb81003ae579 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/BegGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/BegGoal.java -@@ -65,7 +65,7 @@ public class BegGoal extends Goal { - private boolean playerHoldingInteresting(Player player) { - for(InteractionHand interactionHand : InteractionHand.values()) { - ItemStack itemStack = player.getItemInHand(interactionHand); -- if (this.wolf.isTame() && itemStack.is(Items.BONE)) { -+ if (!this.wolf.isTame() && itemStack.is(Items.BONE)) { // Paper - Fix MC-84789 - return true; - } - -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -index 784a894688f98f9d0368a36d456c5c94e1ee3695..a85885ee51df585fa11ae9f8fcd67ff2a71c5a18 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -@@ -77,9 +77,10 @@ public class BreakDoorGoal extends DoorInteractGoal { - return; - } - // CraftBukkit end -+ final net.minecraft.world.level.block.state.BlockState oldState = this.mob.level().getBlockState(this.doorPos); // Paper - fix MC-263999 - this.mob.level().removeBlock(this.doorPos, false); - this.mob.level().levelEvent(1021, this.doorPos, 0); -- this.mob.level().levelEvent(2001, this.doorPos, Block.getId(this.mob.level().getBlockState(this.doorPos))); -+ this.mob.level().levelEvent(2001, this.doorPos, Block.getId(oldState)); // Paper - fix MC-263999 - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -index d802985f1431be4332c07f0dab88feebedea4ce2..4e2c23ccdf4e4a4d65b291dbe20952bae1838bff 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -@@ -31,6 +31,11 @@ public class EatBlockGoal extends Goal { - - @Override - public boolean canUse() { -+ // Paper start - Fix MC-210802 -+ if (!((net.minecraft.server.level.ServerLevel) this.level).chunkSource.chunkMap.anyPlayerCloseEnoughForSpawning(this.mob.chunkPosition())) { -+ return false; -+ } -+ // Paper end - if (this.mob.getRandom().nextInt(this.mob.isBaby() ? 50 : 1000) != 0) { - return false; - } else { -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java -index 11cc26954b4e97114b59df35a4f9b75a09132e20..0a3f7dcc0e205a85dbaa6dee1fc9ae2c7fa9e02d 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java -@@ -74,7 +74,7 @@ public class FollowOwnerGoal extends Goal { - } - - private boolean unableToMove() { -- return this.tamable.isOrderedToSit() || this.tamable.isPassenger() || this.tamable.isLeashed(); -+ return this.tamable.isOrderedToSit() || this.tamable.isPassenger() || this.tamable.isLeashed() || this.tamable.leashInfoTag != null; // Paper - Fix MC-173303 - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java -index 19540fd4a7f992888fadb6501d0c8a5a7e71fcf6..e241ae250f4f04a17ef2c583d00b065a4ca56a4c 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java -@@ -21,6 +21,13 @@ public class SwellGoal extends Goal { - return this.creeper.getSwellDir() > 0 || livingEntity != null && this.creeper.distanceToSqr(livingEntity) < 9.0D; - } - -+ // Paper start - Fix MC-179072 -+ @Override -+ public boolean canContinueToUse() { -+ return !net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(this.creeper.getTarget()) && canUse(); -+ } -+ // Paper end -+ - @Override - public void start() { - this.creeper.getNavigation().stop(); -diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -index 110dd5418b0512a2f27f0c4d5a5f1812356a6a12..5d247ac38fe8a61603b3d934f3000bcda773142b 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -@@ -239,9 +239,10 @@ public class Goat extends Animal { - player.setItemInHand(hand, itemstack1); - return InteractionResult.sidedSuccess(this.level().isClientSide); - } else { -+ boolean isFood = this.isFood(itemstack); // Paper - track before stack is possibly decreased to 0 (Fixes MC-244739) - InteractionResult enuminteractionresult = super.mobInteract(player, hand); - -- if (enuminteractionresult.consumesAction() && this.isFood(itemstack)) { -+ if (enuminteractionresult.consumesAction() && isFood) { // Paper - this.level().playSound((Player) null, (Entity) this, this.getEatingSound(itemstack), SoundSource.NEUTRAL, 1.0F, Mth.randomBetween(this.level().random, 0.8F, 1.2F)); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -index 56cc6ecf7f95687db7c7c062b4ee979bfe49844b..94dd97662ba07689fbfa16ef5c7d99fe12ce83de 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -@@ -252,7 +252,13 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - - @Override - public void equipSaddle(@Nullable SoundSource sound) { -- this.inventory.setItem(0, new ItemStack(Items.SADDLE)); -+ // Paper start - Fix saddles losing nbt data - MC-191591 -+ this.equipSaddle(sound, null); -+ } -+ @Override -+ public void equipSaddle(@Nullable SoundSource sound, @Nullable ItemStack stack) { -+ this.inventory.setItem(0, stack != null ? stack : new ItemStack(Items.SADDLE)); -+ // Paper end - } - - public void equipArmor(Player player, ItemStack stack) { -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -index 3ff1ae5ae705cd8d5c8529e1dcdd5ccaed908830..0c952bfc01b367a297e81768cab436c5474830f1 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -@@ -282,6 +282,14 @@ public class ItemFrame extends HangingEntity { - } - } - -+ // Paper start - Fix MC-123848 (spawn item frame drops above block) -+ @Nullable -+ @Override -+ public net.minecraft.world.entity.item.ItemEntity spawnAtLocation(ItemStack stack) { -+ return this.spawnAtLocation(stack, getDirection().equals(Direction.DOWN) ? -0.6F : 0.0F); -+ } -+ // Paper end -+ - private void removeFramedMap(ItemStack itemstack) { - this.getFramedMapId().ifPresent((i) -> { - MapItemSavedData worldmap = MapItem.getSavedData(i, this.level()); -diff --git a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java -index f174094febfdfdc309f1b50877be60bae8a98156..5f407535298a31a34cfe114dd863fd6a9b977707 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java -+++ b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java -@@ -87,8 +87,8 @@ public class CatSpawner implements CustomSpawner { - if (cat == null) { - return 0; - } else { -+ cat.moveTo(pos, 0.0F, 0.0F); // Paper - move up - Fix MC-147659 - cat.finalizeSpawn(world, world.getCurrentDifficultyAt(pos), MobSpawnType.NATURAL, (SpawnGroupData)null, (CompoundTag)null); -- cat.moveTo(pos, 0.0F, 0.0F); - world.addFreshEntityWithPassengers(cat); - return 1; - } -diff --git a/src/main/java/net/minecraft/world/entity/raid/Raids.java b/src/main/java/net/minecraft/world/entity/raid/Raids.java -index 4a0f4c83228187a2082ad029680056b1801f77bd..31831811ce16265e9828fa34d9e67d8ac195d723 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raids.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raids.java -@@ -134,7 +134,7 @@ public class Raids extends SavedData { - // CraftBukkit end - } else { - player.removeEffect(MobEffects.BAD_OMEN); -- player.connection.send(new ClientboundEntityEventPacket(player, (byte) 43)); -+ this.level.broadcastEntityEvent(player, net.minecraft.world.entity.EntityEvent.BAD_OMEN_TRIGGERED /* (byte) 43 */); // Paper - Fix MC-253884 - } - - if (flag) { -@@ -149,7 +149,7 @@ public class Raids extends SavedData { - } - // CraftBukkit end - raid.absorbBadOmen(player); -- player.connection.send(new ClientboundEntityEventPacket(player, (byte) 43)); -+ this.level.broadcastEntityEvent(player, net.minecraft.world.entity.EntityEvent.BAD_OMEN_TRIGGERED /* (byte) 43 */); // Paper - Fix MC-253884 - if (!raid.hasFirstWaveSpawned()) { - player.awardStat(Stats.RAID_TRIGGER); - CriteriaTriggers.BAD_OMEN.trigger(player); -diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -index b670c0cb3886c99d38a91b5c13aa2cefaae702cf..9599a5f96601030bf7f7cbd3392861d626959f9d 100644 ---- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -@@ -177,6 +177,11 @@ public class BeaconMenu extends AbstractContainerMenu { - // Paper end - Add PlayerChangeBeaconEffectEvent - - public void updateEffects(Optional primary, Optional secondary) { -+ // Paper start - fix MC-174630 - validate secondary power -+ if (secondary.isPresent() && secondary.get() != net.minecraft.world.effect.MobEffects.REGENERATION && (primary.isPresent() && secondary.get() != primary.get())) { -+ secondary = Optional.empty(); -+ } -+ // Paper end - if (this.paymentSlot.hasItem()) { - // Paper start - Add PlayerChangeBeaconEffectEvent - io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent event = new io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), convert(primary), convert(secondary), this.access.getLocation().getBlock()); -diff --git a/src/main/java/net/minecraft/world/item/BundleItem.java b/src/main/java/net/minecraft/world/item/BundleItem.java -index 10b0720ce7eed58fa3cd8c8051efa6225f7d73e1..ac0bc87f60c4e1562d1301522183e449558d42f8 100644 ---- a/src/main/java/net/minecraft/world/item/BundleItem.java -+++ b/src/main/java/net/minecraft/world/item/BundleItem.java -@@ -52,7 +52,7 @@ public class BundleItem extends Item { - }); - } else if (itemStack.getItem().canFitInsideContainerItems()) { - int i = (64 - getContentWeight(stack)) / getWeight(itemStack); -- int j = add(stack, slot.safeTake(itemStack.getCount(), i, player)); -+ int j = add(stack, slot.safeTake(itemStack.getCount(), Math.max(0, i), player)); // Paper - prevent item addition on overfilled bundles - safeTake will yield EMPTY for amount == 0. - if (j > 0) { - this.playInsertSound(player); - } -@@ -121,7 +121,7 @@ public class BundleItem extends Item { - int i = getContentWeight(bundle); - int j = getWeight(stack); - int k = Math.min(stack.getCount(), (64 - i) / j); -- if (k == 0) { -+ if (k <= 0) { // Paper - prevent item addition on overfilled bundles - return 0; - } else { - ListTag listTag = compoundTag.getList("Items", 10); -diff --git a/src/main/java/net/minecraft/world/item/SaddleItem.java b/src/main/java/net/minecraft/world/item/SaddleItem.java -index ca6a2b9840c9ade87ec8effab01d4f184fe876b7..43129ecefcc8beccbcf2978f262b1ce8cf49ca43 100644 ---- a/src/main/java/net/minecraft/world/item/SaddleItem.java -+++ b/src/main/java/net/minecraft/world/item/SaddleItem.java -@@ -18,7 +18,7 @@ public class SaddleItem extends Item { - if (entity instanceof Saddleable saddleable) { - if (entity.isAlive() && !saddleable.isSaddled() && saddleable.isSaddleable()) { - if (!user.level().isClientSide) { -- saddleable.equipSaddle(SoundSource.NEUTRAL); -+ saddleable.equipSaddle(SoundSource.NEUTRAL, stack.copyWithCount(1)); // Paper - Fix saddles losing nbt data - MC-191591 - entity.level().gameEvent(entity, GameEvent.EQUIP, entity.position()); - stack.shrink(1); - } -diff --git a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java -index 6e45582f8ea7dd2a46f58369c5581764538bff0d..3ecc92439fc85d224ff52f41c5e34079e042a5e6 100644 ---- a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java -@@ -51,4 +51,11 @@ public class AzaleaBlock extends BushBlock implements BonemealableBlock { - public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { - TreeGrower.AZALEA.growTree(world, world.getChunkSource().getGenerator(), pos, state, random); - } -+ -+ // Paper start - Fix MC-224454 -+ @Override -+ public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, net.minecraft.world.level.pathfinder.PathComputationType type) { -+ return false; -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -index 42c1e3dfec23d6a7d832bf73d47ecae1212ec2c9..a4857675772d4fe849ba85fc21a369decca42fc0 100644 ---- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -@@ -68,7 +68,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent - if (!world.isClientSide && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { - // CraftBukkit start -- if (entity.mayInteract(world, pos)) { -+ if ((entity instanceof net.minecraft.world.entity.player.Player || world.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING)) && entity.mayInteract(world, pos)) { // Paper - Fixes MC-248588 - if (!this.handleEntityOnFireInsideWithEvent(state, world, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities - return; - } -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 c7ba7ac1a3869e4db1ef6b0350b3cab7f31a94c4..d7beeac4a8e4a16221809663a5aa03389a759742 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 -@@ -675,13 +675,10 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - - @Override - public void fillStackedContents(StackedContents finder) { -- Iterator iterator = this.items.iterator(); -- -- while (iterator.hasNext()) { -- ItemStack itemstack = (ItemStack) iterator.next(); -- -- finder.accountStack(itemstack); -- } -+ // Paper start - don't account fuel stack (fixes MC-243057) -+ finder.accountStack(this.items.get(SLOT_INPUT)); -+ finder.accountStack(this.items.get(SLOT_RESULT)); -+ // Paper end - - } - } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index 61a618f09af475407a78343eecb4352052b1df1e..247f24c7fadc203ee0f6a6f85122c91ab4c82f80 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -291,7 +291,11 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); - new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); - // Paper end - beacon activation/deactivation events -+ // Paper start - fix MC-153086 -+ if (this.levels > 0 && !this.beamSections.isEmpty()) { - BeaconBlockEntity.playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE); -+ } -+ // Paper end - super.setRemoved(); - } - diff --git a/patches/server/0796-Add-PlayerInventorySlotChangeEvent.patch b/patches/server/0796-Add-PlayerInventorySlotChangeEvent.patch new file mode 100644 index 000000000000..7c9e04e6e8d6 --- /dev/null +++ b/patches/server/0796-Add-PlayerInventorySlotChangeEvent.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jakub Zacek +Date: Sun, 24 Apr 2022 22:56:59 +0200 +Subject: [PATCH] Add PlayerInventorySlotChangeEvent + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 84792f21cbb727bdb8f72b3574b39c4dad631612..ecdeaa3f5a793895c3a4584ae7e4594b21087167 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -330,6 +330,25 @@ public class ServerPlayer extends Player { + + } + } ++ // Paper start - Add PlayerInventorySlotChangeEvent ++ @Override ++ public void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) { ++ Slot slot = handler.getSlot(slotId); ++ if (!(slot instanceof ResultSlot)) { ++ if (slot.container == ServerPlayer.this.getInventory()) { ++ if (io.papermc.paper.event.player.PlayerInventorySlotChangeEvent.getHandlerList().getRegisteredListeners().length == 0) { ++ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); ++ return; ++ } ++ io.papermc.paper.event.player.PlayerInventorySlotChangeEvent event = new io.papermc.paper.event.player.PlayerInventorySlotChangeEvent(ServerPlayer.this.getBukkitEntity(), slotId, CraftItemStack.asBukkitCopy(oldStack), CraftItemStack.asBukkitCopy(stack)); ++ event.callEvent(); ++ if (event.shouldTriggerAdvancements()) { ++ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); ++ } ++ } ++ } ++ } ++ // Paper end - Add PlayerInventorySlotChangeEvent + + @Override + public void dataChanged(AbstractContainerMenu handler, int property, int value) {} +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index 94fe6bd403222049e4e7a407bb6ca99bcaab6acb..399b2f4ddb7e9ef26fbc5e83f3b77c46d3868814 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -302,7 +302,7 @@ public abstract class AbstractContainerMenu { + while (iterator.hasNext()) { + ContainerListener icrafting = (ContainerListener) iterator.next(); + +- icrafting.slotChanged(this, slot, itemstack2); ++ icrafting.slotChanged(this, slot, itemstack1, itemstack2); // Paper - Add PlayerInventorySlotChangeEvent + } + } + +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerListener.java b/src/main/java/net/minecraft/world/inventory/ContainerListener.java +index 0e19cc55646625bf32a354d3df2dc2d6bcff96f4..eba024c9aadef8902aacccea1584ffbee6c04e44 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerListener.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerListener.java +@@ -5,5 +5,11 @@ import net.minecraft.world.item.ItemStack; + public interface ContainerListener { + void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack stack); + ++ // Paper start - Add PlayerInventorySlotChangeEvent ++ default void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) { ++ slotChanged(handler, slotId, stack); ++ } ++ // Paper end - Add PlayerInventorySlotChangeEvent ++ + void dataChanged(AbstractContainerMenu handler, int property, int value); + } diff --git a/patches/server/0797-Elder-Guardian-appearance-API.patch b/patches/server/0797-Elder-Guardian-appearance-API.patch new file mode 100644 index 000000000000..302f3da2c29f --- /dev/null +++ b/patches/server/0797-Elder-Guardian-appearance-API.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SoSeDiK +Date: Tue, 11 Oct 2022 20:38:47 +0300 +Subject: [PATCH] Elder Guardian appearance API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index d866ad7fd91c64fd0fe6680a417678e3ef5ca41d..6ea05945e26e517563ed359a5216739dd49c7631 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -3215,6 +3215,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + // Paper end + ++ // Paper start ++ @Override ++ public void showElderGuardian(boolean silent) { ++ if (getHandle().connection != null) getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, silent ? 0F : 1F)); ++ } ++ // Paper end ++ + public Player.Spigot spigot() + { + return this.spigot; diff --git a/patches/server/0802-Allow-changing-bed-s-occupied-property.patch b/patches/server/0798-Allow-changing-bed-s-occupied-property.patch similarity index 100% rename from patches/server/0802-Allow-changing-bed-s-occupied-property.patch rename to patches/server/0798-Allow-changing-bed-s-occupied-property.patch diff --git a/patches/server/0799-Add-entity-knockback-API.patch b/patches/server/0799-Add-entity-knockback-API.patch new file mode 100644 index 000000000000..484391c1da2e --- /dev/null +++ b/patches/server/0799-Add-entity-knockback-API.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MelnCat +Date: Sun, 16 Oct 2022 12:10:17 -0700 +Subject: [PATCH] Add entity knockback API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 1aa4f09d93f36f523923281a6fb17dc184dbed86..908b72e1d85dc3d6992e1aa3018ac4d4c88f6fcf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1073,5 +1073,11 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + } + throw new IllegalArgumentException(entityCategory + " is an unrecognized entity category"); + } ++ ++ @Override ++ public void knockback(double strength, double directionX, double directionZ) { ++ Preconditions.checkArgument(strength > 0, "Knockback strength must be > 0"); ++ getHandle().knockback(strength, directionX, directionZ); ++ }; + // Paper end + } diff --git a/patches/server/0799-Correctly-handle-interactions-with-items-on-cooldown.patch b/patches/server/0799-Correctly-handle-interactions-with-items-on-cooldown.patch deleted file mode 100644 index 472a9fe2d4a8..000000000000 --- a/patches/server/0799-Correctly-handle-interactions-with-items-on-cooldown.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 16 Jun 2022 21:57:02 -0700 -Subject: [PATCH] Correctly handle interactions with items on cooldown - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index da8a60fbbba3866780615e65d6e242774a965bc6..88b71210d0845e8a4a2cd424ba238c4b5e27933b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -514,6 +514,7 @@ public class ServerPlayerGameMode { - BlockState iblockdata = world.getBlockState(blockposition); - InteractionResult enuminteractionresult = InteractionResult.PASS; - boolean cancelledBlock = false; -+ boolean cancelledItem = false; // Paper - correctly handle items on cooldown - - if (!iblockdata.getBlock().isEnabled(world.enabledFeatures())) { - return InteractionResult.FAIL; -@@ -523,10 +524,10 @@ public class ServerPlayerGameMode { - } - - if (player.getCooldowns().isOnCooldown(stack.getItem())) { -- cancelledBlock = true; -+ cancelledItem = true; // Paper - correctly handle items on cooldown - } - -- PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, hand, hitResult.getLocation()); -+ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, cancelledItem, hand, hitResult.getLocation()); // Paper - correctly handle items on cooldown - this.firedInteract = true; - this.interactResult = event.useItemInHand() == Event.Result.DENY; - this.interactPosition = blockposition.immutable(); -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 531eeb68bc0623b3c71e67789603ab7946e05818..a1860e21fd53b801ffd651cd27f5a8f9fcd02ee0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -544,6 +544,12 @@ public class CraftEventFactory { - } - - public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, InteractionHand hand, Vec3 targetPos) { -+ // Paper start - cancelledItem param -+ return CraftEventFactory.callPlayerInteractEvent(who, action, position, direction, itemstack, cancelledBlock, false, hand, targetPos); -+ } -+ -+ public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, boolean cancelledItem, InteractionHand hand, Vec3 targetPos) { -+ // Paper end - cancelledItem param - Player player = (who == null) ? null : (Player) who.getBukkitEntity(); - CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); - -@@ -578,6 +584,11 @@ public class CraftEventFactory { - if (cancelledBlock) { - event.setUseInteractedBlock(Event.Result.DENY); - } -+ // Paper start -+ if (cancelledItem) { -+ event.setUseItemInHand(Result.DENY); -+ } -+ // Paper end - craftServer.getPluginManager().callEvent(event); - - return event; diff --git a/patches/server/0800-Add-PlayerInventorySlotChangeEvent.patch b/patches/server/0800-Add-PlayerInventorySlotChangeEvent.patch deleted file mode 100644 index 41ff90bb2972..000000000000 --- a/patches/server/0800-Add-PlayerInventorySlotChangeEvent.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jakub Zacek -Date: Sun, 24 Apr 2022 22:56:59 +0200 -Subject: [PATCH] Add PlayerInventorySlotChangeEvent - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 2fd04f7d39c4d06aa85133a3f53caa6d741d35ae..89ee8c6f2c7201a7b55b3428a6225334fe316224 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -330,6 +330,25 @@ public class ServerPlayer extends Player { - - } - } -+ // Paper start - Add PlayerInventorySlotChangeEvent -+ @Override -+ public void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) { -+ Slot slot = handler.getSlot(slotId); -+ if (!(slot instanceof ResultSlot)) { -+ if (slot.container == ServerPlayer.this.getInventory()) { -+ if (io.papermc.paper.event.player.PlayerInventorySlotChangeEvent.getHandlerList().getRegisteredListeners().length == 0) { -+ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); -+ return; -+ } -+ io.papermc.paper.event.player.PlayerInventorySlotChangeEvent event = new io.papermc.paper.event.player.PlayerInventorySlotChangeEvent(ServerPlayer.this.getBukkitEntity(), slotId, CraftItemStack.asBukkitCopy(oldStack), CraftItemStack.asBukkitCopy(stack)); -+ event.callEvent(); -+ if (event.shouldTriggerAdvancements()) { -+ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); -+ } -+ } -+ } -+ } -+ // Paper end - Add PlayerInventorySlotChangeEvent - - @Override - public void dataChanged(AbstractContainerMenu handler, int property, int value) {} -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index 94fe6bd403222049e4e7a407bb6ca99bcaab6acb..399b2f4ddb7e9ef26fbc5e83f3b77c46d3868814 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -302,7 +302,7 @@ public abstract class AbstractContainerMenu { - while (iterator.hasNext()) { - ContainerListener icrafting = (ContainerListener) iterator.next(); - -- icrafting.slotChanged(this, slot, itemstack2); -+ icrafting.slotChanged(this, slot, itemstack1, itemstack2); // Paper - Add PlayerInventorySlotChangeEvent - } - } - -diff --git a/src/main/java/net/minecraft/world/inventory/ContainerListener.java b/src/main/java/net/minecraft/world/inventory/ContainerListener.java -index 0e19cc55646625bf32a354d3df2dc2d6bcff96f4..eba024c9aadef8902aacccea1584ffbee6c04e44 100644 ---- a/src/main/java/net/minecraft/world/inventory/ContainerListener.java -+++ b/src/main/java/net/minecraft/world/inventory/ContainerListener.java -@@ -5,5 +5,11 @@ import net.minecraft.world.item.ItemStack; - public interface ContainerListener { - void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack stack); - -+ // Paper start - Add PlayerInventorySlotChangeEvent -+ default void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) { -+ slotChanged(handler, slotId, stack); -+ } -+ // Paper end - Add PlayerInventorySlotChangeEvent -+ - void dataChanged(AbstractContainerMenu handler, int property, int value); - } diff --git a/patches/server/0800-Detect-headless-JREs.patch b/patches/server/0800-Detect-headless-JREs.patch new file mode 100644 index 000000000000..98f0b6a3300b --- /dev/null +++ b/patches/server/0800-Detect-headless-JREs.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Sat, 22 Oct 2022 14:47:45 +0200 +Subject: [PATCH] Detect headless JREs + +Crashes caused by the missing AWT dependency come up in the support channels fairly often. +This patch detects the missing dependency and stops the server with a clear error message, +containing a link to instructions on how to install a non-headless JRE. + +diff --git a/src/main/java/io/papermc/paper/util/ServerEnvironment.java b/src/main/java/io/papermc/paper/util/ServerEnvironment.java +index 6bd0afddbcc461149dfe9a5c7a86fff6ea13a5f1..148d233f4f5278ff39eacdaa0f4f0e7d73be936a 100644 +--- a/src/main/java/io/papermc/paper/util/ServerEnvironment.java ++++ b/src/main/java/io/papermc/paper/util/ServerEnvironment.java +@@ -37,4 +37,14 @@ public class ServerEnvironment { + public static boolean userIsRootOrAdmin() { + return RUNNING_AS_ROOT_OR_ADMIN; + } ++ ++ public static String awtDependencyCheck() { ++ try { ++ new java.awt.Color(0); ++ } catch (UnsatisfiedLinkError e) { ++ return e.getClass().getName() + ": " + e.getMessage(); ++ } ++ ++ return null; ++ } + } +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 7a5e70c99716273d04f4cd28d80a38f6f6b3868e..aa19a24b10cc6f4f79bb9ad65f92f60735ac5387 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -175,6 +175,18 @@ public class Main { + return; + } + ++ // Paper start - Detect headless JRE ++ String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck(); ++ if (awtException != null) { ++ Main.LOGGER.error("You are using a headless JRE distribution."); ++ Main.LOGGER.error("This distribution is missing certain graphic libraries that the Minecraft server needs to function."); ++ Main.LOGGER.error("For instructions on how to install the non-headless JRE, see https://docs.papermc.io/misc/java-install"); ++ Main.LOGGER.error(""); ++ Main.LOGGER.error(awtException); ++ return; ++ } ++ // Paper end - Detect headless JRE ++ + org.spigotmc.SpigotConfig.disabledAdvancements = spigotConfiguration.getStringList("advancements.disabled"); // Paper - fix SPIGOT-5885, must be set early in init + // Paper start - fix SPIGOT-5824 + File file; diff --git a/patches/server/0801-Elder-Guardian-appearance-API.patch b/patches/server/0801-Elder-Guardian-appearance-API.patch deleted file mode 100644 index 37522eeb4b5c..000000000000 --- a/patches/server/0801-Elder-Guardian-appearance-API.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SoSeDiK -Date: Tue, 11 Oct 2022 20:38:47 +0300 -Subject: [PATCH] Elder Guardian appearance API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 10d82f0e0c2c514e971d9cf95848a79ff8b7082d..432790b46ad5edcf504a3aab603597585c82913c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -3164,6 +3164,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - // Paper end - -+ // Paper start -+ @Override -+ public void showElderGuardian(boolean silent) { -+ if (getHandle().connection != null) getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, silent ? 0F : 1F)); -+ } -+ // Paper end -+ - public Player.Spigot spigot() - { - return this.spigot; diff --git a/patches/server/0805-fix-entity-vehicle-collision-event-not-called.patch b/patches/server/0801-fix-entity-vehicle-collision-event-not-called.patch similarity index 100% rename from patches/server/0805-fix-entity-vehicle-collision-event-not-called.patch rename to patches/server/0801-fix-entity-vehicle-collision-event-not-called.patch diff --git a/patches/server/0802-Add-EntityToggleSitEvent.patch b/patches/server/0802-Add-EntityToggleSitEvent.patch new file mode 100644 index 000000000000..49a9b74fe4e6 --- /dev/null +++ b/patches/server/0802-Add-EntityToggleSitEvent.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: KyGuy2002 +Date: Fri, 11 Mar 2022 15:33:10 +0000 +Subject: [PATCH] Add EntityToggleSitEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +index 4d893c445af2b6dc74d5ad731b69eb5a488817b4..e4550d3ac8d93e0dd9a54e41fbbbef2ef9d4f55e 100644 +--- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java ++++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +@@ -67,7 +67,7 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { + } + + this.orderedToSit = nbt.getBoolean("Sitting"); +- this.setInSittingPose(this.orderedToSit); ++ this.setInSittingPose(this.orderedToSit, false); // Paper - Add EntityToggleSitEvent + } + + @Override +@@ -125,6 +125,12 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { + } + + public void setInSittingPose(boolean inSittingPose) { ++ // Paper start - Add EntityToggleSitEvent ++ this.setInSittingPose(inSittingPose, true); ++ } ++ public void setInSittingPose(boolean inSittingPose, boolean callEvent) { ++ if (callEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), inSittingPose).callEvent()) return; ++ // Paper end - Add EntityToggleSitEvent + byte b = this.entityData.get(DATA_FLAGS_ID); + if (inSittingPose) { + this.entityData.set(DATA_FLAGS_ID, (byte)(b | 1)); +diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java +index fa0a7e18fed41653b1625cfb12a78d9ee502f2be..12b49510deb0494c4a70b63679f8818960f2af06 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java +@@ -433,7 +433,7 @@ public class Fox extends Animal implements VariantHolder { + + this.setSleeping(nbt.getBoolean("Sleeping")); + this.setVariant(Fox.Type.byName(nbt.getString("Type"))); +- this.setSitting(nbt.getBoolean("Sitting")); ++ this.setSitting(nbt.getBoolean("Sitting"), false); // Paper - Add EntityToggleSitEvent + this.setIsCrouching(nbt.getBoolean("Crouching")); + if (this.level() instanceof ServerLevel) { + this.setTargetGoals(); +@@ -446,6 +446,12 @@ public class Fox extends Animal implements VariantHolder { + } + + public void setSitting(boolean sitting) { ++ // Paper start - Add EntityToggleSitEvent ++ this.setSitting(sitting, true); ++ } ++ public void setSitting(boolean sitting, boolean fireEvent) { ++ if (fireEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return; ++ // Paper end - Add EntityToggleSitEvent + this.setFlag(1, sitting); + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java +index 11694f103ebc522c2ad6eb6d494d39cc31ea3107..f783fe169141d33e8569ec7f5d71985b74bdbcb6 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Panda.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java +@@ -138,6 +138,7 @@ public class Panda extends Animal { + } + + public void sit(boolean sitting) { ++ if (!new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return; // Paper - Add EntityToggleSitEvent + this.setFlag(8, sitting); + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java +index 16a5e1247a160a7ae3eba2bab9fde42dff5d62c6..2382f4f044e346fbc0e25de8fb2be30f3630c722 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java ++++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java +@@ -556,7 +556,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl + } + + public void sitDown() { +- if (!this.isCamelSitting()) { ++ if (!this.isCamelSitting() && new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), true).callEvent()) { // Paper - Add EntityToggleSitEvent + this.playSound(SoundEvents.CAMEL_SIT, 1.0F, this.getVoicePitch()); + this.setPose(Pose.SITTING); + this.gameEvent(GameEvent.ENTITY_ACTION); +@@ -565,7 +565,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl + } + + public void standUp() { +- if (this.isCamelSitting()) { ++ if (this.isCamelSitting() && new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), false).callEvent()) { // Paper - Add EntityToggleSitEvent + this.playSound(SoundEvents.CAMEL_STAND, 1.0F, this.getVoicePitch()); + this.setPose(Pose.STANDING); + this.gameEvent(GameEvent.ENTITY_ACTION); +@@ -574,6 +574,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl + } + + public void standUpInstantly() { ++ if (this.isCamelSitting() && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), false).callEvent()) return; // Paper - Add EntityToggleSitEvent + this.setPose(Pose.STANDING); + this.gameEvent(GameEvent.ENTITY_ACTION); + this.resetLastPoseChangeTickToFullStand(this.level().getGameTime()); diff --git a/patches/server/0803-Add-entity-knockback-API.patch b/patches/server/0803-Add-entity-knockback-API.patch deleted file mode 100644 index 07893eac96b7..000000000000 --- a/patches/server/0803-Add-entity-knockback-API.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MelnCat -Date: Sun, 16 Oct 2022 12:10:17 -0700 -Subject: [PATCH] Add entity knockback API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index b7aee6c64ffc74924a1b211e097a15e8b27f18f7..6f22f12d37c647c65020bb547ffca95465675e31 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -1060,5 +1060,11 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - } - throw new IllegalArgumentException(entityCategory + " is an unrecognized entity category"); - } -+ -+ @Override -+ public void knockback(double strength, double directionX, double directionZ) { -+ Preconditions.checkArgument(strength > 0, "Knockback strength must be > 0"); -+ getHandle().knockback(strength, directionX, directionZ); -+ }; - // Paper end - } diff --git a/patches/server/0807-Add-fire-tick-delay-option.patch b/patches/server/0803-Add-fire-tick-delay-option.patch similarity index 100% rename from patches/server/0807-Add-fire-tick-delay-option.patch rename to patches/server/0803-Add-fire-tick-delay-option.patch diff --git a/patches/server/0808-Add-Moving-Piston-API.patch b/patches/server/0804-Add-Moving-Piston-API.patch similarity index 100% rename from patches/server/0808-Add-Moving-Piston-API.patch rename to patches/server/0804-Add-Moving-Piston-API.patch diff --git a/patches/server/0804-Detect-headless-JREs.patch b/patches/server/0804-Detect-headless-JREs.patch deleted file mode 100644 index baddcbc26a51..000000000000 --- a/patches/server/0804-Detect-headless-JREs.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Sat, 22 Oct 2022 14:47:45 +0200 -Subject: [PATCH] Detect headless JREs - -Crashes caused by the missing AWT dependency come up in the support channels fairly often. -This patch detects the missing dependency and stops the server with a clear error message, -containing a link to instructions on how to install a non-headless JRE. - -diff --git a/src/main/java/io/papermc/paper/util/ServerEnvironment.java b/src/main/java/io/papermc/paper/util/ServerEnvironment.java -index 6bd0afddbcc461149dfe9a5c7a86fff6ea13a5f1..148d233f4f5278ff39eacdaa0f4f0e7d73be936a 100644 ---- a/src/main/java/io/papermc/paper/util/ServerEnvironment.java -+++ b/src/main/java/io/papermc/paper/util/ServerEnvironment.java -@@ -37,4 +37,14 @@ public class ServerEnvironment { - public static boolean userIsRootOrAdmin() { - return RUNNING_AS_ROOT_OR_ADMIN; - } -+ -+ public static String awtDependencyCheck() { -+ try { -+ new java.awt.Color(0); -+ } catch (UnsatisfiedLinkError e) { -+ return e.getClass().getName() + ": " + e.getMessage(); -+ } -+ -+ return null; -+ } - } -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index 1cad3585ca122a465572b16d4ecbb7231e87c7de..15464ef7779d62f1dba5edeabcb91c6e677e676f 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -175,6 +175,18 @@ public class Main { - return; - } - -+ // Paper start - Detect headless JRE -+ String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck(); -+ if (awtException != null) { -+ Main.LOGGER.error("You are using a headless JRE distribution."); -+ Main.LOGGER.error("This distribution is missing certain graphic libraries that the Minecraft server needs to function."); -+ Main.LOGGER.error("For instructions on how to install the non-headless JRE, see https://docs.papermc.io/misc/java-install"); -+ Main.LOGGER.error(""); -+ Main.LOGGER.error(awtException); -+ return; -+ } -+ // Paper end - Detect headless JRE -+ - org.spigotmc.SpigotConfig.disabledAdvancements = spigotConfiguration.getStringList("advancements.disabled"); // Paper - fix SPIGOT-5885, must be set early in init - // Paper start - fix SPIGOT-5824 - File file; diff --git a/patches/server/0805-Ignore-impossible-spawn-tick.patch b/patches/server/0805-Ignore-impossible-spawn-tick.patch new file mode 100644 index 000000000000..116d0fd7f67b --- /dev/null +++ b/patches/server/0805-Ignore-impossible-spawn-tick.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dannyball710 +Date: Sat, 12 Feb 2022 23:42:48 +0800 +Subject: [PATCH] Ignore impossible spawn tick + + +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index 41d2793e69bd664456b5e3c5891b03bdcb31d103..65c3e91ac4541c0150057dc9f012eb1ee566516e 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -82,6 +82,7 @@ public abstract class BaseSpawner { + } + + public void serverTick(ServerLevel world, BlockPos pos) { ++ if (spawnCount <= 0 || maxNearbyEntities <= 0) return; // Paper - Ignore impossible spawn tick + // Paper start - Configurable mob spawner tick rate + if (spawnDelay > 0 && --tickDelay > 0) return; + tickDelay = world.paperConfig().tickRates.mobSpawner; diff --git a/patches/server/0806-Add-EntityToggleSitEvent.patch b/patches/server/0806-Add-EntityToggleSitEvent.patch deleted file mode 100644 index 71b347d01233..000000000000 --- a/patches/server/0806-Add-EntityToggleSitEvent.patch +++ /dev/null @@ -1,100 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: KyGuy2002 -Date: Fri, 11 Mar 2022 15:33:10 +0000 -Subject: [PATCH] Add EntityToggleSitEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -index 4d893c445af2b6dc74d5ad731b69eb5a488817b4..e4550d3ac8d93e0dd9a54e41fbbbef2ef9d4f55e 100644 ---- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java -+++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -@@ -67,7 +67,7 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { - } - - this.orderedToSit = nbt.getBoolean("Sitting"); -- this.setInSittingPose(this.orderedToSit); -+ this.setInSittingPose(this.orderedToSit, false); // Paper - Add EntityToggleSitEvent - } - - @Override -@@ -125,6 +125,12 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { - } - - public void setInSittingPose(boolean inSittingPose) { -+ // Paper start - Add EntityToggleSitEvent -+ this.setInSittingPose(inSittingPose, true); -+ } -+ public void setInSittingPose(boolean inSittingPose, boolean callEvent) { -+ if (callEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), inSittingPose).callEvent()) return; -+ // Paper end - Add EntityToggleSitEvent - byte b = this.entityData.get(DATA_FLAGS_ID); - if (inSittingPose) { - this.entityData.set(DATA_FLAGS_ID, (byte)(b | 1)); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java -index 94bb69a7f5795e0fbee171433632b5c3bca3b902..287e52dc844c2a64dac74dad117b775f46631157 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java -@@ -433,7 +433,7 @@ public class Fox extends Animal implements VariantHolder { - - this.setSleeping(nbt.getBoolean("Sleeping")); - this.setVariant(Fox.Type.byName(nbt.getString("Type"))); -- this.setSitting(nbt.getBoolean("Sitting")); -+ this.setSitting(nbt.getBoolean("Sitting"), false); // Paper - Add EntityToggleSitEvent - this.setIsCrouching(nbt.getBoolean("Crouching")); - if (this.level() instanceof ServerLevel) { - this.setTargetGoals(); -@@ -446,6 +446,12 @@ public class Fox extends Animal implements VariantHolder { - } - - public void setSitting(boolean sitting) { -+ // Paper start - Add EntityToggleSitEvent -+ this.setSitting(sitting, true); -+ } -+ public void setSitting(boolean sitting, boolean fireEvent) { -+ if (fireEvent && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return; -+ // Paper end - Add EntityToggleSitEvent - this.setFlag(1, sitting); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java -index 11694f103ebc522c2ad6eb6d494d39cc31ea3107..f783fe169141d33e8569ec7f5d71985b74bdbcb6 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java -@@ -138,6 +138,7 @@ public class Panda extends Animal { - } - - public void sit(boolean sitting) { -+ if (!new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), sitting).callEvent()) return; // Paper - Add EntityToggleSitEvent - this.setFlag(8, sitting); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -index 570b39592e7e3a24828c233ec2a2f113b9ef5868..e89f454fe178483a7db381591a4a345ac24db2b8 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -+++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -@@ -556,7 +556,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl - } - - public void sitDown() { -- if (!this.isCamelSitting()) { -+ if (!this.isCamelSitting() && new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), true).callEvent()) { // Paper - Add EntityToggleSitEvent - this.playSound(SoundEvents.CAMEL_SIT, 1.0F, this.getVoicePitch()); - this.setPose(Pose.SITTING); - this.gameEvent(GameEvent.ENTITY_ACTION); -@@ -565,7 +565,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl - } - - public void standUp() { -- if (this.isCamelSitting()) { -+ if (this.isCamelSitting() && new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), false).callEvent()) { // Paper - Add EntityToggleSitEvent - this.playSound(SoundEvents.CAMEL_STAND, 1.0F, this.getVoicePitch()); - this.setPose(Pose.STANDING); - this.gameEvent(GameEvent.ENTITY_ACTION); -@@ -574,6 +574,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl - } - - public void standUpInstantly() { -+ if (this.isCamelSitting() && !new io.papermc.paper.event.entity.EntityToggleSitEvent(this.getBukkitEntity(), false).callEvent()) return; // Paper - Add EntityToggleSitEvent - this.setPose(Pose.STANDING); - this.gameEvent(GameEvent.ENTITY_ACTION); - this.resetLastPoseChangeTickToFullStand(this.level().getGameTime()); diff --git a/patches/server/0810-Track-projectile-source-for-fireworks-from-dispenser.patch b/patches/server/0806-Track-projectile-source-for-fireworks-from-dispenser.patch similarity index 100% rename from patches/server/0810-Track-projectile-source-for-fireworks-from-dispenser.patch rename to patches/server/0806-Track-projectile-source-for-fireworks-from-dispenser.patch diff --git a/patches/server/0811-Fix-EntityArgument-suggestion-permissions-to-align-w.patch b/patches/server/0807-Fix-EntityArgument-suggestion-permissions-to-align-w.patch similarity index 100% rename from patches/server/0811-Fix-EntityArgument-suggestion-permissions-to-align-w.patch rename to patches/server/0807-Fix-EntityArgument-suggestion-permissions-to-align-w.patch diff --git a/patches/server/0808-Fix-EntityCombustEvent-cancellation-cant-fully-preve.patch b/patches/server/0808-Fix-EntityCombustEvent-cancellation-cant-fully-preve.patch new file mode 100644 index 000000000000..17e437d82c90 --- /dev/null +++ b/patches/server/0808-Fix-EntityCombustEvent-cancellation-cant-fully-preve.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Denery +Date: Mon, 31 Oct 2022 14:20:52 +0300 +Subject: [PATCH] Fix EntityCombustEvent cancellation cant fully prevent + entities from being set on fire + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 7d600e90ad8c7a020988a18e2926872180fb4a1e..11e24044753be205af988474c59b4c8418a02f77 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3095,6 +3095,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + pluginManager.callEvent(entityCombustEvent); + if (!entityCombustEvent.isCancelled()) { + this.setSecondsOnFire(entityCombustEvent.getDuration(), false); ++ // Paper start - fix EntityCombustEvent cancellation ++ } else { ++ this.setRemainingFireTicks(this.remainingFireTicks - 1); ++ // Paper end - fix EntityCombustEvent cancellation + } + // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +index ceb5f9867f714b3b6a4602c787574dfa83c006f6..bd7cb2ddcac5cdc1d61899d12fc3383fd00598b1 100644 +--- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +@@ -134,6 +134,10 @@ public abstract class BaseFireBlock extends Block { + + if (!event.isCancelled()) { + entity.setSecondsOnFire(event.getDuration(), false); ++ // Paper start - fix EntityCombustEvent cancellation ++ } else { ++ entity.setRemainingFireTicks(entity.getRemainingFireTicks() - 1); ++ // Paper end - fix EntityCombustEvent cancellation + } + // CraftBukkit end + } diff --git a/patches/server/0809-Add-PrePlayerAttackEntityEvent.patch b/patches/server/0809-Add-PrePlayerAttackEntityEvent.patch new file mode 100644 index 000000000000..2ad09d360591 --- /dev/null +++ b/patches/server/0809-Add-PrePlayerAttackEntityEvent.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 18 Sep 2022 13:10:18 -0400 +Subject: [PATCH] Add PrePlayerAttackEntityEvent + + +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 8ca4f45f1db35a08a335142aa888e98e63cec348..b3a91d7ad2095485006ef1ee7f07c474269ae455 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1233,8 +1233,17 @@ public abstract class Player extends LivingEntity { + } + + public void attack(Entity target) { +- if (target.isAttackable()) { +- if (!target.skipAttackInteraction(this)) { ++ // Paper start - PlayerAttackEntityEvent ++ boolean willAttack = target.isAttackable() && !target.skipAttackInteraction(this); // Vanilla logic ++ io.papermc.paper.event.player.PrePlayerAttackEntityEvent playerAttackEntityEvent = new io.papermc.paper.event.player.PrePlayerAttackEntityEvent( ++ (org.bukkit.entity.Player) this.getBukkitEntity(), ++ target.getBukkitEntity(), ++ willAttack ++ ); ++ ++ if (playerAttackEntityEvent.callEvent() && willAttack) { // Logic moved to willAttack local variable. ++ { ++ // Paper end - PlayerAttackEntityEvent + float f = (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE); + float f1; + diff --git a/patches/server/0809-Ignore-impossible-spawn-tick.patch b/patches/server/0809-Ignore-impossible-spawn-tick.patch deleted file mode 100644 index c123aa837079..000000000000 --- a/patches/server/0809-Ignore-impossible-spawn-tick.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: dannyball710 -Date: Sat, 12 Feb 2022 23:42:48 +0800 -Subject: [PATCH] Ignore impossible spawn tick - - -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index 7fdf026bdeb85d149e24aa013181a848ece4c55e..f936f4664584f19bc6720c664035747721ea8231 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -82,6 +82,7 @@ public abstract class BaseSpawner { - } - - public void serverTick(ServerLevel world, BlockPos pos) { -+ if (spawnCount <= 0 || maxNearbyEntities <= 0) return; // Paper - Ignore impossible spawn tick - // Paper start - Configurable mob spawner tick rate - if (spawnDelay > 0 && --tickDelay > 0) return; - tickDelay = world.paperConfig().tickRates.mobSpawner; diff --git a/patches/server/0810-ensure-reset-EnderDragon-boss-event-name.patch b/patches/server/0810-ensure-reset-EnderDragon-boss-event-name.patch new file mode 100644 index 000000000000..18d504b2fd02 --- /dev/null +++ b/patches/server/0810-ensure-reset-EnderDragon-boss-event-name.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 12 Nov 2022 10:08:58 -0800 +Subject: [PATCH] ensure reset EnderDragon boss event name + +Fix MC-257487 + +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 59f6c3109b34719a7ed487ada5a8ce33ec458e87..b9b773d7dba559afe00b085ded3f020ea6b97c12 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 +@@ -73,6 +73,7 @@ public class EndDragonFight { + private static final int GATEWAY_DISTANCE = 96; + public static final int DRAGON_SPAWN_Y = 128; + private final Predicate validPlayer; ++ private static final Component DEFAULT_BOSS_EVENT_NAME = Component.translatable("entity.minecraft.ender_dragon"); // Paper - ensure reset EnderDragon boss event name + public final ServerBossEvent dragonEvent; + public final ServerLevel level; + private final BlockPos origin; +@@ -101,7 +102,7 @@ public class EndDragonFight { + } + + public EndDragonFight(ServerLevel world, long gatewaysSeed, EndDragonFight.Data data, BlockPos origin) { +- this.dragonEvent = (ServerBossEvent) (new ServerBossEvent(Component.translatable("entity.minecraft.ender_dragon"), BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true); ++ this.dragonEvent = (ServerBossEvent) (new ServerBossEvent(DEFAULT_BOSS_EVENT_NAME, BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true); // Paper - ensure reset EnderDragon boss event name + this.gateways = new ObjectArrayList(); + this.ticksSinceLastPlayerScan = 21; + this.skipArenaLoadedCheck = false; +@@ -503,6 +504,10 @@ public class EndDragonFight { + this.ticksSinceDragonSeen = 0; + if (dragon.hasCustomName()) { + this.dragonEvent.setName(dragon.getDisplayName()); ++ // Paper start - ensure reset EnderDragon boss event name ++ } else { ++ this.dragonEvent.setName(DEFAULT_BOSS_EVENT_NAME); ++ // Paper end - ensure reset EnderDragon boss event name + } + } + diff --git a/patches/server/0815-fix-MC-252817-green-map-markers-do-not-disappear.patch b/patches/server/0811-fix-MC-252817-green-map-markers-do-not-disappear.patch similarity index 100% rename from patches/server/0815-fix-MC-252817-green-map-markers-do-not-disappear.patch rename to patches/server/0811-fix-MC-252817-green-map-markers-do-not-disappear.patch diff --git a/patches/server/0812-Add-Player-Warden-Warning-API.patch b/patches/server/0812-Add-Player-Warden-Warning-API.patch new file mode 100644 index 000000000000..e6584699bb4e --- /dev/null +++ b/patches/server/0812-Add-Player-Warden-Warning-API.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dawon +Date: Sat, 15 Oct 2022 00:46:57 +0200 +Subject: [PATCH] Add Player Warden Warning API + +== AT == +public net.minecraft.server.level.ServerPlayer wardenSpawnTracker +public net.minecraft.world.entity.monster.warden.WardenSpawnTracker ticksSinceLastWarning +public net.minecraft.world.entity.monster.warden.WardenSpawnTracker cooldownTicks +public net.minecraft.world.entity.monster.warden.WardenSpawnTracker increaseWarningLevel()V + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 6ea05945e26e517563ed359a5216739dd49c7631..ca71160491f11bc805a6cb1ad432a6ba4c756931 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -3220,6 +3220,41 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + public void showElderGuardian(boolean silent) { + if (getHandle().connection != null) getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, silent ? 0F : 1F)); + } ++ ++ @Override ++ public int getWardenWarningCooldown() { ++ return this.getHandle().wardenSpawnTracker.cooldownTicks; ++ } ++ ++ @Override ++ public void setWardenWarningCooldown(int cooldown) { ++ this.getHandle().wardenSpawnTracker.cooldownTicks = Math.max(cooldown, 0); ++ } ++ ++ @Override ++ public int getWardenTimeSinceLastWarning() { ++ return this.getHandle().wardenSpawnTracker.ticksSinceLastWarning; ++ } ++ ++ @Override ++ public void setWardenTimeSinceLastWarning(int time) { ++ this.getHandle().wardenSpawnTracker.ticksSinceLastWarning = time; ++ } ++ ++ @Override ++ public int getWardenWarningLevel() { ++ return this.getHandle().wardenSpawnTracker.getWarningLevel(); ++ } ++ ++ @Override ++ public void setWardenWarningLevel(int warningLevel) { ++ this.getHandle().wardenSpawnTracker.setWarningLevel(warningLevel); ++ } ++ ++ @Override ++ public void increaseWardenWarningLevel() { ++ this.getHandle().wardenSpawnTracker.increaseWarningLevel(); ++ } + // Paper end + + public Player.Spigot spigot() diff --git a/patches/server/0812-Fix-EntityCombustEvent-cancellation-cant-fully-preve.patch b/patches/server/0812-Fix-EntityCombustEvent-cancellation-cant-fully-preve.patch deleted file mode 100644 index b576f955d97c..000000000000 --- a/patches/server/0812-Fix-EntityCombustEvent-cancellation-cant-fully-preve.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Denery -Date: Mon, 31 Oct 2022 14:20:52 +0300 -Subject: [PATCH] Fix EntityCombustEvent cancellation cant fully prevent - entities from being set on fire - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index e6739e436f709fd24315932ad6386ed6534721c2..79ceab8d5328df63ec20eb68b30c98fc38bdf51c 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3091,6 +3091,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - pluginManager.callEvent(entityCombustEvent); - if (!entityCombustEvent.isCancelled()) { - this.setSecondsOnFire(entityCombustEvent.getDuration(), false); -+ // Paper start - fix EntityCombustEvent cancellation -+ } else { -+ this.setRemainingFireTicks(this.remainingFireTicks - 1); -+ // Paper end - fix EntityCombustEvent cancellation - } - // CraftBukkit end - } -diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -index 118403953629b405b9db78de1bf684b31289c499..e9ec8a8969651ea6760bf01c622c66efa7f6e7e5 100644 ---- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -@@ -134,6 +134,10 @@ public abstract class BaseFireBlock extends Block { - - if (!event.isCancelled()) { - entity.setSecondsOnFire(event.getDuration(), false); -+ // Paper start - fix EntityCombustEvent cancellation -+ } else { -+ entity.setRemainingFireTicks(entity.getRemainingFireTicks() - 1); -+ // Paper end - fix EntityCombustEvent cancellation - } - // CraftBukkit end - } diff --git a/patches/server/0813-Add-PrePlayerAttackEntityEvent.patch b/patches/server/0813-Add-PrePlayerAttackEntityEvent.patch deleted file mode 100644 index 8c00ecdafc4c..000000000000 --- a/patches/server/0813-Add-PrePlayerAttackEntityEvent.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 18 Sep 2022 13:10:18 -0400 -Subject: [PATCH] Add PrePlayerAttackEntityEvent - - -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 addbfbf1cae0a9c38ee1daabdb74e9af324f3a94..a7690e89bb6ed07512fc8a110f541183b7df0a63 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1232,8 +1232,17 @@ public abstract class Player extends LivingEntity { - } - - public void attack(Entity target) { -- if (target.isAttackable()) { -- if (!target.skipAttackInteraction(this)) { -+ // Paper start - PlayerAttackEntityEvent -+ boolean willAttack = target.isAttackable() && !target.skipAttackInteraction(this); // Vanilla logic -+ io.papermc.paper.event.player.PrePlayerAttackEntityEvent playerAttackEntityEvent = new io.papermc.paper.event.player.PrePlayerAttackEntityEvent( -+ (org.bukkit.entity.Player) this.getBukkitEntity(), -+ target.getBukkitEntity(), -+ willAttack -+ ); -+ -+ if (playerAttackEntityEvent.callEvent() && willAttack) { // Logic moved to willAttack local variable. -+ { -+ // Paper end - PlayerAttackEntityEvent - float f = (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE); - float f1; - diff --git a/patches/server/0817-More-vanilla-friendly-methods-to-update-trades.patch b/patches/server/0813-More-vanilla-friendly-methods-to-update-trades.patch similarity index 100% rename from patches/server/0817-More-vanilla-friendly-methods-to-update-trades.patch rename to patches/server/0813-More-vanilla-friendly-methods-to-update-trades.patch diff --git a/patches/server/0818-Add-paper-dumplisteners-command.patch b/patches/server/0814-Add-paper-dumplisteners-command.patch similarity index 100% rename from patches/server/0818-Add-paper-dumplisteners-command.patch rename to patches/server/0814-Add-paper-dumplisteners-command.patch diff --git a/patches/server/0814-ensure-reset-EnderDragon-boss-event-name.patch b/patches/server/0814-ensure-reset-EnderDragon-boss-event-name.patch deleted file mode 100644 index 56b34053072a..000000000000 --- a/patches/server/0814-ensure-reset-EnderDragon-boss-event-name.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 12 Nov 2022 10:08:58 -0800 -Subject: [PATCH] ensure reset EnderDragon boss event name - -Fix MC-257487 - -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 2d74efff30c93ba664cf8dbf20e47dcdbd767d3f..e2e2c9f168ae4651f8fa484f6f2a17ba25f99207 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 -@@ -73,6 +73,7 @@ public class EndDragonFight { - private static final int GATEWAY_DISTANCE = 96; - public static final int DRAGON_SPAWN_Y = 128; - private final Predicate validPlayer; -+ private static final Component DEFAULT_BOSS_EVENT_NAME = Component.translatable("entity.minecraft.ender_dragon"); // Paper - ensure reset EnderDragon boss event name - public final ServerBossEvent dragonEvent; - public final ServerLevel level; - private final BlockPos origin; -@@ -101,7 +102,7 @@ public class EndDragonFight { - } - - public EndDragonFight(ServerLevel world, long gatewaysSeed, EndDragonFight.Data data, BlockPos origin) { -- this.dragonEvent = (ServerBossEvent) (new ServerBossEvent(Component.translatable("entity.minecraft.ender_dragon"), BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true); -+ this.dragonEvent = (ServerBossEvent) (new ServerBossEvent(DEFAULT_BOSS_EVENT_NAME, BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true); // Paper - ensure reset EnderDragon boss event name - this.gateways = new ObjectArrayList(); - this.ticksSinceLastPlayerScan = 21; - this.skipArenaLoadedCheck = false; -@@ -503,6 +504,10 @@ public class EndDragonFight { - this.ticksSinceDragonSeen = 0; - if (dragon.hasCustomName()) { - this.dragonEvent.setName(dragon.getDisplayName()); -+ // Paper start - ensure reset EnderDragon boss event name -+ } else { -+ this.dragonEvent.setName(DEFAULT_BOSS_EVENT_NAME); -+ // Paper end - ensure reset EnderDragon boss event name - } - } - diff --git a/patches/server/0815-check-global-player-list-where-appropriate.patch b/patches/server/0815-check-global-player-list-where-appropriate.patch new file mode 100644 index 000000000000..53e80e9c9ef4 --- /dev/null +++ b/patches/server/0815-check-global-player-list-where-appropriate.patch @@ -0,0 +1,85 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 22 Nov 2022 13:16:01 -0800 +Subject: [PATCH] check global player list where appropriate + +Makes certain entities check all players when searching for a player +instead of just checking players in their world. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index e7ed2d1160d412790b23550f9ae967179b7a61f4..711712f144d7b0e26d1248f53bf7ac3963c5df4a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2385,4 +2385,12 @@ public class ServerLevel extends Level implements WorldGenLevel { + entity.updateDynamicGameEventListener(DynamicGameEventListener::move); + } + } ++ ++ // Paper start - check global player list where appropriate ++ @Override ++ @Nullable ++ public Player getGlobalPlayerByUUID(UUID uuid) { ++ return this.server.getPlayerList().getPlayer(uuid); ++ } ++ // Paper end - check global player list where appropriate + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index a33c5870874674c29bf5e50ea58357965d24e54a..38fdb9555edcaf9608c315e24382b4f94370e154 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3690,7 +3690,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + public void onItemPickup(ItemEntity item) { +- Entity entity = item.getOwner(); ++ Entity entity = item.thrower != null ? this.level().getGlobalPlayerByUUID(item.thrower) : null; // Paper - check global player list where appropriate + + if (entity instanceof ServerPlayer) { + CriteriaTriggers.THROWN_ITEM_PICKED_UP_BY_ENTITY.trigger((ServerPlayer) entity, item.getItem(), this); +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 d4ac3e566b47cfc8688bcc2ab08385b6de4693f8..7de9d012e7416eaa0189b513a0972c846e93c4b6 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +@@ -272,7 +272,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + entityvillager.finalizeSpawn(world, world.getCurrentDifficultyAt(entityvillager.blockPosition()), MobSpawnType.CONVERSION, (SpawnGroupData) null, (CompoundTag) null); + entityvillager.refreshBrain(world); + if (this.conversionStarter != null) { +- Player entityhuman = world.getPlayerByUUID(this.conversionStarter); ++ Player entityhuman = world.getGlobalPlayerByUUID(this.conversionStarter); // Paper - check global player list where appropriate + + if (entityhuman instanceof ServerPlayer) { + CriteriaTriggers.CURED_ZOMBIE_VILLAGER.trigger((ServerPlayer) entityhuman, this, entityvillager); +diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java +index e7b6f97d8519a5797903664e5ba2a793e37a4bfc..57d4d2014f33a2f069d6c5aaa8e87e36b63a7177 100644 +--- a/src/main/java/net/minecraft/world/level/EntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +@@ -231,4 +231,11 @@ public interface EntityGetter { + + return null; + } ++ ++ // Paper start - check global player list where appropriate ++ @Nullable ++ default Player getGlobalPlayerByUUID(UUID uuid) { ++ return this.getPlayerByUUID(uuid); ++ } ++ // Paper end - check global player list where appropriate + } +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java +index e143f42e71ac774d49b75e6d85591aa1189ee210..c27b97e9f097c824de7c785b4cc9e0a503259b08 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java +@@ -100,6 +100,13 @@ public class SculkShriekerBlockEntity extends BlockEntity implements GameEventLi + + @Nullable + public static ServerPlayer tryGetPlayer(@Nullable Entity entity) { ++ // Paper start - check global player list where appropriate; ensure level is the same for sculk events ++ final ServerPlayer player = tryGetPlayer0(entity); ++ return player != null && player.level() == entity.level() ? player : null; ++ } ++ @Nullable ++ private static ServerPlayer tryGetPlayer0(@Nullable Entity entity) { ++ // Paper end - check global player list where appropriate + if (entity instanceof ServerPlayer serverPlayer) { + return serverPlayer; + } else { diff --git a/patches/server/0816-Add-Player-Warden-Warning-API.patch b/patches/server/0816-Add-Player-Warden-Warning-API.patch deleted file mode 100644 index d0aa5b1c00cc..000000000000 --- a/patches/server/0816-Add-Player-Warden-Warning-API.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: dawon -Date: Sat, 15 Oct 2022 00:46:57 +0200 -Subject: [PATCH] Add Player Warden Warning API - -== AT == -public net.minecraft.server.level.ServerPlayer wardenSpawnTracker -public net.minecraft.world.entity.monster.warden.WardenSpawnTracker ticksSinceLastWarning -public net.minecraft.world.entity.monster.warden.WardenSpawnTracker cooldownTicks -public net.minecraft.world.entity.monster.warden.WardenSpawnTracker increaseWarningLevel()V - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 432790b46ad5edcf504a3aab603597585c82913c..be5b8e12a7a15b97a34ddd9f50b8780a74faffbf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -3169,6 +3169,41 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - public void showElderGuardian(boolean silent) { - if (getHandle().connection != null) getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, silent ? 0F : 1F)); - } -+ -+ @Override -+ public int getWardenWarningCooldown() { -+ return this.getHandle().wardenSpawnTracker.cooldownTicks; -+ } -+ -+ @Override -+ public void setWardenWarningCooldown(int cooldown) { -+ this.getHandle().wardenSpawnTracker.cooldownTicks = Math.max(cooldown, 0); -+ } -+ -+ @Override -+ public int getWardenTimeSinceLastWarning() { -+ return this.getHandle().wardenSpawnTracker.ticksSinceLastWarning; -+ } -+ -+ @Override -+ public void setWardenTimeSinceLastWarning(int time) { -+ this.getHandle().wardenSpawnTracker.ticksSinceLastWarning = time; -+ } -+ -+ @Override -+ public int getWardenWarningLevel() { -+ return this.getHandle().wardenSpawnTracker.getWarningLevel(); -+ } -+ -+ @Override -+ public void setWardenWarningLevel(int warningLevel) { -+ this.getHandle().wardenSpawnTracker.setWarningLevel(warningLevel); -+ } -+ -+ @Override -+ public void increaseWardenWarningLevel() { -+ this.getHandle().wardenSpawnTracker.increaseWarningLevel(); -+ } - // Paper end - - public Player.Spigot spigot() diff --git a/patches/server/0820-Fix-async-entity-add-due-to-fungus-trees.patch b/patches/server/0816-Fix-async-entity-add-due-to-fungus-trees.patch similarity index 100% rename from patches/server/0820-Fix-async-entity-add-due-to-fungus-trees.patch rename to patches/server/0816-Fix-async-entity-add-due-to-fungus-trees.patch diff --git a/patches/server/0817-ItemStack-damage-API.patch b/patches/server/0817-ItemStack-damage-API.patch new file mode 100644 index 000000000000..6f851f92dbd7 --- /dev/null +++ b/patches/server/0817-ItemStack-damage-API.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 8 May 2022 13:35:45 -0700 +Subject: [PATCH] ItemStack damage API + +Adds methods notify clients about item breaks and +to simulate damage done to an itemstack and all +the logic associated with damaging them + +== AT == +public net.minecraft.world.entity.LivingEntity entityEventForEquipmentBreak(Lnet/minecraft/world/entity/EquipmentSlot;)B + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 908b72e1d85dc3d6992e1aa3018ac4d4c88f6fcf..77b32e520f80289a44d75ed0cde0b8bc3b3cecc3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1074,6 +1074,53 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + throw new IllegalArgumentException(entityCategory + " is an unrecognized entity category"); + } + ++ @Override ++ public void broadcastSlotBreak(org.bukkit.inventory.EquipmentSlot slot) { ++ this.getHandle().broadcastBreakEvent(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); ++ } ++ ++ @Override ++ public void broadcastSlotBreak(org.bukkit.inventory.EquipmentSlot slot, Collection players) { ++ if (players.isEmpty()) { ++ return; ++ } ++ final net.minecraft.network.protocol.game.ClientboundEntityEventPacket packet = new net.minecraft.network.protocol.game.ClientboundEntityEventPacket( ++ this.getHandle(), ++ net.minecraft.world.entity.LivingEntity.entityEventForEquipmentBreak(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)) ++ ); ++ players.forEach(player -> ((CraftPlayer) player).getHandle().connection.send(packet)); ++ } ++ ++ @Override ++ public ItemStack damageItemStack(ItemStack stack, int amount) { ++ final net.minecraft.world.item.ItemStack nmsStack; ++ if (stack instanceof CraftItemStack craftItemStack) { ++ if (craftItemStack.handle == null || craftItemStack.handle.isEmpty()) { ++ return stack; ++ } ++ nmsStack = craftItemStack.handle; ++ } else { ++ nmsStack = CraftItemStack.asNMSCopy(stack); ++ stack = CraftItemStack.asCraftMirror(nmsStack); // mirror to capture changes in hurt logic & events ++ } ++ this.damageItemStack0(nmsStack, amount, null); ++ return stack; ++ } ++ ++ @Override ++ public void damageItemStack(org.bukkit.inventory.EquipmentSlot slot, int amount) { ++ final net.minecraft.world.entity.EquipmentSlot nmsSlot = org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot); ++ this.damageItemStack0(this.getHandle().getItemBySlot(nmsSlot), amount, nmsSlot); ++ } ++ ++ private void damageItemStack0(net.minecraft.world.item.ItemStack nmsStack, int amount, net.minecraft.world.entity.EquipmentSlot slot) { ++ nmsStack.hurtAndBreak(amount, this.getHandle(), livingEntity -> { ++ if (slot != null) { ++ livingEntity.broadcastBreakEvent(slot); ++ } ++ }); ++ } ++ + @Override + public void knockback(double strength, double directionX, double directionZ) { + Preconditions.checkArgument(strength > 0, "Knockback strength must be > 0"); diff --git a/patches/server/0818-Friction-API.patch b/patches/server/0818-Friction-API.patch new file mode 100644 index 000000000000..0435b575543c --- /dev/null +++ b/patches/server/0818-Friction-API.patch @@ -0,0 +1,157 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Wed, 15 Sep 2021 20:44:22 +0200 +Subject: [PATCH] Friction API + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 38fdb9555edcaf9608c315e24382b4f94370e154..bcaa8c21fc09bc4cc3fd4c9bb827ef0750c12aef 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -261,6 +261,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + public boolean bukkitPickUpLoot; + public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper + public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event ++ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API + + @Override + public float getBukkitYaw() { +@@ -716,7 +717,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + public boolean shouldDiscardFriction() { +- return this.discardFriction; ++ return !this.frictionState.toBooleanOrElse(!this.discardFriction); // Paper - Friction API + } + + public void setDiscardFriction(boolean noDrag) { +@@ -760,6 +761,11 @@ public abstract class LivingEntity extends Entity implements Attackable { + + @Override + public void addAdditionalSaveData(CompoundTag nbt) { ++ // Paper start - Friction API ++ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { ++ nbt.putString("Paper.FrictionState", this.frictionState.toString()); ++ } ++ // Paper end - Friction API + nbt.putFloat("Health", this.getHealth()); + nbt.putShort("HurtTime", (short) this.hurtTime); + nbt.putInt("HurtByTimestamp", this.lastHurtByMobTimestamp); +@@ -803,6 +809,16 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + this.internalSetAbsorptionAmount(absorptionAmount); + // Paper end - Check for NaN ++ // Paper start - Friction API ++ if (nbt.contains("Paper.FrictionState")) { ++ String fs = nbt.getString("Paper.FrictionState"); ++ try { ++ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); ++ } catch (Exception ignored) { ++ LOGGER.error("Unknown friction state " + fs + " for " + this); ++ } ++ } ++ // Paper end - Friction API + if (nbt.contains("Attributes", 9) && this.level() != null && !this.level().isClientSide) { + this.getAttributes().load(nbt.getList("Attributes", 10)); + } +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 8aaca29b115a55bf48306e71432c4c20d2bd21dc..eb0d6238588efa35fa868f26290547574a08eca2 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -57,6 +57,7 @@ public class ItemEntity extends Entity implements TraceableEntity { + private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit + public boolean canMobPickup = true; // Paper - Item#canEntityPickup + private int despawnRate = -1; // Paper - Alternative item-despawn-rate ++ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API + + public ItemEntity(EntityType type, Level world) { + super(type, world); +@@ -178,7 +179,11 @@ public class ItemEntity extends Entity implements TraceableEntity { + this.move(MoverType.SELF, this.getDeltaMovement()); + float f1 = 0.98F; + +- if (this.onGround()) { ++ // Paper start - Friction API ++ if (frictionState == net.kyori.adventure.util.TriState.FALSE) { ++ f1 = 1F; ++ } else if (this.onGround()) { ++ // Paper end - Friction API + f1 = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getFriction() * 0.98F; + } + +@@ -387,6 +392,11 @@ public class ItemEntity extends Entity implements TraceableEntity { + + @Override + public void addAdditionalSaveData(CompoundTag nbt) { ++ // Paper start - Friction API ++ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { ++ nbt.putString("Paper.FrictionState", this.frictionState.toString()); ++ } ++ // Paper end - Friction API + nbt.putShort("Health", (short) this.health); + nbt.putShort("Age", (short) this.age); + nbt.putShort("PickupDelay", (short) this.pickupDelay); +@@ -421,6 +431,17 @@ public class ItemEntity extends Entity implements TraceableEntity { + this.cachedThrower = null; + } + ++ // Paper start - Friction API ++ if (nbt.contains("Paper.FrictionState")) { ++ String fs = nbt.getString("Paper.FrictionState"); ++ try { ++ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); ++ } catch (Exception ignored) { ++ com.mojang.logging.LogUtils.getLogger().error("Unknown friction state " + fs + " for " + this); ++ } ++ } ++ // Paper end - Friction API ++ + CompoundTag nbttagcompound1 = nbt.getCompound("Item"); + + this.setItem(ItemStack.of(nbttagcompound1)); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +index cbfd4cf1d7d32757cf124d1aaa4b83d8a155868f..832def3c518be8d6d81e71f6022566e6179e2d17 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +@@ -99,6 +99,18 @@ public class CraftItem extends CraftEntity implements Item { + this.getHandle().age = willAge ? 0 : NO_AGE_TIME; + } + ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public net.kyori.adventure.util.TriState getFrictionState() { ++ return this.getHandle().frictionState; ++ } ++ ++ @Override ++ public void setFrictionState(@org.jetbrains.annotations.NotNull net.kyori.adventure.util.TriState state) { ++ java.util.Objects.requireNonNull(state, "state may not be null"); ++ this.getHandle().frictionState = state; ++ } ++ + @Override + public int getHealth() { + return this.getHandle().health; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 77b32e520f80289a44d75ed0cde0b8bc3b3cecc3..bee23867ae7fdc62ee93277ae1959d6a7df7f5b6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1121,6 +1121,18 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + }); + } + ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public net.kyori.adventure.util.TriState getFrictionState() { ++ return this.getHandle().frictionState; ++ } ++ ++ @Override ++ public void setFrictionState(@org.jetbrains.annotations.NotNull net.kyori.adventure.util.TriState state) { ++ java.util.Objects.requireNonNull(state, "state may not be null"); ++ this.getHandle().frictionState = state; ++ } ++ + @Override + public void knockback(double strength, double directionX, double directionZ) { + Preconditions.checkArgument(strength > 0, "Knockback strength must be > 0"); diff --git a/patches/server/0823-Ability-to-control-player-s-insomnia-and-phantoms.patch b/patches/server/0819-Ability-to-control-player-s-insomnia-and-phantoms.patch similarity index 100% rename from patches/server/0823-Ability-to-control-player-s-insomnia-and-phantoms.patch rename to patches/server/0819-Ability-to-control-player-s-insomnia-and-phantoms.patch diff --git a/patches/server/0819-check-global-player-list-where-appropriate.patch b/patches/server/0819-check-global-player-list-where-appropriate.patch deleted file mode 100644 index 0045fbf60711..000000000000 --- a/patches/server/0819-check-global-player-list-where-appropriate.patch +++ /dev/null @@ -1,85 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 22 Nov 2022 13:16:01 -0800 -Subject: [PATCH] check global player list where appropriate - -Makes certain entities check all players when searching for a player -instead of just checking players in their world. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index e7ed2d1160d412790b23550f9ae967179b7a61f4..711712f144d7b0e26d1248f53bf7ac3963c5df4a 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2385,4 +2385,12 @@ public class ServerLevel extends Level implements WorldGenLevel { - entity.updateDynamicGameEventListener(DynamicGameEventListener::move); - } - } -+ -+ // Paper start - check global player list where appropriate -+ @Override -+ @Nullable -+ public Player getGlobalPlayerByUUID(UUID uuid) { -+ return this.server.getPlayerList().getPlayer(uuid); -+ } -+ // Paper end - check global player list where appropriate - } -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 39d9f33e49cc8099e0f6dc9822e6d471b46d6e28..5cc93ed8e082324c12ba55e7d388a8cdc7f67f1a 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3677,7 +3677,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public void onItemPickup(ItemEntity item) { -- Entity entity = item.getOwner(); -+ Entity entity = item.thrower != null ? this.level().getGlobalPlayerByUUID(item.thrower) : null; // Paper - check global player list where appropriate - - if (entity instanceof ServerPlayer) { - CriteriaTriggers.THROWN_ITEM_PICKED_UP_BY_ENTITY.trigger((ServerPlayer) entity, item.getItem(), this); -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 d4ac3e566b47cfc8688bcc2ab08385b6de4693f8..7de9d012e7416eaa0189b513a0972c846e93c4b6 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -@@ -272,7 +272,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - entityvillager.finalizeSpawn(world, world.getCurrentDifficultyAt(entityvillager.blockPosition()), MobSpawnType.CONVERSION, (SpawnGroupData) null, (CompoundTag) null); - entityvillager.refreshBrain(world); - if (this.conversionStarter != null) { -- Player entityhuman = world.getPlayerByUUID(this.conversionStarter); -+ Player entityhuman = world.getGlobalPlayerByUUID(this.conversionStarter); // Paper - check global player list where appropriate - - if (entityhuman instanceof ServerPlayer) { - CriteriaTriggers.CURED_ZOMBIE_VILLAGER.trigger((ServerPlayer) entityhuman, this, entityvillager); -diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index e7b6f97d8519a5797903664e5ba2a793e37a4bfc..57d4d2014f33a2f069d6c5aaa8e87e36b63a7177 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -231,4 +231,11 @@ public interface EntityGetter { - - return null; - } -+ -+ // Paper start - check global player list where appropriate -+ @Nullable -+ default Player getGlobalPlayerByUUID(UUID uuid) { -+ return this.getPlayerByUUID(uuid); -+ } -+ // Paper end - check global player list where appropriate - } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java -index e143f42e71ac774d49b75e6d85591aa1189ee210..c27b97e9f097c824de7c785b4cc9e0a503259b08 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SculkShriekerBlockEntity.java -@@ -100,6 +100,13 @@ public class SculkShriekerBlockEntity extends BlockEntity implements GameEventLi - - @Nullable - public static ServerPlayer tryGetPlayer(@Nullable Entity entity) { -+ // Paper start - check global player list where appropriate; ensure level is the same for sculk events -+ final ServerPlayer player = tryGetPlayer0(entity); -+ return player != null && player.level() == entity.level() ? player : null; -+ } -+ @Nullable -+ private static ServerPlayer tryGetPlayer0(@Nullable Entity entity) { -+ // Paper end - check global player list where appropriate - if (entity instanceof ServerPlayer serverPlayer) { - return serverPlayer; - } else { diff --git a/patches/server/0824-Fix-player-kick-on-shutdown.patch b/patches/server/0820-Fix-player-kick-on-shutdown.patch similarity index 100% rename from patches/server/0824-Fix-player-kick-on-shutdown.patch rename to patches/server/0820-Fix-player-kick-on-shutdown.patch diff --git a/patches/server/0821-ItemStack-damage-API.patch b/patches/server/0821-ItemStack-damage-API.patch deleted file mode 100644 index 05fdb9a49e3c..000000000000 --- a/patches/server/0821-ItemStack-damage-API.patch +++ /dev/null @@ -1,70 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 8 May 2022 13:35:45 -0700 -Subject: [PATCH] ItemStack damage API - -Adds methods notify clients about item breaks and -to simulate damage done to an itemstack and all -the logic associated with damaging them - -== AT == -public net.minecraft.world.entity.LivingEntity entityEventForEquipmentBreak(Lnet/minecraft/world/entity/EquipmentSlot;)B - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 6f22f12d37c647c65020bb547ffca95465675e31..3777d6db6cb5099e9b1454a436262c89312fb442 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -1061,6 +1061,53 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - throw new IllegalArgumentException(entityCategory + " is an unrecognized entity category"); - } - -+ @Override -+ public void broadcastSlotBreak(org.bukkit.inventory.EquipmentSlot slot) { -+ this.getHandle().broadcastBreakEvent(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); -+ } -+ -+ @Override -+ public void broadcastSlotBreak(org.bukkit.inventory.EquipmentSlot slot, Collection players) { -+ if (players.isEmpty()) { -+ return; -+ } -+ final net.minecraft.network.protocol.game.ClientboundEntityEventPacket packet = new net.minecraft.network.protocol.game.ClientboundEntityEventPacket( -+ this.getHandle(), -+ net.minecraft.world.entity.LivingEntity.entityEventForEquipmentBreak(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)) -+ ); -+ players.forEach(player -> ((CraftPlayer) player).getHandle().connection.send(packet)); -+ } -+ -+ @Override -+ public ItemStack damageItemStack(ItemStack stack, int amount) { -+ final net.minecraft.world.item.ItemStack nmsStack; -+ if (stack instanceof CraftItemStack craftItemStack) { -+ if (craftItemStack.handle == null || craftItemStack.handle.isEmpty()) { -+ return stack; -+ } -+ nmsStack = craftItemStack.handle; -+ } else { -+ nmsStack = CraftItemStack.asNMSCopy(stack); -+ stack = CraftItemStack.asCraftMirror(nmsStack); // mirror to capture changes in hurt logic & events -+ } -+ this.damageItemStack0(nmsStack, amount, null); -+ return stack; -+ } -+ -+ @Override -+ public void damageItemStack(org.bukkit.inventory.EquipmentSlot slot, int amount) { -+ final net.minecraft.world.entity.EquipmentSlot nmsSlot = org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot); -+ this.damageItemStack0(this.getHandle().getItemBySlot(nmsSlot), amount, nmsSlot); -+ } -+ -+ private void damageItemStack0(net.minecraft.world.item.ItemStack nmsStack, int amount, net.minecraft.world.entity.EquipmentSlot slot) { -+ nmsStack.hurtAndBreak(amount, this.getHandle(), livingEntity -> { -+ if (slot != null) { -+ livingEntity.broadcastBreakEvent(slot); -+ } -+ }); -+ } -+ - @Override - public void knockback(double strength, double directionX, double directionZ) { - Preconditions.checkArgument(strength > 0, "Knockback strength must be > 0"); diff --git a/patches/server/0821-Sync-offhand-slot-in-menus.patch b/patches/server/0821-Sync-offhand-slot-in-menus.patch new file mode 100644 index 000000000000..19c9999a0427 --- /dev/null +++ b/patches/server/0821-Sync-offhand-slot-in-menus.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 14 Jan 2022 10:20:40 -0800 +Subject: [PATCH] Sync offhand slot in menus + +Menus don't add slots for the offhand, so on sendAllDataToRemote calls the +offhand slot isn't sent. This is not correct because you *can* put stuff into the offhand +by pressing the offhand swap item + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index ecdeaa3f5a793895c3a4584ae7e4594b21087167..d4930c40f03c5f331847bf52736c563a688d7daf 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -299,6 +299,13 @@ public class ServerPlayer extends Player { + + } + ++ // Paper start - Sync offhand slot in menus ++ @Override ++ public void sendOffHandSlotChange() { ++ ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(ServerPlayer.this.inventoryMenu.containerId, ServerPlayer.this.inventoryMenu.incrementStateId(), net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, ServerPlayer.this.inventoryMenu.getSlot(net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT).getItem().copy())); ++ } ++ // Paper end - Sync offhand slot in menus ++ + @Override + public void sendSlotChange(AbstractContainerMenu handler, int slot, ItemStack stack) { + ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(handler.containerId, handler.incrementStateId(), slot, stack)); +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index 399b2f4ddb7e9ef26fbc5e83f3b77c46d3868814..24caa1cf91cd50a5972238119aca1f85ec2b3d2b 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -200,6 +200,7 @@ public abstract class AbstractContainerMenu { + + if (this.synchronizer != null) { + this.synchronizer.sendInitialData(this, this.remoteSlots, this.remoteCarried, this.remoteDataSlots.toIntArray()); ++ this.synchronizer.sendOffHandSlotChange(); // Paper - Sync offhand slot in menus; update player's offhand since the offhand slot is not added to the slots for menus but can be changed by swapping from a menu slot + } + + } +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java b/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java +index ff4fa86f9408e83e505f7e27692d3423f8570c48..a45ef5fcffc05e4e30801b73e82d29c6dbf5b8fd 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java +@@ -6,6 +6,7 @@ import net.minecraft.world.item.ItemStack; + public interface ContainerSynchronizer { + void sendInitialData(AbstractContainerMenu handler, NonNullList stacks, ItemStack cursorStack, int[] properties); + ++ default void sendOffHandSlotChange() {} // Paper - Sync offhand slot in menus + void sendSlotChange(AbstractContainerMenu handler, int slot, ItemStack stack); + + void sendCarriedChange(AbstractContainerMenu handler, ItemStack stack); diff --git a/patches/server/0822-Friction-API.patch b/patches/server/0822-Friction-API.patch deleted file mode 100644 index 4df25cbd6556..000000000000 --- a/patches/server/0822-Friction-API.patch +++ /dev/null @@ -1,157 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Wed, 15 Sep 2021 20:44:22 +0200 -Subject: [PATCH] Friction API - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 2e989789139ede20c808d7b024ce95d6c585b5d8..d6f12c3e995ff1b1d77538fd77402225c29b1c51 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -260,6 +260,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - public boolean bukkitPickUpLoot; - public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper - public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event -+ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API - - @Override - public float getBukkitYaw() { -@@ -715,7 +716,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public boolean shouldDiscardFriction() { -- return this.discardFriction; -+ return !this.frictionState.toBooleanOrElse(!this.discardFriction); // Paper - Friction API - } - - public void setDiscardFriction(boolean noDrag) { -@@ -759,6 +760,11 @@ public abstract class LivingEntity extends Entity implements Attackable { - - @Override - public void addAdditionalSaveData(CompoundTag nbt) { -+ // Paper start - Friction API -+ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { -+ nbt.putString("Paper.FrictionState", this.frictionState.toString()); -+ } -+ // Paper end - Friction API - nbt.putFloat("Health", this.getHealth()); - nbt.putShort("HurtTime", (short) this.hurtTime); - nbt.putInt("HurtByTimestamp", this.lastHurtByMobTimestamp); -@@ -802,6 +808,16 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - this.internalSetAbsorptionAmount(absorptionAmount); - // Paper end - Check for NaN -+ // Paper start - Friction API -+ if (nbt.contains("Paper.FrictionState")) { -+ String fs = nbt.getString("Paper.FrictionState"); -+ try { -+ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); -+ } catch (Exception ignored) { -+ LOGGER.error("Unknown friction state " + fs + " for " + this); -+ } -+ } -+ // Paper end - Friction API - if (nbt.contains("Attributes", 9) && this.level() != null && !this.level().isClientSide) { - this.getAttributes().load(nbt.getList("Attributes", 10)); - } -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 8aaca29b115a55bf48306e71432c4c20d2bd21dc..eb0d6238588efa35fa868f26290547574a08eca2 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -57,6 +57,7 @@ public class ItemEntity extends Entity implements TraceableEntity { - private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit - public boolean canMobPickup = true; // Paper - Item#canEntityPickup - private int despawnRate = -1; // Paper - Alternative item-despawn-rate -+ public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API - - public ItemEntity(EntityType type, Level world) { - super(type, world); -@@ -178,7 +179,11 @@ public class ItemEntity extends Entity implements TraceableEntity { - this.move(MoverType.SELF, this.getDeltaMovement()); - float f1 = 0.98F; - -- if (this.onGround()) { -+ // Paper start - Friction API -+ if (frictionState == net.kyori.adventure.util.TriState.FALSE) { -+ f1 = 1F; -+ } else if (this.onGround()) { -+ // Paper end - Friction API - f1 = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getFriction() * 0.98F; - } - -@@ -387,6 +392,11 @@ public class ItemEntity extends Entity implements TraceableEntity { - - @Override - public void addAdditionalSaveData(CompoundTag nbt) { -+ // Paper start - Friction API -+ if (this.frictionState != net.kyori.adventure.util.TriState.NOT_SET) { -+ nbt.putString("Paper.FrictionState", this.frictionState.toString()); -+ } -+ // Paper end - Friction API - nbt.putShort("Health", (short) this.health); - nbt.putShort("Age", (short) this.age); - nbt.putShort("PickupDelay", (short) this.pickupDelay); -@@ -421,6 +431,17 @@ public class ItemEntity extends Entity implements TraceableEntity { - this.cachedThrower = null; - } - -+ // Paper start - Friction API -+ if (nbt.contains("Paper.FrictionState")) { -+ String fs = nbt.getString("Paper.FrictionState"); -+ try { -+ frictionState = net.kyori.adventure.util.TriState.valueOf(fs); -+ } catch (Exception ignored) { -+ com.mojang.logging.LogUtils.getLogger().error("Unknown friction state " + fs + " for " + this); -+ } -+ } -+ // Paper end - Friction API -+ - CompoundTag nbttagcompound1 = nbt.getCompound("Item"); - - this.setItem(ItemStack.of(nbttagcompound1)); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -index cbfd4cf1d7d32757cf124d1aaa4b83d8a155868f..832def3c518be8d6d81e71f6022566e6179e2d17 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -@@ -99,6 +99,18 @@ public class CraftItem extends CraftEntity implements Item { - this.getHandle().age = willAge ? 0 : NO_AGE_TIME; - } - -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public net.kyori.adventure.util.TriState getFrictionState() { -+ return this.getHandle().frictionState; -+ } -+ -+ @Override -+ public void setFrictionState(@org.jetbrains.annotations.NotNull net.kyori.adventure.util.TriState state) { -+ java.util.Objects.requireNonNull(state, "state may not be null"); -+ this.getHandle().frictionState = state; -+ } -+ - @Override - public int getHealth() { - return this.getHandle().health; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 3777d6db6cb5099e9b1454a436262c89312fb442..330f0f79e9f12c5faf2aa9898047304ae281c10b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -1108,6 +1108,18 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - }); - } - -+ @org.jetbrains.annotations.NotNull -+ @Override -+ public net.kyori.adventure.util.TriState getFrictionState() { -+ return this.getHandle().frictionState; -+ } -+ -+ @Override -+ public void setFrictionState(@org.jetbrains.annotations.NotNull net.kyori.adventure.util.TriState state) { -+ java.util.Objects.requireNonNull(state, "state may not be null"); -+ this.getHandle().frictionState = state; -+ } -+ - @Override - public void knockback(double strength, double directionX, double directionZ) { - Preconditions.checkArgument(strength > 0, "Knockback strength must be > 0"); diff --git a/patches/server/0822-Player-Entity-Tracking-Events.patch b/patches/server/0822-Player-Entity-Tracking-Events.patch new file mode 100644 index 000000000000..1eae0e930b87 --- /dev/null +++ b/patches/server/0822-Player-Entity-Tracking-Events.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yannick Lamprecht +Date: Wed, 30 Mar 2022 18:16:52 +0200 +Subject: [PATCH] Player Entity Tracking Events + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 7c5c9efe4d6037c4c5444d108d76af241144d6b5..601693243c11b06fe0bae0040bf79d95696cdb21 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1749,7 +1749,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // CraftBukkit end + if (flag) { + if (this.seenBy.add(player.connection)) { ++ // Paper start - entity tracking events ++ if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { + this.serverEntity.addPairing(player); ++ } ++ // Paper end - entity tracking events + } + } else if (this.seenBy.remove(player.connection)) { + this.serverEntity.removePairing(player); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 11e24044753be205af988474c59b4c8418a02f77..425e53aaf09618f6e44b9d34ed3412a8f180ac39 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3821,7 +3821,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + + public void startSeenByPlayer(ServerPlayer player) {} + +- public void stopSeenByPlayer(ServerPlayer player) {} ++ // Paper start - entity tracking events ++ public void stopSeenByPlayer(ServerPlayer player) { ++ // Since this event cannot be cancelled, we should call it here to catch all "un-tracks" ++ if (io.papermc.paper.event.player.PlayerUntrackEntityEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ new io.papermc.paper.event.player.PlayerUntrackEntityEvent(player.getBukkitEntity(), this.getBukkitEntity()).callEvent(); ++ } ++ } ++ // Paper end - entity tracking events + + public float rotate(Rotation rotation) { + float f = Mth.wrapDegrees(this.getYRot()); diff --git a/patches/server/0827-Limit-pet-look-distance.patch b/patches/server/0823-Limit-pet-look-distance.patch similarity index 100% rename from patches/server/0827-Limit-pet-look-distance.patch rename to patches/server/0823-Limit-pet-look-distance.patch diff --git a/patches/server/0824-Fixes-and-additions-to-the-SpawnReason-API.patch b/patches/server/0824-Fixes-and-additions-to-the-SpawnReason-API.patch new file mode 100644 index 000000000000..251043d71f54 --- /dev/null +++ b/patches/server/0824-Fixes-and-additions-to-the-SpawnReason-API.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 25 Sep 2022 11:21:01 -0700 +Subject: [PATCH] Fixes and additions to the SpawnReason API + +Fixes some wrong reasons, and adds missing spawn reasons for entities. + +Co-authored-by: Doc + +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index 035af9ccf679a562203a4a2c4f2b38098c57b492..557df259ae54defb43e475e10fc4732854e64f77 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -353,7 +353,7 @@ public class EntityType implements FeatureElement, EntityTypeT + @Nullable + public T spawn(ServerLevel world, @Nullable ItemStack stack, @Nullable Player player, BlockPos pos, MobSpawnType spawnReason, boolean alignPosition, boolean invertY) { + // CraftBukkit start +- return this.spawn(world, stack, player, pos, spawnReason, alignPosition, invertY, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); ++ return this.spawn(world, stack, player, pos, spawnReason, alignPosition, invertY, spawnReason == MobSpawnType.DISPENSER ? org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG : org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // Paper - use correct spawn reason for dispenser spawn eggs + } + + @Nullable +diff --git a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java +index 61a36c4e97ee532e53eee4da00a9aa7e4b3438f5..d3b4420e664fedf86d107e819056d2e20f9c0722 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java +@@ -54,7 +54,7 @@ public class DragonFireball extends AbstractHurtingProjectile { + + if (new com.destroystokyo.paper.event.entity.EnderDragonFireballHitEvent((org.bukkit.entity.DragonFireball) this.getBukkitEntity(), list.stream().map(LivingEntity::getBukkitLivingEntity).collect(java.util.stream.Collectors.toList()), (org.bukkit.entity.AreaEffectCloud) areaEffectCloud.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events + this.level().levelEvent(2006, this.blockPosition(), this.isSilent() ? -1 : 1); +- this.level().addFreshEntity(areaEffectCloud); ++ this.level().addFreshEntity(areaEffectCloud, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EXPLOSION); // Paper - use correct spawn reason + } else areaEffectCloud.discard(); // Paper - EnderDragon Events + this.discard(); + } +diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +index 4a1f2d326c4ece9da717a50b802af86fd9338a0b..94618709e742ebe1a7893d418124e309eaab6c5f 100644 +--- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +@@ -117,7 +117,7 @@ public class FrogspawnBlock extends Block { + int k = random.nextInt(1, 361); + tadpole.moveTo(d, (double)pos.getY() - 0.5D, e, (float)k, 0.0F); + tadpole.setPersistenceRequired(); +- world.addFreshEntity(tadpole); ++ world.addFreshEntity(tadpole, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason + } + } + +diff --git a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java +index 8782b05575e4a8d5e5c667c43808442b81c13f6b..6d073e82daf41e35b63703ab9bce8c576862eb3e 100644 +--- a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java +@@ -88,7 +88,7 @@ public class SnifferEggBlock extends Block { + Vec3 vec3 = pos.getCenter(); + sniffer.setBaby(true); + sniffer.moveTo(vec3.x(), vec3.y(), vec3.z(), Mth.wrapDegrees(world.random.nextFloat() * 360.0F), 0.0F); +- world.addFreshEntity(sniffer); ++ world.addFreshEntity(sniffer, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason + } + + } diff --git a/patches/server/0825-Sync-offhand-slot-in-menus.patch b/patches/server/0825-Sync-offhand-slot-in-menus.patch deleted file mode 100644 index f2e498a9920b..000000000000 --- a/patches/server/0825-Sync-offhand-slot-in-menus.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 14 Jan 2022 10:20:40 -0800 -Subject: [PATCH] Sync offhand slot in menus - -Menus don't add slots for the offhand, so on sendAllDataToRemote calls the -offhand slot isn't sent. This is not correct because you *can* put stuff into the offhand -by pressing the offhand swap item - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 89ee8c6f2c7201a7b55b3428a6225334fe316224..c4d49ddee5f6585e0455cc364f895b00b10226bf 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -299,6 +299,13 @@ public class ServerPlayer extends Player { - - } - -+ // Paper start - Sync offhand slot in menus -+ @Override -+ public void sendOffHandSlotChange() { -+ ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(ServerPlayer.this.inventoryMenu.containerId, ServerPlayer.this.inventoryMenu.incrementStateId(), net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, ServerPlayer.this.inventoryMenu.getSlot(net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT).getItem().copy())); -+ } -+ // Paper end - Sync offhand slot in menus -+ - @Override - public void sendSlotChange(AbstractContainerMenu handler, int slot, ItemStack stack) { - ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(handler.containerId, handler.incrementStateId(), slot, stack)); -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index 399b2f4ddb7e9ef26fbc5e83f3b77c46d3868814..24caa1cf91cd50a5972238119aca1f85ec2b3d2b 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -200,6 +200,7 @@ public abstract class AbstractContainerMenu { - - if (this.synchronizer != null) { - this.synchronizer.sendInitialData(this, this.remoteSlots, this.remoteCarried, this.remoteDataSlots.toIntArray()); -+ this.synchronizer.sendOffHandSlotChange(); // Paper - Sync offhand slot in menus; update player's offhand since the offhand slot is not added to the slots for menus but can be changed by swapping from a menu slot - } - - } -diff --git a/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java b/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java -index ff4fa86f9408e83e505f7e27692d3423f8570c48..a45ef5fcffc05e4e30801b73e82d29c6dbf5b8fd 100644 ---- a/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java -+++ b/src/main/java/net/minecraft/world/inventory/ContainerSynchronizer.java -@@ -6,6 +6,7 @@ import net.minecraft.world.item.ItemStack; - public interface ContainerSynchronizer { - void sendInitialData(AbstractContainerMenu handler, NonNullList stacks, ItemStack cursorStack, int[] properties); - -+ default void sendOffHandSlotChange() {} // Paper - Sync offhand slot in menus - void sendSlotChange(AbstractContainerMenu handler, int slot, ItemStack stack); - - void sendCarriedChange(AbstractContainerMenu handler, ItemStack stack); diff --git a/patches/server/0825-fix-Instruments.patch b/patches/server/0825-fix-Instruments.patch new file mode 100644 index 000000000000..0795f113ccd1 --- /dev/null +++ b/patches/server/0825-fix-Instruments.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 9 Dec 2022 01:47:23 -0800 +Subject: [PATCH] fix Instruments + +properly handle Player#playNote + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index ca71160491f11bc805a6cb1ad432a6ba4c756931..fee318e42bac5f06825352c16f9f03168d8ce271 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -688,7 +688,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + Sound instrumentSound = instrument.getSound(); + if (instrumentSound == null) return; + +- float pitch = note.getPitch(); ++ // Paper start - use correct pitch (modeled off of NoteBlock) ++ final net.minecraft.world.level.block.state.properties.NoteBlockInstrument noteBlockInstrument = CraftBlockData.toNMS(instrument, net.minecraft.world.level.block.state.properties.NoteBlockInstrument.class); ++ final float pitch = noteBlockInstrument.isTunable() ? note.getPitch() : 1.0f; ++ // Paper end + this.getHandle().connection.send(new ClientboundSoundPacket(CraftSound.bukkitToMinecraftHolder(instrumentSound), net.minecraft.sounds.SoundSource.RECORDS, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), 3.0f, pitch, this.getHandle().getRandom().nextLong())); + } + +diff --git a/src/test/java/io/papermc/paper/block/InstrumentSoundTest.java b/src/test/java/io/papermc/paper/block/InstrumentSoundTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..28fc01045675247e75438bdc039fb8a90493419f +--- /dev/null ++++ b/src/test/java/io/papermc/paper/block/InstrumentSoundTest.java +@@ -0,0 +1,27 @@ ++package io.papermc.paper.block; ++ ++import java.util.Arrays; ++import java.util.stream.Stream; ++import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; ++import org.bukkit.Instrument; ++import org.bukkit.craftbukkit.CraftSound; ++import org.bukkit.craftbukkit.block.data.CraftBlockData; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++ ++class InstrumentSoundTest extends AbstractTestingBase { ++ ++ static Stream bukkitInstruments() { ++ return Arrays.stream(Instrument.values()).filter(i -> i.getSound() != null); ++ } ++ ++ @ParameterizedTest ++ @MethodSource("bukkitInstruments") ++ void checkInstrumentSound(final Instrument bukkit) { ++ final NoteBlockInstrument nms = CraftBlockData.toNMS(bukkit, NoteBlockInstrument.class); ++ assertEquals(nms.getSoundEvent(), CraftSound.bukkitToMinecraftHolder(bukkit.getSound())); ++ } ++} diff --git a/patches/server/0830-Improve-inlining-for-some-hot-BlockBehavior-and-Flui.patch b/patches/server/0826-Improve-inlining-for-some-hot-BlockBehavior-and-Flui.patch similarity index 100% rename from patches/server/0830-Improve-inlining-for-some-hot-BlockBehavior-and-Flui.patch rename to patches/server/0826-Improve-inlining-for-some-hot-BlockBehavior-and-Flui.patch diff --git a/patches/server/0826-Player-Entity-Tracking-Events.patch b/patches/server/0826-Player-Entity-Tracking-Events.patch deleted file mode 100644 index 8239b8c4390c..000000000000 --- a/patches/server/0826-Player-Entity-Tracking-Events.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Yannick Lamprecht -Date: Wed, 30 Mar 2022 18:16:52 +0200 -Subject: [PATCH] Player Entity Tracking Events - - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index d1ce32936f3b581a4e60582b4f5a433db49d380a..c56136c785e9239df5f8782fae67edb6e52917f4 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1749,7 +1749,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // CraftBukkit end - if (flag) { - if (this.seenBy.add(player.connection)) { -+ // Paper start - entity tracking events -+ if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { - this.serverEntity.addPairing(player); -+ } -+ // Paper end - entity tracking events - } - } else if (this.seenBy.remove(player.connection)) { - this.serverEntity.removePairing(player); -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index e3bea8fe8b04f47c5432f957f6364fedcead6e3f..b7263260ab1c0a17d05959bb0d30deeb8e645199 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3818,7 +3818,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - - public void startSeenByPlayer(ServerPlayer player) {} - -- public void stopSeenByPlayer(ServerPlayer player) {} -+ // Paper start - entity tracking events -+ public void stopSeenByPlayer(ServerPlayer player) { -+ // Since this event cannot be cancelled, we should call it here to catch all "un-tracks" -+ if (io.papermc.paper.event.player.PlayerUntrackEntityEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ new io.papermc.paper.event.player.PlayerUntrackEntityEvent(player.getBukkitEntity(), this.getBukkitEntity()).callEvent(); -+ } -+ } -+ // Paper end - entity tracking events - - public float rotate(Rotation rotation) { - float f = Mth.wrapDegrees(this.getYRot()); diff --git a/patches/server/0831-Fix-inconsistencies-in-dispense-events-regarding-sta.patch b/patches/server/0827-Fix-inconsistencies-in-dispense-events-regarding-sta.patch similarity index 100% rename from patches/server/0831-Fix-inconsistencies-in-dispense-events-regarding-sta.patch rename to patches/server/0827-Fix-inconsistencies-in-dispense-events-regarding-sta.patch diff --git a/patches/server/0828-Add-BlockLockCheckEvent.patch b/patches/server/0828-Add-BlockLockCheckEvent.patch new file mode 100644 index 000000000000..3ac83356d347 --- /dev/null +++ b/patches/server/0828-Add-BlockLockCheckEvent.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 21 May 2022 20:59:45 -0700 +Subject: [PATCH] Add BlockLockCheckEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +index fce3a45d09a93ca68a3d49f2e666afa4c860d042..b8b4d74076fa5ed6eb3b2045384db77e165931b2 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +@@ -69,17 +69,44 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co + protected abstract Component getDefaultName(); + + public boolean canOpen(Player player) { +- return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName()); ++ return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this); // Paper - Add BlockLockCheckEvent + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add BlockLockCheckEvent + public static boolean canUnlock(Player player, LockCode lock, Component containerName) { ++ // Paper start - Add BlockLockCheckEvent ++ return canUnlock(player, lock, containerName, null); ++ } ++ public static boolean canUnlock(Player player, LockCode lock, Component containerName, @Nullable BlockEntity blockEntity) { ++ if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != null && blockEntity.getLevel().getBlockEntity(blockEntity.getBlockPos()) == blockEntity) { ++ final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(blockEntity.getLevel(), blockEntity.getBlockPos()); ++ net.kyori.adventure.text.Component lockedMessage = net.kyori.adventure.text.Component.translatable("container.isLocked", io.papermc.paper.adventure.PaperAdventure.asAdventure(containerName)); ++ net.kyori.adventure.sound.Sound lockedSound = net.kyori.adventure.sound.Sound.sound(org.bukkit.Sound.BLOCK_CHEST_LOCKED, net.kyori.adventure.sound.Sound.Source.BLOCK, 1.0F, 1.0F); ++ final io.papermc.paper.event.block.BlockLockCheckEvent event = new io.papermc.paper.event.block.BlockLockCheckEvent(block, (io.papermc.paper.block.LockableTileState) block.getState(), serverPlayer.getBukkitEntity(), lockedMessage, lockedSound); ++ event.callEvent(); ++ if (event.getResult() == org.bukkit.event.Event.Result.ALLOW) { ++ return true; ++ } else if (event.getResult() == org.bukkit.event.Event.Result.DENY || (!player.isSpectator() && !lock.unlocksWith(event.isUsingCustomKeyItemStack() ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getKeyItem()) : player.getMainHandItem()))) { ++ if (event.getLockedMessage() != null) { ++ event.getPlayer().sendActionBar(event.getLockedMessage()); ++ } ++ if (event.getLockedSound() != null) { ++ event.getPlayer().playSound(event.getLockedSound()); ++ } ++ return false; ++ } else { ++ return true; ++ } ++ } else { // logic below is replaced by logic above ++ // Paper end - Add BlockLockCheckEvent + if (!player.isSpectator() && !lock.unlocksWith(player.getMainHandItem())) { +- player.displayClientMessage(Component.translatable("container.isLocked", containerName), true); ++ player.displayClientMessage(Component.translatable("container.isLocked", containerName), true); // Paper - diff on change + player.playNotifySound(SoundEvents.CHEST_LOCKED, SoundSource.BLOCKS, 1.0F, 1.0F); + return false; + } else { + return true; + } ++ } // Paper - Add BlockLockCheckEvent + } + + @Nullable +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index 247f24c7fadc203ee0f6a6f85122c91ab4c82f80..eff48e43a35a752bd33de2b55a0ad04332109ce0 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -470,7 +470,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + @Nullable + @Override + public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { +- return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName()) ? new BeaconMenu(syncId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos())) : null; ++ return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this) ? new BeaconMenu(syncId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos())) : null; // Paper - Add BlockLockCheckEvent + } + + @Override diff --git a/patches/server/0828-Fixes-and-additions-to-the-SpawnReason-API.patch b/patches/server/0828-Fixes-and-additions-to-the-SpawnReason-API.patch deleted file mode 100644 index 4169921a245a..000000000000 --- a/patches/server/0828-Fixes-and-additions-to-the-SpawnReason-API.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 25 Sep 2022 11:21:01 -0700 -Subject: [PATCH] Fixes and additions to the SpawnReason API - -Fixes some wrong reasons, and adds missing spawn reasons for entities. - -Co-authored-by: Doc - -diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index bd4ae65070eb4b98dae0529b5985f80093bf8185..dcdc982b22b7fc836e6ad423a75c75c1d33eb7d8 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -353,7 +353,7 @@ public class EntityType implements FeatureElement, EntityTypeT - @Nullable - public T spawn(ServerLevel world, @Nullable ItemStack stack, @Nullable Player player, BlockPos pos, MobSpawnType spawnReason, boolean alignPosition, boolean invertY) { - // CraftBukkit start -- return this.spawn(world, stack, player, pos, spawnReason, alignPosition, invertY, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); -+ return this.spawn(world, stack, player, pos, spawnReason, alignPosition, invertY, spawnReason == MobSpawnType.DISPENSER ? org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DISPENSE_EGG : org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // Paper - use correct spawn reason for dispenser spawn eggs - } - - @Nullable -diff --git a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java -index 61a36c4e97ee532e53eee4da00a9aa7e4b3438f5..d3b4420e664fedf86d107e819056d2e20f9c0722 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/DragonFireball.java -@@ -54,7 +54,7 @@ public class DragonFireball extends AbstractHurtingProjectile { - - if (new com.destroystokyo.paper.event.entity.EnderDragonFireballHitEvent((org.bukkit.entity.DragonFireball) this.getBukkitEntity(), list.stream().map(LivingEntity::getBukkitLivingEntity).collect(java.util.stream.Collectors.toList()), (org.bukkit.entity.AreaEffectCloud) areaEffectCloud.getBukkitEntity()).callEvent()) { // Paper - EnderDragon Events - this.level().levelEvent(2006, this.blockPosition(), this.isSilent() ? -1 : 1); -- this.level().addFreshEntity(areaEffectCloud); -+ this.level().addFreshEntity(areaEffectCloud, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EXPLOSION); // Paper - use correct spawn reason - } else areaEffectCloud.discard(); // Paper - EnderDragon Events - this.discard(); - } -diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -index 4a1f2d326c4ece9da717a50b802af86fd9338a0b..94618709e742ebe1a7893d418124e309eaab6c5f 100644 ---- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -@@ -117,7 +117,7 @@ public class FrogspawnBlock extends Block { - int k = random.nextInt(1, 361); - tadpole.moveTo(d, (double)pos.getY() - 0.5D, e, (float)k, 0.0F); - tadpole.setPersistenceRequired(); -- world.addFreshEntity(tadpole); -+ world.addFreshEntity(tadpole, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason - } - } - -diff --git a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -index 8782b05575e4a8d5e5c667c43808442b81c13f6b..6d073e82daf41e35b63703ab9bce8c576862eb3e 100644 ---- a/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SnifferEggBlock.java -@@ -88,7 +88,7 @@ public class SnifferEggBlock extends Block { - Vec3 vec3 = pos.getCenter(); - sniffer.setBaby(true); - sniffer.moveTo(vec3.x(), vec3.y(), vec3.z(), Mth.wrapDegrees(world.random.nextFloat() * 360.0F), 0.0F); -- world.addFreshEntity(sniffer); -+ world.addFreshEntity(sniffer, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - use correct spawn reason - } - - } diff --git a/patches/server/0833-Add-Sneaking-API-for-Entities.patch b/patches/server/0829-Add-Sneaking-API-for-Entities.patch similarity index 100% rename from patches/server/0833-Add-Sneaking-API-for-Entities.patch rename to patches/server/0829-Add-Sneaking-API-for-Entities.patch diff --git a/patches/server/0829-fix-Instruments.patch b/patches/server/0829-fix-Instruments.patch deleted file mode 100644 index a69fc686707f..000000000000 --- a/patches/server/0829-fix-Instruments.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 9 Dec 2022 01:47:23 -0800 -Subject: [PATCH] fix Instruments - -properly handle Player#playNote - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 5823c8d3e9c2fa15ad0f062b1a5fe0286b64c712..3f8f928b61ff48e403d7a391343e62fbedf5826d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -682,7 +682,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - Sound instrumentSound = instrument.getSound(); - if (instrumentSound == null) return; - -- float pitch = note.getPitch(); -+ // Paper start - use correct pitch (modeled off of NoteBlock) -+ final net.minecraft.world.level.block.state.properties.NoteBlockInstrument noteBlockInstrument = CraftBlockData.toNMS(instrument, net.minecraft.world.level.block.state.properties.NoteBlockInstrument.class); -+ final float pitch = noteBlockInstrument.isTunable() ? note.getPitch() : 1.0f; -+ // Paper end - this.getHandle().connection.send(new ClientboundSoundPacket(CraftSound.bukkitToMinecraftHolder(instrumentSound), net.minecraft.sounds.SoundSource.RECORDS, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), 3.0f, pitch, this.getHandle().getRandom().nextLong())); - } - -diff --git a/src/test/java/io/papermc/paper/block/InstrumentSoundTest.java b/src/test/java/io/papermc/paper/block/InstrumentSoundTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..28fc01045675247e75438bdc039fb8a90493419f ---- /dev/null -+++ b/src/test/java/io/papermc/paper/block/InstrumentSoundTest.java -@@ -0,0 +1,27 @@ -+package io.papermc.paper.block; -+ -+import java.util.Arrays; -+import java.util.stream.Stream; -+import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; -+import org.bukkit.Instrument; -+import org.bukkit.craftbukkit.CraftSound; -+import org.bukkit.craftbukkit.block.data.CraftBlockData; -+import org.bukkit.support.AbstractTestingBase; -+import org.junit.jupiter.params.ParameterizedTest; -+import org.junit.jupiter.params.provider.MethodSource; -+ -+import static org.junit.jupiter.api.Assertions.assertEquals; -+ -+class InstrumentSoundTest extends AbstractTestingBase { -+ -+ static Stream bukkitInstruments() { -+ return Arrays.stream(Instrument.values()).filter(i -> i.getSound() != null); -+ } -+ -+ @ParameterizedTest -+ @MethodSource("bukkitInstruments") -+ void checkInstrumentSound(final Instrument bukkit) { -+ final NoteBlockInstrument nms = CraftBlockData.toNMS(bukkit, NoteBlockInstrument.class); -+ assertEquals(nms.getSoundEvent(), CraftSound.bukkitToMinecraftHolder(bukkit.getSound())); -+ } -+} diff --git a/patches/server/0830-Improve-logging-and-errors.patch b/patches/server/0830-Improve-logging-and-errors.patch new file mode 100644 index 000000000000..04779e43c29a --- /dev/null +++ b/patches/server/0830-Improve-logging-and-errors.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 14 Dec 2022 15:52:11 -0800 +Subject: [PATCH] Improve logging and errors + +Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> + +diff --git a/src/main/java/net/minecraft/advancements/AdvancementTree.java b/src/main/java/net/minecraft/advancements/AdvancementTree.java +index 938fe76677139e7e99698b61691bfcadf70dbd87..a017ebf550e3430c14a7159baa9a644530a0b5ab 100644 +--- a/src/main/java/net/minecraft/advancements/AdvancementTree.java ++++ b/src/main/java/net/minecraft/advancements/AdvancementTree.java +@@ -35,7 +35,7 @@ public class AdvancementTree { + this.remove(advancementnode1); + } + +- AdvancementTree.LOGGER.info("Forgot about advancement {}", advancement.holder()); ++ AdvancementTree.LOGGER.debug("Forgot about advancement {}", advancement.holder()); // Paper - Improve logging and errors + this.nodes.remove(advancement.holder().id()); + if (advancement.parent() == null) { + this.roots.remove(advancement); +@@ -77,7 +77,7 @@ public class AdvancementTree { + } + } + +- // AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size()); // CraftBukkit - moved to AdvancementDataWorld#reload ++ // AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size()); // CraftBukkit - moved to AdvancementDataWorld#reload // Paper - Improve logging and errors; you say it was moved... but it wasn't :) it should be moved however, since this is called when the API creates an advancement + } + + private boolean tryInsert(AdvancementHolder advancement) { +diff --git a/src/main/java/net/minecraft/server/ServerAdvancementManager.java b/src/main/java/net/minecraft/server/ServerAdvancementManager.java +index 536f0c496ce36ca3248fc6eeac9bbd77214a36f9..31718823250a1490b783f426fff65bf5a067b6f4 100644 +--- a/src/main/java/net/minecraft/server/ServerAdvancementManager.java ++++ b/src/main/java/net/minecraft/server/ServerAdvancementManager.java +@@ -66,6 +66,7 @@ public class ServerAdvancementManager extends SimpleJsonResourceReloadListener { + AdvancementTree advancementtree = new AdvancementTree(); + + advancementtree.addAll(this.advancements.values()); ++ LOGGER.info("Loaded {} advancements", advancementtree.nodes().size()); // Paper - Improve logging and errors; moved from AdvancementTree#addAll + Iterator iterator = advancementtree.roots().iterator(); + + while (iterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 5675931fe3ea896027a510944fc484f41f5ef555..1b38d267daa7902bcb7d2a71d28b3f39da722ad1 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -265,6 +265,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!"); + DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString()); + DedicatedServer.LOGGER.warn("Perhaps a server is already running on that port?"); ++ if (true) throw new IllegalStateException("Failed to bind to port", ioexception); // Paper - Propagate failed to bind to port error + return false; + } + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 1eae23b70659b729aaec9d1b5b4dedbefb30a29f..a72af814b81ba3ca82476da47d68ae344bb2b63c 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3305,7 +3305,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + this.resetPlayerChatState(remotechatsession_a.validate(this.player.getGameProfile(), signaturevalidator)); + } catch (ProfilePublicKey.ValidationException profilepublickey_b) { +- ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); ++ // ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); // Paper - Improve logging and errors + this.disconnect(profilepublickey_b.getComponent(), profilepublickey_b.kickCause); // Paper - kick event causes + } + +diff --git a/src/main/java/net/minecraft/server/packs/PathPackResources.java b/src/main/java/net/minecraft/server/packs/PathPackResources.java +index 89aa86a49eda563c82ccedc99641e699f8e578b0..3edd14ce90edf98798b89921ad18547809de5d2c 100644 +--- a/src/main/java/net/minecraft/server/packs/PathPackResources.java ++++ b/src/main/java/net/minecraft/server/packs/PathPackResources.java +@@ -108,6 +108,12 @@ public class PathPackResources extends AbstractPackResources { + try (DirectoryStream directoryStream = Files.newDirectoryStream(path)) { + for(Path path2 : directoryStream) { + String string = path2.getFileName().toString(); ++ // Paper start - Improve logging and errors ++ if (!Files.isDirectory(path2)) { ++ LOGGER.error("Invalid directory entry: {} in {}.", string, this.root, new java.nio.file.NotDirectoryException(string)); ++ continue; ++ } ++ // Paper end - Improve logging and errors + if (ResourceLocation.isValidNamespace(string)) { + set.add(string); + } else { +diff --git a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java +index 4259181bab2dc4f2d0409b56fdf81d966003376d..a0ab3c55826af292d1cdac05648139b4d31f1376 100644 +--- a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java ++++ b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java +@@ -84,7 +84,7 @@ public class RecipeManager extends SimpleJsonResourceReloadListener { + return entry1.getValue(); // CraftBukkit // Paper - decompile fix - *shrugs internally* // todo: is this needed anymore? + })); + this.byName = Maps.newHashMap(builder.build()); // CraftBukkit +- RecipeManager.LOGGER.info("Loaded {} recipes", map1.size()); ++ RecipeManager.LOGGER.info("Loaded {} recipes", this.byName.size()); // Paper - Improve logging and errors; log correct number of recipes + } + + // CraftBukkit start +diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java +index 080cca90f15d90249b7a38f33286ae2f735ba7d9..fde9aadd6c688b9797a6755f9d214918047598a0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java ++++ b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java +@@ -44,6 +44,7 @@ import org.bukkit.material.MaterialData; + */ + @Deprecated + public final class CraftLegacy { ++ private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper - Improve logging and errors + + private static final Map SPAWN_EGGS = new HashMap<>(); + private static final Set whitelistedStates = new HashSet<>(Arrays.asList("explode", "check_decay", "decayable", "facing")); +@@ -255,7 +256,7 @@ public final class CraftLegacy { + } + + static { +- System.err.println("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!"); ++ LOGGER.warn("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!"); // Paper - Improve logging and errors; doesn't need to be an error + if (MinecraftServer.getServer() != null && MinecraftServer.getServer().isDebugging()) { + new Exception().printStackTrace(); + } diff --git a/patches/server/0831-Improve-PortalEvents.patch b/patches/server/0831-Improve-PortalEvents.patch new file mode 100644 index 000000000000..958eba1398c5 --- /dev/null +++ b/patches/server/0831-Improve-PortalEvents.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 15 Dec 2022 10:33:39 -0800 +Subject: [PATCH] Improve PortalEvents + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 425e53aaf09618f6e44b9d34ed3412a8f180ac39..61abb57036199e8d4580cc4e368cdacd9dba58fd 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3479,7 +3479,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + Location enter = bukkitEntity.getLocation(); + Location exit = CraftLocation.toBukkit(exitPosition, exitWorldServer.getWorld()); + +- EntityPortalEvent event = new EntityPortalEvent(bukkitEntity, enter, exit, searchRadius); ++ // Paper start ++ final org.bukkit.PortalType portalType = switch (cause) { ++ case END_PORTAL -> org.bukkit.PortalType.ENDER; ++ case NETHER_PORTAL -> org.bukkit.PortalType.NETHER; ++ default -> org.bukkit.PortalType.CUSTOM; ++ }; ++ EntityPortalEvent event = new EntityPortalEvent(bukkitEntity, enter, exit, searchRadius, portalType); ++ // Paper end + event.getEntity().getServer().getPluginManager().callEvent(event); + if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null || !entity.isAlive()) { + return null; diff --git a/patches/server/0832-Add-BlockLockCheckEvent.patch b/patches/server/0832-Add-BlockLockCheckEvent.patch deleted file mode 100644 index 7ae413510504..000000000000 --- a/patches/server/0832-Add-BlockLockCheckEvent.patch +++ /dev/null @@ -1,70 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 21 May 2022 20:59:45 -0700 -Subject: [PATCH] Add BlockLockCheckEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java -index fce3a45d09a93ca68a3d49f2e666afa4c860d042..b8b4d74076fa5ed6eb3b2045384db77e165931b2 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java -@@ -69,17 +69,44 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co - protected abstract Component getDefaultName(); - - public boolean canOpen(Player player) { -- return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName()); -+ return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this); // Paper - Add BlockLockCheckEvent - } - -+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add BlockLockCheckEvent - public static boolean canUnlock(Player player, LockCode lock, Component containerName) { -+ // Paper start - Add BlockLockCheckEvent -+ return canUnlock(player, lock, containerName, null); -+ } -+ public static boolean canUnlock(Player player, LockCode lock, Component containerName, @Nullable BlockEntity blockEntity) { -+ if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != null && blockEntity.getLevel().getBlockEntity(blockEntity.getBlockPos()) == blockEntity) { -+ final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(blockEntity.getLevel(), blockEntity.getBlockPos()); -+ net.kyori.adventure.text.Component lockedMessage = net.kyori.adventure.text.Component.translatable("container.isLocked", io.papermc.paper.adventure.PaperAdventure.asAdventure(containerName)); -+ net.kyori.adventure.sound.Sound lockedSound = net.kyori.adventure.sound.Sound.sound(org.bukkit.Sound.BLOCK_CHEST_LOCKED, net.kyori.adventure.sound.Sound.Source.BLOCK, 1.0F, 1.0F); -+ final io.papermc.paper.event.block.BlockLockCheckEvent event = new io.papermc.paper.event.block.BlockLockCheckEvent(block, (io.papermc.paper.block.LockableTileState) block.getState(), serverPlayer.getBukkitEntity(), lockedMessage, lockedSound); -+ event.callEvent(); -+ if (event.getResult() == org.bukkit.event.Event.Result.ALLOW) { -+ return true; -+ } else if (event.getResult() == org.bukkit.event.Event.Result.DENY || (!player.isSpectator() && !lock.unlocksWith(event.isUsingCustomKeyItemStack() ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getKeyItem()) : player.getMainHandItem()))) { -+ if (event.getLockedMessage() != null) { -+ event.getPlayer().sendActionBar(event.getLockedMessage()); -+ } -+ if (event.getLockedSound() != null) { -+ event.getPlayer().playSound(event.getLockedSound()); -+ } -+ return false; -+ } else { -+ return true; -+ } -+ } else { // logic below is replaced by logic above -+ // Paper end - Add BlockLockCheckEvent - if (!player.isSpectator() && !lock.unlocksWith(player.getMainHandItem())) { -- player.displayClientMessage(Component.translatable("container.isLocked", containerName), true); -+ player.displayClientMessage(Component.translatable("container.isLocked", containerName), true); // Paper - diff on change - player.playNotifySound(SoundEvents.CHEST_LOCKED, SoundSource.BLOCKS, 1.0F, 1.0F); - return false; - } else { - return true; - } -+ } // Paper - Add BlockLockCheckEvent - } - - @Nullable -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index ae9c69176168e53f6270a0bc02240d190630d015..dbcac8b787e9a18193723b5311fe882060397dc6 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -470,7 +470,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - @Nullable - @Override - public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { -- return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName()) ? new BeaconMenu(syncId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos())) : null; -+ return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this) ? new BeaconMenu(syncId, playerInventory, this.dataAccess, ContainerLevelAccess.create(this.level, this.getBlockPos())) : null; // Paper - Add BlockLockCheckEvent - } - - @Override diff --git a/patches/server/0836-Add-config-option-for-spider-worldborder-climbing.patch b/patches/server/0832-Add-config-option-for-spider-worldborder-climbing.patch similarity index 100% rename from patches/server/0836-Add-config-option-for-spider-worldborder-climbing.patch rename to patches/server/0832-Add-config-option-for-spider-worldborder-climbing.patch diff --git a/patches/server/0833-Add-missing-SpigotConfig-logCommands-check.patch b/patches/server/0833-Add-missing-SpigotConfig-logCommands-check.patch new file mode 100644 index 000000000000..c7b08d7fbe2a --- /dev/null +++ b/patches/server/0833-Add-missing-SpigotConfig-logCommands-check.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NonSwag +Date: Thu, 8 Dec 2022 20:25:05 +0100 +Subject: [PATCH] Add missing SpigotConfig logCommands check + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index a72af814b81ba3ca82476da47d68ae344bb2b63c..6b215d271a884bf60bbda5abc46657ddafdca1dc 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2060,7 +2060,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + private void performChatCommand(ServerboundChatCommandPacket packet, LastSeenMessages lastSeenMessages) { + // CraftBukkit start + String command = "/" + packet.command(); ++ if (org.spigotmc.SpigotConfig.logCommands) { // Paper - Add missing SpigotConfig logCommands check + ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command); ++ } // Paper - Add missing SpigotConfig logCommands check + + PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command, new LazyPlayerSet(this.server)); + this.cserver.getPluginManager().callEvent(event); diff --git a/patches/server/0838-Fix-NPE-on-Allay-stopDancing-while-not-dancing.patch b/patches/server/0834-Fix-NPE-on-Allay-stopDancing-while-not-dancing.patch similarity index 100% rename from patches/server/0838-Fix-NPE-on-Allay-stopDancing-while-not-dancing.patch rename to patches/server/0834-Fix-NPE-on-Allay-stopDancing-while-not-dancing.patch diff --git a/patches/server/0834-Improve-logging-and-errors.patch b/patches/server/0834-Improve-logging-and-errors.patch deleted file mode 100644 index 142ab3d7fe07..000000000000 --- a/patches/server/0834-Improve-logging-and-errors.patch +++ /dev/null @@ -1,117 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 14 Dec 2022 15:52:11 -0800 -Subject: [PATCH] Improve logging and errors - -Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> - -diff --git a/src/main/java/net/minecraft/advancements/AdvancementTree.java b/src/main/java/net/minecraft/advancements/AdvancementTree.java -index 938fe76677139e7e99698b61691bfcadf70dbd87..a017ebf550e3430c14a7159baa9a644530a0b5ab 100644 ---- a/src/main/java/net/minecraft/advancements/AdvancementTree.java -+++ b/src/main/java/net/minecraft/advancements/AdvancementTree.java -@@ -35,7 +35,7 @@ public class AdvancementTree { - this.remove(advancementnode1); - } - -- AdvancementTree.LOGGER.info("Forgot about advancement {}", advancement.holder()); -+ AdvancementTree.LOGGER.debug("Forgot about advancement {}", advancement.holder()); // Paper - Improve logging and errors - this.nodes.remove(advancement.holder().id()); - if (advancement.parent() == null) { - this.roots.remove(advancement); -@@ -77,7 +77,7 @@ public class AdvancementTree { - } - } - -- // AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size()); // CraftBukkit - moved to AdvancementDataWorld#reload -+ // AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size()); // CraftBukkit - moved to AdvancementDataWorld#reload // Paper - Improve logging and errors; you say it was moved... but it wasn't :) it should be moved however, since this is called when the API creates an advancement - } - - private boolean tryInsert(AdvancementHolder advancement) { -diff --git a/src/main/java/net/minecraft/server/ServerAdvancementManager.java b/src/main/java/net/minecraft/server/ServerAdvancementManager.java -index 536f0c496ce36ca3248fc6eeac9bbd77214a36f9..31718823250a1490b783f426fff65bf5a067b6f4 100644 ---- a/src/main/java/net/minecraft/server/ServerAdvancementManager.java -+++ b/src/main/java/net/minecraft/server/ServerAdvancementManager.java -@@ -66,6 +66,7 @@ public class ServerAdvancementManager extends SimpleJsonResourceReloadListener { - AdvancementTree advancementtree = new AdvancementTree(); - - advancementtree.addAll(this.advancements.values()); -+ LOGGER.info("Loaded {} advancements", advancementtree.nodes().size()); // Paper - Improve logging and errors; moved from AdvancementTree#addAll - Iterator iterator = advancementtree.roots().iterator(); - - while (iterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index c666bcd29d39ee7bca05edac348b7fa0325e80ab..0c2145c66369a950567ddfbf157067ca7cb4dd11 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -265,6 +265,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!"); - DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString()); - DedicatedServer.LOGGER.warn("Perhaps a server is already running on that port?"); -+ if (true) throw new IllegalStateException("Failed to bind to port", ioexception); // Paper - Propagate failed to bind to port error - return false; - } - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 17c059020eae2c125971b4f6ffe90db46fb85cd2..6e1f1bd674e02426f42d013e2b0995d45e37ace0 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3305,7 +3305,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - this.resetPlayerChatState(remotechatsession_a.validate(this.player.getGameProfile(), signaturevalidator)); - } catch (ProfilePublicKey.ValidationException profilepublickey_b) { -- ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); -+ // ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); // Paper - Improve logging and errors - this.disconnect(profilepublickey_b.getComponent(), profilepublickey_b.kickCause); // Paper - kick event causes - } - -diff --git a/src/main/java/net/minecraft/server/packs/PathPackResources.java b/src/main/java/net/minecraft/server/packs/PathPackResources.java -index 89aa86a49eda563c82ccedc99641e699f8e578b0..3edd14ce90edf98798b89921ad18547809de5d2c 100644 ---- a/src/main/java/net/minecraft/server/packs/PathPackResources.java -+++ b/src/main/java/net/minecraft/server/packs/PathPackResources.java -@@ -108,6 +108,12 @@ public class PathPackResources extends AbstractPackResources { - try (DirectoryStream directoryStream = Files.newDirectoryStream(path)) { - for(Path path2 : directoryStream) { - String string = path2.getFileName().toString(); -+ // Paper start - Improve logging and errors -+ if (!Files.isDirectory(path2)) { -+ LOGGER.error("Invalid directory entry: {} in {}.", string, this.root, new java.nio.file.NotDirectoryException(string)); -+ continue; -+ } -+ // Paper end - Improve logging and errors - if (ResourceLocation.isValidNamespace(string)) { - set.add(string); - } else { -diff --git a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java -index 4259181bab2dc4f2d0409b56fdf81d966003376d..a0ab3c55826af292d1cdac05648139b4d31f1376 100644 ---- a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java -+++ b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java -@@ -84,7 +84,7 @@ public class RecipeManager extends SimpleJsonResourceReloadListener { - return entry1.getValue(); // CraftBukkit // Paper - decompile fix - *shrugs internally* // todo: is this needed anymore? - })); - this.byName = Maps.newHashMap(builder.build()); // CraftBukkit -- RecipeManager.LOGGER.info("Loaded {} recipes", map1.size()); -+ RecipeManager.LOGGER.info("Loaded {} recipes", this.byName.size()); // Paper - Improve logging and errors; log correct number of recipes - } - - // CraftBukkit start -diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java -index 080cca90f15d90249b7a38f33286ae2f735ba7d9..fde9aadd6c688b9797a6755f9d214918047598a0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java -+++ b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java -@@ -44,6 +44,7 @@ import org.bukkit.material.MaterialData; - */ - @Deprecated - public final class CraftLegacy { -+ private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper - Improve logging and errors - - private static final Map SPAWN_EGGS = new HashMap<>(); - private static final Set whitelistedStates = new HashSet<>(Arrays.asList("explode", "check_decay", "decayable", "facing")); -@@ -255,7 +256,7 @@ public final class CraftLegacy { - } - - static { -- System.err.println("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!"); -+ LOGGER.warn("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!"); // Paper - Improve logging and errors; doesn't need to be an error - if (MinecraftServer.getServer() != null && MinecraftServer.getServer().isDebugging()) { - new Exception().printStackTrace(); - } diff --git a/patches/server/0835-Flying-Fall-Damage.patch b/patches/server/0835-Flying-Fall-Damage.patch new file mode 100644 index 000000000000..0637a3124097 --- /dev/null +++ b/patches/server/0835-Flying-Fall-Damage.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TreyRuffy +Date: Fri, 27 May 2022 02:26:08 -0600 +Subject: [PATCH] Flying Fall Damage + + +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 b3a91d7ad2095485006ef1ee7f07c474269ae455..f32708ed934bbbf2416e32fdddc7bb4329958cf6 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -181,6 +181,7 @@ public abstract class Player extends LivingEntity { + public FishingHook fishing; + public float hurtDir; // Paper - protected -> public + public boolean affectsSpawning = true; // Paper - Affects Spawning API ++ public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage + + // CraftBukkit start + public boolean fauxSleeping; +@@ -1672,7 +1673,7 @@ public abstract class Player extends LivingEntity { + + @Override + public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { +- if (this.abilities.mayfly) { ++ if (this.abilities.mayfly && !this.flyingFallDamage.toBooleanOrElse(false)) { // Paper - flying fall damage + return false; + } else { + if (fallDistance >= 2.0F) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index fee318e42bac5f06825352c16f9f03168d8ce271..4f62bd701e2645ef37bcfda851723c6b51f5f3d5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2498,6 +2498,19 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.getHandle().onUpdateAbilities(); + } + ++ // Paper start - flying fall damage ++ @Override ++ public void setFlyingFallDamage(@NotNull net.kyori.adventure.util.TriState flyingFallDamage) { ++ getHandle().flyingFallDamage = flyingFallDamage; ++ } ++ ++ @NotNull ++ @Override ++ public net.kyori.adventure.util.TriState hasFlyingFallDamage() { ++ return getHandle().flyingFallDamage; ++ } ++ // Paper end - flying fall damage ++ + @Override + public int getNoDamageTicks() { + if (this.getHandle().spawnInvulnerableTime > 0) { diff --git a/patches/server/0835-Improve-PortalEvents.patch b/patches/server/0835-Improve-PortalEvents.patch deleted file mode 100644 index 7c24354836f7..000000000000 --- a/patches/server/0835-Improve-PortalEvents.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 15 Dec 2022 10:33:39 -0800 -Subject: [PATCH] Improve PortalEvents - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index bbf4c314776df09659ba4504b2d0cb90ae4e34d9..57d08daa6ca215b604a3f80db17b78f4bd4f822b 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3476,7 +3476,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - Location enter = bukkitEntity.getLocation(); - Location exit = CraftLocation.toBukkit(exitPosition, exitWorldServer.getWorld()); - -- EntityPortalEvent event = new EntityPortalEvent(bukkitEntity, enter, exit, searchRadius); -+ // Paper start -+ final org.bukkit.PortalType portalType = switch (cause) { -+ case END_PORTAL -> org.bukkit.PortalType.ENDER; -+ case NETHER_PORTAL -> org.bukkit.PortalType.NETHER; -+ default -> org.bukkit.PortalType.CUSTOM; -+ }; -+ EntityPortalEvent event = new EntityPortalEvent(bukkitEntity, enter, exit, searchRadius, portalType); -+ // Paper end - event.getEntity().getServer().getPluginManager().callEvent(event); - if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null || !entity.isAlive()) { - return null; diff --git a/patches/server/0836-Add-exploded-block-state-to-BlockExplodeEvent-and-En.patch b/patches/server/0836-Add-exploded-block-state-to-BlockExplodeEvent-and-En.patch new file mode 100644 index 000000000000..5b9b2afea92b --- /dev/null +++ b/patches/server/0836-Add-exploded-block-state-to-BlockExplodeEvent-and-En.patch @@ -0,0 +1,160 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 22 Oct 2021 16:25:07 -0700 +Subject: [PATCH] Add exploded block state to BlockExplodeEvent and + EntityDamageByBlockEvent + + +diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java +index 8187feffe52efa5c887f1910e581a37c6e439401..fe9b45bfc3d000956f6de5594bf5732fa0e6bb08 100644 +--- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java ++++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java +@@ -20,6 +20,7 @@ public class DamageSource { + private final Entity directEntity; + @Nullable + private final Vec3 damageSourcePosition; ++ public org.bukkit.block.BlockState explodedBlockState; // Paper - add exploded state + // CraftBukkit start + @Nullable + private org.bukkit.block.Block directBlock; // The block that caused the damage. damageSourcePosition is not used for all block damages +diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSources.java b/src/main/java/net/minecraft/world/damagesource/DamageSources.java +index 8c0653012192144cd11c802d1ad9bf7e42e94f59..a47473c9875c70c52b9a61e0156e55961f34c694 100644 +--- a/src/main/java/net/minecraft/world/damagesource/DamageSources.java ++++ b/src/main/java/net/minecraft/world/damagesource/DamageSources.java +@@ -257,8 +257,17 @@ public class DamageSources { + return this.source(DamageTypes.SONIC_BOOM, attacker); + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - add exploded state + public DamageSource badRespawnPointExplosion(Vec3 position) { +- return new DamageSource(this.damageTypes.getHolderOrThrow(DamageTypes.BAD_RESPAWN_POINT), position); ++ // Paper start - add exploded state ++ return this.badRespawnPointExplosion(position, null); ++ } ++ ++ public DamageSource badRespawnPointExplosion(Vec3 position, @Nullable org.bukkit.block.BlockState explodedBlockState) { ++ DamageSource source = new DamageSource(this.damageTypes.getHolderOrThrow(DamageTypes.BAD_RESPAWN_POINT), position); ++ source.explodedBlockState = explodedBlockState; ++ return source; ++ // Paper end - add exploded state + } + + public DamageSource outOfBorder() { +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index cd939ab6958e8eb632056d32f68e2fcae7735d64..32775780df3e6f34961119f10c81462c0f729045 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -373,7 +373,7 @@ public class Explosion { + bukkitBlocks = event.blockList(); + this.yield = event.getYield(); + } else { +- BlockExplodeEvent event = new BlockExplodeEvent(location.getBlock(), blockList, this.yield); ++ BlockExplodeEvent event = new BlockExplodeEvent(location.getBlock(), blockList, this.yield, this.damageSource.explodedBlockState); // Paper - add exploded state + this.level.getCraftServer().getPluginManager().callEvent(event); + this.wasCanceled = event.isCancelled(); + bukkitBlocks = event.blockList(); +diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java +index c5d892950b4027cf9879eafc1c0f4e4c62fb4f51..c14cddd42c61512c312231b1e93ccc6efbde620c 100644 +--- a/src/main/java/net/minecraft/world/level/block/BedBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java +@@ -96,6 +96,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + + // CraftBukkit - moved world and biome check into EntityHuman + if (false && !BedBlock.canSetSpawn(world)) { ++ final org.bukkit.block.BlockState explodedBlockState = org.bukkit.craftbukkit.block.CraftBlockStates.getUnplacedBlockState(world, pos, state); // Paper - add exploded state (this won't be called due to the false, but it's good for reference) + world.removeBlock(pos, false); + BlockPos blockposition1 = pos.relative(((Direction) state.getValue(BedBlock.FACING)).getOpposite()); + +@@ -105,7 +106,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + + Vec3 vec3d = pos.getCenter(); + +- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); ++ world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // Paper - add exploded state + return InteractionResult.SUCCESS; + } else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) { + if (!this.kickVillagerOutOfBed(world, pos)) { +@@ -147,6 +148,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + private InteractionResult explodeBed(BlockState iblockdata, Level world, BlockPos blockposition) { + { + { ++ final org.bukkit.block.BlockState explodedBlockState = org.bukkit.craftbukkit.block.CraftBlockStates.getUnplacedBlockState(world, blockposition, iblockdata); // Paper - add exploded state + world.removeBlock(blockposition, false); + BlockPos blockposition1 = blockposition.relative(((Direction) iblockdata.getValue(BedBlock.FACING)).getOpposite()); + +@@ -156,7 +158,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + + Vec3 vec3d = blockposition.getCenter(); + +- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); ++ world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // Paper - add exploded state + return InteractionResult.SUCCESS; + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java +index acd5ec218b8d4c096f44ae2eec1379eeaf30ddc5..088262f306755a9cb785c7a0cf0a9c66ed0965a8 100644 +--- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java +@@ -131,6 +131,7 @@ public class RespawnAnchorBlock extends Block { + } + + private void explode(BlockState state, Level world, final BlockPos explodedPos) { ++ final org.bukkit.block.BlockState explodedBlockState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(explodedPos, state, null); // Paper - add exploded state + world.removeBlock(explodedPos, false); + Stream stream = Direction.Plane.HORIZONTAL.stream(); // CraftBukkit - decompile error + +@@ -147,7 +148,7 @@ public class RespawnAnchorBlock extends Block { + }; + Vec3 vec3d = explodedPos.getCenter(); + +- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); ++ world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // Paper - add exploded state + } + + public static boolean canSetSpawn(Level world) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +index f81c0d07a5efc92942d8ab5c50a8260db033307d..8afc396c162d928902a9d9beb9f039b06630f755 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +@@ -276,6 +276,12 @@ public final class CraftBlockStates { + BlockEntity tileEntity = (blockEntityTag == null) ? null : BlockEntity.loadStatic(blockPosition, blockData, blockEntityTag); + return CraftBlockStates.getBlockState(null, blockPosition, blockData, tileEntity); + } ++ // Paper start - add exploded state ++ public static BlockState getUnplacedBlockState(net.minecraft.world.level.BlockGetter levelAccessor, BlockPos blockPos, net.minecraft.world.level.block.state.BlockState blockData) { ++ BlockEntity tileEntity = levelAccessor.getBlockEntity(blockPos); ++ return CraftBlockStates.getBlockState(null, blockPos, blockData, tileEntity); ++ } ++ // Paper end - add exploded state + + // See BlockStateFactory#createBlockState(World, BlockPosition, IBlockData, TileEntity) + private static CraftBlockState getBlockState(World world, BlockPos blockPosition, net.minecraft.world.level.block.state.BlockState blockData, BlockEntity tileEntity) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 46445366327eb7868ff844bfa2299a3f261aef42..ee717d1f4cc30e17d7449a52379d6f4a54ec738b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1069,7 +1069,7 @@ public class CraftEventFactory { + Entity damager = source.getCausingEntity(); + if (source.is(DamageTypeTags.IS_EXPLOSION)) { + if (damager == null) { +- return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), entity, DamageCause.BLOCK_EXPLOSION, bukkitDamageSource, modifiers, modifierFunctions, cancelled); ++ return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), entity, DamageCause.BLOCK_EXPLOSION, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.explodedBlockState); + } + DamageCause damageCause = (damager.getBukkitEntity() instanceof org.bukkit.entity.TNTPrimed) ? DamageCause.BLOCK_EXPLOSION : DamageCause.ENTITY_EXPLOSION; + return CraftEventFactory.callEntityDamageEvent(damager, entity, damageCause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API +@@ -1173,8 +1173,13 @@ public class CraftEventFactory { + return CraftEventFactory.callEntityDamageEvent(event, damagee, cancelled); + } + +- private static EntityDamageEvent callEntityDamageEvent(Block damager, Entity damagee, DamageCause cause, org.bukkit.damage.DamageSource bukkitDamageSource, Map modifiers, Map> modifierFunctions, boolean cancelled) { +- EntityDamageByBlockEvent event = new EntityDamageByBlockEvent(damager, damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions); ++ // Paper start ++ private static EntityDamageEvent callEntityDamageEvent(Block damager, Entity damagee, DamageCause cause, org.bukkit.damage.DamageSource bukkitDamageSource, Map modifiers, Map> modifierFunctions, boolean cancelled) { // Paper ++ return callEntityDamageEvent(damager, damagee, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, null); ++ } ++ private static EntityDamageEvent callEntityDamageEvent(Block damager, Entity damagee, DamageCause cause, org.bukkit.damage.DamageSource bukkitDamageSource, Map modifiers, Map> modifierFunctions, boolean cancelled, @Nullable org.bukkit.block.BlockState explodedBlockState) { ++ EntityDamageByBlockEvent event = new EntityDamageByBlockEvent(damager, damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions, explodedBlockState); ++ // Paper end + return CraftEventFactory.callEntityDamageEvent(event, damagee, cancelled); + } + diff --git a/patches/server/0837-Add-missing-SpigotConfig-logCommands-check.patch b/patches/server/0837-Add-missing-SpigotConfig-logCommands-check.patch deleted file mode 100644 index ea5a11a63312..000000000000 --- a/patches/server/0837-Add-missing-SpigotConfig-logCommands-check.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: NonSwag -Date: Thu, 8 Dec 2022 20:25:05 +0100 -Subject: [PATCH] Add missing SpigotConfig logCommands check - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 6e1f1bd674e02426f42d013e2b0995d45e37ace0..853e4f393c2198d6bc263e546a9fa871f4996d05 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2060,7 +2060,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private void performChatCommand(ServerboundChatCommandPacket packet, LastSeenMessages lastSeenMessages) { - // CraftBukkit start - String command = "/" + packet.command(); -+ if (org.spigotmc.SpigotConfig.logCommands) { // Paper - Add missing SpigotConfig logCommands check - ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command); -+ } // Paper - Add missing SpigotConfig logCommands check - - PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command, new LazyPlayerSet(this.server)); - this.cserver.getPluginManager().callEvent(event); diff --git a/patches/server/0837-Expose-pre-collision-moving-velocity-to-VehicleBlock.patch b/patches/server/0837-Expose-pre-collision-moving-velocity-to-VehicleBlock.patch new file mode 100644 index 000000000000..3352d79d455d --- /dev/null +++ b/patches/server/0837-Expose-pre-collision-moving-velocity-to-VehicleBlock.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SoSeDiK +Date: Tue, 11 Oct 2022 23:30:32 +0300 +Subject: [PATCH] Expose pre-collision moving velocity to + VehicleBlockCollisionEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 5b89426cdca61ecc99f53b64d1e088e043effcfd..576cef82f7ea57642430897de2e5212cae234701 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -944,6 +944,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + public void move(MoverType movementType, Vec3 movement) { ++ final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity + if (this.noPhysics) { + this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); + } else { +@@ -1028,7 +1029,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + if (!bl.getType().isAir()) { +- VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl); ++ VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl, org.bukkit.craftbukkit.util.CraftVector.toBukkit(originalMovement)); // Paper - Expose pre-collision velocity + this.level.getCraftServer().getPluginManager().callEvent(event); + } + } diff --git a/patches/server/0842-config-for-disabling-entity-tag-tags.patch b/patches/server/0838-config-for-disabling-entity-tag-tags.patch similarity index 100% rename from patches/server/0842-config-for-disabling-entity-tag-tags.patch rename to patches/server/0838-config-for-disabling-entity-tag-tags.patch diff --git a/patches/server/0839-Flying-Fall-Damage.patch b/patches/server/0839-Flying-Fall-Damage.patch deleted file mode 100644 index bb0c1d954238..000000000000 --- a/patches/server/0839-Flying-Fall-Damage.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TreyRuffy -Date: Fri, 27 May 2022 02:26:08 -0600 -Subject: [PATCH] Flying Fall Damage - - -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 a7690e89bb6ed07512fc8a110f541183b7df0a63..eb6a4518d3919cacc3b7a83870c0f0e87eba3f6e 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -180,6 +180,7 @@ public abstract class Player extends LivingEntity { - public FishingHook fishing; - public float hurtDir; // Paper - protected -> public - public boolean affectsSpawning = true; // Paper - Affects Spawning API -+ public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage - - // CraftBukkit start - public boolean fauxSleeping; -@@ -1671,7 +1672,7 @@ public abstract class Player extends LivingEntity { - - @Override - public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { -- if (this.abilities.mayfly) { -+ if (this.abilities.mayfly && !this.flyingFallDamage.toBooleanOrElse(false)) { // Paper - flying fall damage - return false; - } else { - if (fallDistance >= 2.0F) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index b2990c187d0689eece9e43c7895d8055e8d61586..6748c7f7ccd242dfa782687114bafde051b51cc9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2447,6 +2447,19 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.getHandle().onUpdateAbilities(); - } - -+ // Paper start - flying fall damage -+ @Override -+ public void setFlyingFallDamage(@NotNull net.kyori.adventure.util.TriState flyingFallDamage) { -+ getHandle().flyingFallDamage = flyingFallDamage; -+ } -+ -+ @NotNull -+ @Override -+ public net.kyori.adventure.util.TriState hasFlyingFallDamage() { -+ return getHandle().flyingFallDamage; -+ } -+ // Paper end - flying fall damage -+ - @Override - public int getNoDamageTicks() { - if (this.getHandle().spawnInvulnerableTime > 0) { diff --git a/patches/server/0839-Use-single-player-info-update-packet-on-join.patch b/patches/server/0839-Use-single-player-info-update-packet-on-join.patch new file mode 100644 index 000000000000..44156f411a89 --- /dev/null +++ b/patches/server/0839-Use-single-player-info-update-packet-on-join.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 8 Jan 2023 17:38:28 -0800 +Subject: [PATCH] Use single player info update packet on join + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 6b215d271a884bf60bbda5abc46657ddafdca1dc..006c1fa70bd38a3db9844a77f3f5d22b0d083ddf 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3335,7 +3335,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.signedMessageDecoder = session.createMessageDecoder(this.player.getUUID()); + this.chatMessageChain.append(() -> { + this.player.setChatSession(session); +- this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player))); ++ this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player)), this.player); // Paper - Use single player info update packet on join + }); + } + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 598dce073d7887d44a6630820c7e5746ce1c6dcc..2eeb216002c1c91879780225335225552744524b 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -358,6 +358,7 @@ public abstract class PlayerList { + // CraftBukkit start - sendAll above replaced with this loop + ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); + ++ final List onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join + for (int i = 0; i < this.players.size(); ++i) { + ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i); + +@@ -365,12 +366,17 @@ public abstract class PlayerList { + entityplayer1.connection.send(packet); + } + +- if (!bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { ++ if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { // Paper - Use single player info update packet on join; Don't include joining player + continue; + } + +- player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer1))); ++ onlinePlayers.add(entityplayer1); // Paper - Use single player info update packet on join + } ++ // Paper start - Use single player info update packet on join ++ if (!onlinePlayers.isEmpty()) { ++ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers)); ++ } ++ // Paper end - Use single player info update packet on join + player.sentListPacket = true; + player.supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready + ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now diff --git a/patches/server/0840-Add-exploded-block-state-to-BlockExplodeEvent-and-En.patch b/patches/server/0840-Add-exploded-block-state-to-BlockExplodeEvent-and-En.patch deleted file mode 100644 index b74b89d5b296..000000000000 --- a/patches/server/0840-Add-exploded-block-state-to-BlockExplodeEvent-and-En.patch +++ /dev/null @@ -1,144 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 22 Oct 2021 16:25:07 -0700 -Subject: [PATCH] Add exploded block state to BlockExplodeEvent and - EntityDamageByBlockEvent - - -diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -index ed1277fad60992344b94f8a939febaca3edd9702..fc6903b20a6e084729306fc960a6fc80e094f76c 100644 ---- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java -+++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -@@ -52,6 +52,7 @@ public class DamageSource { - return this; - } - // CraftBukkit end -+ public @Nullable org.bukkit.block.BlockState explodedBlockState; // Paper - add exploded state - - public String toString() { - return "DamageSource (" + this.type().msgId() + ")"; -diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSources.java b/src/main/java/net/minecraft/world/damagesource/DamageSources.java -index 8bde8c581796ed11809b80b9a30a33df86116745..f339475185645f7be30963e4f980ce81a6f7e536 100644 ---- a/src/main/java/net/minecraft/world/damagesource/DamageSources.java -+++ b/src/main/java/net/minecraft/world/damagesource/DamageSources.java -@@ -247,8 +247,17 @@ public class DamageSources { - return this.source(DamageTypes.SONIC_BOOM, attacker); - } - -+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - add exploded state - public DamageSource badRespawnPointExplosion(Vec3 position) { -- return new DamageSource(this.damageTypes.getHolderOrThrow(DamageTypes.BAD_RESPAWN_POINT), position); -+ // Paper start - add exploded state -+ return this.badRespawnPointExplosion(position, null); -+ } -+ -+ public DamageSource badRespawnPointExplosion(Vec3 position, @Nullable org.bukkit.block.BlockState explodedBlockState) { -+ DamageSource source = new DamageSource(this.damageTypes.getHolderOrThrow(DamageTypes.BAD_RESPAWN_POINT), position); -+ source.explodedBlockState = explodedBlockState; -+ return source; -+ // Paper end - add exploded state - } - - public DamageSource outOfBorder() { -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index c7075aaf417b1dc9eab4a19b72fac50d2a44286b..34159798e6617ce13b3ac8aae07d24d9bca6ee36 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -367,7 +367,7 @@ public class Explosion { - bukkitBlocks = event.blockList(); - this.yield = event.getYield(); - } else { -- BlockExplodeEvent event = new BlockExplodeEvent(location.getBlock(), blockList, this.yield); -+ BlockExplodeEvent event = new BlockExplodeEvent(location.getBlock(), blockList, this.yield, this.damageSource.explodedBlockState); // Paper - add exploded state - this.level.getCraftServer().getPluginManager().callEvent(event); - this.wasCanceled = event.isCancelled(); - bukkitBlocks = event.blockList(); -diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java -index c5d892950b4027cf9879eafc1c0f4e4c62fb4f51..c14cddd42c61512c312231b1e93ccc6efbde620c 100644 ---- a/src/main/java/net/minecraft/world/level/block/BedBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java -@@ -96,6 +96,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - - // CraftBukkit - moved world and biome check into EntityHuman - if (false && !BedBlock.canSetSpawn(world)) { -+ final org.bukkit.block.BlockState explodedBlockState = org.bukkit.craftbukkit.block.CraftBlockStates.getUnplacedBlockState(world, pos, state); // Paper - add exploded state (this won't be called due to the false, but it's good for reference) - world.removeBlock(pos, false); - BlockPos blockposition1 = pos.relative(((Direction) state.getValue(BedBlock.FACING)).getOpposite()); - -@@ -105,7 +106,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - - Vec3 vec3d = pos.getCenter(); - -- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); -+ world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // Paper - add exploded state - return InteractionResult.SUCCESS; - } else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) { - if (!this.kickVillagerOutOfBed(world, pos)) { -@@ -147,6 +148,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - private InteractionResult explodeBed(BlockState iblockdata, Level world, BlockPos blockposition) { - { - { -+ final org.bukkit.block.BlockState explodedBlockState = org.bukkit.craftbukkit.block.CraftBlockStates.getUnplacedBlockState(world, blockposition, iblockdata); // Paper - add exploded state - world.removeBlock(blockposition, false); - BlockPos blockposition1 = blockposition.relative(((Direction) iblockdata.getValue(BedBlock.FACING)).getOpposite()); - -@@ -156,7 +158,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - - Vec3 vec3d = blockposition.getCenter(); - -- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); -+ world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // Paper - add exploded state - return InteractionResult.SUCCESS; - } - } -diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -index acd5ec218b8d4c096f44ae2eec1379eeaf30ddc5..088262f306755a9cb785c7a0cf0a9c66ed0965a8 100644 ---- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -@@ -131,6 +131,7 @@ public class RespawnAnchorBlock extends Block { - } - - private void explode(BlockState state, Level world, final BlockPos explodedPos) { -+ final org.bukkit.block.BlockState explodedBlockState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(explodedPos, state, null); // Paper - add exploded state - world.removeBlock(explodedPos, false); - Stream stream = Direction.Plane.HORIZONTAL.stream(); // CraftBukkit - decompile error - -@@ -147,7 +148,7 @@ public class RespawnAnchorBlock extends Block { - }; - Vec3 vec3d = explodedPos.getCenter(); - -- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); -+ world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // Paper - add exploded state - } - - public static boolean canSetSpawn(Level world) { -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -index f81c0d07a5efc92942d8ab5c50a8260db033307d..8afc396c162d928902a9d9beb9f039b06630f755 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -@@ -276,6 +276,12 @@ public final class CraftBlockStates { - BlockEntity tileEntity = (blockEntityTag == null) ? null : BlockEntity.loadStatic(blockPosition, blockData, blockEntityTag); - return CraftBlockStates.getBlockState(null, blockPosition, blockData, tileEntity); - } -+ // Paper start - add exploded state -+ public static BlockState getUnplacedBlockState(net.minecraft.world.level.BlockGetter levelAccessor, BlockPos blockPos, net.minecraft.world.level.block.state.BlockState blockData) { -+ BlockEntity tileEntity = levelAccessor.getBlockEntity(blockPos); -+ return CraftBlockStates.getBlockState(null, blockPos, blockData, tileEntity); -+ } -+ // Paper end - add exploded state - - // See BlockStateFactory#createBlockState(World, BlockPosition, IBlockData, TileEntity) - private static CraftBlockState getBlockState(World world, BlockPos blockPosition, net.minecraft.world.level.block.state.BlockState blockData, BlockEntity tileEntity) { -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index a1860e21fd53b801ffd651cd27f5a8f9fcd02ee0..52444c7c83e60e5fe40c485c13a59cca9ce5ee20 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1071,7 +1071,7 @@ public class CraftEventFactory { - CraftEventFactory.entityDamage = null; - EntityDamageEvent event; - if (damager == null) { -- event = new EntityDamageByBlockEvent(null, entity.getBukkitEntity(), DamageCause.BLOCK_EXPLOSION, modifiers, modifierFunctions); -+ event = new EntityDamageByBlockEvent(null, entity.getBukkitEntity(), DamageCause.BLOCK_EXPLOSION, modifiers, modifierFunctions, source.explodedBlockState); // Paper - add exploded state - } else if (entity instanceof EnderDragon && /*PAIL FIXME ((EntityEnderDragon) entity).target == damager*/ false) { - event = new EntityDamageEvent(entity.getBukkitEntity(), DamageCause.ENTITY_EXPLOSION, modifiers, modifierFunctions); - } else { diff --git a/patches/server/0840-Correctly-shrink-items-during-EntityResurrectEvent.patch b/patches/server/0840-Correctly-shrink-items-during-EntityResurrectEvent.patch new file mode 100644 index 000000000000..984dd39b3569 --- /dev/null +++ b/patches/server/0840-Correctly-shrink-items-during-EntityResurrectEvent.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bjarne Koll +Date: Tue, 10 Jan 2023 21:06:42 +0100 +Subject: [PATCH] Correctly shrink items during EntityResurrectEvent + +The EntityResurrectEvent logic is supposed to locate a totem of undying +in any of the interaction slots of the player inventory and then, if the +called EntityResurrectEvent is not cancelled, shrink that item by 1, +usually reducing it to zero. + +For this, the logic iterates over the items in the interaction slots and +breaks out the loop if a totem of undying was found. +However, even if no totem of undying was found, the iteration item stack +variable remains as a refernce to the last interaction slot probed. + +Plugins uncancelling a EntityResurrectEvent, which is published +pre-cancelled to listeners if no totem of undying could be found, +would hence cause the server logic to shrink completely unrelated items +found in, at the writing of this patch, the players off hand slot. + +This patch corrects this behaviour by only shrinking the item if a totem +of undying was found and the event was called uncancelled. + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index bcaa8c21fc09bc4cc3fd4c9bb827ef0750c12aef..e0071236139ab70ba28794f3c7e44640f8d60a2a 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1610,7 +1610,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.level().getCraftServer().getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +- if (!itemstack1.isEmpty()) { ++ if (!itemstack1.isEmpty() && itemstack != null) { // Paper - only reduce item if actual totem was found + itemstack1.shrink(1); + } + if (itemstack != null && this instanceof ServerPlayer) { diff --git a/patches/server/0841-Expose-pre-collision-moving-velocity-to-VehicleBlock.patch b/patches/server/0841-Expose-pre-collision-moving-velocity-to-VehicleBlock.patch deleted file mode 100644 index c2637e2f183c..000000000000 --- a/patches/server/0841-Expose-pre-collision-moving-velocity-to-VehicleBlock.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SoSeDiK -Date: Tue, 11 Oct 2022 23:30:32 +0300 -Subject: [PATCH] Expose pre-collision moving velocity to - VehicleBlockCollisionEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 57d08daa6ca215b604a3f80db17b78f4bd4f822b..ecef6daf948550a9bf9b30413466b5f3d70ba6b2 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -945,6 +945,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - public void move(MoverType movementType, Vec3 movement) { -+ final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity - if (this.noPhysics) { - this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); - } else { -@@ -1029,7 +1030,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - if (!bl.getType().isAir()) { -- VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl); -+ VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl, org.bukkit.craftbukkit.util.CraftVector.toBukkit(originalMovement)); // Paper - Expose pre-collision velocity - this.level.getCraftServer().getPluginManager().callEvent(event); - } - } diff --git a/patches/server/0841-Win-Screen-API.patch b/patches/server/0841-Win-Screen-API.patch new file mode 100644 index 000000000000..0cc81b1cb8e0 --- /dev/null +++ b/patches/server/0841-Win-Screen-API.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lama06 +Date: Sat, 21 Jan 2023 13:53:23 +0100 +Subject: [PATCH] Win Screen API + +== AT == +public net.minecraft.server.level.ServerPlayer seenCredits + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 4f62bd701e2645ef37bcfda851723c6b51f5f3d5..04e5be79348733f5a6a8b1968b6887379fa65027 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1214,6 +1214,25 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.getHandle().connection.send(packet); + } + ++ // Paper start ++ @Override ++ public void showWinScreen() { ++ if (getHandle().connection == null) return; ++ var packet = new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1); ++ getHandle().connection.send(packet); ++ } ++ ++ @Override ++ public boolean hasSeenWinScreen() { ++ return getHandle().seenCredits; ++ } ++ ++ @Override ++ public void setHasSeenWinScreen(boolean hasSeenWinScreen) { ++ getHandle().seenCredits = hasSeenWinScreen; ++ } ++ // Paper end ++ + @Override + public void setRotation(float yaw, float pitch) { + // Paper start - Teleport API diff --git a/patches/server/0842-Remove-CraftItemStack-setAmount-null-assignment.patch b/patches/server/0842-Remove-CraftItemStack-setAmount-null-assignment.patch new file mode 100644 index 000000000000..8d4f6df39881 --- /dev/null +++ b/patches/server/0842-Remove-CraftItemStack-setAmount-null-assignment.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Josh Roy +Date: Mon, 23 Jan 2023 19:19:01 -0500 +Subject: [PATCH] Remove CraftItemStack#setAmount null assignment + +This creates a problem with Paper's item serialization +api where deserialized items, which are internally +created as a CraftItemStack, will be completely lost if +#setAmount(0) is invoked (since the underlying handle +is set to null), while a regular Bukkit ItemStack +simply sets the amount field to zero, retaining the +item's data. + +Vanilla treats items with zero amounts the same as items +with less than zero amounts, so this code doesn't create +a problem with operations on the vanilla ItemStack. + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 6725c0824b986885c8aade846f6e159986ffbe59..312e756843f62371048a4d8de9deb024bd9846a7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -157,7 +157,7 @@ public final class CraftItemStack extends ItemStack { + } + + this.handle.setCount(amount); +- if (amount == 0) { ++ if (false && amount == 0) { // Paper - remove CraftItemStack#setAmount null assignment + this.handle = null; + } + } diff --git a/patches/server/0847-Fix-force-opening-enchantment-tables.patch b/patches/server/0843-Fix-force-opening-enchantment-tables.patch similarity index 100% rename from patches/server/0847-Fix-force-opening-enchantment-tables.patch rename to patches/server/0843-Fix-force-opening-enchantment-tables.patch diff --git a/patches/server/0843-Use-single-player-info-update-packet-on-join.patch b/patches/server/0843-Use-single-player-info-update-packet-on-join.patch deleted file mode 100644 index 72ccc7111290..000000000000 --- a/patches/server/0843-Use-single-player-info-update-packet-on-join.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 8 Jan 2023 17:38:28 -0800 -Subject: [PATCH] Use single player info update packet on join - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 853e4f393c2198d6bc263e546a9fa871f4996d05..ada6ba862fee7dc474d57141947f18610f6c5974 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3335,7 +3335,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.signedMessageDecoder = session.createMessageDecoder(this.player.getUUID()); - this.chatMessageChain.append(() -> { - this.player.setChatSession(session); -- this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player))); -+ this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player)), this.player); // Paper - Use single player info update packet on join - }); - } - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 598dce073d7887d44a6630820c7e5746ce1c6dcc..2eeb216002c1c91879780225335225552744524b 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -358,6 +358,7 @@ public abstract class PlayerList { - // CraftBukkit start - sendAll above replaced with this loop - ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); - -+ final List onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join - for (int i = 0; i < this.players.size(); ++i) { - ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i); - -@@ -365,12 +366,17 @@ public abstract class PlayerList { - entityplayer1.connection.send(packet); - } - -- if (!bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { -+ if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { // Paper - Use single player info update packet on join; Don't include joining player - continue; - } - -- player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer1))); -+ onlinePlayers.add(entityplayer1); // Paper - Use single player info update packet on join - } -+ // Paper start - Use single player info update packet on join -+ if (!onlinePlayers.isEmpty()) { -+ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers)); -+ } -+ // Paper end - Use single player info update packet on join - player.sentListPacket = true; - player.supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready - ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now diff --git a/patches/server/0844-Add-Entity-Body-Yaw-API.patch b/patches/server/0844-Add-Entity-Body-Yaw-API.patch new file mode 100644 index 000000000000..9f3794076ebb --- /dev/null +++ b/patches/server/0844-Add-Entity-Body-Yaw-API.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheTuso +Date: Thu, 2 Feb 2023 16:40:41 +0100 +Subject: [PATCH] Add Entity Body Yaw API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 55a9c4eb0fe1b912e5ff6c9bb81b46674f71868a..e043a43ebda1df7b78c1368ce33a3648345bcb08 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1135,6 +1135,31 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public boolean isInPowderedSnow() { + return getHandle().isInPowderSnow || getHandle().wasInPowderSnow; // depending on the location in the entity "tick" either could be needed. + } ++ ++ @Override ++ public double getX() { ++ return this.entity.getX(); ++ } ++ ++ @Override ++ public double getY() { ++ return this.entity.getY(); ++ } ++ ++ @Override ++ public double getZ() { ++ return this.entity.getZ(); ++ } ++ ++ @Override ++ public float getPitch() { ++ return this.entity.getXRot(); ++ } ++ ++ @Override ++ public float getYaw() { ++ return this.entity.getBukkitYaw(); ++ } + // Paper end + // Paper start - Collision API + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index bee23867ae7fdc62ee93277ae1959d6a7df7f5b6..4abfe34ee595fef3e1253090410c6309d3459a84 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -1113,6 +1113,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + this.damageItemStack0(this.getHandle().getItemBySlot(nmsSlot), amount, nmsSlot); + } + ++ @Override ++ public float getBodyYaw() { ++ return this.getHandle().getVisualRotationYInDegrees(); ++ } ++ ++ @Override ++ public void setBodyYaw(float bodyYaw) { ++ this.getHandle().setYBodyRot(bodyYaw); ++ } ++ + private void damageItemStack0(net.minecraft.world.item.ItemStack nmsStack, int amount, net.minecraft.world.entity.EquipmentSlot slot) { + nmsStack.hurtAndBreak(amount, this.getHandle(), livingEntity -> { + if (slot != null) { diff --git a/patches/server/0844-Correctly-shrink-items-during-EntityResurrectEvent.patch b/patches/server/0844-Correctly-shrink-items-during-EntityResurrectEvent.patch deleted file mode 100644 index 2e56758940d7..000000000000 --- a/patches/server/0844-Correctly-shrink-items-during-EntityResurrectEvent.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Bjarne Koll -Date: Tue, 10 Jan 2023 21:06:42 +0100 -Subject: [PATCH] Correctly shrink items during EntityResurrectEvent - -The EntityResurrectEvent logic is supposed to locate a totem of undying -in any of the interaction slots of the player inventory and then, if the -called EntityResurrectEvent is not cancelled, shrink that item by 1, -usually reducing it to zero. - -For this, the logic iterates over the items in the interaction slots and -breaks out the loop if a totem of undying was found. -However, even if no totem of undying was found, the iteration item stack -variable remains as a refernce to the last interaction slot probed. - -Plugins uncancelling a EntityResurrectEvent, which is published -pre-cancelled to listeners if no totem of undying could be found, -would hence cause the server logic to shrink completely unrelated items -found in, at the writing of this patch, the players off hand slot. - -This patch corrects this behaviour by only shrinking the item if a totem -of undying was found and the event was called uncancelled. - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index d6f12c3e995ff1b1d77538fd77402225c29b1c51..0bb241158cde5c3af1b28065a39c2455ef82eefe 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1609,7 +1609,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.level().getCraftServer().getPluginManager().callEvent(event); - - if (!event.isCancelled()) { -- if (!itemstack1.isEmpty()) { -+ if (!itemstack1.isEmpty() && itemstack != null) { // Paper - only reduce item if actual totem was found - itemstack1.shrink(1); - } - if (itemstack != null && this instanceof ServerPlayer) { diff --git a/patches/server/0849-Fix-MC-157464-Prevent-sleeping-villagers-moving-towa.patch b/patches/server/0845-Fix-MC-157464-Prevent-sleeping-villagers-moving-towa.patch similarity index 100% rename from patches/server/0849-Fix-MC-157464-Prevent-sleeping-villagers-moving-towa.patch rename to patches/server/0845-Fix-MC-157464-Prevent-sleeping-villagers-moving-towa.patch diff --git a/patches/server/0845-Win-Screen-API.patch b/patches/server/0845-Win-Screen-API.patch deleted file mode 100644 index 1a5412d4c0f2..000000000000 --- a/patches/server/0845-Win-Screen-API.patch +++ /dev/null @@ -1,38 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lama06 -Date: Sat, 21 Jan 2023 13:53:23 +0100 -Subject: [PATCH] Win Screen API - -== AT == -public net.minecraft.server.level.ServerPlayer seenCredits - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index c9616b31b074c82eb06d9254ae65a0a85260d480..66176fc39f5e6a06b1a5e92627747fbbad87c6d0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1184,6 +1184,25 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.getHandle().connection.send(packet); - } - -+ // Paper start -+ @Override -+ public void showWinScreen() { -+ if (getHandle().connection == null) return; -+ var packet = new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1); -+ getHandle().connection.send(packet); -+ } -+ -+ @Override -+ public boolean hasSeenWinScreen() { -+ return getHandle().seenCredits; -+ } -+ -+ @Override -+ public void setHasSeenWinScreen(boolean hasSeenWinScreen) { -+ getHandle().seenCredits = hasSeenWinScreen; -+ } -+ // Paper end -+ - @Override - public void setRotation(float yaw, float pitch) { - // Paper start - Teleport API diff --git a/patches/server/0846-Remove-CraftItemStack-setAmount-null-assignment.patch b/patches/server/0846-Remove-CraftItemStack-setAmount-null-assignment.patch deleted file mode 100644 index 4dff637cb9c2..000000000000 --- a/patches/server/0846-Remove-CraftItemStack-setAmount-null-assignment.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Josh Roy -Date: Mon, 23 Jan 2023 19:19:01 -0500 -Subject: [PATCH] Remove CraftItemStack#setAmount null assignment - -This creates a problem with Paper's item serialization -api where deserialized items, which are internally -created as a CraftItemStack, will be completely lost if -#setAmount(0) is invoked (since the underlying handle -is set to null), while a regular Bukkit ItemStack -simply sets the amount field to zero, retaining the -item's data. - -Vanilla treats items with zero amounts the same as items -with less than zero amounts, so this code doesn't create -a problem with operations on the vanilla ItemStack. - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index a4d522a19f1a1288baa2688d5beb62c91a5fb3a3..749bf6a897b053197988112551192abe6af2f186 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -157,7 +157,7 @@ public final class CraftItemStack extends ItemStack { - } - - this.handle.setCount(amount); -- if (amount == 0) { -+ if (false && amount == 0) { // Paper - remove CraftItemStack#setAmount null assignment - this.handle = null; - } - } diff --git a/patches/server/0850-Update-the-flag-when-a-captured-block-state-is-outda.patch b/patches/server/0846-Update-the-flag-when-a-captured-block-state-is-outda.patch similarity index 100% rename from patches/server/0850-Update-the-flag-when-a-captured-block-state-is-outda.patch rename to patches/server/0846-Update-the-flag-when-a-captured-block-state-is-outda.patch diff --git a/patches/server/0847-Add-EntityFertilizeEggEvent.patch b/patches/server/0847-Add-EntityFertilizeEggEvent.patch new file mode 100644 index 000000000000..c076927ac1ae --- /dev/null +++ b/patches/server/0847-Add-EntityFertilizeEggEvent.patch @@ -0,0 +1,103 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Fri, 24 Jun 2022 12:39:34 +0200 +Subject: [PATCH] Add EntityFertilizeEggEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +index cfc0cee09dfd522409bb5853fc96528bd0137475..6a98f66b7701e8af389ca9a1e9eb230a6100c838 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +@@ -443,6 +443,10 @@ public class Turtle extends Animal { + if (entityplayer == null && this.partner.getLoveCause() != null) { + entityplayer = this.partner.getLoveCause(); + } ++ // Paper start - Add EntityFertilizeEggEvent event ++ io.papermc.paper.event.entity.EntityFertilizeEggEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this.animal, this.partner); ++ if (event.isCancelled()) return; ++ // Paper end - Add EntityFertilizeEggEvent event + + if (entityplayer != null) { + entityplayer.awardStat(Stats.ANIMALS_BRED); +@@ -457,7 +461,7 @@ public class Turtle extends Animal { + RandomSource randomsource = this.animal.getRandom(); + + if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { +- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper; ++ if (event.getExperience() > 0) this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), event.getExperience(), org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper - Add EntityFertilizeEggEvent event + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java +index 90e4e0ec0c7b0ece23c4b53f5f12b1f24e1c18ad..295769d039f2a1e4f48912a60f9dbe267d8992c1 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java ++++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java +@@ -239,7 +239,12 @@ public class Frog extends Animal implements VariantHolder { + + @Override + public void spawnChildFromBreeding(ServerLevel world, Animal other) { +- this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob)null); ++ // Paper start - Add EntityFertilizeEggEvent event ++ final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, other); ++ if (result.isCancelled()) return; ++ ++ this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob)null, result.getExperience()); // Paper - use craftbukkit call that takes experience amount ++ // Paper end - Add EntityFertilizeEggEvent event + this.getBrain().setMemory(MemoryModuleType.IS_PREGNANT, Unit.INSTANCE); + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java +index 0e85e3ab58d848b119212fa7d2eb4f92d3efe29b..0a5b953bd8c0c7f181da4090b950e9e6524b6d61 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java ++++ b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java +@@ -347,11 +347,16 @@ public class Sniffer extends Animal { + + @Override + public void spawnChildFromBreeding(ServerLevel world, Animal other) { ++ // Paper start - Add EntityFertilizeEggEvent event ++ final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, other); ++ if (result.isCancelled()) return; ++ // Paper end - Add EntityFertilizeEggEvent event ++ + ItemStack itemstack = new ItemStack(Items.SNIFFER_EGG); + ItemEntity entityitem = new ItemEntity(world, this.position().x(), this.position().y(), this.position().z(), itemstack); + + entityitem.setDefaultPickUpDelay(); +- this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null); ++ this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null, result.getExperience()); // Paper - Add EntityFertilizeEggEvent event + if (this.spawnAtLocation(entityitem) != null) { // Paper - Call EntityDropItemEvent + this.playSound(SoundEvents.SNIFFER_EGG_PLOP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 0.5F); + } // Paper - Call EntityDropItemEvent +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index ee717d1f4cc30e17d7449a52379d6f4a54ec738b..e1d19207ba70e1ecc85af2ca4cfd6ed8c4d4a9e7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -2092,4 +2092,28 @@ public class CraftEventFactory { + return event.callEvent(); + } + // Paper end ++ ++ // Paper start - add EntityFertilizeEggEvent ++ /** ++ * Calls the {@link io.papermc.paper.event.entity.EntityFertilizeEggEvent}. ++ * If the event is cancelled, this method also resets the love on both the {@code breeding} and {@code other} entity. ++ * ++ * @param breeding the entity on which #spawnChildFromBreeding was called. ++ * @param other the partner of the entity. ++ * @return the event after it was called. The instance may be used to retrieve the experience of the event. ++ */ ++ public static io.papermc.paper.event.entity.EntityFertilizeEggEvent callEntityFertilizeEggEvent(Animal breeding, Animal other) { ++ ServerPlayer serverPlayer = breeding.getLoveCause(); ++ if (serverPlayer == null) serverPlayer = other.getLoveCause(); ++ final int experience = breeding.getRandom().nextInt(7) + 1; // From Animal#spawnChildFromBreeding(ServerLevel, Animal) ++ ++ final io.papermc.paper.event.entity.EntityFertilizeEggEvent event = new io.papermc.paper.event.entity.EntityFertilizeEggEvent((LivingEntity) breeding.getBukkitEntity(), (LivingEntity) other.getBukkitEntity(), serverPlayer == null ? null : serverPlayer.getBukkitEntity(), breeding.breedItem == null ? null : CraftItemStack.asCraftMirror(breeding.breedItem).clone(), experience); ++ if (!event.callEvent()) { ++ breeding.resetLove(); ++ other.resetLove(); // stop the pathfinding to avoid infinite loop ++ } ++ ++ return event; ++ } ++ // Paper end - add EntityFertilizeEggEvent + } diff --git a/patches/server/0848-Add-Entity-Body-Yaw-API.patch b/patches/server/0848-Add-Entity-Body-Yaw-API.patch deleted file mode 100644 index 132de41648ba..000000000000 --- a/patches/server/0848-Add-Entity-Body-Yaw-API.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TheTuso -Date: Thu, 2 Feb 2023 16:40:41 +0100 -Subject: [PATCH] Add Entity Body Yaw API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 55a9c4eb0fe1b912e5ff6c9bb81b46674f71868a..e043a43ebda1df7b78c1368ce33a3648345bcb08 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1135,6 +1135,31 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - public boolean isInPowderedSnow() { - return getHandle().isInPowderSnow || getHandle().wasInPowderSnow; // depending on the location in the entity "tick" either could be needed. - } -+ -+ @Override -+ public double getX() { -+ return this.entity.getX(); -+ } -+ -+ @Override -+ public double getY() { -+ return this.entity.getY(); -+ } -+ -+ @Override -+ public double getZ() { -+ return this.entity.getZ(); -+ } -+ -+ @Override -+ public float getPitch() { -+ return this.entity.getXRot(); -+ } -+ -+ @Override -+ public float getYaw() { -+ return this.entity.getBukkitYaw(); -+ } - // Paper end - // Paper start - Collision API - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 330f0f79e9f12c5faf2aa9898047304ae281c10b..11a52a11891f5dca5f2a6b2e77b6a090f4a69726 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -1100,6 +1100,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - this.damageItemStack0(this.getHandle().getItemBySlot(nmsSlot), amount, nmsSlot); - } - -+ @Override -+ public float getBodyYaw() { -+ return this.getHandle().getVisualRotationYInDegrees(); -+ } -+ -+ @Override -+ public void setBodyYaw(float bodyYaw) { -+ this.getHandle().setYBodyRot(bodyYaw); -+ } -+ - private void damageItemStack0(net.minecraft.world.item.ItemStack nmsStack, int amount, net.minecraft.world.entity.EquipmentSlot slot) { - nmsStack.hurtAndBreak(amount, this.getHandle(), livingEntity -> { - if (slot != null) { diff --git a/patches/server/0852-Fix-HumanEntity-drop-not-updating-the-client-inv.patch b/patches/server/0848-Fix-HumanEntity-drop-not-updating-the-client-inv.patch similarity index 100% rename from patches/server/0852-Fix-HumanEntity-drop-not-updating-the-client-inv.patch rename to patches/server/0848-Fix-HumanEntity-drop-not-updating-the-client-inv.patch diff --git a/patches/server/0853-Add-CompostItemEvent-and-EntityCompostItemEvent.patch b/patches/server/0849-Add-CompostItemEvent-and-EntityCompostItemEvent.patch similarity index 100% rename from patches/server/0853-Add-CompostItemEvent-and-EntityCompostItemEvent.patch rename to patches/server/0849-Add-CompostItemEvent-and-EntityCompostItemEvent.patch diff --git a/patches/server/0854-Correctly-handle-ArmorStand-invisibility.patch b/patches/server/0850-Correctly-handle-ArmorStand-invisibility.patch similarity index 100% rename from patches/server/0854-Correctly-handle-ArmorStand-invisibility.patch rename to patches/server/0850-Correctly-handle-ArmorStand-invisibility.patch diff --git a/patches/server/0851-Add-EntityFertilizeEggEvent.patch b/patches/server/0851-Add-EntityFertilizeEggEvent.patch deleted file mode 100644 index 0e6deec6ade0..000000000000 --- a/patches/server/0851-Add-EntityFertilizeEggEvent.patch +++ /dev/null @@ -1,103 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Fri, 24 Jun 2022 12:39:34 +0200 -Subject: [PATCH] Add EntityFertilizeEggEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 6bbcdd34fb89ea5774e825de8f9a588552716fc2..379990c0ea9d10fd3c627ffff2198cb554780eb0 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -445,6 +445,10 @@ public class Turtle extends Animal { - if (entityplayer == null && this.partner.getLoveCause() != null) { - entityplayer = this.partner.getLoveCause(); - } -+ // Paper start - Add EntityFertilizeEggEvent event -+ io.papermc.paper.event.entity.EntityFertilizeEggEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this.animal, this.partner); -+ if (event.isCancelled()) return; -+ // Paper end - Add EntityFertilizeEggEvent event - - if (entityplayer != null) { - entityplayer.awardStat(Stats.ANIMALS_BRED); -@@ -459,7 +463,7 @@ public class Turtle extends Animal { - RandomSource randomsource = this.animal.getRandom(); - - if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { -- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1, org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper; -+ if (event.getExperience() > 0) this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), event.getExperience(), org.bukkit.entity.ExperienceOrb.SpawnReason.BREED, entityplayer)); // Paper - Add EntityFertilizeEggEvent event - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -index 90e4e0ec0c7b0ece23c4b53f5f12b1f24e1c18ad..295769d039f2a1e4f48912a60f9dbe267d8992c1 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -@@ -239,7 +239,12 @@ public class Frog extends Animal implements VariantHolder { - - @Override - public void spawnChildFromBreeding(ServerLevel world, Animal other) { -- this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob)null); -+ // Paper start - Add EntityFertilizeEggEvent event -+ final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, other); -+ if (result.isCancelled()) return; -+ -+ this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob)null, result.getExperience()); // Paper - use craftbukkit call that takes experience amount -+ // Paper end - Add EntityFertilizeEggEvent event - this.getBrain().setMemory(MemoryModuleType.IS_PREGNANT, Unit.INSTANCE); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -index 0e85e3ab58d848b119212fa7d2eb4f92d3efe29b..0a5b953bd8c0c7f181da4090b950e9e6524b6d61 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -+++ b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java -@@ -347,11 +347,16 @@ public class Sniffer extends Animal { - - @Override - public void spawnChildFromBreeding(ServerLevel world, Animal other) { -+ // Paper start - Add EntityFertilizeEggEvent event -+ final io.papermc.paper.event.entity.EntityFertilizeEggEvent result = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityFertilizeEggEvent(this, other); -+ if (result.isCancelled()) return; -+ // Paper end - Add EntityFertilizeEggEvent event -+ - ItemStack itemstack = new ItemStack(Items.SNIFFER_EGG); - ItemEntity entityitem = new ItemEntity(world, this.position().x(), this.position().y(), this.position().z(), itemstack); - - entityitem.setDefaultPickUpDelay(); -- this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null); -+ this.finalizeSpawnChildFromBreeding(world, other, (AgeableMob) null, result.getExperience()); // Paper - Add EntityFertilizeEggEvent event - if (this.spawnAtLocation(entityitem) != null) { // Paper - Call EntityDropItemEvent - this.playSound(SoundEvents.SNIFFER_EGG_PLOP, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 0.5F); - } // Paper - Call EntityDropItemEvent -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index adad1dd09d190d8b7dfcf5a469d2514b043ee357..06263beb88b6f201d8aef12c28a8d91e148f94e5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -2150,4 +2150,28 @@ public class CraftEventFactory { - return event.callEvent(); - } - // Paper end -+ -+ // Paper start - add EntityFertilizeEggEvent -+ /** -+ * Calls the {@link io.papermc.paper.event.entity.EntityFertilizeEggEvent}. -+ * If the event is cancelled, this method also resets the love on both the {@code breeding} and {@code other} entity. -+ * -+ * @param breeding the entity on which #spawnChildFromBreeding was called. -+ * @param other the partner of the entity. -+ * @return the event after it was called. The instance may be used to retrieve the experience of the event. -+ */ -+ public static io.papermc.paper.event.entity.EntityFertilizeEggEvent callEntityFertilizeEggEvent(Animal breeding, Animal other) { -+ ServerPlayer serverPlayer = breeding.getLoveCause(); -+ if (serverPlayer == null) serverPlayer = other.getLoveCause(); -+ final int experience = breeding.getRandom().nextInt(7) + 1; // From Animal#spawnChildFromBreeding(ServerLevel, Animal) -+ -+ final io.papermc.paper.event.entity.EntityFertilizeEggEvent event = new io.papermc.paper.event.entity.EntityFertilizeEggEvent((LivingEntity) breeding.getBukkitEntity(), (LivingEntity) other.getBukkitEntity(), serverPlayer == null ? null : serverPlayer.getBukkitEntity(), breeding.breedItem == null ? null : CraftItemStack.asCraftMirror(breeding.breedItem).clone(), experience); -+ if (!event.callEvent()) { -+ breeding.resetLove(); -+ other.resetLove(); // stop the pathfinding to avoid infinite loop -+ } -+ -+ return event; -+ } -+ // Paper end - add EntityFertilizeEggEvent - } diff --git a/patches/server/0851-Fix-advancement-triggers-for-entity-damage.patch b/patches/server/0851-Fix-advancement-triggers-for-entity-damage.patch new file mode 100644 index 000000000000..6407d2f94784 --- /dev/null +++ b/patches/server/0851-Fix-advancement-triggers-for-entity-damage.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 16 Mar 2023 10:04:17 +0100 +Subject: [PATCH] Fix advancement triggers for entity damage + +Changes the Interaction entity's trigger to use the vanilla +generic damage source + +Fixes a couple places where the original damage and modified damage +were passed in the reverse order to the advancement triggers + +diff --git a/src/main/java/net/minecraft/world/entity/Interaction.java b/src/main/java/net/minecraft/world/entity/Interaction.java +index f054d67a637b204de604fadc0d321f5c9816d808..fc5f1e1b445f0a55a35a31d58a90920a80275662 100644 +--- a/src/main/java/net/minecraft/world/entity/Interaction.java ++++ b/src/main/java/net/minecraft/world/entity/Interaction.java +@@ -160,7 +160,7 @@ public class Interaction extends Entity implements Attackable, Targeting { + if (entityhuman instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entityhuman; + +- CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, source, (float) event.getFinalDamage(), 1.0F, false); // CraftBukkit ++ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, entityhuman.damageSources().generic(), 1.0F, (float) event.getFinalDamage(), false); // CraftBukkit // Paper - use correct source and fix taken/dealt param order + } + + return !this.getResponse(); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index e0071236139ab70ba28794f3c7e44640f8d60a2a..549bb5caa38e08196fddbd4e4255b499c784a9c2 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2314,7 +2314,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + // Duplicate triggers if blocking + if (event.getDamage(DamageModifier.BLOCKING) < 0) { + if (this instanceof ServerPlayer) { +- CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damagesource, f, originalDamage, true); ++ CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damagesource, originalDamage, f, true); // Paper - fix taken/dealt param order + f2 = (float) -event.getDamage(DamageModifier.BLOCKING); + if (f2 > 0.0F && f2 < 3.4028235E37F) { + ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, Math.round(originalDamage * 10.0F)); +@@ -2322,7 +2322,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + if (damagesource.getEntity() instanceof ServerPlayer) { +- CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damagesource.getEntity(), this, damagesource, f, originalDamage, true); ++ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damagesource.getEntity(), this, damagesource, originalDamage, f, true); // Paper - fix taken/dealt param order + } + + return false; diff --git a/patches/server/0856-Fix-text-display-error-on-spawn.patch b/patches/server/0852-Fix-text-display-error-on-spawn.patch similarity index 100% rename from patches/server/0856-Fix-text-display-error-on-spawn.patch rename to patches/server/0852-Fix-text-display-error-on-spawn.patch diff --git a/patches/server/0853-Fix-inventories-returning-null-Locations.patch b/patches/server/0853-Fix-inventories-returning-null-Locations.patch new file mode 100644 index 000000000000..763bde395339 --- /dev/null +++ b/patches/server/0853-Fix-inventories-returning-null-Locations.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 15 Mar 2023 18:29:45 -0700 +Subject: [PATCH] Fix inventories returning null Locations + +Wandering Trader, AbstractHorse, Beacon and Composter inventories returned null locations +when a block or entity location is readily available + +Co-authored-by: Lukas Planz + +diff --git a/src/main/java/net/minecraft/world/SimpleContainer.java b/src/main/java/net/minecraft/world/SimpleContainer.java +index d5eeb2fcb119b815213aeffb3811d4a843502e50..ff1aba1e69cfde633fd01724f1a8d0af7f59437f 100644 +--- a/src/main/java/net/minecraft/world/SimpleContainer.java ++++ b/src/main/java/net/minecraft/world/SimpleContainer.java +@@ -63,6 +63,16 @@ public class SimpleContainer implements Container, StackedContentsCompatible { + + @Override + public Location getLocation() { ++ // Paper start - Fix inventories returning null Locations ++ // When the block inventory does not have a tile state that implements getLocation, e. g. composters ++ if (this.bukkitOwner instanceof org.bukkit.inventory.BlockInventoryHolder blockInventoryHolder) { ++ return blockInventoryHolder.getBlock().getLocation(); ++ } ++ // When the bukkit owner is a bukkit entity, but does not implement Container itself, e. g. horses ++ if (this.bukkitOwner instanceof org.bukkit.entity.Entity entity) { ++ return entity.getLocation(); ++ } ++ // Paper end - Fix inventories returning null Locations + return null; + } + +diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +index 9599a5f96601030bf7f7cbd3392861d626959f9d..2813a87a01d0704a3de210cd005073f953d538f8 100644 +--- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +@@ -51,6 +51,12 @@ public class BeaconMenu extends AbstractContainerMenu { + public int getMaxStackSize() { + return 1; + } ++ // Paper start - Fix inventories returning null Locations ++ @Override ++ public org.bukkit.Location getLocation() { ++ return context.getLocation(); ++ } ++ // Paper end - Fix inventories returning null Locations + }; + checkContainerDataCount(propertyDelegate, 3); + this.beaconData = propertyDelegate; +diff --git a/src/main/java/net/minecraft/world/inventory/MerchantContainer.java b/src/main/java/net/minecraft/world/inventory/MerchantContainer.java +index 083e50e27685f441ede4c75e913d671fe45d1d15..ac4ec7157cf93b0cbc4472dc42fbbbff3402335a 100644 +--- a/src/main/java/net/minecraft/world/inventory/MerchantContainer.java ++++ b/src/main/java/net/minecraft/world/inventory/MerchantContainer.java +@@ -65,7 +65,7 @@ public class MerchantContainer implements Container { + + @Override + public Location getLocation() { +- return (this.merchant instanceof Villager) ? ((Villager) this.merchant).getBukkitEntity().getLocation() : null; ++ return (this.merchant instanceof AbstractVillager) ? ((AbstractVillager) this.merchant).getBukkitEntity().getLocation() : null; // Paper - Fix inventories returning null Locations + } + // CraftBukkit end + diff --git a/patches/server/0854-Add-Shearable-API.patch b/patches/server/0854-Add-Shearable-API.patch new file mode 100644 index 000000000000..8ab6feec020c --- /dev/null +++ b/patches/server/0854-Add-Shearable-API.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 17 Oct 2021 15:39:48 -0400 +Subject: [PATCH] Add Shearable API + + +diff --git a/src/main/java/io/papermc/paper/entity/PaperShearable.java b/src/main/java/io/papermc/paper/entity/PaperShearable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bcf254e3c81cf1e401bddc850fb24ad29dcc127c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/entity/PaperShearable.java +@@ -0,0 +1,21 @@ ++package io.papermc.paper.entity; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import net.kyori.adventure.sound.Sound; ++import net.minecraft.world.entity.Shearable; ++import org.jetbrains.annotations.NotNull; ++ ++public interface PaperShearable extends io.papermc.paper.entity.Shearable { ++ ++ Shearable getHandle(); ++ ++ @Override ++ default boolean readyToBeSheared() { ++ return this.getHandle().readyForShearing(); ++ } ++ ++ @Override ++ default void shear(@NotNull Sound.Source source) { ++ this.getHandle().shear(PaperAdventure.asVanilla(source)); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java +index a41a85ad89a177759c97d661a89b8b5dc419db1b..1d78ae1db211a452a42fb4a7bef4cca4b7c71a1f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java +@@ -14,7 +14,7 @@ import org.bukkit.entity.MushroomCow; + import org.bukkit.potion.PotionEffect; + import org.bukkit.potion.PotionEffectType; + +-public class CraftMushroomCow extends CraftCow implements MushroomCow { ++public class CraftMushroomCow extends CraftCow implements MushroomCow, io.papermc.paper.entity.PaperShearable { // Paper + public CraftMushroomCow(CraftServer server, net.minecraft.world.entity.animal.MushroomCow entity) { + super(server, entity); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java +index 030bf7b6312799231d0b614ba5c84fec23c276e3..37291d7ad9fdf0fe78894f82a418f40bb581f58b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java +@@ -4,7 +4,7 @@ import org.bukkit.DyeColor; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Sheep; + +-public class CraftSheep extends CraftAnimals implements Sheep { ++public class CraftSheep extends CraftAnimals implements Sheep, io.papermc.paper.entity.PaperShearable { // Paper + public CraftSheep(CraftServer server, net.minecraft.world.entity.animal.Sheep entity) { + super(server, entity); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java +index 1e9807b8f468742d208f817e22d7625106fc1b58..4ce2373ff71c3c1b8951646e057587a3ab09e145 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java +@@ -4,7 +4,7 @@ import net.minecraft.world.entity.animal.SnowGolem; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Snowman; + +-public class CraftSnowman extends CraftGolem implements Snowman, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper ++public class CraftSnowman extends CraftGolem implements Snowman, com.destroystokyo.paper.entity.CraftRangedEntity, io.papermc.paper.entity.PaperShearable { // Paper + public CraftSnowman(CraftServer server, SnowGolem entity) { + super(server, entity); + } diff --git a/patches/server/0859-Fix-SpawnEggMeta-get-setSpawnedType.patch b/patches/server/0855-Fix-SpawnEggMeta-get-setSpawnedType.patch similarity index 100% rename from patches/server/0859-Fix-SpawnEggMeta-get-setSpawnedType.patch rename to patches/server/0855-Fix-SpawnEggMeta-get-setSpawnedType.patch diff --git a/patches/server/0855-Fix-advancement-triggers-for-entity-damage.patch b/patches/server/0855-Fix-advancement-triggers-for-entity-damage.patch deleted file mode 100644 index cb8de1cc65fa..000000000000 --- a/patches/server/0855-Fix-advancement-triggers-for-entity-damage.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 16 Mar 2023 10:04:17 +0100 -Subject: [PATCH] Fix advancement triggers for entity damage - -Changes the Interaction entity's trigger to use the vanilla -generic damage source - -Fixes a couple places where the original damage and modified damage -were passed in the reverse order to the advancement triggers - -diff --git a/src/main/java/net/minecraft/world/entity/Interaction.java b/src/main/java/net/minecraft/world/entity/Interaction.java -index f054d67a637b204de604fadc0d321f5c9816d808..fc5f1e1b445f0a55a35a31d58a90920a80275662 100644 ---- a/src/main/java/net/minecraft/world/entity/Interaction.java -+++ b/src/main/java/net/minecraft/world/entity/Interaction.java -@@ -160,7 +160,7 @@ public class Interaction extends Entity implements Attackable, Targeting { - if (entityhuman instanceof ServerPlayer) { - ServerPlayer entityplayer = (ServerPlayer) entityhuman; - -- CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, source, (float) event.getFinalDamage(), 1.0F, false); // CraftBukkit -+ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, entityhuman.damageSources().generic(), 1.0F, (float) event.getFinalDamage(), false); // CraftBukkit // Paper - use correct source and fix taken/dealt param order - } - - return !this.getResponse(); -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index b6a8530074c898392879405e8ae774ce15c05776..f6592d997a4cfd9d3ca86cd955e3de0a49743bfa 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -2301,7 +2301,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - // Duplicate triggers if blocking - if (event.getDamage(DamageModifier.BLOCKING) < 0) { - if (this instanceof ServerPlayer) { -- CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damagesource, f, originalDamage, true); -+ CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damagesource, originalDamage, f, true); // Paper - fix taken/dealt param order - f2 = (float) -event.getDamage(DamageModifier.BLOCKING); - if (f2 > 0.0F && f2 < 3.4028235E37F) { - ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, Math.round(originalDamage * 10.0F)); -@@ -2309,7 +2309,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - if (damagesource.getEntity() instanceof ServerPlayer) { -- CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damagesource.getEntity(), this, damagesource, f, originalDamage, true); -+ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damagesource.getEntity(), this, damagesource, originalDamage, f, true); // Paper - fix taken/dealt param order - } - - return false; diff --git a/patches/server/0861-Fix-crash-relating-to-bad-recipes-in-furnace-like-ti.patch b/patches/server/0856-Fix-crash-relating-to-bad-recipes-in-furnace-like-ti.patch similarity index 100% rename from patches/server/0861-Fix-crash-relating-to-bad-recipes-in-furnace-like-ti.patch rename to patches/server/0856-Fix-crash-relating-to-bad-recipes-in-furnace-like-ti.patch diff --git a/patches/server/0857-Fix-inventories-returning-null-Locations.patch b/patches/server/0857-Fix-inventories-returning-null-Locations.patch deleted file mode 100644 index 9cf8fcb0e63a..000000000000 --- a/patches/server/0857-Fix-inventories-returning-null-Locations.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 15 Mar 2023 18:29:45 -0700 -Subject: [PATCH] Fix inventories returning null Locations - -Wandering Trader, AbstractHorse, Beacon and Composter inventories returned null locations -when a block or entity location is readily available - -Co-authored-by: Lukas Planz - -diff --git a/src/main/java/net/minecraft/world/SimpleContainer.java b/src/main/java/net/minecraft/world/SimpleContainer.java -index d5eeb2fcb119b815213aeffb3811d4a843502e50..ff1aba1e69cfde633fd01724f1a8d0af7f59437f 100644 ---- a/src/main/java/net/minecraft/world/SimpleContainer.java -+++ b/src/main/java/net/minecraft/world/SimpleContainer.java -@@ -63,6 +63,16 @@ public class SimpleContainer implements Container, StackedContentsCompatible { - - @Override - public Location getLocation() { -+ // Paper start - Fix inventories returning null Locations -+ // When the block inventory does not have a tile state that implements getLocation, e. g. composters -+ if (this.bukkitOwner instanceof org.bukkit.inventory.BlockInventoryHolder blockInventoryHolder) { -+ return blockInventoryHolder.getBlock().getLocation(); -+ } -+ // When the bukkit owner is a bukkit entity, but does not implement Container itself, e. g. horses -+ if (this.bukkitOwner instanceof org.bukkit.entity.Entity entity) { -+ return entity.getLocation(); -+ } -+ // Paper end - Fix inventories returning null Locations - return null; - } - -diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -index de919d72e6e3ef160c7d22ca6a4e9f79a1fe493e..86199457586dc4d4f0d8ccaac812e8340aaac957 100644 ---- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -@@ -51,6 +51,12 @@ public class BeaconMenu extends AbstractContainerMenu { - public int getMaxStackSize() { - return 1; - } -+ // Paper start - Fix inventories returning null Locations -+ @Override -+ public org.bukkit.Location getLocation() { -+ return context.getLocation(); -+ } -+ // Paper end - Fix inventories returning null Locations - }; - checkContainerDataCount(propertyDelegate, 3); - this.beaconData = propertyDelegate; -diff --git a/src/main/java/net/minecraft/world/inventory/MerchantContainer.java b/src/main/java/net/minecraft/world/inventory/MerchantContainer.java -index 083e50e27685f441ede4c75e913d671fe45d1d15..ac4ec7157cf93b0cbc4472dc42fbbbff3402335a 100644 ---- a/src/main/java/net/minecraft/world/inventory/MerchantContainer.java -+++ b/src/main/java/net/minecraft/world/inventory/MerchantContainer.java -@@ -65,7 +65,7 @@ public class MerchantContainer implements Container { - - @Override - public Location getLocation() { -- return (this.merchant instanceof Villager) ? ((Villager) this.merchant).getBukkitEntity().getLocation() : null; -+ return (this.merchant instanceof AbstractVillager) ? ((AbstractVillager) this.merchant).getBukkitEntity().getLocation() : null; // Paper - Fix inventories returning null Locations - } - // CraftBukkit end - diff --git a/patches/server/0857-Treat-sequence-violations-like-they-should-be.patch b/patches/server/0857-Treat-sequence-violations-like-they-should-be.patch new file mode 100644 index 000000000000..360f303ea07d --- /dev/null +++ b/patches/server/0857-Treat-sequence-violations-like-they-should-be.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 30 Mar 2023 03:13:58 +0100 +Subject: [PATCH] Treat sequence violations like they should be + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 006c1fa70bd38a3db9844a77f3f5d22b0d083ddf..2e12d2f22485644bc2fc3d5235cfd3bc755f6c72 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1964,6 +1964,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + public void ackBlockChangesUpTo(int sequence) { + if (sequence < 0) { ++ this.disconnect("Expected packet sequence nr >= 0", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - Treat sequence violations like they should be + throw new IllegalArgumentException("Expected packet sequence nr >= 0"); + } else { + this.ackBlockChangesUpTo = Math.max(sequence, this.ackBlockChangesUpTo); diff --git a/patches/server/0858-Add-Shearable-API.patch b/patches/server/0858-Add-Shearable-API.patch deleted file mode 100644 index 0cd904ff9dd9..000000000000 --- a/patches/server/0858-Add-Shearable-API.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 17 Oct 2021 15:39:48 -0400 -Subject: [PATCH] Add Shearable API - - -diff --git a/src/main/java/io/papermc/paper/entity/PaperShearable.java b/src/main/java/io/papermc/paper/entity/PaperShearable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bcf254e3c81cf1e401bddc850fb24ad29dcc127c ---- /dev/null -+++ b/src/main/java/io/papermc/paper/entity/PaperShearable.java -@@ -0,0 +1,21 @@ -+package io.papermc.paper.entity; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import net.kyori.adventure.sound.Sound; -+import net.minecraft.world.entity.Shearable; -+import org.jetbrains.annotations.NotNull; -+ -+public interface PaperShearable extends io.papermc.paper.entity.Shearable { -+ -+ Shearable getHandle(); -+ -+ @Override -+ default boolean readyToBeSheared() { -+ return this.getHandle().readyForShearing(); -+ } -+ -+ @Override -+ default void shear(@NotNull Sound.Source source) { -+ this.getHandle().shear(PaperAdventure.asVanilla(source)); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java -index c20f470bec5292dde7fbdbf3a6562ae12117521d..e5df527d3f0b82327bcd4cb66c12baa439b4cec6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java -@@ -5,7 +5,7 @@ import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.MushroomCow; - import org.bukkit.entity.MushroomCow.Variant; - --public class CraftMushroomCow extends CraftCow implements MushroomCow { -+public class CraftMushroomCow extends CraftCow implements MushroomCow, io.papermc.paper.entity.PaperShearable { // Paper - public CraftMushroomCow(CraftServer server, net.minecraft.world.entity.animal.MushroomCow entity) { - super(server, entity); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java -index 030bf7b6312799231d0b614ba5c84fec23c276e3..37291d7ad9fdf0fe78894f82a418f40bb581f58b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java -@@ -4,7 +4,7 @@ import org.bukkit.DyeColor; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.Sheep; - --public class CraftSheep extends CraftAnimals implements Sheep { -+public class CraftSheep extends CraftAnimals implements Sheep, io.papermc.paper.entity.PaperShearable { // Paper - public CraftSheep(CraftServer server, net.minecraft.world.entity.animal.Sheep entity) { - super(server, entity); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -index 1e9807b8f468742d208f817e22d7625106fc1b58..4ce2373ff71c3c1b8951646e057587a3ab09e145 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -@@ -4,7 +4,7 @@ import net.minecraft.world.entity.animal.SnowGolem; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.Snowman; - --public class CraftSnowman extends CraftGolem implements Snowman, com.destroystokyo.paper.entity.CraftRangedEntity { // Paper -+public class CraftSnowman extends CraftGolem implements Snowman, com.destroystokyo.paper.entity.CraftRangedEntity, io.papermc.paper.entity.PaperShearable { // Paper - public CraftSnowman(CraftServer server, SnowGolem entity) { - super(server, entity); - } diff --git a/patches/server/0863-remove-duplicate-animate-packet-for-records.patch b/patches/server/0858-remove-duplicate-animate-packet-for-records.patch similarity index 100% rename from patches/server/0863-remove-duplicate-animate-packet-for-records.patch rename to patches/server/0858-remove-duplicate-animate-packet-for-records.patch diff --git a/patches/server/0859-Prevent-causing-expired-keys-from-impacting-new-join.patch b/patches/server/0859-Prevent-causing-expired-keys-from-impacting-new-join.patch new file mode 100644 index 000000000000..00bb9c643b40 --- /dev/null +++ b/patches/server/0859-Prevent-causing-expired-keys-from-impacting-new-join.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Mon, 3 Apr 2023 08:55:52 +0100 +Subject: [PATCH] Prevent causing expired keys from impacting new joins + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java +index 0e54e8faa48751a651b953bec72543a94edf74bc..d43106eb89b14667e85cd6e8fa047d64f2e8ec87 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java +@@ -96,7 +96,13 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet { + serialized.chatSession = buf.readNullable(RemoteChatSession.Data::read); + }, (buf, entry) -> { +- buf.writeNullable(entry.chatSession, RemoteChatSession.Data::write); ++ // Paper start - Prevent causing expired keys from impacting new joins ++ RemoteChatSession.Data chatSession = entry.chatSession; ++ if (chatSession != null && chatSession.profilePublicKey().hasExpired()) { ++ chatSession = null; ++ } ++ buf.writeNullable(chatSession, RemoteChatSession.Data::write); ++ // Paper end - Prevent causing expired keys from impacting new joins + }), + UPDATE_GAME_MODE((serialized, buf) -> { + serialized.gameMode = GameType.byId(buf.readVarInt()); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 2e12d2f22485644bc2fc3d5235cfd3bc755f6c72..d8dd44d0f247ab05bc8323548bdec0f67ab641bc 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -286,6 +286,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + private int knownMovePacketCount; + @Nullable + private RemoteChatSession chatSession; ++ private boolean hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins + private SignedMessageChain.Decoder signedMessageDecoder; + private final LastSeenMessagesValidator lastSeenMessages = new LastSeenMessagesValidator(20); + private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); +@@ -394,6 +395,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause + } + ++ // Paper start - Prevent causing expired keys from impacting new joins ++ if (!hasLoggedExpiry && this.chatSession != null && this.chatSession.profilePublicKey().data().hasExpired()) { ++ LOGGER.info("Player profile key for {} has expired!", this.player.getName().getString()); ++ hasLoggedExpiry = true; ++ } ++ // Paper end - Prevent causing expired keys from impacting new joins ++ + } + + public void resetPosition() { +@@ -3333,6 +3341,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + private void resetPlayerChatState(RemoteChatSession session) { + this.chatSession = session; ++ this.hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins + this.signedMessageDecoder = session.createMessageDecoder(this.player.getUUID()); + this.chatMessageChain.append(() -> { + this.player.setChatSession(session); diff --git a/patches/server/0860-Fix-beehives-generating-from-using-bonemeal.patch b/patches/server/0860-Fix-beehives-generating-from-using-bonemeal.patch deleted file mode 100644 index 51f5e5014e18..000000000000 --- a/patches/server/0860-Fix-beehives-generating-from-using-bonemeal.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 26 Mar 2023 18:07:56 -0700 -Subject: [PATCH] Fix beehives generating from using bonemeal - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 23fec59b51c99c1f0ac19ffd6c84ffa8fc3caaac..0e9d515381a673e683b63a12c1a9e79a5eedd80b 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -422,6 +422,7 @@ public final class ItemStack { - } - for (CraftBlockState blockstate : blocks) { - world.setBlock(blockstate.getPosition(),blockstate.getHandle(), blockstate.getFlag()); // SPIGOT-7248 - manual update to avoid physics where appropriate -+ if (blockstate instanceof org.bukkit.craftbukkit.block.CapturedBlockState capturedBlockState) capturedBlockState.checkTreeBlockHack(); // Paper - Fix beehives generating from using bonemeal - } - entityhuman.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CapturedBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CapturedBlockState.java -index 2aab5ba9e90f09e6d679ee0d0d5d5e52c44b677f..c17c8b2bff32bfd101675d73f8ab81b35a9e1c15 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CapturedBlockState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CapturedBlockState.java -@@ -31,6 +31,12 @@ public final class CapturedBlockState extends CraftBlockState { - public boolean update(boolean force, boolean applyPhysics) { - boolean result = super.update(force, applyPhysics); - -+ // Paper start - Fix beehives generating from using bonemeal -+ this.checkTreeBlockHack(); -+ return result; -+ } -+ public void checkTreeBlockHack() { -+ // Paper end - Fix beehives generating from using bonemeal - // SPIGOT-5537: Horrible hack to manually add bees given World.captureTreeGeneration does not support tiles - if (this.treeBlock && this.getType() == Material.BEE_NEST) { - WorldGenLevel generatoraccessseed = this.world.getHandle(); -@@ -53,7 +59,7 @@ public final class CapturedBlockState extends CraftBlockState { - // End copied block - } - -- return result; -+ // Paper - Fix beehives generating from using bonemeal - } - - @Override diff --git a/patches/server/0865-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch b/patches/server/0860-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch similarity index 100% rename from patches/server/0865-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch rename to patches/server/0860-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch diff --git a/patches/server/0861-Use-array-for-gamerule-storage.patch b/patches/server/0861-Use-array-for-gamerule-storage.patch new file mode 100644 index 000000000000..1a7c2006a541 --- /dev/null +++ b/patches/server/0861-Use-array-for-gamerule-storage.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Sun, 9 May 2021 16:49:49 -0500 +Subject: [PATCH] Use array for gamerule storage + + +diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java +index ac7a5410b01a6741e3b548d153f37ea1d8c1a4cb..4a340bd1f1859e43bb58e68aee4018fdb4ca7a5a 100644 +--- a/src/main/java/net/minecraft/world/level/GameRules.java ++++ b/src/main/java/net/minecraft/world/level/GameRules.java +@@ -115,6 +115,7 @@ public class GameRules { + public static final GameRules.Key RULE_DO_VINES_SPREAD = GameRules.register("doVinesSpread", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true)); + public static final GameRules.Key RULE_ENDER_PEARLS_VANISH_ON_DEATH = GameRules.register("enderPearlsVanishOnDeath", GameRules.Category.PLAYER, GameRules.BooleanValue.create(true)); + private final Map, GameRules.Value> rules; ++ private final GameRules.Value[] gameruleArray; // Paper - Perf: Use array for gamerule storage + + private static > GameRules.Key register(String name, GameRules.Category category, GameRules.Type type) { + GameRules.Key gamerules_gamerulekey = new GameRules.Key<>(name, category); +@@ -133,17 +134,30 @@ public class GameRules { + } + + public GameRules() { +- this.rules = (Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> { ++ // Paper start - Perf: Use array for gamerule storage ++ this((Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> { + return ((GameRules.Type) entry.getValue()).createRule(); +- })); ++ }))); ++ // Paper end - Perf: Use array for gamerule storage + } + + private GameRules(Map, GameRules.Value> rules) { + this.rules = rules; ++ ++ // Paper start - Perf: Use array for gamerule storage ++ int arraySize = rules.keySet().stream().mapToInt(key -> key.gameRuleIndex).max().orElse(-1) + 1; ++ GameRules.Value[] values = new GameRules.Value[arraySize]; ++ ++ for (Entry, GameRules.Value> entry : rules.entrySet()) { ++ values[entry.getKey().gameRuleIndex] = entry.getValue(); ++ } ++ ++ this.gameruleArray = values; ++ // Paper end - Perf: Use array for gamerule storage + } + + public > T getRule(GameRules.Key key) { +- return (T) this.rules.get(key); // CraftBukkit - decompile error ++ return key == null ? null : (T) this.gameruleArray[key.gameRuleIndex]; // Paper - Perf: Use array for gamerule storage + } + + public CompoundTag createTag() { +@@ -202,6 +216,10 @@ public class GameRules { + } + + public static final class Key> { ++ // Paper start - Perf: Use array for gamerule storage ++ private static int lastGameRuleIndex = 0; ++ public final int gameRuleIndex = lastGameRuleIndex++; ++ // Paper end - Perf: Use array for gamerule storage + + final String id; + private final GameRules.Category category; diff --git a/patches/server/0862-Fix-a-couple-of-upstream-bed-issues.patch b/patches/server/0862-Fix-a-couple-of-upstream-bed-issues.patch new file mode 100644 index 000000000000..6740daf4d1ba --- /dev/null +++ b/patches/server/0862-Fix-a-couple-of-upstream-bed-issues.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 9 Apr 2023 21:11:58 -0700 +Subject: [PATCH] Fix a couple of upstream bed issues + +Upstream incorrectly skipped explosion logic if +the bed was occupied and added a "feature" where +if you set your spawn in a respawn anchor world +but then replaced it with a bed, you could respawn +at the bed in that world. + +diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java +index c14cddd42c61512c312231b1e93ccc6efbde620c..38fcde81d797dc46409f5a9ed426fe296d79bdfa 100644 +--- a/src/main/java/net/minecraft/world/level/block/BedBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java +@@ -109,6 +109,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // Paper - add exploded state + return InteractionResult.SUCCESS; + } else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) { ++ if (!BedBlock.canSetSpawn(world)) return this.explodeBed(state, world, pos); // Paper - check explode first + if (!this.kickVillagerOutOfBed(world, pos)) { + player.displayClientMessage(Component.translatable("block.minecraft.bed.occupied"), true); + } +@@ -166,8 +167,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + // CraftBukkit end + + public static boolean canSetSpawn(Level world) { +- // CraftBukkit - moved world and biome check into EntityHuman +- return true || world.dimensionType().bedWorks(); ++ return world.dimensionType().bedWorks(); // Paper - actually check if the bed works + } + + private boolean kickVillagerOutOfBed(Level world, BlockPos pos) { diff --git a/patches/server/0862-Treat-sequence-violations-like-they-should-be.patch b/patches/server/0862-Treat-sequence-violations-like-they-should-be.patch deleted file mode 100644 index f48fe55b712a..000000000000 --- a/patches/server/0862-Treat-sequence-violations-like-they-should-be.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Thu, 30 Mar 2023 03:13:58 +0100 -Subject: [PATCH] Treat sequence violations like they should be - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index ada6ba862fee7dc474d57141947f18610f6c5974..10c17dacef22caa4616e0f93e4004ca4e63b9af0 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1964,6 +1964,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - public void ackBlockChangesUpTo(int sequence) { - if (sequence < 0) { -+ this.disconnect("Expected packet sequence nr >= 0", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - Treat sequence violations like they should be - throw new IllegalArgumentException("Expected packet sequence nr >= 0"); - } else { - this.ackBlockChangesUpTo = Math.max(sequence, this.ackBlockChangesUpTo); diff --git a/patches/server/0868-Fix-demo-flag-not-enabling-demo-mode.patch b/patches/server/0863-Fix-demo-flag-not-enabling-demo-mode.patch similarity index 100% rename from patches/server/0868-Fix-demo-flag-not-enabling-demo-mode.patch rename to patches/server/0863-Fix-demo-flag-not-enabling-demo-mode.patch diff --git a/patches/server/0869-Add-Mob-Experience-reward-API.patch b/patches/server/0864-Add-Mob-Experience-reward-API.patch similarity index 100% rename from patches/server/0869-Add-Mob-Experience-reward-API.patch rename to patches/server/0864-Add-Mob-Experience-reward-API.patch diff --git a/patches/server/0864-Prevent-causing-expired-keys-from-impacting-new-join.patch b/patches/server/0864-Prevent-causing-expired-keys-from-impacting-new-join.patch deleted file mode 100644 index d1779637bfc6..000000000000 --- a/patches/server/0864-Prevent-causing-expired-keys-from-impacting-new-join.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Mon, 3 Apr 2023 08:55:52 +0100 -Subject: [PATCH] Prevent causing expired keys from impacting new joins - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java -index 0e54e8faa48751a651b953bec72543a94edf74bc..d43106eb89b14667e85cd6e8fa047d64f2e8ec87 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java -@@ -96,7 +96,13 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet { - serialized.chatSession = buf.readNullable(RemoteChatSession.Data::read); - }, (buf, entry) -> { -- buf.writeNullable(entry.chatSession, RemoteChatSession.Data::write); -+ // Paper start - Prevent causing expired keys from impacting new joins -+ RemoteChatSession.Data chatSession = entry.chatSession; -+ if (chatSession != null && chatSession.profilePublicKey().hasExpired()) { -+ chatSession = null; -+ } -+ buf.writeNullable(chatSession, RemoteChatSession.Data::write); -+ // Paper end - Prevent causing expired keys from impacting new joins - }), - UPDATE_GAME_MODE((serialized, buf) -> { - serialized.gameMode = GameType.byId(buf.readVarInt()); -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 10c17dacef22caa4616e0f93e4004ca4e63b9af0..0bc974ff4124ff6bfc355a6dac362574a649c476 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -286,6 +286,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private int knownMovePacketCount; - @Nullable - private RemoteChatSession chatSession; -+ private boolean hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins - private SignedMessageChain.Decoder signedMessageDecoder; - private final LastSeenMessagesValidator lastSeenMessages = new LastSeenMessagesValidator(20); - private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); -@@ -394,6 +395,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause - } - -+ // Paper start - Prevent causing expired keys from impacting new joins -+ if (!hasLoggedExpiry && this.chatSession != null && this.chatSession.profilePublicKey().data().hasExpired()) { -+ LOGGER.info("Player profile key for {} has expired!", this.player.getName().getString()); -+ hasLoggedExpiry = true; -+ } -+ // Paper end - Prevent causing expired keys from impacting new joins -+ - } - - public void resetPosition() { -@@ -3333,6 +3341,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - private void resetPlayerChatState(RemoteChatSession session) { - this.chatSession = session; -+ this.hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins - this.signedMessageDecoder = session.createMessageDecoder(this.player.getUUID()); - this.chatMessageChain.append(() -> { - this.player.setChatSession(session); diff --git a/patches/server/0870-Break-redstone-on-top-of-trap-doors-early.patch b/patches/server/0865-Break-redstone-on-top-of-trap-doors-early.patch similarity index 100% rename from patches/server/0870-Break-redstone-on-top-of-trap-doors-early.patch rename to patches/server/0865-Break-redstone-on-top-of-trap-doors-early.patch diff --git a/patches/server/0872-Avoid-Lazy-Initialization-for-Enum-Fields.patch b/patches/server/0866-Avoid-Lazy-Initialization-for-Enum-Fields.patch similarity index 100% rename from patches/server/0872-Avoid-Lazy-Initialization-for-Enum-Fields.patch rename to patches/server/0866-Avoid-Lazy-Initialization-for-Enum-Fields.patch diff --git a/patches/server/0866-Use-array-for-gamerule-storage.patch b/patches/server/0866-Use-array-for-gamerule-storage.patch deleted file mode 100644 index 1629a3b7cfba..000000000000 --- a/patches/server/0866-Use-array-for-gamerule-storage.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Paul Sauve -Date: Sun, 9 May 2021 16:49:49 -0500 -Subject: [PATCH] Use array for gamerule storage - - -diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java -index 0112ef51815c4fab38b95d5e917d335eeaaa21cd..c246981987017a2f86c5d632929356855e2b5714 100644 ---- a/src/main/java/net/minecraft/world/level/GameRules.java -+++ b/src/main/java/net/minecraft/world/level/GameRules.java -@@ -114,6 +114,7 @@ public class GameRules { - public static final GameRules.Key RULE_DO_VINES_SPREAD = GameRules.register("doVinesSpread", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true)); - public static final GameRules.Key RULE_ENDER_PEARLS_VANISH_ON_DEATH = GameRules.register("enderPearlsVanishOnDeath", GameRules.Category.PLAYER, GameRules.BooleanValue.create(true)); - private final Map, GameRules.Value> rules; -+ private final GameRules.Value[] gameruleArray; // Paper - Perf: Use array for gamerule storage - - private static > GameRules.Key register(String name, GameRules.Category category, GameRules.Type type) { - GameRules.Key gamerules_gamerulekey = new GameRules.Key<>(name, category); -@@ -132,17 +133,30 @@ public class GameRules { - } - - public GameRules() { -- this.rules = (Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> { -+ // Paper start - Perf: Use array for gamerule storage -+ this((Map) GameRules.GAME_RULE_TYPES.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry) -> { - return ((GameRules.Type) entry.getValue()).createRule(); -- })); -+ }))); -+ // Paper end - Perf: Use array for gamerule storage - } - - private GameRules(Map, GameRules.Value> rules) { - this.rules = rules; -+ -+ // Paper start - Perf: Use array for gamerule storage -+ int arraySize = rules.keySet().stream().mapToInt(key -> key.gameRuleIndex).max().orElse(-1) + 1; -+ GameRules.Value[] values = new GameRules.Value[arraySize]; -+ -+ for (Entry, GameRules.Value> entry : rules.entrySet()) { -+ values[entry.getKey().gameRuleIndex] = entry.getValue(); -+ } -+ -+ this.gameruleArray = values; -+ // Paper end - Perf: Use array for gamerule storage - } - - public > T getRule(GameRules.Key key) { -- return (T) this.rules.get(key); // CraftBukkit - decompile error -+ return key == null ? null : (T) this.gameruleArray[key.gameRuleIndex]; // Paper - Perf: Use array for gamerule storage - } - - public CompoundTag createTag() { -@@ -201,6 +215,10 @@ public class GameRules { - } - - public static final class Key> { -+ // Paper start - Perf: Use array for gamerule storage -+ private static int lastGameRuleIndex = 0; -+ public final int gameRuleIndex = lastGameRuleIndex++; -+ // Paper end - Perf: Use array for gamerule storage - - final String id; - private final GameRules.Category category; diff --git a/patches/server/0867-Fix-a-couple-of-upstream-bed-issues.patch b/patches/server/0867-Fix-a-couple-of-upstream-bed-issues.patch deleted file mode 100644 index 5e51d9930e5b..000000000000 --- a/patches/server/0867-Fix-a-couple-of-upstream-bed-issues.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 9 Apr 2023 21:11:58 -0700 -Subject: [PATCH] Fix a couple of upstream bed issues - -Upstream incorrectly skipped explosion logic if -the bed was occupied and added a "feature" where -if you set your spawn in a respawn anchor world -but then replaced it with a bed, you could respawn -at the bed in that world. - -diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java -index c6322da14262b8cc2a37ffef5149d008b74bd5e5..8677dc684bd2e0bb3cf5f77b659ce02b79627e76 100644 ---- a/src/main/java/net/minecraft/world/level/block/BedBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java -@@ -109,6 +109,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // Paper - add exploded state - return InteractionResult.SUCCESS; - } else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) { -+ if (!BedBlock.canSetSpawn(world)) return this.explodeBed(state, world, pos); // Paper - check explode first - if (!this.kickVillagerOutOfBed(world, pos)) { - player.displayClientMessage(Component.translatable("block.minecraft.bed.occupied"), true); - } -@@ -166,8 +167,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - // CraftBukkit end - - public static boolean canSetSpawn(Level world) { -- // CraftBukkit - moved world and biome check into EntityHuman -- return true || world.dimensionType().bedWorks(); -+ return world.dimensionType().bedWorks(); // Paper - actually check if the bed works - } - - private boolean kickVillagerOutOfBed(Level world, BlockPos pos) { diff --git a/patches/server/0873-More-accurate-isInOpenWater-impl.patch b/patches/server/0867-More-accurate-isInOpenWater-impl.patch similarity index 100% rename from patches/server/0873-More-accurate-isInOpenWater-impl.patch rename to patches/server/0867-More-accurate-isInOpenWater-impl.patch diff --git a/patches/server/0874-Fix-concurrenct-access-to-lookups-field-in-RegistryO.patch b/patches/server/0868-Fix-concurrenct-access-to-lookups-field-in-RegistryO.patch similarity index 100% rename from patches/server/0874-Fix-concurrenct-access-to-lookups-field-in-RegistryO.patch rename to patches/server/0868-Fix-concurrenct-access-to-lookups-field-in-RegistryO.patch diff --git a/patches/server/0869-Expand-PlayerItemMendEvent.patch b/patches/server/0869-Expand-PlayerItemMendEvent.patch new file mode 100644 index 000000000000..e60315546443 --- /dev/null +++ b/patches/server/0869-Expand-PlayerItemMendEvent.patch @@ -0,0 +1,69 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 20 Jan 2022 18:11:20 -0800 +Subject: [PATCH] Expand PlayerItemMendEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +index 36d5d1736bf826f3abc756277de431c94cabb744..e814b2ef2577f032d6760de2f798d4fe18c67d0c 100644 +--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java ++++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +@@ -337,7 +337,7 @@ public class ExperienceOrb extends Entity { + ItemStack itemstack = (ItemStack) entry.getValue(); + int j = Math.min(this.xpToDurability(amount), itemstack.getDamageValue()); + // CraftBukkit start +- org.bukkit.event.player.PlayerItemMendEvent event = CraftEventFactory.callPlayerItemMendEvent(player, this, itemstack, entry.getKey(), j); ++ org.bukkit.event.player.PlayerItemMendEvent event = CraftEventFactory.callPlayerItemMendEvent(player, this, itemstack, entry.getKey(), j, this::durabilityToXp); // Paper - Expand PlayerItemMendEvent + j = event.getRepairAmount(); + if (event.isCancelled()) { + return amount; +@@ -345,8 +345,13 @@ public class ExperienceOrb extends Entity { + // CraftBukkit end + + itemstack.setDamageValue(itemstack.getDamageValue() - j); +- int k = amount - this.durabilityToXp(j); ++ int k = amount - event.getDurabilityToXpOperation().applyAsInt(j); // Paper - Expand PlayerItemMendEvent + this.value = k; // CraftBukkit - update exp value of orb for PlayerItemMendEvent calls ++ // Paper start - Expand PlayerItemMendEvent ++ if (j == 0 && amount == k) { // if repair amount is 0 and no xp was removed, don't do recursion; treat as cancelled ++ return k; ++ } ++ // Paper end - Expand PlayerItemMendEvent + + return k > 0 ? this.repairPlayerItems(player, k) : 0; + } else { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 04e5be79348733f5a6a8b1968b6887379fa65027..c7e0aa94a8c1f821f723f323b69bacfd2d2d8aa3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1757,11 +1757,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + orb.setPosRaw(handle.getX(), handle.getY(), handle.getZ()); + + int i = Math.min(orb.xpToDurability(amount), itemstack.getDamageValue()); +- org.bukkit.event.player.PlayerItemMendEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemMendEvent(handle, orb, itemstack, stackEntry.getKey(), i); ++ org.bukkit.event.player.PlayerItemMendEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemMendEvent(handle, orb, itemstack, stackEntry.getKey(), i, orb::durabilityToXp); // Paper - Expand PlayerItemMendEvent + i = event.getRepairAmount(); + orb.discard(); + if (!event.isCancelled()) { +- amount -= orb.durabilityToXp(i); ++ amount -= event.getDurabilityToXpOperation().applyAsInt(i); // Paper - Expand PlayerItemMendEvent + itemstack.setDamageValue(itemstack.getDamageValue() - i); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index e1d19207ba70e1ecc85af2ca4cfd6ed8c4d4a9e7..7934eea4ede9db90ad2a45a2a5ac64b264b2f91a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1270,10 +1270,10 @@ public class CraftEventFactory { + return event; + } + +- public static PlayerItemMendEvent callPlayerItemMendEvent(net.minecraft.world.entity.player.Player entity, net.minecraft.world.entity.ExperienceOrb orb, net.minecraft.world.item.ItemStack nmsMendedItem, net.minecraft.world.entity.EquipmentSlot slot, int repairAmount) { ++ public static PlayerItemMendEvent callPlayerItemMendEvent(net.minecraft.world.entity.player.Player entity, net.minecraft.world.entity.ExperienceOrb orb, net.minecraft.world.item.ItemStack nmsMendedItem, net.minecraft.world.entity.EquipmentSlot slot, int repairAmount, java.util.function.IntUnaryOperator durabilityToXpOp) { // Paper - Expand PlayerItemMendEvent + Player player = (Player) entity.getBukkitEntity(); + org.bukkit.inventory.ItemStack bukkitStack = CraftItemStack.asCraftMirror(nmsMendedItem); +- PlayerItemMendEvent event = new PlayerItemMendEvent(player, bukkitStack, CraftEquipmentSlot.getSlot(slot), (ExperienceOrb) orb.getBukkitEntity(), repairAmount); ++ PlayerItemMendEvent event = new PlayerItemMendEvent(player, bukkitStack, CraftEquipmentSlot.getSlot(slot), (ExperienceOrb) orb.getBukkitEntity(), repairAmount, durabilityToXpOp); // Paper - Expand PlayerItemMendEvent + Bukkit.getPluginManager().callEvent(event); + return event; + } diff --git a/patches/server/0870-Refresh-ProjectileSource-for-projectiles.patch b/patches/server/0870-Refresh-ProjectileSource-for-projectiles.patch new file mode 100644 index 000000000000..b8d815849bba --- /dev/null +++ b/patches/server/0870-Refresh-ProjectileSource-for-projectiles.patch @@ -0,0 +1,85 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 30 May 2023 12:59:10 -0700 +Subject: [PATCH] Refresh ProjectileSource for projectiles + +Makes sure the value returned by Projectile#getShooter in +the API matches the owner UUID specified in the entity nbt. +Previously, after the entity reloaded, Projectile#getShooter +would return null, while the entity still had an owner. + +Also fixes setting the shooter/owner to null actually +clearing the owner. + +Co-authored-by: Warrior <50800980+Warriorrrr@users.noreply.github.com> + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 576cef82f7ea57642430897de2e5212cae234701..0bc310f8edb886c84aa71a7502e297127647cbe5 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -383,6 +383,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + public boolean inWorld = false; + public boolean generation; + public int maxAirTicks = this.getDefaultMaxAirSupply(); // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() ++ @Nullable // Paper - Refresh ProjectileSource for projectiles + public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only + public boolean lastDamageCancelled; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled + public boolean persistentInvisibility = false; +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 0a207f3f2e4c0790e784fb4b0c3c2dfa49c39724..156809090f1f83ad68e7e2477a3cfddac5757a8e 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +@@ -50,14 +50,31 @@ public abstract class Projectile extends Entity implements TraceableEntity { + this.ownerUUID = entity.getUUID(); + this.cachedOwner = entity; + } +- this.projectileSource = (entity != null && entity.getBukkitEntity() instanceof ProjectileSource) ? (ProjectileSource) entity.getBukkitEntity() : null; // CraftBukkit +- ++ // Paper start - Refresh ProjectileSource for projectiles ++ else { ++ this.ownerUUID = null; ++ this.cachedOwner = null; ++ this.projectileSource = null; ++ } ++ // Paper end - Refresh ProjectileSource for projectiles ++ this.refreshProjectileSource(false); // Paper ++ } ++ // Paper start - Refresh ProjectileSource for projectiles ++ public void refreshProjectileSource(boolean fillCache) { ++ if (fillCache) { ++ this.getOwner(); ++ } ++ if (this.cachedOwner != null && !this.cachedOwner.isRemoved() && this.projectileSource == null && this.cachedOwner.getBukkitEntity() instanceof ProjectileSource projSource) { ++ this.projectileSource = projSource; ++ } + } ++ // Paper end - Refresh ProjectileSource for projectiles + + @Nullable + @Override + public Entity getOwner() { + if (this.cachedOwner != null && !this.cachedOwner.isRemoved()) { ++ this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles + return this.cachedOwner; + } else { + if (this.ownerUUID != null) { +@@ -67,6 +84,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { + ServerLevel worldserver = (ServerLevel) world; + + this.cachedOwner = worldserver.getEntity(this.ownerUUID); ++ this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles + return this.cachedOwner; + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java +index c1c52f4fc5f900fac4098e5e37c52dfc4e82b8bb..236f94348ff8da661e23e3e17b7fc1b143680da9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java +@@ -60,6 +60,7 @@ public abstract class AbstractProjectile extends CraftEntity implements Projecti + + @Override + public final org.bukkit.projectiles.ProjectileSource getShooter() { ++ this.getHandle().refreshProjectileSource(true); // Paper - Refresh ProjectileSource for projectiles + return this.getHandle().projectileSource; + } + diff --git a/patches/server/0877-Add-transient-modifier-API.patch b/patches/server/0871-Add-transient-modifier-API.patch similarity index 100% rename from patches/server/0877-Add-transient-modifier-API.patch rename to patches/server/0871-Add-transient-modifier-API.patch diff --git a/patches/server/0871-Fix-DamageCause-for-Falling-Blocks.patch b/patches/server/0871-Fix-DamageCause-for-Falling-Blocks.patch deleted file mode 100644 index 1edf21d719b6..000000000000 --- a/patches/server/0871-Fix-DamageCause-for-Falling-Blocks.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Doc -Date: Mon, 1 May 2023 13:34:57 -0400 -Subject: [PATCH] Fix DamageCause for Falling Blocks - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 0e640dc081228484a4568f2c5fed71610ac1c6c6..734f68cee62a31513bdfefa5c7074ac38b3c1622 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1111,6 +1111,11 @@ public class CraftEventFactory { - } else if (source.is(DamageTypes.SONIC_BOOM)) { - cause = DamageCause.SONIC_BOOM; - } -+ // Paper start - fix falling block handling -+ else if (source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_BLOCK) || source.is(DamageTypes.FALLING_ANVIL)) { -+ cause = DamageCause.FALLING_BLOCK; -+ } -+ // Paper end - fix falling block handling - - return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API - } else if (source.is(DamageTypes.FELL_OUT_OF_WORLD)) { diff --git a/patches/server/0872-Fix-block-place-logic.patch b/patches/server/0872-Fix-block-place-logic.patch new file mode 100644 index 000000000000..d94615de794c --- /dev/null +++ b/patches/server/0872-Fix-block-place-logic.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Mon, 3 Apr 2023 18:46:49 +0200 +Subject: [PATCH] Fix block place logic + +Fix several issues when a player interact with a block: +* the place sound isn't played for the dispensed shulker block +* desync of the jukebox blocks between bukkit handler and the vanilla interaction +* poi can desync when the BlockPhysicsEvent is cancelled + +diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java +index 1ac739c92031e80c35e1af4417e6358346079580..8c54b92b65b3d379e14a11370b09d45351ab22e1 100644 +--- a/src/main/java/net/minecraft/world/item/BlockItem.java ++++ b/src/main/java/net/minecraft/world/item/BlockItem.java +@@ -130,7 +130,7 @@ public class BlockItem extends Item { + + SoundType soundeffecttype = iblockdata1.getSoundType(); + +- // world.playSound(entityhuman, blockposition, this.getPlaceSound(iblockdata1), SoundCategory.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); ++ if (entityhuman == null) world.playSound(entityhuman, blockposition, this.getPlaceSound(iblockdata1), net.minecraft.sounds.SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); // Paper - Fix block place logic; reintroduce this for the dispenser (i.e the shulker) + world.gameEvent(GameEvent.BLOCK_PLACE, blockposition, GameEvent.Context.of(entityhuman, iblockdata1)); + if ((entityhuman == null || !entityhuman.getAbilities().instabuild) && itemstack != ItemStack.EMPTY) { // CraftBukkit + itemstack.shrink(1); +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 59c4550b4cb8b0317f5256efc9376265f4583b60..964d6af9eb9752b9e08f712c80b67bd9410d1554 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -494,13 +494,7 @@ public final class ItemStack { + if (tileentity instanceof JukeboxBlockEntity) { + JukeboxBlockEntity tileentityjukebox = (JukeboxBlockEntity) tileentity; + +- // There can only be one +- ItemStack record = this.copy(); +- if (!record.isEmpty()) { +- record.setCount(1); +- } +- +- tileentityjukebox.setTheItem(record); ++ tileentityjukebox.setTheItem(this.copy()); // Paper - Fix block place logic; sync this with record item, jukebox has now an inventory + world.gameEvent(GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entityhuman, world.getBlockState(blockposition))); + } + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index cb4be07de48cbb18d6b7cd79f57aae40cfcc8a56..b942e9f163fa342c58b74d1cd6ffe6bdbe4f691a 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -560,17 +560,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // CraftBukkit start + iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam + CraftWorld world = ((ServerLevel) this).getWorld(); ++ boolean cancelledUpdates = false; // Paper - Fix block place logic + if (world != null && ((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent + BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata)); + this.getCraftServer().getPluginManager().callEvent(event); + +- if (event.isCancelled()) { +- return; +- } ++ cancelledUpdates = event.isCancelled(); // Paper - Fix block place logic + } + // CraftBukkit end ++ if (!cancelledUpdates) { // Paper - Fix block place logic + iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1); + iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); ++ } // Paper - Fix block place logic + } + + // CraftBukkit start - SPIGOT-5710 diff --git a/patches/server/0873-Fix-spigot-sound-playing-for-BlockItem-ItemStacks.patch b/patches/server/0873-Fix-spigot-sound-playing-for-BlockItem-ItemStacks.patch new file mode 100644 index 000000000000..288b2c371172 --- /dev/null +++ b/patches/server/0873-Fix-spigot-sound-playing-for-BlockItem-ItemStacks.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Thu, 8 Jun 2023 20:23:13 -0400 +Subject: [PATCH] Fix spigot sound playing for BlockItem ItemStacks + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 964d6af9eb9752b9e08f712c80b67bd9410d1554..30cf71507c45432b2b5ac636aff852acf662ce60 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -545,7 +545,11 @@ public final class ItemStack { + + // SPIGOT-1288 - play sound stripped from ItemBlock + if (this.item instanceof BlockItem) { +- SoundType soundeffecttype = ((BlockItem) this.item).getBlock().defaultBlockState().getSoundType(); // TODO: not strictly correct, however currently only affects decorated pots ++ // Paper start - Fix spigot sound playing for BlockItem ItemStacks ++ BlockPos position = new net.minecraft.world.item.context.BlockPlaceContext(context).getClickedPos(); ++ net.minecraft.world.level.block.state.BlockState blockData = world.getBlockState(position); ++ SoundType soundeffecttype = blockData.getSoundType(); ++ // Paper end - Fix spigot sound playing for BlockItem ItemStacks + world.playSound(entityhuman, blockposition, soundeffecttype.getPlaceSound(), SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); + } + diff --git a/patches/server/0880-Call-BlockGrowEvent-for-missing-blocks.patch b/patches/server/0874-Call-BlockGrowEvent-for-missing-blocks.patch similarity index 100% rename from patches/server/0880-Call-BlockGrowEvent-for-missing-blocks.patch rename to patches/server/0874-Call-BlockGrowEvent-for-missing-blocks.patch diff --git a/patches/server/0875-Don-t-enforce-icanhasbukkit-default-if-alias-block-e.patch b/patches/server/0875-Don-t-enforce-icanhasbukkit-default-if-alias-block-e.patch new file mode 100644 index 000000000000..954f7480a557 --- /dev/null +++ b/patches/server/0875-Don-t-enforce-icanhasbukkit-default-if-alias-block-e.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheMeinerLP +Date: Tue, 13 Jun 2023 16:10:59 +0200 +Subject: [PATCH] Don't enforce icanhasbukkit default if alias block exists + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 10450a2bb6973a647fb6969d3c142e7a6a1011a4..56a6b3921c74bdeb27f8736302503bee1f731065 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -354,7 +354,11 @@ public final class CraftServer implements Server { + } + this.commandsConfiguration = YamlConfiguration.loadConfiguration(this.getCommandsConfigFile()); + this.commandsConfiguration.options().copyDefaults(true); +- this.commandsConfiguration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8))); ++ // Paper start - don't enforce icanhasbukkit default if alias block exists ++ final YamlConfiguration commandsDefaults = YamlConfiguration.loadConfiguration(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8)); ++ if (this.commandsConfiguration.contains("aliases")) commandsDefaults.set("aliases", null); ++ this.commandsConfiguration.setDefaults(commandsDefaults); ++ // Paper end - don't enforce icanhasbukkit default if alias block exists + this.saveCommandsConfig(); + + // Migrate aliases from old file and add previously implicit $1- to pass all arguments diff --git a/patches/server/0875-Expand-PlayerItemMendEvent.patch b/patches/server/0875-Expand-PlayerItemMendEvent.patch deleted file mode 100644 index 6e8c66232a35..000000000000 --- a/patches/server/0875-Expand-PlayerItemMendEvent.patch +++ /dev/null @@ -1,69 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 20 Jan 2022 18:11:20 -0800 -Subject: [PATCH] Expand PlayerItemMendEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -index 36d5d1736bf826f3abc756277de431c94cabb744..e814b2ef2577f032d6760de2f798d4fe18c67d0c 100644 ---- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -+++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -@@ -337,7 +337,7 @@ public class ExperienceOrb extends Entity { - ItemStack itemstack = (ItemStack) entry.getValue(); - int j = Math.min(this.xpToDurability(amount), itemstack.getDamageValue()); - // CraftBukkit start -- org.bukkit.event.player.PlayerItemMendEvent event = CraftEventFactory.callPlayerItemMendEvent(player, this, itemstack, entry.getKey(), j); -+ org.bukkit.event.player.PlayerItemMendEvent event = CraftEventFactory.callPlayerItemMendEvent(player, this, itemstack, entry.getKey(), j, this::durabilityToXp); // Paper - Expand PlayerItemMendEvent - j = event.getRepairAmount(); - if (event.isCancelled()) { - return amount; -@@ -345,8 +345,13 @@ public class ExperienceOrb extends Entity { - // CraftBukkit end - - itemstack.setDamageValue(itemstack.getDamageValue() - j); -- int k = amount - this.durabilityToXp(j); -+ int k = amount - event.getDurabilityToXpOperation().applyAsInt(j); // Paper - Expand PlayerItemMendEvent - this.value = k; // CraftBukkit - update exp value of orb for PlayerItemMendEvent calls -+ // Paper start - Expand PlayerItemMendEvent -+ if (j == 0 && amount == k) { // if repair amount is 0 and no xp was removed, don't do recursion; treat as cancelled -+ return k; -+ } -+ // Paper end - Expand PlayerItemMendEvent - - return k > 0 ? this.repairPlayerItems(player, k) : 0; - } else { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 561703f56637046bc274c378a63c03f684b5b787..5190613932bc1084d617f49e1517a9942e3765fc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1727,11 +1727,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - orb.setPosRaw(handle.getX(), handle.getY(), handle.getZ()); - - int i = Math.min(orb.xpToDurability(amount), itemstack.getDamageValue()); -- org.bukkit.event.player.PlayerItemMendEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemMendEvent(handle, orb, itemstack, stackEntry.getKey(), i); -+ org.bukkit.event.player.PlayerItemMendEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemMendEvent(handle, orb, itemstack, stackEntry.getKey(), i, orb::durabilityToXp); // Paper - Expand PlayerItemMendEvent - i = event.getRepairAmount(); - orb.discard(); - if (!event.isCancelled()) { -- amount -= orb.durabilityToXp(i); -+ amount -= event.getDurabilityToXpOperation().applyAsInt(i); // Paper - Expand PlayerItemMendEvent - itemstack.setDamageValue(itemstack.getDamageValue() - i); - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 734f68cee62a31513bdfefa5c7074ac38b3c1622..1325f680ce03b6ffae04f5c519b789fdd10ed8a3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1347,10 +1347,10 @@ public class CraftEventFactory { - return event; - } - -- public static PlayerItemMendEvent callPlayerItemMendEvent(net.minecraft.world.entity.player.Player entity, net.minecraft.world.entity.ExperienceOrb orb, net.minecraft.world.item.ItemStack nmsMendedItem, net.minecraft.world.entity.EquipmentSlot slot, int repairAmount) { -+ public static PlayerItemMendEvent callPlayerItemMendEvent(net.minecraft.world.entity.player.Player entity, net.minecraft.world.entity.ExperienceOrb orb, net.minecraft.world.item.ItemStack nmsMendedItem, net.minecraft.world.entity.EquipmentSlot slot, int repairAmount, java.util.function.IntUnaryOperator durabilityToXpOp) { // Paper - Expand PlayerItemMendEvent - Player player = (Player) entity.getBukkitEntity(); - org.bukkit.inventory.ItemStack bukkitStack = CraftItemStack.asCraftMirror(nmsMendedItem); -- PlayerItemMendEvent event = new PlayerItemMendEvent(player, bukkitStack, CraftEquipmentSlot.getSlot(slot), (ExperienceOrb) orb.getBukkitEntity(), repairAmount); -+ PlayerItemMendEvent event = new PlayerItemMendEvent(player, bukkitStack, CraftEquipmentSlot.getSlot(slot), (ExperienceOrb) orb.getBukkitEntity(), repairAmount, durabilityToXpOp); // Paper - Expand PlayerItemMendEvent - Bukkit.getPluginManager().callEvent(event); - return event; - } diff --git a/patches/server/0876-Refresh-ProjectileSource-for-projectiles.patch b/patches/server/0876-Refresh-ProjectileSource-for-projectiles.patch deleted file mode 100644 index dae5f4e3f7a6..000000000000 --- a/patches/server/0876-Refresh-ProjectileSource-for-projectiles.patch +++ /dev/null @@ -1,85 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 30 May 2023 12:59:10 -0700 -Subject: [PATCH] Refresh ProjectileSource for projectiles - -Makes sure the value returned by Projectile#getShooter in -the API matches the owner UUID specified in the entity nbt. -Previously, after the entity reloaded, Projectile#getShooter -would return null, while the entity still had an owner. - -Also fixes setting the shooter/owner to null actually -clearing the owner. - -Co-authored-by: Warrior <50800980+Warriorrrr@users.noreply.github.com> - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 287450e58076e63f9686a1ad2c1148c24a5c7252..1ce9f436fee9722fc7367447333db8e13230c156 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -383,6 +383,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - public boolean inWorld = false; - public boolean generation; - public int maxAirTicks = this.getDefaultMaxAirSupply(); // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() -+ @Nullable // Paper - Refresh ProjectileSource for projectiles - public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only - public boolean lastDamageCancelled; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled - public boolean persistentInvisibility = false; -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 0a207f3f2e4c0790e784fb4b0c3c2dfa49c39724..156809090f1f83ad68e7e2477a3cfddac5757a8e 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -50,14 +50,31 @@ public abstract class Projectile extends Entity implements TraceableEntity { - this.ownerUUID = entity.getUUID(); - this.cachedOwner = entity; - } -- this.projectileSource = (entity != null && entity.getBukkitEntity() instanceof ProjectileSource) ? (ProjectileSource) entity.getBukkitEntity() : null; // CraftBukkit -- -+ // Paper start - Refresh ProjectileSource for projectiles -+ else { -+ this.ownerUUID = null; -+ this.cachedOwner = null; -+ this.projectileSource = null; -+ } -+ // Paper end - Refresh ProjectileSource for projectiles -+ this.refreshProjectileSource(false); // Paper -+ } -+ // Paper start - Refresh ProjectileSource for projectiles -+ public void refreshProjectileSource(boolean fillCache) { -+ if (fillCache) { -+ this.getOwner(); -+ } -+ if (this.cachedOwner != null && !this.cachedOwner.isRemoved() && this.projectileSource == null && this.cachedOwner.getBukkitEntity() instanceof ProjectileSource projSource) { -+ this.projectileSource = projSource; -+ } - } -+ // Paper end - Refresh ProjectileSource for projectiles - - @Nullable - @Override - public Entity getOwner() { - if (this.cachedOwner != null && !this.cachedOwner.isRemoved()) { -+ this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles - return this.cachedOwner; - } else { - if (this.ownerUUID != null) { -@@ -67,6 +84,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { - ServerLevel worldserver = (ServerLevel) world; - - this.cachedOwner = worldserver.getEntity(this.ownerUUID); -+ this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles - return this.cachedOwner; - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -index c1c52f4fc5f900fac4098e5e37c52dfc4e82b8bb..236f94348ff8da661e23e3e17b7fc1b143680da9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -@@ -60,6 +60,7 @@ public abstract class AbstractProjectile extends CraftEntity implements Projecti - - @Override - public final org.bukkit.projectiles.ProjectileSource getShooter() { -+ this.getHandle().refreshProjectileSource(true); // Paper - Refresh ProjectileSource for projectiles - return this.getHandle().projectileSource; - } - diff --git a/patches/server/0882-fix-MapLike-spam-for-missing-key-selector.patch b/patches/server/0876-fix-MapLike-spam-for-missing-key-selector.patch similarity index 100% rename from patches/server/0882-fix-MapLike-spam-for-missing-key-selector.patch rename to patches/server/0876-fix-MapLike-spam-for-missing-key-selector.patch diff --git a/patches/server/0883-Fix-sniffer-removeExploredLocation.patch b/patches/server/0877-Fix-sniffer-removeExploredLocation.patch similarity index 100% rename from patches/server/0883-Fix-sniffer-removeExploredLocation.patch rename to patches/server/0877-Fix-sniffer-removeExploredLocation.patch diff --git a/patches/server/0878-Add-method-to-remove-all-active-potion-effects.patch b/patches/server/0878-Add-method-to-remove-all-active-potion-effects.patch new file mode 100644 index 000000000000..73565fe49ae8 --- /dev/null +++ b/patches/server/0878-Add-method-to-remove-all-active-potion-effects.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 17 Jun 2023 13:17:25 -0700 +Subject: [PATCH] Add method to remove all active potion effects + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 4abfe34ee595fef3e1253090410c6309d3459a84..f1e4dfd203d455ec089cd2b5679c4083400dbc7f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -540,6 +540,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return effects; + } + ++ // Paper start - LivingEntity#clearActivePotionEffects(); ++ @Override ++ public boolean clearActivePotionEffects() { ++ return this.getHandle().removeAllEffects(EntityPotionEffectEvent.Cause.PLUGIN); ++ } ++ // Paper end ++ + @Override + public T launchProjectile(Class projectile) { + return this.launchProjectile(projectile, null); diff --git a/patches/server/0878-Fix-block-place-logic.patch b/patches/server/0878-Fix-block-place-logic.patch deleted file mode 100644 index 63d644e31e14..000000000000 --- a/patches/server/0878-Fix-block-place-logic.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Mon, 3 Apr 2023 18:46:49 +0200 -Subject: [PATCH] Fix block place logic - -Fix several issues when a player interact with a block: -* the place sound isn't played for the dispensed shulker block -* desync of the jukebox blocks between bukkit handler and the vanilla interaction -* poi can desync when the BlockPhysicsEvent is cancelled - -diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java -index 1ac739c92031e80c35e1af4417e6358346079580..8c54b92b65b3d379e14a11370b09d45351ab22e1 100644 ---- a/src/main/java/net/minecraft/world/item/BlockItem.java -+++ b/src/main/java/net/minecraft/world/item/BlockItem.java -@@ -130,7 +130,7 @@ public class BlockItem extends Item { - - SoundType soundeffecttype = iblockdata1.getSoundType(); - -- // world.playSound(entityhuman, blockposition, this.getPlaceSound(iblockdata1), SoundCategory.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); -+ if (entityhuman == null) world.playSound(entityhuman, blockposition, this.getPlaceSound(iblockdata1), net.minecraft.sounds.SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); // Paper - Fix block place logic; reintroduce this for the dispenser (i.e the shulker) - world.gameEvent(GameEvent.BLOCK_PLACE, blockposition, GameEvent.Context.of(entityhuman, iblockdata1)); - if ((entityhuman == null || !entityhuman.getAbilities().instabuild) && itemstack != ItemStack.EMPTY) { // CraftBukkit - itemstack.shrink(1); -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 0e9d515381a673e683b63a12c1a9e79a5eedd80b..096eb30dcfdd62b1d946891f7480e9d9c4dbb71d 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -493,13 +493,7 @@ public final class ItemStack { - if (tileentity instanceof JukeboxBlockEntity) { - JukeboxBlockEntity tileentityjukebox = (JukeboxBlockEntity) tileentity; - -- // There can only be one -- ItemStack record = this.copy(); -- if (!record.isEmpty()) { -- record.setCount(1); -- } -- -- tileentityjukebox.setTheItem(record); -+ tileentityjukebox.setTheItem(this.copy()); // Paper - Fix block place logic; sync this with record item, jukebox has now an inventory - world.gameEvent(GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entityhuman, world.getBlockState(blockposition))); - } - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index cb4be07de48cbb18d6b7cd79f57aae40cfcc8a56..b942e9f163fa342c58b74d1cd6ffe6bdbe4f691a 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -560,17 +560,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - // CraftBukkit start - iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam - CraftWorld world = ((ServerLevel) this).getWorld(); -+ boolean cancelledUpdates = false; // Paper - Fix block place logic - if (world != null && ((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent - BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata)); - this.getCraftServer().getPluginManager().callEvent(event); - -- if (event.isCancelled()) { -- return; -- } -+ cancelledUpdates = event.isCancelled(); // Paper - Fix block place logic - } - // CraftBukkit end -+ if (!cancelledUpdates) { // Paper - Fix block place logic - iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1); - iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); -+ } // Paper - Fix block place logic - } - - // CraftBukkit start - SPIGOT-5710 diff --git a/patches/server/0885-Fix-incorrect-crafting-result-amount-for-fireworks.patch b/patches/server/0879-Fix-incorrect-crafting-result-amount-for-fireworks.patch similarity index 100% rename from patches/server/0885-Fix-incorrect-crafting-result-amount-for-fireworks.patch rename to patches/server/0879-Fix-incorrect-crafting-result-amount-for-fireworks.patch diff --git a/patches/server/0879-Fix-spigot-sound-playing-for-BlockItem-ItemStacks.patch b/patches/server/0879-Fix-spigot-sound-playing-for-BlockItem-ItemStacks.patch deleted file mode 100644 index 3b104d8aa337..000000000000 --- a/patches/server/0879-Fix-spigot-sound-playing-for-BlockItem-ItemStacks.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Thu, 8 Jun 2023 20:23:13 -0400 -Subject: [PATCH] Fix spigot sound playing for BlockItem ItemStacks - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 096eb30dcfdd62b1d946891f7480e9d9c4dbb71d..ecea96202f81e4955c0af554da070984e6e416ff 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -544,7 +544,11 @@ public final class ItemStack { - - // SPIGOT-1288 - play sound stripped from ItemBlock - if (this.item instanceof BlockItem) { -- SoundType soundeffecttype = ((BlockItem) this.item).getBlock().defaultBlockState().getSoundType(); // TODO: not strictly correct, however currently only affects decorated pots -+ // Paper start - Fix spigot sound playing for BlockItem ItemStacks -+ BlockPos position = new net.minecraft.world.item.context.BlockPlaceContext(context).getClickedPos(); -+ net.minecraft.world.level.block.state.BlockState blockData = world.getBlockState(position); -+ SoundType soundeffecttype = blockData.getSoundType(); -+ // Paper end - Fix spigot sound playing for BlockItem ItemStacks - world.playSound(entityhuman, blockposition, soundeffecttype.getPlaceSound(), SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F); - } - diff --git a/patches/server/0880-Add-event-for-player-editing-sign.patch b/patches/server/0880-Add-event-for-player-editing-sign.patch new file mode 100644 index 000000000000..5e205901e517 --- /dev/null +++ b/patches/server/0880-Add-event-for-player-editing-sign.patch @@ -0,0 +1,93 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: by77er +Date: Mon, 12 Jun 2023 12:56:46 -0400 +Subject: [PATCH] Add event for player editing sign + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 30cf71507c45432b2b5ac636aff852acf662ce60..5a86b2c205250ddcd833a15accb27ca4a580eadd 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -524,7 +524,7 @@ public final class ItemStack { + try { + if (world.getBlockEntity(SignItem.openSign) instanceof SignBlockEntity tileentitysign) { + if (world.getBlockState(SignItem.openSign).getBlock() instanceof SignBlock blocksign) { +- blocksign.openTextEdit(entityhuman, tileentitysign, true, org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLACE); // Craftbukkit ++ blocksign.openTextEdit(entityhuman, tileentitysign, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // Paper - Add PlayerOpenSignEvent + } + } + } finally { +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 57b79e7fa34755e68b06f5b3010e68745cabbb7e..27a1e8ffc43efe4e086e7fd88ee4d80c23f98674 100644 +--- a/src/main/java/net/minecraft/world/level/block/SignBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SignBlock.java +@@ -118,7 +118,7 @@ public abstract class SignBlock extends BaseEntityBlock implements SimpleWaterlo + } else if (flag2) { + return InteractionResult.SUCCESS; + } else if (!this.otherPlayerIsEditingSign(player, tileentitysign) && player.mayBuild() && this.hasEditableText(player, tileentitysign, flag1)) { +- this.openTextEdit(player, tileentitysign, flag1, org.bukkit.event.player.PlayerSignOpenEvent.Cause.INTERACT); // CraftBukkit ++ this.openTextEdit(player, tileentitysign, flag1, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.INTERACT); // Paper - Add PlayerOpenSignEvent + return this.getInteractionResult(flag); + } else { + return InteractionResult.PASS; +@@ -170,16 +170,33 @@ public abstract class SignBlock extends BaseEntityBlock implements SimpleWaterlo + return blockpropertywood; + } + ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - Add PlayerOpenSignEvent + public void openTextEdit(Player player, SignBlockEntity blockEntity, boolean front) { +- // Craftbukkit start +- this.openTextEdit(player, blockEntity, front, org.bukkit.event.player.PlayerSignOpenEvent.Cause.UNKNOWN); +- } +- +- public void openTextEdit(Player entityhuman, SignBlockEntity tileentitysign, boolean flag, org.bukkit.event.player.PlayerSignOpenEvent.Cause cause) { +- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerSignOpenEvent(entityhuman, tileentitysign, flag, cause)) { ++ // Paper start - Add PlayerOpenSignEvent ++ this.openTextEdit(player, blockEntity, front, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.UNKNOWN); ++ } ++ public void openTextEdit(Player entityhuman, SignBlockEntity tileentitysign, boolean flag, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause cause) { ++ org.bukkit.entity.Player bukkitPlayer = (org.bukkit.entity.Player) entityhuman.getBukkitEntity(); ++ org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(tileentitysign.getLevel(), tileentitysign.getBlockPos()); ++ org.bukkit.craftbukkit.block.CraftSign bukkitSign = (org.bukkit.craftbukkit.block.CraftSign) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(bukkitBlock); ++ io.papermc.paper.event.player.PlayerOpenSignEvent event = new io.papermc.paper.event.player.PlayerOpenSignEvent( ++ bukkitPlayer, ++ bukkitSign, ++ flag ? org.bukkit.block.sign.Side.FRONT : org.bukkit.block.sign.Side.BACK, ++ cause); ++ if (!event.callEvent()) return; ++ if (org.bukkit.event.player.PlayerSignOpenEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ final org.bukkit.event.player.PlayerSignOpenEvent.Cause legacyCause = switch (cause) { ++ case PLACE -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLACE; ++ case PLUGIN -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLUGIN; ++ case INTERACT -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.INTERACT; ++ case UNKNOWN -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.UNKNOWN; ++ }; ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerSignOpenEvent(entityhuman, tileentitysign, flag, legacyCause)) { ++ // Paper end - Add PlayerOpenSignEvent + return; + } +- // Craftbukkit end ++ } // Paper - Add PlayerOpenSignEvent + tileentitysign.setAllowedPlayerEditor(entityhuman.getUUID()); + entityhuman.openTextEdit(tileentitysign, flag); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +index 94caa0915e1a9ec1c46c7a0380db840901cc8063..3ebfc8e5b5462e6e532f8e8901fd5f8f386bbf34 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +@@ -139,9 +139,15 @@ public class CraftSign extends CraftBlockEntityState< + Preconditions.checkArgument(sign.isPlaced(), "Sign must be placed"); + Preconditions.checkArgument(sign.getWorld() == player.getWorld(), "Sign must be in same world as Player"); + ++ // Paper start - Add PlayerOpenSignEvent ++ io.papermc.paper.event.player.PlayerOpenSignEvent event = new io.papermc.paper.event.player.PlayerOpenSignEvent((Player) player, sign, side, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLUGIN); ++ if (!event.callEvent()) return; ++ if (PlayerSignOpenEvent.getHandlerList().getRegisteredListeners().length > 0) { ++ // Paper end - Add PlayerOpenSignEvent + if (!CraftEventFactory.callPlayerSignOpenEvent(player, sign, side, PlayerSignOpenEvent.Cause.PLUGIN)) { + return; + } ++ } // Paper - Add PlayerOpenSignEvent + + SignBlockEntity handle = ((CraftSign) sign).getTileEntity(); + handle.setAllowedPlayerEditor(player.getUniqueId()); diff --git a/patches/server/0881-Don-t-enforce-icanhasbukkit-default-if-alias-block-e.patch b/patches/server/0881-Don-t-enforce-icanhasbukkit-default-if-alias-block-e.patch deleted file mode 100644 index f487b964caa1..000000000000 --- a/patches/server/0881-Don-t-enforce-icanhasbukkit-default-if-alias-block-e.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TheMeinerLP -Date: Tue, 13 Jun 2023 16:10:59 +0200 -Subject: [PATCH] Don't enforce icanhasbukkit default if alias block exists - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index f5bd1a68502be87e03923934b25fb3e982762be7..b66b01a743353e3b829f65bc619b39cc62d5f4fe 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -352,7 +352,11 @@ public final class CraftServer implements Server { - } - this.commandsConfiguration = YamlConfiguration.loadConfiguration(this.getCommandsConfigFile()); - this.commandsConfiguration.options().copyDefaults(true); -- this.commandsConfiguration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8))); -+ // Paper start - don't enforce icanhasbukkit default if alias block exists -+ final YamlConfiguration commandsDefaults = YamlConfiguration.loadConfiguration(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8)); -+ if (this.commandsConfiguration.contains("aliases")) commandsDefaults.set("aliases", null); -+ this.commandsConfiguration.setDefaults(commandsDefaults); -+ // Paper end - don't enforce icanhasbukkit default if alias block exists - this.saveCommandsConfig(); - - // Migrate aliases from old file and add previously implicit $1- to pass all arguments diff --git a/patches/server/0881-Only-tick-item-frames-if-players-can-see-it.patch b/patches/server/0881-Only-tick-item-frames-if-players-can-see-it.patch new file mode 100644 index 000000000000..decd7a346e58 --- /dev/null +++ b/patches/server/0881-Only-tick-item-frames-if-players-can-see-it.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Redned +Date: Mon, 19 Jun 2023 15:45:53 -0500 +Subject: [PATCH] Only tick item frames if players can see it + +In the event that an item frame cannot be seen by any players, ticking the item frame every tick is unnecessary. This can be a very hot section of the entity tracker when lots of item frames are present on a server, so this reduces the logic which speeds it up. + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 3608257fabec6ad7edb056def8a6f36c50b4871e..1f0931bdd4d82c05d7b5f8b8e5c2cc6d23905c73 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -111,7 +111,7 @@ public class ServerEntity { + + Entity entity = this.entity; + +- if (entity instanceof ItemFrame) { ++ if (!this.trackedPlayers.isEmpty() && entity instanceof ItemFrame) { // Paper - Perf: Only tick item frames if players can see it + ItemFrame entityitemframe = (ItemFrame) entity; + + if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block diff --git a/patches/server/0888-Fix-cmd-permission-levels-for-command-blocks.patch b/patches/server/0882-Fix-cmd-permission-levels-for-command-blocks.patch similarity index 100% rename from patches/server/0888-Fix-cmd-permission-levels-for-command-blocks.patch rename to patches/server/0882-Fix-cmd-permission-levels-for-command-blocks.patch diff --git a/patches/server/0889-Add-option-to-disable-block-updates.patch b/patches/server/0883-Add-option-to-disable-block-updates.patch similarity index 100% rename from patches/server/0889-Add-option-to-disable-block-updates.patch rename to patches/server/0883-Add-option-to-disable-block-updates.patch diff --git a/patches/server/0884-Add-method-to-remove-all-active-potion-effects.patch b/patches/server/0884-Add-method-to-remove-all-active-potion-effects.patch deleted file mode 100644 index ba93cfbecb46..000000000000 --- a/patches/server/0884-Add-method-to-remove-all-active-potion-effects.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 17 Jun 2023 13:17:25 -0700 -Subject: [PATCH] Add method to remove all active potion effects - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 11a52a11891f5dca5f2a6b2e77b6a090f4a69726..12c0581076585dfe97903463c6ec0cbcdeba2806 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -527,6 +527,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return effects; - } - -+ // Paper start - LivingEntity#clearActivePotionEffects(); -+ @Override -+ public boolean clearActivePotionEffects() { -+ return this.getHandle().removeAllEffects(EntityPotionEffectEvent.Cause.PLUGIN); -+ } -+ // Paper end -+ - @Override - public T launchProjectile(Class projectile) { - return this.launchProjectile(projectile, null); diff --git a/patches/server/0884-Call-missing-BlockDispenseEvent.patch b/patches/server/0884-Call-missing-BlockDispenseEvent.patch new file mode 100644 index 000000000000..d63492c38746 --- /dev/null +++ b/patches/server/0884-Call-missing-BlockDispenseEvent.patch @@ -0,0 +1,88 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Sat, 29 Oct 2022 15:41:56 +0200 +Subject: [PATCH] Call missing BlockDispenseEvent + + +diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +index b83af374a33a66a6ceeca119b961eea883bba41c..175b965c92b8b8be9c671e1ee478afa9a2f7bf82 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -1112,6 +1112,13 @@ public interface DispenseItemBehavior { + this.setSuccess(true); + if (iblockdata.is(Blocks.RESPAWN_ANCHOR)) { + if ((Integer) iblockdata.getValue(RespawnAnchorBlock.CHARGE) != 4) { ++ // Paper start - Call missing BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition, stack, this); ++ if (result != null) { ++ this.setSuccess(false); ++ return result; ++ } ++ // Paper end - Call missing BlockDispenseEvent + RespawnAnchorBlock.charge((Entity) null, worldserver, blockposition, iblockdata); + stack.shrink(1); + } else { +@@ -1134,6 +1141,13 @@ public interface DispenseItemBehavior { + Optional optional = HoneycombItem.getWaxed(iblockdata); + + if (optional.isPresent()) { ++ // Paper start - Call missing BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition, stack, this); ++ if (result != null) { ++ this.setSuccess(false); ++ return result; ++ } ++ // Paper end - Call missing BlockDispenseEvent + worldserver.setBlockAndUpdate(blockposition, (BlockState) optional.get()); + worldserver.levelEvent(3003, blockposition, 0); + stack.shrink(1); +@@ -1159,6 +1173,12 @@ public interface DispenseItemBehavior { + if (!worldserver.getBlockState(blockposition1).is(BlockTags.CONVERTABLE_TO_MUD)) { + return this.defaultDispenseItemBehavior.dispense(pointer, stack); + } else { ++ // Paper start - Call missing BlockDispenseEvent ++ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition1, stack, this); ++ if (result != null) { ++ return result; ++ } ++ // Paper end - Call missing BlockDispenseEvent + if (!worldserver.isClientSide) { + for (int k = 0; k < 5; ++k) { + worldserver.sendParticles(ParticleTypes.SPLASH, (double) blockposition.getX() + worldserver.random.nextDouble(), (double) (blockposition.getY() + 1), (double) blockposition.getZ() + worldserver.random.nextDouble(), 1, 0.0D, 0.0D, 0.0D, 1.0D); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 7934eea4ede9db90ad2a45a2a5ac64b264b2f91a..c2beeff8f84de23981624890c2a88cf510f4cbab 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -2093,6 +2093,32 @@ public class CraftEventFactory { + } + // Paper end + ++ // Paper start - Call missing BlockDispenseEvent ++ @Nullable ++ public static ItemStack handleBlockDispenseEvent(net.minecraft.core.dispenser.BlockSource pointer, BlockPos to, ItemStack itemStack, net.minecraft.core.dispenser.DispenseItemBehavior instance) { ++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(pointer.level(), pointer.pos()); ++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemStack.copyWithCount(1)); ++ ++ org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to)); ++ if (!net.minecraft.world.level.block.DispenserBlock.eventFired) { ++ if (!event.callEvent()) { ++ return itemStack; ++ } ++ } ++ ++ if (!event.getItem().equals(craftItem)) { ++ // Chain to handler for new item ++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); ++ net.minecraft.core.dispenser.DispenseItemBehavior itemBehavior = net.minecraft.world.level.block.DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); ++ if (itemBehavior != net.minecraft.core.dispenser.DispenseItemBehavior.NOOP && itemBehavior != instance) { ++ itemBehavior.dispense(pointer, eventStack); ++ return itemStack; ++ } ++ } ++ return null; ++ } ++ // Paper end - Call missing BlockDispenseEvent ++ + // Paper start - add EntityFertilizeEggEvent + /** + * Calls the {@link io.papermc.paper.event.entity.EntityFertilizeEggEvent}. diff --git a/patches/server/0885-Don-t-load-chunks-for-supporting-block-checks.patch b/patches/server/0885-Don-t-load-chunks-for-supporting-block-checks.patch new file mode 100644 index 000000000000..d03e7a3c1ef0 --- /dev/null +++ b/patches/server/0885-Don-t-load-chunks-for-supporting-block-checks.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Wed, 5 Jul 2023 23:11:53 +0100 +Subject: [PATCH] Don't load chunks for supporting block checks + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 0bc310f8edb886c84aa71a7502e297127647cbe5..4aeedd106ccf56e3088f7991f792dec7c4bd357a 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1187,7 +1187,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + protected BlockPos getOnPos(float offset) { +- if (this.mainSupportingBlockPos.isPresent()) { ++ if (this.mainSupportingBlockPos.isPresent() && this.level().getChunkIfLoadedImmediately(this.mainSupportingBlockPos.get()) != null) { // Paper - ensure no loads + BlockPos blockposition = (BlockPos) this.mainSupportingBlockPos.get(); + + if (offset <= 1.0E-5F) { diff --git a/patches/server/0886-Add-event-for-player-editing-sign.patch b/patches/server/0886-Add-event-for-player-editing-sign.patch deleted file mode 100644 index 9e38aba9d85b..000000000000 --- a/patches/server/0886-Add-event-for-player-editing-sign.patch +++ /dev/null @@ -1,93 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: by77er -Date: Mon, 12 Jun 2023 12:56:46 -0400 -Subject: [PATCH] Add event for player editing sign - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index ecea96202f81e4955c0af554da070984e6e416ff..e14d928e8bf484c61f2687621623942a27f30db1 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -523,7 +523,7 @@ public final class ItemStack { - try { - if (world.getBlockEntity(SignItem.openSign) instanceof SignBlockEntity tileentitysign) { - if (world.getBlockState(SignItem.openSign).getBlock() instanceof SignBlock blocksign) { -- blocksign.openTextEdit(entityhuman, tileentitysign, true, org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLACE); // Craftbukkit -+ blocksign.openTextEdit(entityhuman, tileentitysign, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // Paper - Add PlayerOpenSignEvent - } - } - } finally { -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 57b79e7fa34755e68b06f5b3010e68745cabbb7e..27a1e8ffc43efe4e086e7fd88ee4d80c23f98674 100644 ---- a/src/main/java/net/minecraft/world/level/block/SignBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SignBlock.java -@@ -118,7 +118,7 @@ public abstract class SignBlock extends BaseEntityBlock implements SimpleWaterlo - } else if (flag2) { - return InteractionResult.SUCCESS; - } else if (!this.otherPlayerIsEditingSign(player, tileentitysign) && player.mayBuild() && this.hasEditableText(player, tileentitysign, flag1)) { -- this.openTextEdit(player, tileentitysign, flag1, org.bukkit.event.player.PlayerSignOpenEvent.Cause.INTERACT); // CraftBukkit -+ this.openTextEdit(player, tileentitysign, flag1, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.INTERACT); // Paper - Add PlayerOpenSignEvent - return this.getInteractionResult(flag); - } else { - return InteractionResult.PASS; -@@ -170,16 +170,33 @@ public abstract class SignBlock extends BaseEntityBlock implements SimpleWaterlo - return blockpropertywood; - } - -+ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - Add PlayerOpenSignEvent - public void openTextEdit(Player player, SignBlockEntity blockEntity, boolean front) { -- // Craftbukkit start -- this.openTextEdit(player, blockEntity, front, org.bukkit.event.player.PlayerSignOpenEvent.Cause.UNKNOWN); -- } -- -- public void openTextEdit(Player entityhuman, SignBlockEntity tileentitysign, boolean flag, org.bukkit.event.player.PlayerSignOpenEvent.Cause cause) { -- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerSignOpenEvent(entityhuman, tileentitysign, flag, cause)) { -+ // Paper start - Add PlayerOpenSignEvent -+ this.openTextEdit(player, blockEntity, front, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.UNKNOWN); -+ } -+ public void openTextEdit(Player entityhuman, SignBlockEntity tileentitysign, boolean flag, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause cause) { -+ org.bukkit.entity.Player bukkitPlayer = (org.bukkit.entity.Player) entityhuman.getBukkitEntity(); -+ org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(tileentitysign.getLevel(), tileentitysign.getBlockPos()); -+ org.bukkit.craftbukkit.block.CraftSign bukkitSign = (org.bukkit.craftbukkit.block.CraftSign) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(bukkitBlock); -+ io.papermc.paper.event.player.PlayerOpenSignEvent event = new io.papermc.paper.event.player.PlayerOpenSignEvent( -+ bukkitPlayer, -+ bukkitSign, -+ flag ? org.bukkit.block.sign.Side.FRONT : org.bukkit.block.sign.Side.BACK, -+ cause); -+ if (!event.callEvent()) return; -+ if (org.bukkit.event.player.PlayerSignOpenEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ final org.bukkit.event.player.PlayerSignOpenEvent.Cause legacyCause = switch (cause) { -+ case PLACE -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLACE; -+ case PLUGIN -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLUGIN; -+ case INTERACT -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.INTERACT; -+ case UNKNOWN -> org.bukkit.event.player.PlayerSignOpenEvent.Cause.UNKNOWN; -+ }; -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerSignOpenEvent(entityhuman, tileentitysign, flag, legacyCause)) { -+ // Paper end - Add PlayerOpenSignEvent - return; - } -- // Craftbukkit end -+ } // Paper - Add PlayerOpenSignEvent - tileentitysign.setAllowedPlayerEditor(entityhuman.getUUID()); - entityhuman.openTextEdit(tileentitysign, flag); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -index 94caa0915e1a9ec1c46c7a0380db840901cc8063..3ebfc8e5b5462e6e532f8e8901fd5f8f386bbf34 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -@@ -139,9 +139,15 @@ public class CraftSign extends CraftBlockEntityState< - Preconditions.checkArgument(sign.isPlaced(), "Sign must be placed"); - Preconditions.checkArgument(sign.getWorld() == player.getWorld(), "Sign must be in same world as Player"); - -+ // Paper start - Add PlayerOpenSignEvent -+ io.papermc.paper.event.player.PlayerOpenSignEvent event = new io.papermc.paper.event.player.PlayerOpenSignEvent((Player) player, sign, side, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLUGIN); -+ if (!event.callEvent()) return; -+ if (PlayerSignOpenEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ // Paper end - Add PlayerOpenSignEvent - if (!CraftEventFactory.callPlayerSignOpenEvent(player, sign, side, PlayerSignOpenEvent.Cause.PLUGIN)) { - return; - } -+ } // Paper - Add PlayerOpenSignEvent - - SignBlockEntity handle = ((CraftSign) sign).getTileEntity(); - handle.setAllowedPlayerEditor(player.getUniqueId()); diff --git a/patches/server/0886-Optimize-player-lookups-for-beacons.patch b/patches/server/0886-Optimize-player-lookups-for-beacons.patch new file mode 100644 index 000000000000..3b3d11c54712 --- /dev/null +++ b/patches/server/0886-Optimize-player-lookups-for-beacons.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 6 Jul 2023 20:17:37 -0700 +Subject: [PATCH] Optimize player lookups for beacons + +For larger ranges, it's better to iterate over the player list +than the entity slices. + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index eff48e43a35a752bd33de2b55a0ad04332109ce0..4b81b0180dfc96fc6a88646838a886ca5b5d301b 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -329,7 +329,22 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + double d0 = blockEntity != null ? blockEntity.getEffectRange() : (i * 10 + 10); // Paper - Custom beacon ranges + + AABB axisalignedbb = (new AABB(blockposition)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D); +- List list = world.getEntitiesOfClass(Player.class, axisalignedbb); ++ // Paper start - Perf: optimize player lookup for beacons ++ List list; ++ if (d0 <= 128.0) { ++ list = world.getEntitiesOfClass(Player.class, axisalignedbb); ++ } else { ++ list = new java.util.ArrayList<>(); ++ for (Player player : world.players()) { ++ if (player.isSpectator()) { ++ continue; ++ } ++ if (player.getBoundingBox().intersects(axisalignedbb)) { ++ list.add(player); ++ } ++ } ++ } ++ // Paper end - Perf: optimize player lookup for beacons + + return list; + } diff --git a/patches/server/0887-Add-Sign-getInteractableSideFor.patch b/patches/server/0887-Add-Sign-getInteractableSideFor.patch new file mode 100644 index 000000000000..df4e37608bf8 --- /dev/null +++ b/patches/server/0887-Add-Sign-getInteractableSideFor.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 23 Jun 2023 12:16:28 -0700 +Subject: [PATCH] Add Sign#getInteractableSideFor + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +index f3cea7a8de334419b4a2f6dc64ef0e20fd715e75..927c7ea03560be0c86884cec70ee8e408e66cb07 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +@@ -66,13 +66,18 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C + } + + public boolean isFacingFrontText(net.minecraft.world.entity.player.Player player) { ++ // Paper start - Add Sign#getInteractableSideFor ++ return this.isFacingFrontText(player.getX(), player.getZ()); ++ } ++ public boolean isFacingFrontText(double x, double z) { ++ // Paper end - Add Sign#getInteractableSideFor + Block block = this.getBlockState().getBlock(); + + if (block instanceof SignBlock) { + SignBlock blocksign = (SignBlock) block; + Vec3 vec3d = blocksign.getSignHitboxCenterPosition(this.getBlockState()); +- double d0 = player.getX() - ((double) this.getBlockPos().getX() + vec3d.x); +- double d1 = player.getZ() - ((double) this.getBlockPos().getZ() + vec3d.z); ++ double d0 = x - ((double) this.getBlockPos().getX() + vec3d.x); // Paper - Add Sign#getInteractableSideFor ++ double d1 = z - ((double) this.getBlockPos().getZ() + vec3d.z); // Paper - Add Sign#getInteractableSideFor + float f = blocksign.getYRotationDegrees(this.getBlockState()); + float f1 = (float) (Mth.atan2(d1, d0) * 57.2957763671875D) - 90.0F; + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +index 3ebfc8e5b5462e6e532f8e8901fd5f8f386bbf34..2725fd91596a69e12996e838267b6612f745a4bb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +@@ -169,6 +169,14 @@ public class CraftSign extends CraftBlockEntityState< + } + // Paper end + ++ // Paper start - side facing API ++ @Override ++ public Side getInteractableSideFor(final double x, final double z) { ++ this.requirePlaced(); ++ return this.getSnapshot().isFacingFrontText(x, z) ? Side.FRONT : Side.BACK; ++ } ++ // Paper end ++ + public static Component[] sanitizeLines(String[] lines) { + Component[] components = new Component[4]; + diff --git a/patches/server/0887-Only-tick-item-frames-if-players-can-see-it.patch b/patches/server/0887-Only-tick-item-frames-if-players-can-see-it.patch deleted file mode 100644 index 80407ae8c808..000000000000 --- a/patches/server/0887-Only-tick-item-frames-if-players-can-see-it.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Redned -Date: Mon, 19 Jun 2023 15:45:53 -0500 -Subject: [PATCH] Only tick item frames if players can see it - -In the event that an item frame cannot be seen by any players, ticking the item frame every tick is unnecessary. This can be a very hot section of the entity tracker when lots of item frames are present on a server, so this reduces the logic which speeds it up. - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index b52f86e23a35f8727d827155ebb20f847108d673..72e7c44946ef02fcb744f2a4d265706e21b33c10 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -111,7 +111,7 @@ public class ServerEntity { - - Entity entity = this.entity; - -- if (entity instanceof ItemFrame) { -+ if (!this.trackedPlayers.isEmpty() && entity instanceof ItemFrame) { // Paper - Perf: Only tick item frames if players can see it - ItemFrame entityitemframe = (ItemFrame) entity; - - if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block diff --git a/patches/server/0894-Array-backed-synched-entity-data.patch b/patches/server/0888-Array-backed-synched-entity-data.patch similarity index 100% rename from patches/server/0894-Array-backed-synched-entity-data.patch rename to patches/server/0888-Array-backed-synched-entity-data.patch diff --git a/patches/server/0895-fix-item-meta-for-tadpole-buckets.patch b/patches/server/0889-fix-item-meta-for-tadpole-buckets.patch similarity index 100% rename from patches/server/0895-fix-item-meta-for-tadpole-buckets.patch rename to patches/server/0889-fix-item-meta-for-tadpole-buckets.patch diff --git a/patches/server/0890-Call-missing-BlockDispenseEvent.patch b/patches/server/0890-Call-missing-BlockDispenseEvent.patch deleted file mode 100644 index 9cf9fecebfe0..000000000000 --- a/patches/server/0890-Call-missing-BlockDispenseEvent.patch +++ /dev/null @@ -1,88 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Sat, 29 Oct 2022 15:41:56 +0200 -Subject: [PATCH] Call missing BlockDispenseEvent - - -diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -index b83af374a33a66a6ceeca119b961eea883bba41c..175b965c92b8b8be9c671e1ee478afa9a2f7bf82 100644 ---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -1112,6 +1112,13 @@ public interface DispenseItemBehavior { - this.setSuccess(true); - if (iblockdata.is(Blocks.RESPAWN_ANCHOR)) { - if ((Integer) iblockdata.getValue(RespawnAnchorBlock.CHARGE) != 4) { -+ // Paper start - Call missing BlockDispenseEvent -+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition, stack, this); -+ if (result != null) { -+ this.setSuccess(false); -+ return result; -+ } -+ // Paper end - Call missing BlockDispenseEvent - RespawnAnchorBlock.charge((Entity) null, worldserver, blockposition, iblockdata); - stack.shrink(1); - } else { -@@ -1134,6 +1141,13 @@ public interface DispenseItemBehavior { - Optional optional = HoneycombItem.getWaxed(iblockdata); - - if (optional.isPresent()) { -+ // Paper start - Call missing BlockDispenseEvent -+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition, stack, this); -+ if (result != null) { -+ this.setSuccess(false); -+ return result; -+ } -+ // Paper end - Call missing BlockDispenseEvent - worldserver.setBlockAndUpdate(blockposition, (BlockState) optional.get()); - worldserver.levelEvent(3003, blockposition, 0); - stack.shrink(1); -@@ -1159,6 +1173,12 @@ public interface DispenseItemBehavior { - if (!worldserver.getBlockState(blockposition1).is(BlockTags.CONVERTABLE_TO_MUD)) { - return this.defaultDispenseItemBehavior.dispense(pointer, stack); - } else { -+ // Paper start - Call missing BlockDispenseEvent -+ ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(pointer, blockposition1, stack, this); -+ if (result != null) { -+ return result; -+ } -+ // Paper end - Call missing BlockDispenseEvent - if (!worldserver.isClientSide) { - for (int k = 0; k < 5; ++k) { - worldserver.sendParticles(ParticleTypes.SPLASH, (double) blockposition.getX() + worldserver.random.nextDouble(), (double) (blockposition.getY() + 1), (double) blockposition.getZ() + worldserver.random.nextDouble(), 1, 0.0D, 0.0D, 0.0D, 1.0D); -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index bcd734bf14b911682afa367a87a4fd1b042dcd0d..8ba2d84c7daae945e6a2174a740090f4f8a9413c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -2156,6 +2156,32 @@ public class CraftEventFactory { - } - // Paper end - -+ // Paper start - Call missing BlockDispenseEvent -+ @Nullable -+ public static ItemStack handleBlockDispenseEvent(net.minecraft.core.dispenser.BlockSource pointer, BlockPos to, ItemStack itemStack, net.minecraft.core.dispenser.DispenseItemBehavior instance) { -+ org.bukkit.block.Block bukkitBlock = CraftBlock.at(pointer.level(), pointer.pos()); -+ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemStack.copyWithCount(1)); -+ -+ org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to)); -+ if (!net.minecraft.world.level.block.DispenserBlock.eventFired) { -+ if (!event.callEvent()) { -+ return itemStack; -+ } -+ } -+ -+ if (!event.getItem().equals(craftItem)) { -+ // Chain to handler for new item -+ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem()); -+ net.minecraft.core.dispenser.DispenseItemBehavior itemBehavior = net.minecraft.world.level.block.DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem()); -+ if (itemBehavior != net.minecraft.core.dispenser.DispenseItemBehavior.NOOP && itemBehavior != instance) { -+ itemBehavior.dispense(pointer, eventStack); -+ return itemStack; -+ } -+ } -+ return null; -+ } -+ // Paper end - Call missing BlockDispenseEvent -+ - // Paper start - add EntityFertilizeEggEvent - /** - * Calls the {@link io.papermc.paper.event.entity.EntityFertilizeEggEvent}. diff --git a/patches/server/0890-Fix-BanList-API.patch b/patches/server/0890-Fix-BanList-API.patch new file mode 100644 index 000000000000..8d1fcf4c1a54 --- /dev/null +++ b/patches/server/0890-Fix-BanList-API.patch @@ -0,0 +1,351 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 4 Jul 2023 11:27:10 -0700 +Subject: [PATCH] Fix BanList API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +index ef2dfa30686bba3eb510bf95172174cc9972422e..96408d505ce80799868ff84554a3b0b25adabb22 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +@@ -115,17 +115,17 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + } + + @Override +- public BanEntry ban(String reason, Date expires, String source) { ++ public BanEntry ban(String reason, Date expires, String source) { // Paper - fix ban list API + return ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); + } + + @Override +- public BanEntry ban(String reason, Instant expires, String source) { ++ public BanEntry ban(String reason, Instant expires, String source) { // Paper - fix ban list API + return ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); + } + + @Override +- public BanEntry ban(String reason, Duration duration, String source) { ++ public BanEntry ban(String reason, Duration duration, String source) { // Paper - fix ban list API + return ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, duration, source); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java +index 13e5e44b069121e51b9486c445902937f1d6c6d8..4a37c8172b42b10472bb90c9310c7ae3eeaa3481 100644 +--- a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java ++++ b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java +@@ -9,7 +9,7 @@ import org.bukkit.BanEntry; + import org.bukkit.craftbukkit.profile.CraftPlayerProfile; + import org.bukkit.profile.PlayerProfile; + +-public final class CraftProfileBanEntry implements BanEntry { ++public final class CraftProfileBanEntry implements BanEntry { // Paper + private static final Date minorDate = Date.from(Instant.parse("1899-12-31T04:00:00Z")); + private final UserBanList list; + private final GameProfile profile; +@@ -33,8 +33,8 @@ public final class CraftProfileBanEntry implements BanEntry { + } + + @Override +- public PlayerProfile getBanTarget() { +- return new CraftPlayerProfile(this.profile); ++ public com.destroystokyo.paper.profile.PlayerProfile getBanTarget() { // Paper ++ return new com.destroystokyo.paper.profile.CraftPlayerProfile(this.profile); // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java +index 172202accf4448a933fcf1ff820316c7910dd7f7..50ee7656580d386db473c054f5c5ec57bb2b1424 100644 +--- a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java ++++ b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java +@@ -24,42 +24,80 @@ public class CraftProfileBanList implements ProfileBanList { + } + + @Override +- public BanEntry getBanEntry(String target) { ++ public BanEntry getBanEntry(String target) { // Paper + Preconditions.checkArgument(target != null, "Target cannot be null"); + + return this.getBanEntry(CraftProfileBanList.getProfile(target)); + } + + @Override +- public BanEntry getBanEntry(PlayerProfile target) { ++ public BanEntry getBanEntry(PlayerProfile target) { // Paper + Preconditions.checkArgument(target != null, "Target cannot be null"); + +- return this.getBanEntry(((CraftPlayerProfile) target).buildGameProfile()); ++ return this.getBanEntry(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile()); // Paper ++ } ++ // Paper start - fix ban list API ++ @Override ++ public BanEntry getBanEntry(final com.destroystokyo.paper.profile.PlayerProfile target) { ++ Preconditions.checkArgument(target != null, "target cannot be null"); ++ ++ return this.getBanEntry(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile()); ++ } ++ ++ @Override ++ public BanEntry addBan(final com.destroystokyo.paper.profile.PlayerProfile target, final String reason, final Date expires, final String source) { ++ Preconditions.checkArgument(target != null, "PlayerProfile cannot be null"); ++ Preconditions.checkArgument(target.getId() != null, "The PlayerProfile UUID cannot be null"); ++ ++ return this.addBan(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile(), reason, expires, source); ++ } ++ ++ @Override ++ public boolean isBanned(final com.destroystokyo.paper.profile.PlayerProfile target) { ++ return this.isBanned((com.destroystokyo.paper.profile.SharedPlayerProfile) target); ++ } ++ ++ @Override ++ public void pardon(final com.destroystokyo.paper.profile.PlayerProfile target) { ++ this.pardon((com.destroystokyo.paper.profile.SharedPlayerProfile) target); + } + + @Override +- public BanEntry addBan(String target, String reason, Date expires, String source) { ++ public BanEntry addBan(final com.destroystokyo.paper.profile.PlayerProfile target, final String reason, final Instant expires, final String source) { ++ Date date = expires != null ? Date.from(expires) : null; ++ return this.addBan(target, reason, date, source); ++ } ++ ++ @Override ++ public BanEntry addBan(final com.destroystokyo.paper.profile.PlayerProfile target, final String reason, final Duration duration, final String source) { ++ Instant instant = duration != null ? Instant.now().plus(duration) : null; ++ return this.addBan(target, reason, instant, source); ++ } ++ // Paper end - fix ban list API ++ ++ @Override ++ public BanEntry addBan(String target, String reason, Date expires, String source) { // Paper - fix ban list API + Preconditions.checkArgument(target != null, "Ban target cannot be null"); + + return this.addBan(CraftProfileBanList.getProfileByName(target), reason, expires, source); + } + + @Override +- public BanEntry addBan(PlayerProfile target, String reason, Date expires, String source) { ++ public BanEntry addBan(PlayerProfile target, String reason, Date expires, String source) { // Paper - fix ban list API + Preconditions.checkArgument(target != null, "PlayerProfile cannot be null"); + Preconditions.checkArgument(target.getUniqueId() != null, "The PlayerProfile UUID cannot be null"); + +- return this.addBan(((CraftPlayerProfile) target).buildGameProfile(), reason, expires, source); ++ return this.addBan(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile(), reason, expires, source); // Paper + } + + @Override +- public BanEntry addBan(PlayerProfile target, String reason, Instant expires, String source) { ++ public BanEntry addBan(PlayerProfile target, String reason, Instant expires, String source) { // Paper - fix ban list API + Date date = expires != null ? Date.from(expires) : null; + return this.addBan(target, reason, date, source); + } + + @Override +- public BanEntry addBan(PlayerProfile target, String reason, Duration duration, String source) { ++ public BanEntry addBan(PlayerProfile target, String reason, Duration duration, String source) { // Paper - fix ban list API + Instant instant = duration != null ? Instant.now().plus(duration) : null; + return this.addBan(target, reason, instant, source); + } +@@ -76,8 +114,8 @@ public class CraftProfileBanList implements ProfileBanList { + } + + @Override +- public Set> getEntries() { +- ImmutableSet.Builder> builder = ImmutableSet.builder(); ++ public Set> getEntries() { // Paper ++ ImmutableSet.Builder> builder = ImmutableSet.builder(); // Paper + for (UserBanListEntry entry : this.list.getEntries()) { + GameProfile profile = entry.getUser(); + builder.add(new CraftProfileBanEntry(profile, entry, this.list)); +@@ -88,9 +126,14 @@ public class CraftProfileBanList implements ProfileBanList { + + @Override + public boolean isBanned(PlayerProfile target) { ++ // Paper start ++ return this.isBanned((com.destroystokyo.paper.profile.SharedPlayerProfile) target); ++ } ++ private boolean isBanned(com.destroystokyo.paper.profile.SharedPlayerProfile target) { ++ // Paper end + Preconditions.checkArgument(target != null, "Target cannot be null"); + +- return this.isBanned(((CraftPlayerProfile) target).buildGameProfile()); ++ return this.isBanned(target.buildGameProfile()); // Paper + } + + @Override +@@ -102,9 +145,14 @@ public class CraftProfileBanList implements ProfileBanList { + + @Override + public void pardon(PlayerProfile target) { ++ // Paper start ++ this.pardon((com.destroystokyo.paper.profile.SharedPlayerProfile) target); ++ } ++ private void pardon(com.destroystokyo.paper.profile.SharedPlayerProfile target) { ++ // Paper end + Preconditions.checkArgument(target != null, "Target cannot be null"); + +- this.pardon(((CraftPlayerProfile) target).buildGameProfile()); ++ this.pardon(target.buildGameProfile()); // Paper + } + + @Override +@@ -114,7 +162,7 @@ public class CraftProfileBanList implements ProfileBanList { + this.pardon(CraftProfileBanList.getProfile(target)); + } + +- public BanEntry getBanEntry(GameProfile profile) { ++ public BanEntry getBanEntry(GameProfile profile) { // Paper + if (profile == null) { + return null; + } +@@ -127,7 +175,7 @@ public class CraftProfileBanList implements ProfileBanList { + return new CraftProfileBanEntry(profile, entry, this.list); + } + +- public BanEntry addBan(GameProfile profile, String reason, Date expires, String source) { ++ public BanEntry addBan(GameProfile profile, String reason, Date expires, String source) { // Paper + if (profile == null) { + return null; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index c7e0aa94a8c1f821f723f323b69bacfd2d2d8aa3..0f32750ec4bc85033c6da8a21f4ad3150112ece6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1655,23 +1655,23 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + @Override +- public BanEntry ban(String reason, Date expires, String source) { ++ public BanEntry ban(String reason, Date expires, String source) { // Paper - fix ban list API + return this.ban(reason, expires, source, true); + } + + @Override +- public BanEntry ban(String reason, Instant expires, String source) { ++ public BanEntry ban(String reason, Instant expires, String source) { // Paper - fix ban list API + return this.ban(reason, expires != null ? Date.from(expires) : null, source); + } + + @Override +- public BanEntry ban(String reason, Duration duration, String source) { ++ public BanEntry ban(String reason, Duration duration, String source) { // Paper - fix ban list API + return this.ban(reason, duration != null ? Instant.now().plus(duration) : null, source); + } + + @Override +- public BanEntry ban(String reason, Date expires, String source, boolean kickPlayer) { +- BanEntry banEntry = ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); ++ public BanEntry ban(String reason, Date expires, String source, boolean kickPlayer) { // Paper - fix ban list API ++ BanEntry banEntry = ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); // Paper - fix ban list API + if (kickPlayer) { + this.kickPlayer(reason); + } +@@ -1679,12 +1679,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + @Override +- public BanEntry ban(String reason, Instant instant, String source, boolean kickPlayer) { ++ public BanEntry ban(String reason, Instant instant, String source, boolean kickPlayer) { // Paper - fix ban list API + return this.ban(reason, instant != null ? Date.from(instant) : null, source, kickPlayer); + } + + @Override +- public BanEntry ban(String reason, Duration duration, String source, boolean kickPlayer) { ++ public BanEntry ban(String reason, Duration duration, String source, boolean kickPlayer) { // Paper - fix ban list API + return this.ban(reason, duration != null ? Instant.now().plus(duration) : null, source, kickPlayer); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java +index 6f779c6f4422c5b5dc22f66e3e702c714d0e052b..41336821d4e0430e19f2fc021f09430d7a1302f6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java ++++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java +@@ -28,7 +28,7 @@ import org.bukkit.profile.PlayerProfile; + import org.bukkit.profile.PlayerTextures; + + @SerializableAs("PlayerProfile") +-public final class CraftPlayerProfile implements PlayerProfile, com.destroystokyo.paper.profile.SharedPlayerProfile { // Paper ++public final class CraftPlayerProfile implements PlayerProfile, com.destroystokyo.paper.profile.SharedPlayerProfile, com.destroystokyo.paper.profile.PlayerProfile { // Paper + + @Nonnull + public static GameProfile validateSkullProfile(@Nonnull GameProfile gameProfile) { +@@ -123,7 +123,7 @@ public final class CraftPlayerProfile implements PlayerProfile, com.destroystoky + } + + @Override +- public CompletableFuture update() { ++ public CompletableFuture update() { // Paper - have to remove generic to avoid clashing between bukkit.PlayerProfile and paper.PlayerProfile + return CompletableFuture.supplyAsync(this::getUpdatedProfile, Util.PROFILE_EXECUTOR); // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread + } + +@@ -277,4 +277,71 @@ public final class CraftPlayerProfile implements PlayerProfile, com.destroystoky + // Paper - diff on change + return profile; + } ++ ++ // Paper start - This must implement our PlayerProfile so generic casts succeed from cb.CraftPlayerProfile to paper.PlayerProfile ++ // The methods don't actually have to be implemented, because the profile should immediately be cast to SharedPlayerProfile ++ @Override ++ public String setName(final String name) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public UUID getId() { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public UUID setId(final UUID uuid) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public java.util.Set getProperties() { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public boolean hasProperty(final String property) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public void setProperty(final com.destroystokyo.paper.profile.ProfileProperty property) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public void setProperties(final java.util.Collection properties) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public void clearProperties() { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public boolean completeFromCache() { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public boolean completeFromCache(final boolean onlineMode) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public boolean completeFromCache(final boolean lookupUUID, final boolean onlineMode) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public boolean complete(final boolean textures) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } ++ ++ @Override ++ public boolean complete(final boolean textures, final boolean onlineMode) { ++ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); ++ } + } diff --git a/patches/server/0897-Determine-lava-and-water-fluid-explosion-resistance-.patch b/patches/server/0891-Determine-lava-and-water-fluid-explosion-resistance-.patch similarity index 100% rename from patches/server/0897-Determine-lava-and-water-fluid-explosion-resistance-.patch rename to patches/server/0891-Determine-lava-and-water-fluid-explosion-resistance-.patch diff --git a/patches/server/0891-Don-t-load-chunks-for-supporting-block-checks.patch b/patches/server/0891-Don-t-load-chunks-for-supporting-block-checks.patch deleted file mode 100644 index a59e753d0b88..000000000000 --- a/patches/server/0891-Don-t-load-chunks-for-supporting-block-checks.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Wed, 5 Jul 2023 23:11:53 +0100 -Subject: [PATCH] Don't load chunks for supporting block checks - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 18a1229f3be364df56bb99cb6de61c80dfe99390..fb934b069312ce27c8ebaf3d3645b2c2475bd87f 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1188,7 +1188,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - protected BlockPos getOnPos(float offset) { -- if (this.mainSupportingBlockPos.isPresent()) { -+ if (this.mainSupportingBlockPos.isPresent() && this.level().getChunkIfLoadedImmediately(this.mainSupportingBlockPos.get()) != null) { // Paper - ensure no loads - BlockPos blockposition = (BlockPos) this.mainSupportingBlockPos.get(); - - if (offset <= 1.0E-5F) { diff --git a/patches/server/0898-Fix-possible-NPE-on-painting-creation.patch b/patches/server/0892-Fix-possible-NPE-on-painting-creation.patch similarity index 100% rename from patches/server/0898-Fix-possible-NPE-on-painting-creation.patch rename to patches/server/0892-Fix-possible-NPE-on-painting-creation.patch diff --git a/patches/server/0892-Optimize-player-lookups-for-beacons.patch b/patches/server/0892-Optimize-player-lookups-for-beacons.patch deleted file mode 100644 index 4bc0ad23bec5..000000000000 --- a/patches/server/0892-Optimize-player-lookups-for-beacons.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 6 Jul 2023 20:17:37 -0700 -Subject: [PATCH] Optimize player lookups for beacons - -For larger ranges, it's better to iterate over the player list -than the entity slices. - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index dbcac8b787e9a18193723b5311fe882060397dc6..bfcf9e0c342f255d285b1ef7f88d71efed653ecd 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -329,7 +329,22 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - double d0 = blockEntity != null ? blockEntity.getEffectRange() : (i * 10 + 10); // Paper - Custom beacon ranges - - AABB axisalignedbb = (new AABB(blockposition)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D); -- List list = world.getEntitiesOfClass(Player.class, axisalignedbb); -+ // Paper start - Perf: optimize player lookup for beacons -+ List list; -+ if (d0 <= 128.0) { -+ list = world.getEntitiesOfClass(Player.class, axisalignedbb); -+ } else { -+ list = new java.util.ArrayList<>(); -+ for (Player player : world.players()) { -+ if (player.isSpectator()) { -+ continue; -+ } -+ if (player.getBoundingBox().intersects(axisalignedbb)) { -+ list.add(player); -+ } -+ } -+ } -+ // Paper end - Perf: optimize player lookup for beacons - - return list; - } diff --git a/patches/server/0893-Add-Sign-getInteractableSideFor.patch b/patches/server/0893-Add-Sign-getInteractableSideFor.patch deleted file mode 100644 index a295746b04ee..000000000000 --- a/patches/server/0893-Add-Sign-getInteractableSideFor.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 23 Jun 2023 12:16:28 -0700 -Subject: [PATCH] Add Sign#getInteractableSideFor - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -index c79a80428961b0941d4f6ed31d641cbf0e6a7203..979a8f472f866130a3abb10f535df757eaa50c20 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -@@ -66,13 +66,18 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C - } - - public boolean isFacingFrontText(net.minecraft.world.entity.player.Player player) { -+ // Paper start - Add Sign#getInteractableSideFor -+ return this.isFacingFrontText(player.getX(), player.getZ()); -+ } -+ public boolean isFacingFrontText(double x, double z) { -+ // Paper end - Add Sign#getInteractableSideFor - Block block = this.getBlockState().getBlock(); - - if (block instanceof SignBlock) { - SignBlock blocksign = (SignBlock) block; - Vec3 vec3d = blocksign.getSignHitboxCenterPosition(this.getBlockState()); -- double d0 = player.getX() - ((double) this.getBlockPos().getX() + vec3d.x); -- double d1 = player.getZ() - ((double) this.getBlockPos().getZ() + vec3d.z); -+ double d0 = x - ((double) this.getBlockPos().getX() + vec3d.x); // Paper - Add Sign#getInteractableSideFor -+ double d1 = z - ((double) this.getBlockPos().getZ() + vec3d.z); // Paper - Add Sign#getInteractableSideFor - float f = blocksign.getYRotationDegrees(this.getBlockState()); - float f1 = (float) (Mth.atan2(d1, d0) * 57.2957763671875D) - 90.0F; - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -index 3ebfc8e5b5462e6e532f8e8901fd5f8f386bbf34..2725fd91596a69e12996e838267b6612f745a4bb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -@@ -169,6 +169,14 @@ public class CraftSign extends CraftBlockEntityState< - } - // Paper end - -+ // Paper start - side facing API -+ @Override -+ public Side getInteractableSideFor(final double x, final double z) { -+ this.requirePlaced(); -+ return this.getSnapshot().isFacingFrontText(x, z) ? Side.FRONT : Side.BACK; -+ } -+ // Paper end -+ - public static Component[] sanitizeLines(String[] lines) { - Component[] components = new Component[4]; - diff --git a/patches/server/0899-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch b/patches/server/0893-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch similarity index 100% rename from patches/server/0899-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch rename to patches/server/0893-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch diff --git a/patches/server/0894-ExperienceOrb-should-call-EntitySpawnEvent.patch b/patches/server/0894-ExperienceOrb-should-call-EntitySpawnEvent.patch new file mode 100644 index 000000000000..22d04086a371 --- /dev/null +++ b/patches/server/0894-ExperienceOrb-should-call-EntitySpawnEvent.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Golfing8 +Date: Mon, 8 May 2023 09:18:17 -0400 +Subject: [PATCH] ExperienceOrb should call EntitySpawnEvent + + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index c2beeff8f84de23981624890c2a88cf510f4cbab..62f1552d86dc38d709f9e53f643a6d8ab38bd2d2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -734,7 +734,8 @@ public class CraftEventFactory { + // Spigot start - SPIGOT-7523: Merge after spawn event and only merge if the event was not cancelled (gets checked above) + if (entity instanceof net.minecraft.world.entity.ExperienceOrb xp) { + double radius = world.spigotConfig.expMerge; +- if (radius > 0) { ++ event = CraftEventFactory.callEntitySpawnEvent(entity); // Call spawn event for ExperienceOrb entities ++ if (radius > 0 && !event.isCancelled() && !entity.isRemoved()) { + // Paper start - Maximum exp value when merging; Whole section has been tweaked, see comments for specifics + final int maxValue = world.paperConfig().entities.behavior.experienceMergeMaxValue; + final boolean mergeUnconditionally = world.paperConfig().entities.behavior.experienceMergeMaxValue <= 0; diff --git a/patches/server/0901-Make-Amethyst-throw-both-Spread-and-Grow-Events.patch b/patches/server/0895-Make-Amethyst-throw-both-Spread-and-Grow-Events.patch similarity index 100% rename from patches/server/0901-Make-Amethyst-throw-both-Spread-and-Grow-Events.patch rename to patches/server/0895-Make-Amethyst-throw-both-Spread-and-Grow-Events.patch diff --git a/patches/server/0902-Add-whitelist-events.patch b/patches/server/0896-Add-whitelist-events.patch similarity index 100% rename from patches/server/0902-Add-whitelist-events.patch rename to patches/server/0896-Add-whitelist-events.patch diff --git a/patches/server/0896-Fix-BanList-API.patch b/patches/server/0896-Fix-BanList-API.patch deleted file mode 100644 index 2e62214e5c39..000000000000 --- a/patches/server/0896-Fix-BanList-API.patch +++ /dev/null @@ -1,351 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 4 Jul 2023 11:27:10 -0700 -Subject: [PATCH] Fix BanList API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -index ef2dfa30686bba3eb510bf95172174cc9972422e..96408d505ce80799868ff84554a3b0b25adabb22 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -@@ -115,17 +115,17 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - } - - @Override -- public BanEntry ban(String reason, Date expires, String source) { -+ public BanEntry ban(String reason, Date expires, String source) { // Paper - fix ban list API - return ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); - } - - @Override -- public BanEntry ban(String reason, Instant expires, String source) { -+ public BanEntry ban(String reason, Instant expires, String source) { // Paper - fix ban list API - return ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); - } - - @Override -- public BanEntry ban(String reason, Duration duration, String source) { -+ public BanEntry ban(String reason, Duration duration, String source) { // Paper - fix ban list API - return ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, duration, source); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java -index 13e5e44b069121e51b9486c445902937f1d6c6d8..4a37c8172b42b10472bb90c9310c7ae3eeaa3481 100644 ---- a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java -+++ b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanEntry.java -@@ -9,7 +9,7 @@ import org.bukkit.BanEntry; - import org.bukkit.craftbukkit.profile.CraftPlayerProfile; - import org.bukkit.profile.PlayerProfile; - --public final class CraftProfileBanEntry implements BanEntry { -+public final class CraftProfileBanEntry implements BanEntry { // Paper - private static final Date minorDate = Date.from(Instant.parse("1899-12-31T04:00:00Z")); - private final UserBanList list; - private final GameProfile profile; -@@ -33,8 +33,8 @@ public final class CraftProfileBanEntry implements BanEntry { - } - - @Override -- public PlayerProfile getBanTarget() { -- return new CraftPlayerProfile(this.profile); -+ public com.destroystokyo.paper.profile.PlayerProfile getBanTarget() { // Paper -+ return new com.destroystokyo.paper.profile.CraftPlayerProfile(this.profile); // Paper - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java -index 172202accf4448a933fcf1ff820316c7910dd7f7..50ee7656580d386db473c054f5c5ec57bb2b1424 100644 ---- a/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java -+++ b/src/main/java/org/bukkit/craftbukkit/ban/CraftProfileBanList.java -@@ -24,42 +24,80 @@ public class CraftProfileBanList implements ProfileBanList { - } - - @Override -- public BanEntry getBanEntry(String target) { -+ public BanEntry getBanEntry(String target) { // Paper - Preconditions.checkArgument(target != null, "Target cannot be null"); - - return this.getBanEntry(CraftProfileBanList.getProfile(target)); - } - - @Override -- public BanEntry getBanEntry(PlayerProfile target) { -+ public BanEntry getBanEntry(PlayerProfile target) { // Paper - Preconditions.checkArgument(target != null, "Target cannot be null"); - -- return this.getBanEntry(((CraftPlayerProfile) target).buildGameProfile()); -+ return this.getBanEntry(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile()); // Paper -+ } -+ // Paper start - fix ban list API -+ @Override -+ public BanEntry getBanEntry(final com.destroystokyo.paper.profile.PlayerProfile target) { -+ Preconditions.checkArgument(target != null, "target cannot be null"); -+ -+ return this.getBanEntry(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile()); -+ } -+ -+ @Override -+ public BanEntry addBan(final com.destroystokyo.paper.profile.PlayerProfile target, final String reason, final Date expires, final String source) { -+ Preconditions.checkArgument(target != null, "PlayerProfile cannot be null"); -+ Preconditions.checkArgument(target.getId() != null, "The PlayerProfile UUID cannot be null"); -+ -+ return this.addBan(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile(), reason, expires, source); -+ } -+ -+ @Override -+ public boolean isBanned(final com.destroystokyo.paper.profile.PlayerProfile target) { -+ return this.isBanned((com.destroystokyo.paper.profile.SharedPlayerProfile) target); -+ } -+ -+ @Override -+ public void pardon(final com.destroystokyo.paper.profile.PlayerProfile target) { -+ this.pardon((com.destroystokyo.paper.profile.SharedPlayerProfile) target); - } - - @Override -- public BanEntry addBan(String target, String reason, Date expires, String source) { -+ public BanEntry addBan(final com.destroystokyo.paper.profile.PlayerProfile target, final String reason, final Instant expires, final String source) { -+ Date date = expires != null ? Date.from(expires) : null; -+ return this.addBan(target, reason, date, source); -+ } -+ -+ @Override -+ public BanEntry addBan(final com.destroystokyo.paper.profile.PlayerProfile target, final String reason, final Duration duration, final String source) { -+ Instant instant = duration != null ? Instant.now().plus(duration) : null; -+ return this.addBan(target, reason, instant, source); -+ } -+ // Paper end - fix ban list API -+ -+ @Override -+ public BanEntry addBan(String target, String reason, Date expires, String source) { // Paper - fix ban list API - Preconditions.checkArgument(target != null, "Ban target cannot be null"); - - return this.addBan(CraftProfileBanList.getProfileByName(target), reason, expires, source); - } - - @Override -- public BanEntry addBan(PlayerProfile target, String reason, Date expires, String source) { -+ public BanEntry addBan(PlayerProfile target, String reason, Date expires, String source) { // Paper - fix ban list API - Preconditions.checkArgument(target != null, "PlayerProfile cannot be null"); - Preconditions.checkArgument(target.getUniqueId() != null, "The PlayerProfile UUID cannot be null"); - -- return this.addBan(((CraftPlayerProfile) target).buildGameProfile(), reason, expires, source); -+ return this.addBan(((com.destroystokyo.paper.profile.SharedPlayerProfile) target).buildGameProfile(), reason, expires, source); // Paper - } - - @Override -- public BanEntry addBan(PlayerProfile target, String reason, Instant expires, String source) { -+ public BanEntry addBan(PlayerProfile target, String reason, Instant expires, String source) { // Paper - fix ban list API - Date date = expires != null ? Date.from(expires) : null; - return this.addBan(target, reason, date, source); - } - - @Override -- public BanEntry addBan(PlayerProfile target, String reason, Duration duration, String source) { -+ public BanEntry addBan(PlayerProfile target, String reason, Duration duration, String source) { // Paper - fix ban list API - Instant instant = duration != null ? Instant.now().plus(duration) : null; - return this.addBan(target, reason, instant, source); - } -@@ -76,8 +114,8 @@ public class CraftProfileBanList implements ProfileBanList { - } - - @Override -- public Set> getEntries() { -- ImmutableSet.Builder> builder = ImmutableSet.builder(); -+ public Set> getEntries() { // Paper -+ ImmutableSet.Builder> builder = ImmutableSet.builder(); // Paper - for (UserBanListEntry entry : this.list.getEntries()) { - GameProfile profile = entry.getUser(); - builder.add(new CraftProfileBanEntry(profile, entry, this.list)); -@@ -88,9 +126,14 @@ public class CraftProfileBanList implements ProfileBanList { - - @Override - public boolean isBanned(PlayerProfile target) { -+ // Paper start -+ return this.isBanned((com.destroystokyo.paper.profile.SharedPlayerProfile) target); -+ } -+ private boolean isBanned(com.destroystokyo.paper.profile.SharedPlayerProfile target) { -+ // Paper end - Preconditions.checkArgument(target != null, "Target cannot be null"); - -- return this.isBanned(((CraftPlayerProfile) target).buildGameProfile()); -+ return this.isBanned(target.buildGameProfile()); // Paper - } - - @Override -@@ -102,9 +145,14 @@ public class CraftProfileBanList implements ProfileBanList { - - @Override - public void pardon(PlayerProfile target) { -+ // Paper start -+ this.pardon((com.destroystokyo.paper.profile.SharedPlayerProfile) target); -+ } -+ private void pardon(com.destroystokyo.paper.profile.SharedPlayerProfile target) { -+ // Paper end - Preconditions.checkArgument(target != null, "Target cannot be null"); - -- this.pardon(((CraftPlayerProfile) target).buildGameProfile()); -+ this.pardon(target.buildGameProfile()); // Paper - } - - @Override -@@ -114,7 +162,7 @@ public class CraftProfileBanList implements ProfileBanList { - this.pardon(CraftProfileBanList.getProfile(target)); - } - -- public BanEntry getBanEntry(GameProfile profile) { -+ public BanEntry getBanEntry(GameProfile profile) { // Paper - if (profile == null) { - return null; - } -@@ -127,7 +175,7 @@ public class CraftProfileBanList implements ProfileBanList { - return new CraftProfileBanEntry(profile, entry, this.list); - } - -- public BanEntry addBan(GameProfile profile, String reason, Date expires, String source) { -+ public BanEntry addBan(GameProfile profile, String reason, Date expires, String source) { // Paper - if (profile == null) { - return null; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 5190613932bc1084d617f49e1517a9942e3765fc..cd477ae33aa98a629ebb5bd9c39912185df09cbc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1625,23 +1625,23 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - @Override -- public BanEntry ban(String reason, Date expires, String source) { -+ public BanEntry ban(String reason, Date expires, String source) { // Paper - fix ban list API - return this.ban(reason, expires, source, true); - } - - @Override -- public BanEntry ban(String reason, Instant expires, String source) { -+ public BanEntry ban(String reason, Instant expires, String source) { // Paper - fix ban list API - return this.ban(reason, expires != null ? Date.from(expires) : null, source); - } - - @Override -- public BanEntry ban(String reason, Duration duration, String source) { -+ public BanEntry ban(String reason, Duration duration, String source) { // Paper - fix ban list API - return this.ban(reason, duration != null ? Instant.now().plus(duration) : null, source); - } - - @Override -- public BanEntry ban(String reason, Date expires, String source, boolean kickPlayer) { -- BanEntry banEntry = ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); -+ public BanEntry ban(String reason, Date expires, String source, boolean kickPlayer) { // Paper - fix ban list API -+ BanEntry banEntry = ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); // Paper - fix ban list API - if (kickPlayer) { - this.kickPlayer(reason); - } -@@ -1649,12 +1649,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - @Override -- public BanEntry ban(String reason, Instant instant, String source, boolean kickPlayer) { -+ public BanEntry ban(String reason, Instant instant, String source, boolean kickPlayer) { // Paper - fix ban list API - return this.ban(reason, instant != null ? Date.from(instant) : null, source, kickPlayer); - } - - @Override -- public BanEntry ban(String reason, Duration duration, String source, boolean kickPlayer) { -+ public BanEntry ban(String reason, Duration duration, String source, boolean kickPlayer) { // Paper - fix ban list API - return this.ban(reason, duration != null ? Instant.now().plus(duration) : null, source, kickPlayer); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java -index 6f779c6f4422c5b5dc22f66e3e702c714d0e052b..41336821d4e0430e19f2fc021f09430d7a1302f6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java -+++ b/src/main/java/org/bukkit/craftbukkit/profile/CraftPlayerProfile.java -@@ -28,7 +28,7 @@ import org.bukkit.profile.PlayerProfile; - import org.bukkit.profile.PlayerTextures; - - @SerializableAs("PlayerProfile") --public final class CraftPlayerProfile implements PlayerProfile, com.destroystokyo.paper.profile.SharedPlayerProfile { // Paper -+public final class CraftPlayerProfile implements PlayerProfile, com.destroystokyo.paper.profile.SharedPlayerProfile, com.destroystokyo.paper.profile.PlayerProfile { // Paper - - @Nonnull - public static GameProfile validateSkullProfile(@Nonnull GameProfile gameProfile) { -@@ -123,7 +123,7 @@ public final class CraftPlayerProfile implements PlayerProfile, com.destroystoky - } - - @Override -- public CompletableFuture update() { -+ public CompletableFuture update() { // Paper - have to remove generic to avoid clashing between bukkit.PlayerProfile and paper.PlayerProfile - return CompletableFuture.supplyAsync(this::getUpdatedProfile, Util.PROFILE_EXECUTOR); // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread - } - -@@ -277,4 +277,71 @@ public final class CraftPlayerProfile implements PlayerProfile, com.destroystoky - // Paper - diff on change - return profile; - } -+ -+ // Paper start - This must implement our PlayerProfile so generic casts succeed from cb.CraftPlayerProfile to paper.PlayerProfile -+ // The methods don't actually have to be implemented, because the profile should immediately be cast to SharedPlayerProfile -+ @Override -+ public String setName(final String name) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public UUID getId() { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public UUID setId(final UUID uuid) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public java.util.Set getProperties() { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public boolean hasProperty(final String property) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public void setProperty(final com.destroystokyo.paper.profile.ProfileProperty property) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public void setProperties(final java.util.Collection properties) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public void clearProperties() { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public boolean completeFromCache() { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public boolean completeFromCache(final boolean onlineMode) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public boolean completeFromCache(final boolean lookupUUID, final boolean onlineMode) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public boolean complete(final boolean textures) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } -+ -+ @Override -+ public boolean complete(final boolean textures, final boolean onlineMode) { -+ throw new UnsupportedOperationException("Do not cast to com.destroystokyo.paper.profile.PlayerProfile"); -+ } - } diff --git a/patches/server/0897-Implement-PlayerFailMoveEvent.patch b/patches/server/0897-Implement-PlayerFailMoveEvent.patch new file mode 100644 index 000000000000..f9731edc82bd --- /dev/null +++ b/patches/server/0897-Implement-PlayerFailMoveEvent.patch @@ -0,0 +1,109 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Moulberry +Date: Wed, 26 Jul 2023 20:13:31 +0800 +Subject: [PATCH] Implement PlayerFailMoveEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index d8dd44d0f247ab05bc8323548bdec0f67ab641bc..3141681dc21f7a61fcc77bbf65975072b07c8970 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1279,8 +1279,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); final double toX = d0; // Paper - OBFHELPER + double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); final double toY = d1; // Paper - OBFHELPER + double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); final double toZ = d2; // Paper - OBFHELPER +- float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); +- float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); ++ float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); final float toYaw = f; // Paper - OBFHELPER ++ float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); final float toPitch = f1; // Paper - OBFHELPER + + if (this.player.isPassenger()) { + this.player.absMoveTo(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1); +@@ -1345,8 +1345,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + // Paper start - Prevent moving into unloaded chunks + if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position())))) { +- this.internalTeleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot(), Collections.emptySet()); +- return; ++ // Paper start - Add fail move event ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_INTO_UNLOADED_CHUNK, ++ toX, toY, toZ, toYaw, toPitch, false); ++ if (!event.isAllowed()) { ++ this.internalTeleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot(), Collections.emptySet()); ++ return; ++ } ++ // Paper end - Add fail move event + } + // Paper end - Prevent moving into unloaded chunks + +@@ -1355,9 +1361,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + if (d10 - d9 > Math.max(f2, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { + // CraftBukkit end +- ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); +- this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); +- return; ++ // Paper start - Add fail move event ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY, ++ toX, toY, toZ, toYaw, toPitch, true); ++ if (!event.isAllowed()) { ++ if (event.getLogWarning()) ++ ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); ++ this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); ++ return; ++ } ++ // Paper end - Add fail move event + } + } + } +@@ -1419,14 +1432,29 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + d8 = d2 - this.player.getZ(); + d10 = d6 * d6 + d7 * d7 + d8 * d8; +- boolean flag2 = false; ++ boolean movedWrongly = false; // Paper - Add fail move event; rename + + if (!this.player.isChangingDimension() && d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot +- flag2 = true; ++ // Paper start - Add fail move event ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_WRONGLY, ++ toX, toY, toZ, toYaw, toPitch, true); ++ if (!event.isAllowed()) { ++ movedWrongly = true; ++ if (event.getLogWarning()) + ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); ++ } + } + +- if (!this.player.noPhysics && !this.player.isSleeping() && (flag2 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2))) { ++ boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2)); ++ if (teleportBack) { ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK, ++ toX, toY, toZ, toYaw, toPitch, false); ++ if (event.isAllowed()) { ++ teleportBack = false; ++ } ++ } ++ if (teleportBack) { ++ // Paper end - Add fail move event + this.internalTeleport(d3, d4, d5, f, f1, Collections.emptySet()); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet. + this.player.doCheckFallDamage(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5, packet.isOnGround()); + } else { +@@ -3359,4 +3387,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand); + } ++ ++ // Paper start - Add fail move event ++ private io.papermc.paper.event.player.PlayerFailMoveEvent fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason failReason, ++ double toX, double toY, double toZ, float toYaw, float toPitch, boolean logWarning) { ++ Player player = this.getCraftPlayer(); ++ Location from = new Location(player.getWorld(), this.lastPosX, this.lastPosY, this.lastPosZ, this.lastYaw, this.lastPitch); ++ Location to = new Location(player.getWorld(), toX, toY, toZ, toYaw, toPitch); ++ io.papermc.paper.event.player.PlayerFailMoveEvent event = new io.papermc.paper.event.player.PlayerFailMoveEvent(player, failReason, ++ false, logWarning, from, to); ++ event.callEvent(); ++ return event; ++ } ++ // Paper end - Add fail move event + } diff --git a/patches/server/0898-Folia-scheduler-and-owned-region-API.patch b/patches/server/0898-Folia-scheduler-and-owned-region-API.patch new file mode 100644 index 000000000000..ef48c0c8df16 --- /dev/null +++ b/patches/server/0898-Folia-scheduler-and-owned-region-API.patch @@ -0,0 +1,1366 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 17 Jun 2023 11:52:52 +0200 +Subject: [PATCH] Folia scheduler and owned region API + +Pulling Folia API to Paper is primarily intended for plugins +that want to target both Paper and Folia without unnecessary +compatibility layers. + +Add both a location based scheduler, an entity based scheduler, +and a global region scheduler. + +Owned region API may be useful for plugins which want to perform +operations over large areas outside of the buffer zone provided +by the regionaliser, as it is not guaranteed that anything +outside of the buffer zone is owned. Then, the plugins may use +the schedulers depending on the result of the ownership check. + +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +index 0f8fa69577f09030fe96df6fa37e88ed38134a2e..eeea1e6f7b1ed64567a3f90d8eb2e2cfd53e5912 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +@@ -249,6 +249,22 @@ class PaperPluginInstanceManager { + + pluginName + " (Is it up to date?)", ex, plugin); // Paper + } + ++ // Paper start - Folia schedulers ++ try { ++ this.server.getGlobalRegionScheduler().cancelTasks(plugin); ++ } catch (Throwable ex) { ++ this.handlePluginException("Error occurred (in the plugin loader) while cancelling global tasks for " ++ + pluginName + " (Is it up to date?)", ex, plugin); // Paper ++ } ++ ++ try { ++ this.server.getAsyncScheduler().cancelTasks(plugin); ++ } catch (Throwable ex) { ++ this.handlePluginException("Error occurred (in the plugin loader) while cancelling async tasks for " ++ + pluginName + " (Is it up to date?)", ex, plugin); // Paper ++ } ++ // Paper end - Folia schedulers ++ + try { + this.server.getServicesManager().unregisterAll(plugin); + } catch (Throwable ex) { +diff --git a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..62484ebf4550b05182f693a3180bbac5d5fd906d +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java +@@ -0,0 +1,181 @@ ++package io.papermc.paper.threadedregions; ++ ++import ca.spottedleaf.concurrentutil.util.Validate; ++import io.papermc.paper.util.TickThread; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import net.minecraft.world.entity.Entity; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++ ++import java.util.ArrayDeque; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.function.Consumer; ++ ++/** ++ * An entity can move between worlds with an arbitrary tick delay, be temporarily removed ++ * for players (i.e end credits), be partially removed from world state (i.e inactive but not removed), ++ * teleport between ticking regions, teleport between worlds (which will change the underlying Entity object ++ * for non-players), and even be removed entirely from the server. The uncertainty of an entity's state can make ++ * it difficult to schedule tasks without worrying about undefined behaviors resulting from any of the states listed ++ * previously. ++ * ++ *

    ++ * This class is designed to eliminate those states by providing an interface to run tasks only when an entity ++ * is contained in a world, on the owning thread for the region, and by providing the current Entity object. ++ * The scheduler also allows a task to provide a callback, the "retired" callback, that will be invoked ++ * if the entity is removed before a task that was scheduled could be executed. The scheduler is also ++ * completely thread-safe, allowing tasks to be scheduled from any thread context. The scheduler also indicates ++ * properly whether a task was scheduled successfully (i.e scheduler not retired), thus the code scheduling any task ++ * knows whether the given callbacks will be invoked eventually or not - which may be critical for off-thread ++ * contexts. ++ *

    ++ */ ++public final class EntityScheduler { ++ ++ /** ++ * The Entity. Note that it is the CraftEntity, since only that class properly tracks world transfers. ++ */ ++ public final CraftEntity entity; ++ ++ private static final record ScheduledTask(Consumer run, Consumer retired) {} ++ ++ private long tickCount = 0L; ++ private static final long RETIRED_TICK_COUNT = -1L; ++ private final Object stateLock = new Object(); ++ private final Long2ObjectOpenHashMap> oneTimeDelayed = new Long2ObjectOpenHashMap<>(); ++ ++ private final ArrayDeque currentlyExecuting = new ArrayDeque<>(); ++ ++ public EntityScheduler(final CraftEntity entity) { ++ this.entity = Validate.notNull(entity); ++ } ++ ++ /** ++ * Retires the scheduler, preventing new tasks from being scheduled and invoking the retired callback ++ * on all currently scheduled tasks. ++ * ++ *

    ++ * Note: This should only be invoked after synchronously removing the entity from the world. ++ *

    ++ * ++ * @throws IllegalStateException If the scheduler is already retired. ++ */ ++ public void retire() { ++ synchronized (this.stateLock) { ++ if (this.tickCount == RETIRED_TICK_COUNT) { ++ throw new IllegalStateException("Already retired"); ++ } ++ this.tickCount = RETIRED_TICK_COUNT; ++ } ++ ++ final Entity thisEntity = this.entity.getHandleRaw(); ++ ++ // correctly handle and order retiring while running executeTick ++ for (int i = 0, len = this.currentlyExecuting.size(); i < len; ++i) { ++ final ScheduledTask task = this.currentlyExecuting.pollFirst(); ++ final Consumer retireTask = (Consumer)task.retired; ++ if (retireTask == null) { ++ continue; ++ } ++ ++ retireTask.accept(thisEntity); ++ } ++ ++ for (final List tasks : this.oneTimeDelayed.values()) { ++ for (int i = 0, len = tasks.size(); i < len; ++i) { ++ final ScheduledTask task = tasks.get(i); ++ final Consumer retireTask = (Consumer)task.retired; ++ if (retireTask == null) { ++ continue; ++ } ++ ++ retireTask.accept(thisEntity); ++ } ++ } ++ } ++ ++ /** ++ * Schedules a task with the given delay. If the task failed to schedule because the scheduler is retired (entity ++ * removed), then returns {@code false}. Otherwise, either the run callback will be invoked after the specified delay, ++ * or the retired callback will be invoked if the scheduler is retired. ++ * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, remove ++ * other entities, load chunks, load worlds, modify ticket levels, etc. ++ * ++ *

    ++ * It is guaranteed that the run and retired callback are invoked on the region which owns the entity. ++ *

    ++ *

    ++ * The run and retired callback take an Entity parameter representing the current object entity that the scheduler ++ * is tied to. Since the scheduler is transferred when an entity changes dimensions, it is possible the entity parameter ++ * is not the same when the task was first scheduled. Thus, only the parameter provided should be used. ++ *

    ++ * @param run The callback to run after the specified delay, may not be null. ++ * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. ++ * @param delay The delay in ticks before the run callback is invoked. Any value less-than 1 is treated as 1. ++ * @return {@code true} if the task was scheduled, which means that either the run function or the retired function ++ * will be invoked (but never both), or {@code false} indicating neither the run nor retired function will be invoked ++ * since the scheduler has been retired. ++ */ ++ public boolean schedule(final Consumer run, final Consumer retired, final long delay) { ++ Validate.notNull(run, "Run task may not be null"); ++ ++ final ScheduledTask task = new ScheduledTask(run, retired); ++ synchronized (this.stateLock) { ++ if (this.tickCount == RETIRED_TICK_COUNT) { ++ return false; ++ } ++ this.oneTimeDelayed.computeIfAbsent(this.tickCount + Math.max(1L, delay), (final long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(task); ++ } ++ ++ return true; ++ } ++ ++ /** ++ * Executes a tick for the scheduler. ++ * ++ * @throws IllegalStateException If the scheduler is retired. ++ */ ++ public void executeTick() { ++ final Entity thisEntity = this.entity.getHandleRaw(); ++ ++ TickThread.ensureTickThread(thisEntity, "May not tick entity scheduler asynchronously"); ++ final List toRun; ++ synchronized (this.stateLock) { ++ if (this.tickCount == RETIRED_TICK_COUNT) { ++ throw new IllegalStateException("Ticking retired scheduler"); ++ } ++ ++this.tickCount; ++ if (this.oneTimeDelayed.isEmpty()) { ++ toRun = null; ++ } else { ++ toRun = this.oneTimeDelayed.remove(this.tickCount); ++ } ++ } ++ ++ if (toRun != null) { ++ for (int i = 0, len = toRun.size(); i < len; ++i) { ++ this.currentlyExecuting.addLast(toRun.get(i)); ++ } ++ } ++ ++ // Note: It is allowed for the tasks executed to retire the entity in a given task. ++ for (int i = 0, len = this.currentlyExecuting.size(); i < len; ++i) { ++ if (!TickThread.isTickThreadFor(thisEntity)) { ++ // tp has been queued sync by one of the tasks ++ // in this case, we need to delay the tasks for next tick ++ break; ++ } ++ final ScheduledTask task = this.currentlyExecuting.pollFirst(); ++ ++ if (this.tickCount != RETIRED_TICK_COUNT) { ++ ((Consumer)task.run).accept(thisEntity); ++ } else { ++ // retired synchronously ++ // note: here task is null ++ break; ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FallbackRegionScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FallbackRegionScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..94056d61a304ee012ae1828a33412516095f996f +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FallbackRegionScheduler.java +@@ -0,0 +1,30 @@ ++package io.papermc.paper.threadedregions.scheduler; ++ ++import org.bukkit.World; ++import org.bukkit.plugin.Plugin; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.function.Consumer; ++ ++public final class FallbackRegionScheduler implements RegionScheduler { ++ ++ @Override ++ public void execute(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Runnable run) { ++ plugin.getServer().getGlobalRegionScheduler().execute(plugin, run); ++ } ++ ++ @Override ++ public @NotNull ScheduledTask run(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Consumer task) { ++ return plugin.getServer().getGlobalRegionScheduler().run(plugin, task); ++ } ++ ++ @Override ++ public @NotNull ScheduledTask runDelayed(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Consumer task, final long delayTicks) { ++ return plugin.getServer().getGlobalRegionScheduler().runDelayed(plugin, task, delayTicks); ++ } ++ ++ @Override ++ public @NotNull ScheduledTask runAtFixedRate(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Consumer task, final long initialDelayTicks, final long periodTicks) { ++ return plugin.getServer().getGlobalRegionScheduler().runAtFixedRate(plugin, task, initialDelayTicks, periodTicks); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaAsyncScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaAsyncScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..374abffb9f1ce1a308822aed13038e77fe9ca08b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaAsyncScheduler.java +@@ -0,0 +1,328 @@ ++package io.papermc.paper.threadedregions.scheduler; ++ ++import ca.spottedleaf.concurrentutil.util.Validate; ++import com.mojang.logging.LogUtils; ++import org.bukkit.plugin.IllegalPluginAccessException; ++import org.bukkit.plugin.Plugin; ++import org.slf4j.Logger; ++ ++import java.util.Set; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.Executor; ++import java.util.concurrent.Executors; ++import java.util.concurrent.ScheduledExecutorService; ++import java.util.concurrent.ScheduledFuture; ++import java.util.concurrent.SynchronousQueue; ++import java.util.concurrent.ThreadFactory; ++import java.util.concurrent.ThreadPoolExecutor; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.Consumer; ++import java.util.logging.Level; ++ ++public final class FoliaAsyncScheduler implements AsyncScheduler { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ private final Executor executors = new ThreadPoolExecutor(Math.max(4, Runtime.getRuntime().availableProcessors() / 2), Integer.MAX_VALUE, ++ 30L, TimeUnit.SECONDS, new SynchronousQueue<>(), ++ new ThreadFactory() { ++ private final AtomicInteger idGenerator = new AtomicInteger(); ++ ++ @Override ++ public Thread newThread(final Runnable run) { ++ final Thread ret = new Thread(run); ++ ++ ret.setName("Folia Async Scheduler Thread #" + this.idGenerator.getAndIncrement()); ++ ret.setPriority(Thread.NORM_PRIORITY - 1); ++ ret.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { ++ LOGGER.error("Uncaught exception in thread: " + thread.getName(), thr); ++ }); ++ ++ return ret; ++ } ++ } ++ ); ++ ++ private final ScheduledExecutorService timerThread = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { ++ @Override ++ public Thread newThread(final Runnable run) { ++ final Thread ret = new Thread(run); ++ ++ ret.setName("Folia Async Scheduler Thread Timer"); ++ ret.setPriority(Thread.NORM_PRIORITY + 1); ++ ret.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { ++ LOGGER.error("Uncaught exception in thread: " + thread.getName(), thr); ++ }); ++ ++ return ret; ++ } ++ }); ++ ++ private final Set tasks = ConcurrentHashMap.newKeySet(); ++ ++ @Override ++ public ScheduledTask runNow(final Plugin plugin, final Consumer task) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ final AsyncScheduledTask ret = new AsyncScheduledTask(plugin, -1L, task, null, -1L); ++ ++ this.tasks.add(ret); ++ this.executors.execute(ret); ++ ++ if (!plugin.isEnabled()) { ++ // handle race condition where plugin is disabled asynchronously ++ ret.cancel(); ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public ScheduledTask runDelayed(final Plugin plugin, final Consumer task, final long delay, ++ final TimeUnit unit) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ Validate.notNull(unit, "Time unit may not be null"); ++ if (delay < 0L) { ++ throw new IllegalArgumentException("Delay may not be < 0"); ++ } ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ return this.scheduleTimerTask(plugin, task, delay, -1L, unit); ++ } ++ ++ @Override ++ public ScheduledTask runAtFixedRate(final Plugin plugin, final Consumer task, final long initialDelay, ++ final long period, final TimeUnit unit) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ Validate.notNull(unit, "Time unit may not be null"); ++ if (initialDelay < 0L) { ++ throw new IllegalArgumentException("Initial delay may not be < 0"); ++ } ++ if (period <= 0L) { ++ throw new IllegalArgumentException("Period may not be <= 0"); ++ } ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ return this.scheduleTimerTask(plugin, task, initialDelay, period, unit); ++ } ++ ++ private AsyncScheduledTask scheduleTimerTask(final Plugin plugin, final Consumer task, final long initialDelay, ++ final long period, final TimeUnit unit) { ++ final AsyncScheduledTask ret = new AsyncScheduledTask( ++ plugin, period <= 0 ? period : unit.toNanos(period), task, null, ++ System.nanoTime() + unit.toNanos(initialDelay) ++ ); ++ ++ synchronized (ret) { ++ // even though ret is not published, we need to synchronise while scheduling to avoid a race condition ++ // for when a scheduled task immediately executes before we update the delay field and state field ++ ret.setDelay(this.timerThread.schedule(ret, initialDelay, unit)); ++ this.tasks.add(ret); ++ } ++ ++ if (!plugin.isEnabled()) { ++ // handle race condition where plugin is disabled asynchronously ++ ret.cancel(); ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public void cancelTasks(final Plugin plugin) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ ++ for (final AsyncScheduledTask task : this.tasks) { ++ if (task.plugin == plugin) { ++ task.cancel(); ++ } ++ } ++ } ++ ++ private final class AsyncScheduledTask implements ScheduledTask, Runnable { ++ ++ private static final int STATE_ON_TIMER = 0; ++ private static final int STATE_SCHEDULED_EXECUTOR = 1; ++ private static final int STATE_EXECUTING = 2; ++ private static final int STATE_EXECUTING_CANCELLED = 3; ++ private static final int STATE_FINISHED = 4; ++ private static final int STATE_CANCELLED = 5; ++ ++ private final Plugin plugin; ++ private final long repeatDelay; // in ns ++ private Consumer run; ++ private ScheduledFuture delay; ++ private int state; ++ private long scheduleTarget; ++ ++ public AsyncScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer run, ++ final ScheduledFuture delay, final long firstTarget) { ++ this.plugin = plugin; ++ this.repeatDelay = repeatDelay; ++ this.run = run; ++ this.delay = delay; ++ this.state = delay == null ? STATE_SCHEDULED_EXECUTOR : STATE_ON_TIMER; ++ this.scheduleTarget = firstTarget; ++ } ++ ++ private void setDelay(final ScheduledFuture delay) { ++ this.delay = delay; ++ this.state = STATE_SCHEDULED_EXECUTOR; ++ } ++ ++ @Override ++ public void run() { ++ final boolean repeating = this.isRepeatingTask(); ++ // try to advance state ++ final boolean timer; ++ synchronized (this) { ++ if (this.state == STATE_ON_TIMER) { ++ timer = true; ++ this.delay = null; ++ this.state = STATE_SCHEDULED_EXECUTOR; ++ } else if (this.state != STATE_SCHEDULED_EXECUTOR) { ++ // cancelled ++ if (this.state != STATE_CANCELLED) { ++ throw new IllegalStateException("Wrong state: " + this.state); ++ } ++ return; ++ } else { ++ timer = false; ++ this.state = STATE_EXECUTING; ++ } ++ } ++ ++ if (timer) { ++ // the scheduled executor is single thread, and unfortunately not expandable with threads ++ // so we just schedule onto the executor ++ FoliaAsyncScheduler.this.executors.execute(this); ++ return; ++ } ++ ++ try { ++ this.run.accept(this); ++ } catch (final Throwable throwable) { ++ this.plugin.getLogger().log(Level.WARNING, "Async task for " + this.plugin.getDescription().getFullName() + " generated an exception", throwable); ++ } finally { ++ boolean removeFromTasks = false; ++ synchronized (this) { ++ if (!repeating) { ++ // only want to execute once, so we're done ++ removeFromTasks = true; ++ this.state = STATE_FINISHED; ++ } else if (this.state != STATE_EXECUTING_CANCELLED) { ++ this.state = STATE_ON_TIMER; ++ // account for any delays, whether it be by task exec. or scheduler issues so that we keep ++ // the fixed schedule ++ final long currTime = System.nanoTime(); ++ final long delay = Math.max(0L, this.scheduleTarget + this.repeatDelay - currTime); ++ this.scheduleTarget = currTime + delay; ++ this.delay = FoliaAsyncScheduler.this.timerThread.schedule(this, delay, TimeUnit.NANOSECONDS); ++ } else { ++ // cancelled repeating task ++ removeFromTasks = true; ++ } ++ } ++ ++ if (removeFromTasks) { ++ this.run = null; ++ FoliaAsyncScheduler.this.tasks.remove(this); ++ } ++ } ++ } ++ ++ @Override ++ public Plugin getOwningPlugin() { ++ return this.plugin; ++ } ++ ++ @Override ++ public boolean isRepeatingTask() { ++ return this.repeatDelay > 0L; ++ } ++ ++ @Override ++ public CancelledState cancel() { ++ ScheduledFuture delay = null; ++ CancelledState ret; ++ synchronized (this) { ++ switch (this.state) { ++ case STATE_ON_TIMER: { ++ delay = this.delay; ++ this.delay = null; ++ this.state = STATE_CANCELLED; ++ ret = CancelledState.CANCELLED_BY_CALLER; ++ break; ++ } ++ case STATE_SCHEDULED_EXECUTOR: { ++ this.state = STATE_CANCELLED; ++ ret = CancelledState.CANCELLED_BY_CALLER; ++ break; ++ } ++ case STATE_EXECUTING: { ++ if (!this.isRepeatingTask()) { ++ return CancelledState.RUNNING; ++ } ++ this.state = STATE_EXECUTING_CANCELLED; ++ return CancelledState.NEXT_RUNS_CANCELLED; ++ } ++ case STATE_EXECUTING_CANCELLED: { ++ return CancelledState.NEXT_RUNS_CANCELLED_ALREADY; ++ } ++ case STATE_FINISHED: { ++ return CancelledState.ALREADY_EXECUTED; ++ } ++ case STATE_CANCELLED: { ++ return CancelledState.CANCELLED_ALREADY; ++ } ++ default: { ++ throw new IllegalStateException("Unknown state: " + this.state); ++ } ++ } ++ } ++ ++ if (delay != null) { ++ delay.cancel(false); ++ } ++ this.run = null; ++ FoliaAsyncScheduler.this.tasks.remove(this); ++ return ret; ++ } ++ ++ @Override ++ public ExecutionState getExecutionState() { ++ synchronized (this) { ++ switch (this.state) { ++ case STATE_ON_TIMER: ++ case STATE_SCHEDULED_EXECUTOR: ++ return ExecutionState.IDLE; ++ case STATE_EXECUTING: ++ return ExecutionState.RUNNING; ++ case STATE_EXECUTING_CANCELLED: ++ return ExecutionState.CANCELLED_RUNNING; ++ case STATE_FINISHED: ++ return ExecutionState.FINISHED; ++ case STATE_CANCELLED: ++ return ExecutionState.CANCELLED; ++ default: { ++ throw new IllegalStateException("Unknown state: " + this.state); ++ } ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..011754962896e32f51ed4606dcbea18a430a2bc1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java +@@ -0,0 +1,268 @@ ++package io.papermc.paper.threadedregions.scheduler; ++ ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Validate; ++import net.minecraft.world.entity.Entity; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++import org.bukkit.plugin.IllegalPluginAccessException; ++import org.bukkit.plugin.Plugin; ++import org.jetbrains.annotations.Nullable; ++ ++import java.lang.invoke.VarHandle; ++import java.util.function.Consumer; ++import java.util.logging.Level; ++ ++public final class FoliaEntityScheduler implements EntityScheduler { ++ ++ private final CraftEntity entity; ++ ++ public FoliaEntityScheduler(final CraftEntity entity) { ++ this.entity = entity; ++ } ++ ++ private static Consumer wrap(final Plugin plugin, final Runnable runnable) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(runnable, "Runnable may not be null"); ++ ++ return (final Entity nmsEntity) -> { ++ if (!plugin.isEnabled()) { ++ // don't execute if the plugin is disabled ++ return; ++ } ++ try { ++ runnable.run(); ++ } catch (final Throwable throwable) { ++ plugin.getLogger().log(Level.WARNING, "Entity task for " + plugin.getDescription().getFullName() + " generated an exception", throwable); ++ } ++ }; ++ } ++ ++ @Override ++ public boolean execute(final Plugin plugin, final Runnable run, final Runnable retired, ++ final long delay) { ++ final Consumer runNMS = wrap(plugin, run); ++ final Consumer runRetired = retired == null ? null : wrap(plugin, retired); ++ ++ return this.entity.taskScheduler.schedule(runNMS, runRetired, delay); ++ } ++ ++ @Override ++ public @Nullable ScheduledTask run(final Plugin plugin, final Consumer task, final Runnable retired) { ++ return this.runDelayed(plugin, task, retired, 1); ++ } ++ ++ @Override ++ public @Nullable ScheduledTask runDelayed(final Plugin plugin, final Consumer task, final Runnable retired, ++ final long delayTicks) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ if (delayTicks <= 0) { ++ throw new IllegalArgumentException("Delay ticks may not be <= 0"); ++ } ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ final EntityScheduledTask ret = new EntityScheduledTask(plugin, -1, task, retired); ++ ++ if (!this.scheduleInternal(ret, delayTicks)) { ++ return null; ++ } ++ ++ if (!plugin.isEnabled()) { ++ // handle race condition where plugin is disabled asynchronously ++ ret.cancel(); ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public @Nullable ScheduledTask runAtFixedRate(final Plugin plugin, final Consumer task, ++ final Runnable retired, final long initialDelayTicks, final long periodTicks) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ if (initialDelayTicks <= 0) { ++ throw new IllegalArgumentException("Initial delay ticks may not be <= 0"); ++ } ++ if (periodTicks <= 0) { ++ throw new IllegalArgumentException("Period ticks may not be <= 0"); ++ } ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ final EntityScheduledTask ret = new EntityScheduledTask(plugin, periodTicks, task, retired); ++ ++ if (!this.scheduleInternal(ret, initialDelayTicks)) { ++ return null; ++ } ++ ++ if (!plugin.isEnabled()) { ++ // handle race condition where plugin is disabled asynchronously ++ ret.cancel(); ++ } ++ ++ return ret; ++ } ++ ++ private boolean scheduleInternal(final EntityScheduledTask ret, final long delay) { ++ return this.entity.taskScheduler.schedule(ret, ret, delay); ++ } ++ ++ private final class EntityScheduledTask implements ScheduledTask, Consumer { ++ ++ private static final int STATE_IDLE = 0; ++ private static final int STATE_EXECUTING = 1; ++ private static final int STATE_EXECUTING_CANCELLED = 2; ++ private static final int STATE_FINISHED = 3; ++ private static final int STATE_CANCELLED = 4; ++ ++ private final Plugin plugin; ++ private final long repeatDelay; // in ticks ++ private Consumer run; ++ private Runnable retired; ++ private volatile int state; ++ ++ private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(EntityScheduledTask.class, "state", int.class); ++ ++ private EntityScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer run, final Runnable retired) { ++ this.plugin = plugin; ++ this.repeatDelay = repeatDelay; ++ this.run = run; ++ this.retired = retired; ++ } ++ ++ private final int getStateVolatile() { ++ return (int)STATE_HANDLE.get(this); ++ } ++ ++ private final int compareAndExchangeStateVolatile(final int expect, final int update) { ++ return (int)STATE_HANDLE.compareAndExchange(this, expect, update); ++ } ++ ++ private final void setStateVolatile(final int value) { ++ STATE_HANDLE.setVolatile(this, value); ++ } ++ ++ @Override ++ public void accept(final Entity entity) { ++ if (!this.plugin.isEnabled()) { ++ // don't execute if the plugin is disabled ++ this.setStateVolatile(STATE_CANCELLED); ++ return; ++ } ++ ++ final boolean repeating = this.isRepeatingTask(); ++ if (STATE_IDLE != this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_EXECUTING)) { ++ // cancelled ++ return; ++ } ++ ++ final boolean retired = entity.isRemoved(); ++ ++ try { ++ if (!retired) { ++ this.run.accept(this); ++ } else { ++ if (this.retired != null) { ++ this.retired.run(); ++ } ++ } ++ } catch (final Throwable throwable) { ++ this.plugin.getLogger().log(Level.WARNING, "Entity task for " + this.plugin.getDescription().getFullName() + " generated an exception", throwable); ++ } finally { ++ boolean reschedule = false; ++ if (!repeating && !retired) { ++ this.setStateVolatile(STATE_FINISHED); ++ } else if (retired || !this.plugin.isEnabled()) { ++ this.setStateVolatile(STATE_CANCELLED); ++ } else if (STATE_EXECUTING == this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_IDLE)) { ++ reschedule = true; ++ } // else: cancelled repeating task ++ ++ if (!reschedule) { ++ this.run = null; ++ this.retired = null; ++ } else { ++ if (!FoliaEntityScheduler.this.scheduleInternal(this, this.repeatDelay)) { ++ // the task itself must have removed the entity, so in this case we need to mark as cancelled ++ this.setStateVolatile(STATE_CANCELLED); ++ } ++ } ++ } ++ } ++ ++ @Override ++ public Plugin getOwningPlugin() { ++ return this.plugin; ++ } ++ ++ @Override ++ public boolean isRepeatingTask() { ++ return this.repeatDelay > 0; ++ } ++ ++ @Override ++ public CancelledState cancel() { ++ for (int curr = this.getStateVolatile();;) { ++ switch (curr) { ++ case STATE_IDLE: { ++ if (STATE_IDLE == (curr = this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_CANCELLED))) { ++ this.state = STATE_CANCELLED; ++ this.run = null; ++ this.retired = null; ++ return CancelledState.CANCELLED_BY_CALLER; ++ } ++ // try again ++ continue; ++ } ++ case STATE_EXECUTING: { ++ if (!this.isRepeatingTask()) { ++ return CancelledState.RUNNING; ++ } ++ if (STATE_EXECUTING == (curr = this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_EXECUTING_CANCELLED))) { ++ return CancelledState.NEXT_RUNS_CANCELLED; ++ } ++ // try again ++ continue; ++ } ++ case STATE_EXECUTING_CANCELLED: { ++ return CancelledState.NEXT_RUNS_CANCELLED_ALREADY; ++ } ++ case STATE_FINISHED: { ++ return CancelledState.ALREADY_EXECUTED; ++ } ++ case STATE_CANCELLED: { ++ return CancelledState.CANCELLED_ALREADY; ++ } ++ default: { ++ throw new IllegalStateException("Unknown state: " + curr); ++ } ++ } ++ } ++ } ++ ++ @Override ++ public ExecutionState getExecutionState() { ++ final int state = this.getStateVolatile(); ++ switch (state) { ++ case STATE_IDLE: ++ return ExecutionState.IDLE; ++ case STATE_EXECUTING: ++ return ExecutionState.RUNNING; ++ case STATE_EXECUTING_CANCELLED: ++ return ExecutionState.CANCELLED_RUNNING; ++ case STATE_FINISHED: ++ return ExecutionState.FINISHED; ++ case STATE_CANCELLED: ++ return ExecutionState.CANCELLED; ++ default: { ++ throw new IllegalStateException("Unknown state: " + state); ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d306f911757a4d556c82c0070d4837db87afc497 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java +@@ -0,0 +1,267 @@ ++package io.papermc.paper.threadedregions.scheduler; ++ ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.concurrentutil.util.Validate; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import org.bukkit.plugin.IllegalPluginAccessException; ++import org.bukkit.plugin.Plugin; ++ ++import java.lang.invoke.VarHandle; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.function.Consumer; ++import java.util.logging.Level; ++ ++public class FoliaGlobalRegionScheduler implements GlobalRegionScheduler { ++ ++ private long tickCount = 0L; ++ private final Object stateLock = new Object(); ++ private final Long2ObjectOpenHashMap> tasksByDeadline = new Long2ObjectOpenHashMap<>(); ++ ++ public void tick() { ++ final List run; ++ synchronized (this.stateLock) { ++ ++this.tickCount; ++ if (this.tasksByDeadline.isEmpty()) { ++ run = null; ++ } else { ++ run = this.tasksByDeadline.remove(this.tickCount); ++ } ++ } ++ ++ if (run == null) { ++ return; ++ } ++ ++ for (int i = 0, len = run.size(); i < len; ++i) { ++ run.get(i).run(); ++ } ++ } ++ ++ @Override ++ public void execute(final Plugin plugin, final Runnable run) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(run, "Runnable may not be null"); ++ ++ this.run(plugin, (final ScheduledTask task) -> { ++ run.run(); ++ }); ++ } ++ ++ @Override ++ public ScheduledTask run(final Plugin plugin, final Consumer task) { ++ return this.runDelayed(plugin, task, 1); ++ } ++ ++ @Override ++ public ScheduledTask runDelayed(final Plugin plugin, final Consumer task, final long delayTicks) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ if (delayTicks <= 0) { ++ throw new IllegalArgumentException("Delay ticks may not be <= 0"); ++ } ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ final GlobalScheduledTask ret = new GlobalScheduledTask(plugin, -1, task); ++ ++ this.scheduleInternal(ret, delayTicks); ++ ++ if (!plugin.isEnabled()) { ++ // handle race condition where plugin is disabled asynchronously ++ ret.cancel(); ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public ScheduledTask runAtFixedRate(final Plugin plugin, final Consumer task, final long initialDelayTicks, final long periodTicks) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ Validate.notNull(task, "Task may not be null"); ++ if (initialDelayTicks <= 0) { ++ throw new IllegalArgumentException("Initial delay ticks may not be <= 0"); ++ } ++ if (periodTicks <= 0) { ++ throw new IllegalArgumentException("Period ticks may not be <= 0"); ++ } ++ ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); ++ } ++ ++ final GlobalScheduledTask ret = new GlobalScheduledTask(plugin, periodTicks, task); ++ ++ this.scheduleInternal(ret, initialDelayTicks); ++ ++ if (!plugin.isEnabled()) { ++ // handle race condition where plugin is disabled asynchronously ++ ret.cancel(); ++ } ++ ++ return ret; ++ } ++ ++ @Override ++ public void cancelTasks(final Plugin plugin) { ++ Validate.notNull(plugin, "Plugin may not be null"); ++ ++ final List toCancel = new ArrayList<>(); ++ synchronized (this.stateLock) { ++ for (final List tasks : this.tasksByDeadline.values()) { ++ for (int i = 0, len = tasks.size(); i < len; ++i) { ++ final GlobalScheduledTask task = tasks.get(i); ++ if (task.plugin == plugin) { ++ toCancel.add(task); ++ } ++ } ++ } ++ } ++ ++ for (int i = 0, len = toCancel.size(); i < len; ++i) { ++ toCancel.get(i).cancel(); ++ } ++ } ++ ++ private void scheduleInternal(final GlobalScheduledTask task, final long delay) { ++ // note: delay > 0 ++ synchronized (this.stateLock) { ++ this.tasksByDeadline.computeIfAbsent(this.tickCount + delay, (final long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(task); ++ } ++ } ++ ++ private final class GlobalScheduledTask implements ScheduledTask, Runnable { ++ ++ private static final int STATE_IDLE = 0; ++ private static final int STATE_EXECUTING = 1; ++ private static final int STATE_EXECUTING_CANCELLED = 2; ++ private static final int STATE_FINISHED = 3; ++ private static final int STATE_CANCELLED = 4; ++ ++ private final Plugin plugin; ++ private final long repeatDelay; // in ticks ++ private Consumer run; ++ private volatile int state; ++ ++ private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(GlobalScheduledTask.class, "state", int.class); ++ ++ private GlobalScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer run) { ++ this.plugin = plugin; ++ this.repeatDelay = repeatDelay; ++ this.run = run; ++ } ++ ++ private final int getStateVolatile() { ++ return (int)STATE_HANDLE.get(this); ++ } ++ ++ private final int compareAndExchangeStateVolatile(final int expect, final int update) { ++ return (int)STATE_HANDLE.compareAndExchange(this, expect, update); ++ } ++ ++ private final void setStateVolatile(final int value) { ++ STATE_HANDLE.setVolatile(this, value); ++ } ++ ++ @Override ++ public void run() { ++ final boolean repeating = this.isRepeatingTask(); ++ if (STATE_IDLE != this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_EXECUTING)) { ++ // cancelled ++ return; ++ } ++ ++ try { ++ this.run.accept(this); ++ } catch (final Throwable throwable) { ++ this.plugin.getLogger().log(Level.WARNING, "Global task for " + this.plugin.getDescription().getFullName() + " generated an exception", throwable); ++ } finally { ++ boolean reschedule = false; ++ if (!repeating) { ++ this.setStateVolatile(STATE_FINISHED); ++ } else if (STATE_EXECUTING == this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_IDLE)) { ++ reschedule = true; ++ } // else: cancelled repeating task ++ ++ if (!reschedule) { ++ this.run = null; ++ } else { ++ FoliaGlobalRegionScheduler.this.scheduleInternal(this, this.repeatDelay); ++ } ++ } ++ } ++ ++ @Override ++ public Plugin getOwningPlugin() { ++ return this.plugin; ++ } ++ ++ @Override ++ public boolean isRepeatingTask() { ++ return this.repeatDelay > 0; ++ } ++ ++ @Override ++ public CancelledState cancel() { ++ for (int curr = this.getStateVolatile();;) { ++ switch (curr) { ++ case STATE_IDLE: { ++ if (STATE_IDLE == (curr = this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_CANCELLED))) { ++ this.state = STATE_CANCELLED; ++ this.run = null; ++ return CancelledState.CANCELLED_BY_CALLER; ++ } ++ // try again ++ continue; ++ } ++ case STATE_EXECUTING: { ++ if (!this.isRepeatingTask()) { ++ return CancelledState.RUNNING; ++ } ++ if (STATE_EXECUTING == (curr = this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_EXECUTING_CANCELLED))) { ++ return CancelledState.NEXT_RUNS_CANCELLED; ++ } ++ // try again ++ continue; ++ } ++ case STATE_EXECUTING_CANCELLED: { ++ return CancelledState.NEXT_RUNS_CANCELLED_ALREADY; ++ } ++ case STATE_FINISHED: { ++ return CancelledState.ALREADY_EXECUTED; ++ } ++ case STATE_CANCELLED: { ++ return CancelledState.CANCELLED_ALREADY; ++ } ++ default: { ++ throw new IllegalStateException("Unknown state: " + curr); ++ } ++ } ++ } ++ } ++ ++ @Override ++ public ExecutionState getExecutionState() { ++ final int state = this.getStateVolatile(); ++ switch (state) { ++ case STATE_IDLE: ++ return ExecutionState.IDLE; ++ case STATE_EXECUTING: ++ return ExecutionState.RUNNING; ++ case STATE_EXECUTING_CANCELLED: ++ return ExecutionState.CANCELLED_RUNNING; ++ case STATE_FINISHED: ++ return ExecutionState.FINISHED; ++ case STATE_CANCELLED: ++ return ExecutionState.CANCELLED; ++ default: { ++ throw new IllegalStateException("Unknown state: " + state); ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 97dbe5a44d2791c6dee830654c3935f4ac54aad4..48da5bdabcf38afbbd1509eca56d5c761622409f 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1497,6 +1497,20 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { ++ for (final Entity entity : level.getEntities().getAll()) { ++ if (entity.isRemoved()) { ++ continue; ++ } ++ final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw(); ++ if (bukkit != null) { ++ bukkit.taskScheduler.executeTick(); ++ } ++ } ++ }); ++ // Paper end - Folia scheduler API + io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper + this.profiler.push("commandFunctions"); + MinecraftTimings.commandFunctionsTimer.startTiming(); // Spigot // Paper +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 2eeb216002c1c91879780225335225552744524b..74b3f459c898dc9f5c4411a38c9018fb4866f0b1 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -647,6 +647,7 @@ public abstract class PlayerList { + + entityplayer.unRide(); + worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER); ++ entityplayer.retireScheduler(); // Paper - Folia schedulers + entityplayer.getAdvancements().stopListening(); + this.players.remove(entityplayer); + this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 11833b3c755e6ad6d802264ada02e80eb5545143..742ba42e60f9fdb0a78df681732af2be4ce5bdc3 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -246,11 +246,23 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + public @org.jetbrains.annotations.Nullable net.minecraft.server.level.ChunkMap.TrackedEntity tracker; // Paper + public CraftEntity getBukkitEntity() { + if (this.bukkitEntity == null) { +- this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this); ++ // Paper start - Folia schedulers ++ synchronized (this) { ++ if (this.bukkitEntity == null) { ++ return this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this); ++ } ++ } ++ // Paper end - Folia schedulers + } + return this.bukkitEntity; + } + ++ // Paper start ++ public CraftEntity getBukkitEntityRaw() { ++ return this.bukkitEntity; ++ } ++ // Paper end ++ + @Override + public CommandSender getBukkitSender(CommandSourceStack wrapper) { + return this.getBukkitEntity(); +@@ -4424,6 +4436,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + + @Override + public final void setRemoved(Entity.RemovalReason reason) { ++ final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers + if (this.removalReason == null) { + this.removalReason = reason; + } +@@ -4434,12 +4447,28 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + + this.getPassengers().forEach(Entity::stopRiding); + this.levelCallback.onRemove(reason); ++ // Paper start - Folia schedulers ++ if (!(this instanceof ServerPlayer) && reason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) { ++ // Players need to be special cased, because they are regularly removed from the world ++ this.retireScheduler(); ++ } ++ // Paper end - Folia schedulers + } + + public void unsetRemoved() { + this.removalReason = null; + } + ++ // Paper start - Folia schedulers ++ /** ++ * Invoked only when the entity is truly removed from the server, never to be added to any world. ++ */ ++ public final void retireScheduler() { ++ // we need to force create the bukkit entity so that the scheduler can be retired... ++ this.getBukkitEntity().taskScheduler.retire(); ++ } ++ // Paper end - Folia schedulers ++ + @Override + public void setLevelCallback(EntityInLevelCallback changeListener) { + this.levelCallback = changeListener; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 56a6b3921c74bdeb27f8736302503bee1f731065..b5140b37d2161d89c6c5d6465949e96f5472057a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -308,6 +308,76 @@ 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 + ++ // Paper start - Folia region threading API ++ private final io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler regionizedScheduler = new io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler(); ++ private final io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler asyncScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler(); ++ private final io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler globalRegionScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler(); ++ ++ @Override ++ public final io.papermc.paper.threadedregions.scheduler.RegionScheduler getRegionScheduler() { ++ return this.regionizedScheduler; ++ } ++ ++ @Override ++ public final io.papermc.paper.threadedregions.scheduler.AsyncScheduler getAsyncScheduler() { ++ return this.asyncScheduler; ++ } ++ ++ @Override ++ public final io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler getGlobalRegionScheduler() { ++ return this.globalRegionScheduler; ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(World world, io.papermc.paper.math.Position position) { ++ return io.papermc.paper.util.TickThread.isTickThreadFor( ++ ((CraftWorld) world).getHandle(), position.blockX() >> 4, position.blockZ() >> 4 ++ ); ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(World world, io.papermc.paper.math.Position position, int squareRadiusChunks) { ++ return io.papermc.paper.util.TickThread.isTickThreadFor( ++ ((CraftWorld) world).getHandle(), position.blockX() >> 4, position.blockZ() >> 4, squareRadiusChunks ++ ); ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(Location location) { ++ World world = location.getWorld(); ++ return io.papermc.paper.util.TickThread.isTickThreadFor( ++ ((CraftWorld) world).getHandle(), location.getBlockX() >> 4, location.getBlockZ() >> 4 ++ ); ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(Location location, int squareRadiusChunks) { ++ World world = location.getWorld(); ++ return io.papermc.paper.util.TickThread.isTickThreadFor( ++ ((CraftWorld) world).getHandle(), location.getBlockX() >> 4, location.getBlockZ() >> 4, squareRadiusChunks ++ ); ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(World world, int chunkX, int chunkZ) { ++ return io.papermc.paper.util.TickThread.isTickThreadFor( ++ ((CraftWorld) world).getHandle(), chunkX, chunkZ ++ ); ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(World world, int chunkX, int chunkZ, int squareRadiusChunks) { ++ return io.papermc.paper.util.TickThread.isTickThreadFor( ++ ((CraftWorld) world).getHandle(), chunkX, chunkZ, squareRadiusChunks ++ ); ++ } ++ ++ @Override ++ public final boolean isOwnedByCurrentRegion(Entity entity) { ++ return io.papermc.paper.util.TickThread.isTickThreadFor(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandleRaw()); ++ } ++ // Paper end - Folia reagion threading API ++ + static { + ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); + ConfigurationSerialization.registerClass(CraftPlayerProfile.class); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index e043a43ebda1df7b78c1368ce33a3648345bcb08..b86746be78e909b75a91751fbc4759db088d42a4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -67,6 +67,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + private EntityDamageEvent lastDamageEvent; + private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftEntity.DATA_TYPE_REGISTRY); + protected net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers ++ // Paper start - Folia shedulers ++ public final io.papermc.paper.threadedregions.EntityScheduler taskScheduler = new io.papermc.paper.threadedregions.EntityScheduler(this); ++ private final io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler apiScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler(this); ++ ++ @Override ++ public final io.papermc.paper.threadedregions.scheduler.EntityScheduler getScheduler() { ++ return this.apiScheduler; ++ }; ++ // Paper end - Folia schedulers + + public CraftEntity(final CraftServer server, final Entity entity) { + this.server = server; +@@ -483,6 +492,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return this.entity; + } + ++ // Paper start ++ public Entity getHandleRaw() { ++ return this.entity; ++ } ++ // Paper end ++ + @Override + public final EntityType getType() { + return this.entityType; diff --git a/patches/server/0905-Only-erase-allay-memory-on-non-item-targets.patch b/patches/server/0899-Only-erase-allay-memory-on-non-item-targets.patch similarity index 100% rename from patches/server/0905-Only-erase-allay-memory-on-non-item-targets.patch rename to patches/server/0899-Only-erase-allay-memory-on-non-item-targets.patch diff --git a/patches/server/0900-API-for-updating-recipes-on-clients.patch b/patches/server/0900-API-for-updating-recipes-on-clients.patch new file mode 100644 index 000000000000..dd62659d862c --- /dev/null +++ b/patches/server/0900-API-for-updating-recipes-on-clients.patch @@ -0,0 +1,114 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 21 Aug 2021 17:25:38 -0700 +Subject: [PATCH] API for updating recipes on clients + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 74b3f459c898dc9f5c4411a38c9018fb4866f0b1..090e76b7e13e1c7e4346188679e64cca3b665d48 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1530,6 +1530,13 @@ public abstract class PlayerList { + } + + public void reloadResources() { ++ // Paper start - API for updating recipes on clients ++ this.reloadAdvancementData(); ++ this.reloadTagData(); ++ this.reloadRecipeData(); ++ } ++ public void reloadAdvancementData() { ++ // Paper end - API for updating recipes on clients + // CraftBukkit start + /*Iterator iterator = this.advancements.values().iterator(); + +@@ -1545,7 +1552,15 @@ public abstract class PlayerList { + } + // CraftBukkit end + ++ // Paper start - API for updating recipes on clients ++ } ++ public void reloadTagData() { ++ // Paper end - API for updating recipes on clients + this.broadcastAll(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries))); ++ // Paper start - API for updating recipes on clients ++ } ++ public void reloadRecipeData() { ++ // Paper end - API for updating recipes on clients + ClientboundUpdateRecipesPacket packetplayoutrecipeupdate = new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getRecipes()); + Iterator iterator1 = this.players.iterator(); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index b5140b37d2161d89c6c5d6465949e96f5472057a..458bf151cb733e023b897a2acff2ab3a10fe0949 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1141,6 +1141,18 @@ public final class CraftServer implements Server { + ReloadCommand.reload(this.console); + } + ++ // Paper start - API for updating recipes on clients ++ @Override ++ public void updateResources() { ++ this.playerList.reloadResources(); ++ } ++ ++ @Override ++ public void updateRecipes() { ++ this.playerList.reloadRecipeData(); ++ } ++ // Paper end - API for updating recipes on clients ++ + private void loadIcon() { + this.icon = new CraftIconCache(null); + try { +@@ -1516,6 +1528,13 @@ public final class CraftServer implements Server { + + @Override + public boolean addRecipe(Recipe recipe) { ++ // Paper start - API for updating recipes on clients ++ return this.addRecipe(recipe, false); ++ } ++ ++ @Override ++ public boolean addRecipe(Recipe recipe, boolean resendRecipes) { ++ // Paper end - API for updating recipes on clients + CraftRecipe toAdd; + if (recipe instanceof CraftRecipe) { + toAdd = (CraftRecipe) recipe; +@@ -1545,6 +1564,11 @@ public final class CraftServer implements Server { + } + } + toAdd.addToCraftingManager(); ++ // Paper start - API for updating recipes on clients ++ if (resendRecipes) { ++ this.playerList.reloadRecipeData(); ++ } ++ // Paper end - API for updating recipes on clients + return true; + } + +@@ -1725,10 +1749,23 @@ public final class CraftServer implements Server { + + @Override + public boolean removeRecipe(NamespacedKey recipeKey) { ++ // Paper start - API for updating recipes on clients ++ return this.removeRecipe(recipeKey, false); ++ } ++ ++ @Override ++ public boolean removeRecipe(NamespacedKey recipeKey, boolean resendRecipes) { ++ // Paper end - API for updating recipes on clients + Preconditions.checkArgument(recipeKey != null, "recipeKey == null"); + + ResourceLocation mcKey = CraftNamespacedKey.toMinecraft(recipeKey); +- return this.getServer().getRecipeManager().removeRecipe(mcKey); ++ // Paper start - resend recipes on successful removal ++ boolean removed = this.getServer().getRecipeManager().removeRecipe(mcKey); ++ if (removed && resendRecipes) { ++ this.playerList.reloadRecipeData(); ++ } ++ return removed; ++ // Paper end + } + + @Override diff --git a/patches/server/0900-ExperienceOrb-should-call-EntitySpawnEvent.patch b/patches/server/0900-ExperienceOrb-should-call-EntitySpawnEvent.patch deleted file mode 100644 index 29e949790c92..000000000000 --- a/patches/server/0900-ExperienceOrb-should-call-EntitySpawnEvent.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Golfing8 -Date: Mon, 8 May 2023 09:18:17 -0400 -Subject: [PATCH] ExperienceOrb should call EntitySpawnEvent - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 11f38ba85aa7410642ac541ce84f8efd65590bc1..470558446908581188b007410082cfcee3f16f4d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -734,7 +734,8 @@ public class CraftEventFactory { - // Spigot start - SPIGOT-7523: Merge after spawn event and only merge if the event was not cancelled (gets checked above) - if (entity instanceof net.minecraft.world.entity.ExperienceOrb xp) { - double radius = world.spigotConfig.expMerge; -- if (radius > 0) { -+ event = CraftEventFactory.callEntitySpawnEvent(entity); // Call spawn event for ExperienceOrb entities -+ if (radius > 0 && !event.isCancelled() && !entity.isRemoved()) { - // Paper start - Maximum exp value when merging; Whole section has been tweaked, see comments for specifics - final int maxValue = world.paperConfig().entities.behavior.experienceMergeMaxValue; - final boolean mergeUnconditionally = world.paperConfig().entities.behavior.experienceMergeMaxValue <= 0; diff --git a/patches/server/0907-Fix-rotation-when-spawning-display-entities.patch b/patches/server/0901-Fix-rotation-when-spawning-display-entities.patch similarity index 100% rename from patches/server/0907-Fix-rotation-when-spawning-display-entities.patch rename to patches/server/0901-Fix-rotation-when-spawning-display-entities.patch diff --git a/patches/server/0902-Only-capture-actual-tree-growth.patch b/patches/server/0902-Only-capture-actual-tree-growth.patch new file mode 100644 index 000000000000..9255870831fd --- /dev/null +++ b/patches/server/0902-Only-capture-actual-tree-growth.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 21 Aug 2021 18:53:03 -0700 +Subject: [PATCH] Only capture actual tree growth + + +diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +index 175b965c92b8b8be9c671e1ee478afa9a2f7bf82..1fb809486ee56efd3d0ef3fa02503ba9be459f68 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -868,6 +868,7 @@ public interface DispenseItemBehavior { + if (!fertilizeEvent.isCancelled()) { + for (org.bukkit.block.BlockState blockstate : blocks) { + blockstate.update(true); ++ worldserver.checkCapturedTreeStateForObserverNotify(blockposition, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed + } + } + } +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 5a86b2c205250ddcd833a15accb27ca4a580eadd..9b4e20d4bfba2de08084f1d69cb2ebfff7455c14 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -424,6 +424,7 @@ public final class ItemStack { + for (CraftBlockState blockstate : blocks) { + // SPIGOT-7572 - Move fix for SPIGOT-7248 to CapturedBlockState, to allow bees in bee nest + CapturedBlockState.setBlockState(blockstate); ++ world.checkCapturedTreeStateForObserverNotify(blockposition, blockstate); // Paper - notify observers even if grow failed + } + entityhuman.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat + } +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index b942e9f163fa342c58b74d1cd6ffe6bdbe4f691a..cd19005d3f239a27a4ce764588c8df0b229035bf 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1372,4 +1372,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent + } + // Paper end - respect global sound events gamerule ++ // Paper start - notify observers even if grow failed ++ public void checkCapturedTreeStateForObserverNotify(final BlockPos pos, final CraftBlockState craftBlockState) { ++ // notify observers if the block state is the same and the Y level equals the original y level (for mega trees) ++ // blocks at the same Y level with the same state can be assumed to be saplings which trigger observers regardless of if the ++ // tree grew or not ++ if (craftBlockState.getPosition().getY() == pos.getY() && this.getBlockState(craftBlockState.getPosition()) == craftBlockState.getHandle()) { ++ this.notifyAndUpdatePhysics(craftBlockState.getPosition(), null, craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getFlag(), 512); ++ } ++ } ++ // Paper end - notify observers even if grow failed + } +diff --git a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java +index 83e6e3286d04c39d6d7ba496251aec962621f72e..3ff0d08e4964aae82d8e51d3b8bf9aa002096f81 100644 +--- a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java +@@ -86,6 +86,7 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { + if (event == null || !event.isCancelled()) { + for (BlockState blockstate : blocks) { + CapturedBlockState.setBlockState(blockstate); ++ world.checkCapturedTreeStateForObserverNotify(pos, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed + } + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 0a96b00a98227714ef99005e0a223765feae8fe9..e5506a7d074a9f89d41f4d5d7549a458779bef20 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -566,6 +566,7 @@ public class CraftBlock implements Block { + if (!event.isCancelled()) { + for (BlockState blockstate : blocks) { + blockstate.update(true); ++ world.checkCapturedTreeStateForObserverNotify(this.position, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed + } + } + } diff --git a/patches/server/0903-Implement-PlayerFailMoveEvent.patch b/patches/server/0903-Implement-PlayerFailMoveEvent.patch deleted file mode 100644 index 3f87c43562fd..000000000000 --- a/patches/server/0903-Implement-PlayerFailMoveEvent.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Moulberry -Date: Wed, 26 Jul 2023 20:13:31 +0800 -Subject: [PATCH] Implement PlayerFailMoveEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0bc974ff4124ff6bfc355a6dac362574a649c476..e19c05504151885ca18496b50dcf6091d94078c0 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1279,8 +1279,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); final double toX = d0; // Paper - OBFHELPER - double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); final double toY = d1; // Paper - OBFHELPER - double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); final double toZ = d2; // Paper - OBFHELPER -- float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); -- float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); -+ float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); final float toYaw = f; // Paper - OBFHELPER -+ float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); final float toPitch = f1; // Paper - OBFHELPER - - if (this.player.isPassenger()) { - this.player.absMoveTo(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1); -@@ -1345,8 +1345,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - // Paper start - Prevent moving into unloaded chunks - if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position())))) { -- this.internalTeleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot(), Collections.emptySet()); -- return; -+ // Paper start - Add fail move event -+ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_INTO_UNLOADED_CHUNK, -+ toX, toY, toZ, toYaw, toPitch, false); -+ if (!event.isAllowed()) { -+ this.internalTeleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot(), Collections.emptySet()); -+ return; -+ } -+ // Paper end - Add fail move event - } - // Paper end - Prevent moving into unloaded chunks - -@@ -1355,9 +1361,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - if (d10 - d9 > Math.max(f2, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { - // CraftBukkit end -- ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); -- this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); -- return; -+ // Paper start - Add fail move event -+ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY, -+ toX, toY, toZ, toYaw, toPitch, true); -+ if (!event.isAllowed()) { -+ if (event.getLogWarning()) -+ ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); -+ this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); -+ return; -+ } -+ // Paper end - Add fail move event - } - } - } -@@ -1419,14 +1432,29 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - d8 = d2 - this.player.getZ(); - d10 = d6 * d6 + d7 * d7 + d8 * d8; -- boolean flag2 = false; -+ boolean movedWrongly = false; // Paper - Add fail move event; rename - - if (!this.player.isChangingDimension() && d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot -- flag2 = true; -+ // Paper start - Add fail move event -+ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_WRONGLY, -+ toX, toY, toZ, toYaw, toPitch, true); -+ if (!event.isAllowed()) { -+ movedWrongly = true; -+ if (event.getLogWarning()) - ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); -+ } - } - -- if (!this.player.noPhysics && !this.player.isSleeping() && (flag2 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2))) { -+ boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2)); -+ if (teleportBack) { -+ io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK, -+ toX, toY, toZ, toYaw, toPitch, false); -+ if (event.isAllowed()) { -+ teleportBack = false; -+ } -+ } -+ if (teleportBack) { -+ // Paper end - Add fail move event - this.internalTeleport(d3, d4, d5, f, f1, Collections.emptySet()); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet. - this.player.doCheckFallDamage(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5, packet.isOnGround()); - } else { -@@ -3359,4 +3387,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand); - } -+ -+ // Paper start - Add fail move event -+ private io.papermc.paper.event.player.PlayerFailMoveEvent fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason failReason, -+ double toX, double toY, double toZ, float toYaw, float toPitch, boolean logWarning) { -+ Player player = this.getCraftPlayer(); -+ Location from = new Location(player.getWorld(), this.lastPosX, this.lastPosY, this.lastPosZ, this.lastYaw, this.lastPitch); -+ Location to = new Location(player.getWorld(), toX, toY, toZ, toYaw, toPitch); -+ io.papermc.paper.event.player.PlayerFailMoveEvent event = new io.papermc.paper.event.player.PlayerFailMoveEvent(player, failReason, -+ false, logWarning, from, to); -+ event.callEvent(); -+ return event; -+ } -+ // Paper end - Add fail move event - } diff --git a/patches/server/0903-Use-correct-source-for-mushroom-block-spread-event.patch b/patches/server/0903-Use-correct-source-for-mushroom-block-spread-event.patch new file mode 100644 index 000000000000..ef72acd36e19 --- /dev/null +++ b/patches/server/0903-Use-correct-source-for-mushroom-block-spread-event.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Tue, 8 Aug 2023 11:49:32 +0200 +Subject: [PATCH] Use correct source for mushroom block spread event + + +diff --git a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java +index 1448386e71a1f7c81b48788ac42b49d6dff29912..1f27ae8abd5891a0b8057b454f2210b088b4e95a 100644 +--- a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java +@@ -68,6 +68,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { + } + + BlockPos blockposition2 = pos.offset(random.nextInt(3) - 1, random.nextInt(2) - random.nextInt(2), random.nextInt(3) - 1); ++ final BlockPos sourcePos = pos; // Paper - Use correct source for mushroom block spread event + + for (int j = 0; j < 4; ++j) { + if (world.isEmptyBlock(blockposition2) && state.canSurvive(world, blockposition2)) { +@@ -78,7 +79,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { + } + + if (world.isEmptyBlock(blockposition2) && state.canSurvive(world, blockposition2)) { +- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition2, state, 2); // CraftBukkit ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, sourcePos, blockposition2, state, 2); // CraftBukkit // Paper - Use correct source for mushroom block spread event + } + } + diff --git a/patches/server/0904-Folia-scheduler-and-owned-region-API.patch b/patches/server/0904-Folia-scheduler-and-owned-region-API.patch deleted file mode 100644 index a981b4c7de51..000000000000 --- a/patches/server/0904-Folia-scheduler-and-owned-region-API.patch +++ /dev/null @@ -1,1366 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 17 Jun 2023 11:52:52 +0200 -Subject: [PATCH] Folia scheduler and owned region API - -Pulling Folia API to Paper is primarily intended for plugins -that want to target both Paper and Folia without unnecessary -compatibility layers. - -Add both a location based scheduler, an entity based scheduler, -and a global region scheduler. - -Owned region API may be useful for plugins which want to perform -operations over large areas outside of the buffer zone provided -by the regionaliser, as it is not guaranteed that anything -outside of the buffer zone is owned. Then, the plugins may use -the schedulers depending on the result of the ownership check. - -diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -index 0f8fa69577f09030fe96df6fa37e88ed38134a2e..eeea1e6f7b1ed64567a3f90d8eb2e2cfd53e5912 100644 ---- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -@@ -249,6 +249,22 @@ class PaperPluginInstanceManager { - + pluginName + " (Is it up to date?)", ex, plugin); // Paper - } - -+ // Paper start - Folia schedulers -+ try { -+ this.server.getGlobalRegionScheduler().cancelTasks(plugin); -+ } catch (Throwable ex) { -+ this.handlePluginException("Error occurred (in the plugin loader) while cancelling global tasks for " -+ + pluginName + " (Is it up to date?)", ex, plugin); // Paper -+ } -+ -+ try { -+ this.server.getAsyncScheduler().cancelTasks(plugin); -+ } catch (Throwable ex) { -+ this.handlePluginException("Error occurred (in the plugin loader) while cancelling async tasks for " -+ + pluginName + " (Is it up to date?)", ex, plugin); // Paper -+ } -+ // Paper end - Folia schedulers -+ - try { - this.server.getServicesManager().unregisterAll(plugin); - } catch (Throwable ex) { -diff --git a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..62484ebf4550b05182f693a3180bbac5d5fd906d ---- /dev/null -+++ b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java -@@ -0,0 +1,181 @@ -+package io.papermc.paper.threadedregions; -+ -+import ca.spottedleaf.concurrentutil.util.Validate; -+import io.papermc.paper.util.TickThread; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import net.minecraft.world.entity.Entity; -+import org.bukkit.craftbukkit.entity.CraftEntity; -+ -+import java.util.ArrayDeque; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.function.Consumer; -+ -+/** -+ * An entity can move between worlds with an arbitrary tick delay, be temporarily removed -+ * for players (i.e end credits), be partially removed from world state (i.e inactive but not removed), -+ * teleport between ticking regions, teleport between worlds (which will change the underlying Entity object -+ * for non-players), and even be removed entirely from the server. The uncertainty of an entity's state can make -+ * it difficult to schedule tasks without worrying about undefined behaviors resulting from any of the states listed -+ * previously. -+ * -+ *

    -+ * This class is designed to eliminate those states by providing an interface to run tasks only when an entity -+ * is contained in a world, on the owning thread for the region, and by providing the current Entity object. -+ * The scheduler also allows a task to provide a callback, the "retired" callback, that will be invoked -+ * if the entity is removed before a task that was scheduled could be executed. The scheduler is also -+ * completely thread-safe, allowing tasks to be scheduled from any thread context. The scheduler also indicates -+ * properly whether a task was scheduled successfully (i.e scheduler not retired), thus the code scheduling any task -+ * knows whether the given callbacks will be invoked eventually or not - which may be critical for off-thread -+ * contexts. -+ *

    -+ */ -+public final class EntityScheduler { -+ -+ /** -+ * The Entity. Note that it is the CraftEntity, since only that class properly tracks world transfers. -+ */ -+ public final CraftEntity entity; -+ -+ private static final record ScheduledTask(Consumer run, Consumer retired) {} -+ -+ private long tickCount = 0L; -+ private static final long RETIRED_TICK_COUNT = -1L; -+ private final Object stateLock = new Object(); -+ private final Long2ObjectOpenHashMap> oneTimeDelayed = new Long2ObjectOpenHashMap<>(); -+ -+ private final ArrayDeque currentlyExecuting = new ArrayDeque<>(); -+ -+ public EntityScheduler(final CraftEntity entity) { -+ this.entity = Validate.notNull(entity); -+ } -+ -+ /** -+ * Retires the scheduler, preventing new tasks from being scheduled and invoking the retired callback -+ * on all currently scheduled tasks. -+ * -+ *

    -+ * Note: This should only be invoked after synchronously removing the entity from the world. -+ *

    -+ * -+ * @throws IllegalStateException If the scheduler is already retired. -+ */ -+ public void retire() { -+ synchronized (this.stateLock) { -+ if (this.tickCount == RETIRED_TICK_COUNT) { -+ throw new IllegalStateException("Already retired"); -+ } -+ this.tickCount = RETIRED_TICK_COUNT; -+ } -+ -+ final Entity thisEntity = this.entity.getHandleRaw(); -+ -+ // correctly handle and order retiring while running executeTick -+ for (int i = 0, len = this.currentlyExecuting.size(); i < len; ++i) { -+ final ScheduledTask task = this.currentlyExecuting.pollFirst(); -+ final Consumer retireTask = (Consumer)task.retired; -+ if (retireTask == null) { -+ continue; -+ } -+ -+ retireTask.accept(thisEntity); -+ } -+ -+ for (final List tasks : this.oneTimeDelayed.values()) { -+ for (int i = 0, len = tasks.size(); i < len; ++i) { -+ final ScheduledTask task = tasks.get(i); -+ final Consumer retireTask = (Consumer)task.retired; -+ if (retireTask == null) { -+ continue; -+ } -+ -+ retireTask.accept(thisEntity); -+ } -+ } -+ } -+ -+ /** -+ * Schedules a task with the given delay. If the task failed to schedule because the scheduler is retired (entity -+ * removed), then returns {@code false}. Otherwise, either the run callback will be invoked after the specified delay, -+ * or the retired callback will be invoked if the scheduler is retired. -+ * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, remove -+ * other entities, load chunks, load worlds, modify ticket levels, etc. -+ * -+ *

    -+ * It is guaranteed that the run and retired callback are invoked on the region which owns the entity. -+ *

    -+ *

    -+ * The run and retired callback take an Entity parameter representing the current object entity that the scheduler -+ * is tied to. Since the scheduler is transferred when an entity changes dimensions, it is possible the entity parameter -+ * is not the same when the task was first scheduled. Thus, only the parameter provided should be used. -+ *

    -+ * @param run The callback to run after the specified delay, may not be null. -+ * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. -+ * @param delay The delay in ticks before the run callback is invoked. Any value less-than 1 is treated as 1. -+ * @return {@code true} if the task was scheduled, which means that either the run function or the retired function -+ * will be invoked (but never both), or {@code false} indicating neither the run nor retired function will be invoked -+ * since the scheduler has been retired. -+ */ -+ public boolean schedule(final Consumer run, final Consumer retired, final long delay) { -+ Validate.notNull(run, "Run task may not be null"); -+ -+ final ScheduledTask task = new ScheduledTask(run, retired); -+ synchronized (this.stateLock) { -+ if (this.tickCount == RETIRED_TICK_COUNT) { -+ return false; -+ } -+ this.oneTimeDelayed.computeIfAbsent(this.tickCount + Math.max(1L, delay), (final long keyInMap) -> { -+ return new ArrayList<>(); -+ }).add(task); -+ } -+ -+ return true; -+ } -+ -+ /** -+ * Executes a tick for the scheduler. -+ * -+ * @throws IllegalStateException If the scheduler is retired. -+ */ -+ public void executeTick() { -+ final Entity thisEntity = this.entity.getHandleRaw(); -+ -+ TickThread.ensureTickThread(thisEntity, "May not tick entity scheduler asynchronously"); -+ final List toRun; -+ synchronized (this.stateLock) { -+ if (this.tickCount == RETIRED_TICK_COUNT) { -+ throw new IllegalStateException("Ticking retired scheduler"); -+ } -+ ++this.tickCount; -+ if (this.oneTimeDelayed.isEmpty()) { -+ toRun = null; -+ } else { -+ toRun = this.oneTimeDelayed.remove(this.tickCount); -+ } -+ } -+ -+ if (toRun != null) { -+ for (int i = 0, len = toRun.size(); i < len; ++i) { -+ this.currentlyExecuting.addLast(toRun.get(i)); -+ } -+ } -+ -+ // Note: It is allowed for the tasks executed to retire the entity in a given task. -+ for (int i = 0, len = this.currentlyExecuting.size(); i < len; ++i) { -+ if (!TickThread.isTickThreadFor(thisEntity)) { -+ // tp has been queued sync by one of the tasks -+ // in this case, we need to delay the tasks for next tick -+ break; -+ } -+ final ScheduledTask task = this.currentlyExecuting.pollFirst(); -+ -+ if (this.tickCount != RETIRED_TICK_COUNT) { -+ ((Consumer)task.run).accept(thisEntity); -+ } else { -+ // retired synchronously -+ // note: here task is null -+ break; -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FallbackRegionScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FallbackRegionScheduler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..94056d61a304ee012ae1828a33412516095f996f ---- /dev/null -+++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FallbackRegionScheduler.java -@@ -0,0 +1,30 @@ -+package io.papermc.paper.threadedregions.scheduler; -+ -+import org.bukkit.World; -+import org.bukkit.plugin.Plugin; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.function.Consumer; -+ -+public final class FallbackRegionScheduler implements RegionScheduler { -+ -+ @Override -+ public void execute(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Runnable run) { -+ plugin.getServer().getGlobalRegionScheduler().execute(plugin, run); -+ } -+ -+ @Override -+ public @NotNull ScheduledTask run(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Consumer task) { -+ return plugin.getServer().getGlobalRegionScheduler().run(plugin, task); -+ } -+ -+ @Override -+ public @NotNull ScheduledTask runDelayed(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Consumer task, final long delayTicks) { -+ return plugin.getServer().getGlobalRegionScheduler().runDelayed(plugin, task, delayTicks); -+ } -+ -+ @Override -+ public @NotNull ScheduledTask runAtFixedRate(@NotNull final Plugin plugin, @NotNull final World world, final int chunkX, final int chunkZ, @NotNull final Consumer task, final long initialDelayTicks, final long periodTicks) { -+ return plugin.getServer().getGlobalRegionScheduler().runAtFixedRate(plugin, task, initialDelayTicks, periodTicks); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaAsyncScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaAsyncScheduler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..374abffb9f1ce1a308822aed13038e77fe9ca08b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaAsyncScheduler.java -@@ -0,0 +1,328 @@ -+package io.papermc.paper.threadedregions.scheduler; -+ -+import ca.spottedleaf.concurrentutil.util.Validate; -+import com.mojang.logging.LogUtils; -+import org.bukkit.plugin.IllegalPluginAccessException; -+import org.bukkit.plugin.Plugin; -+import org.slf4j.Logger; -+ -+import java.util.Set; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.Executor; -+import java.util.concurrent.Executors; -+import java.util.concurrent.ScheduledExecutorService; -+import java.util.concurrent.ScheduledFuture; -+import java.util.concurrent.SynchronousQueue; -+import java.util.concurrent.ThreadFactory; -+import java.util.concurrent.ThreadPoolExecutor; -+import java.util.concurrent.TimeUnit; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.function.Consumer; -+import java.util.logging.Level; -+ -+public final class FoliaAsyncScheduler implements AsyncScheduler { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ private final Executor executors = new ThreadPoolExecutor(Math.max(4, Runtime.getRuntime().availableProcessors() / 2), Integer.MAX_VALUE, -+ 30L, TimeUnit.SECONDS, new SynchronousQueue<>(), -+ new ThreadFactory() { -+ private final AtomicInteger idGenerator = new AtomicInteger(); -+ -+ @Override -+ public Thread newThread(final Runnable run) { -+ final Thread ret = new Thread(run); -+ -+ ret.setName("Folia Async Scheduler Thread #" + this.idGenerator.getAndIncrement()); -+ ret.setPriority(Thread.NORM_PRIORITY - 1); -+ ret.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { -+ LOGGER.error("Uncaught exception in thread: " + thread.getName(), thr); -+ }); -+ -+ return ret; -+ } -+ } -+ ); -+ -+ private final ScheduledExecutorService timerThread = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { -+ @Override -+ public Thread newThread(final Runnable run) { -+ final Thread ret = new Thread(run); -+ -+ ret.setName("Folia Async Scheduler Thread Timer"); -+ ret.setPriority(Thread.NORM_PRIORITY + 1); -+ ret.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { -+ LOGGER.error("Uncaught exception in thread: " + thread.getName(), thr); -+ }); -+ -+ return ret; -+ } -+ }); -+ -+ private final Set tasks = ConcurrentHashMap.newKeySet(); -+ -+ @Override -+ public ScheduledTask runNow(final Plugin plugin, final Consumer task) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ final AsyncScheduledTask ret = new AsyncScheduledTask(plugin, -1L, task, null, -1L); -+ -+ this.tasks.add(ret); -+ this.executors.execute(ret); -+ -+ if (!plugin.isEnabled()) { -+ // handle race condition where plugin is disabled asynchronously -+ ret.cancel(); -+ } -+ -+ return ret; -+ } -+ -+ @Override -+ public ScheduledTask runDelayed(final Plugin plugin, final Consumer task, final long delay, -+ final TimeUnit unit) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ Validate.notNull(unit, "Time unit may not be null"); -+ if (delay < 0L) { -+ throw new IllegalArgumentException("Delay may not be < 0"); -+ } -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ return this.scheduleTimerTask(plugin, task, delay, -1L, unit); -+ } -+ -+ @Override -+ public ScheduledTask runAtFixedRate(final Plugin plugin, final Consumer task, final long initialDelay, -+ final long period, final TimeUnit unit) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ Validate.notNull(unit, "Time unit may not be null"); -+ if (initialDelay < 0L) { -+ throw new IllegalArgumentException("Initial delay may not be < 0"); -+ } -+ if (period <= 0L) { -+ throw new IllegalArgumentException("Period may not be <= 0"); -+ } -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ return this.scheduleTimerTask(plugin, task, initialDelay, period, unit); -+ } -+ -+ private AsyncScheduledTask scheduleTimerTask(final Plugin plugin, final Consumer task, final long initialDelay, -+ final long period, final TimeUnit unit) { -+ final AsyncScheduledTask ret = new AsyncScheduledTask( -+ plugin, period <= 0 ? period : unit.toNanos(period), task, null, -+ System.nanoTime() + unit.toNanos(initialDelay) -+ ); -+ -+ synchronized (ret) { -+ // even though ret is not published, we need to synchronise while scheduling to avoid a race condition -+ // for when a scheduled task immediately executes before we update the delay field and state field -+ ret.setDelay(this.timerThread.schedule(ret, initialDelay, unit)); -+ this.tasks.add(ret); -+ } -+ -+ if (!plugin.isEnabled()) { -+ // handle race condition where plugin is disabled asynchronously -+ ret.cancel(); -+ } -+ -+ return ret; -+ } -+ -+ @Override -+ public void cancelTasks(final Plugin plugin) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ -+ for (final AsyncScheduledTask task : this.tasks) { -+ if (task.plugin == plugin) { -+ task.cancel(); -+ } -+ } -+ } -+ -+ private final class AsyncScheduledTask implements ScheduledTask, Runnable { -+ -+ private static final int STATE_ON_TIMER = 0; -+ private static final int STATE_SCHEDULED_EXECUTOR = 1; -+ private static final int STATE_EXECUTING = 2; -+ private static final int STATE_EXECUTING_CANCELLED = 3; -+ private static final int STATE_FINISHED = 4; -+ private static final int STATE_CANCELLED = 5; -+ -+ private final Plugin plugin; -+ private final long repeatDelay; // in ns -+ private Consumer run; -+ private ScheduledFuture delay; -+ private int state; -+ private long scheduleTarget; -+ -+ public AsyncScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer run, -+ final ScheduledFuture delay, final long firstTarget) { -+ this.plugin = plugin; -+ this.repeatDelay = repeatDelay; -+ this.run = run; -+ this.delay = delay; -+ this.state = delay == null ? STATE_SCHEDULED_EXECUTOR : STATE_ON_TIMER; -+ this.scheduleTarget = firstTarget; -+ } -+ -+ private void setDelay(final ScheduledFuture delay) { -+ this.delay = delay; -+ this.state = STATE_SCHEDULED_EXECUTOR; -+ } -+ -+ @Override -+ public void run() { -+ final boolean repeating = this.isRepeatingTask(); -+ // try to advance state -+ final boolean timer; -+ synchronized (this) { -+ if (this.state == STATE_ON_TIMER) { -+ timer = true; -+ this.delay = null; -+ this.state = STATE_SCHEDULED_EXECUTOR; -+ } else if (this.state != STATE_SCHEDULED_EXECUTOR) { -+ // cancelled -+ if (this.state != STATE_CANCELLED) { -+ throw new IllegalStateException("Wrong state: " + this.state); -+ } -+ return; -+ } else { -+ timer = false; -+ this.state = STATE_EXECUTING; -+ } -+ } -+ -+ if (timer) { -+ // the scheduled executor is single thread, and unfortunately not expandable with threads -+ // so we just schedule onto the executor -+ FoliaAsyncScheduler.this.executors.execute(this); -+ return; -+ } -+ -+ try { -+ this.run.accept(this); -+ } catch (final Throwable throwable) { -+ this.plugin.getLogger().log(Level.WARNING, "Async task for " + this.plugin.getDescription().getFullName() + " generated an exception", throwable); -+ } finally { -+ boolean removeFromTasks = false; -+ synchronized (this) { -+ if (!repeating) { -+ // only want to execute once, so we're done -+ removeFromTasks = true; -+ this.state = STATE_FINISHED; -+ } else if (this.state != STATE_EXECUTING_CANCELLED) { -+ this.state = STATE_ON_TIMER; -+ // account for any delays, whether it be by task exec. or scheduler issues so that we keep -+ // the fixed schedule -+ final long currTime = System.nanoTime(); -+ final long delay = Math.max(0L, this.scheduleTarget + this.repeatDelay - currTime); -+ this.scheduleTarget = currTime + delay; -+ this.delay = FoliaAsyncScheduler.this.timerThread.schedule(this, delay, TimeUnit.NANOSECONDS); -+ } else { -+ // cancelled repeating task -+ removeFromTasks = true; -+ } -+ } -+ -+ if (removeFromTasks) { -+ this.run = null; -+ FoliaAsyncScheduler.this.tasks.remove(this); -+ } -+ } -+ } -+ -+ @Override -+ public Plugin getOwningPlugin() { -+ return this.plugin; -+ } -+ -+ @Override -+ public boolean isRepeatingTask() { -+ return this.repeatDelay > 0L; -+ } -+ -+ @Override -+ public CancelledState cancel() { -+ ScheduledFuture delay = null; -+ CancelledState ret; -+ synchronized (this) { -+ switch (this.state) { -+ case STATE_ON_TIMER: { -+ delay = this.delay; -+ this.delay = null; -+ this.state = STATE_CANCELLED; -+ ret = CancelledState.CANCELLED_BY_CALLER; -+ break; -+ } -+ case STATE_SCHEDULED_EXECUTOR: { -+ this.state = STATE_CANCELLED; -+ ret = CancelledState.CANCELLED_BY_CALLER; -+ break; -+ } -+ case STATE_EXECUTING: { -+ if (!this.isRepeatingTask()) { -+ return CancelledState.RUNNING; -+ } -+ this.state = STATE_EXECUTING_CANCELLED; -+ return CancelledState.NEXT_RUNS_CANCELLED; -+ } -+ case STATE_EXECUTING_CANCELLED: { -+ return CancelledState.NEXT_RUNS_CANCELLED_ALREADY; -+ } -+ case STATE_FINISHED: { -+ return CancelledState.ALREADY_EXECUTED; -+ } -+ case STATE_CANCELLED: { -+ return CancelledState.CANCELLED_ALREADY; -+ } -+ default: { -+ throw new IllegalStateException("Unknown state: " + this.state); -+ } -+ } -+ } -+ -+ if (delay != null) { -+ delay.cancel(false); -+ } -+ this.run = null; -+ FoliaAsyncScheduler.this.tasks.remove(this); -+ return ret; -+ } -+ -+ @Override -+ public ExecutionState getExecutionState() { -+ synchronized (this) { -+ switch (this.state) { -+ case STATE_ON_TIMER: -+ case STATE_SCHEDULED_EXECUTOR: -+ return ExecutionState.IDLE; -+ case STATE_EXECUTING: -+ return ExecutionState.RUNNING; -+ case STATE_EXECUTING_CANCELLED: -+ return ExecutionState.CANCELLED_RUNNING; -+ case STATE_FINISHED: -+ return ExecutionState.FINISHED; -+ case STATE_CANCELLED: -+ return ExecutionState.CANCELLED; -+ default: { -+ throw new IllegalStateException("Unknown state: " + this.state); -+ } -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..011754962896e32f51ed4606dcbea18a430a2bc1 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaEntityScheduler.java -@@ -0,0 +1,268 @@ -+package io.papermc.paper.threadedregions.scheduler; -+ -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Validate; -+import net.minecraft.world.entity.Entity; -+import org.bukkit.craftbukkit.entity.CraftEntity; -+import org.bukkit.plugin.IllegalPluginAccessException; -+import org.bukkit.plugin.Plugin; -+import org.jetbrains.annotations.Nullable; -+ -+import java.lang.invoke.VarHandle; -+import java.util.function.Consumer; -+import java.util.logging.Level; -+ -+public final class FoliaEntityScheduler implements EntityScheduler { -+ -+ private final CraftEntity entity; -+ -+ public FoliaEntityScheduler(final CraftEntity entity) { -+ this.entity = entity; -+ } -+ -+ private static Consumer wrap(final Plugin plugin, final Runnable runnable) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(runnable, "Runnable may not be null"); -+ -+ return (final Entity nmsEntity) -> { -+ if (!plugin.isEnabled()) { -+ // don't execute if the plugin is disabled -+ return; -+ } -+ try { -+ runnable.run(); -+ } catch (final Throwable throwable) { -+ plugin.getLogger().log(Level.WARNING, "Entity task for " + plugin.getDescription().getFullName() + " generated an exception", throwable); -+ } -+ }; -+ } -+ -+ @Override -+ public boolean execute(final Plugin plugin, final Runnable run, final Runnable retired, -+ final long delay) { -+ final Consumer runNMS = wrap(plugin, run); -+ final Consumer runRetired = retired == null ? null : wrap(plugin, retired); -+ -+ return this.entity.taskScheduler.schedule(runNMS, runRetired, delay); -+ } -+ -+ @Override -+ public @Nullable ScheduledTask run(final Plugin plugin, final Consumer task, final Runnable retired) { -+ return this.runDelayed(plugin, task, retired, 1); -+ } -+ -+ @Override -+ public @Nullable ScheduledTask runDelayed(final Plugin plugin, final Consumer task, final Runnable retired, -+ final long delayTicks) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ if (delayTicks <= 0) { -+ throw new IllegalArgumentException("Delay ticks may not be <= 0"); -+ } -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ final EntityScheduledTask ret = new EntityScheduledTask(plugin, -1, task, retired); -+ -+ if (!this.scheduleInternal(ret, delayTicks)) { -+ return null; -+ } -+ -+ if (!plugin.isEnabled()) { -+ // handle race condition where plugin is disabled asynchronously -+ ret.cancel(); -+ } -+ -+ return ret; -+ } -+ -+ @Override -+ public @Nullable ScheduledTask runAtFixedRate(final Plugin plugin, final Consumer task, -+ final Runnable retired, final long initialDelayTicks, final long periodTicks) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ if (initialDelayTicks <= 0) { -+ throw new IllegalArgumentException("Initial delay ticks may not be <= 0"); -+ } -+ if (periodTicks <= 0) { -+ throw new IllegalArgumentException("Period ticks may not be <= 0"); -+ } -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ final EntityScheduledTask ret = new EntityScheduledTask(plugin, periodTicks, task, retired); -+ -+ if (!this.scheduleInternal(ret, initialDelayTicks)) { -+ return null; -+ } -+ -+ if (!plugin.isEnabled()) { -+ // handle race condition where plugin is disabled asynchronously -+ ret.cancel(); -+ } -+ -+ return ret; -+ } -+ -+ private boolean scheduleInternal(final EntityScheduledTask ret, final long delay) { -+ return this.entity.taskScheduler.schedule(ret, ret, delay); -+ } -+ -+ private final class EntityScheduledTask implements ScheduledTask, Consumer { -+ -+ private static final int STATE_IDLE = 0; -+ private static final int STATE_EXECUTING = 1; -+ private static final int STATE_EXECUTING_CANCELLED = 2; -+ private static final int STATE_FINISHED = 3; -+ private static final int STATE_CANCELLED = 4; -+ -+ private final Plugin plugin; -+ private final long repeatDelay; // in ticks -+ private Consumer run; -+ private Runnable retired; -+ private volatile int state; -+ -+ private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(EntityScheduledTask.class, "state", int.class); -+ -+ private EntityScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer run, final Runnable retired) { -+ this.plugin = plugin; -+ this.repeatDelay = repeatDelay; -+ this.run = run; -+ this.retired = retired; -+ } -+ -+ private final int getStateVolatile() { -+ return (int)STATE_HANDLE.get(this); -+ } -+ -+ private final int compareAndExchangeStateVolatile(final int expect, final int update) { -+ return (int)STATE_HANDLE.compareAndExchange(this, expect, update); -+ } -+ -+ private final void setStateVolatile(final int value) { -+ STATE_HANDLE.setVolatile(this, value); -+ } -+ -+ @Override -+ public void accept(final Entity entity) { -+ if (!this.plugin.isEnabled()) { -+ // don't execute if the plugin is disabled -+ this.setStateVolatile(STATE_CANCELLED); -+ return; -+ } -+ -+ final boolean repeating = this.isRepeatingTask(); -+ if (STATE_IDLE != this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_EXECUTING)) { -+ // cancelled -+ return; -+ } -+ -+ final boolean retired = entity.isRemoved(); -+ -+ try { -+ if (!retired) { -+ this.run.accept(this); -+ } else { -+ if (this.retired != null) { -+ this.retired.run(); -+ } -+ } -+ } catch (final Throwable throwable) { -+ this.plugin.getLogger().log(Level.WARNING, "Entity task for " + this.plugin.getDescription().getFullName() + " generated an exception", throwable); -+ } finally { -+ boolean reschedule = false; -+ if (!repeating && !retired) { -+ this.setStateVolatile(STATE_FINISHED); -+ } else if (retired || !this.plugin.isEnabled()) { -+ this.setStateVolatile(STATE_CANCELLED); -+ } else if (STATE_EXECUTING == this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_IDLE)) { -+ reschedule = true; -+ } // else: cancelled repeating task -+ -+ if (!reschedule) { -+ this.run = null; -+ this.retired = null; -+ } else { -+ if (!FoliaEntityScheduler.this.scheduleInternal(this, this.repeatDelay)) { -+ // the task itself must have removed the entity, so in this case we need to mark as cancelled -+ this.setStateVolatile(STATE_CANCELLED); -+ } -+ } -+ } -+ } -+ -+ @Override -+ public Plugin getOwningPlugin() { -+ return this.plugin; -+ } -+ -+ @Override -+ public boolean isRepeatingTask() { -+ return this.repeatDelay > 0; -+ } -+ -+ @Override -+ public CancelledState cancel() { -+ for (int curr = this.getStateVolatile();;) { -+ switch (curr) { -+ case STATE_IDLE: { -+ if (STATE_IDLE == (curr = this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_CANCELLED))) { -+ this.state = STATE_CANCELLED; -+ this.run = null; -+ this.retired = null; -+ return CancelledState.CANCELLED_BY_CALLER; -+ } -+ // try again -+ continue; -+ } -+ case STATE_EXECUTING: { -+ if (!this.isRepeatingTask()) { -+ return CancelledState.RUNNING; -+ } -+ if (STATE_EXECUTING == (curr = this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_EXECUTING_CANCELLED))) { -+ return CancelledState.NEXT_RUNS_CANCELLED; -+ } -+ // try again -+ continue; -+ } -+ case STATE_EXECUTING_CANCELLED: { -+ return CancelledState.NEXT_RUNS_CANCELLED_ALREADY; -+ } -+ case STATE_FINISHED: { -+ return CancelledState.ALREADY_EXECUTED; -+ } -+ case STATE_CANCELLED: { -+ return CancelledState.CANCELLED_ALREADY; -+ } -+ default: { -+ throw new IllegalStateException("Unknown state: " + curr); -+ } -+ } -+ } -+ } -+ -+ @Override -+ public ExecutionState getExecutionState() { -+ final int state = this.getStateVolatile(); -+ switch (state) { -+ case STATE_IDLE: -+ return ExecutionState.IDLE; -+ case STATE_EXECUTING: -+ return ExecutionState.RUNNING; -+ case STATE_EXECUTING_CANCELLED: -+ return ExecutionState.CANCELLED_RUNNING; -+ case STATE_FINISHED: -+ return ExecutionState.FINISHED; -+ case STATE_CANCELLED: -+ return ExecutionState.CANCELLED; -+ default: { -+ throw new IllegalStateException("Unknown state: " + state); -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d306f911757a4d556c82c0070d4837db87afc497 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaGlobalRegionScheduler.java -@@ -0,0 +1,267 @@ -+package io.papermc.paper.threadedregions.scheduler; -+ -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Validate; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import org.bukkit.plugin.IllegalPluginAccessException; -+import org.bukkit.plugin.Plugin; -+ -+import java.lang.invoke.VarHandle; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.function.Consumer; -+import java.util.logging.Level; -+ -+public class FoliaGlobalRegionScheduler implements GlobalRegionScheduler { -+ -+ private long tickCount = 0L; -+ private final Object stateLock = new Object(); -+ private final Long2ObjectOpenHashMap> tasksByDeadline = new Long2ObjectOpenHashMap<>(); -+ -+ public void tick() { -+ final List run; -+ synchronized (this.stateLock) { -+ ++this.tickCount; -+ if (this.tasksByDeadline.isEmpty()) { -+ run = null; -+ } else { -+ run = this.tasksByDeadline.remove(this.tickCount); -+ } -+ } -+ -+ if (run == null) { -+ return; -+ } -+ -+ for (int i = 0, len = run.size(); i < len; ++i) { -+ run.get(i).run(); -+ } -+ } -+ -+ @Override -+ public void execute(final Plugin plugin, final Runnable run) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(run, "Runnable may not be null"); -+ -+ this.run(plugin, (final ScheduledTask task) -> { -+ run.run(); -+ }); -+ } -+ -+ @Override -+ public ScheduledTask run(final Plugin plugin, final Consumer task) { -+ return this.runDelayed(plugin, task, 1); -+ } -+ -+ @Override -+ public ScheduledTask runDelayed(final Plugin plugin, final Consumer task, final long delayTicks) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ if (delayTicks <= 0) { -+ throw new IllegalArgumentException("Delay ticks may not be <= 0"); -+ } -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ final GlobalScheduledTask ret = new GlobalScheduledTask(plugin, -1, task); -+ -+ this.scheduleInternal(ret, delayTicks); -+ -+ if (!plugin.isEnabled()) { -+ // handle race condition where plugin is disabled asynchronously -+ ret.cancel(); -+ } -+ -+ return ret; -+ } -+ -+ @Override -+ public ScheduledTask runAtFixedRate(final Plugin plugin, final Consumer task, final long initialDelayTicks, final long periodTicks) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(task, "Task may not be null"); -+ if (initialDelayTicks <= 0) { -+ throw new IllegalArgumentException("Initial delay ticks may not be <= 0"); -+ } -+ if (periodTicks <= 0) { -+ throw new IllegalArgumentException("Period ticks may not be <= 0"); -+ } -+ -+ if (!plugin.isEnabled()) { -+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); -+ } -+ -+ final GlobalScheduledTask ret = new GlobalScheduledTask(plugin, periodTicks, task); -+ -+ this.scheduleInternal(ret, initialDelayTicks); -+ -+ if (!plugin.isEnabled()) { -+ // handle race condition where plugin is disabled asynchronously -+ ret.cancel(); -+ } -+ -+ return ret; -+ } -+ -+ @Override -+ public void cancelTasks(final Plugin plugin) { -+ Validate.notNull(plugin, "Plugin may not be null"); -+ -+ final List toCancel = new ArrayList<>(); -+ synchronized (this.stateLock) { -+ for (final List tasks : this.tasksByDeadline.values()) { -+ for (int i = 0, len = tasks.size(); i < len; ++i) { -+ final GlobalScheduledTask task = tasks.get(i); -+ if (task.plugin == plugin) { -+ toCancel.add(task); -+ } -+ } -+ } -+ } -+ -+ for (int i = 0, len = toCancel.size(); i < len; ++i) { -+ toCancel.get(i).cancel(); -+ } -+ } -+ -+ private void scheduleInternal(final GlobalScheduledTask task, final long delay) { -+ // note: delay > 0 -+ synchronized (this.stateLock) { -+ this.tasksByDeadline.computeIfAbsent(this.tickCount + delay, (final long keyInMap) -> { -+ return new ArrayList<>(); -+ }).add(task); -+ } -+ } -+ -+ private final class GlobalScheduledTask implements ScheduledTask, Runnable { -+ -+ private static final int STATE_IDLE = 0; -+ private static final int STATE_EXECUTING = 1; -+ private static final int STATE_EXECUTING_CANCELLED = 2; -+ private static final int STATE_FINISHED = 3; -+ private static final int STATE_CANCELLED = 4; -+ -+ private final Plugin plugin; -+ private final long repeatDelay; // in ticks -+ private Consumer run; -+ private volatile int state; -+ -+ private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(GlobalScheduledTask.class, "state", int.class); -+ -+ private GlobalScheduledTask(final Plugin plugin, final long repeatDelay, final Consumer run) { -+ this.plugin = plugin; -+ this.repeatDelay = repeatDelay; -+ this.run = run; -+ } -+ -+ private final int getStateVolatile() { -+ return (int)STATE_HANDLE.get(this); -+ } -+ -+ private final int compareAndExchangeStateVolatile(final int expect, final int update) { -+ return (int)STATE_HANDLE.compareAndExchange(this, expect, update); -+ } -+ -+ private final void setStateVolatile(final int value) { -+ STATE_HANDLE.setVolatile(this, value); -+ } -+ -+ @Override -+ public void run() { -+ final boolean repeating = this.isRepeatingTask(); -+ if (STATE_IDLE != this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_EXECUTING)) { -+ // cancelled -+ return; -+ } -+ -+ try { -+ this.run.accept(this); -+ } catch (final Throwable throwable) { -+ this.plugin.getLogger().log(Level.WARNING, "Global task for " + this.plugin.getDescription().getFullName() + " generated an exception", throwable); -+ } finally { -+ boolean reschedule = false; -+ if (!repeating) { -+ this.setStateVolatile(STATE_FINISHED); -+ } else if (STATE_EXECUTING == this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_IDLE)) { -+ reschedule = true; -+ } // else: cancelled repeating task -+ -+ if (!reschedule) { -+ this.run = null; -+ } else { -+ FoliaGlobalRegionScheduler.this.scheduleInternal(this, this.repeatDelay); -+ } -+ } -+ } -+ -+ @Override -+ public Plugin getOwningPlugin() { -+ return this.plugin; -+ } -+ -+ @Override -+ public boolean isRepeatingTask() { -+ return this.repeatDelay > 0; -+ } -+ -+ @Override -+ public CancelledState cancel() { -+ for (int curr = this.getStateVolatile();;) { -+ switch (curr) { -+ case STATE_IDLE: { -+ if (STATE_IDLE == (curr = this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_CANCELLED))) { -+ this.state = STATE_CANCELLED; -+ this.run = null; -+ return CancelledState.CANCELLED_BY_CALLER; -+ } -+ // try again -+ continue; -+ } -+ case STATE_EXECUTING: { -+ if (!this.isRepeatingTask()) { -+ return CancelledState.RUNNING; -+ } -+ if (STATE_EXECUTING == (curr = this.compareAndExchangeStateVolatile(STATE_EXECUTING, STATE_EXECUTING_CANCELLED))) { -+ return CancelledState.NEXT_RUNS_CANCELLED; -+ } -+ // try again -+ continue; -+ } -+ case STATE_EXECUTING_CANCELLED: { -+ return CancelledState.NEXT_RUNS_CANCELLED_ALREADY; -+ } -+ case STATE_FINISHED: { -+ return CancelledState.ALREADY_EXECUTED; -+ } -+ case STATE_CANCELLED: { -+ return CancelledState.CANCELLED_ALREADY; -+ } -+ default: { -+ throw new IllegalStateException("Unknown state: " + curr); -+ } -+ } -+ } -+ } -+ -+ @Override -+ public ExecutionState getExecutionState() { -+ final int state = this.getStateVolatile(); -+ switch (state) { -+ case STATE_IDLE: -+ return ExecutionState.IDLE; -+ case STATE_EXECUTING: -+ return ExecutionState.RUNNING; -+ case STATE_EXECUTING_CANCELLED: -+ return ExecutionState.CANCELLED_RUNNING; -+ case STATE_FINISHED: -+ return ExecutionState.FINISHED; -+ case STATE_CANCELLED: -+ return ExecutionState.CANCELLED; -+ default: { -+ throw new IllegalStateException("Unknown state: " + state); -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 97dbe5a44d2791c6dee830654c3935f4ac54aad4..48da5bdabcf38afbbd1509eca56d5c761622409f 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1497,6 +1497,20 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -+ for (final Entity entity : level.getEntities().getAll()) { -+ if (entity.isRemoved()) { -+ continue; -+ } -+ final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw(); -+ if (bukkit != null) { -+ bukkit.taskScheduler.executeTick(); -+ } -+ } -+ }); -+ // Paper end - Folia scheduler API - io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper - this.profiler.push("commandFunctions"); - MinecraftTimings.commandFunctionsTimer.startTiming(); // Spigot // Paper -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 2eeb216002c1c91879780225335225552744524b..74b3f459c898dc9f5c4411a38c9018fb4866f0b1 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -647,6 +647,7 @@ public abstract class PlayerList { - - entityplayer.unRide(); - worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER); -+ entityplayer.retireScheduler(); // Paper - Folia schedulers - entityplayer.getAdvancements().stopListening(); - this.players.remove(entityplayer); - this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 417e64d587b516df94abee5893a6dc9e8917eeca..e2f6401249470af599b8b1371105fc01a58b0091 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -246,11 +246,23 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - public @org.jetbrains.annotations.Nullable net.minecraft.server.level.ChunkMap.TrackedEntity tracker; // Paper - public CraftEntity getBukkitEntity() { - if (this.bukkitEntity == null) { -- this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this); -+ // Paper start - Folia schedulers -+ synchronized (this) { -+ if (this.bukkitEntity == null) { -+ return this.bukkitEntity = CraftEntity.getEntity(this.level.getCraftServer(), this); -+ } -+ } -+ // Paper end - Folia schedulers - } - return this.bukkitEntity; - } - -+ // Paper start -+ public CraftEntity getBukkitEntityRaw() { -+ return this.bukkitEntity; -+ } -+ // Paper end -+ - @Override - public CommandSender getBukkitSender(CommandSourceStack wrapper) { - return this.getBukkitEntity(); -@@ -4421,6 +4433,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - - @Override - public final void setRemoved(Entity.RemovalReason reason) { -+ final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers - if (this.removalReason == null) { - this.removalReason = reason; - } -@@ -4431,12 +4444,28 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - - this.getPassengers().forEach(Entity::stopRiding); - this.levelCallback.onRemove(reason); -+ // Paper start - Folia schedulers -+ if (!(this instanceof ServerPlayer) && reason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) { -+ // Players need to be special cased, because they are regularly removed from the world -+ this.retireScheduler(); -+ } -+ // Paper end - Folia schedulers - } - - public void unsetRemoved() { - this.removalReason = null; - } - -+ // Paper start - Folia schedulers -+ /** -+ * Invoked only when the entity is truly removed from the server, never to be added to any world. -+ */ -+ public final void retireScheduler() { -+ // we need to force create the bukkit entity so that the scheduler can be retired... -+ this.getBukkitEntity().taskScheduler.retire(); -+ } -+ // Paper end - Folia schedulers -+ - @Override - public void setLevelCallback(EntityInLevelCallback changeListener) { - this.levelCallback = changeListener; -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index c026db3758b7fd9c57a1badd4c1a9c2b34c8712d..248292bdb26cb2f08a41692ed7e9262ca6d6dd13 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -306,6 +306,76 @@ 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 - -+ // Paper start - Folia region threading API -+ private final io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler regionizedScheduler = new io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler(); -+ private final io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler asyncScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler(); -+ private final io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler globalRegionScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler(); -+ -+ @Override -+ public final io.papermc.paper.threadedregions.scheduler.RegionScheduler getRegionScheduler() { -+ return this.regionizedScheduler; -+ } -+ -+ @Override -+ public final io.papermc.paper.threadedregions.scheduler.AsyncScheduler getAsyncScheduler() { -+ return this.asyncScheduler; -+ } -+ -+ @Override -+ public final io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler getGlobalRegionScheduler() { -+ return this.globalRegionScheduler; -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(World world, io.papermc.paper.math.Position position) { -+ return io.papermc.paper.util.TickThread.isTickThreadFor( -+ ((CraftWorld) world).getHandle(), position.blockX() >> 4, position.blockZ() >> 4 -+ ); -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(World world, io.papermc.paper.math.Position position, int squareRadiusChunks) { -+ return io.papermc.paper.util.TickThread.isTickThreadFor( -+ ((CraftWorld) world).getHandle(), position.blockX() >> 4, position.blockZ() >> 4, squareRadiusChunks -+ ); -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(Location location) { -+ World world = location.getWorld(); -+ return io.papermc.paper.util.TickThread.isTickThreadFor( -+ ((CraftWorld) world).getHandle(), location.getBlockX() >> 4, location.getBlockZ() >> 4 -+ ); -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(Location location, int squareRadiusChunks) { -+ World world = location.getWorld(); -+ return io.papermc.paper.util.TickThread.isTickThreadFor( -+ ((CraftWorld) world).getHandle(), location.getBlockX() >> 4, location.getBlockZ() >> 4, squareRadiusChunks -+ ); -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(World world, int chunkX, int chunkZ) { -+ return io.papermc.paper.util.TickThread.isTickThreadFor( -+ ((CraftWorld) world).getHandle(), chunkX, chunkZ -+ ); -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(World world, int chunkX, int chunkZ, int squareRadiusChunks) { -+ return io.papermc.paper.util.TickThread.isTickThreadFor( -+ ((CraftWorld) world).getHandle(), chunkX, chunkZ, squareRadiusChunks -+ ); -+ } -+ -+ @Override -+ public final boolean isOwnedByCurrentRegion(Entity entity) { -+ return io.papermc.paper.util.TickThread.isTickThreadFor(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandleRaw()); -+ } -+ // Paper end - Folia reagion threading API -+ - static { - ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); - ConfigurationSerialization.registerClass(CraftPlayerProfile.class); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index e043a43ebda1df7b78c1368ce33a3648345bcb08..b86746be78e909b75a91751fbc4759db088d42a4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -67,6 +67,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - private EntityDamageEvent lastDamageEvent; - private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftEntity.DATA_TYPE_REGISTRY); - protected net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers -+ // Paper start - Folia shedulers -+ public final io.papermc.paper.threadedregions.EntityScheduler taskScheduler = new io.papermc.paper.threadedregions.EntityScheduler(this); -+ private final io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler apiScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler(this); -+ -+ @Override -+ public final io.papermc.paper.threadedregions.scheduler.EntityScheduler getScheduler() { -+ return this.apiScheduler; -+ }; -+ // Paper end - Folia schedulers - - public CraftEntity(final CraftServer server, final Entity entity) { - this.server = server; -@@ -483,6 +492,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return this.entity; - } - -+ // Paper start -+ public Entity getHandleRaw() { -+ return this.entity; -+ } -+ // Paper end -+ - @Override - public final EntityType getType() { - return this.entityType; diff --git a/patches/server/0910-Respect-randomizeData-on-more-entities-when-spawning.patch b/patches/server/0904-Respect-randomizeData-on-more-entities-when-spawning.patch similarity index 100% rename from patches/server/0910-Respect-randomizeData-on-more-entities-when-spawning.patch rename to patches/server/0904-Respect-randomizeData-on-more-entities-when-spawning.patch diff --git a/patches/server/0905-Use-correct-seed-on-api-world-load.patch b/patches/server/0905-Use-correct-seed-on-api-world-load.patch new file mode 100644 index 000000000000..a39775a339ea --- /dev/null +++ b/patches/server/0905-Use-correct-seed-on-api-world-load.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Florian Schmidt +Date: Fri, 28 Jul 2023 14:14:35 +0200 +Subject: [PATCH] Use correct seed on api world load + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 458bf151cb733e023b897a2acff2ab3a10fe0949..33d5454b95011395e0868b4e6a338a2db4b5c398 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1361,7 +1361,7 @@ public final class CraftServer implements Server { + net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), this.console.options.has("eraseCache"), () -> true, iregistry); + } + +- long j = BiomeManager.obfuscateSeed(creator.seed()); ++ long j = BiomeManager.obfuscateSeed(worlddata.worldGenOptions().seed()); // Paper - use world seed + List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); + LevelStem worlddimension = iregistry.get(actualDimension); + diff --git a/patches/server/0906-API-for-updating-recipes-on-clients.patch b/patches/server/0906-API-for-updating-recipes-on-clients.patch deleted file mode 100644 index d169b018467c..000000000000 --- a/patches/server/0906-API-for-updating-recipes-on-clients.patch +++ /dev/null @@ -1,114 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 21 Aug 2021 17:25:38 -0700 -Subject: [PATCH] API for updating recipes on clients - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 56d8767a19b03b5e70c6a5a5cd747a59abf062ee..fd32811f00a2c82dcb6efb9d78ffee0240d5de0b 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1530,6 +1530,13 @@ public abstract class PlayerList { - } - - public void reloadResources() { -+ // Paper start - API for updating recipes on clients -+ this.reloadAdvancementData(); -+ this.reloadTagData(); -+ this.reloadRecipeData(); -+ } -+ public void reloadAdvancementData() { -+ // Paper end - API for updating recipes on clients - // CraftBukkit start - /*Iterator iterator = this.advancements.values().iterator(); - -@@ -1545,7 +1552,15 @@ public abstract class PlayerList { - } - // CraftBukkit end - -+ // Paper start - API for updating recipes on clients -+ } -+ public void reloadTagData() { -+ // Paper end - API for updating recipes on clients - this.broadcastAll(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries))); -+ // Paper start - API for updating recipes on clients -+ } -+ public void reloadRecipeData() { -+ // Paper end - API for updating recipes on clients - ClientboundUpdateRecipesPacket packetplayoutrecipeupdate = new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getRecipes()); - Iterator iterator1 = this.players.iterator(); - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index cc8e826c72ca954030c5b42f6704a6e95cb88c31..5ca1878e7c64ff1c270a3b90c456662cc2361f26 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1129,6 +1129,18 @@ public final class CraftServer implements Server { - ReloadCommand.reload(this.console); - } - -+ // Paper start - API for updating recipes on clients -+ @Override -+ public void updateResources() { -+ this.playerList.reloadResources(); -+ } -+ -+ @Override -+ public void updateRecipes() { -+ this.playerList.reloadRecipeData(); -+ } -+ // Paper end - API for updating recipes on clients -+ - private void loadIcon() { - this.icon = new CraftIconCache(null); - try { -@@ -1504,6 +1516,13 @@ public final class CraftServer implements Server { - - @Override - public boolean addRecipe(Recipe recipe) { -+ // Paper start - API for updating recipes on clients -+ return this.addRecipe(recipe, false); -+ } -+ -+ @Override -+ public boolean addRecipe(Recipe recipe, boolean resendRecipes) { -+ // Paper end - API for updating recipes on clients - CraftRecipe toAdd; - if (recipe instanceof CraftRecipe) { - toAdd = (CraftRecipe) recipe; -@@ -1533,6 +1552,11 @@ public final class CraftServer implements Server { - } - } - toAdd.addToCraftingManager(); -+ // Paper start - API for updating recipes on clients -+ if (resendRecipes) { -+ this.playerList.reloadRecipeData(); -+ } -+ // Paper end - API for updating recipes on clients - return true; - } - -@@ -1713,10 +1737,23 @@ public final class CraftServer implements Server { - - @Override - public boolean removeRecipe(NamespacedKey recipeKey) { -+ // Paper start - API for updating recipes on clients -+ return this.removeRecipe(recipeKey, false); -+ } -+ -+ @Override -+ public boolean removeRecipe(NamespacedKey recipeKey, boolean resendRecipes) { -+ // Paper end - API for updating recipes on clients - Preconditions.checkArgument(recipeKey != null, "recipeKey == null"); - - ResourceLocation mcKey = CraftNamespacedKey.toMinecraft(recipeKey); -- return this.getServer().getRecipeManager().removeRecipe(mcKey); -+ // Paper start - resend recipes on successful removal -+ boolean removed = this.getServer().getRecipeManager().removeRecipe(mcKey); -+ if (removed && resendRecipes) { -+ this.playerList.reloadRecipeData(); -+ } -+ return removed; -+ // Paper end - } - - @Override diff --git a/patches/server/0912-Remove-UpgradeData-neighbour-ticks-outside-of-range.patch b/patches/server/0906-Remove-UpgradeData-neighbour-ticks-outside-of-range.patch similarity index 100% rename from patches/server/0912-Remove-UpgradeData-neighbour-ticks-outside-of-range.patch rename to patches/server/0906-Remove-UpgradeData-neighbour-ticks-outside-of-range.patch diff --git a/patches/server/0907-Cache-map-ids-on-item-frames.patch b/patches/server/0907-Cache-map-ids-on-item-frames.patch new file mode 100644 index 000000000000..a65e4d702190 --- /dev/null +++ b/patches/server/0907-Cache-map-ids-on-item-frames.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Mon, 7 Aug 2023 12:58:28 +0200 +Subject: [PATCH] Cache map ids on item frames + + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 1f0931bdd4d82c05d7b5f8b8e5c2cc6d23905c73..da45984c9b2d3a55256efddde94580505f692655 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -118,7 +118,7 @@ public class ServerEntity { + ItemStack itemstack = entityitemframe.getItem(); + + if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable +- Integer integer = MapItem.getMapId(itemstack); ++ Integer integer = entityitemframe.cachedMapId; // Paper - Perf: Cache map ids on item frames + MapItemSavedData worldmap = MapItem.getSavedData(integer, this.level); + + if (worldmap != null) { +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +index 0cd57021cf308984415ca670f727ae61ac343fe7..80303f9466b8c7097151be313afc9a383693d18a 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +@@ -50,6 +50,7 @@ public class ItemFrame extends HangingEntity { + public static final int NUM_ROTATIONS = 8; + public float dropChance; + public boolean fixed; ++ public Integer cachedMapId; // Paper - Perf: Cache map ids on item frames + + public ItemFrame(EntityType type, Level world) { + super(type, world); +@@ -388,6 +389,7 @@ public class ItemFrame extends HangingEntity { + } + + private void onItemChanged(ItemStack stack) { ++ this.cachedMapId = MapItem.getMapId(stack); // Paper - Perf: Cache map ids on item frames + if (!stack.isEmpty() && stack.getFrame() != this) { + stack.setEntityRepresentation(this); + } diff --git a/patches/server/0908-Fix-custom-statistic-criteria-creation.patch b/patches/server/0908-Fix-custom-statistic-criteria-creation.patch new file mode 100644 index 000000000000..a8aba99c9f1f --- /dev/null +++ b/patches/server/0908-Fix-custom-statistic-criteria-creation.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Sat, 12 Aug 2023 15:33:49 +0200 +Subject: [PATCH] Fix custom statistic criteria creation + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 5f93c5a6e1c381898c50332099cc98063a108b4e..fab7dc81f774889300ed0affaef71cbda36517df 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -633,6 +633,12 @@ public final class CraftMagicNumbers implements UnsafeValues { + net.minecraft.core.Holder biomeBase = cra.getHandle().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME).getHolderOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.BIOME, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(biomeKey))); + cra.setBiome(x, y, z, biomeBase); + } ++ ++ @Override ++ public String getStatisticCriteriaKey(org.bukkit.Statistic statistic) { ++ if (statistic.getType() != org.bukkit.Statistic.Type.UNTYPED) return "minecraft.custom:minecraft." + statistic.getKey().getKey(); ++ return org.bukkit.craftbukkit.CraftStatistic.getNMSStatistic(statistic).getName(); ++ } + // Paper end + + /** diff --git a/patches/server/0908-Only-capture-actual-tree-growth.patch b/patches/server/0908-Only-capture-actual-tree-growth.patch deleted file mode 100644 index 9670be275b9e..000000000000 --- a/patches/server/0908-Only-capture-actual-tree-growth.patch +++ /dev/null @@ -1,73 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 21 Aug 2021 18:53:03 -0700 -Subject: [PATCH] Only capture actual tree growth - - -diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -index 175b965c92b8b8be9c671e1ee478afa9a2f7bf82..1fb809486ee56efd3d0ef3fa02503ba9be459f68 100644 ---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -868,6 +868,7 @@ public interface DispenseItemBehavior { - if (!fertilizeEvent.isCancelled()) { - for (org.bukkit.block.BlockState blockstate : blocks) { - blockstate.update(true); -+ worldserver.checkCapturedTreeStateForObserverNotify(blockposition, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed - } - } - } -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index e14d928e8bf484c61f2687621623942a27f30db1..0fd5decb0790423aba80a7c1e55ce39aff6761b4 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -422,6 +422,7 @@ public final class ItemStack { - } - for (CraftBlockState blockstate : blocks) { - world.setBlock(blockstate.getPosition(),blockstate.getHandle(), blockstate.getFlag()); // SPIGOT-7248 - manual update to avoid physics where appropriate -+ world.checkCapturedTreeStateForObserverNotify(blockposition, blockstate); // Paper - notify observers even if grow failed - if (blockstate instanceof org.bukkit.craftbukkit.block.CapturedBlockState capturedBlockState) capturedBlockState.checkTreeBlockHack(); // Paper - Fix beehives generating from using bonemeal - } - entityhuman.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index b942e9f163fa342c58b74d1cd6ffe6bdbe4f691a..cd19005d3f239a27a4ce764588c8df0b229035bf 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -1372,4 +1372,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent - } - // Paper end - respect global sound events gamerule -+ // Paper start - notify observers even if grow failed -+ public void checkCapturedTreeStateForObserverNotify(final BlockPos pos, final CraftBlockState craftBlockState) { -+ // notify observers if the block state is the same and the Y level equals the original y level (for mega trees) -+ // blocks at the same Y level with the same state can be assumed to be saplings which trigger observers regardless of if the -+ // tree grew or not -+ if (craftBlockState.getPosition().getY() == pos.getY() && this.getBlockState(craftBlockState.getPosition()) == craftBlockState.getHandle()) { -+ this.notifyAndUpdatePhysics(craftBlockState.getPosition(), null, craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getFlag(), 512); -+ } -+ } -+ // Paper end - notify observers even if grow failed - } -diff --git a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java -index 7e6ee9c1ccef3eaa6b2edc39e414bd186426aee5..836c86104ed4f0d375330c9123af5d502efefa4d 100644 ---- a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java -@@ -85,6 +85,7 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { - if (event == null || !event.isCancelled()) { - for (BlockState blockstate : blocks) { - blockstate.update(true); -+ world.checkCapturedTreeStateForObserverNotify(pos, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed - } - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 0a96b00a98227714ef99005e0a223765feae8fe9..e5506a7d074a9f89d41f4d5d7549a458779bef20 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -566,6 +566,7 @@ public class CraftBlock implements Block { - if (!event.isCancelled()) { - for (BlockState blockstate : blocks) { - blockstate.update(true); -+ world.checkCapturedTreeStateForObserverNotify(this.position, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed - } - } - } diff --git a/patches/server/0909-Bandaid-fix-for-Effect.patch b/patches/server/0909-Bandaid-fix-for-Effect.patch new file mode 100644 index 000000000000..f8dd13a38f88 --- /dev/null +++ b/patches/server/0909-Bandaid-fix-for-Effect.patch @@ -0,0 +1,157 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 28 Jul 2023 15:02:44 -0700 +Subject: [PATCH] Bandaid fix for Effect + +Effect or LevelEvent needs to be replaced +but ideally after the enum PR has been merged +upstream. Until then, this test and these fixes +should address all the known issues with them + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftEffect.java b/src/main/java/org/bukkit/craftbukkit/CraftEffect.java +index a4519762175c68256b1f303daca8b9408ac182bb..457e9093adb99d31ffc7f061d8c858f98c5d0572 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftEffect.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftEffect.java +@@ -16,12 +16,16 @@ public class CraftEffect { + public static int getDataValue(Effect effect, T data) { + int datavalue; + switch (effect) { ++ case PARTICLES_SCULK_CHARGE: // Paper - add missing effects ++ case TRIAL_SPAWNER_DETECT_PLAYER: // Paper - add missing effects + case VILLAGER_PLANT_GROW: + datavalue = (Integer) data; + break; + case POTION_BREAK: ++ if (data instanceof Potion) { // Paper - use Color for POTION_BREAK + datavalue = ((Potion) data).toDamageValue() & 0x3F; + break; ++ } // Paper - Color will fall through to cast below + case INSTANT_POTION_BREAK: + datavalue = ((Color) data).asRGB(); + break; +@@ -29,6 +33,13 @@ public class CraftEffect { + Preconditions.checkArgument(data == Material.AIR || ((Material) data).isRecord(), "Invalid record type for Material %s!", data); + datavalue = Item.getId(CraftItemType.bukkitToMinecraft((Material) data)); + break; ++ // Paper start - handle shoot white smoke event ++ case SHOOT_WHITE_SMOKE: ++ final BlockFace face = (BlockFace) data; ++ Preconditions.checkArgument(face.isCartesian(), face + " isn't cartesian"); ++ datavalue = org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(face).get3DDataValue(); ++ break; ++ // Paper end - handle shoot white smoke event + case SMOKE: + switch ((BlockFace) data) { + case DOWN: +@@ -60,8 +71,15 @@ public class CraftEffect { + } + break; + case STEP_SOUND: ++ if (data instanceof Material) { // Paper - support BlockData + Preconditions.checkArgument(((Material) data).isBlock(), "Material %s is not a block!", data); + datavalue = Block.getId(CraftBlockType.bukkitToMinecraft((Material) data).defaultBlockState()); ++ // Paper start - support BlockData ++ break; ++ } ++ case PARTICLES_AND_SOUND_BRUSH_BLOCK_COMPLETE: ++ datavalue = Block.getId(((org.bukkit.craftbukkit.block.data.CraftBlockData) data).getState()); ++ // Paper end + break; + case COMPOSTER_FILL_ATTEMPT: + datavalue = ((Boolean) data) ? 1 : 0; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 54cdb4b6a97250c1e15e2fce355e3699c9189948..457d5cdb510a11a069ac7f54a8ed95a74527bff3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1371,7 +1371,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public void playEffect(Location loc, Effect effect, T data, int radius) { + if (data != null) { + Preconditions.checkArgument(effect.getData() != null, "Effect.%s does not have a valid Data", effect); +- Preconditions.checkArgument(effect.getData().isAssignableFrom(data.getClass()), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); ++ Preconditions.checkArgument(effect.isApplicable(data), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); // Paper + } else { + // Special case: the axis is optional for ELECTRIC_SPARK + Preconditions.checkArgument(effect.getData() == null || effect == Effect.ELECTRIC_SPARK, "Wrong kind of data for the %s effect", effect); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 0f32750ec4bc85033c6da8a21f4ad3150112ece6..59dd4f06d33a0bb8e747857ee81f6465b7ed78b1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -837,7 +837,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + Preconditions.checkArgument(effect != null, "Effect cannot be null"); + if (data != null) { + Preconditions.checkArgument(effect.getData() != null, "Effect.%s does not have a valid Data", effect); +- Preconditions.checkArgument(effect.getData().isAssignableFrom(data.getClass()), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); ++ Preconditions.checkArgument(effect.isApplicable(data), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); // Paper + } else { + // Special case: the axis is optional for ELECTRIC_SPARK + Preconditions.checkArgument(effect.getData() == null || effect == Effect.ELECTRIC_SPARK, "Wrong kind of data for the %s effect", effect); +diff --git a/src/test/java/org/bukkit/EffectTest.java b/src/test/java/org/bukkit/EffectTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..875eacc2e5776901ba8593d0183844db2571f71b +--- /dev/null ++++ b/src/test/java/org/bukkit/EffectTest.java +@@ -0,0 +1,64 @@ ++package org.bukkit; ++ ++import java.lang.reflect.Field; ++import java.lang.reflect.Modifier; ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++import net.minecraft.world.level.block.LevelEvent; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertNotNull; ++import static org.junit.jupiter.api.Assertions.assertNull; ++import static org.junit.jupiter.api.Assertions.assertTrue; ++ ++public class EffectTest { ++ ++ private static List collectNmsLevelEvents() throws ReflectiveOperationException { ++ final List events = new ArrayList<>(); ++ for (final Field field : LevelEvent.class.getFields()) { ++ if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.getType() == int.class) { ++ events.add((int) field.get(null)); ++ } ++ } ++ return events; ++ } ++ ++ private static boolean isNotDeprecated(Effect effect) throws ReflectiveOperationException { ++ return !Effect.class.getDeclaredField(effect.name()).isAnnotationPresent(Deprecated.class); ++ } ++ ++ @SuppressWarnings("deprecation") ++ @Test ++ public void checkAllApiExists() throws ReflectiveOperationException { ++ Map toId = new HashMap<>(); ++ for (final Effect effect : Effect.values()) { ++ if (isNotDeprecated(effect)) { ++ final Effect put = toId.put(effect.getId(), effect); ++ assertNull(put, "duplicate API effect: " + put); ++ } ++ } ++ ++ for (final Integer event : collectNmsLevelEvents()) { ++ assertNotNull(toId.get(event), "missing API Effect: " + event); ++ } ++ } ++ ++ @SuppressWarnings("deprecation") ++ @Test ++ public void checkNoExtraApi() throws ReflectiveOperationException { ++ Map toId = new HashMap<>(); ++ for (final Effect effect : Effect.values()) { ++ if (isNotDeprecated(effect)) { ++ final Effect put = toId.put(effect.getId(), effect); ++ assertNull(put, "duplicate API effect: " + put); ++ } ++ } ++ ++ final List nmsEvents = collectNmsLevelEvents(); ++ for (final Map.Entry entry : toId.entrySet()) { ++ assertTrue(nmsEvents.contains(entry.getKey()), "Extra API Effect: " + entry.getValue()); ++ } ++ } ++} diff --git a/patches/server/0909-Use-correct-source-for-mushroom-block-spread-event.patch b/patches/server/0909-Use-correct-source-for-mushroom-block-spread-event.patch deleted file mode 100644 index b1a7bde7b7f8..000000000000 --- a/patches/server/0909-Use-correct-source-for-mushroom-block-spread-event.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Warrior <50800980+Warriorrrr@users.noreply.github.com> -Date: Tue, 8 Aug 2023 11:49:32 +0200 -Subject: [PATCH] Use correct source for mushroom block spread event - - -diff --git a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java -index 5889cb1cdb64875f0d7a7c681808b45cdc661d8e..96f9ca2439a617b5f90b826d4fc99c857301b1c2 100644 ---- a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java -@@ -68,6 +68,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { - } - - BlockPos blockposition2 = pos.offset(random.nextInt(3) - 1, random.nextInt(2) - random.nextInt(2), random.nextInt(3) - 1); -+ final BlockPos sourcePos = pos; // Paper - Use correct source for mushroom block spread event - - for (int j = 0; j < 4; ++j) { - if (world.isEmptyBlock(blockposition2) && state.canSurvive(world, blockposition2)) { -@@ -78,7 +79,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { - } - - if (world.isEmptyBlock(blockposition2) && state.canSurvive(world, blockposition2)) { -- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition2, state, 2); // CraftBukkit -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, sourcePos, blockposition2, state, 2); // CraftBukkit // Paper - Use correct source for mushroom block spread event - } - } - diff --git a/patches/server/0916-SculkCatalyst-bloom-API.patch b/patches/server/0910-SculkCatalyst-bloom-API.patch similarity index 100% rename from patches/server/0916-SculkCatalyst-bloom-API.patch rename to patches/server/0910-SculkCatalyst-bloom-API.patch diff --git a/patches/server/0917-API-for-an-entity-s-scoreboard-name.patch b/patches/server/0911-API-for-an-entity-s-scoreboard-name.patch similarity index 100% rename from patches/server/0917-API-for-an-entity-s-scoreboard-name.patch rename to patches/server/0911-API-for-an-entity-s-scoreboard-name.patch diff --git a/patches/server/0911-Use-correct-seed-on-api-world-load.patch b/patches/server/0911-Use-correct-seed-on-api-world-load.patch deleted file mode 100644 index 0b5ac6f5b900..000000000000 --- a/patches/server/0911-Use-correct-seed-on-api-world-load.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Florian Schmidt -Date: Fri, 28 Jul 2023 14:14:35 +0200 -Subject: [PATCH] Use correct seed on api world load - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 5ca1878e7c64ff1c270a3b90c456662cc2361f26..11be490bd3c92143cd608aafe94e4e18027ddaea 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1349,7 +1349,7 @@ public final class CraftServer implements Server { - net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), this.console.options.has("eraseCache"), () -> true, iregistry); - } - -- long j = BiomeManager.obfuscateSeed(creator.seed()); -+ long j = BiomeManager.obfuscateSeed(worlddata.worldGenOptions().seed()); // Paper - use world seed - List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); - LevelStem worlddimension = iregistry.get(actualDimension); - diff --git a/patches/server/0912-Deprecate-and-replace-methods-with-old-StructureType.patch b/patches/server/0912-Deprecate-and-replace-methods-with-old-StructureType.patch new file mode 100644 index 000000000000..ff4c42a8ca10 --- /dev/null +++ b/patches/server/0912-Deprecate-and-replace-methods-with-old-StructureType.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 10 Dec 2022 17:52:38 -0800 +Subject: [PATCH] Deprecate and replace methods with old StructureType + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 33d5454b95011395e0868b4e6a338a2db4b5c398..92369eb350fd795a4e99731d7ceda4f8b890791e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1958,6 +1958,11 @@ public final class CraftServer implements Server { + + ServerLevel worldServer = ((CraftWorld) world).getHandle(); + Location structureLocation = world.locateNearestStructure(location, structureType, radius, findUnexplored); ++ // Paper start - don't throw NPE ++ if (structureLocation == null) { ++ throw new IllegalStateException("Could not find a structure for " + structureType); ++ } ++ // Paper end + BlockPos structurePosition = CraftLocation.toBlockPosition(structureLocation); + + // Create map with trackPlayer = true, unlimitedTracking = true +@@ -1968,6 +1973,31 @@ public final class CraftServer implements Server { + + return CraftItemStack.asBukkitCopy(stack); + } ++ // Paper start - copied from above (uses un-deprecated StructureType type) ++ @Override ++ public ItemStack createExplorerMap(World world, Location location, org.bukkit.generator.structure.StructureType structureType, org.bukkit.map.MapCursor.Type mapIcon, int radius, boolean findUnexplored) { ++ Preconditions.checkArgument(world != null, "World cannot be null"); ++ Preconditions.checkArgument(location != null, "Location cannot be null"); ++ Preconditions.checkArgument(structureType != null, "StructureType cannot be null"); ++ Preconditions.checkArgument(mapIcon != null, "mapIcon cannot be null"); ++ ++ ServerLevel worldServer = ((CraftWorld) world).getHandle(); ++ final org.bukkit.util.StructureSearchResult structureSearchResult = world.locateNearestStructure(location, structureType, radius, findUnexplored); ++ if (structureSearchResult == null) { ++ return null; ++ } ++ Location structureLocation = structureSearchResult.getLocation(); ++ BlockPos structurePosition = new BlockPos(structureLocation.getBlockX(), structureLocation.getBlockY(), structureLocation.getBlockZ()); ++ ++ // Create map with showIcons = true, unlimitedTracking = true ++ net.minecraft.world.item.ItemStack stack = MapItem.create(worldServer, structurePosition.getX(), structurePosition.getZ(), MapView.Scale.NORMAL.getValue(), true, true); ++ MapItem.renderBiomePreviewMap(worldServer, stack); ++ // "+" map ID taken from VillagerTrades$TreasureMapForEmeralds ++ MapItem.getSavedData(stack, worldServer).addTargetDecoration(stack, structurePosition, "+", MapDecoration.Type.byIcon(mapIcon.getValue())); ++ ++ return CraftItemStack.asBukkitCopy(stack); ++ } ++ // Paper end + + @Override + public void shutdown() { diff --git a/patches/server/0913-Cache-map-ids-on-item-frames.patch b/patches/server/0913-Cache-map-ids-on-item-frames.patch deleted file mode 100644 index b8a8eeda7304..000000000000 --- a/patches/server/0913-Cache-map-ids-on-item-frames.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Warrior <50800980+Warriorrrr@users.noreply.github.com> -Date: Mon, 7 Aug 2023 12:58:28 +0200 -Subject: [PATCH] Cache map ids on item frames - - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 72e7c44946ef02fcb744f2a4d265706e21b33c10..dc6d2ebbce9ccda5aa4a80dadcc34d48b67b9368 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -118,7 +118,7 @@ public class ServerEntity { - ItemStack itemstack = entityitemframe.getItem(); - - if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable -- Integer integer = MapItem.getMapId(itemstack); -+ Integer integer = entityitemframe.cachedMapId; // Paper - Perf: Cache map ids on item frames - MapItemSavedData worldmap = MapItem.getSavedData(integer, this.level); - - if (worldmap != null) { -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -index 0cd57021cf308984415ca670f727ae61ac343fe7..80303f9466b8c7097151be313afc9a383693d18a 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -@@ -50,6 +50,7 @@ public class ItemFrame extends HangingEntity { - public static final int NUM_ROTATIONS = 8; - public float dropChance; - public boolean fixed; -+ public Integer cachedMapId; // Paper - Perf: Cache map ids on item frames - - public ItemFrame(EntityType type, Level world) { - super(type, world); -@@ -388,6 +389,7 @@ public class ItemFrame extends HangingEntity { - } - - private void onItemChanged(ItemStack stack) { -+ this.cachedMapId = MapItem.getMapId(stack); // Paper - Perf: Cache map ids on item frames - if (!stack.isEmpty() && stack.getFrame() != this) { - stack.setEntityRepresentation(this); - } diff --git a/patches/server/0913-Don-t-tab-complete-namespaced-commands-if-send-names.patch b/patches/server/0913-Don-t-tab-complete-namespaced-commands-if-send-names.patch new file mode 100644 index 000000000000..73b317b26d5a --- /dev/null +++ b/patches/server/0913-Don-t-tab-complete-namespaced-commands-if-send-names.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: EpicPlayerA10 +Date: Sun, 18 Jun 2023 12:38:24 +0200 +Subject: [PATCH] Don't tab-complete namespaced commands if send-namespaced is + false + +Tab-complete packet is supposed to tab-complete args for commands, but +it also can suggest commands like in version 1.12.2 or lower. + +This patch prevents server from sending namespaced commands when player +requests tab-complete only commands. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 3141681dc21f7a61fcc77bbf65975072b07c8970..5092aed16ad9ed049624030a6c26a8013e92ae56 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -792,6 +792,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); + + this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { ++ // Paper start - Don't tab-complete namespaced commands if send-namespaced is false ++ if (!org.spigotmc.SpigotConfig.sendNamespaced && suggestions.getRange().getStart() <= 1) { ++ suggestions.getList().removeIf(suggestion -> suggestion.getText().contains(":")); ++ } ++ // Paper end - Don't tab-complete namespaced commands if send-namespaced is false + // Paper start - Brigadier API + com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); + suggestEvent.setCancelled(suggestions.isEmpty()); diff --git a/patches/server/0914-Fix-custom-statistic-criteria-creation.patch b/patches/server/0914-Fix-custom-statistic-criteria-creation.patch deleted file mode 100644 index 65ade03da584..000000000000 --- a/patches/server/0914-Fix-custom-statistic-criteria-creation.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Sat, 12 Aug 2023 15:33:49 +0200 -Subject: [PATCH] Fix custom statistic criteria creation - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 3bf1c2a5273879a64e81bcd8c107e7bc82cf679c..187854e8c560234710763f8e92c1a026550ba60d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -618,6 +618,12 @@ public final class CraftMagicNumbers implements UnsafeValues { - net.minecraft.core.Holder biomeBase = cra.getHandle().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME).getHolderOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.BIOME, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(biomeKey))); - cra.setBiome(x, y, z, biomeBase); - } -+ -+ @Override -+ public String getStatisticCriteriaKey(org.bukkit.Statistic statistic) { -+ if (statistic.getType() != org.bukkit.Statistic.Type.UNTYPED) return "minecraft.custom:minecraft." + statistic.getKey().getKey(); -+ return org.bukkit.craftbukkit.CraftStatistic.getNMSStatistic(statistic).getName(); -+ } - // Paper end - - /** diff --git a/patches/server/0920-Properly-handle-BlockBreakEvent-isDropItems.patch b/patches/server/0914-Properly-handle-BlockBreakEvent-isDropItems.patch similarity index 100% rename from patches/server/0920-Properly-handle-BlockBreakEvent-isDropItems.patch rename to patches/server/0914-Properly-handle-BlockBreakEvent-isDropItems.patch diff --git a/patches/server/0915-Bandaid-fix-for-Effect.patch b/patches/server/0915-Bandaid-fix-for-Effect.patch deleted file mode 100644 index 323cc5986c8d..000000000000 --- a/patches/server/0915-Bandaid-fix-for-Effect.patch +++ /dev/null @@ -1,157 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 28 Jul 2023 15:02:44 -0700 -Subject: [PATCH] Bandaid fix for Effect - -Effect or LevelEvent needs to be replaced -but ideally after the enum PR has been merged -upstream. Until then, this test and these fixes -should address all the known issues with them - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftEffect.java b/src/main/java/org/bukkit/craftbukkit/CraftEffect.java -index a4519762175c68256b1f303daca8b9408ac182bb..457e9093adb99d31ffc7f061d8c858f98c5d0572 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftEffect.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftEffect.java -@@ -16,12 +16,16 @@ public class CraftEffect { - public static int getDataValue(Effect effect, T data) { - int datavalue; - switch (effect) { -+ case PARTICLES_SCULK_CHARGE: // Paper - add missing effects -+ case TRIAL_SPAWNER_DETECT_PLAYER: // Paper - add missing effects - case VILLAGER_PLANT_GROW: - datavalue = (Integer) data; - break; - case POTION_BREAK: -+ if (data instanceof Potion) { // Paper - use Color for POTION_BREAK - datavalue = ((Potion) data).toDamageValue() & 0x3F; - break; -+ } // Paper - Color will fall through to cast below - case INSTANT_POTION_BREAK: - datavalue = ((Color) data).asRGB(); - break; -@@ -29,6 +33,13 @@ public class CraftEffect { - Preconditions.checkArgument(data == Material.AIR || ((Material) data).isRecord(), "Invalid record type for Material %s!", data); - datavalue = Item.getId(CraftItemType.bukkitToMinecraft((Material) data)); - break; -+ // Paper start - handle shoot white smoke event -+ case SHOOT_WHITE_SMOKE: -+ final BlockFace face = (BlockFace) data; -+ Preconditions.checkArgument(face.isCartesian(), face + " isn't cartesian"); -+ datavalue = org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(face).get3DDataValue(); -+ break; -+ // Paper end - handle shoot white smoke event - case SMOKE: - switch ((BlockFace) data) { - case DOWN: -@@ -60,8 +71,15 @@ public class CraftEffect { - } - break; - case STEP_SOUND: -+ if (data instanceof Material) { // Paper - support BlockData - Preconditions.checkArgument(((Material) data).isBlock(), "Material %s is not a block!", data); - datavalue = Block.getId(CraftBlockType.bukkitToMinecraft((Material) data).defaultBlockState()); -+ // Paper start - support BlockData -+ break; -+ } -+ case PARTICLES_AND_SOUND_BRUSH_BLOCK_COMPLETE: -+ datavalue = Block.getId(((org.bukkit.craftbukkit.block.data.CraftBlockData) data).getState()); -+ // Paper end - break; - case COMPOSTER_FILL_ATTEMPT: - datavalue = ((Boolean) data) ? 1 : 0; -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index caca37e0febbfaa2012820c8a6f0e6adbaf2451b..467aacb5ce61ac79d8294067fd681b081c195fbe 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1358,7 +1358,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public void playEffect(Location loc, Effect effect, T data, int radius) { - if (data != null) { - Preconditions.checkArgument(effect.getData() != null, "Effect.%s does not have a valid Data", effect); -- Preconditions.checkArgument(effect.getData().isAssignableFrom(data.getClass()), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); -+ Preconditions.checkArgument(effect.isApplicable(data), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); // Paper - } else { - // Special case: the axis is optional for ELECTRIC_SPARK - Preconditions.checkArgument(effect.getData() == null || effect == Effect.ELECTRIC_SPARK, "Wrong kind of data for the %s effect", effect); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 63b7118d23959ad75565271cc2b8a1143cd550e1..d06bce05edf6026be2af9583cddea70739a72032 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -831,7 +831,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - Preconditions.checkArgument(effect != null, "Effect cannot be null"); - if (data != null) { - Preconditions.checkArgument(effect.getData() != null, "Effect.%s does not have a valid Data", effect); -- Preconditions.checkArgument(effect.getData().isAssignableFrom(data.getClass()), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); -+ Preconditions.checkArgument(effect.isApplicable(data), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); // Paper - } else { - // Special case: the axis is optional for ELECTRIC_SPARK - Preconditions.checkArgument(effect.getData() == null || effect == Effect.ELECTRIC_SPARK, "Wrong kind of data for the %s effect", effect); -diff --git a/src/test/java/org/bukkit/EffectTest.java b/src/test/java/org/bukkit/EffectTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..875eacc2e5776901ba8593d0183844db2571f71b ---- /dev/null -+++ b/src/test/java/org/bukkit/EffectTest.java -@@ -0,0 +1,64 @@ -+package org.bukkit; -+ -+import java.lang.reflect.Field; -+import java.lang.reflect.Modifier; -+import java.util.ArrayList; -+import java.util.HashMap; -+import java.util.List; -+import java.util.Map; -+import net.minecraft.world.level.block.LevelEvent; -+import org.junit.jupiter.api.Test; -+ -+import static org.junit.jupiter.api.Assertions.assertNotNull; -+import static org.junit.jupiter.api.Assertions.assertNull; -+import static org.junit.jupiter.api.Assertions.assertTrue; -+ -+public class EffectTest { -+ -+ private static List collectNmsLevelEvents() throws ReflectiveOperationException { -+ final List events = new ArrayList<>(); -+ for (final Field field : LevelEvent.class.getFields()) { -+ if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.getType() == int.class) { -+ events.add((int) field.get(null)); -+ } -+ } -+ return events; -+ } -+ -+ private static boolean isNotDeprecated(Effect effect) throws ReflectiveOperationException { -+ return !Effect.class.getDeclaredField(effect.name()).isAnnotationPresent(Deprecated.class); -+ } -+ -+ @SuppressWarnings("deprecation") -+ @Test -+ public void checkAllApiExists() throws ReflectiveOperationException { -+ Map toId = new HashMap<>(); -+ for (final Effect effect : Effect.values()) { -+ if (isNotDeprecated(effect)) { -+ final Effect put = toId.put(effect.getId(), effect); -+ assertNull(put, "duplicate API effect: " + put); -+ } -+ } -+ -+ for (final Integer event : collectNmsLevelEvents()) { -+ assertNotNull(toId.get(event), "missing API Effect: " + event); -+ } -+ } -+ -+ @SuppressWarnings("deprecation") -+ @Test -+ public void checkNoExtraApi() throws ReflectiveOperationException { -+ Map toId = new HashMap<>(); -+ for (final Effect effect : Effect.values()) { -+ if (isNotDeprecated(effect)) { -+ final Effect put = toId.put(effect.getId(), effect); -+ assertNull(put, "duplicate API effect: " + put); -+ } -+ } -+ -+ final List nmsEvents = collectNmsLevelEvents(); -+ for (final Map.Entry entry : toId.entrySet()) { -+ assertTrue(nmsEvents.contains(entry.getKey()), "Extra API Effect: " + entry.getValue()); -+ } -+ } -+} diff --git a/patches/server/0921-Fire-entity-death-event-for-ender-dragon.patch b/patches/server/0915-Fire-entity-death-event-for-ender-dragon.patch similarity index 100% rename from patches/server/0921-Fire-entity-death-event-for-ender-dragon.patch rename to patches/server/0915-Fire-entity-death-event-for-ender-dragon.patch diff --git a/patches/server/0916-Configurable-entity-tracking-range-by-Y-coordinate.patch b/patches/server/0916-Configurable-entity-tracking-range-by-Y-coordinate.patch new file mode 100644 index 000000000000..2856a1f09c71 --- /dev/null +++ b/patches/server/0916-Configurable-entity-tracking-range-by-Y-coordinate.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ruViolence <78062896+ruViolence@users.noreply.github.com> +Date: Tue, 27 Jun 2023 15:38:18 +0800 +Subject: [PATCH] Configurable entity tracking range by Y coordinate + +Options to configure entity tracking by Y coordinate, also for each entity category. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 601693243c11b06fe0bae0040bf79d95696cdb21..4793871db838aba8b0370ada299406d3fb904c14 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1741,6 +1741,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z; + double d2 = d0 * d0; + boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); ++ // Paper start - Configurable entity tracking range by Y ++ if (flag && level.paperConfig().entities.trackingRangeY.enabled) { ++ double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1); ++ if (rangeY != -1) { ++ double vec3d_dy = player.getY() - this.entity.getY(); ++ flag = vec3d_dy * vec3d_dy <= rangeY * rangeY; ++ } ++ } ++ // Paper end - Configurable entity tracking range by Y + + // CraftBukkit start - respect vanish API + if (!player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { diff --git a/patches/server/0917-Add-Listing-API-for-Player.patch b/patches/server/0917-Add-Listing-API-for-Player.patch new file mode 100644 index 000000000000..93b847a66a7d --- /dev/null +++ b/patches/server/0917-Add-Listing-API-for-Player.patch @@ -0,0 +1,177 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Corey Shupe +Date: Wed, 11 Jan 2023 16:40:39 -0500 +Subject: [PATCH] Add Listing API for Player + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java +index d43106eb89b14667e85cd6e8fa047d64f2e8ec87..56eddd28429cf42c02d88b8bf79f8b616fa45289 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java +@@ -29,12 +29,46 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet actions, List entries) { ++ this.actions = actions; ++ this.entries = entries; ++ } ++ ++ public ClientboundPlayerInfoUpdatePacket(EnumSet actions, ClientboundPlayerInfoUpdatePacket.Entry entry) { ++ this.actions = actions; ++ this.entries = List.of(entry); ++ } ++ // Paper end - Add Listing API for Player + + public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(Collection players) { + EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); + return new ClientboundPlayerInfoUpdatePacket(enumSet, players); + } + ++ // Paper start - Add Listing API for Player ++ public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(Collection players, ServerPlayer forPlayer) { ++ final EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); ++ final List entries = new java.util.ArrayList<>(players.size()); ++ final org.bukkit.craftbukkit.entity.CraftPlayer bukkitEntity = forPlayer.getBukkitEntity(); ++ for (final ServerPlayer player : players) { ++ entries.add(new ClientboundPlayerInfoUpdatePacket.Entry(player, bukkitEntity.isListed(player.getBukkitEntity()))); ++ } ++ return new ClientboundPlayerInfoUpdatePacket(enumSet, entries); ++ } ++ ++ public static ClientboundPlayerInfoUpdatePacket createSinglePlayerInitializing(ServerPlayer player, boolean listed) { ++ final EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); ++ final List entries = List.of(new Entry(player, listed)); ++ return new ClientboundPlayerInfoUpdatePacket(enumSet, entries); ++ } ++ ++ public static ClientboundPlayerInfoUpdatePacket updateListed(UUID playerInfoId, boolean listed) { ++ EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); ++ return new ClientboundPlayerInfoUpdatePacket(enumSet, new ClientboundPlayerInfoUpdatePacket.Entry(playerInfoId, listed)); ++ } ++ // Paper end - Add Listing API for Player ++ + public ClientboundPlayerInfoUpdatePacket(FriendlyByteBuf buf) { + this.actions = buf.readEnumSet(ClientboundPlayerInfoUpdatePacket.Action.class); + this.entries = buf.readList((buf2) -> { +@@ -144,8 +178,16 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join + for (int i = 0; i < this.players.size(); ++i) { + ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i); + + if (entityplayer1.getBukkitEntity().canSee(bukkitPlayer)) { ++ // Paper start - Add Listing API for Player ++ if (entityplayer1.getBukkitEntity().isListed(bukkitPlayer)) { ++ // Paper end - Add Listing API for Player + entityplayer1.connection.send(packet); ++ // Paper start - Add Listing API for Player ++ } else { ++ entityplayer1.connection.send(ClientboundPlayerInfoUpdatePacket.createSinglePlayerInitializing(player, false)); ++ } ++ // Paper end - Add Listing API for Player + } + + if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { // Paper - Use single player info update packet on join; Don't include joining player +@@ -374,7 +382,7 @@ public abstract class PlayerList { + } + // Paper start - Use single player info update packet on join + if (!onlinePlayers.isEmpty()) { +- player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers)); ++ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player)); // Paper - Add Listing API for Player + } + // Paper end - Use single player info update packet on join + player.sentListPacket = true; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 59dd4f06d33a0bb8e747857ee81f6465b7ed78b1..a8bdeb0c5045344983efe083aa3214be00c07cf1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -188,6 +188,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + private final ConversationTracker conversationTracker = new ConversationTracker(); + private final Set channels = new HashSet(); + private final Map>> invertedVisibilityEntities = new HashMap<>(); ++ private final Set unlistedEntities = new HashSet<>(); // Paper - Add Listing API for Player + private static final WeakHashMap> pluginWeakReferences = new WeakHashMap<>(); + private int hash = 0; + private double health = 20; +@@ -1995,7 +1996,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + otherPlayer.setUUID(uuidOverride); + } + // Paper end +- this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer))); ++ this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer), this.getHandle())); // Paper - Add Listing API for Player + if (original != null) otherPlayer.setUUID(original); // Paper - uuid override + } + +@@ -2102,6 +2103,43 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return (entity != null) ? this.canSee(entity) : false; // If we can't find it, we can't see it + } + ++ // Paper start - Add Listing API for Player ++ @Override ++ public boolean isListed(Player other) { ++ return !this.unlistedEntities.contains(other.getUniqueId()); ++ } ++ ++ @Override ++ public boolean unlistPlayer(@NotNull Player other) { ++ Preconditions.checkNotNull(other, "hidden entity cannot be null"); ++ if (this.getHandle().connection == null) return false; ++ if (this.equals(other)) return false; ++ if (!this.canSee(other)) return false; ++ ++ if (unlistedEntities.add(other.getUniqueId())) { ++ this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.updateListed(other.getUniqueId(), false)); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ @Override ++ public boolean listPlayer(@NotNull Player other) { ++ Preconditions.checkNotNull(other, "hidden entity cannot be null"); ++ if (this.getHandle().connection == null) return false; ++ if (this.equals(other)) return false; ++ if (!this.canSee(other)) throw new IllegalStateException("Player cannot see other player"); ++ ++ if (this.unlistedEntities.remove(other.getUniqueId())) { ++ this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.updateListed(other.getUniqueId(), true)); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ // Paper end - Add Listing API for Player ++ + @Override + public Map serialize() { + Map result = new LinkedHashMap(); diff --git a/patches/server/0924-Configurable-Region-Compression-Format.patch b/patches/server/0918-Configurable-Region-Compression-Format.patch similarity index 100% rename from patches/server/0924-Configurable-Region-Compression-Format.patch rename to patches/server/0918-Configurable-Region-Compression-Format.patch diff --git a/patches/server/0918-Deprecate-and-replace-methods-with-old-StructureType.patch b/patches/server/0918-Deprecate-and-replace-methods-with-old-StructureType.patch deleted file mode 100644 index 25f7929557b6..000000000000 --- a/patches/server/0918-Deprecate-and-replace-methods-with-old-StructureType.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 10 Dec 2022 17:52:38 -0800 -Subject: [PATCH] Deprecate and replace methods with old StructureType - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 11be490bd3c92143cd608aafe94e4e18027ddaea..b23cc5371cb8fc2cfaba70c73cd9d4b0accb3283 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1946,6 +1946,11 @@ public final class CraftServer implements Server { - - ServerLevel worldServer = ((CraftWorld) world).getHandle(); - Location structureLocation = world.locateNearestStructure(location, structureType, radius, findUnexplored); -+ // Paper start - don't throw NPE -+ if (structureLocation == null) { -+ throw new IllegalStateException("Could not find a structure for " + structureType); -+ } -+ // Paper end - BlockPos structurePosition = CraftLocation.toBlockPosition(structureLocation); - - // Create map with trackPlayer = true, unlimitedTracking = true -@@ -1956,6 +1961,31 @@ public final class CraftServer implements Server { - - return CraftItemStack.asBukkitCopy(stack); - } -+ // Paper start - copied from above (uses un-deprecated StructureType type) -+ @Override -+ public ItemStack createExplorerMap(World world, Location location, org.bukkit.generator.structure.StructureType structureType, org.bukkit.map.MapCursor.Type mapIcon, int radius, boolean findUnexplored) { -+ Preconditions.checkArgument(world != null, "World cannot be null"); -+ Preconditions.checkArgument(location != null, "Location cannot be null"); -+ Preconditions.checkArgument(structureType != null, "StructureType cannot be null"); -+ Preconditions.checkArgument(mapIcon != null, "mapIcon cannot be null"); -+ -+ ServerLevel worldServer = ((CraftWorld) world).getHandle(); -+ final org.bukkit.util.StructureSearchResult structureSearchResult = world.locateNearestStructure(location, structureType, radius, findUnexplored); -+ if (structureSearchResult == null) { -+ return null; -+ } -+ Location structureLocation = structureSearchResult.getLocation(); -+ BlockPos structurePosition = new BlockPos(structureLocation.getBlockX(), structureLocation.getBlockY(), structureLocation.getBlockZ()); -+ -+ // Create map with showIcons = true, unlimitedTracking = true -+ net.minecraft.world.item.ItemStack stack = MapItem.create(worldServer, structurePosition.getX(), structurePosition.getZ(), MapView.Scale.NORMAL.getValue(), true, true); -+ MapItem.renderBiomePreviewMap(worldServer, stack); -+ // "+" map ID taken from VillagerTrades$TreasureMapForEmeralds -+ MapItem.getSavedData(stack, worldServer).addTargetDecoration(stack, structurePosition, "+", MapDecoration.Type.byIcon(mapIcon.getValue())); -+ -+ return CraftItemStack.asBukkitCopy(stack); -+ } -+ // Paper end - - @Override - public void shutdown() { diff --git a/patches/server/0919-Add-BlockFace-to-BlockDamageEvent.patch b/patches/server/0919-Add-BlockFace-to-BlockDamageEvent.patch new file mode 100644 index 000000000000..81b837c30e2b --- /dev/null +++ b/patches/server/0919-Add-BlockFace-to-BlockDamageEvent.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: aerulion +Date: Mon, 21 Aug 2023 04:36:07 +0200 +Subject: [PATCH] Add BlockFace to BlockDamageEvent + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 4747b05619f37009a5a236678aceec6cfc1c0b79..d0ca98c3f9ea5c8cb1053da6b17e9a90c86b3ae7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -253,7 +253,7 @@ public class ServerPlayerGameMode { + } + return; + } +- org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, this.player.getInventory().getSelected(), f >= 1.0f); ++ org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, direction, this.player.getInventory().getSelected(), f >= 1.0f); // Paper - Add BlockFace to BlockDamageEvent + + if (blockEvent.isCancelled()) { + // Let the client know the block still exists +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 62f1552d86dc38d709f9e53f643a6d8ab38bd2d2..7d2d9b1069b1b76d0aa4cc6077b94350a331bb19 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -649,13 +649,13 @@ public class CraftEventFactory { + /** + * BlockDamageEvent + */ +- public static BlockDamageEvent callBlockDamageEvent(ServerPlayer who, BlockPos pos, ItemStack itemstack, boolean instaBreak) { ++ public static BlockDamageEvent callBlockDamageEvent(ServerPlayer who, BlockPos pos, Direction direction, ItemStack itemstack, boolean instaBreak) { // Paper - Add BlockFace to BlockDamageEvent + Player player = who.getBukkitEntity(); + CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); + + Block blockClicked = CraftBlock.at(who.level(), pos); + +- BlockDamageEvent event = new BlockDamageEvent(player, blockClicked, itemInHand, instaBreak); ++ BlockDamageEvent event = new BlockDamageEvent(player, blockClicked, CraftBlock.notchToBlockFace(direction), itemInHand, instaBreak); // Paper - Add BlockFace to BlockDamageEvent + player.getServer().getPluginManager().callEvent(event); + + return event; diff --git a/patches/server/0919-Don-t-tab-complete-namespaced-commands-if-send-names.patch b/patches/server/0919-Don-t-tab-complete-namespaced-commands-if-send-names.patch deleted file mode 100644 index be6481a9bdc2..000000000000 --- a/patches/server/0919-Don-t-tab-complete-namespaced-commands-if-send-names.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: EpicPlayerA10 -Date: Sun, 18 Jun 2023 12:38:24 +0200 -Subject: [PATCH] Don't tab-complete namespaced commands if send-namespaced is - false - -Tab-complete packet is supposed to tab-complete args for commands, but -it also can suggest commands like in version 1.12.2 or lower. - -This patch prevents server from sending namespaced commands when player -requests tab-complete only commands. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index e19c05504151885ca18496b50dcf6091d94078c0..10965bdd67dd1357d47f12cd96c204372aefd2d9 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -792,6 +792,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); - - this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { -+ // Paper start - Don't tab-complete namespaced commands if send-namespaced is false -+ if (!org.spigotmc.SpigotConfig.sendNamespaced && suggestions.getRange().getStart() <= 1) { -+ suggestions.getList().removeIf(suggestion -> suggestion.getText().contains(":")); -+ } -+ // Paper end - Don't tab-complete namespaced commands if send-namespaced is false - // Paper start - Brigadier API - com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); - suggestEvent.setCancelled(suggestions.isEmpty()); diff --git a/patches/server/0926-Fix-NPE-on-Boat-getStatus.patch b/patches/server/0920-Fix-NPE-on-Boat-getStatus.patch similarity index 100% rename from patches/server/0926-Fix-NPE-on-Boat-getStatus.patch rename to patches/server/0920-Fix-NPE-on-Boat-getStatus.patch diff --git a/patches/server/0921-Expand-Pose-API.patch b/patches/server/0921-Expand-Pose-API.patch new file mode 100644 index 000000000000..9adc4311d8a2 --- /dev/null +++ b/patches/server/0921-Expand-Pose-API.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SoSeDiK +Date: Wed, 11 Jan 2023 20:59:01 +0200 +Subject: [PATCH] Expand Pose API + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 777448a112951e4772adde27d4066d392235e44c..effd39457989f34823e4fa7bc038c47d04714317 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -418,6 +418,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + @javax.annotation.Nullable + private UUID originWorld; + public boolean freezeLocked = false; // Paper - Freeze Tick Lock API ++ public boolean fixedPose = false; // Paper - Expand Pose API + + public void setOrigin(@javax.annotation.Nonnull Location location) { + this.origin = location.toVector(); +@@ -618,6 +619,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + public void onClientRemoval() {} + + public void setPose(net.minecraft.world.entity.Pose pose) { ++ if (this.fixedPose) return; // Paper - Expand Pose API + // CraftBukkit start + if (pose == this.getPose()) { + return; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 5abf149210f2338a125994d653209eea68359b11..1c035d1a9acc5f0a21169c66541d890a23bba033 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -896,6 +896,20 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public boolean isSneaking() { + return this.getHandle().isShiftKeyDown(); + } ++ ++ @Override ++ public void setPose(Pose pose, boolean fixed) { ++ Preconditions.checkNotNull(pose, "Pose cannot be null"); ++ final Entity handle = this.getHandle(); ++ handle.fixedPose = false; ++ handle.setPose(net.minecraft.world.entity.Pose.values()[pose.ordinal()]); ++ handle.fixedPose = fixed; ++ } ++ ++ @Override ++ public boolean hasFixedPose() { ++ return this.getHandle().fixedPose; ++ } + // Paper end + + @Override diff --git a/patches/server/0922-Configurable-entity-tracking-range-by-Y-coordinate.patch b/patches/server/0922-Configurable-entity-tracking-range-by-Y-coordinate.patch deleted file mode 100644 index 2279776ee8c4..000000000000 --- a/patches/server/0922-Configurable-entity-tracking-range-by-Y-coordinate.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: ruViolence <78062896+ruViolence@users.noreply.github.com> -Date: Tue, 27 Jun 2023 15:38:18 +0800 -Subject: [PATCH] Configurable entity tracking range by Y coordinate - -Options to configure entity tracking by Y coordinate, also for each entity category. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index c56136c785e9239df5f8782fae67edb6e52917f4..aaca1b2394641a74bbe1309f150507a49b8e1459 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1741,6 +1741,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z; - double d2 = d0 * d0; - boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); -+ // Paper start - Configurable entity tracking range by Y -+ if (flag && level.paperConfig().entities.trackingRangeY.enabled) { -+ double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1); -+ if (rangeY != -1) { -+ double vec3d_dy = player.getY() - this.entity.getY(); -+ flag = vec3d_dy * vec3d_dy <= rangeY * rangeY; -+ } -+ } -+ // Paper end - Configurable entity tracking range by Y - - // CraftBukkit start - respect vanish API - if (!player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { diff --git a/patches/server/0928-More-DragonBattle-API.patch b/patches/server/0922-More-DragonBattle-API.patch similarity index 100% rename from patches/server/0928-More-DragonBattle-API.patch rename to patches/server/0922-More-DragonBattle-API.patch diff --git a/patches/server/0923-Add-Listing-API-for-Player.patch b/patches/server/0923-Add-Listing-API-for-Player.patch deleted file mode 100644 index 92d73c686c05..000000000000 --- a/patches/server/0923-Add-Listing-API-for-Player.patch +++ /dev/null @@ -1,177 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Corey Shupe -Date: Wed, 11 Jan 2023 16:40:39 -0500 -Subject: [PATCH] Add Listing API for Player - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java -index d43106eb89b14667e85cd6e8fa047d64f2e8ec87..56eddd28429cf42c02d88b8bf79f8b616fa45289 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java -@@ -29,12 +29,46 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet actions, List entries) { -+ this.actions = actions; -+ this.entries = entries; -+ } -+ -+ public ClientboundPlayerInfoUpdatePacket(EnumSet actions, ClientboundPlayerInfoUpdatePacket.Entry entry) { -+ this.actions = actions; -+ this.entries = List.of(entry); -+ } -+ // Paper end - Add Listing API for Player - - public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(Collection players) { - EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); - return new ClientboundPlayerInfoUpdatePacket(enumSet, players); - } - -+ // Paper start - Add Listing API for Player -+ public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(Collection players, ServerPlayer forPlayer) { -+ final EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); -+ final List entries = new java.util.ArrayList<>(players.size()); -+ final org.bukkit.craftbukkit.entity.CraftPlayer bukkitEntity = forPlayer.getBukkitEntity(); -+ for (final ServerPlayer player : players) { -+ entries.add(new ClientboundPlayerInfoUpdatePacket.Entry(player, bukkitEntity.isListed(player.getBukkitEntity()))); -+ } -+ return new ClientboundPlayerInfoUpdatePacket(enumSet, entries); -+ } -+ -+ public static ClientboundPlayerInfoUpdatePacket createSinglePlayerInitializing(ServerPlayer player, boolean listed) { -+ final EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); -+ final List entries = List.of(new Entry(player, listed)); -+ return new ClientboundPlayerInfoUpdatePacket(enumSet, entries); -+ } -+ -+ public static ClientboundPlayerInfoUpdatePacket updateListed(UUID playerInfoId, boolean listed) { -+ EnumSet enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); -+ return new ClientboundPlayerInfoUpdatePacket(enumSet, new ClientboundPlayerInfoUpdatePacket.Entry(playerInfoId, listed)); -+ } -+ // Paper end - Add Listing API for Player -+ - public ClientboundPlayerInfoUpdatePacket(FriendlyByteBuf buf) { - this.actions = buf.readEnumSet(ClientboundPlayerInfoUpdatePacket.Action.class); - this.entries = buf.readList((buf2) -> { -@@ -144,8 +178,16 @@ public class ClientboundPlayerInfoUpdatePacket implements Packet onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join - for (int i = 0; i < this.players.size(); ++i) { - ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i); - - if (entityplayer1.getBukkitEntity().canSee(bukkitPlayer)) { -+ // Paper start - Add Listing API for Player -+ if (entityplayer1.getBukkitEntity().isListed(bukkitPlayer)) { -+ // Paper end - Add Listing API for Player - entityplayer1.connection.send(packet); -+ // Paper start - Add Listing API for Player -+ } else { -+ entityplayer1.connection.send(ClientboundPlayerInfoUpdatePacket.createSinglePlayerInitializing(player, false)); -+ } -+ // Paper end - Add Listing API for Player - } - - if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { // Paper - Use single player info update packet on join; Don't include joining player -@@ -374,7 +382,7 @@ public abstract class PlayerList { - } - // Paper start - Use single player info update packet on join - if (!onlinePlayers.isEmpty()) { -- player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers)); -+ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player)); // Paper - Add Listing API for Player - } - // Paper end - Use single player info update packet on join - player.sentListPacket = true; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 059c7a71812bf65124d7422e85f25f09eca1ea9e..de5d0b29d9b4631e7197520de7eb99ac6d9c8165 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -182,6 +182,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - private final ConversationTracker conversationTracker = new ConversationTracker(); - private final Set channels = new HashSet(); - private final Map>> invertedVisibilityEntities = new HashMap<>(); -+ private final Set unlistedEntities = new HashSet<>(); // Paper - Add Listing API for Player - private static final WeakHashMap> pluginWeakReferences = new WeakHashMap<>(); - private int hash = 0; - private double health = 20; -@@ -1965,7 +1966,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - otherPlayer.setUUID(uuidOverride); - } - // Paper end -- this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer))); -+ this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer), this.getHandle())); // Paper - Add Listing API for Player - if (original != null) otherPlayer.setUUID(original); // Paper - uuid override - } - -@@ -2072,6 +2073,43 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - return (entity != null) ? this.canSee(entity) : false; // If we can't find it, we can't see it - } - -+ // Paper start - Add Listing API for Player -+ @Override -+ public boolean isListed(Player other) { -+ return !this.unlistedEntities.contains(other.getUniqueId()); -+ } -+ -+ @Override -+ public boolean unlistPlayer(@NotNull Player other) { -+ Preconditions.checkNotNull(other, "hidden entity cannot be null"); -+ if (this.getHandle().connection == null) return false; -+ if (this.equals(other)) return false; -+ if (!this.canSee(other)) return false; -+ -+ if (unlistedEntities.add(other.getUniqueId())) { -+ this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.updateListed(other.getUniqueId(), false)); -+ return true; -+ } else { -+ return false; -+ } -+ } -+ -+ @Override -+ public boolean listPlayer(@NotNull Player other) { -+ Preconditions.checkNotNull(other, "hidden entity cannot be null"); -+ if (this.getHandle().connection == null) return false; -+ if (this.equals(other)) return false; -+ if (!this.canSee(other)) throw new IllegalStateException("Player cannot see other player"); -+ -+ if (this.unlistedEntities.remove(other.getUniqueId())) { -+ this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.updateListed(other.getUniqueId(), true)); -+ return true; -+ } else { -+ return false; -+ } -+ } -+ // Paper end - Add Listing API for Player -+ - @Override - public Map serialize() { - Map result = new LinkedHashMap(); diff --git a/patches/server/0929-Deep-clone-unhandled-nbt-tags.patch b/patches/server/0923-Deep-clone-unhandled-nbt-tags.patch similarity index 100% rename from patches/server/0929-Deep-clone-unhandled-nbt-tags.patch rename to patches/server/0923-Deep-clone-unhandled-nbt-tags.patch diff --git a/patches/server/0924-Add-PlayerPickItemEvent.patch b/patches/server/0924-Add-PlayerPickItemEvent.patch new file mode 100644 index 000000000000..79578e9ee69f --- /dev/null +++ b/patches/server/0924-Add-PlayerPickItemEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: RodneyMKay <36546810+RodneyMKay@users.noreply.github.com> +Date: Wed, 8 Sep 2021 21:34:01 +0200 +Subject: [PATCH] Add PlayerPickItemEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 5092aed16ad9ed049624030a6c26a8013e92ae56..9d24b63c9a8a22cafac570421333f6edfd4fd5c6 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -907,8 +907,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } +- this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed + // Paper end - validate pick item position ++ // Paper start - Add PlayerPickItemEvent ++ Player bukkitPlayer = this.player.getBukkitEntity(); ++ int targetSlot = this.player.getInventory().getSuitableHotbarSlot(); ++ int sourceSlot = packet.getSlot(); ++ ++ io.papermc.paper.event.player.PlayerPickItemEvent event = new io.papermc.paper.event.player.PlayerPickItemEvent(bukkitPlayer, targetSlot, sourceSlot); ++ if (!event.callEvent()) return; ++ ++ this.player.getInventory().pickSlot(event.getSourceSlot(), event.getTargetSlot()); ++ // Paper end - Add PlayerPickItemEvent + this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, this.player.getInventory().selected, this.player.getInventory().getItem(this.player.getInventory().selected))); + this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, packet.getSlot(), this.player.getInventory().getItem(packet.getSlot()))); + this.player.connection.send(new ClientboundSetCarriedItemPacket(this.player.getInventory().selected)); +diff --git a/src/main/java/net/minecraft/world/entity/player/Inventory.java b/src/main/java/net/minecraft/world/entity/player/Inventory.java +index 395cecc385e4126a7534ac9aeb15cf323efab03e..309acf7bd07e38043aa81e0e686edba1136bd04c 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Inventory.java ++++ b/src/main/java/net/minecraft/world/entity/player/Inventory.java +@@ -174,7 +174,13 @@ public class Inventory implements Container, Nameable { + } + + public void pickSlot(int slot) { +- this.selected = this.getSuitableHotbarSlot(); ++ // Paper start - Add PlayerPickItemEvent ++ pickSlot(slot, this.getSuitableHotbarSlot()); ++ } ++ ++ public void pickSlot(int slot, int targetSlot) { ++ this.selected = targetSlot; ++ // Paper end - Add PlayerPickItemEvent + ItemStack itemstack = (ItemStack) this.items.get(this.selected); + + this.items.set(this.selected, (ItemStack) this.items.get(slot)); diff --git a/patches/server/0925-Add-BlockFace-to-BlockDamageEvent.patch b/patches/server/0925-Add-BlockFace-to-BlockDamageEvent.patch deleted file mode 100644 index 9fc000d2c8a7..000000000000 --- a/patches/server/0925-Add-BlockFace-to-BlockDamageEvent.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: aerulion -Date: Mon, 21 Aug 2023 04:36:07 +0200 -Subject: [PATCH] Add BlockFace to BlockDamageEvent - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index e9b41b818c867d26fe9d3fa6e4d55fb432083d62..c9594f82c31cd750cdfb26619b1094a865058faf 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -253,7 +253,7 @@ public class ServerPlayerGameMode { - } - return; - } -- org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, this.player.getInventory().getSelected(), f >= 1.0f); -+ org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, direction, this.player.getInventory().getSelected(), f >= 1.0f); // Paper - Add BlockFace to BlockDamageEvent - - if (blockEvent.isCancelled()) { - // Let the client know the block still exists -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 470558446908581188b007410082cfcee3f16f4d..e420a558994129b2907d7e75152a558a01dc7b2e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -649,13 +649,13 @@ public class CraftEventFactory { - /** - * BlockDamageEvent - */ -- public static BlockDamageEvent callBlockDamageEvent(ServerPlayer who, BlockPos pos, ItemStack itemstack, boolean instaBreak) { -+ public static BlockDamageEvent callBlockDamageEvent(ServerPlayer who, BlockPos pos, Direction direction, ItemStack itemstack, boolean instaBreak) { // Paper - Add BlockFace to BlockDamageEvent - Player player = who.getBukkitEntity(); - CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); - - Block blockClicked = CraftBlock.at(who.level(), pos); - -- BlockDamageEvent event = new BlockDamageEvent(player, blockClicked, itemInHand, instaBreak); -+ BlockDamageEvent event = new BlockDamageEvent(player, blockClicked, CraftBlock.notchToBlockFace(direction), itemInHand, instaBreak); // Paper - Add BlockFace to BlockDamageEvent - player.getServer().getPluginManager().callEvent(event); - - return event; diff --git a/patches/server/0931-Allow-trident-custom-damage.patch b/patches/server/0925-Allow-trident-custom-damage.patch similarity index 100% rename from patches/server/0931-Allow-trident-custom-damage.patch rename to patches/server/0925-Allow-trident-custom-damage.patch diff --git a/patches/server/0932-Expose-hand-in-BlockCanBuildEvent.patch b/patches/server/0926-Expose-hand-in-BlockCanBuildEvent.patch similarity index 100% rename from patches/server/0932-Expose-hand-in-BlockCanBuildEvent.patch rename to patches/server/0926-Expose-hand-in-BlockCanBuildEvent.patch diff --git a/patches/server/0927-Expand-Pose-API.patch b/patches/server/0927-Expand-Pose-API.patch deleted file mode 100644 index cbabe035802e..000000000000 --- a/patches/server/0927-Expand-Pose-API.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SoSeDiK -Date: Wed, 11 Jan 2023 20:59:01 +0200 -Subject: [PATCH] Expand Pose API - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index ecdf98872f2f9b9b067be80701f20775b45e4aad..0e299073086cc06324794ca8b6e74674a70cc77a 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -418,6 +418,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - @javax.annotation.Nullable - private UUID originWorld; - public boolean freezeLocked = false; // Paper - Freeze Tick Lock API -+ public boolean fixedPose = false; // Paper - Expand Pose API - - public void setOrigin(@javax.annotation.Nonnull Location location) { - this.origin = location.toVector(); -@@ -618,6 +619,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - public void onClientRemoval() {} - - public void setPose(net.minecraft.world.entity.Pose pose) { -+ if (this.fixedPose) return; // Paper - Expand Pose API - // CraftBukkit start - if (pose == this.getPose()) { - return; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 5abf149210f2338a125994d653209eea68359b11..1c035d1a9acc5f0a21169c66541d890a23bba033 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -896,6 +896,20 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - public boolean isSneaking() { - return this.getHandle().isShiftKeyDown(); - } -+ -+ @Override -+ public void setPose(Pose pose, boolean fixed) { -+ Preconditions.checkNotNull(pose, "Pose cannot be null"); -+ final Entity handle = this.getHandle(); -+ handle.fixedPose = false; -+ handle.setPose(net.minecraft.world.entity.Pose.values()[pose.ordinal()]); -+ handle.fixedPose = fixed; -+ } -+ -+ @Override -+ public boolean hasFixedPose() { -+ return this.getHandle().fixedPose; -+ } - // Paper end - - @Override diff --git a/patches/server/0927-Optimize-nearest-structure-border-iteration.patch b/patches/server/0927-Optimize-nearest-structure-border-iteration.patch new file mode 100644 index 000000000000..e073d7a1dfe1 --- /dev/null +++ b/patches/server/0927-Optimize-nearest-structure-border-iteration.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martijn Muijsers +Date: Mon, 21 Aug 2023 21:05:09 +0200 +Subject: [PATCH] Optimize nearest structure border iteration + +Getting the nearest generated structure contains a nested set of loops that +iterates over all chunks at a specific chessboard distance. It does this by +iterating over the entire square of chunks within that distance, and checking +if the coordinates are at exactly the right distance to be on the border. + +This patch optimizes the iteration by only iterating over the border chunks. +This evaluated chunks are the same, and in the same order, as before, to +ensure that the returned found structure (which may for example be a buried +treasure that will be marked on a treasure map) is the same as in vanilla. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +index f767c822a8e86ce689d40d12c4ef2db3829a97cc..b26a4eb4951e87f891b59028d98b8ffba8e103a8 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -260,12 +260,15 @@ public abstract class ChunkGenerator { + int i1 = placement.spacing(); + + for (int j1 = -radius; j1 <= radius; ++j1) { +- boolean flag1 = j1 == -radius || j1 == radius; ++ // Paper start - Perf: iterate over border chunks instead of entire square chunk area ++ boolean flag1 = j1 == -radius || j1 == radius; final boolean onBorderAlongZAxis = flag1; // Paper - OBFHELPER + +- for (int k1 = -radius; k1 <= radius; ++k1) { +- boolean flag2 = k1 == -radius || k1 == radius; ++ for (int k1 = -radius; k1 <= radius; k1 += onBorderAlongZAxis ? 1 : radius * 2) { ++ // boolean flag2 = k1 == -radius || k1 == radius; + +- if (flag1 || flag2) { ++ // if (flag1 || flag2) { ++ if (true) { ++ // Paper end - Perf: iterate over border chunks instead of entire square chunk area + int l1 = centerChunkX + i1 * j1; + int i2 = centerChunkZ + i1 * k1; + ChunkPos chunkcoordintpair = placement.getPotentialStructureChunk(seed, l1, i2); diff --git a/patches/server/0928-Implement-OfflinePlayer-isConnected.patch b/patches/server/0928-Implement-OfflinePlayer-isConnected.patch new file mode 100644 index 000000000000..eecbaca15f35 --- /dev/null +++ b/patches/server/0928-Implement-OfflinePlayer-isConnected.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aeltumn +Date: Thu, 24 Aug 2023 13:05:30 +0200 +Subject: [PATCH] Implement OfflinePlayer#isConnected + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +index 96408d505ce80799868ff84554a3b0b25adabb22..4a875bce9563f3b9351ebecde9b0eb1287beb50e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +@@ -54,6 +54,13 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + return this.getPlayer() != null; + } + ++ // Paper start ++ @Override ++ public boolean isConnected() { ++ return false; ++ } ++ // Paper end ++ + @Override + public String getName() { + Player player = this.getPlayer(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index a8bdeb0c5045344983efe083aa3214be00c07cf1..1e36d9ef87507d1a771c605ec51c0f66d2cec089 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -243,6 +243,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return this.server.getPlayer(this.getUniqueId()) != null; + } + ++ // Paper start ++ @Override ++ public boolean isConnected() { ++ return !this.getHandle().hasDisconnected(); ++ } ++ // Paper end ++ + @Override + public InetSocketAddress getAddress() { + if (this.getHandle().connection == null) return null; diff --git a/patches/server/0935-Fix-inventory-desync.patch b/patches/server/0929-Fix-inventory-desync.patch similarity index 100% rename from patches/server/0935-Fix-inventory-desync.patch rename to patches/server/0929-Fix-inventory-desync.patch diff --git a/patches/server/0930-Add-PlayerPickItemEvent.patch b/patches/server/0930-Add-PlayerPickItemEvent.patch deleted file mode 100644 index 64e15e8e6de4..000000000000 --- a/patches/server/0930-Add-PlayerPickItemEvent.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: RodneyMKay <36546810+RodneyMKay@users.noreply.github.com> -Date: Wed, 8 Sep 2021 21:34:01 +0200 -Subject: [PATCH] Add PlayerPickItemEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 10965bdd67dd1357d47f12cd96c204372aefd2d9..27c2eed2aed6e0a2868501e553a5fbfc0307bb0c 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -907,8 +907,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause - return; - } -- this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed - // Paper end - validate pick item position -+ // Paper start - Add PlayerPickItemEvent -+ Player bukkitPlayer = this.player.getBukkitEntity(); -+ int targetSlot = this.player.getInventory().getSuitableHotbarSlot(); -+ int sourceSlot = packet.getSlot(); -+ -+ io.papermc.paper.event.player.PlayerPickItemEvent event = new io.papermc.paper.event.player.PlayerPickItemEvent(bukkitPlayer, targetSlot, sourceSlot); -+ if (!event.callEvent()) return; -+ -+ this.player.getInventory().pickSlot(event.getSourceSlot(), event.getTargetSlot()); -+ // Paper end - Add PlayerPickItemEvent - this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, this.player.getInventory().selected, this.player.getInventory().getItem(this.player.getInventory().selected))); - this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, packet.getSlot(), this.player.getInventory().getItem(packet.getSlot()))); - this.player.connection.send(new ClientboundSetCarriedItemPacket(this.player.getInventory().selected)); -diff --git a/src/main/java/net/minecraft/world/entity/player/Inventory.java b/src/main/java/net/minecraft/world/entity/player/Inventory.java -index 395cecc385e4126a7534ac9aeb15cf323efab03e..309acf7bd07e38043aa81e0e686edba1136bd04c 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Inventory.java -+++ b/src/main/java/net/minecraft/world/entity/player/Inventory.java -@@ -174,7 +174,13 @@ public class Inventory implements Container, Nameable { - } - - public void pickSlot(int slot) { -- this.selected = this.getSuitableHotbarSlot(); -+ // Paper start - Add PlayerPickItemEvent -+ pickSlot(slot, this.getSuitableHotbarSlot()); -+ } -+ -+ public void pickSlot(int slot, int targetSlot) { -+ this.selected = targetSlot; -+ // Paper end - Add PlayerPickItemEvent - ItemStack itemstack = (ItemStack) this.items.get(this.selected); - - this.items.set(this.selected, (ItemStack) this.items.get(slot)); diff --git a/patches/server/0930-Add-titleOverride-to-InventoryOpenEvent.patch b/patches/server/0930-Add-titleOverride-to-InventoryOpenEvent.patch new file mode 100644 index 000000000000..18021d7c5a54 --- /dev/null +++ b/patches/server/0930-Add-titleOverride-to-InventoryOpenEvent.patch @@ -0,0 +1,120 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 4 Mar 2022 12:45:03 -0800 +Subject: [PATCH] Add titleOverride to InventoryOpenEvent + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index d4930c40f03c5f331847bf52736c563a688d7daf..7272dc058c575efee5ac2643ce41b7d12e346e89 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1523,12 +1523,17 @@ public class ServerPlayer extends Player { + this.nextContainerCounter(); + AbstractContainerMenu container = factory.createMenu(this.containerCounter, this.getInventory(), this); + ++ Component title = null; // Paper - Add titleOverride to InventoryOpenEvent + // CraftBukkit start - Inventory open hook + if (container != null) { + container.setTitle(factory.getDisplayName()); + + boolean cancelled = false; +- container = CraftEventFactory.callInventoryOpenEvent(this, container, cancelled); ++ // Paper start - Add titleOverride to InventoryOpenEvent ++ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(this, container, cancelled); ++ container = result.getSecond(); ++ title = PaperAdventure.asVanilla(result.getFirst()); ++ // Paper end - Add titleOverride to InventoryOpenEvent + if (container == null && !cancelled) { // Let pre-cancelled events fall through + // SPIGOT-5263 - close chest if cancelled + if (factory instanceof Container) { +@@ -1550,7 +1555,7 @@ public class ServerPlayer extends Player { + } else { + // CraftBukkit start + this.containerMenu = container; +- if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); // Paper - Prevent opening inventories when frozen ++ if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), Objects.requireNonNullElseGet(title, container::getTitle))); // Paper - Add titleOverride to InventoryOpenEvent + // CraftBukkit end + this.initMenu(container); + return OptionalInt.of(this.containerCounter); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 94c2ea713e0614de570458f6b9c418a3d67d14b5..acbb64010cd59668aa1bcb52ff1220789cadb1d3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -357,12 +357,16 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + Preconditions.checkArgument(windowType != null, "Unknown windowType"); + AbstractContainerMenu container = new CraftContainer(inventory, player, player.nextContainerCounter()); + +- container = CraftEventFactory.callInventoryOpenEvent(player, container); ++ // Paper start - Add titleOverride to InventoryOpenEvent ++ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(player, container); ++ container = result.getSecond(); ++ // Paper end - Add titleOverride to InventoryOpenEvent + if (container == null) return; + + //String title = container.getBukkitView().getTitle(); // Paper - comment + net.kyori.adventure.text.Component adventure$title = container.getBukkitView().title(); // Paper + if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(container.getBukkitView().getTitle()); // Paper ++ if (result.getFirst() != null) adventure$title = result.getFirst(); // Paper - Add titleOverride to InventoryOpenEvent + + //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment + if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen +@@ -438,7 +442,10 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + } + + // Trigger an INVENTORY_OPEN event +- container = CraftEventFactory.callInventoryOpenEvent(player, container); ++ // Paper start - Add titleOverride to InventoryOpenEvent ++ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(player, container); ++ container = result.getSecond(); ++ // Paper end - Add titleOverride to InventoryOpenEvent + if (container == null) { + return; + } +@@ -449,6 +456,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + //String title = inventory.getTitle(); // Paper - comment + net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper + if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(inventory.getTitle()); // Paper ++ if (result.getFirst() != null) adventure$title = result.getFirst(); // Paper - Add titleOverride to InventoryOpenEvent + //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment + if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen + player.containerMenu = container; +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 7d2d9b1069b1b76d0aa4cc6077b94350a331bb19..0937d70b575b12bdfc0f643648088fa4cf13c230 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1387,10 +1387,21 @@ public class CraftEventFactory { + } + + public static AbstractContainerMenu callInventoryOpenEvent(ServerPlayer player, AbstractContainerMenu container) { +- return CraftEventFactory.callInventoryOpenEvent(player, container, false); ++ // Paper start - Add titleOverride to InventoryOpenEvent ++ return callInventoryOpenEventWithTitle(player, container).getSecond(); ++ } ++ public static com.mojang.datafixers.util.Pair callInventoryOpenEventWithTitle(ServerPlayer player, AbstractContainerMenu container) { ++ return CraftEventFactory.callInventoryOpenEventWithTitle(player, container, false); ++ // Paper end - Add titleOverride to InventoryOpenEvent + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - use method that acknowledges title overrides + public static AbstractContainerMenu callInventoryOpenEvent(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { ++ // Paper start - Add titleOverride to InventoryOpenEvent ++ return callInventoryOpenEventWithTitle(player, container, cancelled).getSecond(); ++ } ++ public static com.mojang.datafixers.util.Pair callInventoryOpenEventWithTitle(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { ++ // Paper end - Add titleOverride to InventoryOpenEvent + if (player.containerMenu != player.inventoryMenu) { // fire INVENTORY_CLOSE if one already open + player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId), InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason + } +@@ -1405,10 +1416,10 @@ public class CraftEventFactory { + + if (event.isCancelled()) { + container.transferTo(player.containerMenu, craftPlayer); +- return null; ++ return com.mojang.datafixers.util.Pair.of(null, null); // Paper - Add titleOverride to InventoryOpenEvent + } + +- return container; ++ return com.mojang.datafixers.util.Pair.of(event.titleOverride(), container); // Paper - Add titleOverride to InventoryOpenEvent + } + + public static ItemStack callPreCraftEvent(Container matrix, Container resultInventory, ItemStack result, InventoryView lastCraftView, boolean isRepair) { diff --git a/patches/server/0937-Configure-sniffer-egg-hatch-time.patch b/patches/server/0931-Configure-sniffer-egg-hatch-time.patch similarity index 100% rename from patches/server/0937-Configure-sniffer-egg-hatch-time.patch rename to patches/server/0931-Configure-sniffer-egg-hatch-time.patch diff --git a/patches/server/0932-Do-crystal-portal-proximity-check-before-entity-look.patch b/patches/server/0932-Do-crystal-portal-proximity-check-before-entity-look.patch new file mode 100644 index 000000000000..f71552dee54e --- /dev/null +++ b/patches/server/0932-Do-crystal-portal-proximity-check-before-entity-look.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Martijn Muijsers +Date: Tue, 15 Aug 2023 21:04:55 +0200 +Subject: [PATCH] Do crystal-portal proximity check before entity lookup + +This adds a very cheap distance check when an end crystal is placed. + +Attempting to respawn the dragon, which involves looking up the end crystal +entities near the portal, every time an end crystal is placed, can be slow on +some servers that have players placing end crystals as a style of combat. + +The very cheap distance check prevents running the entity lookup every time. + +diff --git a/src/main/java/net/minecraft/world/item/EndCrystalItem.java b/src/main/java/net/minecraft/world/item/EndCrystalItem.java +index ca1edc083847b47bb450b291723aca778a5912dc..e1696f6b77df4c8fceaece64701d4db78b0a4c42 100644 +--- a/src/main/java/net/minecraft/world/item/EndCrystalItem.java ++++ b/src/main/java/net/minecraft/world/item/EndCrystalItem.java +@@ -29,7 +29,7 @@ public class EndCrystalItem extends Item { + if (!iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) { + return InteractionResult.FAIL; + } else { +- BlockPos blockposition1 = blockposition.above(); ++ BlockPos blockposition1 = blockposition.above(); final BlockPos aboveBlockPosition = blockposition1; // Paper - OBFHELPER + + if (!world.isEmptyBlock(blockposition1)) { + return InteractionResult.FAIL; +@@ -56,7 +56,7 @@ public class EndCrystalItem extends Item { + EndDragonFight enderdragonbattle = ((ServerLevel) world).getDragonFight(); + + if (enderdragonbattle != null) { +- enderdragonbattle.tryRespawn(); ++ enderdragonbattle.tryRespawn(aboveBlockPosition); // Paper - Perf: Do crystal-portal proximity check before entity lookup + } + } + +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 adbe8ca78716114a3bc03136f02c631f30aff977..7e9c502a7bcda445333adc250a3106f33d38c128 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 +@@ -558,6 +558,12 @@ public class EndDragonFight { + } + + public boolean tryRespawn() { // CraftBukkit - return boolean ++ // Paper start - Perf: Do crystal-portal proximity check before entity lookup ++ return this.tryRespawn(null); ++ } ++ ++ public boolean tryRespawn(@Nullable BlockPos placedEndCrystalPos) { // placedEndCrystalPos is null if the tryRespawn() call was not caused by a placed end crystal ++ // Paper end - Perf: Do crystal-portal proximity check before entity lookup + if (this.dragonKilled && this.respawnStage == null) { + BlockPos blockposition = this.portalLocation; + +@@ -575,6 +581,22 @@ public class EndDragonFight { + blockposition = this.portalLocation; + } + ++ // Paper start - Perf: Do crystal-portal proximity check before entity lookup ++ if (placedEndCrystalPos != null) { ++ // The end crystal must be 0 or 1 higher than the portal origin ++ int dy = placedEndCrystalPos.getY() - blockposition.getY(); ++ if (dy != 0 && dy != 1) { ++ return false; ++ } ++ // The end crystal must be within a distance of 1 in one planar direction, and 3 in the other ++ int dx = placedEndCrystalPos.getX() - blockposition.getX(); ++ int dz = placedEndCrystalPos.getZ() - blockposition.getZ(); ++ if (!((dx >= -1 && dx <= 1 && dz >= -3 && dz <= 3) || (dx >= -3 && dx <= 3 && dz >= -1 && dz <= 1))) { ++ return false; ++ } ++ } ++ // Paper end - Perf: Do crystal-portal proximity check before entity lookup ++ + List list = Lists.newArrayList(); + BlockPos blockposition1 = blockposition.above(1); + Iterator iterator = Direction.Plane.HORIZONTAL.iterator(); diff --git a/patches/server/0933-Optimize-nearest-structure-border-iteration.patch b/patches/server/0933-Optimize-nearest-structure-border-iteration.patch deleted file mode 100644 index 2fedf7960147..000000000000 --- a/patches/server/0933-Optimize-nearest-structure-border-iteration.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Martijn Muijsers -Date: Mon, 21 Aug 2023 21:05:09 +0200 -Subject: [PATCH] Optimize nearest structure border iteration - -Getting the nearest generated structure contains a nested set of loops that -iterates over all chunks at a specific chessboard distance. It does this by -iterating over the entire square of chunks within that distance, and checking -if the coordinates are at exactly the right distance to be on the border. - -This patch optimizes the iteration by only iterating over the border chunks. -This evaluated chunks are the same, and in the same order, as before, to -ensure that the returned found structure (which may for example be a buried -treasure that will be marked on a treasure map) is the same as in vanilla. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -index aa2a014f5451e780219415fffcb64a6e7cdf9b87..b8b78494449c0cd638f9706a803dc54e184d981f 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -260,12 +260,15 @@ public abstract class ChunkGenerator { - int i1 = placement.spacing(); - - for (int j1 = -radius; j1 <= radius; ++j1) { -- boolean flag1 = j1 == -radius || j1 == radius; -+ // Paper start - Perf: iterate over border chunks instead of entire square chunk area -+ boolean flag1 = j1 == -radius || j1 == radius; final boolean onBorderAlongZAxis = flag1; // Paper - OBFHELPER - -- for (int k1 = -radius; k1 <= radius; ++k1) { -- boolean flag2 = k1 == -radius || k1 == radius; -+ for (int k1 = -radius; k1 <= radius; k1 += onBorderAlongZAxis ? 1 : radius * 2) { -+ // boolean flag2 = k1 == -radius || k1 == radius; - -- if (flag1 || flag2) { -+ // if (flag1 || flag2) { -+ if (true) { -+ // Paper end - Perf: iterate over border chunks instead of entire square chunk area - int l1 = centerChunkX + i1 * j1; - int i2 = centerChunkZ + i1 * k1; - ChunkPos chunkcoordintpair = placement.getPotentialStructureChunk(seed, l1, i2); diff --git a/patches/server/0939-Skip-POI-finding-if-stuck-in-vehicle.patch b/patches/server/0933-Skip-POI-finding-if-stuck-in-vehicle.patch similarity index 100% rename from patches/server/0939-Skip-POI-finding-if-stuck-in-vehicle.patch rename to patches/server/0933-Skip-POI-finding-if-stuck-in-vehicle.patch diff --git a/patches/server/0934-Add-slot-sanity-checks-in-container-clicks.patch b/patches/server/0934-Add-slot-sanity-checks-in-container-clicks.patch new file mode 100644 index 000000000000..9faa2b47073c --- /dev/null +++ b/patches/server/0934-Add-slot-sanity-checks-in-container-clicks.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Mon, 11 Sep 2023 12:01:57 +1000 +Subject: [PATCH] Add slot sanity checks in container clicks + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 9d24b63c9a8a22cafac570421333f6edfd4fd5c6..f59b49a7e9884def9bc9f30dbd4c72dac2915c76 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2888,6 +2888,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + break; + case SWAP: + if ((packet.getButtonNum() >= 0 && packet.getButtonNum() < 9) || packet.getButtonNum() == 40) { ++ // Paper start - Add slot sanity checks to container clicks ++ if (packet.getSlotNum() < 0) { ++ action = InventoryAction.NOTHING; ++ break; ++ } ++ // Paper end - Add slot sanity checks to container clicks + click = (packet.getButtonNum() == 40) ? ClickType.SWAP_OFFHAND : ClickType.NUMBER_KEY; + Slot clickedSlot = this.player.containerMenu.getSlot(packet.getSlotNum()); + if (clickedSlot.mayPickup(this.player)) { +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index 24caa1cf91cd50a5972238119aca1f85ec2b3d2b..75f836f07c66dbf71017ef0b7697851353d0f2e1 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -423,6 +423,7 @@ public abstract class AbstractContainerMenu { + this.resetQuickCraft(); + } + } else if (this.quickcraftStatus == 1) { ++ if (slotIndex < 0) return; // Paper - Add slot sanity checks to container clicks + slot = (Slot) this.slots.get(slotIndex); + itemstack = this.getCarried(); + if (AbstractContainerMenu.canItemQuickReplace(slot, itemstack, true) && slot.mayPlace(itemstack) && (this.quickcraftType == 2 || itemstack.getCount() > this.quickcraftSlots.size()) && this.canDragTo(slot)) { +@@ -597,6 +598,7 @@ public abstract class AbstractContainerMenu { + int j2; + + if (actionType == ClickType.SWAP && (button >= 0 && button < 9 || button == 40)) { ++ if (slotIndex < 0) return; // Paper - Add slot sanity checks to container clicks + ItemStack itemstack4 = playerinventory.getItem(button); + + slot = (Slot) this.slots.get(slotIndex); diff --git a/patches/server/0934-Implement-OfflinePlayer-isConnected.patch b/patches/server/0934-Implement-OfflinePlayer-isConnected.patch deleted file mode 100644 index 4b99a3257e3b..000000000000 --- a/patches/server/0934-Implement-OfflinePlayer-isConnected.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aeltumn -Date: Thu, 24 Aug 2023 13:05:30 +0200 -Subject: [PATCH] Implement OfflinePlayer#isConnected - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -index 96408d505ce80799868ff84554a3b0b25adabb22..4a875bce9563f3b9351ebecde9b0eb1287beb50e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -@@ -54,6 +54,13 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - return this.getPlayer() != null; - } - -+ // Paper start -+ @Override -+ public boolean isConnected() { -+ return false; -+ } -+ // Paper end -+ - @Override - public String getName() { - Player player = this.getPlayer(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index de5d0b29d9b4631e7197520de7eb99ac6d9c8165..3d061f6dd550e395ec572ecdbd80b8d2d36a4453 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -237,6 +237,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - return this.server.getPlayer(this.getUniqueId()) != null; - } - -+ // Paper start -+ @Override -+ public boolean isConnected() { -+ return !this.getHandle().hasDisconnected(); -+ } -+ // Paper end -+ - @Override - public InetSocketAddress getAddress() { - if (this.getHandle().connection == null) return null; diff --git a/patches/server/0941-Call-BlockRedstoneEvents-for-lecterns.patch b/patches/server/0935-Call-BlockRedstoneEvents-for-lecterns.patch similarity index 100% rename from patches/server/0941-Call-BlockRedstoneEvents-for-lecterns.patch rename to patches/server/0935-Call-BlockRedstoneEvents-for-lecterns.patch diff --git a/patches/server/0936-Add-titleOverride-to-InventoryOpenEvent.patch b/patches/server/0936-Add-titleOverride-to-InventoryOpenEvent.patch deleted file mode 100644 index 54eed0251c51..000000000000 --- a/patches/server/0936-Add-titleOverride-to-InventoryOpenEvent.patch +++ /dev/null @@ -1,120 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 4 Mar 2022 12:45:03 -0800 -Subject: [PATCH] Add titleOverride to InventoryOpenEvent - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index c4d49ddee5f6585e0455cc364f895b00b10226bf..6147ffdcb83a9d013a05facd75453d6500064fe7 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1523,12 +1523,17 @@ public class ServerPlayer extends Player { - this.nextContainerCounter(); - AbstractContainerMenu container = factory.createMenu(this.containerCounter, this.getInventory(), this); - -+ Component title = null; // Paper - Add titleOverride to InventoryOpenEvent - // CraftBukkit start - Inventory open hook - if (container != null) { - container.setTitle(factory.getDisplayName()); - - boolean cancelled = false; -- container = CraftEventFactory.callInventoryOpenEvent(this, container, cancelled); -+ // Paper start - Add titleOverride to InventoryOpenEvent -+ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(this, container, cancelled); -+ container = result.getSecond(); -+ title = PaperAdventure.asVanilla(result.getFirst()); -+ // Paper end - Add titleOverride to InventoryOpenEvent - if (container == null && !cancelled) { // Let pre-cancelled events fall through - // SPIGOT-5263 - close chest if cancelled - if (factory instanceof Container) { -@@ -1550,7 +1555,7 @@ public class ServerPlayer extends Player { - } else { - // CraftBukkit start - this.containerMenu = container; -- if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); // Paper - Prevent opening inventories when frozen -+ if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), Objects.requireNonNullElseGet(title, container::getTitle))); // Paper - Add titleOverride to InventoryOpenEvent - // CraftBukkit end - this.initMenu(container); - return OptionalInt.of(this.containerCounter); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 94c2ea713e0614de570458f6b9c418a3d67d14b5..acbb64010cd59668aa1bcb52ff1220789cadb1d3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -357,12 +357,16 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - Preconditions.checkArgument(windowType != null, "Unknown windowType"); - AbstractContainerMenu container = new CraftContainer(inventory, player, player.nextContainerCounter()); - -- container = CraftEventFactory.callInventoryOpenEvent(player, container); -+ // Paper start - Add titleOverride to InventoryOpenEvent -+ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(player, container); -+ container = result.getSecond(); -+ // Paper end - Add titleOverride to InventoryOpenEvent - if (container == null) return; - - //String title = container.getBukkitView().getTitle(); // Paper - comment - net.kyori.adventure.text.Component adventure$title = container.getBukkitView().title(); // Paper - if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(container.getBukkitView().getTitle()); // Paper -+ if (result.getFirst() != null) adventure$title = result.getFirst(); // Paper - Add titleOverride to InventoryOpenEvent - - //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment - if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen -@@ -438,7 +442,10 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - } - - // Trigger an INVENTORY_OPEN event -- container = CraftEventFactory.callInventoryOpenEvent(player, container); -+ // Paper start - Add titleOverride to InventoryOpenEvent -+ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(player, container); -+ container = result.getSecond(); -+ // Paper end - Add titleOverride to InventoryOpenEvent - if (container == null) { - return; - } -@@ -449,6 +456,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - //String title = inventory.getTitle(); // Paper - comment - net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper - if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(inventory.getTitle()); // Paper -+ if (result.getFirst() != null) adventure$title = result.getFirst(); // Paper - Add titleOverride to InventoryOpenEvent - //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment - if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - Prevent opening inventories when frozen - player.containerMenu = container; -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index e420a558994129b2907d7e75152a558a01dc7b2e..c74e6c6cd92d618cde3733200bcc23279c0df679 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1464,10 +1464,21 @@ public class CraftEventFactory { - } - - public static AbstractContainerMenu callInventoryOpenEvent(ServerPlayer player, AbstractContainerMenu container) { -- return CraftEventFactory.callInventoryOpenEvent(player, container, false); -+ // Paper start - Add titleOverride to InventoryOpenEvent -+ return callInventoryOpenEventWithTitle(player, container).getSecond(); -+ } -+ public static com.mojang.datafixers.util.Pair callInventoryOpenEventWithTitle(ServerPlayer player, AbstractContainerMenu container) { -+ return CraftEventFactory.callInventoryOpenEventWithTitle(player, container, false); -+ // Paper end - Add titleOverride to InventoryOpenEvent - } - -+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - use method that acknowledges title overrides - public static AbstractContainerMenu callInventoryOpenEvent(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { -+ // Paper start - Add titleOverride to InventoryOpenEvent -+ return callInventoryOpenEventWithTitle(player, container, cancelled).getSecond(); -+ } -+ public static com.mojang.datafixers.util.Pair callInventoryOpenEventWithTitle(ServerPlayer player, AbstractContainerMenu container, boolean cancelled) { -+ // Paper end - Add titleOverride to InventoryOpenEvent - if (player.containerMenu != player.inventoryMenu) { // fire INVENTORY_CLOSE if one already open - player.connection.handleContainerClose(new ServerboundContainerClosePacket(player.containerMenu.containerId), InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason - } -@@ -1482,10 +1493,10 @@ public class CraftEventFactory { - - if (event.isCancelled()) { - container.transferTo(player.containerMenu, craftPlayer); -- return null; -+ return com.mojang.datafixers.util.Pair.of(null, null); // Paper - Add titleOverride to InventoryOpenEvent - } - -- return container; -+ return com.mojang.datafixers.util.Pair.of(event.titleOverride(), container); // Paper - Add titleOverride to InventoryOpenEvent - } - - public static ItemStack callPreCraftEvent(Container matrix, Container resultInventory, ItemStack result, InventoryView lastCraftView, boolean isRepair) { diff --git a/patches/server/0936-Allow-proper-checking-of-empty-item-stacks.patch b/patches/server/0936-Allow-proper-checking-of-empty-item-stacks.patch new file mode 100644 index 000000000000..eefdaefa4b00 --- /dev/null +++ b/patches/server/0936-Allow-proper-checking-of-empty-item-stacks.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aeltumn +Date: Mon, 28 Aug 2023 13:44:09 +0200 +Subject: [PATCH] Allow proper checking of empty item stacks + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index a2e605bc1418dc0b5570566a6e348df03c9aee4c..e1f9a603e7adf3468faa9bb6d93dd3339327b47e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -33,12 +33,19 @@ public final class CraftItemStack extends ItemStack { + } + // Paper end - MC Utils + ++ // Paper start - override isEmpty to use vanilla's impl ++ @Override ++ public boolean isEmpty() { ++ return handle == null || handle.isEmpty(); ++ } ++ // Paper end - override isEmpty to use vanilla's impl ++ + public static net.minecraft.world.item.ItemStack asNMSCopy(ItemStack original) { + if (original instanceof CraftItemStack) { + CraftItemStack stack = (CraftItemStack) original; + return stack.handle == null ? net.minecraft.world.item.ItemStack.EMPTY : stack.handle.copy(); + } +- if (original == null || original.getType() == Material.AIR) { ++ if (original == null || original.isEmpty()) { // Paper - override isEmpty to use vanilla's impl; use isEmpty + return net.minecraft.world.item.ItemStack.EMPTY; + } + diff --git a/patches/server/0937-Fix-silent-equipment-change-for-mobs.patch b/patches/server/0937-Fix-silent-equipment-change-for-mobs.patch new file mode 100644 index 000000000000..515b91f30cc7 --- /dev/null +++ b/patches/server/0937-Fix-silent-equipment-change-for-mobs.patch @@ -0,0 +1,105 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> +Date: Thu, 31 Aug 2023 17:32:48 +0200 +Subject: [PATCH] Fix silent equipment change for mobs + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 8b239769a3a7ce6f85d472ddb2ff7ea7de0ce5c0..1721504912c9e5744f09c17d059315ee357afeb4 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1046,13 +1046,20 @@ public abstract class Mob extends LivingEntity implements Targeting { + + @Override + public void setItemSlot(EquipmentSlot slot, ItemStack stack) { ++ // Paper start - Fix silent equipment change ++ setItemSlot(slot, stack, false); ++ } ++ ++ @Override ++ public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) { ++ // Paper end - Fix silent equipment change + this.verifyEquippedItem(stack); + switch (slot.getType()) { + case HAND: +- this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack); ++ this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change + break; + case ARMOR: +- this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack); ++ this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +index 40664cc7e3665432a2ab5e552802c3fc3edbdb22..586e3e92ccc275446df6dbbff9bf010a37a9aa8f 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +@@ -250,8 +250,8 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + // Paper end - shouldBurnInDay API + + @Override +- public void setItemSlot(EquipmentSlot slot, ItemStack stack) { +- super.setItemSlot(slot, stack); ++ public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) { // Paper - Fix silent equipment change ++ super.setItemSlot(slot, stack, silent); // Paper - Fix silent equipment change + if (!this.level().isClientSide) { + this.reassessWeaponGoal(); + } +diff --git a/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java b/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..81947843d1f2f7dc6f59d7b52f327d60b17d0dcc +--- /dev/null ++++ b/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java +@@ -0,0 +1,51 @@ ++package io.papermc.paper.entity; ++ ++import io.github.classgraph.ClassGraph; ++import io.github.classgraph.ClassInfo; ++import io.github.classgraph.MethodInfo; ++import io.github.classgraph.MethodInfoList; ++import io.github.classgraph.MethodParameterInfo; ++import io.github.classgraph.ScanResult; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.stream.Stream; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++import static org.junit.jupiter.api.Assertions.fail; ++ ++public class EntitySetItemSlotSilentOverrideTest extends AbstractTestingBase { ++ ++ public static Stream parameters() { ++ final List classInfo = new ArrayList<>(); ++ try (ScanResult scanResult = new ClassGraph() ++ .enableClassInfo() ++ .enableMethodInfo() ++ .whitelistPackages("net.minecraft") ++ .scan() ++ ) { ++ for (final ClassInfo subclass : scanResult.getSubclasses("net.minecraft.world.entity.LivingEntity")) { ++ final MethodInfoList setItemSlot = subclass.getDeclaredMethodInfo("setItemSlot"); ++ if (!setItemSlot.isEmpty()) { ++ classInfo.add(subclass); ++ } ++ } ++ } ++ return classInfo.stream(); ++ } ++ ++ @ParameterizedTest ++ @MethodSource("parameters") ++ public void checkSetItemSlotSilentOverrides(ClassInfo overridesSetItemSlot) { ++ final MethodInfoList setItemSlot = overridesSetItemSlot.getDeclaredMethodInfo("setItemSlot"); ++ for (final MethodInfo methodInfo : setItemSlot) { ++ for (final MethodParameterInfo methodParameterInfo : methodInfo.getParameterInfo()) { ++ if ("boolean".equals(methodParameterInfo.getTypeDescriptor().toStringWithSimpleNames())) { ++ return; ++ } ++ } ++ } ++ fail(overridesSetItemSlot.getName() + " needs to override setItemSlot with the boolean silent parameter as well"); ++ } ++} diff --git a/patches/server/0938-Do-crystal-portal-proximity-check-before-entity-look.patch b/patches/server/0938-Do-crystal-portal-proximity-check-before-entity-look.patch deleted file mode 100644 index 5d4a0ea4ebf3..000000000000 --- a/patches/server/0938-Do-crystal-portal-proximity-check-before-entity-look.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Martijn Muijsers -Date: Tue, 15 Aug 2023 21:04:55 +0200 -Subject: [PATCH] Do crystal-portal proximity check before entity lookup - -This adds a very cheap distance check when an end crystal is placed. - -Attempting to respawn the dragon, which involves looking up the end crystal -entities near the portal, every time an end crystal is placed, can be slow on -some servers that have players placing end crystals as a style of combat. - -The very cheap distance check prevents running the entity lookup every time. - -diff --git a/src/main/java/net/minecraft/world/item/EndCrystalItem.java b/src/main/java/net/minecraft/world/item/EndCrystalItem.java -index ca1edc083847b47bb450b291723aca778a5912dc..e1696f6b77df4c8fceaece64701d4db78b0a4c42 100644 ---- a/src/main/java/net/minecraft/world/item/EndCrystalItem.java -+++ b/src/main/java/net/minecraft/world/item/EndCrystalItem.java -@@ -29,7 +29,7 @@ public class EndCrystalItem extends Item { - if (!iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) { - return InteractionResult.FAIL; - } else { -- BlockPos blockposition1 = blockposition.above(); -+ BlockPos blockposition1 = blockposition.above(); final BlockPos aboveBlockPosition = blockposition1; // Paper - OBFHELPER - - if (!world.isEmptyBlock(blockposition1)) { - return InteractionResult.FAIL; -@@ -56,7 +56,7 @@ public class EndCrystalItem extends Item { - EndDragonFight enderdragonbattle = ((ServerLevel) world).getDragonFight(); - - if (enderdragonbattle != null) { -- enderdragonbattle.tryRespawn(); -+ enderdragonbattle.tryRespawn(aboveBlockPosition); // Paper - Perf: Do crystal-portal proximity check before entity lookup - } - } - -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 2d9a7a91eb14c4f4f82d6ee491fbc628d91fb07d..4d2ec3b14ed3b6c527745111950bbdf111129c41 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 -@@ -558,6 +558,12 @@ public class EndDragonFight { - } - - public boolean tryRespawn() { // CraftBukkit - return boolean -+ // Paper start - Perf: Do crystal-portal proximity check before entity lookup -+ return this.tryRespawn(null); -+ } -+ -+ public boolean tryRespawn(@Nullable BlockPos placedEndCrystalPos) { // placedEndCrystalPos is null if the tryRespawn() call was not caused by a placed end crystal -+ // Paper end - Perf: Do crystal-portal proximity check before entity lookup - if (this.dragonKilled && this.respawnStage == null) { - BlockPos blockposition = this.portalLocation; - -@@ -575,6 +581,22 @@ public class EndDragonFight { - blockposition = this.portalLocation; - } - -+ // Paper start - Perf: Do crystal-portal proximity check before entity lookup -+ if (placedEndCrystalPos != null) { -+ // The end crystal must be 0 or 1 higher than the portal origin -+ int dy = placedEndCrystalPos.getY() - blockposition.getY(); -+ if (dy != 0 && dy != 1) { -+ return false; -+ } -+ // The end crystal must be within a distance of 1 in one planar direction, and 3 in the other -+ int dx = placedEndCrystalPos.getX() - blockposition.getX(); -+ int dz = placedEndCrystalPos.getZ() - blockposition.getZ(); -+ if (!((dx >= -1 && dx <= 1 && dz >= -3 && dz <= 3) || (dx >= -3 && dx <= 3 && dz >= -1 && dz <= 1))) { -+ return false; -+ } -+ } -+ // Paper end - Perf: Do crystal-portal proximity check before entity lookup -+ - List list = Lists.newArrayList(); - BlockPos blockposition1 = blockposition.above(1); - Iterator iterator = Direction.Plane.HORIZONTAL.iterator(); diff --git a/patches/server/0944-Fix-spigot-s-Forced-Stats.patch b/patches/server/0938-Fix-spigot-s-Forced-Stats.patch similarity index 100% rename from patches/server/0944-Fix-spigot-s-Forced-Stats.patch rename to patches/server/0938-Fix-spigot-s-Forced-Stats.patch diff --git a/patches/server/0939-Add-missing-InventoryHolders-to-inventories.patch b/patches/server/0939-Add-missing-InventoryHolders-to-inventories.patch new file mode 100644 index 000000000000..e6954fc31cde --- /dev/null +++ b/patches/server/0939-Add-missing-InventoryHolders-to-inventories.patch @@ -0,0 +1,302 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 24 Jan 2022 00:09:02 -0800 +Subject: [PATCH] Add missing InventoryHolders to inventories + + +diff --git a/src/main/java/net/minecraft/world/Container.java b/src/main/java/net/minecraft/world/Container.java +index da5ff65fade5cdf14fad3705c08b48896bc4c36d..d6cbe98e67fdbf8db46338a88ab1356dd63b50a3 100644 +--- a/src/main/java/net/minecraft/world/Container.java ++++ b/src/main/java/net/minecraft/world/Container.java +@@ -100,7 +100,7 @@ public interface Container extends Clearable { + + java.util.List getViewers(); + +- org.bukkit.inventory.InventoryHolder getOwner(); ++ org.bukkit.inventory.@org.jetbrains.annotations.Nullable InventoryHolder getOwner(); // Paper - annotation + + void setMaxStackSize(int size); + +diff --git a/src/main/java/net/minecraft/world/SimpleContainer.java b/src/main/java/net/minecraft/world/SimpleContainer.java +index ff1aba1e69cfde633fd01724f1a8d0af7f59437f..9546d93f90ca34b4d35bd98df847bf896c654043 100644 +--- a/src/main/java/net/minecraft/world/SimpleContainer.java ++++ b/src/main/java/net/minecraft/world/SimpleContainer.java +@@ -30,7 +30,7 @@ public class SimpleContainer implements Container, StackedContentsCompatible { + // CraftBukkit start - add fields and methods + public List transaction = new java.util.ArrayList(); + private int maxStack = MAX_STACK; +- protected org.bukkit.inventory.InventoryHolder bukkitOwner; ++ protected @Nullable org.bukkit.inventory.InventoryHolder bukkitOwner; // Paper - annotation + + public List getContents() { + return this.items; +@@ -58,6 +58,11 @@ public class SimpleContainer implements Container, StackedContentsCompatible { + } + + public org.bukkit.inventory.InventoryHolder getOwner() { ++ // Paper start - Add missing InventoryHolders ++ if (this.bukkitOwner == null && this.bukkitOwnerCreator != null) { ++ this.bukkitOwner = this.bukkitOwnerCreator.get(); ++ } ++ // Paper end - Add missing InventoryHolders + return this.bukkitOwner; + } + +@@ -86,6 +91,13 @@ public class SimpleContainer implements Container, StackedContentsCompatible { + public SimpleContainer(int size) { + this(size, null); + } ++ // Paper start - Add missing InventoryHolders ++ private @Nullable java.util.function.Supplier bukkitOwnerCreator; ++ public SimpleContainer(java.util.function.Supplier bukkitOwnerCreator, int size) { ++ this(size); ++ this.bukkitOwnerCreator = bukkitOwnerCreator; ++ } ++ // Paper end - Add missing InventoryHolders + + public SimpleContainer(int i, org.bukkit.inventory.InventoryHolder owner) { + this.bukkitOwner = owner; +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index 75f836f07c66dbf71017ef0b7697851353d0f2e1..eef0d3c59f0ce6e89033a5e228d31b63339c2773 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -993,4 +993,15 @@ public abstract class AbstractContainerMenu { + this.stateId = this.stateId + 1 & 32767; + return this.stateId; + } ++ ++ // Paper start - Add missing InventoryHolders ++ // The reason this is a supplier, is that the createHolder method uses the bukkit InventoryView#getTopInventory to get the inventory in question ++ // and that can't be obtained safely until the AbstractContainerMenu has been fully constructed. Using a supplier lazily ++ // initializes the InventoryHolder safely. ++ protected final Supplier createBlockHolder(final ContainerLevelAccess context) { ++ //noinspection ConstantValue ++ Preconditions.checkArgument(context != null, "context was null"); ++ return () -> context.createBlockHolder(this); ++ } ++ // Paper end - Add missing InventoryHolders + } +diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +index 2813a87a01d0704a3de210cd005073f953d538f8..88842f31836df70717fdf7f77f39a2ad8bb45326 100644 +--- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +@@ -41,7 +41,7 @@ public class BeaconMenu extends AbstractContainerMenu { + public BeaconMenu(int syncId, Container inventory, ContainerData propertyDelegate, ContainerLevelAccess context) { + super(MenuType.BEACON, syncId); + this.player = (Inventory) inventory; // CraftBukkit - TODO: check this +- this.beacon = new SimpleContainer(1) { ++ this.beacon = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders + @Override + public boolean canPlaceItem(int slot, ItemStack stack) { + return stack.is(ItemTags.BEACON_PAYMENT_ITEMS); +diff --git a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +index ca3c8b31967a6efd7b0caacb091ab2151e7c0bee..45bf1c95d86bdfc709c5f1a1fbefb18e1cc51f4c 100644 +--- a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +@@ -52,7 +52,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { + + public CartographyTableMenu(int syncId, Inventory inventory, final ContainerLevelAccess context) { + super(MenuType.CARTOGRAPHY_TABLE, syncId); +- this.container = new SimpleContainer(2) { ++ this.container = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + CartographyTableMenu.this.slotsChanged(this); +@@ -66,7 +66,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { + } + // CraftBukkit end + }; +- this.resultContainer = new ResultContainer() { ++ this.resultContainer = new ResultContainer(this.createBlockHolder(context)) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + CartographyTableMenu.this.slotsChanged(this); +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java +index 85e336637db8643fc5aca1dba724c9b341cbf46f..12b466ccb7c36021cf807c4f3fd2bcb037943abc 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java +@@ -21,6 +21,18 @@ public interface ContainerLevelAccess { + return new org.bukkit.Location(this.getWorld().getWorld(), this.getPosition().getX(), this.getPosition().getY(), this.getPosition().getZ()); + } + // CraftBukkit end ++ // Paper start - Add missing InventoryHolders ++ default boolean isBlock() { ++ return false; ++ } ++ ++ default org.bukkit.inventory.@org.jetbrains.annotations.Nullable BlockInventoryHolder createBlockHolder(AbstractContainerMenu menu) { ++ if (!this.isBlock()) { ++ return null; ++ } ++ return new org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder(this, menu.getBukkitView().getTopInventory()); ++ } ++ // Paper end - Add missing InventoryHolders + + ContainerLevelAccess NULL = new ContainerLevelAccess() { + @Override +@@ -48,6 +60,12 @@ public interface ContainerLevelAccess { + return pos; + } + // CraftBukkit end ++ // Paper start - Add missing InventoryHolders ++ @Override ++ public boolean isBlock() { ++ return true; ++ } ++ // Paper end - Add missing InventoryHolders + + @Override + public Optional evaluate(BiFunction getter) { +diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java +index c2d6265933dc4ceed80e2bd517970d02164a63df..343f44db579839eb61376f876b5eff2e615dc2e5 100644 +--- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java +@@ -61,7 +61,7 @@ public class EnchantmentMenu extends AbstractContainerMenu { + + public EnchantmentMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context) { + super(MenuType.ENCHANTMENT, syncId); +- this.enchantSlots = new SimpleContainer(2) { ++ this.enchantSlots = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); +diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +index 95ac3446fba1f37637c9700080de2e1ce7a3550a..23462de504932bd351b8dfacde514fe361343912 100644 +--- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +@@ -59,8 +59,8 @@ public class GrindstoneMenu extends AbstractContainerMenu { + + public GrindstoneMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) { + super(MenuType.GRINDSTONE, syncId); +- this.resultSlots = new ResultContainer(); +- this.repairSlots = new SimpleContainer(2) { ++ this.resultSlots = new ResultContainer(this.createBlockHolder(context)); // Paper - Add missing InventoryHolders ++ this.repairSlots = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); +diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +index eb36a69b8da492aec9609cc9ef80d7d68ff9af03..62e1b7096fa659778b737b3d520389e73138dc5d 100644 +--- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +@@ -18,7 +18,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { + protected final Player player; + protected final Container inputSlots; + private final List inputSlotIndexes; +- protected final ResultContainer resultSlots = new ResultContainer(); ++ protected final ResultContainer resultSlots; // Paper - Add missing InventoryHolders; delay field init + private final int resultSlotIndex; + + protected abstract boolean mayPickup(Player player, boolean present); +@@ -30,6 +30,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { + public ItemCombinerMenu(@Nullable MenuType type, int syncId, Inventory playerInventory, ContainerLevelAccess context) { + super(type, syncId); + this.access = context; ++ this.resultSlots = new ResultContainer(this.createBlockHolder(this.access)); // Paper - Add missing InventoryHolders; delay field init + this.player = playerInventory.player; + ItemCombinerMenuSlotDefinition itemcombinermenuslotdefinition = this.createInputSlotDefinitions(); + +@@ -96,7 +97,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { + protected abstract ItemCombinerMenuSlotDefinition createInputSlotDefinitions(); + + private SimpleContainer createContainer(int size) { +- return new SimpleContainer(size) { ++ return new SimpleContainer(this.createBlockHolder(this.access), size) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); +diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +index e8c755dc71d9818f025eb25224122b19c5f9e15b..c1c9cfd3f77b2dbbc39741d629c7dfb24a48d4f6 100644 +--- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +@@ -73,7 +73,7 @@ public class LoomMenu extends AbstractContainerMenu { + this.selectablePatterns = List.of(); + this.slotUpdateListener = () -> { + }; +- this.inputContainer = new SimpleContainer(3) { ++ this.inputContainer = new SimpleContainer(this.createBlockHolder(context), 3) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); +@@ -88,7 +88,7 @@ public class LoomMenu extends AbstractContainerMenu { + } + // CraftBukkit end + }; +- this.outputContainer = new SimpleContainer(1) { ++ this.outputContainer = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); +diff --git a/src/main/java/net/minecraft/world/inventory/ResultContainer.java b/src/main/java/net/minecraft/world/inventory/ResultContainer.java +index d4592218d761eb38402e3d95c642e80a708cb333..4c4266a85c38e41e6c7e6144a68624f4daa50c54 100644 +--- a/src/main/java/net/minecraft/world/inventory/ResultContainer.java ++++ b/src/main/java/net/minecraft/world/inventory/ResultContainer.java +@@ -29,7 +29,12 @@ public class ResultContainer implements Container, RecipeCraftingHolder { + } + + public org.bukkit.inventory.InventoryHolder getOwner() { +- return null; // Result slots don't get an owner ++ // Paper start - Add missing InventoryHolders ++ if (this.holder == null && this.holderCreator != null) { ++ this.holder = this.holderCreator.get(); ++ } ++ return this.holder; // Result slots don't get an owner ++ // Paper end - Add missing InventoryHolders + } + + // Don't need a transaction; the InventoryCrafting keeps track of it for us +@@ -53,6 +58,14 @@ public class ResultContainer implements Container, RecipeCraftingHolder { + return null; + } + // CraftBukkit end ++ // Paper start - Add missing InventoryHolders ++ private @Nullable java.util.function.Supplier holderCreator; ++ private @Nullable org.bukkit.inventory.InventoryHolder holder; ++ public ResultContainer(java.util.function.Supplier holderCreator) { ++ this(); ++ this.holderCreator = holderCreator; ++ } ++ // Paper end - Add missing InventoryHolders + + public ResultContainer() { + this.itemStacks = NonNullList.withSize(1, ItemStack.EMPTY); +diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +index 1a5d8debc053b24e5856de916f1d248b36f645ba..eade15820dd9db38b6af2a5c4314acfb14ca03e9 100644 +--- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +@@ -68,7 +68,7 @@ public class StonecutterMenu extends AbstractContainerMenu { + this.input = ItemStack.EMPTY; + this.slotUpdateListener = () -> { + }; +- this.container = new SimpleContainer(1) { ++ this.container = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders + @Override + public void setChanged() { + super.setChanged(); +@@ -83,7 +83,7 @@ public class StonecutterMenu extends AbstractContainerMenu { + } + // CraftBukkit end + }; +- this.resultContainer = new ResultContainer(); ++ this.resultContainer = new ResultContainer(this.createBlockHolder(context)); // Paper - Add missing InventoryHolders + this.access = context; + this.level = playerInventory.player.level(); + this.inputSlot = this.addSlot(new Slot(this.container, 0, 20, 33)); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java +index 7ae484b0fa5bf5494c6ead15f7f1c0fa840ae270..7129eb5f5cea39992b4c690cb421004004a952ea 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java +@@ -17,6 +17,13 @@ public class CraftBlockInventoryHolder implements BlockInventoryHolder { + this.block = CraftBlock.at(world, pos); + this.inventory = new CraftInventory(inv); + } ++ // Paper start - Add missing InventoryHolders ++ public CraftBlockInventoryHolder(net.minecraft.world.inventory.ContainerLevelAccess levelAccess, Inventory inventory) { ++ com.google.common.base.Preconditions.checkArgument(levelAccess.isBlock()); ++ this.block = CraftBlock.at(levelAccess.getWorld(), levelAccess.getPosition()); ++ this.inventory = inventory; ++ } ++ // Paper end - Add missing InventoryHolders + + @Override + public Block getBlock() { diff --git a/patches/server/0940-Add-slot-sanity-checks-in-container-clicks.patch b/patches/server/0940-Add-slot-sanity-checks-in-container-clicks.patch deleted file mode 100644 index 3e7b7c8b9b04..000000000000 --- a/patches/server/0940-Add-slot-sanity-checks-in-container-clicks.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Mon, 11 Sep 2023 12:01:57 +1000 -Subject: [PATCH] Add slot sanity checks in container clicks - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 27c2eed2aed6e0a2868501e553a5fbfc0307bb0c..9e8b37f446a382204bc9ad61efed913f70a99b90 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2888,6 +2888,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - break; - case SWAP: - if ((packet.getButtonNum() >= 0 && packet.getButtonNum() < 9) || packet.getButtonNum() == 40) { -+ // Paper start - Add slot sanity checks to container clicks -+ if (packet.getSlotNum() < 0) { -+ action = InventoryAction.NOTHING; -+ break; -+ } -+ // Paper end - Add slot sanity checks to container clicks - click = (packet.getButtonNum() == 40) ? ClickType.SWAP_OFFHAND : ClickType.NUMBER_KEY; - Slot clickedSlot = this.player.containerMenu.getSlot(packet.getSlotNum()); - if (clickedSlot.mayPickup(this.player)) { -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index 24caa1cf91cd50a5972238119aca1f85ec2b3d2b..75f836f07c66dbf71017ef0b7697851353d0f2e1 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -423,6 +423,7 @@ public abstract class AbstractContainerMenu { - this.resetQuickCraft(); - } - } else if (this.quickcraftStatus == 1) { -+ if (slotIndex < 0) return; // Paper - Add slot sanity checks to container clicks - slot = (Slot) this.slots.get(slotIndex); - itemstack = this.getCarried(); - if (AbstractContainerMenu.canItemQuickReplace(slot, itemstack, true) && slot.mayPlace(itemstack) && (this.quickcraftType == 2 || itemstack.getCount() > this.quickcraftSlots.size()) && this.canDragTo(slot)) { -@@ -597,6 +598,7 @@ public abstract class AbstractContainerMenu { - int j2; - - if (actionType == ClickType.SWAP && (button >= 0 && button < 9 || button == 40)) { -+ if (slotIndex < 0) return; // Paper - Add slot sanity checks to container clicks - ItemStack itemstack4 = playerinventory.getItem(button); - - slot = (Slot) this.slots.get(slotIndex); diff --git a/patches/server/0940-Do-not-read-tile-entities-in-chunks-that-are-positio.patch b/patches/server/0940-Do-not-read-tile-entities-in-chunks-that-are-positio.patch new file mode 100644 index 000000000000..a5090d6c0077 --- /dev/null +++ b/patches/server/0940-Do-not-read-tile-entities-in-chunks-that-are-positio.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 18 Jun 2023 23:04:46 -0700 +Subject: [PATCH] Do not read tile entities in chunks that are positioned + outside of the chunk + +The tile entities are not accessible and so should not be loaded. +This can happen as a result of users moving regionfiles around, +which would cause a crash on Folia but would appear to function +fine on Paper. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 3b046dc106b96b7ca2b148d605e8b7c97453d033..85de64c1e75e1323f8425fc53e525c215ff417ce 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -307,6 +307,13 @@ public class ChunkSerializer { + for (int k1 = 0; k1 < nbttaglist3.size(); ++k1) { + CompoundTag nbttagcompound4 = nbttaglist3.getCompound(k1); + ++ // Paper start - do not read tile entities positioned outside the chunk ++ BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound4); ++ if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) { ++ LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk"); ++ continue; ++ } ++ // Paper end - do not read tile entities positioned outside the chunk + ((ChunkAccess) object1).setBlockEntityNbt(nbttagcompound4); + } + +@@ -517,10 +524,19 @@ public class ChunkSerializer { + CompoundTag nbttagcompound1 = nbttaglist1.getCompound(i); + boolean flag = nbttagcompound1.getBoolean("keepPacked"); + ++ // Paper start - do not read tile entities positioned outside the chunk ++ BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound1); // moved up ++ ChunkPos chunkPos = chunk.getPos(); ++ if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) { ++ LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk"); ++ continue; ++ } ++ // Paper end - do not read tile entities positioned outside the chunk ++ + if (flag) { + chunk.setBlockEntityNbt(nbttagcompound1); + } else { +- BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound1); ++ // Paper - do not read tile entities positioned outside the chunk; move up + BlockEntity tileentity = BlockEntity.loadStatic(blockposition, chunk.getBlockState(blockposition), nbttagcompound1); + + if (tileentity != null) { diff --git a/patches/server/0947-Add-missing-logs-for-log-ips-config-option.patch b/patches/server/0941-Add-missing-logs-for-log-ips-config-option.patch similarity index 100% rename from patches/server/0947-Add-missing-logs-for-log-ips-config-option.patch rename to patches/server/0941-Add-missing-logs-for-log-ips-config-option.patch diff --git a/patches/server/0942-Allow-proper-checking-of-empty-item-stacks.patch b/patches/server/0942-Allow-proper-checking-of-empty-item-stacks.patch deleted file mode 100644 index 11c616703a85..000000000000 --- a/patches/server/0942-Allow-proper-checking-of-empty-item-stacks.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aeltumn -Date: Mon, 28 Aug 2023 13:44:09 +0200 -Subject: [PATCH] Allow proper checking of empty item stacks - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index d1604c52784c1bf94e797ca1f5d8146c19314c11..414a67096478ca57be54bd2ce565e7d50c8dd100 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -33,12 +33,19 @@ public final class CraftItemStack extends ItemStack { - } - // Paper end - MC Utils - -+ // Paper start - override isEmpty to use vanilla's impl -+ @Override -+ public boolean isEmpty() { -+ return handle == null || handle.isEmpty(); -+ } -+ // Paper end - override isEmpty to use vanilla's impl -+ - public static net.minecraft.world.item.ItemStack asNMSCopy(ItemStack original) { - if (original instanceof CraftItemStack) { - CraftItemStack stack = (CraftItemStack) original; - return stack.handle == null ? net.minecraft.world.item.ItemStack.EMPTY : stack.handle.copy(); - } -- if (original == null || original.getType() == Material.AIR) { -+ if (original == null || original.isEmpty()) { // Paper - override isEmpty to use vanilla's impl; use isEmpty - return net.minecraft.world.item.ItemStack.EMPTY; - } - diff --git a/patches/server/0942-Remove-Spigot-Bug-Fix-for-MC-109346.patch b/patches/server/0942-Remove-Spigot-Bug-Fix-for-MC-109346.patch new file mode 100644 index 000000000000..8cd300599d84 --- /dev/null +++ b/patches/server/0942-Remove-Spigot-Bug-Fix-for-MC-109346.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sat, 23 Sep 2023 01:51:22 -0400 +Subject: [PATCH] Remove Spigot Bug Fix for MC-109346 + + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index da45984c9b2d3a55256efddde94580505f692655..c2a4fde17673a2bc3133aa0c68608c3da75d5cc5 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -348,12 +348,6 @@ public class ServerEntity { + ((LivingEntity) this.entity).detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending + } + +- // CraftBukkit start - MC-109346: Fix for nonsensical head yaw +- if (this.entity instanceof ServerPlayer) { +- sender.accept(new ClientboundRotateHeadPacket(this.entity, (byte) Mth.floor(this.entity.getYHeadRot() * 256.0F / 360.0F))); +- } +- // CraftBukkit end +- + if (!this.entity.getPassengers().isEmpty()) { + sender.accept(new ClientboundSetPassengersPacket(this.entity)); + } diff --git a/patches/server/0949-Fix-race-condition-on-UpgradeData.BlockFixers-class-.patch b/patches/server/0943-Fix-race-condition-on-UpgradeData.BlockFixers-class-.patch similarity index 100% rename from patches/server/0949-Fix-race-condition-on-UpgradeData.BlockFixers-class-.patch rename to patches/server/0943-Fix-race-condition-on-UpgradeData.BlockFixers-class-.patch diff --git a/patches/server/0943-Fix-silent-equipment-change-for-mobs.patch b/patches/server/0943-Fix-silent-equipment-change-for-mobs.patch deleted file mode 100644 index f0c25febe768..000000000000 --- a/patches/server/0943-Fix-silent-equipment-change-for-mobs.patch +++ /dev/null @@ -1,105 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> -Date: Thu, 31 Aug 2023 17:32:48 +0200 -Subject: [PATCH] Fix silent equipment change for mobs - - -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 8cc40521146d02bbfafbb113dda8183840814c96..433d8eccdd225651af8c88babfdb94d19ce546d8 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1046,13 +1046,20 @@ public abstract class Mob extends LivingEntity implements Targeting { - - @Override - public void setItemSlot(EquipmentSlot slot, ItemStack stack) { -+ // Paper start - Fix silent equipment change -+ setItemSlot(slot, stack, false); -+ } -+ -+ @Override -+ public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) { -+ // Paper end - Fix silent equipment change - this.verifyEquippedItem(stack); - switch (slot.getType()) { - case HAND: -- this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack); -+ this.onEquipItem(slot, (ItemStack) this.handItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change - break; - case ARMOR: -- this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack); -+ this.onEquipItem(slot, (ItemStack) this.armorItems.set(slot.getIndex(), stack), stack, silent); // Paper - Fix silent equipment change - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -index 40664cc7e3665432a2ab5e552802c3fc3edbdb22..586e3e92ccc275446df6dbbff9bf010a37a9aa8f 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -@@ -250,8 +250,8 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - // Paper end - shouldBurnInDay API - - @Override -- public void setItemSlot(EquipmentSlot slot, ItemStack stack) { -- super.setItemSlot(slot, stack); -+ public void setItemSlot(EquipmentSlot slot, ItemStack stack, boolean silent) { // Paper - Fix silent equipment change -+ super.setItemSlot(slot, stack, silent); // Paper - Fix silent equipment change - if (!this.level().isClientSide) { - this.reassessWeaponGoal(); - } -diff --git a/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java b/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..81947843d1f2f7dc6f59d7b52f327d60b17d0dcc ---- /dev/null -+++ b/src/test/java/io/papermc/paper/entity/EntitySetItemSlotSilentOverrideTest.java -@@ -0,0 +1,51 @@ -+package io.papermc.paper.entity; -+ -+import io.github.classgraph.ClassGraph; -+import io.github.classgraph.ClassInfo; -+import io.github.classgraph.MethodInfo; -+import io.github.classgraph.MethodInfoList; -+import io.github.classgraph.MethodParameterInfo; -+import io.github.classgraph.ScanResult; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.stream.Stream; -+import org.bukkit.support.AbstractTestingBase; -+import org.junit.jupiter.params.ParameterizedTest; -+import org.junit.jupiter.params.provider.MethodSource; -+ -+import static org.junit.jupiter.api.Assertions.fail; -+ -+public class EntitySetItemSlotSilentOverrideTest extends AbstractTestingBase { -+ -+ public static Stream parameters() { -+ final List classInfo = new ArrayList<>(); -+ try (ScanResult scanResult = new ClassGraph() -+ .enableClassInfo() -+ .enableMethodInfo() -+ .whitelistPackages("net.minecraft") -+ .scan() -+ ) { -+ for (final ClassInfo subclass : scanResult.getSubclasses("net.minecraft.world.entity.LivingEntity")) { -+ final MethodInfoList setItemSlot = subclass.getDeclaredMethodInfo("setItemSlot"); -+ if (!setItemSlot.isEmpty()) { -+ classInfo.add(subclass); -+ } -+ } -+ } -+ return classInfo.stream(); -+ } -+ -+ @ParameterizedTest -+ @MethodSource("parameters") -+ public void checkSetItemSlotSilentOverrides(ClassInfo overridesSetItemSlot) { -+ final MethodInfoList setItemSlot = overridesSetItemSlot.getDeclaredMethodInfo("setItemSlot"); -+ for (final MethodInfo methodInfo : setItemSlot) { -+ for (final MethodParameterInfo methodParameterInfo : methodInfo.getParameterInfo()) { -+ if ("boolean".equals(methodParameterInfo.getTypeDescriptor().toStringWithSimpleNames())) { -+ return; -+ } -+ } -+ } -+ fail(overridesSetItemSlot.getName() + " needs to override setItemSlot with the boolean silent parameter as well"); -+ } -+} diff --git a/patches/server/0950-Fix-NPE-in-AdvancementProgress-getDateAwarded.patch b/patches/server/0944-Fix-NPE-in-AdvancementProgress-getDateAwarded.patch similarity index 100% rename from patches/server/0950-Fix-NPE-in-AdvancementProgress-getDateAwarded.patch rename to patches/server/0944-Fix-NPE-in-AdvancementProgress-getDateAwarded.patch diff --git a/patches/server/0945-Add-missing-InventoryHolders-to-inventories.patch b/patches/server/0945-Add-missing-InventoryHolders-to-inventories.patch deleted file mode 100644 index c189f4b7d297..000000000000 --- a/patches/server/0945-Add-missing-InventoryHolders-to-inventories.patch +++ /dev/null @@ -1,302 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 24 Jan 2022 00:09:02 -0800 -Subject: [PATCH] Add missing InventoryHolders to inventories - - -diff --git a/src/main/java/net/minecraft/world/Container.java b/src/main/java/net/minecraft/world/Container.java -index da5ff65fade5cdf14fad3705c08b48896bc4c36d..d6cbe98e67fdbf8db46338a88ab1356dd63b50a3 100644 ---- a/src/main/java/net/minecraft/world/Container.java -+++ b/src/main/java/net/minecraft/world/Container.java -@@ -100,7 +100,7 @@ public interface Container extends Clearable { - - java.util.List getViewers(); - -- org.bukkit.inventory.InventoryHolder getOwner(); -+ org.bukkit.inventory.@org.jetbrains.annotations.Nullable InventoryHolder getOwner(); // Paper - annotation - - void setMaxStackSize(int size); - -diff --git a/src/main/java/net/minecraft/world/SimpleContainer.java b/src/main/java/net/minecraft/world/SimpleContainer.java -index ff1aba1e69cfde633fd01724f1a8d0af7f59437f..9546d93f90ca34b4d35bd98df847bf896c654043 100644 ---- a/src/main/java/net/minecraft/world/SimpleContainer.java -+++ b/src/main/java/net/minecraft/world/SimpleContainer.java -@@ -30,7 +30,7 @@ public class SimpleContainer implements Container, StackedContentsCompatible { - // CraftBukkit start - add fields and methods - public List transaction = new java.util.ArrayList(); - private int maxStack = MAX_STACK; -- protected org.bukkit.inventory.InventoryHolder bukkitOwner; -+ protected @Nullable org.bukkit.inventory.InventoryHolder bukkitOwner; // Paper - annotation - - public List getContents() { - return this.items; -@@ -58,6 +58,11 @@ public class SimpleContainer implements Container, StackedContentsCompatible { - } - - public org.bukkit.inventory.InventoryHolder getOwner() { -+ // Paper start - Add missing InventoryHolders -+ if (this.bukkitOwner == null && this.bukkitOwnerCreator != null) { -+ this.bukkitOwner = this.bukkitOwnerCreator.get(); -+ } -+ // Paper end - Add missing InventoryHolders - return this.bukkitOwner; - } - -@@ -86,6 +91,13 @@ public class SimpleContainer implements Container, StackedContentsCompatible { - public SimpleContainer(int size) { - this(size, null); - } -+ // Paper start - Add missing InventoryHolders -+ private @Nullable java.util.function.Supplier bukkitOwnerCreator; -+ public SimpleContainer(java.util.function.Supplier bukkitOwnerCreator, int size) { -+ this(size); -+ this.bukkitOwnerCreator = bukkitOwnerCreator; -+ } -+ // Paper end - Add missing InventoryHolders - - public SimpleContainer(int i, org.bukkit.inventory.InventoryHolder owner) { - this.bukkitOwner = owner; -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index 75f836f07c66dbf71017ef0b7697851353d0f2e1..eef0d3c59f0ce6e89033a5e228d31b63339c2773 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -993,4 +993,15 @@ public abstract class AbstractContainerMenu { - this.stateId = this.stateId + 1 & 32767; - return this.stateId; - } -+ -+ // Paper start - Add missing InventoryHolders -+ // The reason this is a supplier, is that the createHolder method uses the bukkit InventoryView#getTopInventory to get the inventory in question -+ // and that can't be obtained safely until the AbstractContainerMenu has been fully constructed. Using a supplier lazily -+ // initializes the InventoryHolder safely. -+ protected final Supplier createBlockHolder(final ContainerLevelAccess context) { -+ //noinspection ConstantValue -+ Preconditions.checkArgument(context != null, "context was null"); -+ return () -> context.createBlockHolder(this); -+ } -+ // Paper end - Add missing InventoryHolders - } -diff --git a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -index 86199457586dc4d4f0d8ccaac812e8340aaac957..ae2dc7fc4df54f9ed8e78ff1347f9782eccc9d4b 100644 ---- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -@@ -41,7 +41,7 @@ public class BeaconMenu extends AbstractContainerMenu { - public BeaconMenu(int syncId, Container inventory, ContainerData propertyDelegate, ContainerLevelAccess context) { - super(MenuType.BEACON, syncId); - this.player = (Inventory) inventory; // CraftBukkit - TODO: check this -- this.beacon = new SimpleContainer(1) { -+ this.beacon = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders - @Override - public boolean canPlaceItem(int slot, ItemStack stack) { - return stack.is(ItemTags.BEACON_PAYMENT_ITEMS); -diff --git a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -index ca3c8b31967a6efd7b0caacb091ab2151e7c0bee..45bf1c95d86bdfc709c5f1a1fbefb18e1cc51f4c 100644 ---- a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -@@ -52,7 +52,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { - - public CartographyTableMenu(int syncId, Inventory inventory, final ContainerLevelAccess context) { - super(MenuType.CARTOGRAPHY_TABLE, syncId); -- this.container = new SimpleContainer(2) { -+ this.container = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - CartographyTableMenu.this.slotsChanged(this); -@@ -66,7 +66,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { - } - // CraftBukkit end - }; -- this.resultContainer = new ResultContainer() { -+ this.resultContainer = new ResultContainer(this.createBlockHolder(context)) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - CartographyTableMenu.this.slotsChanged(this); -diff --git a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java -index 85e336637db8643fc5aca1dba724c9b341cbf46f..12b466ccb7c36021cf807c4f3fd2bcb037943abc 100644 ---- a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java -+++ b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java -@@ -21,6 +21,18 @@ public interface ContainerLevelAccess { - return new org.bukkit.Location(this.getWorld().getWorld(), this.getPosition().getX(), this.getPosition().getY(), this.getPosition().getZ()); - } - // CraftBukkit end -+ // Paper start - Add missing InventoryHolders -+ default boolean isBlock() { -+ return false; -+ } -+ -+ default org.bukkit.inventory.@org.jetbrains.annotations.Nullable BlockInventoryHolder createBlockHolder(AbstractContainerMenu menu) { -+ if (!this.isBlock()) { -+ return null; -+ } -+ return new org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder(this, menu.getBukkitView().getTopInventory()); -+ } -+ // Paper end - Add missing InventoryHolders - - ContainerLevelAccess NULL = new ContainerLevelAccess() { - @Override -@@ -48,6 +60,12 @@ public interface ContainerLevelAccess { - return pos; - } - // CraftBukkit end -+ // Paper start - Add missing InventoryHolders -+ @Override -+ public boolean isBlock() { -+ return true; -+ } -+ // Paper end - Add missing InventoryHolders - - @Override - public Optional evaluate(BiFunction getter) { -diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -index c2d6265933dc4ceed80e2bd517970d02164a63df..343f44db579839eb61376f876b5eff2e615dc2e5 100644 ---- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -@@ -61,7 +61,7 @@ public class EnchantmentMenu extends AbstractContainerMenu { - - public EnchantmentMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context) { - super(MenuType.ENCHANTMENT, syncId); -- this.enchantSlots = new SimpleContainer(2) { -+ this.enchantSlots = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - super.setChanged(); -diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -index 95ac3446fba1f37637c9700080de2e1ce7a3550a..23462de504932bd351b8dfacde514fe361343912 100644 ---- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -@@ -59,8 +59,8 @@ public class GrindstoneMenu extends AbstractContainerMenu { - - public GrindstoneMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) { - super(MenuType.GRINDSTONE, syncId); -- this.resultSlots = new ResultContainer(); -- this.repairSlots = new SimpleContainer(2) { -+ this.resultSlots = new ResultContainer(this.createBlockHolder(context)); // Paper - Add missing InventoryHolders -+ this.repairSlots = new SimpleContainer(this.createBlockHolder(context), 2) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - super.setChanged(); -diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -index eb36a69b8da492aec9609cc9ef80d7d68ff9af03..62e1b7096fa659778b737b3d520389e73138dc5d 100644 ---- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -@@ -18,7 +18,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { - protected final Player player; - protected final Container inputSlots; - private final List inputSlotIndexes; -- protected final ResultContainer resultSlots = new ResultContainer(); -+ protected final ResultContainer resultSlots; // Paper - Add missing InventoryHolders; delay field init - private final int resultSlotIndex; - - protected abstract boolean mayPickup(Player player, boolean present); -@@ -30,6 +30,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { - public ItemCombinerMenu(@Nullable MenuType type, int syncId, Inventory playerInventory, ContainerLevelAccess context) { - super(type, syncId); - this.access = context; -+ this.resultSlots = new ResultContainer(this.createBlockHolder(this.access)); // Paper - Add missing InventoryHolders; delay field init - this.player = playerInventory.player; - ItemCombinerMenuSlotDefinition itemcombinermenuslotdefinition = this.createInputSlotDefinitions(); - -@@ -96,7 +97,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { - protected abstract ItemCombinerMenuSlotDefinition createInputSlotDefinitions(); - - private SimpleContainer createContainer(int size) { -- return new SimpleContainer(size) { -+ return new SimpleContainer(this.createBlockHolder(this.access), size) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - super.setChanged(); -diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -index b8440f2cd7ca46c243407ae1ba8f8c7adab4ecd4..e51dabca7b81adee35246e429dcb8f42ae0bf22c 100644 ---- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -@@ -73,7 +73,7 @@ public class LoomMenu extends AbstractContainerMenu { - this.selectablePatterns = List.of(); - this.slotUpdateListener = () -> { - }; -- this.inputContainer = new SimpleContainer(3) { -+ this.inputContainer = new SimpleContainer(this.createBlockHolder(context), 3) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - super.setChanged(); -@@ -88,7 +88,7 @@ public class LoomMenu extends AbstractContainerMenu { - } - // CraftBukkit end - }; -- this.outputContainer = new SimpleContainer(1) { -+ this.outputContainer = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - super.setChanged(); -diff --git a/src/main/java/net/minecraft/world/inventory/ResultContainer.java b/src/main/java/net/minecraft/world/inventory/ResultContainer.java -index d4592218d761eb38402e3d95c642e80a708cb333..4c4266a85c38e41e6c7e6144a68624f4daa50c54 100644 ---- a/src/main/java/net/minecraft/world/inventory/ResultContainer.java -+++ b/src/main/java/net/minecraft/world/inventory/ResultContainer.java -@@ -29,7 +29,12 @@ public class ResultContainer implements Container, RecipeCraftingHolder { - } - - public org.bukkit.inventory.InventoryHolder getOwner() { -- return null; // Result slots don't get an owner -+ // Paper start - Add missing InventoryHolders -+ if (this.holder == null && this.holderCreator != null) { -+ this.holder = this.holderCreator.get(); -+ } -+ return this.holder; // Result slots don't get an owner -+ // Paper end - Add missing InventoryHolders - } - - // Don't need a transaction; the InventoryCrafting keeps track of it for us -@@ -53,6 +58,14 @@ public class ResultContainer implements Container, RecipeCraftingHolder { - return null; - } - // CraftBukkit end -+ // Paper start - Add missing InventoryHolders -+ private @Nullable java.util.function.Supplier holderCreator; -+ private @Nullable org.bukkit.inventory.InventoryHolder holder; -+ public ResultContainer(java.util.function.Supplier holderCreator) { -+ this(); -+ this.holderCreator = holderCreator; -+ } -+ // Paper end - Add missing InventoryHolders - - public ResultContainer() { - this.itemStacks = NonNullList.withSize(1, ItemStack.EMPTY); -diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -index 4924993fab57bcf72a5d75cc2f7e6bed4f9f511c..6ba59a60b85c04127abd7df37a647fa71745327a 100644 ---- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -@@ -68,7 +68,7 @@ public class StonecutterMenu extends AbstractContainerMenu { - this.input = ItemStack.EMPTY; - this.slotUpdateListener = () -> { - }; -- this.container = new SimpleContainer(1) { -+ this.container = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders - @Override - public void setChanged() { - super.setChanged(); -@@ -83,7 +83,7 @@ public class StonecutterMenu extends AbstractContainerMenu { - } - // CraftBukkit end - }; -- this.resultContainer = new ResultContainer(); -+ this.resultContainer = new ResultContainer(this.createBlockHolder(context)); // Paper - Add missing InventoryHolders - this.access = context; - this.level = playerInventory.player.level(); - this.inputSlot = this.addSlot(new Slot(this.container, 0, 20, 33)); -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java -index 7ae484b0fa5bf5494c6ead15f7f1c0fa840ae270..7129eb5f5cea39992b4c690cb421004004a952ea 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftBlockInventoryHolder.java -@@ -17,6 +17,13 @@ public class CraftBlockInventoryHolder implements BlockInventoryHolder { - this.block = CraftBlock.at(world, pos); - this.inventory = new CraftInventory(inv); - } -+ // Paper start - Add missing InventoryHolders -+ public CraftBlockInventoryHolder(net.minecraft.world.inventory.ContainerLevelAccess levelAccess, Inventory inventory) { -+ com.google.common.base.Preconditions.checkArgument(levelAccess.isBlock()); -+ this.block = CraftBlock.at(levelAccess.getWorld(), levelAccess.getPosition()); -+ this.inventory = inventory; -+ } -+ // Paper end - Add missing InventoryHolders - - @Override - public Block getBlock() { diff --git a/patches/server/0951-Fix-team-sidebar-objectives-not-being-cleared.patch b/patches/server/0945-Fix-team-sidebar-objectives-not-being-cleared.patch similarity index 100% rename from patches/server/0951-Fix-team-sidebar-objectives-not-being-cleared.patch rename to patches/server/0945-Fix-team-sidebar-objectives-not-being-cleared.patch diff --git a/patches/server/0946-Do-not-read-tile-entities-in-chunks-that-are-positio.patch b/patches/server/0946-Do-not-read-tile-entities-in-chunks-that-are-positio.patch deleted file mode 100644 index 0dabb115673f..000000000000 --- a/patches/server/0946-Do-not-read-tile-entities-in-chunks-that-are-positio.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 18 Jun 2023 23:04:46 -0700 -Subject: [PATCH] Do not read tile entities in chunks that are positioned - outside of the chunk - -The tile entities are not accessible and so should not be loaded. -This can happen as a result of users moving regionfiles around, -which would cause a crash on Folia but would appear to function -fine on Paper. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 29aaedbe70901fdd98f15f2ca5ba382106091d1a..f594b5c60c723ef70e51ab30b45b90f89d6972d6 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -307,6 +307,13 @@ public class ChunkSerializer { - for (int k1 = 0; k1 < nbttaglist3.size(); ++k1) { - CompoundTag nbttagcompound4 = nbttaglist3.getCompound(k1); - -+ // Paper start - do not read tile entities positioned outside the chunk -+ BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound4); -+ if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) { -+ LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk"); -+ continue; -+ } -+ // Paper end - do not read tile entities positioned outside the chunk - ((ChunkAccess) object1).setBlockEntityNbt(nbttagcompound4); - } - -@@ -517,10 +524,19 @@ public class ChunkSerializer { - CompoundTag nbttagcompound1 = nbttaglist1.getCompound(i); - boolean flag = nbttagcompound1.getBoolean("keepPacked"); - -+ // Paper start - do not read tile entities positioned outside the chunk -+ BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound1); // moved up -+ ChunkPos chunkPos = chunk.getPos(); -+ if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) { -+ LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk"); -+ continue; -+ } -+ // Paper end - do not read tile entities positioned outside the chunk -+ - if (flag) { - chunk.setBlockEntityNbt(nbttagcompound1); - } else { -- BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound1); -+ // Paper - do not read tile entities positioned outside the chunk; move up - BlockEntity tileentity = BlockEntity.loadStatic(blockposition, chunk.getBlockState(blockposition), nbttagcompound1); - - if (tileentity != null) { diff --git a/patches/server/0952-Fix-missing-map-initialize-event-call.patch b/patches/server/0946-Fix-missing-map-initialize-event-call.patch similarity index 100% rename from patches/server/0952-Fix-missing-map-initialize-event-call.patch rename to patches/server/0946-Fix-missing-map-initialize-event-call.patch diff --git a/patches/server/0953-Update-entity-data-when-attaching-firework-to-entity.patch b/patches/server/0947-Update-entity-data-when-attaching-firework-to-entity.patch similarity index 100% rename from patches/server/0953-Update-entity-data-when-attaching-firework-to-entity.patch rename to patches/server/0947-Update-entity-data-when-attaching-firework-to-entity.patch diff --git a/patches/server/0948-Remove-Spigot-Bug-Fix-for-MC-109346.patch b/patches/server/0948-Remove-Spigot-Bug-Fix-for-MC-109346.patch deleted file mode 100644 index 485f7359e3d3..000000000000 --- a/patches/server/0948-Remove-Spigot-Bug-Fix-for-MC-109346.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sat, 23 Sep 2023 01:51:22 -0400 -Subject: [PATCH] Remove Spigot Bug Fix for MC-109346 - - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index dc6d2ebbce9ccda5aa4a80dadcc34d48b67b9368..8cd52a6172fc7bdd5dc980329fed1d765e3750f2 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -348,12 +348,6 @@ public class ServerEntity { - ((LivingEntity) this.entity).detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending - } - -- // CraftBukkit start - MC-109346: Fix for nonsensical head yaw -- if (this.entity instanceof ServerPlayer) { -- sender.accept(new ClientboundRotateHeadPacket(this.entity, (byte) Mth.floor(this.entity.getYHeadRot() * 256.0F / 360.0F))); -- } -- // CraftBukkit end -- - if (!this.entity.getPassengers().isEmpty()) { - sender.accept(new ClientboundSetPassengersPacket(this.entity)); - } diff --git a/patches/server/0954-Use-correct-variable-for-initializing-CraftLootTable.patch b/patches/server/0948-Use-correct-variable-for-initializing-CraftLootTable.patch similarity index 100% rename from patches/server/0954-Use-correct-variable-for-initializing-CraftLootTable.patch rename to patches/server/0948-Use-correct-variable-for-initializing-CraftLootTable.patch diff --git a/patches/server/0955-Make-setVelocity-method-of-Fireballs-change-the-trav.patch b/patches/server/0949-Make-setVelocity-method-of-Fireballs-change-the-trav.patch similarity index 100% rename from patches/server/0955-Make-setVelocity-method-of-Fireballs-change-the-trav.patch rename to patches/server/0949-Make-setVelocity-method-of-Fireballs-change-the-trav.patch diff --git a/patches/server/0950-Fix-UnsafeValues-loadAdvancement.patch b/patches/server/0950-Fix-UnsafeValues-loadAdvancement.patch new file mode 100644 index 000000000000..0de7a22d6dd6 --- /dev/null +++ b/patches/server/0950-Fix-UnsafeValues-loadAdvancement.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: LemonCaramel +Date: Sun, 24 Sep 2023 20:19:44 +0900 +Subject: [PATCH] Fix UnsafeValues#loadAdvancement + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index fab7dc81f774889300ed0affaef71cbda36517df..6372f3a4fdf6e37ef785749ec40c3bd67b003b28 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -321,7 +321,27 @@ public final class CraftMagicNumbers implements UnsafeValues { + JsonElement jsonelement = ServerAdvancementManager.GSON.fromJson(advancement, JsonElement.class); + net.minecraft.advancements.Advancement nms = Util.getOrThrow(net.minecraft.advancements.Advancement.CODEC.parse(JsonOps.INSTANCE, jsonelement), JsonParseException::new); + if (nms != null) { +- MinecraftServer.getServer().getAdvancements().advancements.put(minecraftkey, new AdvancementHolder(minecraftkey, nms)); ++ // Paper start - Fix throw UnsupportedOperationException ++ //MinecraftServer.getServer().getAdvancements().advancements.put(minecraftkey, new AdvancementHolder(minecraftkey, nms)); ++ final com.google.common.collect.ImmutableMap.Builder mapBuilder = com.google.common.collect.ImmutableMap.builder(); ++ mapBuilder.putAll(MinecraftServer.getServer().getAdvancements().advancements); ++ ++ final AdvancementHolder holder = new AdvancementHolder(minecraftkey, nms); ++ mapBuilder.put(minecraftkey, holder); ++ ++ MinecraftServer.getServer().getAdvancements().advancements = mapBuilder.build(); ++ final net.minecraft.advancements.AdvancementTree tree = MinecraftServer.getServer().getAdvancements().tree(); ++ tree.addAll(List.of(holder)); ++ ++ // recalculate advancement position ++ final net.minecraft.advancements.AdvancementNode node = tree.get(minecraftkey); ++ if (node != null) { ++ final net.minecraft.advancements.AdvancementNode root = node.root(); ++ if (root.holder().value().display().isPresent()) { ++ net.minecraft.advancements.TreeNodePosition.run(root); ++ } ++ } ++ // Paper end - Fix throw UnsupportedOperationException + Advancement bukkit = Bukkit.getAdvancement(key); + + if (bukkit != null) { diff --git a/patches/server/0951-Add-player-idle-duration-API.patch b/patches/server/0951-Add-player-idle-duration-API.patch new file mode 100644 index 000000000000..992aafd75772 --- /dev/null +++ b/patches/server/0951-Add-player-idle-duration-API.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: booky10 +Date: Sat, 14 Oct 2023 03:11:11 +0200 +Subject: [PATCH] Add player idle duration API + +Implements API for getting and resetting a player's idle duration. + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 1e36d9ef87507d1a771c605ec51c0f66d2cec089..6130e66bcacec92c067c423470f41865a64452d7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -3337,6 +3337,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + // Paper end + ++ // Paper start ++ @Override ++ public Duration getIdleDuration() { ++ return Duration.ofMillis(net.minecraft.Util.getMillis() - this.getHandle().getLastActionTime()); ++ } ++ ++ @Override ++ public void resetIdleDuration() { ++ this.getHandle().resetLastActionTime(); ++ } ++ // Paper end ++ + public Player.Spigot spigot() + { + return this.spigot; diff --git a/patches/server/0958-Don-t-check-if-we-can-see-non-visible-entities.patch b/patches/server/0952-Don-t-check-if-we-can-see-non-visible-entities.patch similarity index 100% rename from patches/server/0958-Don-t-check-if-we-can-see-non-visible-entities.patch rename to patches/server/0952-Don-t-check-if-we-can-see-non-visible-entities.patch diff --git a/patches/server/0959-Fix-NPE-in-SculkBloomEvent-world-access.patch b/patches/server/0953-Fix-NPE-in-SculkBloomEvent-world-access.patch similarity index 100% rename from patches/server/0959-Fix-NPE-in-SculkBloomEvent-world-access.patch rename to patches/server/0953-Fix-NPE-in-SculkBloomEvent-world-access.patch diff --git a/patches/server/0954-Allow-null-itemstack-for-Player-sendEquipmentChange.patch b/patches/server/0954-Allow-null-itemstack-for-Player-sendEquipmentChange.patch new file mode 100644 index 000000000000..e01df45b178a --- /dev/null +++ b/patches/server/0954-Allow-null-itemstack-for-Player-sendEquipmentChange.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: David Scandurra +Date: Wed, 25 Oct 2023 20:36:25 +0200 +Subject: [PATCH] Allow null itemstack for Player#sendEquipmentChange + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 6130e66bcacec92c067c423470f41865a64452d7..b88c71c4a1d4eb5c24d143a0d8ddff507df690f7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1047,7 +1047,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void sendEquipmentChange(LivingEntity entity, EquipmentSlot slot, ItemStack item) { +- this.sendEquipmentChange(entity, Map.of(slot, item)); ++ this.sendEquipmentChange(entity, java.util.Collections.singletonMap(slot, item)); // Paper - replace Map.of to allow null values + } + + @Override diff --git a/patches/server/0961-Optimize-VarInts.patch b/patches/server/0955-Optimize-VarInts.patch similarity index 100% rename from patches/server/0961-Optimize-VarInts.patch rename to patches/server/0955-Optimize-VarInts.patch diff --git a/patches/server/0962-Add-API-to-get-the-collision-shape-of-a-block-before.patch b/patches/server/0956-Add-API-to-get-the-collision-shape-of-a-block-before.patch similarity index 100% rename from patches/server/0962-Add-API-to-get-the-collision-shape-of-a-block-before.patch rename to patches/server/0956-Add-API-to-get-the-collision-shape-of-a-block-before.patch diff --git a/patches/server/0956-Fix-UnsafeValues-loadAdvancement.patch b/patches/server/0956-Fix-UnsafeValues-loadAdvancement.patch deleted file mode 100644 index e1da894c6c3a..000000000000 --- a/patches/server/0956-Fix-UnsafeValues-loadAdvancement.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: LemonCaramel -Date: Sun, 24 Sep 2023 20:19:44 +0900 -Subject: [PATCH] Fix UnsafeValues#loadAdvancement - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 187854e8c560234710763f8e92c1a026550ba60d..b9183a9a657c2cd320fca0f15db0dae6827546f1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -316,7 +316,27 @@ public final class CraftMagicNumbers implements UnsafeValues { - JsonElement jsonelement = ServerAdvancementManager.GSON.fromJson(advancement, JsonElement.class); - net.minecraft.advancements.Advancement nms = Util.getOrThrow(net.minecraft.advancements.Advancement.CODEC.parse(JsonOps.INSTANCE, jsonelement), JsonParseException::new); - if (nms != null) { -- MinecraftServer.getServer().getAdvancements().advancements.put(minecraftkey, new AdvancementHolder(minecraftkey, nms)); -+ // Paper start - Fix throw UnsupportedOperationException -+ //MinecraftServer.getServer().getAdvancements().advancements.put(minecraftkey, new AdvancementHolder(minecraftkey, nms)); -+ final com.google.common.collect.ImmutableMap.Builder mapBuilder = com.google.common.collect.ImmutableMap.builder(); -+ mapBuilder.putAll(MinecraftServer.getServer().getAdvancements().advancements); -+ -+ final AdvancementHolder holder = new AdvancementHolder(minecraftkey, nms); -+ mapBuilder.put(minecraftkey, holder); -+ -+ MinecraftServer.getServer().getAdvancements().advancements = mapBuilder.build(); -+ final net.minecraft.advancements.AdvancementTree tree = MinecraftServer.getServer().getAdvancements().tree(); -+ tree.addAll(List.of(holder)); -+ -+ // recalculate advancement position -+ final net.minecraft.advancements.AdvancementNode node = tree.get(minecraftkey); -+ if (node != null) { -+ final net.minecraft.advancements.AdvancementNode root = node.root(); -+ if (root.holder().value().display().isPresent()) { -+ net.minecraft.advancements.TreeNodePosition.run(root); -+ } -+ } -+ // Paper end - Fix throw UnsupportedOperationException - Advancement bukkit = Bukkit.getAdvancement(key); - - if (bukkit != null) { diff --git a/patches/server/0957-Add-player-idle-duration-API.patch b/patches/server/0957-Add-player-idle-duration-API.patch deleted file mode 100644 index 6cf66e53bb8f..000000000000 --- a/patches/server/0957-Add-player-idle-duration-API.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: booky10 -Date: Sat, 14 Oct 2023 03:11:11 +0200 -Subject: [PATCH] Add player idle duration API - -Implements API for getting and resetting a player's idle duration. - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 3d061f6dd550e395ec572ecdbd80b8d2d36a4453..f983f5dfb0bc5a4aee3971c13d2b84f12519edec 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -3286,6 +3286,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - // Paper end - -+ // Paper start -+ @Override -+ public Duration getIdleDuration() { -+ return Duration.ofMillis(net.minecraft.Util.getMillis() - this.getHandle().getLastActionTime()); -+ } -+ -+ @Override -+ public void resetIdleDuration() { -+ this.getHandle().resetLastActionTime(); -+ } -+ // Paper end -+ - public Player.Spigot spigot() - { - return this.spigot; diff --git a/patches/server/0957-Add-predicate-for-blocks-when-raytracing.patch b/patches/server/0957-Add-predicate-for-blocks-when-raytracing.patch new file mode 100644 index 000000000000..2a30c191f619 --- /dev/null +++ b/patches/server/0957-Add-predicate-for-blocks-when-raytracing.patch @@ -0,0 +1,116 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TonytheMacaroni +Date: Wed, 6 Sep 2023 19:24:16 -0400 +Subject: [PATCH] Add predicate for blocks when raytracing + + +diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java +index c978f3b2d42f512e982f289e76c2422e41b7eec6..bb8e962e63c7a2d931f9bd7f7c002aa35cfa5fd3 100644 +--- a/src/main/java/net/minecraft/world/level/BlockGetter.java ++++ b/src/main/java/net/minecraft/world/level/BlockGetter.java +@@ -70,6 +70,12 @@ public interface BlockGetter extends LevelHeightAccessor { + + // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace + default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) { ++ // Paper start - Add predicate for blocks when raytracing ++ return clip(raytrace1, blockposition, null); ++ } ++ ++ default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition, java.util.function.Predicate canCollide) { ++ // Paper end - Add predicate for blocks when raytracing + // Paper start - Prevent raytrace from loading chunks + BlockState iblockdata = this.getBlockStateIfLoaded(blockposition); + if (iblockdata == null) { +@@ -79,7 +85,7 @@ public interface BlockGetter extends LevelHeightAccessor { + return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo())); + } + // Paper end - Prevent raytrace from loading chunks +- if (iblockdata.isAir()) return null; // Paper - Perf: optimise air cases ++ if (iblockdata.isAir() || (canCollide != null && this instanceof LevelAccessor levelAccessor && !canCollide.test(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, blockposition)))) return null; // Paper - Perf: optimise air cases & check canCollide predicate + FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: don't need to go to world state again + Vec3 vec3d = raytrace1.getFrom(); + Vec3 vec3d1 = raytrace1.getTo(); +@@ -95,8 +101,14 @@ public interface BlockGetter extends LevelHeightAccessor { + // CraftBukkit end + + default BlockHitResult clip(ClipContext context) { ++ // Paper start - Add predicate for blocks when raytracing ++ return clip(context, (java.util.function.Predicate) null); ++ } ++ ++ default BlockHitResult clip(ClipContext context, java.util.function.Predicate canCollide) { ++ // Paper end - Add predicate for blocks when raytracing + return (BlockHitResult) BlockGetter.traverseBlocks(context.getFrom(), context.getTo(), context, (raytrace1, blockposition) -> { +- return this.clip(raytrace1, blockposition); // CraftBukkit - moved into separate method ++ return this.clip(raytrace1, blockposition, canCollide); // CraftBukkit - moved into separate method // Paper - Add predicate for blocks when raytracing + }, (raytrace1) -> { + Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo()); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 457d5cdb510a11a069ac7f54a8ed95a74527bff3..be4fc09f9e30cb4bb5aaad49c23a19fd84f029bc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1105,9 +1105,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, double raySize, Predicate filter) { ++ // Paper start - Add predicate for blocks when raytracing ++ return rayTraceEntities((io.papermc.paper.math.Position) start, direction, maxDistance, raySize, filter); ++ } ++ ++ public RayTraceResult rayTraceEntities(io.papermc.paper.math.Position start, Vector direction, double maxDistance, double raySize, Predicate filter) { + Preconditions.checkArgument(start != null, "Location start cannot be null"); +- Preconditions.checkArgument(this.equals(start.getWorld()), "Location start cannot be in a different world"); +- start.checkFinite(); ++ Preconditions.checkArgument(!(start instanceof Location location) || this.equals(location.getWorld()), "Location start cannot be in a different world"); ++ Preconditions.checkArgument(start.isFinite(), "Location start is not finite"); ++ // Paper end - Add predicate for blocks when raytracing + + Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); + direction.checkFinite(); +@@ -1157,9 +1163,16 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks) { ++ // Paper start - Add predicate for blocks when raytracing ++ return this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, null); ++ } ++ ++ @Override ++ public RayTraceResult rayTraceBlocks(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, Predicate canCollide) { + Preconditions.checkArgument(start != null, "Location start cannot be null"); +- Preconditions.checkArgument(this.equals(start.getWorld()), "Location start cannot be in a different world"); +- start.checkFinite(); ++ Preconditions.checkArgument(!(start instanceof Location location) || this.equals(location.getWorld()), "Location start cannot be in a different world"); ++ Preconditions.checkArgument(start.isFinite(), "Location start is not finite"); ++ // Paper end - Add predicate for blocks when raytracing + + Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); + direction.checkFinite(); +@@ -1172,16 +1185,23 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + + Vector dir = direction.clone().normalize().multiply(maxDistance); +- Vec3 startPos = CraftLocation.toVec3D(start); ++ Vec3 startPos = io.papermc.paper.util.MCUtil.toVec3(start); // Paper - Add predicate for blocks when raytracing + Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ()); +- HitResult nmsHitResult = this.getHandle().clip(new ClipContext(startPos, endPos, ignorePassableBlocks ? ClipContext.Block.COLLIDER : ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty())); ++ HitResult nmsHitResult = this.getHandle().clip(new ClipContext(startPos, endPos, ignorePassableBlocks ? ClipContext.Block.COLLIDER : ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty()), canCollide); // Paper - Add predicate for blocks when raytracing + + return CraftRayTraceResult.fromNMS(this, nmsHitResult); + } + + @Override + public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate filter) { +- RayTraceResult blockHit = this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks); ++ // Paper start - Add predicate for blocks when raytracing ++ return this.rayTrace(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, raySize, filter, null); ++ } ++ ++ @Override ++ public RayTraceResult rayTrace(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate filter, Predicate canCollide) { ++ RayTraceResult blockHit = this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, canCollide); ++ // Paper end - Add predicate for blocks when raytracing + Vector startVec = null; + double blockHitDistance = maxDistance; + diff --git a/patches/server/0958-Broadcast-take-item-packets-with-collector-as-source.patch b/patches/server/0958-Broadcast-take-item-packets-with-collector-as-source.patch new file mode 100644 index 000000000000..bc32e3307de9 --- /dev/null +++ b/patches/server/0958-Broadcast-take-item-packets-with-collector-as-source.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: booky10 +Date: Sun, 29 Oct 2023 02:36:10 +0100 +Subject: [PATCH] Broadcast take item packets with collector as source + +This fixes players (which can't view the collector) seeing item pickups with themselves as the target. + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 549bb5caa38e08196fddbd4e4255b499c784a9c2..294c4950ebe63a5d0f74907692010c9c99cf82da 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3716,7 +3716,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + + public void take(Entity item, int count) { + if (!item.isRemoved() && !this.level().isClientSide && (item instanceof ItemEntity || item instanceof AbstractArrow || item instanceof ExperienceOrb)) { +- ((ServerLevel) this.level()).getChunkSource().broadcast(item, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count)); ++ ((ServerLevel) this.level()).getChunkSource().broadcastAndSend(this, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count)); // Paper - broadcast with collector as source + } + + } diff --git a/patches/server/0959-Expand-LingeringPotion-API.patch b/patches/server/0959-Expand-LingeringPotion-API.patch new file mode 100644 index 000000000000..59154f7803f8 --- /dev/null +++ b/patches/server/0959-Expand-LingeringPotion-API.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tamion <70228790+notTamion@users.noreply.github.com> +Date: Sat, 4 Nov 2023 23:57:05 +0100 +Subject: [PATCH] Expand LingeringPotion API + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +index f191b0e303a97b5406133454374ff9448aee1a5a..0c5bac5d955b1e380103c9b51635010212c6526e 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +@@ -288,7 +288,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + + // CraftBukkit start + org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, position, entityareaeffectcloud); +- if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (noEffects && entityareaeffectcloud.effects.isEmpty() && entityareaeffectcloud.getPotion().getEffects().isEmpty()))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling ++ if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (!event.allowsEmptyCreation() && (noEffects && entityareaeffectcloud.effects.isEmpty() && entityareaeffectcloud.getPotion().getEffects().isEmpty())))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling + this.level().addFreshEntity(entityareaeffectcloud); + } else { + entityareaeffectcloud.discard(); diff --git a/patches/server/0966-Add-MaterialTagsTest.patch b/patches/server/0960-Add-MaterialTagsTest.patch similarity index 100% rename from patches/server/0966-Add-MaterialTagsTest.patch rename to patches/server/0960-Add-MaterialTagsTest.patch diff --git a/patches/server/0960-Allow-null-itemstack-for-Player-sendEquipmentChange.patch b/patches/server/0960-Allow-null-itemstack-for-Player-sendEquipmentChange.patch deleted file mode 100644 index 95b8d87e3efb..000000000000 --- a/patches/server/0960-Allow-null-itemstack-for-Player-sendEquipmentChange.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: David Scandurra -Date: Wed, 25 Oct 2023 20:36:25 +0200 -Subject: [PATCH] Allow null itemstack for Player#sendEquipmentChange - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index f983f5dfb0bc5a4aee3971c13d2b84f12519edec..cd58b5e338ece695cddab37b5bcce730b04dc63d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1041,7 +1041,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void sendEquipmentChange(LivingEntity entity, EquipmentSlot slot, ItemStack item) { -- this.sendEquipmentChange(entity, Map.of(slot, item)); -+ this.sendEquipmentChange(entity, java.util.Collections.singletonMap(slot, item)); // Paper - replace Map.of to allow null values - } - - @Override diff --git a/patches/server/0961-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch b/patches/server/0961-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch new file mode 100644 index 000000000000..4a02216b2f35 --- /dev/null +++ b/patches/server/0961-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tamion <70228790+notTamion@users.noreply.github.com> +Date: Sat, 30 Sep 2023 12:36:14 +0200 +Subject: [PATCH] Fix strikeLightningEffect powers lightning rods and clears + copper + + +diff --git a/src/main/java/net/minecraft/world/entity/LightningBolt.java b/src/main/java/net/minecraft/world/entity/LightningBolt.java +index 0db0d67f9ac15372becc1166c37f7f0aede4a4da..a9e70484b01fc082ea25d43d1d42833499b5e41d 100644 +--- a/src/main/java/net/minecraft/world/entity/LightningBolt.java ++++ b/src/main/java/net/minecraft/world/entity/LightningBolt.java +@@ -45,6 +45,7 @@ public class LightningBolt extends Entity { + private ServerPlayer cause; + private final Set hitEntities = Sets.newHashSet(); + private int blocksSetOnFire; ++ public boolean isEffect; // Paper - Properly handle lightning effects api + + public LightningBolt(EntityType type, Level world) { + super(type, world); +@@ -85,7 +86,7 @@ public class LightningBolt extends Entity { + @Override + public void tick() { + super.tick(); +- if (this.life == 2) { ++ if (!this.isEffect && this.life == 2) { // Paper - Properly handle lightning effects api + if (this.level().isClientSide()) { + this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.WEATHER, 10000.0F, 0.8F + this.random.nextFloat() * 0.2F, false); + this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F, false); +@@ -132,7 +133,7 @@ public class LightningBolt extends Entity { + } + } + +- if (this.life >= 0 && !this.visualOnly) { // CraftBukkit - add !this.visualOnly ++ if (this.life >= 0 && !this.isEffect) { // CraftBukkit - add !this.visualOnly // Paper - Properly handle lightning effects api + if (!(this.level() instanceof ServerLevel)) { + this.level().setSkyFlashTime(2); + } else if (!this.visualOnly) { +@@ -161,7 +162,7 @@ public class LightningBolt extends Entity { + } + + private void spawnFire(int spreadAttempts) { +- if (!this.visualOnly && !this.level().isClientSide && this.level().getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) { ++ if (!this.visualOnly && !this.isEffect && !this.level().isClientSide && this.level().getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) { // Paper - Properly handle lightning effects api + BlockPos blockposition = this.blockPosition(); + BlockState iblockdata = BaseFireBlock.getState(this.level(), blockposition); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index be4fc09f9e30cb4bb5aaad49c23a19fd84f029bc..263d60cb498f601b1381124127c084efe424409e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -746,7 +746,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + LightningBolt lightning = EntityType.LIGHTNING_BOLT.create(this.world); + lightning.moveTo(loc.getX(), loc.getY(), loc.getZ()); +- lightning.setVisualOnly(isVisual); ++ lightning.isEffect = isVisual; // Paper - Properly handle lightning effects api + this.world.strikeLightning(lightning, LightningStrikeEvent.Cause.CUSTOM); + return (LightningStrike) lightning.getBukkitEntity(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java +index 6fed8075aa75e3852dc826a45ca44603c0446a56..e9f471e60af0725ec34e2985d63ae9ea9f88590a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java +@@ -13,7 +13,7 @@ public class CraftLightningStrike extends CraftEntity implements LightningStrike + + @Override + public boolean isEffect() { +- return this.getHandle().visualOnly; ++ return this.getHandle().isEffect; // Paper - Properly handle lightning effects api + } + + public int getFlashes() { diff --git a/patches/server/0962-Add-hand-to-fish-event-for-all-player-interactions.patch b/patches/server/0962-Add-hand-to-fish-event-for-all-player-interactions.patch new file mode 100644 index 000000000000..59919994a538 --- /dev/null +++ b/patches/server/0962-Add-hand-to-fish-event-for-all-player-interactions.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: booky10 +Date: Mon, 3 Jul 2023 01:55:32 +0200 +Subject: [PATCH] Add hand to fish event for all player interactions + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +index b6ebae97dc863ba1748e9b32555f940077846be8..90a5f6bd729148f2adc745273536e48d704fcd1e 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +@@ -474,7 +474,15 @@ public class FishingHook extends Projectile { + @Override + public void readAdditionalSaveData(CompoundTag nbt) {} + ++ // Paper start - Add hand parameter to PlayerFishEvent ++ @Deprecated ++ @io.papermc.paper.annotation.DoNotUse + public int retrieve(ItemStack usedItem) { ++ return this.retrieve(net.minecraft.world.InteractionHand.MAIN_HAND, usedItem); ++ } ++ ++ public int retrieve(net.minecraft.world.InteractionHand hand, ItemStack usedItem) { ++ // Paper end - Add hand parameter to PlayerFishEvent + net.minecraft.world.entity.player.Player entityhuman = this.getPlayerOwner(); + + if (!this.level().isClientSide && entityhuman != null && !this.shouldStopFishing(entityhuman)) { +@@ -482,7 +490,7 @@ public class FishingHook extends Projectile { + + if (this.hookedIn != null) { + // CraftBukkit start +- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), this.hookedIn.getBukkitEntity(), (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_ENTITY); ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), this.hookedIn.getBukkitEntity(), (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.CAUGHT_ENTITY); // Paper - Add hand parameter to PlayerFishEvent + this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); + + if (playerFishEvent.isCancelled()) { +@@ -511,7 +519,7 @@ public class FishingHook extends Projectile { + } + // Paper end + // CraftBukkit start +- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem != null ? entityitem.getBukkitEntity() : null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH); // Paper - entityitem may be null ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem != null ? entityitem.getBukkitEntity() : null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.CAUGHT_FISH); // Paper - entityitem may be null // Paper - Add hand parameter to PlayerFishEvent + playerFishEvent.setExpToDrop(this.random.nextInt(6) + 1); + this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); + +@@ -545,7 +553,7 @@ public class FishingHook extends Projectile { + + if (this.onGround()) { + // CraftBukkit start +- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.IN_GROUND); ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.IN_GROUND); // Paper - Add hand parameter to PlayerFishEvent + this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); + + if (playerFishEvent.isCancelled()) { +@@ -556,7 +564,7 @@ public class FishingHook extends Projectile { + } + // CraftBukkit start + if (i == 0) { +- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.REEL_IN); ++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.REEL_IN); // Paper - Add hand parameter to PlayerFishEvent + this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); + if (playerFishEvent.isCancelled()) { + return 0; +diff --git a/src/main/java/net/minecraft/world/item/FishingRodItem.java b/src/main/java/net/minecraft/world/item/FishingRodItem.java +index b9aca584c9765e995d1f8b9b2e45e5257fb6ab9d..95144f0ea5e99285c0a82b9d2e60766b785a236d 100644 +--- a/src/main/java/net/minecraft/world/item/FishingRodItem.java ++++ b/src/main/java/net/minecraft/world/item/FishingRodItem.java +@@ -29,7 +29,7 @@ public class FishingRodItem extends Item implements Vanishable { + + if (user.fishing != null) { + if (!world.isClientSide) { +- i = user.fishing.retrieve(itemstack); ++ i = user.fishing.retrieve(hand, itemstack); // Paper - Add hand parameter to PlayerFishEvent + itemstack.hurtAndBreak(i, user, (entityhuman1) -> { + entityhuman1.broadcastBreakEvent(hand); + }); diff --git a/patches/server/0963-Add-predicate-for-blocks-when-raytracing.patch b/patches/server/0963-Add-predicate-for-blocks-when-raytracing.patch deleted file mode 100644 index 8488f5abd834..000000000000 --- a/patches/server/0963-Add-predicate-for-blocks-when-raytracing.patch +++ /dev/null @@ -1,116 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TonytheMacaroni -Date: Wed, 6 Sep 2023 19:24:16 -0400 -Subject: [PATCH] Add predicate for blocks when raytracing - - -diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java -index c978f3b2d42f512e982f289e76c2422e41b7eec6..bb8e962e63c7a2d931f9bd7f7c002aa35cfa5fd3 100644 ---- a/src/main/java/net/minecraft/world/level/BlockGetter.java -+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java -@@ -70,6 +70,12 @@ public interface BlockGetter extends LevelHeightAccessor { - - // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace - default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) { -+ // Paper start - Add predicate for blocks when raytracing -+ return clip(raytrace1, blockposition, null); -+ } -+ -+ default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition, java.util.function.Predicate canCollide) { -+ // Paper end - Add predicate for blocks when raytracing - // Paper start - Prevent raytrace from loading chunks - BlockState iblockdata = this.getBlockStateIfLoaded(blockposition); - if (iblockdata == null) { -@@ -79,7 +85,7 @@ public interface BlockGetter extends LevelHeightAccessor { - return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo())); - } - // Paper end - Prevent raytrace from loading chunks -- if (iblockdata.isAir()) return null; // Paper - Perf: optimise air cases -+ if (iblockdata.isAir() || (canCollide != null && this instanceof LevelAccessor levelAccessor && !canCollide.test(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, blockposition)))) return null; // Paper - Perf: optimise air cases & check canCollide predicate - FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: don't need to go to world state again - Vec3 vec3d = raytrace1.getFrom(); - Vec3 vec3d1 = raytrace1.getTo(); -@@ -95,8 +101,14 @@ public interface BlockGetter extends LevelHeightAccessor { - // CraftBukkit end - - default BlockHitResult clip(ClipContext context) { -+ // Paper start - Add predicate for blocks when raytracing -+ return clip(context, (java.util.function.Predicate) null); -+ } -+ -+ default BlockHitResult clip(ClipContext context, java.util.function.Predicate canCollide) { -+ // Paper end - Add predicate for blocks when raytracing - return (BlockHitResult) BlockGetter.traverseBlocks(context.getFrom(), context.getTo(), context, (raytrace1, blockposition) -> { -- return this.clip(raytrace1, blockposition); // CraftBukkit - moved into separate method -+ return this.clip(raytrace1, blockposition, canCollide); // CraftBukkit - moved into separate method // Paper - Add predicate for blocks when raytracing - }, (raytrace1) -> { - Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo()); - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 467aacb5ce61ac79d8294067fd681b081c195fbe..3a941be852a2dd5114ca9673597e84e3813d6ee2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1092,9 +1092,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, double raySize, Predicate filter) { -+ // Paper start - Add predicate for blocks when raytracing -+ return rayTraceEntities((io.papermc.paper.math.Position) start, direction, maxDistance, raySize, filter); -+ } -+ -+ public RayTraceResult rayTraceEntities(io.papermc.paper.math.Position start, Vector direction, double maxDistance, double raySize, Predicate filter) { - Preconditions.checkArgument(start != null, "Location start cannot be null"); -- Preconditions.checkArgument(this.equals(start.getWorld()), "Location start cannot be in a different world"); -- start.checkFinite(); -+ Preconditions.checkArgument(!(start instanceof Location location) || this.equals(location.getWorld()), "Location start cannot be in a different world"); -+ Preconditions.checkArgument(start.isFinite(), "Location start is not finite"); -+ // Paper end - Add predicate for blocks when raytracing - - Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); - direction.checkFinite(); -@@ -1144,9 +1150,16 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks) { -+ // Paper start - Add predicate for blocks when raytracing -+ return this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, null); -+ } -+ -+ @Override -+ public RayTraceResult rayTraceBlocks(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, Predicate canCollide) { - Preconditions.checkArgument(start != null, "Location start cannot be null"); -- Preconditions.checkArgument(this.equals(start.getWorld()), "Location start cannot be in a different world"); -- start.checkFinite(); -+ Preconditions.checkArgument(!(start instanceof Location location) || this.equals(location.getWorld()), "Location start cannot be in a different world"); -+ Preconditions.checkArgument(start.isFinite(), "Location start is not finite"); -+ // Paper end - Add predicate for blocks when raytracing - - Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); - direction.checkFinite(); -@@ -1159,16 +1172,23 @@ public class CraftWorld extends CraftRegionAccessor implements World { - } - - Vector dir = direction.clone().normalize().multiply(maxDistance); -- Vec3 startPos = CraftLocation.toVec3D(start); -+ Vec3 startPos = io.papermc.paper.util.MCUtil.toVec3(start); // Paper - Add predicate for blocks when raytracing - Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ()); -- HitResult nmsHitResult = this.getHandle().clip(new ClipContext(startPos, endPos, ignorePassableBlocks ? ClipContext.Block.COLLIDER : ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty())); -+ HitResult nmsHitResult = this.getHandle().clip(new ClipContext(startPos, endPos, ignorePassableBlocks ? ClipContext.Block.COLLIDER : ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty()), canCollide); // Paper - Add predicate for blocks when raytracing - - return CraftRayTraceResult.fromNMS(this, nmsHitResult); - } - - @Override - public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate filter) { -- RayTraceResult blockHit = this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks); -+ // Paper start - Add predicate for blocks when raytracing -+ return this.rayTrace(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, raySize, filter, null); -+ } -+ -+ @Override -+ public RayTraceResult rayTrace(io.papermc.paper.math.Position start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate filter, Predicate canCollide) { -+ RayTraceResult blockHit = this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks, canCollide); -+ // Paper end - Add predicate for blocks when raytracing - Vector startVec = null; - double blockHitDistance = maxDistance; - diff --git a/patches/server/0963-Fix-several-issues-with-EntityBreedEvent.patch b/patches/server/0963-Fix-several-issues-with-EntityBreedEvent.patch new file mode 100644 index 000000000000..eafe12a1bb6d --- /dev/null +++ b/patches/server/0963-Fix-several-issues-with-EntityBreedEvent.patch @@ -0,0 +1,118 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 15 Dec 2022 00:14:44 -0800 +Subject: [PATCH] Fix several issues with EntityBreedEvent + +Upstream did not account for different hands when storing +the breed item for later use in the event. Also they only +stored a reference to the stack, not a copy so if the stack +changed after love mode was started, the breed item in the event +also changed. Also in several places, the breed item was stored after +it was decreased by one to consume the item. + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java +index 907ed82fea71254d6624eda878e2668cd26422a7..081d1e38b7b1f286e138b0981aaa760e58761215 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Animal.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java +@@ -152,8 +152,9 @@ public abstract class Animal extends AgeableMob { + int i = this.getAge(); + + if (!this.level().isClientSide && i == 0 && this.canFallInLove()) { ++ final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying + this.usePlayerItem(player, hand, itemstack); +- this.setInLove(player); ++ this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying + return InteractionResult.SUCCESS; + } + +@@ -182,10 +183,18 @@ public abstract class Animal extends AgeableMob { + return this.inLove <= 0; + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Fix EntityBreedEvent copying + public void setInLove(@Nullable Player player) { ++ // Paper start - Fix EntityBreedEvent copying ++ this.setInLove(player, null); ++ } ++ public void setInLove(@Nullable Player player, @Nullable ItemStack breedItemCopy) { ++ if (breedItemCopy != null) this.breedItem = breedItemCopy; ++ // Paper end - Fix EntityBreedEvent copying + // CraftBukkit start + EntityEnterLoveModeEvent entityEnterLoveModeEvent = CraftEventFactory.callEntityEnterLoveModeEvent(player, this, 600); + if (entityEnterLoveModeEvent.isCancelled()) { ++ this.breedItem = null; // Paper - Fix EntityBreedEvent copying; clear if cancelled + return; + } + this.inLove = entityEnterLoveModeEvent.getTicksInLove(); +@@ -193,7 +202,7 @@ public abstract class Animal extends AgeableMob { + if (player != null) { + this.loveCause = player.getUUID(); + } +- this.breedItem = player.getInventory().getSelected(); // CraftBukkit ++ // Paper - Fix EntityBreedEvent copying; set breed item in better place + + this.level().broadcastEntityEvent(this, (byte) 18); + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java +index f783fe169141d33e8569ec7f5d71985b74bdbcb6..be554dbaa9900207753e4f67f0ba402333e21338 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Panda.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java +@@ -649,8 +649,9 @@ public class Panda extends Animal { + this.usePlayerItem(player, hand, itemstack); + this.ageUp((int) ((float) (-this.getAge() / 20) * 0.1F), true); + } else if (!this.level().isClientSide && this.getAge() == 0 && this.canFallInLove()) { ++ final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying + this.usePlayerItem(player, hand, itemstack); +- this.setInLove(player); ++ this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying + } else { + if (this.level().isClientSide || this.isSitting() || this.isInWater()) { + return InteractionResult.PASS; +diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java +index 2382f4f044e346fbc0e25de8fb2be30f3630c722..cb48eb0a856338e6012dd66bff47692ee1dfd958 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java ++++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java +@@ -389,7 +389,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl + + boolean bl2 = this.isTamed() && this.getAge() == 0 && this.canFallInLove(); + if (bl2) { +- this.setInLove(player); ++ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying + } + + boolean bl3 = this.isBaby(); +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +index 94dd97662ba07689fbfa16ef5c7d99fe12ce83de..815eb15086976b8f9e03bf8182d9ed50aec14720 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +@@ -513,7 +513,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + b0 = 5; + if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) { + flag = true; +- this.setInLove(player); ++ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying + } + } else if (item.is(Items.GOLDEN_APPLE) || item.is(Items.ENCHANTED_GOLDEN_APPLE)) { + f = 10.0F; +@@ -521,7 +521,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + b0 = 10; + if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) { + flag = true; +- this.setInLove(player); ++ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +index 9b5b894d43f25566ab9c3698705e978ab823a0d2..6623674136b0f865d5b3d7a10d3bf05793b82f87 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +@@ -191,7 +191,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder -Date: Sun, 29 Oct 2023 02:36:10 +0100 -Subject: [PATCH] Broadcast take item packets with collector as source - -This fixes players (which can't view the collector) seeing item pickups with themselves as the target. - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index f6592d997a4cfd9d3ca86cd955e3de0a49743bfa..f6eda18a0bcc398538c76bf4ca7c2a611523aa5d 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3703,7 +3703,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - public void take(Entity item, int count) { - if (!item.isRemoved() && !this.level().isClientSide && (item instanceof ItemEntity || item instanceof AbstractArrow || item instanceof ExperienceOrb)) { -- ((ServerLevel) this.level()).getChunkSource().broadcast(item, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count)); -+ ((ServerLevel) this.level()).getChunkSource().broadcastAndSend(this, new ClientboundTakeItemEntityPacket(item.getId(), this.getId(), count)); // Paper - broadcast with collector as source - } - - } diff --git a/patches/server/0965-Expand-LingeringPotion-API.patch b/patches/server/0965-Expand-LingeringPotion-API.patch deleted file mode 100644 index 6036e51ec0c5..000000000000 --- a/patches/server/0965-Expand-LingeringPotion-API.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Tamion <70228790+notTamion@users.noreply.github.com> -Date: Sat, 4 Nov 2023 23:57:05 +0100 -Subject: [PATCH] Expand LingeringPotion API - - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -index aaed936c7b6a6ebcd69c8c51f5c92c3b1c51ec27..8bfe0e87c6db0fc89dd64a7ed2a0b4f5cb90b207 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -@@ -288,7 +288,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - - // CraftBukkit start - org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, position, entityareaeffectcloud); -- if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (noEffects && entityareaeffectcloud.effects.isEmpty() && entityareaeffectcloud.getPotion().getEffects().isEmpty()))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling -+ if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (!event.allowsEmptyCreation() && (noEffects && entityareaeffectcloud.effects.isEmpty() && entityareaeffectcloud.getPotion().getEffects().isEmpty())))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling - this.level().addFreshEntity(entityareaeffectcloud); - } else { - entityareaeffectcloud.discard(); diff --git a/patches/server/0971-Fix-missing-event-call-for-entity-teleport-API.patch b/patches/server/0965-Fix-missing-event-call-for-entity-teleport-API.patch similarity index 100% rename from patches/server/0971-Fix-missing-event-call-for-entity-teleport-API.patch rename to patches/server/0965-Fix-missing-event-call-for-entity-teleport-API.patch diff --git a/patches/server/0972-Lazily-create-LootContext-for-criterions.patch b/patches/server/0966-Lazily-create-LootContext-for-criterions.patch similarity index 100% rename from patches/server/0972-Lazily-create-LootContext-for-criterions.patch rename to patches/server/0966-Lazily-create-LootContext-for-criterions.patch diff --git a/patches/server/0967-Don-t-fire-sync-events-during-worldgen.patch b/patches/server/0967-Don-t-fire-sync-events-during-worldgen.patch new file mode 100644 index 000000000000..fa62a6db6582 --- /dev/null +++ b/patches/server/0967-Don-t-fire-sync-events-during-worldgen.patch @@ -0,0 +1,208 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 23 Nov 2023 10:33:25 -0800 +Subject: [PATCH] Don't fire sync events during worldgen + +Fixes EntityPotionEffectEvent +Fixes EntityPoseChangeEvent + +Asynchronous chunk generation provides an opportunity for things +to happen async that previously fired synchronous-only events. This +patch is for mitigating those issues by various methods. + +Also fixes correctly marking/clearing the entity generation flag. +This patch sets the generation flag anytime an entity is created +via StructureTemplate before loading from NBT to catch uses of +the flag during the loading logic. This patch clears the generation +flag from an entity when added to a ServerLevel for the situation +where generation happened directly to a ServerLevel and the +entity still has the flag set. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 0e7811ae2a8731ae7475aabd2322e56ab364bc32..b5d6a7eaa24d9968e159d77a4295be00332a5457 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1220,6 +1220,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + // CraftBukkit start + private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { + org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process + // Paper start - extra debug info + if (entity.valid) { + MinecraftServer.LOGGER.error("Attempted Double World add on {}", entity, new Throwable()); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index effd39457989f34823e4fa7bc038c47d04714317..c153912929e7b505ffebb91ccda2f9175347b089 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -624,7 +624,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + if (pose == this.getPose()) { + return; + } +- this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), Pose.values()[pose.ordinal()])); ++ // Paper start - Don't fire sync event during generation ++ if (!this.generation) { ++ this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), Pose.values()[pose.ordinal()])); ++ } ++ // Paper end - Don't fire sync event during generation + // CraftBukkit end + this.entityData.set(Entity.DATA_POSE, pose); + } +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index 656c68b37bc25d6b77f295f9efe0a81dd20b69c1..8ba573bb4099ee5b27b61f333e72d794c48d5f29 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -584,9 +584,15 @@ public class EntityType implements FeatureElement, EntityTypeT + } + + public static Optional create(CompoundTag nbt, Level world) { ++ // Paper start - Don't fire sync event during generation ++ return create(nbt, world, false); ++ } ++ public static Optional create(CompoundTag nbt, Level world, boolean generation) { ++ // Paper end - Don't fire sync event during generation + return Util.ifElse(EntityType.by(nbt).map((entitytypes) -> { + return entitytypes.create(world); + }), (entity) -> { ++ if (generation) entity.generation = true; // Paper - Don't fire sync event during generation + entity.load(nbt); + }, () -> { + EntityType.LOGGER.warn("Skipping Entity with id {}", nbt.getString("id")); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 294c4950ebe63a5d0f74907692010c9c99cf82da..fe95119a8d26887f4e9cd1b9cd1299515835e958 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1134,6 +1134,11 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) { ++ // Paper start - Don't fire sync event during generation ++ return this.addEffect(mobeffect, entity, cause, true); ++ } ++ public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) { ++ // Paper end - Don't fire sync event during generation + // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API + if (this.isTickingEffects) { + this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause)); +@@ -1153,10 +1158,13 @@ public abstract class LivingEntity extends Entity implements Attackable { + override = new MobEffectInstance(mobeffect1).update(mobeffect); + } + ++ if (fireEvent) { // Paper - Don't fire sync event during generation + EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect1, mobeffect, cause, override); ++ override = event.isOverride(); // Paper - Don't fire sync event during generation + if (event.isCancelled()) { + return false; + } ++ } // Paper - Don't fire sync event during generation + // CraftBukkit end + + if (mobeffect1 == null) { +@@ -1164,7 +1172,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.onEffectAdded(mobeffect, entity); + flag = true; + // CraftBukkit start +- } else if (event.isOverride()) { ++ } else if (override) { // Paper - Don't fire sync event during generation + mobeffect1.update(mobeffect); + this.onEffectUpdated(mobeffect1, true, entity); + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java +index d90da2f9e4d6214577bc81bd6c70ba8593788898..ffa4f34d964fbcc53e2dfe11677832db21a6eb93 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Spider.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java +@@ -182,7 +182,7 @@ public class Spider extends Monster { + MobEffect mobeffectlist = entityspider_groupdataspider.effect; + + if (mobeffectlist != null) { +- this.addEffect(new MobEffectInstance(mobeffectlist, -1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN); // CraftBukkit ++ this.addEffect(new MobEffectInstance(mobeffectlist, -1), null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN, world instanceof net.minecraft.server.level.ServerLevel); // CraftBukkit // Paper - Don't fire sync event during generation; only if this is happening in a ServerLevel + } + } + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +index ae8a42261337bf736d0cc1bbe18da2b773417ca4..471e8493622c89d44a82f42f135cb308c9e0fbfe 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +@@ -518,7 +518,7 @@ public class StructureTemplate { + private static Optional createEntityIgnoreException(ServerLevelAccessor world, CompoundTag nbt) { + // CraftBukkit start + // try { +- return EntityType.create(nbt, world.getLevel()); ++ return EntityType.create(nbt, world.getLevel(), true); // Paper - Don't fire sync event during generation + // } catch (Exception exception) { + // return Optional.empty(); + // } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java +index e37c2d82ed606cbfe00c152b08c3ab99ac751f69..7ed861cd67889e525ab4987c0afed245aca08833 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java +@@ -93,15 +93,17 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { + return this.handle.getLevel(); + } + +- @Override +- public void addFreshEntityWithPassengers(Entity arg0, CreatureSpawnEvent.SpawnReason arg1) { +- this.handle.addFreshEntityWithPassengers(arg0, arg1); +- } +- +- @Override +- public void addFreshEntityWithPassengers(Entity entity) { +- this.handle.addFreshEntityWithPassengers(entity); +- } ++ // Paper start - Don't fire sync event during generation; don't override these methods so all entities are run through addFreshEntity ++ // @Override ++ // public void addFreshEntityWithPassengers(Entity arg0, CreatureSpawnEvent.SpawnReason arg1) { ++ // this.handle.addFreshEntityWithPassengers(arg0, arg1); ++ // } ++ // ++ // @Override ++ // public void addFreshEntityWithPassengers(Entity entity) { ++ // this.handle.addFreshEntityWithPassengers(entity); ++ // } ++ // Paper end - Don't fire sync event during generation; don't override these methods + + @Override + public ServerLevel getMinecraftWorld() { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java +index b4b297945fb601701aac845d09e88fb74b09c3fa..7482dfe64458320d44089c0778591694202e9f70 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java +@@ -39,21 +39,23 @@ public class TransformerGeneratorAccess extends DelegatedGeneratorAccess { + return super.addFreshEntity(arg0, arg1); + } + +- @Override +- public void addFreshEntityWithPassengers(Entity entity) { +- if (this.structureTransformer != null && !this.structureTransformer.transformEntity(entity)) { +- return; +- } +- super.addFreshEntityWithPassengers(entity); +- } +- +- @Override +- public void addFreshEntityWithPassengers(Entity arg0, SpawnReason arg1) { +- if (this.structureTransformer != null && !this.structureTransformer.transformEntity(arg0)) { +- return; +- } +- super.addFreshEntityWithPassengers(arg0, arg1); +- } ++ // Paper start - Don't fire sync event during generation; don't override these methods so all entities are run through addFreshEntity ++ // @Override ++ // public void addFreshEntityWithPassengers(Entity entity) { ++ // if (this.structureTransformer != null && !this.structureTransformer.transformEntity(entity)) { ++ // return; ++ // } ++ // super.addFreshEntityWithPassengers(entity); ++ // } ++ // ++ // @Override ++ // public void addFreshEntityWithPassengers(Entity arg0, SpawnReason arg1) { ++ // if (this.structureTransformer != null && !this.structureTransformer.transformEntity(arg0)) { ++ // return; ++ // } ++ // super.addFreshEntityWithPassengers(arg0, arg1); ++ // } ++ // Paper end - Don't fire sync event during generation; don't override these methods + + public boolean setCraftBlock(BlockPos position, CraftBlockState craftBlockState, int i, int j) { + if (this.structureTransformer != null) { diff --git a/patches/server/0967-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch b/patches/server/0967-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch deleted file mode 100644 index beed351bbb2d..000000000000 --- a/patches/server/0967-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Tamion <70228790+notTamion@users.noreply.github.com> -Date: Sat, 30 Sep 2023 12:36:14 +0200 -Subject: [PATCH] Fix strikeLightningEffect powers lightning rods and clears - copper - - -diff --git a/src/main/java/net/minecraft/world/entity/LightningBolt.java b/src/main/java/net/minecraft/world/entity/LightningBolt.java -index 0db0d67f9ac15372becc1166c37f7f0aede4a4da..a9e70484b01fc082ea25d43d1d42833499b5e41d 100644 ---- a/src/main/java/net/minecraft/world/entity/LightningBolt.java -+++ b/src/main/java/net/minecraft/world/entity/LightningBolt.java -@@ -45,6 +45,7 @@ public class LightningBolt extends Entity { - private ServerPlayer cause; - private final Set hitEntities = Sets.newHashSet(); - private int blocksSetOnFire; -+ public boolean isEffect; // Paper - Properly handle lightning effects api - - public LightningBolt(EntityType type, Level world) { - super(type, world); -@@ -85,7 +86,7 @@ public class LightningBolt extends Entity { - @Override - public void tick() { - super.tick(); -- if (this.life == 2) { -+ if (!this.isEffect && this.life == 2) { // Paper - Properly handle lightning effects api - if (this.level().isClientSide()) { - this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.WEATHER, 10000.0F, 0.8F + this.random.nextFloat() * 0.2F, false); - this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F, false); -@@ -132,7 +133,7 @@ public class LightningBolt extends Entity { - } - } - -- if (this.life >= 0 && !this.visualOnly) { // CraftBukkit - add !this.visualOnly -+ if (this.life >= 0 && !this.isEffect) { // CraftBukkit - add !this.visualOnly // Paper - Properly handle lightning effects api - if (!(this.level() instanceof ServerLevel)) { - this.level().setSkyFlashTime(2); - } else if (!this.visualOnly) { -@@ -161,7 +162,7 @@ public class LightningBolt extends Entity { - } - - private void spawnFire(int spreadAttempts) { -- if (!this.visualOnly && !this.level().isClientSide && this.level().getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) { -+ if (!this.visualOnly && !this.isEffect && !this.level().isClientSide && this.level().getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) { // Paper - Properly handle lightning effects api - BlockPos blockposition = this.blockPosition(); - BlockState iblockdata = BaseFireBlock.getState(this.level(), blockposition); - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 3a941be852a2dd5114ca9673597e84e3813d6ee2..da5f8dc6b4fce78f5f6278396d58474a5cc13f12 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -740,7 +740,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - LightningBolt lightning = EntityType.LIGHTNING_BOLT.create(this.world); - lightning.moveTo(loc.getX(), loc.getY(), loc.getZ()); -- lightning.setVisualOnly(isVisual); -+ lightning.isEffect = isVisual; // Paper - Properly handle lightning effects api - this.world.strikeLightning(lightning, LightningStrikeEvent.Cause.CUSTOM); - return (LightningStrike) lightning.getBukkitEntity(); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java -index 6fed8075aa75e3852dc826a45ca44603c0446a56..e9f471e60af0725ec34e2985d63ae9ea9f88590a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java -@@ -13,7 +13,7 @@ public class CraftLightningStrike extends CraftEntity implements LightningStrike - - @Override - public boolean isEffect() { -- return this.getHandle().visualOnly; -+ return this.getHandle().isEffect; // Paper - Properly handle lightning effects api - } - - public int getFlashes() { diff --git a/patches/server/0968-Add-Structure-check-API.patch b/patches/server/0968-Add-Structure-check-API.patch new file mode 100644 index 000000000000..b291fdf60419 --- /dev/null +++ b/patches/server/0968-Add-Structure-check-API.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 27 Mar 2023 10:20:00 -0700 +Subject: [PATCH] Add Structure check API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 263d60cb498f601b1381124127c084efe424409e..18dbaf5d73898756086b94d06d08f8384224709a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -236,6 +236,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + }; + } + // Paper end ++ // Paper start - structure check API ++ @Override ++ public boolean hasStructureAt(final io.papermc.paper.math.Position position, final Structure structure) { ++ return this.world.structureManager().getStructureWithPieceAt(io.papermc.paper.util.MCUtil.toBlockPos(position), net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.STRUCTURE, CraftNamespacedKey.toMinecraft(structure.getKey()))).isValid(); ++ } ++ // Paper end + + private static final Random rand = new Random(); + diff --git a/patches/server/0968-Add-hand-to-fish-event-for-all-player-interactions.patch b/patches/server/0968-Add-hand-to-fish-event-for-all-player-interactions.patch deleted file mode 100644 index 92577eb2a191..000000000000 --- a/patches/server/0968-Add-hand-to-fish-event-for-all-player-interactions.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: booky10 -Date: Mon, 3 Jul 2023 01:55:32 +0200 -Subject: [PATCH] Add hand to fish event for all player interactions - - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -index f3694d432e280cace281eda95d8c2d4dd5d6930a..2378850b5d9e93ad0a52976de65ef35e29703a2c 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -@@ -474,7 +474,15 @@ public class FishingHook extends Projectile { - @Override - public void readAdditionalSaveData(CompoundTag nbt) {} - -+ // Paper start - Add hand parameter to PlayerFishEvent -+ @Deprecated -+ @io.papermc.paper.annotation.DoNotUse - public int retrieve(ItemStack usedItem) { -+ return this.retrieve(net.minecraft.world.InteractionHand.MAIN_HAND, usedItem); -+ } -+ -+ public int retrieve(net.minecraft.world.InteractionHand hand, ItemStack usedItem) { -+ // Paper end - Add hand parameter to PlayerFishEvent - net.minecraft.world.entity.player.Player entityhuman = this.getPlayerOwner(); - - if (!this.level().isClientSide && entityhuman != null && !this.shouldStopFishing(entityhuman)) { -@@ -482,7 +490,7 @@ public class FishingHook extends Projectile { - - if (this.hookedIn != null) { - // CraftBukkit start -- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), this.hookedIn.getBukkitEntity(), (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_ENTITY); -+ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), this.hookedIn.getBukkitEntity(), (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.CAUGHT_ENTITY); // Paper - Add hand parameter to PlayerFishEvent - this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); - - if (playerFishEvent.isCancelled()) { -@@ -511,7 +519,7 @@ public class FishingHook extends Projectile { - } - // Paper end - // CraftBukkit start -- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem != null ? entityitem.getBukkitEntity() : null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH); // Paper - entityitem may be null -+ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem != null ? entityitem.getBukkitEntity() : null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.CAUGHT_FISH); // Paper - entityitem may be null // Paper - Add hand parameter to PlayerFishEvent - playerFishEvent.setExpToDrop(this.random.nextInt(6) + 1); - this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); - -@@ -545,7 +553,7 @@ public class FishingHook extends Projectile { - - if (this.onGround()) { - // CraftBukkit start -- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.IN_GROUND); -+ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.IN_GROUND); // Paper - Add hand parameter to PlayerFishEvent - this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); - - if (playerFishEvent.isCancelled()) { -@@ -556,7 +564,7 @@ public class FishingHook extends Projectile { - } - // CraftBukkit start - if (i == 0) { -- PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.REEL_IN); -+ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.REEL_IN); // Paper - Add hand parameter to PlayerFishEvent - this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent); - if (playerFishEvent.isCancelled()) { - return 0; -diff --git a/src/main/java/net/minecraft/world/item/FishingRodItem.java b/src/main/java/net/minecraft/world/item/FishingRodItem.java -index b9aca584c9765e995d1f8b9b2e45e5257fb6ab9d..95144f0ea5e99285c0a82b9d2e60766b785a236d 100644 ---- a/src/main/java/net/minecraft/world/item/FishingRodItem.java -+++ b/src/main/java/net/minecraft/world/item/FishingRodItem.java -@@ -29,7 +29,7 @@ public class FishingRodItem extends Item implements Vanishable { - - if (user.fishing != null) { - if (!world.isClientSide) { -- i = user.fishing.retrieve(itemstack); -+ i = user.fishing.retrieve(hand, itemstack); // Paper - Add hand parameter to PlayerFishEvent - itemstack.hurtAndBreak(i, user, (entityhuman1) -> { - entityhuman1.broadcastBreakEvent(hand); - }); diff --git a/patches/server/0975-Fix-CraftMetaItem-getAttributeModifier-duplication-c.patch b/patches/server/0969-Fix-CraftMetaItem-getAttributeModifier-duplication-c.patch similarity index 100% rename from patches/server/0975-Fix-CraftMetaItem-getAttributeModifier-duplication-c.patch rename to patches/server/0969-Fix-CraftMetaItem-getAttributeModifier-duplication-c.patch diff --git a/patches/server/0969-Fix-several-issues-with-EntityBreedEvent.patch b/patches/server/0969-Fix-several-issues-with-EntityBreedEvent.patch deleted file mode 100644 index 27e4615bf02f..000000000000 --- a/patches/server/0969-Fix-several-issues-with-EntityBreedEvent.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 15 Dec 2022 00:14:44 -0800 -Subject: [PATCH] Fix several issues with EntityBreedEvent - -Upstream did not account for different hands when storing -the breed item for later use in the event. Also they only -stored a reference to the stack, not a copy so if the stack -changed after love mode was started, the breed item in the event -also changed. Also in several places, the breed item was stored after -it was decreased by one to consume the item. - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java -index 907ed82fea71254d6624eda878e2668cd26422a7..081d1e38b7b1f286e138b0981aaa760e58761215 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Animal.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java -@@ -152,8 +152,9 @@ public abstract class Animal extends AgeableMob { - int i = this.getAge(); - - if (!this.level().isClientSide && i == 0 && this.canFallInLove()) { -+ final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying - this.usePlayerItem(player, hand, itemstack); -- this.setInLove(player); -+ this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying - return InteractionResult.SUCCESS; - } - -@@ -182,10 +183,18 @@ public abstract class Animal extends AgeableMob { - return this.inLove <= 0; - } - -+ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Fix EntityBreedEvent copying - public void setInLove(@Nullable Player player) { -+ // Paper start - Fix EntityBreedEvent copying -+ this.setInLove(player, null); -+ } -+ public void setInLove(@Nullable Player player, @Nullable ItemStack breedItemCopy) { -+ if (breedItemCopy != null) this.breedItem = breedItemCopy; -+ // Paper end - Fix EntityBreedEvent copying - // CraftBukkit start - EntityEnterLoveModeEvent entityEnterLoveModeEvent = CraftEventFactory.callEntityEnterLoveModeEvent(player, this, 600); - if (entityEnterLoveModeEvent.isCancelled()) { -+ this.breedItem = null; // Paper - Fix EntityBreedEvent copying; clear if cancelled - return; - } - this.inLove = entityEnterLoveModeEvent.getTicksInLove(); -@@ -193,7 +202,7 @@ public abstract class Animal extends AgeableMob { - if (player != null) { - this.loveCause = player.getUUID(); - } -- this.breedItem = player.getInventory().getSelected(); // CraftBukkit -+ // Paper - Fix EntityBreedEvent copying; set breed item in better place - - this.level().broadcastEntityEvent(this, (byte) 18); - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java -index f783fe169141d33e8569ec7f5d71985b74bdbcb6..be554dbaa9900207753e4f67f0ba402333e21338 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java -@@ -649,8 +649,9 @@ public class Panda extends Animal { - this.usePlayerItem(player, hand, itemstack); - this.ageUp((int) ((float) (-this.getAge() / 20) * 0.1F), true); - } else if (!this.level().isClientSide && this.getAge() == 0 && this.canFallInLove()) { -+ final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying - this.usePlayerItem(player, hand, itemstack); -- this.setInLove(player); -+ this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying - } else { - if (this.level().isClientSide || this.isSitting() || this.isInWater()) { - return InteractionResult.PASS; -diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -index e89f454fe178483a7db381591a4a345ac24db2b8..f693d4d6a6a3c3d31c2d85ceb5b5b01366c970a1 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -+++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -@@ -389,7 +389,7 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Saddl - - boolean bl2 = this.isTamed() && this.getAge() == 0 && this.canFallInLove(); - if (bl2) { -- this.setInLove(player); -+ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying - } - - boolean bl3 = this.isBaby(); -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -index 94dd97662ba07689fbfa16ef5c7d99fe12ce83de..815eb15086976b8f9e03bf8182d9ed50aec14720 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -@@ -513,7 +513,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - b0 = 5; - if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) { - flag = true; -- this.setInLove(player); -+ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying - } - } else if (item.is(Items.GOLDEN_APPLE) || item.is(Items.ENCHANTED_GOLDEN_APPLE)) { - f = 10.0F; -@@ -521,7 +521,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - b0 = 10; - if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) { - flag = true; -- this.setInLove(player); -+ this.setInLove(player, item.copy()); // Paper - Fix EntityBreedEvent copying - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -index 9b5b894d43f25566ab9c3698705e978ab823a0d2..6623674136b0f865d5b3d7a10d3bf05793b82f87 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -@@ -191,7 +191,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder +Date: Tue, 22 Mar 2022 09:34:41 -0700 +Subject: [PATCH] Restore vanilla entity drops behavior + +Instead of just tracking the itemstacks, this tracks with it, the +action to take with that itemstack to apply the correct logic +on dropping the item instead of generalizing it for all dropped +items like CB does. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 7272dc058c575efee5ac2643ce41b7d12e346e89..ae5a2136a0e266d4c35190f5d33552994c842786 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -891,22 +891,20 @@ public class ServerPlayer extends Player { + if (this.isRemoved()) { + return; + } +- java.util.List loot = new java.util.ArrayList(this.getInventory().getContainerSize()); ++ List loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); // Paper - Restore vanilla drops behavior + boolean keepInventory = this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator(); + + if (!keepInventory) { + for (ItemStack item : this.getInventory().getContents()) { + if (!item.isEmpty() && !EnchantmentHelper.hasVanishingCurse(item)) { +- loot.add(CraftItemStack.asCraftMirror(item)); ++ loot.add(new DefaultDrop(item, stack -> this.drop(stack, true, false, false))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event) + } + } + } + if (this.shouldDropLoot() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false + // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) + this.dropFromLootTable(damageSource, this.lastHurtByPlayerTime > 0); +- for (org.bukkit.inventory.ItemStack item : this.drops) { +- loot.add(item); +- } ++ loot.addAll(this.drops); // Paper + this.drops.clear(); // SPIGOT-5188: make sure to clear + } // Paper - fix player loottables running when mob loot gamerule is false + +@@ -2389,8 +2387,8 @@ public class ServerPlayer extends Player { + } + + @Override +- public ItemEntity drop(ItemStack stack, boolean throwRandomly, boolean retainOwnership) { +- ItemEntity entityitem = super.drop(stack, throwRandomly, retainOwnership); ++ public ItemEntity drop(ItemStack stack, boolean throwRandomly, boolean retainOwnership, boolean callDropEvent) { // Paper - Restore vanilla drops behavior; override method with most params ++ ItemEntity entityitem = super.drop(stack, throwRandomly, retainOwnership, callDropEvent); // Paper - Restore vanilla drops behavior; override method with most params + + if (entityitem == null) { + return null; +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f49a1e0ff2c81e9a714458e8c4d3e7220522f813..8c42792ea5cb0fe5d1da7467875efda9be9040e1 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2480,6 +2480,25 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + + @Nullable + public ItemEntity spawnAtLocation(ItemStack stack, float yOffset) { ++ // Paper start - Restore vanilla drops behavior ++ return this.spawnAtLocation(stack, yOffset, null); ++ } ++ public record DefaultDrop(Item item, org.bukkit.inventory.ItemStack stack, @Nullable java.util.function.Consumer dropConsumer) { ++ public DefaultDrop(final ItemStack stack, final java.util.function.Consumer dropConsumer) { ++ this(stack.getItem(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), dropConsumer); ++ } ++ ++ public void runConsumer(final org.bukkit.World fallbackWorld, final Location fallbackLoc) { ++ if (this.dropConsumer == null || org.bukkit.craftbukkit.inventory.CraftItemType.bukkitToMinecraft(this.stack.getType()) != this.item) { ++ fallbackWorld.dropItem(fallbackLoc, this.stack); ++ } else { ++ this.dropConsumer.accept(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(this.stack)); ++ } ++ } ++ } ++ @Nullable ++ public ItemEntity spawnAtLocation(ItemStack stack, float yOffset, @Nullable java.util.function.Consumer delayedAddConsumer) { ++ // Paper end - Restore vanilla drops behavior + if (stack.isEmpty()) { + return null; + } else if (this.level().isClientSide) { +@@ -2487,14 +2506,21 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } else { + // CraftBukkit start - Capture drops for death event + if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) { +- ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack)); // Paper - mirror so we can destroy it later ++ // Paper start - Restore vanilla drops behavior ++ ((net.minecraft.world.entity.LivingEntity) this).drops.add(new net.minecraft.world.entity.Entity.DefaultDrop(stack, itemStack -> { ++ ItemEntity itemEntity = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), itemStack); // stack is copied before consumer ++ itemEntity.setDefaultPickUpDelay(); ++ this.level.addFreshEntity(itemEntity); ++ if (delayedAddConsumer != null) delayedAddConsumer.accept(itemEntity); ++ })); ++ // Paper end - Restore vanilla drops behavior + return null; + } + // CraftBukkit end + ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original + stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe + +- entityitem.setDefaultPickUpDelay(); ++ entityitem.setDefaultPickUpDelay(); // Paper - diff on change (in dropConsumer) + // Paper start - Call EntityDropItemEvent + return this.spawnAtLocation(entityitem); + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index fe95119a8d26887f4e9cd1b9cd1299515835e958..728136663685882c6fd2d94900a27368c7c340f1 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -254,7 +254,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + // CraftBukkit start + public int expToDrop; + public boolean forceDrops; +- public ArrayList drops = new ArrayList(); ++ public ArrayList drops = new ArrayList<>(); // Paper - Restore vanilla drops behavior + public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes; + public boolean collides = true; + public Set collidableExemptions = new HashSet<>(); +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index 45906d273e6d6ec20cf44b4d07efdac68752ee9b..ac9eaeaf7df1e84ee588f371628c0a10784d50bc 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -534,10 +534,10 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob + @Override + protected void dropCustomDeathLoot(DamageSource source, int lootingMultiplier, boolean allowDrops) { + super.dropCustomDeathLoot(source, lootingMultiplier, allowDrops); +- ItemEntity entityitem = this.spawnAtLocation((ItemLike) Items.NETHER_STAR); ++ ItemEntity entityitem = this.spawnAtLocation(new net.minecraft.world.item.ItemStack(Items.NETHER_STAR), 0, ItemEntity::setExtendedLifetime); // Paper - Restore vanilla drops behavior; spawnAtLocation returns null so modify the item entity with a consumer + + if (entityitem != null) { +- entityitem.setExtendedLifetime(); ++ entityitem.setExtendedLifetime(); // Paper - diff on change + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index e3412f9dd86dddd241bea8f6dcaeed77a7e67f08..6dfcc296ff7e59ecbebc5446973fabc9eff3cb43 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -610,7 +610,7 @@ public class ArmorStand extends LivingEntity { + itemstack.setHoverName(this.getCustomName()); + } + +- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops ++ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior + return this.brokenByAnything(damageSource); // Paper + } + +@@ -624,7 +624,7 @@ public class ArmorStand extends LivingEntity { + for (i = 0; i < this.handItems.size(); ++i) { + itemstack = (ItemStack) this.handItems.get(i); + if (!itemstack.isEmpty()) { +- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe ++ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly + this.handItems.set(i, ItemStack.EMPTY); + } + } +@@ -632,7 +632,7 @@ public class ArmorStand extends LivingEntity { + for (i = 0; i < this.armorItems.size(); ++i) { + itemstack = (ItemStack) this.armorItems.get(i); + if (!itemstack.isEmpty()) { +- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe ++ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly + this.armorItems.set(i, ItemStack.EMPTY); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 0937d70b575b12bdfc0f643648088fa4cf13c230..1a7c002d1a84a52d91d4753ef4d78a8688f6d6ad 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -963,17 +963,23 @@ public class CraftEventFactory { + } + + public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim) { +- return CraftEventFactory.callEntityDeathEvent(victim, new ArrayList(0)); ++ return CraftEventFactory.callEntityDeathEvent(victim, new ArrayList<>(0)); // Paper - Restore vanilla drops behavior + } + +- public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops) { ++ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops) { // Paper - Restore vanilla drops behavior + // Paper start + return CraftEventFactory.callEntityDeathEvent(victim, drops, com.google.common.util.concurrent.Runnables.doNothing()); + } +- public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops, Runnable lootCheck) { ++ ++ private static final java.util.function.Function FROM_FUNCTION = stack -> { ++ if (stack == null) return null; ++ return new Entity.DefaultDrop(CraftItemType.bukkitToMinecraft(stack.getType()), stack, null); ++ }; ++ ++ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops, Runnable lootCheck) { // Paper + // Paper end + CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); +- EntityDeathEvent event = new EntityDeathEvent(entity, drops, victim.getExpReward()); ++ EntityDeathEvent event = new EntityDeathEvent(entity, new io.papermc.paper.util.TransformingRandomAccessList<>(drops, Entity.DefaultDrop::stack, FROM_FUNCTION), victim.getExpReward()); // Paper - Restore vanilla drops behavior + populateFields(victim, event); // Paper - make cancellable + CraftWorld world = (CraftWorld) entity.getWorld(); + Bukkit.getServer().getPluginManager().callEvent(event); +@@ -987,19 +993,23 @@ public class CraftEventFactory { + victim.expToDrop = event.getDroppedExp(); + lootCheck.run(); // Paper - advancement triggers before destroying items + +- for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { ++ // Paper start - Restore vanilla drops behavior ++ for (Entity.DefaultDrop drop : drops) { ++ if (drop == null) continue; ++ final org.bukkit.inventory.ItemStack stack = drop.stack(); ++ // Paper end - Restore vanilla drops behavior + if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; + +- world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS ++ drop.runConsumer(world, entity.getLocation()); // Paper - Restore vanilla drops behavior + if (stack instanceof CraftItemStack) stack.setAmount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe, but don't nuke bukkit stacks of manually added items + } + + return event; + } + +- public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, List drops, net.kyori.adventure.text.Component deathMessage, boolean keepInventory) { // Paper - Adventure ++ public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, List drops, net.kyori.adventure.text.Component deathMessage, boolean keepInventory) { // Paper - Adventure & Restore vanilla drops behavior + CraftPlayer entity = victim.getBukkitEntity(); +- PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage); ++ PlayerDeathEvent event = new PlayerDeathEvent(entity, new io.papermc.paper.util.TransformingRandomAccessList<>(drops, Entity.DefaultDrop::stack, FROM_FUNCTION), victim.getExpReward(), 0, deathMessage); // Paper - Restore vanilla drops behavior + event.setKeepInventory(keepInventory); + event.setKeepLevel(victim.keepLevel); // SPIGOT-2222: pre-set keepLevel + populateFields(victim, event); // Paper - make cancellable +@@ -1018,10 +1028,14 @@ public class CraftEventFactory { + victim.expToDrop = event.getDroppedExp(); + victim.newExp = event.getNewExp(); + +- for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { ++ // Paper start - Restore vanilla drops behavior ++ for (Entity.DefaultDrop drop : drops) { ++ if (drop == null) continue; ++ final org.bukkit.inventory.ItemStack stack = drop.stack(); ++ // Paper end - Restore vanilla drops behavior + if (stack == null || stack.getType() == Material.AIR) continue; + +- world.dropItem(entity.getLocation(), stack); ++ drop.runConsumer(world, entity.getLocation()); // Paper - Restore vanilla drops behavior + } + + return event; diff --git a/patches/server/0971-Dont-resend-blocks-on-interactions.patch b/patches/server/0971-Dont-resend-blocks-on-interactions.patch new file mode 100644 index 000000000000..8a8a5bb4ed2a --- /dev/null +++ b/patches/server/0971-Dont-resend-blocks-on-interactions.patch @@ -0,0 +1,171 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Tue, 27 Jun 2023 21:09:11 -0400 +Subject: [PATCH] Dont resend blocks on interactions + +In general, the client now has an acknowledgment system which will prevent block changes made by the client to be reverted correctly. + +It should be noted that this system does not yet support block entities, so those still need to resynced when needed. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index d0ca98c3f9ea5c8cb1053da6b17e9a90c86b3ae7..5063eb6d4a24600262c32d2c9eb5fb5bf8fa354e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -199,7 +199,7 @@ public class ServerPlayerGameMode { + PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); + if (event.isCancelled()) { + // Let the client know the block still exists +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks + // Update any tile entity data for this block + capturedBlockEntity = true; // Paper - Send block entities after destroy prediction + return; +@@ -214,7 +214,7 @@ public class ServerPlayerGameMode { + // Spigot start - handle debug stick left click for non-creative + if (this.player.getMainHandItem().is(net.minecraft.world.item.Items.DEBUG_STICK) + && ((net.minecraft.world.item.DebugStickItem) net.minecraft.world.item.Items.DEBUG_STICK).handleInteraction(this.player, this.level.getBlockState(pos), this.level, pos, false, this.player.getMainHandItem())) { +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block + return; + } + // Spigot end +@@ -232,15 +232,17 @@ public class ServerPlayerGameMode { + // CraftBukkit start - Swings at air do *NOT* exist. + if (event.useInteractedBlock() == Event.Result.DENY) { + // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door. +- BlockState data = this.level.getBlockState(pos); +- if (data.getBlock() instanceof DoorBlock) { +- // For some reason *BOTH* the bottom/top part have to be marked updated. +- boolean bottom = data.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below())); +- } else if (data.getBlock() instanceof TrapDoorBlock) { +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); +- } ++ // Paper start - Don't resync blocks ++ //BlockState data = this.level.getBlockState(pos); ++ //if (data.getBlock() instanceof DoorBlock) { ++ // // For some reason *BOTH* the bottom/top part have to be marked updated. ++ // boolean bottom = data.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below())); ++ //} else if (data.getBlock() instanceof TrapDoorBlock) { ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ //} ++ // Paper end - Don't resync blocks + } else if (!iblockdata.isAir()) { + iblockdata.attack(this.level, pos, this.player); + f = iblockdata.getDestroyProgress(this.player, this.player.level(), pos); +@@ -249,7 +251,7 @@ public class ServerPlayerGameMode { + if (event.useItemInHand() == Event.Result.DENY) { + // If we 'insta destroyed' then the client needs to be informed. + if (f > 1.0f) { +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks + } + return; + } +@@ -257,7 +259,7 @@ public class ServerPlayerGameMode { + + if (blockEvent.isCancelled()) { + // Let the client know the block still exists +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block + return; + } + +@@ -348,7 +350,7 @@ public class ServerPlayerGameMode { + + // Tell client the block is gone immediately then process events + // Don't tell the client if its a creative sword break because its not broken! +- if (this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { ++ if (false && this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { // Paper - Don't resync block + ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, Blocks.AIR.defaultBlockState()); + this.player.connection.send(packet); + } +@@ -374,13 +376,15 @@ public class ServerPlayerGameMode { + if (isSwordNoBreak) { + return false; + } ++ // Paper start - Don't resync blocks + // Let the client know the block still exists +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); ++ //this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); + + // Brute force all possible updates +- for (Direction dir : Direction.values()) { +- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos.relative(dir))); +- } ++ //for (Direction dir : Direction.values()) { ++ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos.relative(dir))); ++ //} ++ // Paper end - Don't resync blocks + + // Update any tile entity data for this block + if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction +@@ -537,16 +541,18 @@ public class ServerPlayerGameMode { + if (event.useInteractedBlock() == Event.Result.DENY) { + // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door. + if (iblockdata.getBlock() instanceof DoorBlock) { +- boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; +- player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below())); ++ // Paper start - Don't resync blocks ++ // boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; ++ // player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below())); ++ // Paper end - Don't resync blocks + } else if (iblockdata.getBlock() instanceof CakeBlock) { + player.getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake + } else if (this.interactItemStack.getItem() instanceof DoubleHighBlockItem) { + // send a correcting update to the client, as it already placed the upper half of the bisected item +- player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.relative(hitResult.getDirection()).above())); ++ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.relative(hitResult.getDirection()).above())); // Paper - Don't resync blocks + + // send a correcting update to the client for the block above as well, this because of replaceable blocks (such as grass, sea grass etc) +- player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); ++ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); // Paper - Don't resync blocks + // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method + } else if (iblockdata.is(Blocks.STRUCTURE_BLOCK) || iblockdata.getBlock() instanceof net.minecraft.world.level.block.CommandBlock) { + player.connection.send(new net.minecraft.network.protocol.game.ClientboundContainerClosePacket(this.player.containerMenu.containerId)); +diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java +index 4b9e726e6ac255e743479d5c2e0cdb98464399a4..6371f326fc86cfc53e39bf8ed13b646f7705fbbc 100644 +--- a/src/main/java/net/minecraft/world/item/BucketItem.java ++++ b/src/main/java/net/minecraft/world/item/BucketItem.java +@@ -77,7 +77,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { + PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) world, user, blockposition, blockposition, movingobjectpositionblock.getDirection(), itemstack, dummyFluid.getItem(), hand); + + if (event.isCancelled()) { +- ((ServerPlayer) user).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-5163 (see PlayerInteractManager) ++ // ((ServerPlayer) user).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-5163 (see PlayerInteractManager) // Paper - Don't resend blocks + ((ServerPlayer) user).getBukkitEntity().updateInventory(); // SPIGOT-4541 + return InteractionResultHolder.fail(itemstack); + } +@@ -186,7 +186,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { + if (flag2 && entityhuman != null) { + PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent((ServerLevel) world, entityhuman, blockposition, clicked, enumdirection, itemstack, enumhand); + if (event.isCancelled()) { +- ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-4238: needed when looking through entity ++ // ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-4238: needed when looking through entity // Paper - Don't resend blocks + ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 + return false; + } +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 9b4e20d4bfba2de08084f1d69cb2ebfff7455c14..06dc04a1fbb91a5a20d662aeee168b6a319551d0 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -458,10 +458,12 @@ public final class ItemStack { + world.preventPoiUpdated = false; + + // Brute force all possible updates +- BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition(); +- for (Direction dir : Direction.values()) { +- ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir))); +- } ++ // Paper start - Don't resync blocks ++ // BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition(); ++ // for (Direction dir : Direction.values()) { ++ // ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir))); ++ // } ++ // Paper end - Don't resync blocks + SignItem.openSign = null; // SPIGOT-6758 - Reset on early return + } else { + // Change the stack to its new contents if it hasn't been tampered with. diff --git a/patches/server/0978-add-more-scoreboard-API.patch b/patches/server/0972-add-more-scoreboard-API.patch similarity index 100% rename from patches/server/0978-add-more-scoreboard-API.patch rename to patches/server/0972-add-more-scoreboard-API.patch diff --git a/patches/server/0973-Don-t-fire-sync-events-during-worldgen.patch b/patches/server/0973-Don-t-fire-sync-events-during-worldgen.patch deleted file mode 100644 index 302a19d2bad5..000000000000 --- a/patches/server/0973-Don-t-fire-sync-events-during-worldgen.patch +++ /dev/null @@ -1,208 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 23 Nov 2023 10:33:25 -0800 -Subject: [PATCH] Don't fire sync events during worldgen - -Fixes EntityPotionEffectEvent -Fixes EntityPoseChangeEvent - -Asynchronous chunk generation provides an opportunity for things -to happen async that previously fired synchronous-only events. This -patch is for mitigating those issues by various methods. - -Also fixes correctly marking/clearing the entity generation flag. -This patch sets the generation flag anytime an entity is created -via StructureTemplate before loading from NBT to catch uses of -the flag during the loading logic. This patch clears the generation -flag from an entity when added to a ServerLevel for the situation -where generation happened directly to a ServerLevel and the -entity still has the flag set. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 0e7811ae2a8731ae7475aabd2322e56ab364bc32..b5d6a7eaa24d9968e159d77a4295be00332a5457 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1220,6 +1220,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - // CraftBukkit start - private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { - org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot -+ entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process - // Paper start - extra debug info - if (entity.valid) { - MinecraftServer.LOGGER.error("Attempted Double World add on {}", entity, new Throwable()); -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index ce6bbf5b56925c501804bff4626da8b7b2c8ff63..9e1d0e46e94cc72705af5e1d01de6bb7bea40107 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -624,7 +624,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - if (pose == this.getPose()) { - return; - } -- this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), Pose.values()[pose.ordinal()])); -+ // Paper start - Don't fire sync event during generation -+ if (!this.generation) { -+ this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), Pose.values()[pose.ordinal()])); -+ } -+ // Paper end - Don't fire sync event during generation - // CraftBukkit end - this.entityData.set(Entity.DATA_POSE, pose); - } -diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index 656c68b37bc25d6b77f295f9efe0a81dd20b69c1..8ba573bb4099ee5b27b61f333e72d794c48d5f29 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -584,9 +584,15 @@ public class EntityType implements FeatureElement, EntityTypeT - } - - public static Optional create(CompoundTag nbt, Level world) { -+ // Paper start - Don't fire sync event during generation -+ return create(nbt, world, false); -+ } -+ public static Optional create(CompoundTag nbt, Level world, boolean generation) { -+ // Paper end - Don't fire sync event during generation - return Util.ifElse(EntityType.by(nbt).map((entitytypes) -> { - return entitytypes.create(world); - }), (entity) -> { -+ if (generation) entity.generation = true; // Paper - Don't fire sync event during generation - entity.load(nbt); - }, () -> { - EntityType.LOGGER.warn("Skipping Entity with id {}", nbt.getString("id")); -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 5a47bffc059df227352a497881ebf1364a6c0019..1d1fe8e8c23fc177960fb78d2e38ea9846fc3938 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1133,6 +1133,11 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) { -+ // Paper start - Don't fire sync event during generation -+ return this.addEffect(mobeffect, entity, cause, true); -+ } -+ public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) { -+ // Paper end - Don't fire sync event during generation - // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API - if (this.isTickingEffects) { - this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause)); -@@ -1152,10 +1157,13 @@ public abstract class LivingEntity extends Entity implements Attackable { - override = new MobEffectInstance(mobeffect1).update(mobeffect); - } - -+ if (fireEvent) { // Paper - Don't fire sync event during generation - EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect1, mobeffect, cause, override); -+ override = event.isOverride(); // Paper - Don't fire sync event during generation - if (event.isCancelled()) { - return false; - } -+ } // Paper - Don't fire sync event during generation - // CraftBukkit end - - if (mobeffect1 == null) { -@@ -1163,7 +1171,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.onEffectAdded(mobeffect, entity); - flag = true; - // CraftBukkit start -- } else if (event.isOverride()) { -+ } else if (override) { // Paper - Don't fire sync event during generation - mobeffect1.update(mobeffect); - this.onEffectUpdated(mobeffect1, true, entity); - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java -index d90da2f9e4d6214577bc81bd6c70ba8593788898..ffa4f34d964fbcc53e2dfe11677832db21a6eb93 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Spider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java -@@ -182,7 +182,7 @@ public class Spider extends Monster { - MobEffect mobeffectlist = entityspider_groupdataspider.effect; - - if (mobeffectlist != null) { -- this.addEffect(new MobEffectInstance(mobeffectlist, -1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN); // CraftBukkit -+ this.addEffect(new MobEffectInstance(mobeffectlist, -1), null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN, world instanceof net.minecraft.server.level.ServerLevel); // CraftBukkit // Paper - Don't fire sync event during generation; only if this is happening in a ServerLevel - } - } - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -index ae8a42261337bf736d0cc1bbe18da2b773417ca4..471e8493622c89d44a82f42f135cb308c9e0fbfe 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -@@ -518,7 +518,7 @@ public class StructureTemplate { - private static Optional createEntityIgnoreException(ServerLevelAccessor world, CompoundTag nbt) { - // CraftBukkit start - // try { -- return EntityType.create(nbt, world.getLevel()); -+ return EntityType.create(nbt, world.getLevel(), true); // Paper - Don't fire sync event during generation - // } catch (Exception exception) { - // return Optional.empty(); - // } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -index e37c2d82ed606cbfe00c152b08c3ab99ac751f69..7ed861cd67889e525ab4987c0afed245aca08833 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -@@ -93,15 +93,17 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { - return this.handle.getLevel(); - } - -- @Override -- public void addFreshEntityWithPassengers(Entity arg0, CreatureSpawnEvent.SpawnReason arg1) { -- this.handle.addFreshEntityWithPassengers(arg0, arg1); -- } -- -- @Override -- public void addFreshEntityWithPassengers(Entity entity) { -- this.handle.addFreshEntityWithPassengers(entity); -- } -+ // Paper start - Don't fire sync event during generation; don't override these methods so all entities are run through addFreshEntity -+ // @Override -+ // public void addFreshEntityWithPassengers(Entity arg0, CreatureSpawnEvent.SpawnReason arg1) { -+ // this.handle.addFreshEntityWithPassengers(arg0, arg1); -+ // } -+ // -+ // @Override -+ // public void addFreshEntityWithPassengers(Entity entity) { -+ // this.handle.addFreshEntityWithPassengers(entity); -+ // } -+ // Paper end - Don't fire sync event during generation; don't override these methods - - @Override - public ServerLevel getMinecraftWorld() { -diff --git a/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java -index b4b297945fb601701aac845d09e88fb74b09c3fa..7482dfe64458320d44089c0778591694202e9f70 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/TransformerGeneratorAccess.java -@@ -39,21 +39,23 @@ public class TransformerGeneratorAccess extends DelegatedGeneratorAccess { - return super.addFreshEntity(arg0, arg1); - } - -- @Override -- public void addFreshEntityWithPassengers(Entity entity) { -- if (this.structureTransformer != null && !this.structureTransformer.transformEntity(entity)) { -- return; -- } -- super.addFreshEntityWithPassengers(entity); -- } -- -- @Override -- public void addFreshEntityWithPassengers(Entity arg0, SpawnReason arg1) { -- if (this.structureTransformer != null && !this.structureTransformer.transformEntity(arg0)) { -- return; -- } -- super.addFreshEntityWithPassengers(arg0, arg1); -- } -+ // Paper start - Don't fire sync event during generation; don't override these methods so all entities are run through addFreshEntity -+ // @Override -+ // public void addFreshEntityWithPassengers(Entity entity) { -+ // if (this.structureTransformer != null && !this.structureTransformer.transformEntity(entity)) { -+ // return; -+ // } -+ // super.addFreshEntityWithPassengers(entity); -+ // } -+ // -+ // @Override -+ // public void addFreshEntityWithPassengers(Entity arg0, SpawnReason arg1) { -+ // if (this.structureTransformer != null && !this.structureTransformer.transformEntity(arg0)) { -+ // return; -+ // } -+ // super.addFreshEntityWithPassengers(arg0, arg1); -+ // } -+ // Paper end - Don't fire sync event during generation; don't override these methods - - public boolean setCraftBlock(BlockPos position, CraftBlockState craftBlockState, int i, int j) { - if (this.structureTransformer != null) { diff --git a/patches/server/0973-Improve-Registry.patch b/patches/server/0973-Improve-Registry.patch new file mode 100644 index 000000000000..b3e7a17ff64d --- /dev/null +++ b/patches/server/0973-Improve-Registry.patch @@ -0,0 +1,102 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 20 Dec 2023 02:03:05 -0800 +Subject: [PATCH] Improve Registry + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +index b12b99253543445475b73a1d3d7c6364856b49e8..4fc02698a9312496e7f9bce1c64f317374d2a42f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +@@ -131,6 +131,7 @@ public class CraftRegistry implements Registry { + + private final Class bukkitClass; + private final Map cache = new HashMap<>(); ++ private final Map byValue = new java.util.IdentityHashMap<>(); // Paper - improve Registry + private final net.minecraft.core.Registry minecraftRegistry; + private final BiFunction minecraftToBukkit; + private boolean init; +@@ -175,6 +176,7 @@ public class CraftRegistry implements Registry { + } + + this.cache.put(namespacedKey, bukkit); ++ this.byValue.put(bukkit, namespacedKey); // Paper - improve Registry + + return bukkit; + } +@@ -197,4 +199,11 @@ public class CraftRegistry implements Registry { + + return this.minecraftToBukkit.apply(namespacedKey, minecraft); + } ++ ++ // Paper start - improve Registry ++ @Override ++ public NamespacedKey getKey(final B value) { ++ return this.byValue.get(value); ++ } ++ // Paper end - improve Registry + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java +index 2e6b04a150eae4fbac7b4c6413d81b755ac87be2..6cce693a24e0b1b485832935d398fb26c91e0be0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java +@@ -34,6 +34,7 @@ public class CraftTrimMaterial implements TrimMaterial, Handleable this + " doesn't have a key"); // Paper + return this.key; + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java +index 5a570bae1262f768d86a6078bfded427294ed135..bf13fe2f858ee35c84c5a1f3fb2f7df61a62315b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java +@@ -34,6 +34,7 @@ public class CraftTrimPattern implements TrimPattern, Handleable this + " doesn't have a key"); // Paper + return this.key; + } + } +diff --git a/src/test/java/org/bukkit/registry/PerRegistryTest.java b/src/test/java/org/bukkit/registry/PerRegistryTest.java +index 1c4966520b6401e6571aa44d5934dfa280bc80e3..010de6fbb75eb5d51639695d260f916072fdb22d 100644 +--- a/src/test/java/org/bukkit/registry/PerRegistryTest.java ++++ b/src/test/java/org/bukkit/registry/PerRegistryTest.java +@@ -49,19 +49,22 @@ public class PerRegistryTest extends AbstractTestingBase { + + @ParameterizedTest + @MethodSource("data") +- public void testGet(Registry registry) { ++ public void testGet(Registry registry) { // Paper - improve Registry + registry.forEach(element -> { ++ NamespacedKey key = registry.getKey(element); // Paper - improve Registry ++ assertNotNull(key); // Paper - improve Registry + // Values in the registry should be referentially equal to what is returned with #get() + // This ensures that new instances are not created each time #get() is invoked +- assertSame(element, registry.get(element.getKey())); ++ assertSame(element, registry.get(key)); // Paper - improve Registry + }); + } + + @ParameterizedTest + @MethodSource("data") +- public void testMatch(Registry registry) { ++ public void testMatch(Registry registry) { // Paper - improve Registry + registry.forEach(element -> { +- NamespacedKey key = element.getKey(); ++ NamespacedKey key = registry.getKey(element); // Paper - improve Registry ++ assertNotNull(key); // Paper - improve Registry + + this.assertSameMatchWithKeyMessage(registry, element, key.toString()); // namespace:key + this.assertSameMatchWithKeyMessage(registry, element, key.getKey()); // key +@@ -72,7 +75,7 @@ public class PerRegistryTest extends AbstractTestingBase { + }); + } + +- private void assertSameMatchWithKeyMessage(Registry registry, Keyed element, String key) { ++ private void assertSameMatchWithKeyMessage(Registry registry, T element, String key) { // Paper - improve Registry + assertSame(element, registry.match(key), key); + } + diff --git a/patches/server/0974-Add-Structure-check-API.patch b/patches/server/0974-Add-Structure-check-API.patch deleted file mode 100644 index 2a2c03ae5778..000000000000 --- a/patches/server/0974-Add-Structure-check-API.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 27 Mar 2023 10:20:00 -0700 -Subject: [PATCH] Add Structure check API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index da5f8dc6b4fce78f5f6278396d58474a5cc13f12..4ae206af89a413edb09319fd4bce2a94c575c617 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -230,6 +230,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - }; - } - // Paper end -+ // Paper start - structure check API -+ @Override -+ public boolean hasStructureAt(final io.papermc.paper.math.Position position, final Structure structure) { -+ return this.world.structureManager().getStructureWithPieceAt(io.papermc.paper.util.MCUtil.toBlockPos(position), net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.STRUCTURE, CraftNamespacedKey.toMinecraft(structure.getKey()))).isValid(); -+ } -+ // Paper end - - private static final Random rand = new Random(); - diff --git a/patches/server/0974-Fix-NPE-on-null-loc-for-EntityTeleportEvent.patch b/patches/server/0974-Fix-NPE-on-null-loc-for-EntityTeleportEvent.patch new file mode 100644 index 000000000000..bf344a8f6bdf --- /dev/null +++ b/patches/server/0974-Fix-NPE-on-null-loc-for-EntityTeleportEvent.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 9 Dec 2023 19:15:59 -0800 +Subject: [PATCH] Fix NPE on null loc for EntityTeleportEvent + +EntityTeleportEvent#setTo is marked as nullable and so is the +getTo method. This fixes the handling of a null "to" location +by treating it the same as the event being cancelled. This is +already existing behavior for the EntityPortalEvent (which +extends EntityTeleportEvent). + +diff --git a/src/main/java/net/minecraft/server/commands/TeleportCommand.java b/src/main/java/net/minecraft/server/commands/TeleportCommand.java +index 3fec07b250a8f145e30c8c41888e47d2a3c902e1..2ddd033e1c3a2e5c8950b93c838491923803ccce 100644 +--- a/src/main/java/net/minecraft/server/commands/TeleportCommand.java ++++ b/src/main/java/net/minecraft/server/commands/TeleportCommand.java +@@ -169,9 +169,10 @@ public class TeleportCommand { + Location to = new Location(world.getWorld(), x, y, z, f2, f3); + EntityTeleportEvent event = new EntityTeleportEvent(target.getBukkitEntity(), target.getBukkitEntity().getLocation(), to); + world.getCraftServer().getPluginManager().callEvent(event); +- if (event.isCancelled()) { ++ if (event.isCancelled() || event.getTo() == null) { // Paper + return; + } ++ to = event.getTo(); // Paper - actually track new location + + x = to.getX(); + y = to.getY(); +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 728136663685882c6fd2d94900a27368c7c340f1..4591011a142f33f0c0ff84a2765cededde0e0c57 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -4193,7 +4193,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (!(this instanceof ServerPlayer)) { + EntityTeleportEvent teleport = new EntityTeleportEvent(this.getBukkitEntity(), new Location(this.level().getWorld(), d3, d4, d5), new Location(this.level().getWorld(), d0, d6, d2)); + this.level().getCraftServer().getPluginManager().callEvent(teleport); +- if (!teleport.isCancelled()) { ++ if (!teleport.isCancelled() && teleport.getTo() != null) { // Paper + Location to = teleport.getTo(); + this.teleportTo(to.getX(), to.getY(), to.getZ()); + } else { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java +index 8e2f7e2385588224018f7f94ed9686415bc91deb..c0da573e3818a1dd2c1ef5a61c7cb34934b0a252 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java +@@ -129,7 +129,7 @@ public class FollowOwnerGoal extends Goal { + } else { + // CraftBukkit start + EntityTeleportEvent event = CraftEventFactory.callEntityTeleportEvent(this.tamable, (double) x + 0.5D, (double) y, (double) z + 0.5D); +- if (event.isCancelled()) { ++ if (event.isCancelled() || event.getTo() == null) { // Paper + return false; + } + Location to = event.getTo(); +diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java +index 06ab07fb5d8d0e2f97325890218a11fef551a0ba..b73dac8f68041f8a2e0752d70cc9d08b5cfd1cde 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java +@@ -408,7 +408,7 @@ public class Shulker extends AbstractGolem implements VariantHolder +Date: Tue, 5 Sep 2023 20:34:20 +0200 +Subject: [PATCH] Add experience points API + + +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 f32708ed934bbbf2416e32fdddc7bb4329958cf6..2a280a54bdd7e4b7f26e0b2d2da0d9d3fd432583 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1826,7 +1826,7 @@ public abstract class Player extends LivingEntity { + } + + public int getXpNeededForNextLevel() { +- return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); ++ return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); // Paper - diff on change; calculateTotalExperiencePoints + } + // Paper start - send while respecting visibility + private static void sendSoundEffect(Player fromEntity, double x, double y, double z, SoundEvent soundEffect, SoundSource soundCategory, float volume, float pitch) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index b88c71c4a1d4eb5c24d143a0d8ddff507df690f7..205dfed388db7d022e4fd8b3f89485735d3320a8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1824,6 +1824,49 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + Preconditions.checkArgument(exp >= 0, "Total experience points must not be negative (%s)", exp); + this.getHandle().totalExperience = exp; + } ++ // Paper start ++ @Override ++ public int calculateTotalExperiencePoints() { ++ return calculateTotalExperiencePoints(this.getLevel()) + Math.round(this.getExperiencePointsNeededForNextLevel() * getExp()); ++ } ++ ++ @Override ++ public void setExperienceLevelAndProgress(final int totalExperience) { ++ Preconditions.checkArgument(totalExperience >= 0, "Total experience points must not be negative (%s)", totalExperience); ++ int level = calculateLevelsForExperiencePoints(totalExperience); ++ int remainingPoints = totalExperience - calculateTotalExperiencePoints(level); ++ ++ this.getHandle().experienceLevel = level; ++ this.getHandle().experienceProgress = (float) remainingPoints / this.getExperiencePointsNeededForNextLevel(); ++ this.getHandle().lastSentExp = -1; ++ } ++ ++ @Override ++ public int getExperiencePointsNeededForNextLevel() { ++ return this.getHandle().getXpNeededForNextLevel(); ++ } ++ ++ // See https://minecraft.wiki/w/Experience#Leveling_up for reference ++ private int calculateTotalExperiencePoints(int level) { ++ if (level <= 16) { ++ return (int) (Math.pow(level, 2) + 6 * level); ++ } else if (level <= 31) { ++ return (int) (2.5 * Math.pow(level, 2) - 40.5 * level + 360.0); ++ } else { ++ return (int) (4.5 * Math.pow(level, 2) - 162.5 * level + 2220.0); ++ } ++ } ++ ++ private int calculateLevelsForExperiencePoints(int points) { ++ if (points <= 352) { // Level 0-16 ++ return (int) Math.floor(Math.sqrt(points + 9) - 3); ++ } else if (points <= 1507) { // Level 17-31 ++ return (int) Math.floor(8.1 + Math.sqrt(0.4 * (points - (7839.0 / 40.0)))); ++ } else { // 32+ ++ return (int) Math.floor((325.0 / 18.0) + Math.sqrt((2.0 / 9.0) * (points - (54215.0 / 72.0)))); ++ } ++ } ++ // Paper end + + @Override + public void sendExperienceChange(float progress) { diff --git a/patches/server/0976-Add-drops-to-shear-events.patch b/patches/server/0976-Add-drops-to-shear-events.patch new file mode 100644 index 000000000000..88b61dc3d049 --- /dev/null +++ b/patches/server/0976-Add-drops-to-shear-events.patch @@ -0,0 +1,326 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 18 May 2021 12:32:02 -0700 +Subject: [PATCH] Add drops to shear events + + +diff --git a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +index 45d356c1ed888b4d749379ceaa8a95d7d7c876d5..8d65cdb013706a932c2c73231108b2810b99e1c7 100644 +--- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +@@ -103,11 +103,14 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { + + if (ishearable.readyForShearing()) { + // CraftBukkit start +- if (CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem).isCancelled()) { ++ // Paper start - Add drops to shear events ++ org.bukkit.event.block.BlockShearEntityEvent event = CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem, ishearable.generateDefaultDrops()); ++ if (event.isCancelled()) { ++ // Paper end - Add drops to shear events + continue; + } + // CraftBukkit end +- ishearable.shear(SoundSource.BLOCKS); ++ ishearable.shear(SoundSource.BLOCKS, CraftItemStack.asNMSCopy(event.getDrops())); // Paper - Add drops to shear events + worldserver.gameEvent((Entity) null, GameEvent.SHEAR, blockposition); + return true; + } +diff --git a/src/main/java/net/minecraft/world/entity/Shearable.java b/src/main/java/net/minecraft/world/entity/Shearable.java +index 5e8cc5cfac8888628c6d513148f41be09ca65a2c..2ee48ac3b665db2b02bcb1a30ec972d43a3725b0 100644 +--- a/src/main/java/net/minecraft/world/entity/Shearable.java ++++ b/src/main/java/net/minecraft/world/entity/Shearable.java +@@ -3,7 +3,13 @@ package net.minecraft.world.entity; + import net.minecraft.sounds.SoundSource; + + public interface Shearable { ++ default void shear(SoundSource soundCategory, java.util.List drops) { this.shear(soundCategory); } // Paper - Add drops to shear events + void shear(SoundSource shearedSoundCategory); + + boolean readyForShearing(); ++ // Paper start - custom shear drops; ensure all implementing entities override this ++ default java.util.List generateDefaultDrops() { ++ return java.util.Collections.emptyList(); ++ } ++ // Paper end - custom shear drops + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java +index 6f70d3a5a561e88a8e03d2165c71766ed09fcd41..e1f174ff0f791b20be7d6ad8e4a172d1e0c81e33 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java ++++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java +@@ -122,11 +122,18 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder drops = this.generateDefaultDrops(); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); + } ++ // Paper end - custom shear drops + // CraftBukkit end +- this.shear(SoundSource.PLAYERS); ++ this.shear(SoundSource.PLAYERS, drops); // Paper - custom shear drops + this.gameEvent(GameEvent.SHEAR, player); + if (!this.level().isClientSide) { + itemstack.hurtAndBreak(1, player, (entityhuman1) -> { +@@ -167,6 +174,22 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder generateDefaultDrops() { ++ List dropEntities = new java.util.ArrayList<>(5); ++ for (int i = 0; i < 5; ++i) { ++ dropEntities.add(new ItemStack(this.getVariant().getBlockState().getBlock())); ++ } ++ return dropEntities; ++ } ++ ++ @Override ++ public void shear(SoundSource shearedSoundCategory, List drops) { // If drops is null, need to generate drops ++ // Paper end - custom shear drops + this.level().playSound((Player) null, (Entity) this, SoundEvents.MOOSHROOM_SHEAR, shearedSoundCategory, 1.0F, 1.0F); + if (!this.level().isClientSide()) { + Cow entitycow = (Cow) EntityType.COW.create(this.level()); +@@ -196,17 +219,12 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder drops = this.generateDefaultDrops(); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); + } ++ // Paper end - custom shear drops + // CraftBukkit end +- this.shear(SoundSource.PLAYERS); ++ this.shear(SoundSource.PLAYERS, drops); // Paper + this.gameEvent(GameEvent.SHEAR, player); + itemstack.hurtAndBreak(1, player, (entityhuman1) -> { + entityhuman1.broadcastBreakEvent(hand); +@@ -273,13 +280,30 @@ public class Sheep extends Animal implements Shearable { + + @Override + public void shear(SoundSource shearedSoundCategory) { ++ // Paper start - custom shear drops ++ this.shear(shearedSoundCategory, this.generateDefaultDrops()); ++ } ++ ++ @Override ++ public java.util.List generateDefaultDrops() { ++ int count = 1 + this.random.nextInt(3); ++ java.util.List dropEntities = new java.util.ArrayList<>(count); ++ for (int j = 0; j < count; ++j) { ++ dropEntities.add(new ItemStack(Sheep.ITEM_BY_DYE.get(this.getColor()))); ++ } ++ return dropEntities; ++ } ++ ++ @Override ++ public void shear(SoundSource shearedSoundCategory, java.util.List drops) { ++ // Paper end - custom shear drops + this.level().playSound((Player) null, (Entity) this, SoundEvents.SHEEP_SHEAR, shearedSoundCategory, 1.0F, 1.0F); + this.setSheared(true); + int i = 1 + this.random.nextInt(3); + +- for (int j = 0; j < i; ++j) { ++ for (final ItemStack drop : drops) { // Paper - custom shear drops (moved drop generation to separate method) + this.forceDrops = true; // CraftBukkit +- ItemEntity entityitem = this.spawnAtLocation((ItemLike) Sheep.ITEM_BY_DYE.get(this.getColor()), 1); ++ ItemEntity entityitem = this.spawnAtLocation(drop, 1); // Paper - custom shear drops + this.forceDrops = false; // CraftBukkit + + if (entityitem != null) { +diff --git a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java +index 54c01fdaf507a30196fb8f38888a570f9e393559..9eab1170cb123d3b60a02314702516704f959ab7 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java ++++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java +@@ -153,11 +153,18 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM + + if (itemstack.is(Items.SHEARS) && this.readyForShearing()) { + // CraftBukkit start +- if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) { +- return InteractionResult.PASS; ++ // Paper start - custom shear drops ++ java.util.List drops = this.generateDefaultDrops(); ++ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); ++ if (event != null) { ++ if (event.isCancelled()) { ++ return InteractionResult.PASS; ++ } ++ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); + } ++ // Paper end - custom shear drops + // CraftBukkit end +- this.shear(SoundSource.PLAYERS); ++ this.shear(SoundSource.PLAYERS, drops); // Paper + this.gameEvent(GameEvent.SHEAR, player); + if (!this.level().isClientSide) { + itemstack.hurtAndBreak(1, player, (entityhuman1) -> { +@@ -173,12 +180,28 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM + + @Override + public void shear(SoundSource shearedSoundCategory) { ++ // Paper start - custom shear drops ++ this.shear(shearedSoundCategory, this.generateDefaultDrops()); ++ } ++ ++ @Override ++ public java.util.List generateDefaultDrops() { ++ return java.util.Collections.singletonList(new ItemStack(Items.CARVED_PUMPKIN)); ++ } ++ ++ @Override ++ public void shear(SoundSource shearedSoundCategory, java.util.List drops) { ++ // Paper end - custom shear drops + this.level().playSound((Player) null, (Entity) this, SoundEvents.SNOW_GOLEM_SHEAR, shearedSoundCategory, 1.0F, 1.0F); + if (!this.level().isClientSide()) { + this.setPumpkin(false); +- this.forceDrops = true; // CraftBukkit +- this.spawnAtLocation(new ItemStack(Items.CARVED_PUMPKIN), 1.7F); +- this.forceDrops = false; // CraftBukkit ++ // Paper start - custom shear drops (moved drop generation to separate method) ++ for (final ItemStack drop : drops) { ++ this.forceDrops = true; ++ this.spawnAtLocation(drop, 1.7F); ++ this.forceDrops = false; ++ } ++ // Paper end - custom shear drops + } + + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 1a7c002d1a84a52d91d4753ef4d78a8688f6d6ad..347bd2482c89e06716121bd7d05941203bab2a8b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1667,20 +1667,20 @@ public class CraftEventFactory { + return event; + } + +- public static BlockShearEntityEvent callBlockShearEntityEvent(Entity animal, org.bukkit.block.Block dispenser, CraftItemStack is) { +- BlockShearEntityEvent bse = new BlockShearEntityEvent(dispenser, animal.getBukkitEntity(), is); ++ public static BlockShearEntityEvent callBlockShearEntityEvent(Entity animal, Block dispenser, CraftItemStack is, List drops) { // Paper - custom shear drops ++ BlockShearEntityEvent bse = new BlockShearEntityEvent(dispenser, animal.getBukkitEntity(), is, Lists.transform(drops, CraftItemStack::asCraftMirror)); // Paper - custom shear drops + Bukkit.getPluginManager().callEvent(bse); + return bse; + } + +- public static boolean handlePlayerShearEntityEvent(net.minecraft.world.entity.player.Player player, Entity sheared, ItemStack shears, InteractionHand hand) { ++ public static PlayerShearEntityEvent handlePlayerShearEntityEvent(net.minecraft.world.entity.player.Player player, Entity sheared, ItemStack shears, InteractionHand hand, List drops) { // Paper - custom shear drops + if (!(player instanceof ServerPlayer)) { +- return true; ++ return null; // Paper - custom shear drops + } + +- PlayerShearEntityEvent event = new PlayerShearEntityEvent((Player) player.getBukkitEntity(), sheared.getBukkitEntity(), CraftItemStack.asCraftMirror(shears), (hand == InteractionHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); ++ PlayerShearEntityEvent event = new PlayerShearEntityEvent((Player) player.getBukkitEntity(), sheared.getBukkitEntity(), CraftItemStack.asCraftMirror(shears), (hand == InteractionHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND), Lists.transform(drops, CraftItemStack::asCraftMirror)); // Paper - custom shear drops + Bukkit.getPluginManager().callEvent(event); +- return !event.isCancelled(); ++ return event; // Paper - custom shear drops + } + + public static Cancellable handleStatisticsIncrease(net.minecraft.world.entity.player.Player entityHuman, net.minecraft.stats.Stat statistic, int current, int newValue) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index e1f9a603e7adf3468faa9bb6d93dd3339327b47e..870954fc59efdc1e0c6b5047f5a89dfaf7522d0e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -62,6 +62,16 @@ public final class CraftItemStack extends ItemStack { + return stack; + } + ++ // Paper start ++ public static java.util.List asNMSCopy(java.util.List originals) { ++ final java.util.List items = new java.util.ArrayList<>(originals.size()); ++ for (final ItemStack original : originals) { ++ items.add(asNMSCopy(original)); ++ } ++ return items; ++ } ++ // Paper end ++ + public static net.minecraft.world.item.ItemStack copyNMSStack(net.minecraft.world.item.ItemStack original, int amount) { + net.minecraft.world.item.ItemStack stack = original.copy(); + stack.setCount(amount); +diff --git a/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java b/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e612515f7709dbe5d1fa5751337cdc34fce10a98 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java +@@ -0,0 +1,34 @@ ++package io.papermc.paper.entity; ++ ++import io.github.classgraph.ClassGraph; ++import io.github.classgraph.ClassInfo; ++import io.github.classgraph.MethodInfoList; ++import io.github.classgraph.ScanResult; ++import java.util.ArrayList; ++import net.minecraft.world.entity.Shearable; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++ ++class ShearableDropsTest extends AbstractTestingBase { ++ ++ static Iterable parameters() { ++ try (ScanResult scanResult = new ClassGraph() ++ .enableClassInfo() ++ .enableMethodInfo() ++ .whitelistPackages("net.minecraft") ++ .scan() ++ ) { ++ return new ArrayList<>(scanResult.getClassesImplementing(Shearable.class.getName())); ++ } ++ } ++ ++ @ParameterizedTest ++ @MethodSource("parameters") ++ void checkShearableDropOverrides(final ClassInfo classInfo) { ++ final MethodInfoList generateDefaultDrops = classInfo.getDeclaredMethodInfo("generateDefaultDrops"); ++ assertEquals(1, generateDefaultDrops.size(), classInfo.getName() + " doesn't implement Shearable#generateDefaultDrops"); ++ } ++} diff --git a/patches/server/0976-Restore-vanilla-entity-drops-behavior.patch b/patches/server/0976-Restore-vanilla-entity-drops-behavior.patch deleted file mode 100644 index b66f72864285..000000000000 --- a/patches/server/0976-Restore-vanilla-entity-drops-behavior.patch +++ /dev/null @@ -1,243 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 22 Mar 2022 09:34:41 -0700 -Subject: [PATCH] Restore vanilla entity drops behavior - -Instead of just tracking the itemstacks, this tracks with it, the -action to take with that itemstack to apply the correct logic -on dropping the item instead of generalizing it for all dropped -items like CB does. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 6147ffdcb83a9d013a05facd75453d6500064fe7..ecf463139bb6567103d81ae26cfff53d843cbd26 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -891,22 +891,20 @@ public class ServerPlayer extends Player { - if (this.isRemoved()) { - return; - } -- java.util.List loot = new java.util.ArrayList(this.getInventory().getContainerSize()); -+ List loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); // Paper - Restore vanilla drops behavior - boolean keepInventory = this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator(); - - if (!keepInventory) { - for (ItemStack item : this.getInventory().getContents()) { - if (!item.isEmpty() && !EnchantmentHelper.hasVanishingCurse(item)) { -- loot.add(CraftItemStack.asCraftMirror(item)); -+ loot.add(new DefaultDrop(item, stack -> this.drop(stack, true, false, false))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event) - } - } - } - if (this.shouldDropLoot() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false - // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) - this.dropFromLootTable(damageSource, this.lastHurtByPlayerTime > 0); -- for (org.bukkit.inventory.ItemStack item : this.drops) { -- loot.add(item); -- } -+ loot.addAll(this.drops); // Paper - this.drops.clear(); // SPIGOT-5188: make sure to clear - } // Paper - fix player loottables running when mob loot gamerule is false - -@@ -2389,8 +2387,8 @@ public class ServerPlayer extends Player { - } - - @Override -- public ItemEntity drop(ItemStack stack, boolean throwRandomly, boolean retainOwnership) { -- ItemEntity entityitem = super.drop(stack, throwRandomly, retainOwnership); -+ public ItemEntity drop(ItemStack stack, boolean throwRandomly, boolean retainOwnership, boolean callDropEvent) { // Paper - Restore vanilla drops behavior; override method with most params -+ ItemEntity entityitem = super.drop(stack, throwRandomly, retainOwnership, callDropEvent); // Paper - Restore vanilla drops behavior; override method with most params - - if (entityitem == null) { - return null; -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 9e1d0e46e94cc72705af5e1d01de6bb7bea40107..86013d6eda6708b38c2013a242ced07eea7a3f01 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2476,6 +2476,25 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - - @Nullable - public ItemEntity spawnAtLocation(ItemStack stack, float yOffset) { -+ // Paper start - Restore vanilla drops behavior -+ return this.spawnAtLocation(stack, yOffset, null); -+ } -+ public record DefaultDrop(Item item, org.bukkit.inventory.ItemStack stack, @Nullable java.util.function.Consumer dropConsumer) { -+ public DefaultDrop(final ItemStack stack, final java.util.function.Consumer dropConsumer) { -+ this(stack.getItem(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), dropConsumer); -+ } -+ -+ public void runConsumer(final org.bukkit.World fallbackWorld, final Location fallbackLoc) { -+ if (this.dropConsumer == null || org.bukkit.craftbukkit.inventory.CraftItemType.bukkitToMinecraft(this.stack.getType()) != this.item) { -+ fallbackWorld.dropItem(fallbackLoc, this.stack); -+ } else { -+ this.dropConsumer.accept(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(this.stack)); -+ } -+ } -+ } -+ @Nullable -+ public ItemEntity spawnAtLocation(ItemStack stack, float yOffset, @Nullable java.util.function.Consumer delayedAddConsumer) { -+ // Paper end - Restore vanilla drops behavior - if (stack.isEmpty()) { - return null; - } else if (this.level().isClientSide) { -@@ -2483,14 +2502,21 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } else { - // CraftBukkit start - Capture drops for death event - if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) { -- ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack)); // Paper - mirror so we can destroy it later -+ // Paper start - Restore vanilla drops behavior -+ ((net.minecraft.world.entity.LivingEntity) this).drops.add(new net.minecraft.world.entity.Entity.DefaultDrop(stack, itemStack -> { -+ ItemEntity itemEntity = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), itemStack); // stack is copied before consumer -+ itemEntity.setDefaultPickUpDelay(); -+ this.level.addFreshEntity(itemEntity); -+ if (delayedAddConsumer != null) delayedAddConsumer.accept(itemEntity); -+ })); -+ // Paper end - Restore vanilla drops behavior - return null; - } - // CraftBukkit end - ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - copy so we can destroy original - stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe - -- entityitem.setDefaultPickUpDelay(); -+ entityitem.setDefaultPickUpDelay(); // Paper - diff on change (in dropConsumer) - // Paper start - Call EntityDropItemEvent - return this.spawnAtLocation(entityitem); - } -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 1d1fe8e8c23fc177960fb78d2e38ea9846fc3938..1b1bb94138c0f5ce197ec4cab251cdc9991c8fc7 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -253,7 +253,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - // CraftBukkit start - public int expToDrop; - public boolean forceDrops; -- public ArrayList drops = new ArrayList(); -+ public ArrayList drops = new ArrayList<>(); // Paper - Restore vanilla drops behavior - public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes; - public boolean collides = true; - public Set collidableExemptions = new HashSet<>(); -diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 45906d273e6d6ec20cf44b4d07efdac68752ee9b..ac9eaeaf7df1e84ee588f371628c0a10784d50bc 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -534,10 +534,10 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - @Override - protected void dropCustomDeathLoot(DamageSource source, int lootingMultiplier, boolean allowDrops) { - super.dropCustomDeathLoot(source, lootingMultiplier, allowDrops); -- ItemEntity entityitem = this.spawnAtLocation((ItemLike) Items.NETHER_STAR); -+ ItemEntity entityitem = this.spawnAtLocation(new net.minecraft.world.item.ItemStack(Items.NETHER_STAR), 0, ItemEntity::setExtendedLifetime); // Paper - Restore vanilla drops behavior; spawnAtLocation returns null so modify the item entity with a consumer - - if (entityitem != null) { -- entityitem.setExtendedLifetime(); -+ entityitem.setExtendedLifetime(); // Paper - diff on change - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -index e3412f9dd86dddd241bea8f6dcaeed77a7e67f08..6dfcc296ff7e59ecbebc5446973fabc9eff3cb43 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -610,7 +610,7 @@ public class ArmorStand extends LivingEntity { - itemstack.setHoverName(this.getCustomName()); - } - -- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops -+ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior - return this.brokenByAnything(damageSource); // Paper - } - -@@ -624,7 +624,7 @@ public class ArmorStand extends LivingEntity { - for (i = 0; i < this.handItems.size(); ++i) { - itemstack = (ItemStack) this.handItems.get(i); - if (!itemstack.isEmpty()) { -- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe -+ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly - this.handItems.set(i, ItemStack.EMPTY); - } - } -@@ -632,7 +632,7 @@ public class ArmorStand extends LivingEntity { - for (i = 0; i < this.armorItems.size(); ++i) { - itemstack = (ItemStack) this.armorItems.get(i); - if (!itemstack.isEmpty()) { -- this.drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)); // CraftBukkit - add to drops // Paper - mirror so we can destroy it later - though this call site was safe -+ this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition().above(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior; mirror so we can destroy it later - though this call site was safe & spawn drops correctly - this.armorItems.set(i, ItemStack.EMPTY); - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 7c5b6ab17439c6dc958a910c683114b6397f3379..5519ef3b82b5a04536bb2ffb7ce1ba460a2df8d6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -963,17 +963,23 @@ public class CraftEventFactory { - } - - public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim) { -- return CraftEventFactory.callEntityDeathEvent(victim, new ArrayList(0)); -+ return CraftEventFactory.callEntityDeathEvent(victim, new ArrayList<>(0)); // Paper - Restore vanilla drops behavior - } - -- public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops) { -+ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops) { // Paper - Restore vanilla drops behavior - // Paper start - return CraftEventFactory.callEntityDeathEvent(victim, drops, com.google.common.util.concurrent.Runnables.doNothing()); - } -- public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops, Runnable lootCheck) { -+ -+ private static final java.util.function.Function FROM_FUNCTION = stack -> { -+ if (stack == null) return null; -+ return new Entity.DefaultDrop(CraftItemType.bukkitToMinecraft(stack.getType()), stack, null); -+ }; -+ -+ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops, Runnable lootCheck) { // Paper - // Paper end - CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); -- EntityDeathEvent event = new EntityDeathEvent(entity, drops, victim.getExpReward()); -+ EntityDeathEvent event = new EntityDeathEvent(entity, new io.papermc.paper.util.TransformingRandomAccessList<>(drops, Entity.DefaultDrop::stack, FROM_FUNCTION), victim.getExpReward()); // Paper - Restore vanilla drops behavior - populateFields(victim, event); // Paper - make cancellable - CraftWorld world = (CraftWorld) entity.getWorld(); - Bukkit.getServer().getPluginManager().callEvent(event); -@@ -987,19 +993,23 @@ public class CraftEventFactory { - victim.expToDrop = event.getDroppedExp(); - lootCheck.run(); // Paper - advancement triggers before destroying items - -- for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { -+ // Paper start - Restore vanilla drops behavior -+ for (Entity.DefaultDrop drop : drops) { -+ if (drop == null) continue; -+ final org.bukkit.inventory.ItemStack stack = drop.stack(); -+ // Paper end - Restore vanilla drops behavior - if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; - -- world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS -+ drop.runConsumer(world, entity.getLocation()); // Paper - Restore vanilla drops behavior - if (stack instanceof CraftItemStack) stack.setAmount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe, but don't nuke bukkit stacks of manually added items - } - - return event; - } - -- public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, List drops, net.kyori.adventure.text.Component deathMessage, boolean keepInventory) { // Paper - Adventure -+ public static PlayerDeathEvent callPlayerDeathEvent(ServerPlayer victim, List drops, net.kyori.adventure.text.Component deathMessage, boolean keepInventory) { // Paper - Adventure & Restore vanilla drops behavior - CraftPlayer entity = victim.getBukkitEntity(); -- PlayerDeathEvent event = new PlayerDeathEvent(entity, drops, victim.getExpReward(), 0, deathMessage); -+ PlayerDeathEvent event = new PlayerDeathEvent(entity, new io.papermc.paper.util.TransformingRandomAccessList<>(drops, Entity.DefaultDrop::stack, FROM_FUNCTION), victim.getExpReward(), 0, deathMessage); // Paper - Restore vanilla drops behavior - event.setKeepInventory(keepInventory); - event.setKeepLevel(victim.keepLevel); // SPIGOT-2222: pre-set keepLevel - populateFields(victim, event); // Paper - make cancellable -@@ -1018,10 +1028,14 @@ public class CraftEventFactory { - victim.expToDrop = event.getDroppedExp(); - victim.newExp = event.getNewExp(); - -- for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { -+ // Paper start - Restore vanilla drops behavior -+ for (Entity.DefaultDrop drop : drops) { -+ if (drop == null) continue; -+ final org.bukkit.inventory.ItemStack stack = drop.stack(); -+ // Paper end - Restore vanilla drops behavior - if (stack == null || stack.getType() == Material.AIR) continue; - -- world.dropItem(entity.getLocation(), stack); -+ drop.runConsumer(world, entity.getLocation()); // Paper - Restore vanilla drops behavior - } - - return event; diff --git a/patches/server/0977-Add-PlayerShieldDisableEvent.patch b/patches/server/0977-Add-PlayerShieldDisableEvent.patch new file mode 100644 index 000000000000..559e3c5bc709 --- /dev/null +++ b/patches/server/0977-Add-PlayerShieldDisableEvent.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Mon, 1 May 2023 16:22:43 -0500 +Subject: [PATCH] Add PlayerShieldDisableEvent + +Called whenever a players shield is disabled. This is mainly caused by +attacking players or monsters that carry axes. + +The event, while similar to the PlayerItemCooldownEvent, offers other +behaviour and can hence not be implemented as a childtype of said event. +Specifically, cancelling the event prevents the game events from being +sent to the player. + +Plugins listening to just the PlayerItemCooldownEvent may not want said +sideeffects, meaning the disable event cannot share a handlerlist with +the cooldown event + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 1721504912c9e5744f09c17d059315ee357afeb4..9ecabac9e95bb0b550260770fcc713ad82070d0b 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1697,7 +1697,11 @@ public abstract class Mob extends LivingEntity implements Targeting { + float f = 0.25F + (float) EnchantmentHelper.getBlockEfficiency(this) * 0.05F; + + if (this.random.nextFloat() < f) { +- player.getCooldowns().addCooldown(Items.SHIELD, 100); ++ // Paper start - Add PlayerShieldDisableEvent ++ final io.papermc.paper.event.player.PlayerShieldDisableEvent shieldDisableEvent = new io.papermc.paper.event.player.PlayerShieldDisableEvent((org.bukkit.entity.Player) player.getBukkitEntity(), getBukkitEntity(), 100); ++ if (!shieldDisableEvent.callEvent()) return; ++ player.getCooldowns().addCooldown(Items.SHIELD, shieldDisableEvent.getCooldown()); ++ // Paper end - Add PlayerShieldDisableEvent + this.level().broadcastEntityEvent(player, (byte) 30); + } + } +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 2a280a54bdd7e4b7f26e0b2d2da0d9d3fd432583..ade64d3af069abdb5c94895fe699ac5eee603a6e 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -972,7 +972,7 @@ public abstract class Player extends LivingEntity { + protected void blockUsingShield(LivingEntity attacker) { + super.blockUsingShield(attacker); + if (attacker.canDisableShield()) { +- this.disableShield(true); ++ this.disableShield(true, attacker); // Paper - Add PlayerShieldDisableEvent + } + + } +@@ -1455,7 +1455,14 @@ public abstract class Player extends LivingEntity { + this.attack(target); + } + ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - Add PlayerShieldDisableEvent + public void disableShield(boolean sprinting) { ++ // Paper start - Add PlayerShieldDisableEvent ++ disableShield(sprinting, null); ++ } ++ ++ public void disableShield(boolean sprinting, @Nullable LivingEntity attacker) { ++ // Paper end - Add PlayerShieldDisableEvent + float f = 0.25F + (float) EnchantmentHelper.getBlockEfficiency(this) * 0.05F; + + if (sprinting) { +@@ -1463,7 +1470,16 @@ public abstract class Player extends LivingEntity { + } + + if (this.random.nextFloat() < f) { +- this.getCooldowns().addCooldown(Items.SHIELD, 100); ++ // Paper start - Add PlayerShieldDisableEvent ++ final org.bukkit.entity.Entity finalAttacker = attacker != null ? attacker.getBukkitEntity() : null; ++ if (finalAttacker != null) { ++ final io.papermc.paper.event.player.PlayerShieldDisableEvent shieldDisableEvent = new io.papermc.paper.event.player.PlayerShieldDisableEvent((org.bukkit.entity.Player) getBukkitEntity(), finalAttacker, 100); ++ if (!shieldDisableEvent.callEvent()) return; ++ this.getCooldowns().addCooldown(Items.SHIELD, shieldDisableEvent.getCooldown()); ++ } else { ++ this.getCooldowns().addCooldown(Items.SHIELD, 100); ++ } ++ // Paper end - Add PlayerShieldDisableEvent + this.stopUsingItem(); + this.level().broadcastEntityEvent(this, (byte) 30); + } diff --git a/patches/server/0977-Dont-resend-blocks-on-interactions.patch b/patches/server/0977-Dont-resend-blocks-on-interactions.patch deleted file mode 100644 index d79a6f92ea51..000000000000 --- a/patches/server/0977-Dont-resend-blocks-on-interactions.patch +++ /dev/null @@ -1,171 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Tue, 27 Jun 2023 21:09:11 -0400 -Subject: [PATCH] Dont resend blocks on interactions - -In general, the client now has an acknowledgment system which will prevent block changes made by the client to be reverted correctly. - -It should be noted that this system does not yet support block entities, so those still need to resynced when needed. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index d0ca98c3f9ea5c8cb1053da6b17e9a90c86b3ae7..5063eb6d4a24600262c32d2c9eb5fb5bf8fa354e 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -199,7 +199,7 @@ public class ServerPlayerGameMode { - PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); - if (event.isCancelled()) { - // Let the client know the block still exists -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks - // Update any tile entity data for this block - capturedBlockEntity = true; // Paper - Send block entities after destroy prediction - return; -@@ -214,7 +214,7 @@ public class ServerPlayerGameMode { - // Spigot start - handle debug stick left click for non-creative - if (this.player.getMainHandItem().is(net.minecraft.world.item.Items.DEBUG_STICK) - && ((net.minecraft.world.item.DebugStickItem) net.minecraft.world.item.Items.DEBUG_STICK).handleInteraction(this.player, this.level.getBlockState(pos), this.level, pos, false, this.player.getMainHandItem())) { -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block - return; - } - // Spigot end -@@ -232,15 +232,17 @@ public class ServerPlayerGameMode { - // CraftBukkit start - Swings at air do *NOT* exist. - if (event.useInteractedBlock() == Event.Result.DENY) { - // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door. -- BlockState data = this.level.getBlockState(pos); -- if (data.getBlock() instanceof DoorBlock) { -- // For some reason *BOTH* the bottom/top part have to be marked updated. -- boolean bottom = data.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below())); -- } else if (data.getBlock() instanceof TrapDoorBlock) { -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -- } -+ // Paper start - Don't resync blocks -+ //BlockState data = this.level.getBlockState(pos); -+ //if (data.getBlock() instanceof DoorBlock) { -+ // // For some reason *BOTH* the bottom/top part have to be marked updated. -+ // boolean bottom = data.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below())); -+ //} else if (data.getBlock() instanceof TrapDoorBlock) { -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ //} -+ // Paper end - Don't resync blocks - } else if (!iblockdata.isAir()) { - iblockdata.attack(this.level, pos, this.player); - f = iblockdata.getDestroyProgress(this.player, this.player.level(), pos); -@@ -249,7 +251,7 @@ public class ServerPlayerGameMode { - if (event.useItemInHand() == Event.Result.DENY) { - // If we 'insta destroyed' then the client needs to be informed. - if (f > 1.0f) { -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks - } - return; - } -@@ -257,7 +259,7 @@ public class ServerPlayerGameMode { - - if (blockEvent.isCancelled()) { - // Let the client know the block still exists -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block - return; - } - -@@ -348,7 +350,7 @@ public class ServerPlayerGameMode { - - // Tell client the block is gone immediately then process events - // Don't tell the client if its a creative sword break because its not broken! -- if (this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { -+ if (false && this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { // Paper - Don't resync block - ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, Blocks.AIR.defaultBlockState()); - this.player.connection.send(packet); - } -@@ -374,13 +376,15 @@ public class ServerPlayerGameMode { - if (isSwordNoBreak) { - return false; - } -+ // Paper start - Don't resync blocks - // Let the client know the block still exists -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ //this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); - - // Brute force all possible updates -- for (Direction dir : Direction.values()) { -- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos.relative(dir))); -- } -+ //for (Direction dir : Direction.values()) { -+ // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos.relative(dir))); -+ //} -+ // Paper end - Don't resync blocks - - // Update any tile entity data for this block - if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction -@@ -537,16 +541,18 @@ public class ServerPlayerGameMode { - if (event.useInteractedBlock() == Event.Result.DENY) { - // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door. - if (iblockdata.getBlock() instanceof DoorBlock) { -- boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; -- player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below())); -+ // Paper start - Don't resync blocks -+ // boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; -+ // player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below())); -+ // Paper end - Don't resync blocks - } else if (iblockdata.getBlock() instanceof CakeBlock) { - player.getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake - } else if (this.interactItemStack.getItem() instanceof DoubleHighBlockItem) { - // send a correcting update to the client, as it already placed the upper half of the bisected item -- player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.relative(hitResult.getDirection()).above())); -+ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.relative(hitResult.getDirection()).above())); // Paper - Don't resync blocks - - // send a correcting update to the client for the block above as well, this because of replaceable blocks (such as grass, sea grass etc) -- player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); -+ //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); // Paper - Don't resync blocks - // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method - } else if (iblockdata.is(Blocks.STRUCTURE_BLOCK) || iblockdata.getBlock() instanceof net.minecraft.world.level.block.CommandBlock) { - player.connection.send(new net.minecraft.network.protocol.game.ClientboundContainerClosePacket(this.player.containerMenu.containerId)); -diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java -index 4b9e726e6ac255e743479d5c2e0cdb98464399a4..6371f326fc86cfc53e39bf8ed13b646f7705fbbc 100644 ---- a/src/main/java/net/minecraft/world/item/BucketItem.java -+++ b/src/main/java/net/minecraft/world/item/BucketItem.java -@@ -77,7 +77,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { - PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) world, user, blockposition, blockposition, movingobjectpositionblock.getDirection(), itemstack, dummyFluid.getItem(), hand); - - if (event.isCancelled()) { -- ((ServerPlayer) user).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-5163 (see PlayerInteractManager) -+ // ((ServerPlayer) user).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-5163 (see PlayerInteractManager) // Paper - Don't resend blocks - ((ServerPlayer) user).getBukkitEntity().updateInventory(); // SPIGOT-4541 - return InteractionResultHolder.fail(itemstack); - } -@@ -186,7 +186,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { - if (flag2 && entityhuman != null) { - PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent((ServerLevel) world, entityhuman, blockposition, clicked, enumdirection, itemstack, enumhand); - if (event.isCancelled()) { -- ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-4238: needed when looking through entity -+ // ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-4238: needed when looking through entity // Paper - Don't resend blocks - ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 - return false; - } -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 0fd5decb0790423aba80a7c1e55ce39aff6761b4..2470acc82292bedd930be404a2e1d1f8fad700e1 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -457,10 +457,12 @@ public final class ItemStack { - world.preventPoiUpdated = false; - - // Brute force all possible updates -- BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition(); -- for (Direction dir : Direction.values()) { -- ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir))); -- } -+ // Paper start - Don't resync blocks -+ // BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition(); -+ // for (Direction dir : Direction.values()) { -+ // ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir))); -+ // } -+ // Paper end - Don't resync blocks - SignItem.openSign = null; // SPIGOT-6758 - Reset on early return - } else { - // Change the stack to its new contents if it hasn't been tampered with. diff --git a/patches/server/0978-Validate-ResourceLocation-in-NBT-reading.patch b/patches/server/0978-Validate-ResourceLocation-in-NBT-reading.patch new file mode 100644 index 000000000000..2f94b7ad0323 --- /dev/null +++ b/patches/server/0978-Validate-ResourceLocation-in-NBT-reading.patch @@ -0,0 +1,112 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 4 Jan 2024 13:49:14 +0100 +Subject: [PATCH] Validate ResourceLocation in NBT reading + + +diff --git a/src/main/java/net/minecraft/nbt/NbtUtils.java b/src/main/java/net/minecraft/nbt/NbtUtils.java +index ba0726157417cdde1c9bca93a9e37e68d9b2286d..e3a3f19a6e63fd42e29c418e5a7439972484d492 100644 +--- a/src/main/java/net/minecraft/nbt/NbtUtils.java ++++ b/src/main/java/net/minecraft/nbt/NbtUtils.java +@@ -230,8 +230,10 @@ public final class NbtUtils { + if (!nbt.contains("Name", 8)) { + return Blocks.AIR.defaultBlockState(); + } else { +- ResourceLocation resourceLocation = new ResourceLocation(nbt.getString("Name")); +- Optional> optional = blockLookup.get(ResourceKey.create(Registries.BLOCK, resourceLocation)); ++ // Paper start - Validate resource location ++ ResourceLocation resourceLocation = ResourceLocation.tryParse(nbt.getString("Name")); ++ Optional> optional = resourceLocation != null ? blockLookup.get(ResourceKey.create(Registries.BLOCK, resourceLocation)) : Optional.empty(); ++ // Paper end - Validate resource location + if (optional.isEmpty()) { + return Blocks.AIR.defaultBlockState(); + } else { +diff --git a/src/main/java/net/minecraft/resources/ResourceLocation.java b/src/main/java/net/minecraft/resources/ResourceLocation.java +index 5f9dcab27a07969c93555ad0892683c62cbebc8c..a4d875df936b6de16f0233482b03af05b427a79f 100644 +--- a/src/main/java/net/minecraft/resources/ResourceLocation.java ++++ b/src/main/java/net/minecraft/resources/ResourceLocation.java +@@ -31,6 +31,13 @@ public class ResourceLocation implements Comparable { + private final String path; + + protected ResourceLocation(String namespace, String path, @Nullable ResourceLocation.Dummy extraData) { ++ // Paper start - Validate ResourceLocation ++ // Check for the max network string length (capped at Short.MAX_VALUE) as well as the max bytes of a StringTag (length written as an unsigned short) ++ final String resourceLocation = namespace + ":" + path; ++ if (resourceLocation.length() > Short.MAX_VALUE || io.netty.buffer.ByteBufUtil.utf8MaxBytes(resourceLocation) > 2 * Short.MAX_VALUE + 1) { ++ throw new ResourceLocationException("Resource location too long: " + resourceLocation); ++ } ++ // Paper end - Validate ResourceLocation + this.namespace = namespace; + this.path = path; + } +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index 8ba573bb4099ee5b27b61f333e72d794c48d5f29..69bdf3f2ee731e59e8d454816a9ca72cb49c0fe0 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -614,7 +614,7 @@ public class EntityType implements FeatureElement, EntityTypeT + } + + public static Optional> by(CompoundTag nbt) { +- return BuiltInRegistries.ENTITY_TYPE.getOptional(new ResourceLocation(nbt.getString("id"))); ++ return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.tryParse(nbt.getString("id"))); // Paper - Validate ResourceLocation + } + + @Nullable +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 4591011a142f33f0c0ff84a2765cededde0e0c57..c0bfce7266bbdfe0c5a753367032eb333f56c182 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -871,12 +871,13 @@ public abstract class LivingEntity extends Entity implements Attackable { + + if (nbt.contains("SleepingX", 99) && nbt.contains("SleepingY", 99) && nbt.contains("SleepingZ", 99)) { + BlockPos blockposition = new BlockPos(nbt.getInt("SleepingX"), nbt.getInt("SleepingY"), nbt.getInt("SleepingZ")); +- ++ if (this.position().distanceToSqr(blockposition.getX(), blockposition.getY(), blockposition.getZ()) < 16 * 16) { // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong + this.setSleepingPos(blockposition); + this.entityData.set(LivingEntity.DATA_POSE, Pose.SLEEPING); + if (!this.firstTick) { + this.setPosToBed(blockposition); + } ++ } // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong + } + + if (nbt.contains("Brain", 10)) { +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 9ecabac9e95bb0b550260770fcc713ad82070d0b..fd093e5bc79a44e02f57bacd8273dc87342f5709 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -607,7 +607,7 @@ public abstract class Mob extends LivingEntity implements Targeting { + + this.setLeftHanded(nbt.getBoolean("LeftHanded")); + if (nbt.contains("DeathLootTable", 8)) { +- this.lootTable = new ResourceLocation(nbt.getString("DeathLootTable")); ++ this.lootTable = ResourceLocation.tryParse(nbt.getString("DeathLootTable")); // Paper - Validate ResourceLocation + this.lootTableSeed = nbt.getLong("DeathLootTableSeed"); + } + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index 618de60680de015bc68bf95a68eda98db7bab3c5..d14eab0d83d629a4522bf3f7d789d2853eb84f06 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -560,7 +560,7 @@ public abstract class AbstractArrow extends Projectile { + this.setCritArrow(nbt.getBoolean("crit")); + this.setPierceLevel(nbt.getByte("PierceLevel")); + if (nbt.contains("SoundEvent", 8)) { +- this.soundEvent = (SoundEvent) BuiltInRegistries.SOUND_EVENT.getOptional(new ResourceLocation(nbt.getString("SoundEvent"))).orElse(this.getDefaultHitGroundSoundEvent()); ++ this.soundEvent = (SoundEvent) BuiltInRegistries.SOUND_EVENT.getOptional(ResourceLocation.tryParse(nbt.getString("SoundEvent"))).orElse(this.getDefaultHitGroundSoundEvent()); // Paper - Validate resource location + } + + this.setShotFromCrossbow(nbt.getBoolean("ShotFromCrossbow")); +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java +index 7529751afa2932fd16bc4591189b0358268a7b14..e2e1c7a017e82dc7299e5cd1783818e4f0319c0b 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java +@@ -67,7 +67,7 @@ public interface ContainerEntity extends Container, MenuProvider { + default void readChestVehicleSaveData(CompoundTag nbt) { + this.clearItemStacks(); + if (nbt.contains("LootTable", 8)) { +- this.setLootTable(new ResourceLocation(nbt.getString("LootTable"))); ++ this.setLootTable(ResourceLocation.tryParse(nbt.getString("LootTable"))); // Paper - Validate ResourceLocation + this.setLootTableSeed(nbt.getLong("LootTableSeed")); + } + diff --git a/patches/server/0979-Improve-Registry.patch b/patches/server/0979-Improve-Registry.patch deleted file mode 100644 index 58247ca58069..000000000000 --- a/patches/server/0979-Improve-Registry.patch +++ /dev/null @@ -1,102 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 20 Dec 2023 02:03:05 -0800 -Subject: [PATCH] Improve Registry - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -index 13270b2197c594dc03d089aea46aa410dd9efd13..493d054cdda04bc08ab610a09c2a1d0290ae046c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java -@@ -126,6 +126,7 @@ public class CraftRegistry implements Registry { - - private final Class bukkitClass; - private final Map cache = new HashMap<>(); -+ private final Map byValue = new java.util.IdentityHashMap<>(); // Paper - improve Registry - private final net.minecraft.core.Registry minecraftRegistry; - private final BiFunction minecraftToBukkit; - private boolean init; -@@ -170,6 +171,7 @@ public class CraftRegistry implements Registry { - } - - this.cache.put(namespacedKey, bukkit); -+ this.byValue.put(bukkit, namespacedKey); // Paper - improve Registry - - return bukkit; - } -@@ -192,4 +194,11 @@ public class CraftRegistry implements Registry { - - return this.minecraftToBukkit.apply(namespacedKey, minecraft); - } -+ -+ // Paper start - improve Registry -+ @Override -+ public NamespacedKey getKey(final B value) { -+ return this.byValue.get(value); -+ } -+ // Paper end - improve Registry - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java -index 2e6b04a150eae4fbac7b4c6413d81b755ac87be2..6cce693a24e0b1b485832935d398fb26c91e0be0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java -@@ -34,6 +34,7 @@ public class CraftTrimMaterial implements TrimMaterial, Handleable this + " doesn't have a key"); // Paper - return this.key; - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java -index 5a570bae1262f768d86a6078bfded427294ed135..bf13fe2f858ee35c84c5a1f3fb2f7df61a62315b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java -@@ -34,6 +34,7 @@ public class CraftTrimPattern implements TrimPattern, Handleable this + " doesn't have a key"); // Paper - return this.key; - } - } -diff --git a/src/test/java/org/bukkit/registry/PerRegistryTest.java b/src/test/java/org/bukkit/registry/PerRegistryTest.java -index 1c4966520b6401e6571aa44d5934dfa280bc80e3..010de6fbb75eb5d51639695d260f916072fdb22d 100644 ---- a/src/test/java/org/bukkit/registry/PerRegistryTest.java -+++ b/src/test/java/org/bukkit/registry/PerRegistryTest.java -@@ -49,19 +49,22 @@ public class PerRegistryTest extends AbstractTestingBase { - - @ParameterizedTest - @MethodSource("data") -- public void testGet(Registry registry) { -+ public void testGet(Registry registry) { // Paper - improve Registry - registry.forEach(element -> { -+ NamespacedKey key = registry.getKey(element); // Paper - improve Registry -+ assertNotNull(key); // Paper - improve Registry - // Values in the registry should be referentially equal to what is returned with #get() - // This ensures that new instances are not created each time #get() is invoked -- assertSame(element, registry.get(element.getKey())); -+ assertSame(element, registry.get(key)); // Paper - improve Registry - }); - } - - @ParameterizedTest - @MethodSource("data") -- public void testMatch(Registry registry) { -+ public void testMatch(Registry registry) { // Paper - improve Registry - registry.forEach(element -> { -- NamespacedKey key = element.getKey(); -+ NamespacedKey key = registry.getKey(element); // Paper - improve Registry -+ assertNotNull(key); // Paper - improve Registry - - this.assertSameMatchWithKeyMessage(registry, element, key.toString()); // namespace:key - this.assertSameMatchWithKeyMessage(registry, element, key.getKey()); // key -@@ -72,7 +75,7 @@ public class PerRegistryTest extends AbstractTestingBase { - }); - } - -- private void assertSameMatchWithKeyMessage(Registry registry, Keyed element, String key) { -+ private void assertSameMatchWithKeyMessage(Registry registry, T element, String key) { // Paper - improve Registry - assertSame(element, registry.match(key), key); - } - diff --git a/patches/server/0985-Properly-handle-experience-dropping-on-block-break.patch b/patches/server/0979-Properly-handle-experience-dropping-on-block-break.patch similarity index 100% rename from patches/server/0985-Properly-handle-experience-dropping-on-block-break.patch rename to patches/server/0979-Properly-handle-experience-dropping-on-block-break.patch diff --git a/patches/server/0980-Fix-NPE-on-null-loc-for-EntityTeleportEvent.patch b/patches/server/0980-Fix-NPE-on-null-loc-for-EntityTeleportEvent.patch deleted file mode 100644 index aa658a4835f1..000000000000 --- a/patches/server/0980-Fix-NPE-on-null-loc-for-EntityTeleportEvent.patch +++ /dev/null @@ -1,66 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 9 Dec 2023 19:15:59 -0800 -Subject: [PATCH] Fix NPE on null loc for EntityTeleportEvent - -EntityTeleportEvent#setTo is marked as nullable and so is the -getTo method. This fixes the handling of a null "to" location -by treating it the same as the event being cancelled. This is -already existing behavior for the EntityPortalEvent (which -extends EntityTeleportEvent). - -diff --git a/src/main/java/net/minecraft/server/commands/TeleportCommand.java b/src/main/java/net/minecraft/server/commands/TeleportCommand.java -index 3fec07b250a8f145e30c8c41888e47d2a3c902e1..2ddd033e1c3a2e5c8950b93c838491923803ccce 100644 ---- a/src/main/java/net/minecraft/server/commands/TeleportCommand.java -+++ b/src/main/java/net/minecraft/server/commands/TeleportCommand.java -@@ -169,9 +169,10 @@ public class TeleportCommand { - Location to = new Location(world.getWorld(), x, y, z, f2, f3); - EntityTeleportEvent event = new EntityTeleportEvent(target.getBukkitEntity(), target.getBukkitEntity().getLocation(), to); - world.getCraftServer().getPluginManager().callEvent(event); -- if (event.isCancelled()) { -+ if (event.isCancelled() || event.getTo() == null) { // Paper - return; - } -+ to = event.getTo(); // Paper - actually track new location - - x = to.getX(); - y = to.getY(); -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index d3d958b58934bcb513ffef474a9de58c61e654a2..976b9981d6022eab6b124dd279eac82364644585 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -4180,7 +4180,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (!(this instanceof ServerPlayer)) { - EntityTeleportEvent teleport = new EntityTeleportEvent(this.getBukkitEntity(), new Location(this.level().getWorld(), d3, d4, d5), new Location(this.level().getWorld(), d0, d6, d2)); - this.level().getCraftServer().getPluginManager().callEvent(teleport); -- if (!teleport.isCancelled()) { -+ if (!teleport.isCancelled() && teleport.getTo() != null) { // Paper - Location to = teleport.getTo(); - this.teleportTo(to.getX(), to.getY(), to.getZ()); - } else { -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java -index 8e2f7e2385588224018f7f94ed9686415bc91deb..c0da573e3818a1dd2c1ef5a61c7cb34934b0a252 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java -@@ -129,7 +129,7 @@ public class FollowOwnerGoal extends Goal { - } else { - // CraftBukkit start - EntityTeleportEvent event = CraftEventFactory.callEntityTeleportEvent(this.tamable, (double) x + 0.5D, (double) y, (double) z + 0.5D); -- if (event.isCancelled()) { -+ if (event.isCancelled() || event.getTo() == null) { // Paper - return false; - } - Location to = event.getTo(); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -index 06ab07fb5d8d0e2f97325890218a11fef551a0ba..b73dac8f68041f8a2e0752d70cc9d08b5cfd1cde 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -@@ -408,7 +408,7 @@ public class Shulker extends AbstractGolem implements VariantHolder -Date: Tue, 5 Sep 2023 20:34:20 +0200 -Subject: [PATCH] Add experience points API - - -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 eb6a4518d3919cacc3b7a83870c0f0e87eba3f6e..d8ef2466e235b8121dd04bde0800ed2dbf8e370c 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1825,7 +1825,7 @@ public abstract class Player extends LivingEntity { - } - - public int getXpNeededForNextLevel() { -- return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); -+ return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2); // Paper - diff on change; calculateTotalExperiencePoints - } - // Paper start - send while respecting visibility - private static void sendSoundEffect(Player fromEntity, double x, double y, double z, SoundEvent soundEffect, SoundSource soundCategory, float volume, float pitch) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index cd58b5e338ece695cddab37b5bcce730b04dc63d..a85f7f56bdabe246ce47ab83f4000bd779cf5c3b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1794,6 +1794,49 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - Preconditions.checkArgument(exp >= 0, "Total experience points must not be negative (%s)", exp); - this.getHandle().totalExperience = exp; - } -+ // Paper start -+ @Override -+ public int calculateTotalExperiencePoints() { -+ return calculateTotalExperiencePoints(this.getLevel()) + Math.round(this.getExperiencePointsNeededForNextLevel() * getExp()); -+ } -+ -+ @Override -+ public void setExperienceLevelAndProgress(final int totalExperience) { -+ Preconditions.checkArgument(totalExperience >= 0, "Total experience points must not be negative (%s)", totalExperience); -+ int level = calculateLevelsForExperiencePoints(totalExperience); -+ int remainingPoints = totalExperience - calculateTotalExperiencePoints(level); -+ -+ this.getHandle().experienceLevel = level; -+ this.getHandle().experienceProgress = (float) remainingPoints / this.getExperiencePointsNeededForNextLevel(); -+ this.getHandle().lastSentExp = -1; -+ } -+ -+ @Override -+ public int getExperiencePointsNeededForNextLevel() { -+ return this.getHandle().getXpNeededForNextLevel(); -+ } -+ -+ // See https://minecraft.wiki/w/Experience#Leveling_up for reference -+ private int calculateTotalExperiencePoints(int level) { -+ if (level <= 16) { -+ return (int) (Math.pow(level, 2) + 6 * level); -+ } else if (level <= 31) { -+ return (int) (2.5 * Math.pow(level, 2) - 40.5 * level + 360.0); -+ } else { -+ return (int) (4.5 * Math.pow(level, 2) - 162.5 * level + 2220.0); -+ } -+ } -+ -+ private int calculateLevelsForExperiencePoints(int points) { -+ if (points <= 352) { // Level 0-16 -+ return (int) Math.floor(Math.sqrt(points + 9) - 3); -+ } else if (points <= 1507) { // Level 17-31 -+ return (int) Math.floor(8.1 + Math.sqrt(0.4 * (points - (7839.0 / 40.0)))); -+ } else { // 32+ -+ return (int) Math.floor((325.0 / 18.0) + Math.sqrt((2.0 / 9.0) * (points - (54215.0 / 72.0)))); -+ } -+ } -+ // Paper end - - @Override - public void sendExperienceChange(float progress) { diff --git a/patches/server/0987-Expose-LootTable-of-DecoratedPot.patch b/patches/server/0981-Expose-LootTable-of-DecoratedPot.patch similarity index 100% rename from patches/server/0987-Expose-LootTable-of-DecoratedPot.patch rename to patches/server/0981-Expose-LootTable-of-DecoratedPot.patch diff --git a/patches/server/0982-Add-drops-to-shear-events.patch b/patches/server/0982-Add-drops-to-shear-events.patch deleted file mode 100644 index 5f0cfb9709fd..000000000000 --- a/patches/server/0982-Add-drops-to-shear-events.patch +++ /dev/null @@ -1,326 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 18 May 2021 12:32:02 -0700 -Subject: [PATCH] Add drops to shear events - - -diff --git a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java -index 45d356c1ed888b4d749379ceaa8a95d7d7c876d5..8d65cdb013706a932c2c73231108b2810b99e1c7 100644 ---- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java -@@ -103,11 +103,14 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { - - if (ishearable.readyForShearing()) { - // CraftBukkit start -- if (CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem).isCancelled()) { -+ // Paper start - Add drops to shear events -+ org.bukkit.event.block.BlockShearEntityEvent event = CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem, ishearable.generateDefaultDrops()); -+ if (event.isCancelled()) { -+ // Paper end - Add drops to shear events - continue; - } - // CraftBukkit end -- ishearable.shear(SoundSource.BLOCKS); -+ ishearable.shear(SoundSource.BLOCKS, CraftItemStack.asNMSCopy(event.getDrops())); // Paper - Add drops to shear events - worldserver.gameEvent((Entity) null, GameEvent.SHEAR, blockposition); - return true; - } -diff --git a/src/main/java/net/minecraft/world/entity/Shearable.java b/src/main/java/net/minecraft/world/entity/Shearable.java -index 5e8cc5cfac8888628c6d513148f41be09ca65a2c..2ee48ac3b665db2b02bcb1a30ec972d43a3725b0 100644 ---- a/src/main/java/net/minecraft/world/entity/Shearable.java -+++ b/src/main/java/net/minecraft/world/entity/Shearable.java -@@ -3,7 +3,13 @@ package net.minecraft.world.entity; - import net.minecraft.sounds.SoundSource; - - public interface Shearable { -+ default void shear(SoundSource soundCategory, java.util.List drops) { this.shear(soundCategory); } // Paper - Add drops to shear events - void shear(SoundSource shearedSoundCategory); - - boolean readyForShearing(); -+ // Paper start - custom shear drops; ensure all implementing entities override this -+ default java.util.List generateDefaultDrops() { -+ return java.util.Collections.emptyList(); -+ } -+ // Paper end - custom shear drops - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -index e42b0b19019ef74733fd19b08f882cccff920142..7a82ba6e29fde33841c049e8520300aa66608f34 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -+++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -@@ -122,11 +122,18 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder drops = this.generateDefaultDrops(); -+ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); -+ if (event != null) { -+ if (event.isCancelled()) { -+ return InteractionResult.PASS; -+ } -+ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); - } -+ // Paper end - custom shear drops - // CraftBukkit end -- this.shear(SoundSource.PLAYERS); -+ this.shear(SoundSource.PLAYERS, drops); // Paper - custom shear drops - this.gameEvent(GameEvent.SHEAR, player); - if (!this.level().isClientSide) { - itemstack.hurtAndBreak(1, player, (entityhuman1) -> { -@@ -167,6 +174,22 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder generateDefaultDrops() { -+ List dropEntities = new java.util.ArrayList<>(5); -+ for (int i = 0; i < 5; ++i) { -+ dropEntities.add(new ItemStack(this.getVariant().getBlockState().getBlock())); -+ } -+ return dropEntities; -+ } -+ -+ @Override -+ public void shear(SoundSource shearedSoundCategory, List drops) { // If drops is null, need to generate drops -+ // Paper end - custom shear drops - this.level().playSound((Player) null, (Entity) this, SoundEvents.MOOSHROOM_SHEAR, shearedSoundCategory, 1.0F, 1.0F); - if (!this.level().isClientSide()) { - Cow entitycow = (Cow) EntityType.COW.create(this.level()); -@@ -196,17 +219,12 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder drops = this.generateDefaultDrops(); -+ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); -+ if (event != null) { -+ if (event.isCancelled()) { -+ return InteractionResult.PASS; -+ } -+ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); - } -+ // Paper end - custom shear drops - // CraftBukkit end -- this.shear(SoundSource.PLAYERS); -+ this.shear(SoundSource.PLAYERS, drops); // Paper - this.gameEvent(GameEvent.SHEAR, player); - itemstack.hurtAndBreak(1, player, (entityhuman1) -> { - entityhuman1.broadcastBreakEvent(hand); -@@ -273,13 +280,30 @@ public class Sheep extends Animal implements Shearable { - - @Override - public void shear(SoundSource shearedSoundCategory) { -+ // Paper start - custom shear drops -+ this.shear(shearedSoundCategory, this.generateDefaultDrops()); -+ } -+ -+ @Override -+ public java.util.List generateDefaultDrops() { -+ int count = 1 + this.random.nextInt(3); -+ java.util.List dropEntities = new java.util.ArrayList<>(count); -+ for (int j = 0; j < count; ++j) { -+ dropEntities.add(new ItemStack(Sheep.ITEM_BY_DYE.get(this.getColor()))); -+ } -+ return dropEntities; -+ } -+ -+ @Override -+ public void shear(SoundSource shearedSoundCategory, java.util.List drops) { -+ // Paper end - custom shear drops - this.level().playSound((Player) null, (Entity) this, SoundEvents.SHEEP_SHEAR, shearedSoundCategory, 1.0F, 1.0F); - this.setSheared(true); - int i = 1 + this.random.nextInt(3); - -- for (int j = 0; j < i; ++j) { -+ for (final ItemStack drop : drops) { // Paper - custom shear drops (moved drop generation to separate method) - this.forceDrops = true; // CraftBukkit -- ItemEntity entityitem = this.spawnAtLocation((ItemLike) Sheep.ITEM_BY_DYE.get(this.getColor()), 1); -+ ItemEntity entityitem = this.spawnAtLocation(drop, 1); // Paper - custom shear drops - this.forceDrops = false; // CraftBukkit - - if (entityitem != null) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -index 8adcfc8f6772a32b5915e4a07100e8eb735f907a..b5d6857eaf2bed14adcb5f5e80d91b44eb8b0dcc 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -+++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -@@ -153,11 +153,18 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - - if (itemstack.is(Items.SHEARS) && this.readyForShearing()) { - // CraftBukkit start -- if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) { -- return InteractionResult.PASS; -+ // Paper start - custom shear drops -+ java.util.List drops = this.generateDefaultDrops(); -+ org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); -+ if (event != null) { -+ if (event.isCancelled()) { -+ return InteractionResult.PASS; -+ } -+ drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); - } -+ // Paper end - custom shear drops - // CraftBukkit end -- this.shear(SoundSource.PLAYERS); -+ this.shear(SoundSource.PLAYERS, drops); // Paper - this.gameEvent(GameEvent.SHEAR, player); - if (!this.level().isClientSide) { - itemstack.hurtAndBreak(1, player, (entityhuman1) -> { -@@ -173,12 +180,28 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - - @Override - public void shear(SoundSource shearedSoundCategory) { -+ // Paper start - custom shear drops -+ this.shear(shearedSoundCategory, this.generateDefaultDrops()); -+ } -+ -+ @Override -+ public java.util.List generateDefaultDrops() { -+ return java.util.Collections.singletonList(new ItemStack(Items.CARVED_PUMPKIN)); -+ } -+ -+ @Override -+ public void shear(SoundSource shearedSoundCategory, java.util.List drops) { -+ // Paper end - custom shear drops - this.level().playSound((Player) null, (Entity) this, SoundEvents.SNOW_GOLEM_SHEAR, shearedSoundCategory, 1.0F, 1.0F); - if (!this.level().isClientSide()) { - this.setPumpkin(false); -- this.forceDrops = true; // CraftBukkit -- this.spawnAtLocation(new ItemStack(Items.CARVED_PUMPKIN), 1.7F); -- this.forceDrops = false; // CraftBukkit -+ // Paper start - custom shear drops (moved drop generation to separate method) -+ for (final ItemStack drop : drops) { -+ this.forceDrops = true; -+ this.spawnAtLocation(drop, 1.7F); -+ this.forceDrops = false; -+ } -+ // Paper end - custom shear drops - } - - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 5519ef3b82b5a04536bb2ffb7ce1ba460a2df8d6..9389249662f4c7a8656c60671152cc6969cad96c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1744,20 +1744,20 @@ public class CraftEventFactory { - return event; - } - -- public static BlockShearEntityEvent callBlockShearEntityEvent(Entity animal, org.bukkit.block.Block dispenser, CraftItemStack is) { -- BlockShearEntityEvent bse = new BlockShearEntityEvent(dispenser, animal.getBukkitEntity(), is); -+ public static BlockShearEntityEvent callBlockShearEntityEvent(Entity animal, Block dispenser, CraftItemStack is, List drops) { // Paper - custom shear drops -+ BlockShearEntityEvent bse = new BlockShearEntityEvent(dispenser, animal.getBukkitEntity(), is, Lists.transform(drops, CraftItemStack::asCraftMirror)); // Paper - custom shear drops - Bukkit.getPluginManager().callEvent(bse); - return bse; - } - -- public static boolean handlePlayerShearEntityEvent(net.minecraft.world.entity.player.Player player, Entity sheared, ItemStack shears, InteractionHand hand) { -+ public static PlayerShearEntityEvent handlePlayerShearEntityEvent(net.minecraft.world.entity.player.Player player, Entity sheared, ItemStack shears, InteractionHand hand, List drops) { // Paper - custom shear drops - if (!(player instanceof ServerPlayer)) { -- return true; -+ return null; // Paper - custom shear drops - } - -- PlayerShearEntityEvent event = new PlayerShearEntityEvent((Player) player.getBukkitEntity(), sheared.getBukkitEntity(), CraftItemStack.asCraftMirror(shears), (hand == InteractionHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); -+ PlayerShearEntityEvent event = new PlayerShearEntityEvent((Player) player.getBukkitEntity(), sheared.getBukkitEntity(), CraftItemStack.asCraftMirror(shears), (hand == InteractionHand.OFF_HAND ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND), Lists.transform(drops, CraftItemStack::asCraftMirror)); // Paper - custom shear drops - Bukkit.getPluginManager().callEvent(event); -- return !event.isCancelled(); -+ return event; // Paper - custom shear drops - } - - public static Cancellable handleStatisticsIncrease(net.minecraft.world.entity.player.Player entityHuman, net.minecraft.stats.Stat statistic, int current, int newValue) { -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index e1f9a603e7adf3468faa9bb6d93dd3339327b47e..870954fc59efdc1e0c6b5047f5a89dfaf7522d0e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -62,6 +62,16 @@ public final class CraftItemStack extends ItemStack { - return stack; - } - -+ // Paper start -+ public static java.util.List asNMSCopy(java.util.List originals) { -+ final java.util.List items = new java.util.ArrayList<>(originals.size()); -+ for (final ItemStack original : originals) { -+ items.add(asNMSCopy(original)); -+ } -+ return items; -+ } -+ // Paper end -+ - public static net.minecraft.world.item.ItemStack copyNMSStack(net.minecraft.world.item.ItemStack original, int amount) { - net.minecraft.world.item.ItemStack stack = original.copy(); - stack.setCount(amount); -diff --git a/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java b/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e612515f7709dbe5d1fa5751337cdc34fce10a98 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/entity/ShearableDropsTest.java -@@ -0,0 +1,34 @@ -+package io.papermc.paper.entity; -+ -+import io.github.classgraph.ClassGraph; -+import io.github.classgraph.ClassInfo; -+import io.github.classgraph.MethodInfoList; -+import io.github.classgraph.ScanResult; -+import java.util.ArrayList; -+import net.minecraft.world.entity.Shearable; -+import org.bukkit.support.AbstractTestingBase; -+import org.junit.jupiter.params.ParameterizedTest; -+import org.junit.jupiter.params.provider.MethodSource; -+ -+import static org.junit.jupiter.api.Assertions.assertEquals; -+ -+class ShearableDropsTest extends AbstractTestingBase { -+ -+ static Iterable parameters() { -+ try (ScanResult scanResult = new ClassGraph() -+ .enableClassInfo() -+ .enableMethodInfo() -+ .whitelistPackages("net.minecraft") -+ .scan() -+ ) { -+ return new ArrayList<>(scanResult.getClassesImplementing(Shearable.class.getName())); -+ } -+ } -+ -+ @ParameterizedTest -+ @MethodSource("parameters") -+ void checkShearableDropOverrides(final ClassInfo classInfo) { -+ final MethodInfoList generateDefaultDrops = classInfo.getDeclaredMethodInfo("generateDefaultDrops"); -+ assertEquals(1, generateDefaultDrops.size(), classInfo.getName() + " doesn't implement Shearable#generateDefaultDrops"); -+ } -+} diff --git a/patches/server/0988-Reduce-allocation-of-Vec3D-by-entity-tracker.patch b/patches/server/0982-Reduce-allocation-of-Vec3D-by-entity-tracker.patch similarity index 100% rename from patches/server/0988-Reduce-allocation-of-Vec3D-by-entity-tracker.patch rename to patches/server/0982-Reduce-allocation-of-Vec3D-by-entity-tracker.patch diff --git a/patches/server/0983-Add-PlayerShieldDisableEvent.patch b/patches/server/0983-Add-PlayerShieldDisableEvent.patch deleted file mode 100644 index f01564c4b99b..000000000000 --- a/patches/server/0983-Add-PlayerShieldDisableEvent.patch +++ /dev/null @@ -1,80 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Cryptite -Date: Mon, 1 May 2023 16:22:43 -0500 -Subject: [PATCH] Add PlayerShieldDisableEvent - -Called whenever a players shield is disabled. This is mainly caused by -attacking players or monsters that carry axes. - -The event, while similar to the PlayerItemCooldownEvent, offers other -behaviour and can hence not be implemented as a childtype of said event. -Specifically, cancelling the event prevents the game events from being -sent to the player. - -Plugins listening to just the PlayerItemCooldownEvent may not want said -sideeffects, meaning the disable event cannot share a handlerlist with -the cooldown event - -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index a1ccd300791ccb3f1ef47b771e4fe33542039fea..7c5d7856a7982f0b3cad21f2cb8dde8569d2ec28 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1697,7 +1697,11 @@ public abstract class Mob extends LivingEntity implements Targeting { - float f = 0.25F + (float) EnchantmentHelper.getBlockEfficiency(this) * 0.05F; - - if (this.random.nextFloat() < f) { -- player.getCooldowns().addCooldown(Items.SHIELD, 100); -+ // Paper start - Add PlayerShieldDisableEvent -+ final io.papermc.paper.event.player.PlayerShieldDisableEvent shieldDisableEvent = new io.papermc.paper.event.player.PlayerShieldDisableEvent((org.bukkit.entity.Player) player.getBukkitEntity(), getBukkitEntity(), 100); -+ if (!shieldDisableEvent.callEvent()) return; -+ player.getCooldowns().addCooldown(Items.SHIELD, shieldDisableEvent.getCooldown()); -+ // Paper end - Add PlayerShieldDisableEvent - this.level().broadcastEntityEvent(player, (byte) 30); - } - } -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 d8ef2466e235b8121dd04bde0800ed2dbf8e370c..4f2d4ed485ce0d5f82f562281c40dc6a660e554b 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -971,7 +971,7 @@ public abstract class Player extends LivingEntity { - protected void blockUsingShield(LivingEntity attacker) { - super.blockUsingShield(attacker); - if (attacker.canDisableShield()) { -- this.disableShield(true); -+ this.disableShield(true, attacker); // Paper - Add PlayerShieldDisableEvent - } - - } -@@ -1454,7 +1454,14 @@ public abstract class Player extends LivingEntity { - this.attack(target); - } - -+ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - Add PlayerShieldDisableEvent - public void disableShield(boolean sprinting) { -+ // Paper start - Add PlayerShieldDisableEvent -+ disableShield(sprinting, null); -+ } -+ -+ public void disableShield(boolean sprinting, @Nullable LivingEntity attacker) { -+ // Paper end - Add PlayerShieldDisableEvent - float f = 0.25F + (float) EnchantmentHelper.getBlockEfficiency(this) * 0.05F; - - if (sprinting) { -@@ -1462,7 +1469,16 @@ public abstract class Player extends LivingEntity { - } - - if (this.random.nextFloat() < f) { -- this.getCooldowns().addCooldown(Items.SHIELD, 100); -+ // Paper start - Add PlayerShieldDisableEvent -+ final org.bukkit.entity.Entity finalAttacker = attacker != null ? attacker.getBukkitEntity() : null; -+ if (finalAttacker != null) { -+ final io.papermc.paper.event.player.PlayerShieldDisableEvent shieldDisableEvent = new io.papermc.paper.event.player.PlayerShieldDisableEvent((org.bukkit.entity.Player) getBukkitEntity(), finalAttacker, 100); -+ if (!shieldDisableEvent.callEvent()) return; -+ this.getCooldowns().addCooldown(Items.SHIELD, shieldDisableEvent.getCooldown()); -+ } else { -+ this.getCooldowns().addCooldown(Items.SHIELD, 100); -+ } -+ // Paper end - Add PlayerShieldDisableEvent - this.stopUsingItem(); - this.level().broadcastEntityEvent(this, (byte) 30); - } diff --git a/patches/server/0983-Rewrite-dataconverter-system.patch b/patches/server/0983-Rewrite-dataconverter-system.patch new file mode 100644 index 000000000000..0d6247320813 --- /dev/null +++ b/patches/server/0983-Rewrite-dataconverter-system.patch @@ -0,0 +1,24963 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 19 Jun 2021 10:43:01 -0700 +Subject: [PATCH] Rewrite dataconverter system + +Please see https://github.com/PaperMC/DataConverter +for details. + +diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java b/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1863c606be715683d53863a0c9293525d199c9cf +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java +@@ -0,0 +1,54 @@ ++package ca.spottedleaf.dataconverter.converters; ++ ++import java.util.Comparator; ++ ++public abstract class DataConverter { ++ ++ public static final Comparator> LOWEST_VERSION_COMPARATOR = (x, y) -> { ++ return Long.compare(x.getEncodedVersion(), y.getEncodedVersion()); ++ }; ++ ++ protected final int toVersion; ++ protected final int versionStep; ++ ++ public DataConverter(final int toVersion) { ++ this.toVersion = toVersion; ++ this.versionStep = 0; ++ } ++ ++ public DataConverter(final int toVersion, final int versionStep) { ++ this.toVersion = toVersion; ++ this.versionStep = versionStep; ++ } ++ ++ public final int getToVersion() { ++ return this.toVersion; ++ } ++ ++ public final int getVersionStep() { ++ return this.versionStep; ++ } ++ ++ public final long getEncodedVersion() { ++ return encodeVersions(this.toVersion, this.versionStep); ++ } ++ ++ public abstract R convert(final T data, final long sourceVersion, final long toVersion); ++ ++ // step must be in the lower bits, so that encodeVersions(version, step) < encodeVersions(version, step + 1) ++ public static long encodeVersions(final int version, final int step) { ++ return ((long)version << 32) | (step & 0xFFFFFFFFL); ++ } ++ ++ public static int getVersion(final long encoded) { ++ return (int)(encoded >>> 32); ++ } ++ ++ public static int getStep(final long encoded) { ++ return (int)encoded; ++ } ++ ++ public static String encodedToString(final long encoded) { ++ return getVersion(encoded) + "." + getStep(encoded); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b92c5c66ad3a5198873f98287a5ced71c231d09 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java +@@ -0,0 +1,9 @@ ++package ca.spottedleaf.dataconverter.converters.datatypes; ++ ++public interface DataHook { ++ ++ public R preHook(final T data, final long fromVersion, final long toVersion); ++ ++ public R postHook(final T data, final long fromVersion, final long toVersion); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b56a7f9ace3b947fed49101b6e9936721fb99ea5 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java +@@ -0,0 +1,7 @@ ++package ca.spottedleaf.dataconverter.converters.datatypes; ++ ++public abstract class DataType { ++ ++ public abstract R convert(final T data, final long fromVersion, final long toVersion); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cf9fae4451ead4860343b915fb70e3a7cdf0de31 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java +@@ -0,0 +1,9 @@ ++package ca.spottedleaf.dataconverter.converters.datatypes; ++ ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public interface DataWalker { ++ ++ public MapType walk(final MapType data, final long fromVersion, final long toVersion); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dde9d36bf6212196caa18f3c9c535aec330a33ed +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java +@@ -0,0 +1,79 @@ ++package ca.spottedleaf.dataconverter.minecraft; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataType; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCDataType; ++import ca.spottedleaf.dataconverter.types.json.JsonMapType; ++import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; ++import com.google.gson.JsonObject; ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import net.minecraft.nbt.CompoundTag; ++ ++public final class MCDataConverter { ++ ++ private static final LongArrayList BREAKPOINTS = MCVersionRegistry.getBreakpoints(); ++ ++ public static T copy(final T type) { ++ if (type instanceof CompoundTag) { ++ return (T)((CompoundTag)type).copy(); ++ } else if (type instanceof JsonObject) { ++ return (T)((JsonObject)type).deepCopy(); ++ } ++ ++ return type; ++ } ++ ++ public static CompoundTag convertTag(final MCDataType type, final CompoundTag data, final int fromVersion, final int toVersion) { ++ final NBTMapType wrapped = new NBTMapType(data); ++ ++ final NBTMapType replaced = (NBTMapType)convert(type, wrapped, fromVersion, toVersion); ++ ++ return replaced == null ? wrapped.getTag() : replaced.getTag(); ++ } ++ ++ public static JsonObject convertJson(final MCDataType type, final JsonObject data, final boolean compressed, final int fromVersion, final int toVersion) { ++ final JsonMapType wrapped = new JsonMapType(data, compressed); ++ ++ final JsonMapType replaced = (JsonMapType)convert(type, wrapped, fromVersion, toVersion); ++ ++ return replaced == null ? wrapped.getJson() : replaced.getJson(); ++ } ++ ++ public static R convert(final DataType type, final T data, int fromVersion, final int toVersion) { ++ Object ret = data; ++ ++ long currentVersion = DataConverter.encodeVersions(fromVersion < 99 ? 99 : fromVersion, Integer.MAX_VALUE); ++ final long nextVersion = DataConverter.encodeVersions(toVersion, Integer.MAX_VALUE); ++ ++ for (int i = 0, len = BREAKPOINTS.size(); i < len; ++i) { ++ final long breakpoint = BREAKPOINTS.getLong(i); ++ ++ if (currentVersion >= breakpoint) { ++ continue; ++ } ++ ++ final Object converted = type.convert((T)ret, currentVersion, Math.min(nextVersion, breakpoint - 1)); ++ if (converted != null) { ++ ret = converted; ++ } ++ ++ currentVersion = Math.min(nextVersion, breakpoint - 1); ++ ++ if (currentVersion == nextVersion) { ++ break; ++ } ++ } ++ ++ if (currentVersion != nextVersion) { ++ final Object converted = type.convert((T)ret, currentVersion, nextVersion); ++ if (converted != null) { ++ ret = converted; ++ } ++ } ++ ++ return (R)ret; ++ } ++ ++ private MCDataConverter() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4a392b3d53e330bf22100d57aec7ee1755e80a11 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java +@@ -0,0 +1,388 @@ ++package ca.spottedleaf.dataconverter.minecraft; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import com.mojang.logging.LogUtils; ++import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.ints.IntArrayList; ++import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet; ++import it.unimi.dsi.fastutil.ints.IntRBTreeSet; ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import it.unimi.dsi.fastutil.longs.LongComparator; ++import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; ++import org.slf4j.Logger; ++import java.lang.reflect.Field; ++import java.util.Arrays; ++import java.util.Locale; ++ ++public final class MCVersionRegistry { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final Int2ObjectLinkedOpenHashMap VERSION_NAMES = new Int2ObjectLinkedOpenHashMap<>(); ++ protected static final IntArrayList VERSION_LIST; ++ protected static final LongArrayList DATA_VERSION_LIST; ++ ++ protected static final IntArrayList DATACONVERTER_VERSIONS_LIST; ++ protected static final IntLinkedOpenHashSet DATACONVERTER_VERSIONS_MAJOR = new IntLinkedOpenHashSet(); ++ protected static final LongLinkedOpenHashSet DATACONVERTER_VERSIONS = new LongLinkedOpenHashSet(); ++ protected static final Int2ObjectLinkedOpenHashMap SUBVERSIONS = new Int2ObjectLinkedOpenHashMap<>(); ++ protected static final LongArrayList BREAKPOINTS = new LongArrayList(); ++ static { ++ // Note: Some of these are nameless. ++ // Unless a data version is specified here, it will NOT have converters ran for it. Please add them on update! ++ final int[] converterVersions = new int[] { ++ 99, ++ 100, ++ 101, ++ 102, ++ 105, ++ 106, ++ 107, ++ 108, ++ 109, ++ 110, ++ 111, ++ 113, ++ 135, ++ 143, ++ 147, ++ 165, ++ 501, ++ 502, ++ 505, ++ 700, ++ 701, ++ 702, ++ 703, ++ 704, ++ 705, ++ 804, ++ 806, ++ 808, ++ 808, ++ 813, ++ 816, ++ 820, ++ 1022, ++ 1125, ++ 1344, ++ 1446, ++ 1450, ++ 1451, ++ 1451, ++ 1451, ++ 1451, ++ 1451, ++ 1451, ++ 1451, ++ 1451, ++ 1451, ++ 1456, ++ 1458, ++ 1460, ++ 1466, ++ 1470, ++ 1474, ++ 1475, ++ 1480, ++ 1481, ++ 1483, ++ 1484, ++ 1486, ++ 1487, ++ 1488, ++ 1490, ++ 1492, ++ 1494, ++ 1496, ++ 1500, ++ 1501, ++ 1502, ++ 1506, ++ 1510, ++ 1514, ++ 1515, ++ 1624, ++ 1800, ++ 1801, ++ 1802, ++ 1803, ++ 1904, ++ 1905, ++ 1906, ++ 1909, ++ 1911, ++ 1914, ++ 1917, ++ 1918, ++ 1920, ++ 1925, ++ 1928, ++ 1929, ++ 1931, ++ 1936, ++ 1946, ++ 1948, ++ 1953, ++ 1955, ++ 1961, ++ 1963, ++ 2100, ++ 2202, ++ 2209, ++ 2211, ++ 2218, ++ 2501, ++ 2502, ++ 2503, ++ 2505, ++ 2508, ++ 2509, ++ 2511, ++ 2514, ++ 2516, ++ 2518, ++ 2519, ++ 2522, ++ 2523, ++ 2527, ++ 2528, ++ 2529, ++ 2531, ++ 2533, ++ 2535, ++ 2538, ++ 2550, ++ 2551, ++ 2552, ++ 2553, ++ 2558, ++ 2568, ++ 2671, ++ 2679, ++ 2680, ++ 2684, ++ 2686, ++ 2688, ++ 2690, ++ 2691, ++ 2693, ++ 2696, ++ 2700, ++ 2701, ++ 2702, ++ 2704, ++ 2707, ++ 2710, ++ 2717, ++ 2825, ++ 2831, ++ 2832, ++ 2833, ++ 2838, ++ 2841, ++ 2842, ++ 2843, ++ 2846, ++ 2852, ++ 2967, ++ 2970, ++ 3077, ++ 3078, ++ 3081, ++ 3082, ++ 3083, ++ 3084, ++ 3086, ++ 3087, ++ 3088, ++ 3090, ++ 3093, ++ 3094, ++ 3097, ++ 3108, ++ 3201, ++ 3203, ++ 3204, ++ 3209, ++ 3214, ++ 3319, ++ 3322, ++ 3438, ++ 3439, ++ 3440, ++ 3441, ++ 3447, ++ 3448, ++ 3450, ++ 3451, ++ 3459, ++ 3564, ++ 3565, ++ 3566, ++ 3568, ++ 3683, ++ 3685, ++ 3692, ++ // All up to 1.20.3 ++ }; ++ Arrays.sort(converterVersions); ++ ++ DATACONVERTER_VERSIONS_MAJOR.addAll(DATACONVERTER_VERSIONS_LIST = new IntArrayList(converterVersions)); ++ ++ // add sub versions ++ registerSubVersion(MCVersions.V16W38A + 1, 1); ++ ++ registerSubVersion(MCVersions.V17W47A, 1); ++ registerSubVersion(MCVersions.V17W47A, 2); ++ registerSubVersion(MCVersions.V17W47A, 3); ++ registerSubVersion(MCVersions.V17W47A, 4); ++ registerSubVersion(MCVersions.V17W47A, 5); ++ registerSubVersion(MCVersions.V17W47A, 6); ++ registerSubVersion(MCVersions.V17W47A, 7); ++ ++ // register breakpoints here ++ // for all major releases after 1.16, add them. this reduces the work required to determine if a breakpoint ++ // is needed for new converters ++ ++ // Too much changed in this version. ++ registerBreakpoint(MCVersions.V17W47A); ++ registerBreakpoint(MCVersions.V17W47A, Integer.MAX_VALUE); ++ ++ // final release of major version ++ registerBreakpoint(MCVersions.V1_17_1, Integer.MAX_VALUE); ++ ++ // final release of major version ++ registerBreakpoint(MCVersions.V1_18_2, Integer.MAX_VALUE); ++ ++ // final release of major version ++ registerBreakpoint(MCVersions.V1_19_4, Integer.MAX_VALUE); ++ } ++ ++ static { ++ final Field[] fields = MCVersions.class.getDeclaredFields(); ++ for (final Field field : fields) { ++ final String name = field.getName(); ++ final int value; ++ try { ++ value = field.getInt(null); ++ } catch (final Exception ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ if (VERSION_NAMES.containsKey(value) && value != MCVersions.V15W33B) { // Mojang registered 15w33a and 15w33b under the same id. ++ LOGGER.warn("Error registering version \"" + name + "\", version number '" + value + "' is already associated with \"" + VERSION_NAMES.get(value) + "\""); ++ } ++ ++ VERSION_NAMES.put(value, name.substring(1).replace("_PRE", "-PRE").replace("_RC", "-RC").replace('_', '.').toLowerCase(Locale.ROOT)); ++ } ++ ++ for (final int version : DATACONVERTER_VERSIONS_MAJOR) { ++ if (VERSION_NAMES.containsKey(version)) { ++ continue; ++ } ++ ++ // find closest greatest version above this one ++ int closest = Integer.MAX_VALUE; ++ String closestName = null; ++ for (final int v : VERSION_NAMES.keySet()) { ++ if (v > version && v < closest) { ++ closest = v; ++ closestName = VERSION_NAMES.get(v); ++ } ++ } ++ ++ if (closestName == null) { ++ VERSION_NAMES.put(version, "unregistered_v" + version); ++ } else { ++ VERSION_NAMES.put(version, closestName + "-dev" + (closest - version)); ++ } ++ } ++ ++ // Explicit override for V99, as 99 is very special. ++ VERSION_NAMES.put(99, "pre_converter"); ++ ++ VERSION_LIST = new IntArrayList(new IntRBTreeSet(VERSION_NAMES.keySet())); ++ ++ DATA_VERSION_LIST = new LongArrayList(); ++ for (final int version : VERSION_LIST) { ++ DATA_VERSION_LIST.add(DataConverter.encodeVersions(version, 0)); ++ ++ final IntArrayList subVersions = SUBVERSIONS.get(version); ++ if (subVersions == null) { ++ continue; ++ } ++ ++ for (final int step : subVersions) { ++ DATA_VERSION_LIST.add(DataConverter.encodeVersions(version, step)); ++ } ++ } ++ ++ DATA_VERSION_LIST.sort((LongComparator)null); ++ ++ for (final int version : DATACONVERTER_VERSIONS_MAJOR) { ++ DATACONVERTER_VERSIONS.add(DataConverter.encodeVersions(version, 0)); ++ ++ final IntArrayList subVersions = SUBVERSIONS.get(version); ++ if (subVersions == null) { ++ continue; ++ } ++ ++ for (final int step : subVersions) { ++ DATACONVERTER_VERSIONS.add(DataConverter.encodeVersions(version, step)); ++ } ++ } ++ } ++ ++ private static void registerSubVersion(final int version, final int step) { ++ if (DATA_VERSION_LIST != null) { ++ throw new IllegalStateException("Added too late!"); ++ } ++ SUBVERSIONS.computeIfAbsent(version, (final int keyInMap) -> { ++ return new IntArrayList(); ++ }).add(step); ++ } ++ ++ private static void registerBreakpoint(final int version) { ++ registerBreakpoint(version, 0); ++ } ++ ++ private static void registerBreakpoint(final int version, final int step) { ++ BREAKPOINTS.add(DataConverter.encodeVersions(version, step)); ++ } ++ ++ // returns only versions that have dataconverters ++ public static boolean hasDataConverters(final int version) { ++ return DATACONVERTER_VERSIONS_MAJOR.contains(version); ++ } ++ ++ public String getVersionName(final int version) { ++ return VERSION_NAMES.get(version); ++ } ++ ++ public boolean isRegisteredVersion(final int version) { ++ return VERSION_NAMES.containsKey(version); ++ } ++ ++ public static IntArrayList getVersionList() { ++ return VERSION_LIST; ++ } ++ ++ public static LongArrayList getDataVersionList() { ++ return DATA_VERSION_LIST; ++ } ++ ++ public static int getMaxVersion() { ++ return VERSION_LIST.getInt(VERSION_LIST.size() - 1); ++ } ++ ++ public static LongArrayList getBreakpoints() { ++ return BREAKPOINTS; ++ } ++ ++ public static void checkVersion(final long version) { ++ if (!DATACONVERTER_VERSIONS.contains(version)) { ++ throw new IllegalStateException("Version " + DataConverter.encodedToString(version) + " is not registered to have dataconverters, yet has a dataconverter"); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9b92547b72c0e188293fcc0c7b8ad1e133520c70 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java +@@ -0,0 +1,499 @@ ++package ca.spottedleaf.dataconverter.minecraft; ++ ++@SuppressWarnings("unused") ++public final class MCVersions { ++ ++ /* https://minecraft.wiki/wiki/Data_version */ ++ ++ public static final int V15W32A = 100; ++ public static final int V15W32B = 103; ++ public static final int V15W32C = 104; ++ public static final int V15W33A = 111; ++ public static final int V15W33B = 111; ++ public static final int V15W33C = 112; ++ public static final int V15W34A = 114; ++ public static final int V15W34B = 115; ++ public static final int V15W34C = 116; ++ public static final int V15W34D = 117; ++ public static final int V15W35A = 118; ++ public static final int V15W35B = 119; ++ public static final int V15W35C = 120; ++ public static final int V15W35D = 121; ++ public static final int V15W35E = 122; ++ public static final int V15W36A = 123; ++ public static final int V15W36B = 124; ++ public static final int V15W36C = 125; ++ public static final int V15W36D = 126; ++ public static final int V15W37A = 127; ++ public static final int V15W38A = 128; ++ public static final int V15W38B = 129; ++ public static final int V15W39A = 130; ++ public static final int V15W39B = 131; ++ public static final int V15W39C = 132; ++ public static final int V15W40A = 133; ++ public static final int V15W40B = 134; ++ public static final int V15W41A = 136; ++ public static final int V15W41B = 137; ++ public static final int V15W42A = 138; ++ public static final int V15W43A = 139; ++ public static final int V15W43B = 140; ++ public static final int V15W43C = 141; ++ public static final int V15W44A = 142; ++ public static final int V15W44B = 143; ++ public static final int V15W45A = 145; ++ public static final int V15W46A = 146; ++ public static final int V15W47A = 148; ++ public static final int V15W47B = 149; ++ public static final int V15W47C = 150; ++ public static final int V15W49A = 151; ++ public static final int V15W49B = 152; ++ public static final int V15W50A = 153; ++ public static final int V15W51A = 154; ++ public static final int V15W51B = 155; ++ public static final int V16W02A = 156; ++ public static final int V16W03A = 157; ++ public static final int V16W04A = 158; ++ public static final int V16W05A = 159; ++ public static final int V16W05B = 160; ++ public static final int V16W06A = 161; ++ public static final int V16W07A = 162; ++ public static final int V16W07B = 163; ++ public static final int V1_9_PRE1 = 164; ++ public static final int V1_9_PRE2 = 165; ++ public static final int V1_9_PRE3 = 167; ++ public static final int V1_9_PRE4 = 168; ++ public static final int V1_9 = 169; ++ public static final int V1_9_1_PRE1 = 170; ++ public static final int V1_9_1_PRE2 = 171; ++ public static final int V1_9_1_PRE3 = 172; ++ public static final int V1_9_1 = 175; ++ public static final int V1_9_2 = 176; ++ public static final int V16W14A = 177; ++ public static final int V16W15A = 178; ++ public static final int V16W15B = 179; ++ public static final int V1_9_3_PRE1 = 180; ++ public static final int V1_9_3_PRE2 = 181; ++ public static final int V1_9_3_PRE3 = 182; ++ public static final int V1_9_3 = 183; ++ public static final int V1_9_4 = 184; ++ public static final int V16W20A = 501; ++ public static final int V16W21A = 503; ++ public static final int V16W21B = 504; ++ public static final int V1_10_PRE1 = 506; ++ public static final int V1_10_PRE2 = 507; ++ public static final int V1_10 = 510; ++ public static final int V1_10_1 = 511; ++ public static final int V1_10_2 = 512; ++ public static final int V16W32A = 800; ++ public static final int V16W32B = 801; ++ public static final int V16W33A = 802; ++ public static final int V16W35A = 803; ++ public static final int V16W36A = 805; ++ public static final int V16W38A = 807; ++ public static final int V16W39A = 809; ++ public static final int V16W39B = 811; ++ public static final int V16W39C = 812; ++ public static final int V16W40A = 813; ++ public static final int V16W41A = 814; ++ public static final int V16W42A = 815; ++ public static final int V16W43A = 816; ++ public static final int V16W44A = 817; ++ public static final int V1_11_PRE1 = 818; ++ public static final int V1_11 = 819; ++ public static final int V16W50A = 920; ++ public static final int V1_11_1 = 921; ++ public static final int V1_11_2 = 922; ++ public static final int V17W06A = 1022; ++ public static final int V17W13A = 1122; ++ public static final int V17W13B = 1123; ++ public static final int V17W14A = 1124; ++ public static final int V17W15A = 1125; ++ public static final int V17W16A = 1126; ++ public static final int V17W16B = 1127; ++ public static final int V17W17A = 1128; ++ public static final int V17W17B = 1129; ++ public static final int V17W18A = 1130; ++ public static final int V17W18B = 1131; ++ public static final int V1_12_PRE1 = 1132; ++ public static final int V1_12_PRE2 = 1133; ++ public static final int V1_12_PRE3 = 1134; ++ public static final int V1_12_PRE4 = 1135; ++ public static final int V1_12_PRE5 = 1136; ++ public static final int V1_12_PRE6 = 1137; ++ public static final int V1_12_PRE7 = 1138; ++ public static final int V1_12 = 1139; ++ public static final int V17W31A = 1239; ++ public static final int V1_12_1_PRE1 = 1240; ++ public static final int V1_12_1 = 1241; ++ public static final int V1_12_2_PRE1 = 1341; ++ public static final int V1_12_2_PRE2 = 1342; ++ public static final int V1_12_2 = 1343; ++ public static final int V17W43A = 1444; ++ public static final int V17W43B = 1445; ++ public static final int V17W45A = 1447; ++ public static final int V17W45B = 1448; ++ public static final int V17W46A = 1449; ++ public static final int V17W47A = 1451; ++ public static final int V17W47B = 1452; ++ public static final int V17W48A = 1453; ++ public static final int V17W49A = 1454; ++ public static final int V17W49B = 1455; ++ public static final int V17W50A = 1457; ++ public static final int V18W01A = 1459; ++ public static final int V18W02A = 1461; ++ public static final int V18W03A = 1462; ++ public static final int V18W03B = 1463; ++ public static final int V18W05A = 1464; ++ public static final int V18W06A = 1466; ++ public static final int V18W07A = 1467; ++ public static final int V18W07B = 1468; ++ public static final int V18W07C = 1469; ++ public static final int V18W08A = 1470; ++ public static final int V18W08B = 1471; ++ public static final int V18W09A = 1472; ++ public static final int V18W10A = 1473; ++ public static final int V18W10B = 1474; ++ public static final int V18W10C = 1476; ++ public static final int V18W10D = 1477; ++ public static final int V18W11A = 1478; ++ public static final int V18W14A = 1479; ++ public static final int V18W14B = 1481; ++ public static final int V18W15A = 1482; ++ public static final int V18W16A = 1483; ++ public static final int V18W19A = 1484; ++ public static final int V18W19B = 1485; ++ public static final int V18W20A = 1489; ++ public static final int V18W20B = 1491; ++ public static final int V18W20C = 1493; ++ public static final int V18W21A = 1495; ++ public static final int V18W21B = 1496; ++ public static final int V18W22A = 1497; ++ public static final int V18W22B = 1498; ++ public static final int V18W22C = 1499; ++ public static final int V1_13_PRE1 = 1501; ++ public static final int V1_13_PRE2 = 1502; ++ public static final int V1_13_PRE3 = 1503; ++ public static final int V1_13_PRE4 = 1504; ++ public static final int V1_13_PRE5 = 1511; ++ public static final int V1_13_PRE6 = 1512; ++ public static final int V1_13_PRE7 = 1513; ++ public static final int V1_13_PRE8 = 1516; ++ public static final int V1_13_PRE9 = 1517; ++ public static final int V1_13_PRE10 = 1518; ++ public static final int V1_13 = 1519; ++ public static final int V18W30A = 1620; ++ public static final int V18W30B = 1621; ++ public static final int V18W31A = 1622; ++ public static final int V18W32A = 1623; ++ public static final int V18W33A = 1625; ++ public static final int V1_13_1_PRE1 = 1626; ++ public static final int V1_13_1_PRE2 = 1627; ++ public static final int V1_13_1 = 1628; ++ public static final int V1_13_2_PRE1 = 1629; ++ public static final int V1_13_2_PRE2 = 1630; ++ public static final int V1_13_2 = 1631; ++ public static final int V18W43A = 1901; ++ public static final int V18W43B = 1902; ++ public static final int V18W43C = 1903; ++ public static final int V18W44A = 1907; ++ public static final int V18W45A = 1908; ++ public static final int V18W46A = 1910; ++ public static final int V18W47A = 1912; ++ public static final int V18W47B = 1913; ++ public static final int V18W48A = 1914; ++ public static final int V18W48B = 1915; ++ public static final int V18W49A = 1916; ++ public static final int V18W50A = 1919; ++ public static final int V19W02A = 1921; ++ public static final int V19W03A = 1922; ++ public static final int V19W03B = 1923; ++ public static final int V19W03C = 1924; ++ public static final int V19W04A = 1926; ++ public static final int V19W04B = 1927; ++ public static final int V19W05A = 1930; ++ public static final int V19W06A = 1931; ++ public static final int V19W07A = 1932; ++ public static final int V19W08A = 1933; ++ public static final int V19W08B = 1934; ++ public static final int V19W09A = 1935; ++ public static final int V19W11A = 1937; ++ public static final int V19W11B = 1938; ++ public static final int V19W12A = 1940; ++ public static final int V19W12B = 1941; ++ public static final int V19W13A = 1942; ++ public static final int V19W13B = 1943; ++ public static final int V19W14A = 1944; ++ public static final int V19W14B = 1945; ++ public static final int V1_14_PRE1 = 1947; ++ public static final int V1_14_PRE2 = 1948; ++ public static final int V1_14_PRE3 = 1949; ++ public static final int V1_14_PRE4 = 1950; ++ public static final int V1_14_PRE5 = 1951; ++ public static final int V1_14 = 1952; ++ public static final int V1_14_1_PRE1 = 1955; ++ public static final int V1_14_1_PRE2 = 1956; ++ public static final int V1_14_1 = 1957; ++ public static final int V1_14_2_PRE1 = 1958; ++ public static final int V1_14_2_PRE2 = 1959; ++ public static final int V1_14_2_PRE3 = 1960; ++ public static final int V1_14_2_PRE4 = 1962; ++ public static final int V1_14_2 = 1963; ++ public static final int V1_14_3_PRE1 = 1964; ++ public static final int V1_14_3_PRE2 = 1965; ++ public static final int V1_14_3_PRE3 = 1966; ++ public static final int V1_14_3_PRE4 = 1967; ++ public static final int V1_14_3 = 1968; ++ public static final int V1_14_4_PRE1 = 1969; ++ public static final int V1_14_4_PRE2 = 1970; ++ public static final int V1_14_4_PRE3 = 1971; ++ public static final int V1_14_4_PRE4 = 1972; ++ public static final int V1_14_4_PRE5 = 1973; ++ public static final int V1_14_4_PRE6 = 1974; ++ public static final int V1_14_4_PRE7 = 1975; ++ public static final int V1_14_4 = 1976; ++ public static final int V19W34A = 2200; ++ public static final int V19W35A = 2201; ++ public static final int V19W36A = 2203; ++ public static final int V19W37A = 2204; ++ public static final int V19W38A = 2205; ++ public static final int V19W38B = 2206; ++ public static final int V19W39A = 2207; ++ public static final int V19W40A = 2208; ++ public static final int V19W41A = 2210; ++ public static final int V19W42A = 2212; ++ public static final int V19W44A = 2213; ++ public static final int V19W45A = 2214; ++ public static final int V19W45B = 2215; ++ public static final int V19W46A = 2216; ++ public static final int V19W46B = 2217; ++ public static final int V1_15_PRE1 = 2218; ++ public static final int V1_15_PRE2 = 2219; ++ public static final int V1_15_PRE3 = 2220; ++ public static final int V1_15_PRE4 = 2221; ++ public static final int V1_15_PRE5 = 2222; ++ public static final int V1_15_PRE6 = 2223; ++ public static final int V1_15_PRE7 = 2224; ++ public static final int V1_15 = 2225; ++ public static final int V1_15_1_PRE1 = 2226; ++ public static final int V1_15_1 = 2227; ++ public static final int V1_15_2_PRE1 = 2228; ++ public static final int V1_15_2_PRE2 = 2229; ++ public static final int V1_15_2 = 2230; ++ public static final int V20W06A = 2504; ++ public static final int V20W07A = 2506; ++ public static final int V20W08A = 2507; ++ public static final int V20W09A = 2510; ++ public static final int V20W10A = 2512; ++ public static final int V20W11A = 2513; ++ public static final int V20W12A = 2515; ++ public static final int V20W13A = 2520; ++ public static final int V20W13B = 2521; ++ public static final int V20W14A = 2524; ++ public static final int V20W15A = 2525; ++ public static final int V20W16A = 2526; ++ public static final int V20W17A = 2529; ++ public static final int V20W18A = 2532; ++ public static final int V20W19A = 2534; ++ public static final int V20W20A = 2536; ++ public static final int V20W20B = 2537; ++ public static final int V20W21A = 2554; ++ public static final int V20W22A = 2555; ++ public static final int V1_16_PRE1 = 2556; ++ public static final int V1_16_PRE2 = 2557; ++ public static final int V1_16_PRE3 = 2559; ++ public static final int V1_16_PRE4 = 2560; ++ public static final int V1_16_PRE5 = 2561; ++ public static final int V1_16_PRE6 = 2562; ++ public static final int V1_16_PRE7 = 2563; ++ public static final int V1_16_PRE8 = 2564; ++ public static final int V1_16_RC1 = 2565; ++ public static final int V1_16 = 2566; ++ public static final int V1_16_1 = 2567; ++ public static final int V20W27A = 2569; ++ public static final int V20W28A = 2570; ++ public static final int V20W29A = 2571; ++ public static final int V20W30A = 2572; ++ public static final int V1_16_2_PRE1 = 2573; ++ public static final int V1_16_2_PRE2 = 2574; ++ public static final int V1_16_2_PRE3 = 2575; ++ public static final int V1_16_2_RC1 = 2576; ++ public static final int V1_16_2_RC2 = 2577; ++ public static final int V1_16_2 = 2578; ++ public static final int V1_16_3_RC1 = 2579; ++ public static final int V1_16_3 = 2580; ++ public static final int V1_16_4_PRE1 = 2581; ++ public static final int V1_16_4_PRE2 = 2582; ++ public static final int V1_16_4_RC1 = 2583; ++ public static final int V1_16_4 = 2584; ++ public static final int V1_16_5_RC1 = 2585; ++ public static final int V1_16_5 = 2586; ++ public static final int V20W45A = 2681; ++ public static final int V20W46A = 2682; ++ public static final int V20W48A = 2683; ++ public static final int V20W49A = 2685; ++ public static final int V20W51A = 2687; ++ public static final int V21W03A = 2689; ++ public static final int V21W05A = 2690; ++ public static final int V21W05B = 2692; ++ public static final int V21W06A = 2694; ++ public static final int V21W07A = 2695; ++ public static final int V21W08A = 2697; ++ public static final int V21W08B = 2698; ++ public static final int V21W10A = 2699; ++ public static final int V21W11A = 2703; ++ public static final int V21W13A = 2705; ++ public static final int V21W14A = 2706; ++ public static final int V21W15A = 2709; ++ public static final int V21W16A = 2711; ++ public static final int V21W17A = 2712; ++ public static final int V21W18A = 2713; ++ public static final int V21W19A = 2714; ++ public static final int V21W20A = 2715; ++ public static final int V1_17_PRE1 = 2716; ++ public static final int V1_17_PRE2 = 2718; ++ public static final int V1_17_PRE3 = 2719; ++ public static final int V1_17_PRE4 = 2720; ++ public static final int V1_17_PRE5 = 2721; ++ public static final int V1_17_RC1 = 2722; ++ public static final int V1_17_RC2 = 2723; ++ public static final int V1_17 = 2724; ++ public static final int V1_17_1_PRE1 = 2725; ++ public static final int V1_17_1_PRE2 = 2726; ++ public static final int V1_17_1_PRE3 = 2727; ++ public static final int V1_17_1_RC1 = 2728; ++ public static final int V1_17_1_RC2 = 2729; ++ public static final int V1_17_1 = 2730; ++ public static final int V21W37A = 2834; ++ public static final int V21W38A = 2835; ++ public static final int V21W39A = 2836; ++ public static final int V21W40A = 2838; ++ public static final int V21W41A = 2839; ++ public static final int V21W42A = 2840; ++ public static final int V21W43A = 2844; ++ public static final int V21W44A = 2845; ++ public static final int V1_18_PRE1 = 2847; ++ public static final int V1_18_PRE2 = 2848; ++ public static final int V1_18_PRE3 = 2849; ++ public static final int V1_18_PRE4 = 2850; ++ public static final int V1_18_PRE5 = 2851; ++ public static final int V1_18_PRE6 = 2853; ++ public static final int V1_18_PRE7 = 2854; ++ public static final int V1_18_PRE8 = 2855; ++ public static final int V1_18_RC1 = 2856; ++ public static final int V1_18_RC2 = 2857; ++ public static final int V1_18_RC3 = 2858; ++ public static final int V1_18_RC4 = 2859; ++ public static final int V1_18 = 2860; ++ public static final int V1_18_1_PRE1 = 2861; ++ public static final int V1_18_1_RC1 = 2862; ++ public static final int V1_18_1_RC2 = 2863; ++ public static final int V1_18_1_RC3 = 2864; ++ public static final int V1_18_1 = 2865; ++ public static final int V22W03A = 2966; ++ public static final int V22W05A = 2967; ++ public static final int V22W06A = 2968; ++ public static final int V22W07A = 2969; ++ public static final int V1_18_2_PRE1 = 2971; ++ public static final int V1_18_2_PRE2 = 2972; ++ public static final int V1_18_2_PRE3 = 2973; ++ public static final int V1_18_2_RC1 = 2974; ++ public static final int V1_18_2 = 2975; ++ public static final int V22W11A = 3080; ++ public static final int V22W12A = 3082; ++ public static final int V22W13A = 3085; ++ public static final int V22W14A = 3088; ++ public static final int V22W15A = 3089; ++ public static final int V22W16A = 3091; ++ public static final int V22W16B = 3092; ++ public static final int V22W17A = 3093; ++ public static final int V22W18A = 3095; ++ public static final int V22W19A = 3096; ++ public static final int V1_19_PRE1 = 3098; ++ public static final int V1_19_PRE2 = 3099; ++ public static final int V1_19_PRE3 = 3100; ++ public static final int V1_19_PRE4 = 3101; ++ public static final int V1_19_PRE5 = 3102; ++ public static final int V1_19_RC1 = 3103; ++ public static final int V1_19_RC2 = 3104; ++ public static final int V1_19 = 3105; ++ public static final int V22W24A = 3106; ++ public static final int V1_19_1_PRE1 = 3107; ++ public static final int V1_19_1_RC1 = 3109; ++ public static final int V1_19_1_PRE2 = 3110; ++ public static final int V1_19_1_PRE3 = 3111; ++ public static final int V1_19_1_PRE4 = 3112; ++ public static final int V1_19_1_PRE5 = 3113; ++ public static final int V1_19_1_PRE6 = 3114; ++ public static final int V1_19_1_RC2 = 3115; ++ public static final int V1_19_1_RC3 = 3116; ++ public static final int V1_19_1 = 3117; ++ public static final int V1_19_2_RC1 = 3118; ++ public static final int V1_19_2_RC2 = 3119; ++ public static final int V1_19_2 = 3120; ++ public static final int V22W42A = 3205; ++ public static final int V22W43A = 3206; ++ public static final int V22W44A = 3207; ++ public static final int V22W45A = 3208; ++ public static final int V22W46A = 3210; ++ public static final int V1_19_3_PRE1 = 3211; ++ public static final int V1_19_3_PRE2 = 3212; ++ public static final int V1_19_3_PRE3 = 3213; ++ public static final int V1_19_3_RC1 = 3215; ++ public static final int V1_19_3 = 3218; ++ public static final int V23W03A = 3320; ++ public static final int V23W04A = 3321; ++ public static final int V23W05A = 3323; ++ public static final int V23W06A = 3326; ++ public static final int V23W07A = 3329; ++ public static final int V1_19_4_PRE1 = 3330; ++ public static final int V1_19_4_PRE2 = 3331; ++ public static final int V1_19_4_PRE3 = 3332; ++ public static final int V1_19_4_PRE4 = 3333; ++ public static final int V1_19_4_RC1 = 3334; ++ public static final int V1_19_4_RC2 = 3335; ++ public static final int V1_19_4_RC3 = 3336; ++ public static final int V1_19_4 = 3337; ++ public static final int V23W12A = 3442; ++ public static final int V23W13A = 3443; ++ public static final int V23W14A = 3445; ++ public static final int V23W16A = 3449; ++ public static final int V23W17A = 3452; ++ public static final int V23W19A = 3453; ++ public static final int V1_20_PRE1 = 3454; ++ public static final int V1_20_PRE2 = 3455; ++ public static final int V1_20_PRE3 = 3456; ++ public static final int V1_20_PRE4 = 3457; ++ public static final int V1_20_PRE5 = 3458; ++ public static final int V1_20_PRE6 = 3460; ++ public static final int V1_20_PRE7 = 3461; ++ public static final int V1_20_RC1 = 3462; ++ public static final int V1_20 = 3463; ++ public static final int V1_20_1_RC1 = 3464; ++ public static final int V1_20_1 = 3465; ++ public static final int V23W31A = 3567; ++ public static final int V23W32A = 3569; ++ public static final int V23W33A = 3570; ++ public static final int V23W35A = 3571; ++ public static final int V1_20_2_PRE1 = 3572; ++ public static final int V1_20_2_PRE2 = 3573; ++ public static final int V1_20_2_PRE3 = 3574; ++ public static final int V1_20_2_PRE4 = 3575; ++ public static final int V1_20_2_RC1 = 3576; ++ public static final int V1_20_2_RC2 = 3577; ++ public static final int V1_20_2 = 3578; ++ public static final int V23W40A = 3679; ++ public static final int V23W41A = 3681; ++ public static final int V23W42A = 3684; ++ public static final int V23W43A = 3686; ++ public static final int V23W43B = 3687; ++ public static final int V23W44A = 3688; ++ public static final int V23W45A = 3690; ++ public static final int V23W46A = 3691; ++ public static final int V1_20_3_PRE1 = 3693; ++ public static final int V1_20_3_PRE2 = 3694; ++ public static final int V1_20_3_PRE3 = 3695; ++ public static final int V1_20_3_PRE4 = 3696; ++ public static final int V1_20_3_RC1 = 3697; ++ public static final int V1_20_3 = 3698; ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ae3aed21c1fccb688e9a1665e2d317a77508d157 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java +@@ -0,0 +1,28 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.advancements; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.ArrayList; ++import java.util.function.Function; ++ ++public final class ConverterAbstractAdvancementsRename { ++ ++ private ConverterAbstractAdvancementsRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ RenameHelper.renameKeys(data, renamer); ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b2a4d16e6a2f9d71dbfa692922671581c2bec136 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java +@@ -0,0 +1,42 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.advancements; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.function.Function; ++ ++public final class ConverterCriteriaRename extends DataConverter, MapType> { ++ ++ public final String path; ++ public final Function conversion; ++ ++ public ConverterCriteriaRename(final int toVersion, final String path, final Function conversion) { ++ super(toVersion); ++ this.path = path; ++ this.conversion = conversion; ++ } ++ ++ public ConverterCriteriaRename(final int toVersion, final int versionStep, final String path, final Function conversion) { ++ super(toVersion, versionStep); ++ this.path = path; ++ this.conversion = conversion; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType advancement = data.getMap(this.path); ++ if (advancement == null) { ++ return null; ++ } ++ ++ final MapType criteria = advancement.getMap("criteria"); ++ if (criteria == null) { ++ return null; ++ } ++ ++ RenameHelper.renameKeys(criteria, this.conversion); ++ ++ return null; ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ba9daaab1abd53a3fbdebd78e05ba363251188c6 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java +@@ -0,0 +1,73 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.blockname; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.function.Function; ++ ++public final class ConverterAbstractBlockRename { ++ ++ private ConverterAbstractBlockRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.BLOCK_NAME, renamer); ++ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String name = data.getString("Name"); ++ if (name != null) { ++ final String converted = renamer.apply(name); ++ if (converted != null) { ++ data.setString("Name", converted); ++ } ++ } ++ return null; ++ } ++ }); ++ } ++ ++ public static void registerAndFixJigsaw(final int version, final Function renamer) { ++ registerAndFixJigsaw(version, 0, renamer); ++ } ++ ++ public static void registerAndFixJigsaw(final int version, final int subVersion, final Function renamer) { ++ register(version, subVersion, renamer); ++ // TODO check on update, minecraft:jigsaw can change ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jigsaw", new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String finalState = data.getString("final_state"); ++ if (finalState == null || finalState.isEmpty()) { ++ return null; ++ } ++ ++ final int nbtStart1 = finalState.indexOf('['); ++ final int nbtStart2 = finalState.indexOf('{'); ++ int stateNameEnd = finalState.length(); ++ if (nbtStart1 > 0) { ++ stateNameEnd = Math.min(stateNameEnd, nbtStart1); ++ } ++ ++ if (nbtStart2 > 0) { ++ stateNameEnd = Math.min(stateNameEnd, nbtStart2); ++ } ++ ++ final String blockStateName = finalState.substring(0, stateNameEnd); ++ final String converted = renamer.apply(blockStateName); ++ if (converted == null) { ++ return null; ++ } ++ ++ final String convertedState = converted.concat(finalState.substring(stateNameEnd)); ++ data.setString("final_state", convertedState); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d4cd5362e77eb71cb8eb45ffcc73185e01be1157 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java +@@ -0,0 +1,65 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.chunk; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++import ca.spottedleaf.dataconverter.util.NamespaceUtil; ++import java.util.Arrays; ++import java.util.HashSet; ++import java.util.Set; ++ ++public final class ConverterAddBlendingData extends DataConverter, MapType> { ++ ++ private static final Set STATUSES_TO_SKIP_BLENDING = new HashSet<>( ++ Arrays.asList( ++ "minecraft:empty", ++ "minecraft:structure_starts", ++ "minecraft:structure_references", ++ "minecraft:biomes" ++ ) ++ ); ++ ++ public ConverterAddBlendingData(final int toVersion) { ++ super(toVersion); ++ } ++ ++ public ConverterAddBlendingData(final int toVersion, final int versionStep) { ++ super(toVersion, versionStep); ++ } ++ ++ private static MapType createBlendingData(final int height, final int minY) { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setInt("min_section", minY >> 4); ++ ret.setInt("max_section", (minY + height) >> 4); ++ ++ return ret; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.remove("blending_data"); ++ final MapType context = data.getMap("__context"); ++ if (!"minecraft:overworld".equals(context == null ? null : context.getString("dimension"))) { ++ return null; ++ } ++ ++ final String status = NamespaceUtil.correctNamespace(data.getString("Status")); ++ if (status == null) { ++ return null; ++ } ++ ++ final MapType belowZeroRetrogen = data.getMap("below_zero_retrogen"); ++ ++ if (!STATUSES_TO_SKIP_BLENDING.contains(status)) { ++ data.setMap("blending_data", createBlendingData(384, -64)); ++ } else if (belowZeroRetrogen != null) { ++ final String realStatus = NamespaceUtil.correctNamespace(belowZeroRetrogen.getString("target_status", "empty")); ++ if (!STATUSES_TO_SKIP_BLENDING.contains(realStatus)) { ++ data.setMap("blending_data", createBlendingData(256, 0)); ++ } ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java +new file mode 100644 +index 0000000000000000000000000000000000000000..300c2d14818b1e0cfe7341aba573ec75d0581b26 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java +@@ -0,0 +1,1016 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.chunk; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.mojang.datafixers.DataFixUtils; ++import com.mojang.logging.LogUtils; ++import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.ints.Int2ObjectMap; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.ints.IntArrayList; ++import it.unimi.dsi.fastutil.ints.IntIterator; ++import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; ++import net.minecraft.util.datafix.PackedBitStorage; ++import org.slf4j.Logger; ++import java.util.Arrays; ++import java.util.BitSet; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.Map; ++import java.util.Objects; ++ ++import static it.unimi.dsi.fastutil.HashCommon.arraySize; ++ ++public final class ConverterFlattenChunk extends DataConverter, MapType> { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ static final BitSet VIRTUAL_SET = new BitSet(256); ++ static final BitSet IDS_NEEDING_FIX_SET = new BitSet(256); ++ ++ static { ++ IDS_NEEDING_FIX_SET.set(2); ++ IDS_NEEDING_FIX_SET.set(3); ++ IDS_NEEDING_FIX_SET.set(110); ++ IDS_NEEDING_FIX_SET.set(140); ++ IDS_NEEDING_FIX_SET.set(144); ++ IDS_NEEDING_FIX_SET.set(25); ++ IDS_NEEDING_FIX_SET.set(86); ++ IDS_NEEDING_FIX_SET.set(26); ++ IDS_NEEDING_FIX_SET.set(176); ++ IDS_NEEDING_FIX_SET.set(177); ++ IDS_NEEDING_FIX_SET.set(175); ++ IDS_NEEDING_FIX_SET.set(64); ++ IDS_NEEDING_FIX_SET.set(71); ++ IDS_NEEDING_FIX_SET.set(193); ++ IDS_NEEDING_FIX_SET.set(194); ++ IDS_NEEDING_FIX_SET.set(195); ++ IDS_NEEDING_FIX_SET.set(196); ++ IDS_NEEDING_FIX_SET.set(197); ++ ++ VIRTUAL_SET.set(54); ++ VIRTUAL_SET.set(146); ++ VIRTUAL_SET.set(25); ++ VIRTUAL_SET.set(26); ++ VIRTUAL_SET.set(51); ++ VIRTUAL_SET.set(53); ++ VIRTUAL_SET.set(67); ++ VIRTUAL_SET.set(108); ++ VIRTUAL_SET.set(109); ++ VIRTUAL_SET.set(114); ++ VIRTUAL_SET.set(128); ++ VIRTUAL_SET.set(134); ++ VIRTUAL_SET.set(135); ++ VIRTUAL_SET.set(136); ++ VIRTUAL_SET.set(156); ++ VIRTUAL_SET.set(163); ++ VIRTUAL_SET.set(164); ++ VIRTUAL_SET.set(180); ++ VIRTUAL_SET.set(203); ++ VIRTUAL_SET.set(55); ++ VIRTUAL_SET.set(85); ++ VIRTUAL_SET.set(113); ++ VIRTUAL_SET.set(188); ++ VIRTUAL_SET.set(189); ++ VIRTUAL_SET.set(190); ++ VIRTUAL_SET.set(191); ++ VIRTUAL_SET.set(192); ++ VIRTUAL_SET.set(93); ++ VIRTUAL_SET.set(94); ++ VIRTUAL_SET.set(101); ++ VIRTUAL_SET.set(102); ++ VIRTUAL_SET.set(160); ++ VIRTUAL_SET.set(106); ++ VIRTUAL_SET.set(107); ++ VIRTUAL_SET.set(183); ++ VIRTUAL_SET.set(184); ++ VIRTUAL_SET.set(185); ++ VIRTUAL_SET.set(186); ++ VIRTUAL_SET.set(187); ++ VIRTUAL_SET.set(132); ++ VIRTUAL_SET.set(139); ++ VIRTUAL_SET.set(199); ++ } ++ ++ static final boolean[] VIRTUAL = toBooleanArray(VIRTUAL_SET); ++ static final boolean[] IDS_NEEDING_FIX = toBooleanArray(IDS_NEEDING_FIX_SET); ++ ++ private static boolean[] toBooleanArray(final BitSet set) { ++ final boolean[] ret = new boolean[4096]; ++ for (int i = 0; i < 4096; ++i) { ++ ret[i] = set.get(i); ++ } ++ ++ return ret; ++ } ++ ++ static final MapType PUMPKIN = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:pumpkin'}"); ++ static final MapType SNOWY_PODZOL = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:podzol',Properties:{snowy:'true'}}"); ++ static final MapType SNOWY_GRASS = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:grass_block',Properties:{snowy:'true'}}"); ++ static final MapType SNOWY_MYCELIUM = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:mycelium',Properties:{snowy:'true'}}"); ++ static final MapType UPPER_SUNFLOWER = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:sunflower',Properties:{half:'upper'}}"); ++ static final MapType UPPER_LILAC = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:lilac',Properties:{half:'upper'}}"); ++ static final MapType UPPER_TALL_GRASS = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:tall_grass',Properties:{half:'upper'}}"); ++ static final MapType UPPER_LARGE_FERN = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:large_fern',Properties:{half:'upper'}}"); ++ static final MapType UPPER_ROSE_BUSH = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:rose_bush',Properties:{half:'upper'}}"); ++ static final MapType UPPER_PEONY = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:peony',Properties:{half:'upper'}}"); ++ ++ static final Map> FLOWER_POT_MAP = new HashMap<>(); ++ static { ++ FLOWER_POT_MAP.put("minecraft:air0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:flower_pot'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_poppy'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower1", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_blue_orchid'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower2", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_allium'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower3", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_azure_bluet'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower4", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_red_tulip'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower5", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_orange_tulip'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower6", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_white_tulip'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower7", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_pink_tulip'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower8", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_oxeye_daisy'}")); ++ FLOWER_POT_MAP.put("minecraft:yellow_flower0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_dandelion'}")); ++ FLOWER_POT_MAP.put("minecraft:sapling0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_oak_sapling'}")); ++ FLOWER_POT_MAP.put("minecraft:sapling1", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_spruce_sapling'}")); ++ FLOWER_POT_MAP.put("minecraft:sapling2", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_birch_sapling'}")); ++ FLOWER_POT_MAP.put("minecraft:sapling3", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_jungle_sapling'}")); ++ FLOWER_POT_MAP.put("minecraft:sapling4", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_acacia_sapling'}")); ++ FLOWER_POT_MAP.put("minecraft:sapling5", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_dark_oak_sapling'}")); ++ FLOWER_POT_MAP.put("minecraft:red_mushroom0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_red_mushroom'}")); ++ FLOWER_POT_MAP.put("minecraft:brown_mushroom0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_brown_mushroom'}")); ++ FLOWER_POT_MAP.put("minecraft:deadbush0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_dead_bush'}")); ++ FLOWER_POT_MAP.put("minecraft:tallgrass2", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_fern'}")); ++ FLOWER_POT_MAP.put("minecraft:cactus0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_cactus'}")); // we change default to empty ++ } ++ ++ static final Map> SKULL_MAP = new HashMap<>(); ++ static { ++ mapSkull(SKULL_MAP, 0, "skeleton", "skull"); ++ mapSkull(SKULL_MAP, 1, "wither_skeleton", "skull"); ++ mapSkull(SKULL_MAP, 2, "zombie", "head"); ++ mapSkull(SKULL_MAP, 3, "player", "head"); ++ mapSkull(SKULL_MAP, 4, "creeper", "head"); ++ mapSkull(SKULL_MAP, 5, "dragon", "head"); ++ }; ++ ++ private static void mapSkull(final Map> into, final int oldId, final String newId, final String skullType) { ++ into.put(oldId + "north", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'north'}}")); ++ into.put(oldId + "east", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'east'}}")); ++ into.put(oldId + "south", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'south'}}")); ++ into.put(oldId + "west", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'west'}}")); ++ ++ for (int rotation = 0; rotation < 16; ++rotation) { ++ into.put(oldId + "" + rotation, ++ HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_" + skullType + "',Properties:{rotation:'" + rotation + "'}}")); ++ } ++ } ++ ++ static final Map> DOOR_MAP = new HashMap<>(); ++ static { ++ mapDoor(DOOR_MAP, "oak_door", 1024); ++ mapDoor(DOOR_MAP, "iron_door", 1136); ++ mapDoor(DOOR_MAP, "spruce_door", 3088); ++ mapDoor(DOOR_MAP, "birch_door", 3104); ++ mapDoor(DOOR_MAP, "jungle_door", 3120); ++ mapDoor(DOOR_MAP, "acacia_door", 3136); ++ mapDoor(DOOR_MAP, "dark_oak_door", 3152); ++ }; ++ ++ private static void mapDoor(final Map> into, final String type, final int oldId) { ++ into.put("minecraft:" + type + "eastlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "eastlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "eastlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "eastlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "eastlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId))); ++ into.put("minecraft:" + type + "eastlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "eastlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 4))); ++ into.put("minecraft:" + type + "eastlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "eastupperleftfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 8))); ++ into.put("minecraft:" + type + "eastupperleftfalsetrue", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 10))); ++ into.put("minecraft:" + type + "eastupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "eastupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "eastupperrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 9))); ++ into.put("minecraft:" + type + "eastupperrightfalsetrue", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 11))); ++ into.put("minecraft:" + type + "eastupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "eastupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "northlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "northlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "northlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "northlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "northlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 3))); ++ into.put("minecraft:" + type + "northlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "northlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 7))); ++ into.put("minecraft:" + type + "northlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "northupperleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "northupperleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "northupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "northupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "northupperrightfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "northupperrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "northupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "northupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "southlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "southlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "southlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "southlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "southlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 1))); ++ into.put("minecraft:" + type + "southlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "southlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 5))); ++ into.put("minecraft:" + type + "southlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "southupperleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "southupperleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "southupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "southupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "southupperrightfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "southupperrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "southupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "southupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "westlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "westlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "westlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "westlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "westlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 2))); ++ into.put("minecraft:" + type + "westlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "westlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 6))); ++ into.put("minecraft:" + type + "westlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "westupperleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "westupperleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "westupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "westupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "westupperrightfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "westupperrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "westupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "westupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}")); ++ } ++ ++ static final Map> NOTE_BLOCK_MAP = new HashMap<>(); ++ static { ++ for(int note = 0; note < 26; ++note) { ++ NOTE_BLOCK_MAP.put("true" + note, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:note_block',Properties:{powered:'true',note:'" + note + "'}}")); ++ NOTE_BLOCK_MAP.put("false" + note, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:note_block',Properties:{powered:'false',note:'" + note + "'}}")); ++ } ++ } ++ ++ static final Int2ObjectOpenHashMap DYE_COLOR_MAP = new Int2ObjectOpenHashMap<>(); ++ static { ++ DYE_COLOR_MAP.put(0, "white"); ++ DYE_COLOR_MAP.put(1, "orange"); ++ DYE_COLOR_MAP.put(2, "magenta"); ++ DYE_COLOR_MAP.put(3, "light_blue"); ++ DYE_COLOR_MAP.put(4, "yellow"); ++ DYE_COLOR_MAP.put(5, "lime"); ++ DYE_COLOR_MAP.put(6, "pink"); ++ DYE_COLOR_MAP.put(7, "gray"); ++ DYE_COLOR_MAP.put(8, "light_gray"); ++ DYE_COLOR_MAP.put(9, "cyan"); ++ DYE_COLOR_MAP.put(10, "purple"); ++ DYE_COLOR_MAP.put(11, "blue"); ++ DYE_COLOR_MAP.put(12, "brown"); ++ DYE_COLOR_MAP.put(13, "green"); ++ DYE_COLOR_MAP.put(14, "red"); ++ DYE_COLOR_MAP.put(15, "black"); ++ } ++ ++ static final Map> BED_BLOCK_MAP = new HashMap<>(); ++ ++ static { ++ for (final Int2ObjectMap.Entry entry : DYE_COLOR_MAP.int2ObjectEntrySet()) { ++ if (!Objects.equals(entry.getValue(), "red")) { ++ addBeds(BED_BLOCK_MAP, entry.getIntKey(), entry.getValue()); ++ } ++ } ++ } ++ ++ private static void addBeds(final Map> into, final int colourId, final String colourName) { ++ into.put("southfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'south',occupied:'false',part:'foot'}}")); ++ into.put("westfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'west',occupied:'false',part:'foot'}}")); ++ into.put("northfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'north',occupied:'false',part:'foot'}}")); ++ into.put("eastfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'east',occupied:'false',part:'foot'}}")); ++ into.put("southfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'south',occupied:'false',part:'head'}}")); ++ into.put("westfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'west',occupied:'false',part:'head'}}")); ++ into.put("northfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'north',occupied:'false',part:'head'}}")); ++ into.put("eastfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'east',occupied:'false',part:'head'}}")); ++ into.put("southtruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'south',occupied:'true',part:'head'}}")); ++ into.put("westtruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'west',occupied:'true',part:'head'}}")); ++ into.put("northtruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'north',occupied:'true',part:'head'}}")); ++ into.put("easttruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'east',occupied:'true',part:'head'}}")); ++ } ++ ++ static final Map> BANNER_BLOCK_MAP = new HashMap<>(); ++ ++ static { ++ for (final Int2ObjectMap.Entry entry : DYE_COLOR_MAP.int2ObjectEntrySet()) { ++ if (!Objects.equals(entry.getValue(), "white")) { ++ addBanners(BANNER_BLOCK_MAP, 15 - entry.getIntKey(), entry.getValue()); ++ } ++ } ++ } ++ ++ private static void addBanners(final Map> into, final int colourId, final String colourName) { ++ for(int rotation = 0; rotation < 16; ++rotation) { ++ into.put("" + rotation + "_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_banner',Properties:{rotation:'" + rotation + "'}}")); ++ } ++ ++ into.put("north_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'north'}}")); ++ into.put("south_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'south'}}")); ++ into.put("west_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'west'}}")); ++ into.put("east_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'east'}}")); ++ } ++ ++ static final MapType AIR = Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(0)); ++ ++ public ConverterFlattenChunk() { ++ super(MCVersions.V17W47A, 1); ++ } ++ ++ static String getName(final MapType blockState) { ++ return blockState.getString("Name"); ++ } ++ ++ static String getProperty(final MapType blockState, final String propertyName) { ++ final MapType properties = blockState.getMap("Properties"); ++ if (properties == null) { ++ return ""; ++ } ++ ++ return properties.getString(propertyName, ""); ++ } ++ ++ static int getSideMask(final boolean noLeft, final boolean noRight, final boolean noBack, final boolean noForward) { ++ if (noBack) { ++ if (noRight) { ++ return 2; ++ } else if (noLeft) { ++ return 128; ++ } else { ++ return 1; ++ } ++ } else if (noForward) { ++ if (noLeft) { ++ return 32; ++ } else if (noRight) { ++ return 8; ++ } else { ++ return 16; ++ } ++ } else if (noRight) { ++ return 4; ++ } else if (noLeft) { ++ return 64; ++ } else { ++ return 0; ++ } ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ if (!level.hasKey("Sections", ObjectType.LIST)) { ++ return null; ++ } ++ ++ data.setMap("Level", new UpgradeChunk(level).writeBackToLevel()); ++ ++ return null; ++ } ++ ++ static enum Direction { ++ DOWN(AxisDirection.NEGATIVE, Axis.Y), ++ UP(AxisDirection.POSITIVE, Axis.Y), ++ NORTH(AxisDirection.NEGATIVE, Axis.Z), ++ SOUTH(AxisDirection.POSITIVE, Axis.Z), ++ WEST(AxisDirection.NEGATIVE, Axis.X), ++ EAST(AxisDirection.POSITIVE, Axis.X); ++ ++ private final Axis axis; ++ private final AxisDirection axisDirection; ++ ++ private Direction(final AxisDirection axisDirection, final Axis axis) { ++ this.axis = axis; ++ this.axisDirection = axisDirection; ++ } ++ ++ public AxisDirection getAxisDirection() { ++ return this.axisDirection; ++ } ++ ++ public Axis getAxis() { ++ return this.axis; ++ } ++ ++ public static enum AxisDirection { ++ POSITIVE(1), ++ NEGATIVE(-1); ++ ++ private final int step; ++ ++ private AxisDirection(final int step) { ++ this.step = step; ++ } ++ ++ public int getStep() { ++ return this.step; ++ } ++ } ++ ++ public static enum Axis { ++ X, Y, Z; ++ } ++ } ++ ++ static class DataLayer { ++ private final byte[] data; ++ ++ public DataLayer() { ++ this.data = new byte[2048]; ++ } ++ ++ public DataLayer(final byte[] data) { ++ this.data = data; ++ if (data.length != 2048) { ++ throw new IllegalArgumentException("ChunkNibbleArrays should be 2048 bytes not: " + data.length); ++ } ++ } ++ ++ public static DataLayer getOrNull(final byte[] data) { ++ return data == null ? null : new DataLayer(data); ++ } ++ ++ public static DataLayer getOrCreate(final byte[] data) { ++ return data == null ? new DataLayer() : new DataLayer(data); ++ } ++ ++ public int get(final int index) { ++ final byte value = this.data[index >>> 1]; ++ ++ // if we are an even index, we want lower 4 bits ++ // if we are an odd index, we want upper 4 bits ++ return ((value >>> ((index & 1) << 2)) & 0xF); ++ } ++ ++ public int get(final int x, final int y, final int z) { ++ final int index = y << 8 | z << 4 | x; ++ final byte value = this.data[index >>> 1]; ++ ++ // if we are an even index, we want lower 4 bits ++ // if we are an odd index, we want upper 4 bits ++ return ((value >>> ((index & 1) << 2)) & 0xF); ++ } ++ } ++ ++ static final class UpgradeChunk { ++ int sides; ++ ++ final Section[] sections = new Section[16]; ++ final MapType level; ++ final int blockX; ++ final int blockZ; ++ final Int2ObjectLinkedOpenHashMap> tileEntities = new Int2ObjectLinkedOpenHashMap<>(16); ++ ++ public UpgradeChunk(final MapType level) { ++ this.level = level; ++ this.blockX = level.getInt("xPos") << 4; ++ this.blockZ = level.getInt("zPos") << 4; ++ ++ final ListType tileEntities = level.getList("TileEntities", ObjectType.MAP); ++ if (tileEntities != null) { ++ for (int i = 0, len = tileEntities.size(); i < len; ++i) { ++ final MapType tileEntity = tileEntities.getMap(i); ++ ++ final int x = (tileEntity.getInt("x") - this.blockX) & 15; ++ final int y = tileEntity.getInt("y"); ++ final int z = (tileEntity.getInt("z") - this.blockZ) & 15; ++ final int index = (y << 8) | (z << 4) | x; ++ if (this.tileEntities.put(index, tileEntity) != null) { ++ LOGGER.warn("In chunk: {}x{} found a duplicate block entity at position (ConverterFlattenChunk): [{}, {}, {}]", this.blockX, this.blockZ, x, y, z); ++ } ++ } ++ } ++ ++ final boolean convertedFromAlphaFormat = level.getBoolean("convertedFromAlphaFormat"); ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType sectionData = sections.getMap(i); ++ final Section section = new Section(sectionData); ++ ++ if (section.y < 0 || section.y > 15) { ++ LOGGER.warn("In chunk: {}x{} found an invalid chunk section y (ConverterFlattenChunk): {}", this.blockX, this.blockZ, section.y); ++ continue; ++ } ++ ++ if (this.sections[section.y] != null) { ++ LOGGER.warn("In chunk: {}x{} found a duplicate chunk section (ConverterFlattenChunk): {}", this.blockX, this.blockZ, section.y); ++ } ++ ++ this.sides = section.upgrade(this.sides); ++ this.sections[section.y] = section; ++ } ++ } ++ ++ for (final Section section : this.sections) { ++ if (section == null) { ++ continue; ++ } ++ ++ final int yIndex = section.y << (8 + 4); ++ ++ for (final Iterator> iterator = section.toFix.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ final Int2ObjectMap.Entry fixEntry = iterator.next(); ++ final IntIterator positionIterator = fixEntry.getValue().iterator(); ++ switch (fixEntry.getIntKey()) { ++ case 2: { // grass block ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType blockState = this.getBlock(position); ++ if (!"minecraft:grass_block".equals(getName(blockState))) { ++ continue; ++ } ++ ++ final String blockAbove = getName(getBlock(relative(position, Direction.UP))); ++ if ("minecraft:snow".equals(blockAbove) || "minecraft:snow_layer".equals(blockAbove)) { ++ this.setBlock(position, SNOWY_GRASS); ++ } ++ } ++ break; ++ } ++ case 3: { // dirt ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType blockState = this.getBlock(position); ++ if (!"minecraft:podzol".equals(getName(blockState))) { ++ continue; ++ } ++ ++ final String blockAbove = getName(getBlock(relative(position, Direction.UP))); ++ if ("minecraft:snow".equals(blockAbove) || "minecraft:snow_layer".equals(blockAbove)) { ++ this.setBlock(position, SNOWY_PODZOL); ++ } ++ } ++ break; ++ } ++ case 25: { // note block ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType tile = this.removeBlockEntity(position); ++ if (tile != null) { ++ final String state = Boolean.toString(tile.getBoolean("powered")) + (byte) Math.min(Math.max(tile.getInt("note"), 0), 24); ++ this.setBlock(position, NOTE_BLOCK_MAP.getOrDefault(state, NOTE_BLOCK_MAP.get("false0"))); ++ } ++ } ++ break; ++ } ++ case 26: { // bed ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType tile = this.getBlockEntity(position); ++ ++ if (tile == null) { ++ continue; ++ } ++ ++ final MapType blockState = this.getBlock(position); ++ ++ final int colour = tile.getInt("color"); ++ if (colour != 14 && colour >= 0 && colour < 16) { ++ final String state = getProperty(blockState, "facing") + getProperty(blockState, "occupied") + getProperty(blockState, "part") + colour; ++ ++ final MapType update = BED_BLOCK_MAP.get(state); ++ if (update != null) { ++ this.setBlock(position, update); ++ } ++ } ++ } ++ break; ++ } ++ case 64: // oak door ++ case 71: // iron door ++ case 193: // spruce door ++ case 194: // birch door ++ case 195: // jungle door ++ case 196: // acacia door ++ case 197: { // dark oak door ++ // aka the door updater ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType blockState = this.getBlock(position); ++ if (!getName(blockState).endsWith("_door")) { ++ continue; ++ } ++ ++ if (!"lower".equals(getProperty(blockState, "half"))) { ++ continue; ++ } ++ ++ final int positionAbove = relative(position, Direction.UP); ++ final MapType blockStateAbove = this.getBlock(positionAbove); ++ ++ final String name = getName(blockState); ++ if (name.equals(getName(blockStateAbove))) { ++ final String facingBelow = getProperty(blockState, "facing"); ++ final String openBelow = getProperty(blockState, "open"); ++ final String hingeAbove = convertedFromAlphaFormat ? "left" : getProperty(blockStateAbove, "hinge"); ++ final String poweredAbove = convertedFromAlphaFormat ? "false" : getProperty(blockStateAbove, "powered"); ++ ++ this.setBlock(position, DOOR_MAP.get(name + facingBelow + "lower" + hingeAbove + openBelow + poweredAbove)); ++ this.setBlock(positionAbove, DOOR_MAP.get(name + facingBelow + "upper" + hingeAbove + openBelow + poweredAbove)); ++ } ++ } ++ break; ++ } ++ case 86: { // pumpkin ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType blockState = this.getBlock(position); ++ ++ // I guess this is some terrible hack to convert carved pumpkins from world gen into ++ // regular pumpkins? ++ ++ if ("minecraft:carved_pumpkin".equals(getName(blockState))) { ++ final String downName = getName(this.getBlock(relative(position, Direction.DOWN))); ++ if ("minecraft:grass_block".equals(downName) || "minecraft:dirt".equals(downName)) { ++ this.setBlock(position, PUMPKIN); ++ } ++ } ++ } ++ break; ++ } ++ case 110: { // mycelium ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType blockState = this.getBlock(position); ++ if ("minecraft:mycelium".equals(getName(blockState))) { ++ final String nameAbove = getName(this.getBlock(relative(position, Direction.UP))); ++ if ("minecraft:snow".equals(nameAbove) || "minecraft:snow_layer".equals(nameAbove)) { ++ this.setBlock(position, SNOWY_MYCELIUM); ++ } ++ } ++ } ++ break; ++ } ++ case 140: { // flower pot ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType tile = this.removeBlockEntity(position); ++ if (tile == null) { ++ continue; ++ } ++ ++ final String item; ++ if (tile.hasKey("Item", ObjectType.NUMBER)) { ++ // the item name converter should have migrated to number, however no legacy converter ++ // ever did this. so we can get data with versions above v102 (old worlds, converted prior to DFU) ++ // that didn't convert. so just do it here. ++ item = HelperItemNameV102.getNameFromId(tile.getInt("Item")); ++ } else { ++ item = tile.getString("Item", ""); ++ } ++ ++ final String state = item + tile.getInt("Data"); ++ this.setBlock(position, FLOWER_POT_MAP.getOrDefault(state, FLOWER_POT_MAP.get("minecraft:air0"))); ++ } ++ break; ++ } ++ case 144: { // mob head ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType tile = this.getBlockEntity(position); ++ if (tile == null) { ++ continue; ++ } ++ ++ final String typeString = Integer.toString(tile.getInt("SkullType")); ++ final String facing = getProperty(this.getBlock(position), "facing"); ++ final String state; ++ if (!"up".equals(facing) && !"down".equals(facing)) { ++ state = typeString + facing; ++ } else { ++ state = typeString + tile.getInt("Rot"); ++ } ++ ++ tile.remove("SkullType"); ++ tile.remove("facing"); ++ tile.remove("Rot"); ++ ++ this.setBlock(position, SKULL_MAP.getOrDefault(state, SKULL_MAP.get("0north"))); ++ } ++ break; ++ } ++ case 175: { // sunflower ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType blockState = this.getBlock(position); ++ if (!"upper".equals(getProperty(blockState, "half"))) { ++ continue; ++ } ++ ++ final MapType blockStateBelow = this.getBlock(relative(position, Direction.DOWN)); ++ final String nameBelow = getName(blockStateBelow); ++ switch (nameBelow) { ++ case "minecraft:sunflower": ++ this.setBlock(position, UPPER_SUNFLOWER); ++ break; ++ case "minecraft:lilac": ++ this.setBlock(position, UPPER_LILAC); ++ break; ++ case "minecraft:tall_grass": ++ this.setBlock(position, UPPER_TALL_GRASS); ++ break; ++ case "minecraft:large_fern": ++ this.setBlock(position, UPPER_LARGE_FERN); ++ break; ++ case "minecraft:rose_bush": ++ this.setBlock(position, UPPER_ROSE_BUSH); ++ break; ++ case "minecraft:peony": ++ this.setBlock(position, UPPER_PEONY); ++ break; ++ } ++ } ++ break; ++ } ++ case 176: // free standing banner ++ case 177: { // wall mounted banner ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType tile = this.getBlockEntity(position); ++ ++ if (tile == null) { ++ continue; ++ } ++ ++ final MapType blockState = this.getBlock(position); ++ ++ final int base = tile.getInt("Base"); ++ if (base != 15 && base >= 0 && base < 16) { ++ final String state = getProperty(blockState, fixEntry.getIntKey() == 176 ? "rotation" : "facing") + "_" + base; ++ final MapType update = BANNER_BLOCK_MAP.get(state); ++ if (update != null) { ++ this.setBlock(position, update); ++ } ++ } ++ } ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ private MapType getBlockEntity(final int index) { ++ return this.tileEntities.get(index); ++ } ++ ++ private MapType removeBlockEntity(final int index) { ++ return this.tileEntities.remove(index); ++ } ++ ++ public static int relative(final int index, final Direction direction) { ++ switch (direction.getAxis()) { ++ case X: ++ int j = (index & 15) + direction.getAxisDirection().getStep(); ++ return j >= 0 && j <= 15 ? index & -16 | j : -1; ++ case Y: ++ int k = (index >> 8) + direction.getAxisDirection().getStep(); ++ return k >= 0 && k <= 255 ? index & 255 | k << 8 : -1; ++ case Z: ++ int l = (index >> 4 & 15) + direction.getAxisDirection().getStep(); ++ return l >= 0 && l <= 15 ? index & -241 | l << 4 : -1; ++ default: ++ return -1; ++ } ++ } ++ ++ private void setBlock(final int index, final MapType blockState) { ++ if (index >= 0 && index <= 65535) { ++ final Section section = this.getSection(index); ++ if (section != null) { ++ section.setBlock(index & 4095, blockState); ++ } ++ } ++ } ++ ++ private Section getSection(final int index) { ++ final int y = index >> 12; ++ return y < this.sections.length ? this.sections[y] : null; ++ } ++ ++ public MapType getBlock(int i) { ++ if (i >= 0 && i <= 65535) { ++ final Section section = this.getSection(i); ++ return section == null ? AIR : section.getBlock(i & 4095); ++ } else { ++ return AIR; ++ } ++ } ++ ++ public MapType writeBackToLevel() { ++ if (this.tileEntities.isEmpty()) { ++ this.level.remove("TileEntities"); ++ } else { ++ final ListType tileEntities = Types.NBT.createEmptyList(); ++ this.tileEntities.values().forEach(tileEntities::addMap); ++ this.level.setList("TileEntities", tileEntities); ++ } ++ ++ final MapType indices = Types.NBT.createEmptyMap(); ++ final ListType sections = Types.NBT.createEmptyList(); ++ for (final Section section : this.sections) { ++ if (section == null) { ++ continue; ++ } ++ ++ sections.addMap(section.writeBackToSection()); ++ indices.setInts(Integer.toString(section.y), Arrays.copyOf(section.update.elements(), section.update.size())); ++ } ++ ++ this.level.setList("Sections", sections); ++ ++ final MapType upgradeData = Types.NBT.createEmptyMap(); ++ upgradeData.setByte("Sides", (byte)this.sides); ++ upgradeData.setMap("Indices", indices); ++ ++ this.level.setMap("UpgradeData", upgradeData); ++ ++ return this.level; ++ } ++ } ++ ++ static class Section { ++ final Palette palette = new Palette(); ++ ++ static final class Palette extends Reference2IntOpenHashMap> { ++ ++ final ListType paletteStates = Types.NBT.createEmptyList(); ++ ++ private int find(final MapType k) { ++ if (((k) == (null))) ++ return containsNullKey ? n : -(n + 1); ++ MapType curr; ++ final Object[] key = this.key; ++ int pos; ++ // The starting point. ++ if (((curr = (MapType)key[pos = (it.unimi.dsi.fastutil.HashCommon.mix(System.identityHashCode(k))) & mask]) == (null))) ++ return -(pos + 1); ++ if (((k) == (curr))) ++ return pos; ++ // There's always an unused entry. ++ while (true) { ++ if (((curr = (MapType)key[pos = (pos + 1) & mask]) == (null))) ++ return -(pos + 1); ++ if (((k) == (curr))) ++ return pos; ++ } ++ } ++ ++ private void insert(final int pos, final MapType k, final int v) { ++ if (pos == n) ++ containsNullKey = true; ++ ((Object[])key)[pos] = k; ++ value[pos] = v; ++ if (size++ >= maxFill) ++ rehash(arraySize(size + 1, f)); ++ } ++ ++ private MapType[] byId = new MapType[4]; ++ private MapType last = null; ++ ++ public int getOrCreateId(final MapType k) { ++ if (k == this.last) { ++ return this.size - 1; ++ } ++ final int pos = find(k); ++ if (pos >= 0) { ++ return this.value[pos]; ++ } ++ ++ final int insert = this.size; ++ MapType inPalette = k; ++ ++ if ("%%FILTER_ME%%".equals(getName(k))) { ++ inPalette = AIR; ++ } ++ ++ if (insert >= this.byId.length) { ++ this.byId = Arrays.copyOf(this.byId, this.byId.length * 2); ++ this.byId[insert] = k; ++ } else { ++ this.byId[insert] = k; ++ } ++ this.paletteStates.addMap(inPalette); ++ ++ this.last = k; ++ ++ this.insert(-pos - 1, k, insert); ++ ++ return insert; ++ } ++ ++ } ++ ++ final MapType section; ++ final boolean hasData; ++ final Int2ObjectLinkedOpenHashMap toFix = new Int2ObjectLinkedOpenHashMap<>(); ++ final IntArrayList update = new IntArrayList(); ++ final int y; ++ final int[] buffer = new int[4096]; ++ ++ public Section(final MapType section) { ++ this.section = section; ++ this.y = section.getInt("Y"); ++ this.hasData = section.hasKey("Blocks", ObjectType.BYTE_ARRAY); ++ } ++ ++ public MapType getBlock(final int index) { ++ if (index >= 0 && index <= 4095) { ++ final MapType state = this.palette.byId[this.buffer[index]]; ++ return state == null ? AIR : state; ++ } else { ++ return AIR; ++ } ++ } ++ ++ public void setBlock(final int index, final MapType blockState) { ++ this.buffer[index] = this.palette.getOrCreateId(blockState); ++ } ++ ++ public int upgrade(int sides) { ++ if (!this.hasData) { ++ return sides; ++ } ++ ++ final byte[] blocks = this.section.getBytes("Blocks"); ++ final DataLayer data = DataLayer.getOrNull(this.section.getBytes("Data")); ++ final DataLayer add = DataLayer.getOrNull(this.section.getBytes("Add")); ++ ++ this.palette.getOrCreateId(AIR); ++ ++ for (int index = 0; index < 4096; ++index) { ++ final int x = index & 15; ++ final int z = index >> 4 & 15; ++ ++ int blockStateId = (blocks[index] & 255) << 4; ++ if (data != null) { ++ blockStateId |= data.get(index); ++ } ++ if (add != null) { ++ blockStateId |= add.get(index) << 12; ++ } ++ if (IDS_NEEDING_FIX[blockStateId >>> 4]) { ++ this.addFix(blockStateId >>> 4, index); ++ } ++ ++ if (VIRTUAL[blockStateId >>> 4]) { ++ final int additionalSides = getSideMask(x == 0, x == 15, z == 0, z == 15); ++ if (additionalSides == 0) { ++ this.update.add(index); ++ } else { ++ sides |= additionalSides; ++ } ++ } ++ ++ this.setBlock(index, HelperBlockFlatteningV1450.getNBTForId(blockStateId)); ++ } ++ ++ return sides; ++ } ++ ++ private void addFix(final int block, final int index) { ++ this.toFix.computeIfAbsent(block, (final int keyInMap) -> { ++ return new IntArrayList(); ++ }).add(index); ++ } ++ ++ // Note: modifies the current section and returns it. ++ public MapType writeBackToSection() { ++ if (!this.hasData) { ++ return this.section; ++ } ++ ++ this.section.setList("Palette", this.palette.paletteStates.copy()); // deep copy to ensure palette compound tags are NOT shared ++ ++ final int bitSize = Math.max(4, DataFixUtils.ceillog2(this.palette.size())); ++ final PackedBitStorage packedIds = new PackedBitStorage(bitSize, 4096); ++ ++ for(int index = 0; index < this.buffer.length; ++index) { ++ packedIds.set(index, this.buffer[index]); ++ } ++ ++ this.section.setLongs("BlockStates", packedIds.getRaw()); ++ ++ this.section.remove("Blocks"); ++ this.section.remove("Data"); ++ this.section.remove("Add"); ++ ++ return this.section; ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java +new file mode 100644 +index 0000000000000000000000000000000000000000..084c67a46bc5ec7f5a4bef3216805a87b32c83d0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java +@@ -0,0 +1,32 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.chunk; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.util.NamespaceUtil; ++import java.util.function.Function; ++ ++public final class ConverterRenameStatus extends DataConverter, MapType> { ++ ++ private final Function renamer; ++ ++ public ConverterRenameStatus(final int toVersion, final Function renamer) { ++ this(toVersion, 0, renamer); ++ } ++ ++ public ConverterRenameStatus(final int toVersion, final int versionStep, final Function renamer) { ++ super(toVersion, versionStep); ++ this.renamer = renamer; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ // Note: DFU technically enforces namespace due to how they wrote their converter, so we will do the same. ++ NamespaceUtil.enforceForPath(data, "Status"); ++ RenameHelper.renameString(data, "Status", this.renamer); ++ ++ NamespaceUtil.enforceForPath(data.getMap("below_zero_retrogen"), "target_status"); ++ RenameHelper.renameString(data.getMap("below_zero_retrogen"), "target_status", this.renamer); ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6684915d6c0c44328a9296dc3ceb530e69482083 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java +@@ -0,0 +1,38 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.entity; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.function.Function; ++ ++public final class ConverterAbstractEntityRename { ++ ++ private ConverterAbstractEntityRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String id = data.getString("id"); ++ if (id == null) { ++ return null; ++ } ++ ++ final String converted = renamer.apply(id); ++ ++ if (converted != null) { ++ data.setString("id", converted); ++ } ++ ++ return null; ++ } ++ }); ++ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.ENTITY_NAME, renamer); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java +new file mode 100644 +index 0000000000000000000000000000000000000000..985af815e3c23ad7c8b774eac46a7202d3020234 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java +@@ -0,0 +1,44 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.entity; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.function.IntFunction; ++ ++public final class ConverterEntityToVariant extends DataConverter, MapType> { ++ ++ public final String path; ++ public final IntFunction conversion; ++ ++ public ConverterEntityToVariant(final int toVersion, final String path, final IntFunction conversion) { ++ super(toVersion); ++ this.path = path; ++ this.conversion = conversion; ++ } ++ ++ public ConverterEntityToVariant(final int toVersion, final int versionStep, final String path, final IntFunction conversion) { ++ super(toVersion, versionStep); ++ this.path = path; ++ this.conversion = conversion; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final Number value = data.getNumber(this.path); ++ if (value == null) { ++ // nothing to do, DFU does the same ++ return null; ++ } ++ ++ final String converted = this.conversion.apply(value.intValue()); ++ ++ if (converted == null) { ++ throw new NullPointerException("Conversion " + this.conversion + " cannot return null value!"); ++ } ++ ++ // DFU doesn't appear to remove the old field, so why should we? ++ ++ data.setString("variant", converted); ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ed5dcf6f8160742c07e23e98c85409209350a7d4 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java +@@ -0,0 +1,37 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.entity; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.function.Function; ++ ++public final class ConverterEntityVariantRename extends DataConverter, MapType> { ++ ++ private final Function renamer; ++ ++ public ConverterEntityVariantRename(final int toVersion, final Function renamer) { ++ super(toVersion); ++ this.renamer = renamer; ++ } ++ ++ public ConverterEntityVariantRename(final int toVersion, final int versionStep, final Function renamer) { ++ super(toVersion, versionStep); ++ this.renamer = renamer; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String variant = data.getString("variant"); ++ ++ if (variant == null) { ++ return null; ++ } ++ ++ final String rename = this.renamer.apply(variant); ++ ++ if (rename != null) { ++ data.setString("variant", rename); ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java +new file mode 100644 +index 0000000000000000000000000000000000000000..afad2d92f78d4727ff4440ad2778f018d5a2a609 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java +@@ -0,0 +1,371 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.entity; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class ConverterFlattenEntity extends DataConverter, MapType> { ++ ++ private static final Map BLOCK_NAME_TO_ID = new HashMap<>(); ++ static { ++ BLOCK_NAME_TO_ID.put("minecraft:air", 0); ++ BLOCK_NAME_TO_ID.put("minecraft:stone", 1); ++ BLOCK_NAME_TO_ID.put("minecraft:grass", 2); ++ BLOCK_NAME_TO_ID.put("minecraft:dirt", 3); ++ BLOCK_NAME_TO_ID.put("minecraft:cobblestone", 4); ++ BLOCK_NAME_TO_ID.put("minecraft:planks", 5); ++ BLOCK_NAME_TO_ID.put("minecraft:sapling", 6); ++ BLOCK_NAME_TO_ID.put("minecraft:bedrock", 7); ++ BLOCK_NAME_TO_ID.put("minecraft:flowing_water", 8); ++ BLOCK_NAME_TO_ID.put("minecraft:water", 9); ++ BLOCK_NAME_TO_ID.put("minecraft:flowing_lava", 10); ++ BLOCK_NAME_TO_ID.put("minecraft:lava", 11); ++ BLOCK_NAME_TO_ID.put("minecraft:sand", 12); ++ BLOCK_NAME_TO_ID.put("minecraft:gravel", 13); ++ BLOCK_NAME_TO_ID.put("minecraft:gold_ore", 14); ++ BLOCK_NAME_TO_ID.put("minecraft:iron_ore", 15); ++ BLOCK_NAME_TO_ID.put("minecraft:coal_ore", 16); ++ BLOCK_NAME_TO_ID.put("minecraft:log", 17); ++ BLOCK_NAME_TO_ID.put("minecraft:leaves", 18); ++ BLOCK_NAME_TO_ID.put("minecraft:sponge", 19); ++ BLOCK_NAME_TO_ID.put("minecraft:glass", 20); ++ BLOCK_NAME_TO_ID.put("minecraft:lapis_ore", 21); ++ BLOCK_NAME_TO_ID.put("minecraft:lapis_block", 22); ++ BLOCK_NAME_TO_ID.put("minecraft:dispenser", 23); ++ BLOCK_NAME_TO_ID.put("minecraft:sandstone", 24); ++ BLOCK_NAME_TO_ID.put("minecraft:noteblock", 25); ++ BLOCK_NAME_TO_ID.put("minecraft:bed", 26); ++ BLOCK_NAME_TO_ID.put("minecraft:golden_rail", 27); ++ BLOCK_NAME_TO_ID.put("minecraft:detector_rail", 28); ++ BLOCK_NAME_TO_ID.put("minecraft:sticky_piston", 29); ++ BLOCK_NAME_TO_ID.put("minecraft:web", 30); ++ BLOCK_NAME_TO_ID.put("minecraft:tallgrass", 31); ++ BLOCK_NAME_TO_ID.put("minecraft:deadbush", 32); ++ BLOCK_NAME_TO_ID.put("minecraft:piston", 33); ++ BLOCK_NAME_TO_ID.put("minecraft:piston_head", 34); ++ BLOCK_NAME_TO_ID.put("minecraft:wool", 35); ++ BLOCK_NAME_TO_ID.put("minecraft:piston_extension", 36); ++ BLOCK_NAME_TO_ID.put("minecraft:yellow_flower", 37); ++ BLOCK_NAME_TO_ID.put("minecraft:red_flower", 38); ++ BLOCK_NAME_TO_ID.put("minecraft:brown_mushroom", 39); ++ BLOCK_NAME_TO_ID.put("minecraft:red_mushroom", 40); ++ BLOCK_NAME_TO_ID.put("minecraft:gold_block", 41); ++ BLOCK_NAME_TO_ID.put("minecraft:iron_block", 42); ++ BLOCK_NAME_TO_ID.put("minecraft:double_stone_slab", 43); ++ BLOCK_NAME_TO_ID.put("minecraft:stone_slab", 44); ++ BLOCK_NAME_TO_ID.put("minecraft:brick_block", 45); ++ BLOCK_NAME_TO_ID.put("minecraft:tnt", 46); ++ BLOCK_NAME_TO_ID.put("minecraft:bookshelf", 47); ++ BLOCK_NAME_TO_ID.put("minecraft:mossy_cobblestone", 48); ++ BLOCK_NAME_TO_ID.put("minecraft:obsidian", 49); ++ BLOCK_NAME_TO_ID.put("minecraft:torch", 50); ++ BLOCK_NAME_TO_ID.put("minecraft:fire", 51); ++ BLOCK_NAME_TO_ID.put("minecraft:mob_spawner", 52); ++ BLOCK_NAME_TO_ID.put("minecraft:oak_stairs", 53); ++ BLOCK_NAME_TO_ID.put("minecraft:chest", 54); ++ BLOCK_NAME_TO_ID.put("minecraft:redstone_wire", 55); ++ BLOCK_NAME_TO_ID.put("minecraft:diamond_ore", 56); ++ BLOCK_NAME_TO_ID.put("minecraft:diamond_block", 57); ++ BLOCK_NAME_TO_ID.put("minecraft:crafting_table", 58); ++ BLOCK_NAME_TO_ID.put("minecraft:wheat", 59); ++ BLOCK_NAME_TO_ID.put("minecraft:farmland", 60); ++ BLOCK_NAME_TO_ID.put("minecraft:furnace", 61); ++ BLOCK_NAME_TO_ID.put("minecraft:lit_furnace", 62); ++ BLOCK_NAME_TO_ID.put("minecraft:standing_sign", 63); ++ BLOCK_NAME_TO_ID.put("minecraft:wooden_door", 64); ++ BLOCK_NAME_TO_ID.put("minecraft:ladder", 65); ++ BLOCK_NAME_TO_ID.put("minecraft:rail", 66); ++ BLOCK_NAME_TO_ID.put("minecraft:stone_stairs", 67); ++ BLOCK_NAME_TO_ID.put("minecraft:wall_sign", 68); ++ BLOCK_NAME_TO_ID.put("minecraft:lever", 69); ++ BLOCK_NAME_TO_ID.put("minecraft:stone_pressure_plate", 70); ++ BLOCK_NAME_TO_ID.put("minecraft:iron_door", 71); ++ BLOCK_NAME_TO_ID.put("minecraft:wooden_pressure_plate", 72); ++ BLOCK_NAME_TO_ID.put("minecraft:redstone_ore", 73); ++ BLOCK_NAME_TO_ID.put("minecraft:lit_redstone_ore", 74); ++ BLOCK_NAME_TO_ID.put("minecraft:unlit_redstone_torch", 75); ++ BLOCK_NAME_TO_ID.put("minecraft:redstone_torch", 76); ++ BLOCK_NAME_TO_ID.put("minecraft:stone_button", 77); ++ BLOCK_NAME_TO_ID.put("minecraft:snow_layer", 78); ++ BLOCK_NAME_TO_ID.put("minecraft:ice", 79); ++ BLOCK_NAME_TO_ID.put("minecraft:snow", 80); ++ BLOCK_NAME_TO_ID.put("minecraft:cactus", 81); ++ BLOCK_NAME_TO_ID.put("minecraft:clay", 82); ++ BLOCK_NAME_TO_ID.put("minecraft:reeds", 83); ++ BLOCK_NAME_TO_ID.put("minecraft:jukebox", 84); ++ BLOCK_NAME_TO_ID.put("minecraft:fence", 85); ++ BLOCK_NAME_TO_ID.put("minecraft:pumpkin", 86); ++ BLOCK_NAME_TO_ID.put("minecraft:netherrack", 87); ++ BLOCK_NAME_TO_ID.put("minecraft:soul_sand", 88); ++ BLOCK_NAME_TO_ID.put("minecraft:glowstone", 89); ++ BLOCK_NAME_TO_ID.put("minecraft:portal", 90); ++ BLOCK_NAME_TO_ID.put("minecraft:lit_pumpkin", 91); ++ BLOCK_NAME_TO_ID.put("minecraft:cake", 92); ++ BLOCK_NAME_TO_ID.put("minecraft:unpowered_repeater", 93); ++ BLOCK_NAME_TO_ID.put("minecraft:powered_repeater", 94); ++ BLOCK_NAME_TO_ID.put("minecraft:stained_glass", 95); ++ BLOCK_NAME_TO_ID.put("minecraft:trapdoor", 96); ++ BLOCK_NAME_TO_ID.put("minecraft:monster_egg", 97); ++ BLOCK_NAME_TO_ID.put("minecraft:stonebrick", 98); ++ BLOCK_NAME_TO_ID.put("minecraft:brown_mushroom_block", 99); ++ BLOCK_NAME_TO_ID.put("minecraft:red_mushroom_block", 100); ++ BLOCK_NAME_TO_ID.put("minecraft:iron_bars", 101); ++ BLOCK_NAME_TO_ID.put("minecraft:glass_pane", 102); ++ BLOCK_NAME_TO_ID.put("minecraft:melon_block", 103); ++ BLOCK_NAME_TO_ID.put("minecraft:pumpkin_stem", 104); ++ BLOCK_NAME_TO_ID.put("minecraft:melon_stem", 105); ++ BLOCK_NAME_TO_ID.put("minecraft:vine", 106); ++ BLOCK_NAME_TO_ID.put("minecraft:fence_gate", 107); ++ BLOCK_NAME_TO_ID.put("minecraft:brick_stairs", 108); ++ BLOCK_NAME_TO_ID.put("minecraft:stone_brick_stairs", 109); ++ BLOCK_NAME_TO_ID.put("minecraft:mycelium", 110); ++ BLOCK_NAME_TO_ID.put("minecraft:waterlily", 111); ++ BLOCK_NAME_TO_ID.put("minecraft:nether_brick", 112); ++ BLOCK_NAME_TO_ID.put("minecraft:nether_brick_fence", 113); ++ BLOCK_NAME_TO_ID.put("minecraft:nether_brick_stairs", 114); ++ BLOCK_NAME_TO_ID.put("minecraft:nether_wart", 115); ++ BLOCK_NAME_TO_ID.put("minecraft:enchanting_table", 116); ++ BLOCK_NAME_TO_ID.put("minecraft:brewing_stand", 117); ++ BLOCK_NAME_TO_ID.put("minecraft:cauldron", 118); ++ BLOCK_NAME_TO_ID.put("minecraft:end_portal", 119); ++ BLOCK_NAME_TO_ID.put("minecraft:end_portal_frame", 120); ++ BLOCK_NAME_TO_ID.put("minecraft:end_stone", 121); ++ BLOCK_NAME_TO_ID.put("minecraft:dragon_egg", 122); ++ BLOCK_NAME_TO_ID.put("minecraft:redstone_lamp", 123); ++ BLOCK_NAME_TO_ID.put("minecraft:lit_redstone_lamp", 124); ++ BLOCK_NAME_TO_ID.put("minecraft:double_wooden_slab", 125); ++ BLOCK_NAME_TO_ID.put("minecraft:wooden_slab", 126); ++ BLOCK_NAME_TO_ID.put("minecraft:cocoa", 127); ++ BLOCK_NAME_TO_ID.put("minecraft:sandstone_stairs", 128); ++ BLOCK_NAME_TO_ID.put("minecraft:emerald_ore", 129); ++ BLOCK_NAME_TO_ID.put("minecraft:ender_chest", 130); ++ BLOCK_NAME_TO_ID.put("minecraft:tripwire_hook", 131); ++ BLOCK_NAME_TO_ID.put("minecraft:tripwire", 132); ++ BLOCK_NAME_TO_ID.put("minecraft:emerald_block", 133); ++ BLOCK_NAME_TO_ID.put("minecraft:spruce_stairs", 134); ++ BLOCK_NAME_TO_ID.put("minecraft:birch_stairs", 135); ++ BLOCK_NAME_TO_ID.put("minecraft:jungle_stairs", 136); ++ BLOCK_NAME_TO_ID.put("minecraft:command_block", 137); ++ BLOCK_NAME_TO_ID.put("minecraft:beacon", 138); ++ BLOCK_NAME_TO_ID.put("minecraft:cobblestone_wall", 139); ++ BLOCK_NAME_TO_ID.put("minecraft:flower_pot", 140); ++ BLOCK_NAME_TO_ID.put("minecraft:carrots", 141); ++ BLOCK_NAME_TO_ID.put("minecraft:potatoes", 142); ++ BLOCK_NAME_TO_ID.put("minecraft:wooden_button", 143); ++ BLOCK_NAME_TO_ID.put("minecraft:skull", 144); ++ BLOCK_NAME_TO_ID.put("minecraft:anvil", 145); ++ BLOCK_NAME_TO_ID.put("minecraft:trapped_chest", 146); ++ BLOCK_NAME_TO_ID.put("minecraft:light_weighted_pressure_plate", 147); ++ BLOCK_NAME_TO_ID.put("minecraft:heavy_weighted_pressure_plate", 148); ++ BLOCK_NAME_TO_ID.put("minecraft:unpowered_comparator", 149); ++ BLOCK_NAME_TO_ID.put("minecraft:powered_comparator", 150); ++ BLOCK_NAME_TO_ID.put("minecraft:daylight_detector", 151); ++ BLOCK_NAME_TO_ID.put("minecraft:redstone_block", 152); ++ BLOCK_NAME_TO_ID.put("minecraft:quartz_ore", 153); ++ BLOCK_NAME_TO_ID.put("minecraft:hopper", 154); ++ BLOCK_NAME_TO_ID.put("minecraft:quartz_block", 155); ++ BLOCK_NAME_TO_ID.put("minecraft:quartz_stairs", 156); ++ BLOCK_NAME_TO_ID.put("minecraft:activator_rail", 157); ++ BLOCK_NAME_TO_ID.put("minecraft:dropper", 158); ++ BLOCK_NAME_TO_ID.put("minecraft:stained_hardened_clay", 159); ++ BLOCK_NAME_TO_ID.put("minecraft:stained_glass_pane", 160); ++ BLOCK_NAME_TO_ID.put("minecraft:leaves2", 161); ++ BLOCK_NAME_TO_ID.put("minecraft:log2", 162); ++ BLOCK_NAME_TO_ID.put("minecraft:acacia_stairs", 163); ++ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_stairs", 164); ++ BLOCK_NAME_TO_ID.put("minecraft:slime", 165); ++ BLOCK_NAME_TO_ID.put("minecraft:barrier", 166); ++ BLOCK_NAME_TO_ID.put("minecraft:iron_trapdoor", 167); ++ BLOCK_NAME_TO_ID.put("minecraft:prismarine", 168); ++ BLOCK_NAME_TO_ID.put("minecraft:sea_lantern", 169); ++ BLOCK_NAME_TO_ID.put("minecraft:hay_block", 170); ++ BLOCK_NAME_TO_ID.put("minecraft:carpet", 171); ++ BLOCK_NAME_TO_ID.put("minecraft:hardened_clay", 172); ++ BLOCK_NAME_TO_ID.put("minecraft:coal_block", 173); ++ BLOCK_NAME_TO_ID.put("minecraft:packed_ice", 174); ++ BLOCK_NAME_TO_ID.put("minecraft:double_plant", 175); ++ BLOCK_NAME_TO_ID.put("minecraft:standing_banner", 176); ++ BLOCK_NAME_TO_ID.put("minecraft:wall_banner", 177); ++ BLOCK_NAME_TO_ID.put("minecraft:daylight_detector_inverted", 178); ++ BLOCK_NAME_TO_ID.put("minecraft:red_sandstone", 179); ++ BLOCK_NAME_TO_ID.put("minecraft:red_sandstone_stairs", 180); ++ BLOCK_NAME_TO_ID.put("minecraft:double_stone_slab2", 181); ++ BLOCK_NAME_TO_ID.put("minecraft:stone_slab2", 182); ++ BLOCK_NAME_TO_ID.put("minecraft:spruce_fence_gate", 183); ++ BLOCK_NAME_TO_ID.put("minecraft:birch_fence_gate", 184); ++ BLOCK_NAME_TO_ID.put("minecraft:jungle_fence_gate", 185); ++ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_fence_gate", 186); ++ BLOCK_NAME_TO_ID.put("minecraft:acacia_fence_gate", 187); ++ BLOCK_NAME_TO_ID.put("minecraft:spruce_fence", 188); ++ BLOCK_NAME_TO_ID.put("minecraft:birch_fence", 189); ++ BLOCK_NAME_TO_ID.put("minecraft:jungle_fence", 190); ++ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_fence", 191); ++ BLOCK_NAME_TO_ID.put("minecraft:acacia_fence", 192); ++ BLOCK_NAME_TO_ID.put("minecraft:spruce_door", 193); ++ BLOCK_NAME_TO_ID.put("minecraft:birch_door", 194); ++ BLOCK_NAME_TO_ID.put("minecraft:jungle_door", 195); ++ BLOCK_NAME_TO_ID.put("minecraft:acacia_door", 196); ++ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_door", 197); ++ BLOCK_NAME_TO_ID.put("minecraft:end_rod", 198); ++ BLOCK_NAME_TO_ID.put("minecraft:chorus_plant", 199); ++ BLOCK_NAME_TO_ID.put("minecraft:chorus_flower", 200); ++ BLOCK_NAME_TO_ID.put("minecraft:purpur_block", 201); ++ BLOCK_NAME_TO_ID.put("minecraft:purpur_pillar", 202); ++ BLOCK_NAME_TO_ID.put("minecraft:purpur_stairs", 203); ++ BLOCK_NAME_TO_ID.put("minecraft:purpur_double_slab", 204); ++ BLOCK_NAME_TO_ID.put("minecraft:purpur_slab", 205); ++ BLOCK_NAME_TO_ID.put("minecraft:end_bricks", 206); ++ BLOCK_NAME_TO_ID.put("minecraft:beetroots", 207); ++ BLOCK_NAME_TO_ID.put("minecraft:grass_path", 208); ++ BLOCK_NAME_TO_ID.put("minecraft:end_gateway", 209); ++ BLOCK_NAME_TO_ID.put("minecraft:repeating_command_block", 210); ++ BLOCK_NAME_TO_ID.put("minecraft:chain_command_block", 211); ++ BLOCK_NAME_TO_ID.put("minecraft:frosted_ice", 212); ++ BLOCK_NAME_TO_ID.put("minecraft:magma", 213); ++ BLOCK_NAME_TO_ID.put("minecraft:nether_wart_block", 214); ++ BLOCK_NAME_TO_ID.put("minecraft:red_nether_brick", 215); ++ BLOCK_NAME_TO_ID.put("minecraft:bone_block", 216); ++ BLOCK_NAME_TO_ID.put("minecraft:structure_void", 217); ++ BLOCK_NAME_TO_ID.put("minecraft:observer", 218); ++ BLOCK_NAME_TO_ID.put("minecraft:white_shulker_box", 219); ++ BLOCK_NAME_TO_ID.put("minecraft:orange_shulker_box", 220); ++ BLOCK_NAME_TO_ID.put("minecraft:magenta_shulker_box", 221); ++ BLOCK_NAME_TO_ID.put("minecraft:light_blue_shulker_box", 222); ++ BLOCK_NAME_TO_ID.put("minecraft:yellow_shulker_box", 223); ++ BLOCK_NAME_TO_ID.put("minecraft:lime_shulker_box", 224); ++ BLOCK_NAME_TO_ID.put("minecraft:pink_shulker_box", 225); ++ BLOCK_NAME_TO_ID.put("minecraft:gray_shulker_box", 226); ++ BLOCK_NAME_TO_ID.put("minecraft:silver_shulker_box", 227); ++ BLOCK_NAME_TO_ID.put("minecraft:cyan_shulker_box", 228); ++ BLOCK_NAME_TO_ID.put("minecraft:purple_shulker_box", 229); ++ BLOCK_NAME_TO_ID.put("minecraft:blue_shulker_box", 230); ++ BLOCK_NAME_TO_ID.put("minecraft:brown_shulker_box", 231); ++ BLOCK_NAME_TO_ID.put("minecraft:green_shulker_box", 232); ++ BLOCK_NAME_TO_ID.put("minecraft:red_shulker_box", 233); ++ BLOCK_NAME_TO_ID.put("minecraft:black_shulker_box", 234); ++ BLOCK_NAME_TO_ID.put("minecraft:white_glazed_terracotta", 235); ++ BLOCK_NAME_TO_ID.put("minecraft:orange_glazed_terracotta", 236); ++ BLOCK_NAME_TO_ID.put("minecraft:magenta_glazed_terracotta", 237); ++ BLOCK_NAME_TO_ID.put("minecraft:light_blue_glazed_terracotta", 238); ++ BLOCK_NAME_TO_ID.put("minecraft:yellow_glazed_terracotta", 239); ++ BLOCK_NAME_TO_ID.put("minecraft:lime_glazed_terracotta", 240); ++ BLOCK_NAME_TO_ID.put("minecraft:pink_glazed_terracotta", 241); ++ BLOCK_NAME_TO_ID.put("minecraft:gray_glazed_terracotta", 242); ++ BLOCK_NAME_TO_ID.put("minecraft:silver_glazed_terracotta", 243); ++ BLOCK_NAME_TO_ID.put("minecraft:cyan_glazed_terracotta", 244); ++ BLOCK_NAME_TO_ID.put("minecraft:purple_glazed_terracotta", 245); ++ BLOCK_NAME_TO_ID.put("minecraft:blue_glazed_terracotta", 246); ++ BLOCK_NAME_TO_ID.put("minecraft:brown_glazed_terracotta", 247); ++ BLOCK_NAME_TO_ID.put("minecraft:green_glazed_terracotta", 248); ++ BLOCK_NAME_TO_ID.put("minecraft:red_glazed_terracotta", 249); ++ BLOCK_NAME_TO_ID.put("minecraft:black_glazed_terracotta", 250); ++ BLOCK_NAME_TO_ID.put("minecraft:concrete", 251); ++ BLOCK_NAME_TO_ID.put("minecraft:concrete_powder", 252); ++ BLOCK_NAME_TO_ID.put("minecraft:structure_block", 255); ++ } ++ ++ protected static final int VERSION = MCVersions.V17W47A; ++ ++ protected final String[] paths; ++ ++ public ConverterFlattenEntity(final String... paths) { ++ super(VERSION, 3); ++ this.paths = paths; ++ } ++ ++ private static void register(final String id, final String... paths) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, new ConverterFlattenEntity(paths)); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:falling_block", new DataConverter<>(VERSION, 3) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int blockId; ++ if (data.hasKey("Block")) { ++ final Number id = data.getNumber("Block"); ++ if (id != null) { ++ blockId = id.intValue(); ++ } else { ++ blockId = getBlockId(data.getString("Block")); ++ } ++ } else { ++ final Number tileId = data.getNumber("TileID"); ++ if (tileId != null) { ++ blockId = tileId.intValue(); ++ } else { ++ blockId = data.getByte("Tile") & 255; ++ } ++ } ++ ++ final int blockData = data.getInt("Data") & 15; ++ ++ data.remove("Block"); // from type update ++ data.remove("Data"); ++ data.remove("TileID"); ++ data.remove("Tile"); ++ ++ // key is from type update ++ data.setMap("BlockState", HelperBlockFlatteningV1450.getNBTForId((blockId << 4) | blockData).copy()); // copy to avoid problems with later state datafixers ++ ++ return null; ++ } ++ }); ++ register("minecraft:enderman", "carried", "carriedData", "carriedBlockState"); ++ register("minecraft:arrow", "inTile", "inData", "inBlockState"); ++ register("minecraft:spectral_arrow", "inTile", "inData", "inBlockState"); ++ register("minecraft:egg", "inTile"); ++ register("minecraft:ender_pearl", "inTile"); ++ register("minecraft:fireball", "inTile"); ++ register("minecraft:potion", "inTile"); ++ register("minecraft:small_fireball", "inTile"); ++ register("minecraft:snowball", "inTile"); ++ register("minecraft:wither_skull", "inTile"); ++ register("minecraft:xp_bottle", "inTile"); ++ register("minecraft:commandblock_minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ register("minecraft:minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ register("minecraft:chest_minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ register("minecraft:furnace_minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ register("minecraft:tnt_minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ register("minecraft:hopper_minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ register("minecraft:spawner_minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ } ++ ++ public static int getBlockId(final String block) { ++ final Integer ret = BLOCK_NAME_TO_ID.get(block); ++ return ret == null ? 0 : ret.intValue(); ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (this.paths.length == 1) { ++ data.remove(this.paths[0]); ++ return null; ++ } ++ final String idPath = this.paths[0]; ++ final String dataPath = this.paths[1]; ++ final String outputStatePath = this.paths[2]; ++ ++ final int blockId; ++ if (data.hasKey(idPath, ObjectType.NUMBER)) { ++ blockId = data.getInt(idPath); ++ } else { ++ blockId = getBlockId(data.getString(idPath)); ++ } ++ ++ final int blockData = data.getInt(dataPath) & 15; ++ ++ data.remove(idPath); // from type update ++ data.remove(dataPath); ++ ++ data.setMap(outputStatePath, HelperBlockFlatteningV1450.getNBTForId((blockId << 4) | blockData).copy()); // copy to avoid problems with later state datafixers ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4ab607f946782cc483535564e86fa9753dd7897a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java +@@ -0,0 +1,30 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.helpers; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class AddFlagIfAbsent extends DataConverter, MapType> { ++ ++ public final String path; ++ public final boolean dfl; ++ ++ public AddFlagIfAbsent(final int toVersion, final String path, final boolean dfl) { ++ super(toVersion); ++ this.path = path; ++ this.dfl = dfl; ++ } ++ ++ public AddFlagIfAbsent(final int toVersion, final int versionStep, final String path, final boolean dfl) { ++ super(toVersion, versionStep); ++ this.path = path; ++ this.dfl = dfl; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!data.hasKey(this.path)) { ++ data.setBoolean(this.path, this.dfl); ++ } ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bc79670f47aaa413ea3e96ef6a32e14099ad8a58 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java +@@ -0,0 +1,24 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.helpers; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCValueType; ++import java.util.function.Function; ++ ++public final class ConverterAbstractStringValueTypeRename { ++ ++ private ConverterAbstractStringValueTypeRename() {} ++ ++ public static void register(final int version, final MCValueType type, final Function renamer) { ++ register(version, 0, type, renamer); ++ } ++ public static void register(final int version, final int subVersion, final MCValueType type, final Function renamer) { ++ type.addConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public Object convert(final Object data, final long sourceVersion, final long toVersion) { ++ final String ret = (data instanceof String) ? renamer.apply((String)data) : null; ++ return ret == data ? null : ret; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4f4f4cb6037c2a46ffcf427f5812164bbb98b8b7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java +@@ -0,0 +1,1829 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.helpers; ++ ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; ++import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import net.minecraft.nbt.TagParser; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class HelperBlockFlatteningV1450 { ++ ++ protected static final MapType[] FLATTENED_BY_ID = new MapType[4096]; ++ protected static final MapType[] BLOCK_DEFAULTS = new MapType[4096]; ++ ++ private static final Object2IntOpenHashMap> ID_BY_OLD_NBT = new Object2IntOpenHashMap>(64, 0.7f) { ++ @Override ++ public int put(final MapType o, final int v) { ++ if (this.containsKey(o)) { ++ throw new RuntimeException("Already contains mapping for " + o); ++ } ++ ++ return super.put(o, v); ++ } ++ }; ++ static { ++ ID_BY_OLD_NBT.defaultReturnValue(-1); ++ } ++ ++ private static final Object2IntOpenHashMap ID_BY_OLD_NAME = new Object2IntOpenHashMap(64, 0.7f) { ++ @Override ++ public int put(final String o, final int v) { ++ if (this.containsKey(o)) { ++ throw new RuntimeException("Already contains mapping for " + o); ++ } ++ ++ return super.put(o, v); ++ } ++ }; ++ static { ++ ID_BY_OLD_NAME.defaultReturnValue(-1); ++ } ++ ++ // map used to ensure that each parsed block state contains no duplicates ++ protected static final Map, MapType> IDENTITY_ENSURE = new HashMap<>(); ++ ++ public static MapType parseTag(final String blockstate) { ++ try { ++ final MapType ret = new NBTMapType(TagParser.parseTag(blockstate.replace('\'', '"'))); ++ ++ synchronized (IDENTITY_ENSURE) { ++ final MapType identity = IDENTITY_ENSURE.putIfAbsent(ret, ret); ++ ++ return identity == null ? ret : identity; ++ } ++ ++ } catch (final Exception ex) { ++ throw new RuntimeException("Exception parsing " + blockstate, ex); ++ } ++ } ++ ++ private static void register(final int id, final String flattened, final String... preFlattenings) { ++ final MapType flattenedNBT = parseTag(flattened); ++ if (FLATTENED_BY_ID[id] != null) { ++ throw new RuntimeException("Mapping already exists for id " + id); ++ } ++ FLATTENED_BY_ID[id] = flattenedNBT; ++ ++ // it's important that we register ids from smallest to largest, so that ++ // the default is going to be correct ++ final int block = id >> 4; ++ if (BLOCK_DEFAULTS[block] == null) { ++ BLOCK_DEFAULTS[block] = flattenedNBT; ++ } ++ ++ for (final String preFlattening : preFlattenings) { ++ final MapType preFlatteningNBT = parseTag(preFlattening); ++ final String name = preFlatteningNBT.getString("Name"); ++ if (name == null) { ++ throw new RuntimeException("Name does not exist for pre flattenings for id " + id); ++ } ++ ++ // putIfAbsent so we default to the lowest id, which is going to be the block default ++ ID_BY_OLD_NAME.putIfAbsent(name, id); ++ ID_BY_OLD_NBT.put(preFlatteningNBT, id); ++ } ++ } ++ ++ private static void finalizeMaps() { ++ for(int i = 0; i < FLATTENED_BY_ID.length; ++i) { ++ if (FLATTENED_BY_ID[i] == null) { ++ FLATTENED_BY_ID[i] = BLOCK_DEFAULTS[i >> 4]; ++ } ++ } ++ } ++ ++ public static MapType flattenNBT(final MapType old) { ++ final int id = ID_BY_OLD_NBT.getInt(old); ++ final MapType ret = getNBTForIdRaw(id); ++ ++ return ret == null ? old : ret; ++ } ++ ++ public static String getNewBlockName(final String old) { ++ final int id = ID_BY_OLD_NAME.getInt(old); ++ final MapType ret = getNBTForIdRaw(id); ++ return ret == null ? old : ret.getString("Name"); ++ } ++ ++ public static String getNameForId(final int block) { ++ final MapType nbt = getNBTForIdRaw(block); ++ return nbt == null ? "minecraft:air" : nbt.getString("Name"); ++ } ++ ++ protected static MapType getNBTForIdRaw(final int block) { ++ return block >= 0 && block < FLATTENED_BY_ID.length ? FLATTENED_BY_ID[block] : null; ++ } ++ ++ public static MapType getNBTForId(final int block) { ++ MapType ret = getNBTForIdRaw(block); ++ return ret == null ? FLATTENED_BY_ID[0] : ret; ++ } ++ ++ private HelperBlockFlatteningV1450() {} ++ ++ static { ++ ID_BY_OLD_NBT.defaultReturnValue(-1); ++ register(0, "{Name:'minecraft:air'}", "{Name:'minecraft:air'}"); ++ register(16, "{Name:'minecraft:stone'}", "{Name:'minecraft:stone',Properties:{variant:'stone'}}"); ++ register(17, "{Name:'minecraft:granite'}", "{Name:'minecraft:stone',Properties:{variant:'granite'}}"); ++ register(18, "{Name:'minecraft:polished_granite'}", "{Name:'minecraft:stone',Properties:{variant:'smooth_granite'}}"); ++ register(19, "{Name:'minecraft:diorite'}", "{Name:'minecraft:stone',Properties:{variant:'diorite'}}"); ++ register(20, "{Name:'minecraft:polished_diorite'}", "{Name:'minecraft:stone',Properties:{variant:'smooth_diorite'}}"); ++ register(21, "{Name:'minecraft:andesite'}", "{Name:'minecraft:stone',Properties:{variant:'andesite'}}"); ++ register(22, "{Name:'minecraft:polished_andesite'}", "{Name:'minecraft:stone',Properties:{variant:'smooth_andesite'}}"); ++ register(32, "{Name:'minecraft:grass_block',Properties:{snowy:'false'}}", "{Name:'minecraft:grass',Properties:{snowy:'false'}}", "{Name:'minecraft:grass',Properties:{snowy:'true'}}"); ++ register(48, "{Name:'minecraft:dirt'}", "{Name:'minecraft:dirt',Properties:{snowy:'false',variant:'dirt'}}", "{Name:'minecraft:dirt',Properties:{snowy:'true',variant:'dirt'}}"); ++ register(49, "{Name:'minecraft:coarse_dirt'}", "{Name:'minecraft:dirt',Properties:{snowy:'false',variant:'coarse_dirt'}}", "{Name:'minecraft:dirt',Properties:{snowy:'true',variant:'coarse_dirt'}}"); ++ register(50, "{Name:'minecraft:podzol',Properties:{snowy:'false'}}", "{Name:'minecraft:dirt',Properties:{snowy:'false',variant:'podzol'}}", "{Name:'minecraft:dirt',Properties:{snowy:'true',variant:'podzol'}}"); ++ register(64, "{Name:'minecraft:cobblestone'}", "{Name:'minecraft:cobblestone'}"); ++ register(80, "{Name:'minecraft:oak_planks'}", "{Name:'minecraft:planks',Properties:{variant:'oak'}}"); ++ register(81, "{Name:'minecraft:spruce_planks'}", "{Name:'minecraft:planks',Properties:{variant:'spruce'}}"); ++ register(82, "{Name:'minecraft:birch_planks'}", "{Name:'minecraft:planks',Properties:{variant:'birch'}}"); ++ register(83, "{Name:'minecraft:jungle_planks'}", "{Name:'minecraft:planks',Properties:{variant:'jungle'}}"); ++ register(84, "{Name:'minecraft:acacia_planks'}", "{Name:'minecraft:planks',Properties:{variant:'acacia'}}"); ++ register(85, "{Name:'minecraft:dark_oak_planks'}", "{Name:'minecraft:planks',Properties:{variant:'dark_oak'}}"); ++ register(96, "{Name:'minecraft:oak_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'oak'}}"); ++ register(97, "{Name:'minecraft:spruce_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'spruce'}}"); ++ register(98, "{Name:'minecraft:birch_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'birch'}}"); ++ register(99, "{Name:'minecraft:jungle_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'jungle'}}"); ++ register(100, "{Name:'minecraft:acacia_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'acacia'}}"); ++ register(101, "{Name:'minecraft:dark_oak_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'dark_oak'}}"); ++ register(104, "{Name:'minecraft:oak_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'oak'}}"); ++ register(105, "{Name:'minecraft:spruce_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'spruce'}}"); ++ register(106, "{Name:'minecraft:birch_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'birch'}}"); ++ register(107, "{Name:'minecraft:jungle_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'jungle'}}"); ++ register(108, "{Name:'minecraft:acacia_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'acacia'}}"); ++ register(109, "{Name:'minecraft:dark_oak_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'dark_oak'}}"); ++ register(112, "{Name:'minecraft:bedrock'}", "{Name:'minecraft:bedrock'}"); ++ register(128, "{Name:'minecraft:water',Properties:{level:'0'}}", "{Name:'minecraft:flowing_water',Properties:{level:'0'}}"); ++ register(129, "{Name:'minecraft:water',Properties:{level:'1'}}", "{Name:'minecraft:flowing_water',Properties:{level:'1'}}"); ++ register(130, "{Name:'minecraft:water',Properties:{level:'2'}}", "{Name:'minecraft:flowing_water',Properties:{level:'2'}}"); ++ register(131, "{Name:'minecraft:water',Properties:{level:'3'}}", "{Name:'minecraft:flowing_water',Properties:{level:'3'}}"); ++ register(132, "{Name:'minecraft:water',Properties:{level:'4'}}", "{Name:'minecraft:flowing_water',Properties:{level:'4'}}"); ++ register(133, "{Name:'minecraft:water',Properties:{level:'5'}}", "{Name:'minecraft:flowing_water',Properties:{level:'5'}}"); ++ register(134, "{Name:'minecraft:water',Properties:{level:'6'}}", "{Name:'minecraft:flowing_water',Properties:{level:'6'}}"); ++ register(135, "{Name:'minecraft:water',Properties:{level:'7'}}", "{Name:'minecraft:flowing_water',Properties:{level:'7'}}"); ++ register(136, "{Name:'minecraft:water',Properties:{level:'8'}}", "{Name:'minecraft:flowing_water',Properties:{level:'8'}}"); ++ register(137, "{Name:'minecraft:water',Properties:{level:'9'}}", "{Name:'minecraft:flowing_water',Properties:{level:'9'}}"); ++ register(138, "{Name:'minecraft:water',Properties:{level:'10'}}", "{Name:'minecraft:flowing_water',Properties:{level:'10'}}"); ++ register(139, "{Name:'minecraft:water',Properties:{level:'11'}}", "{Name:'minecraft:flowing_water',Properties:{level:'11'}}"); ++ register(140, "{Name:'minecraft:water',Properties:{level:'12'}}", "{Name:'minecraft:flowing_water',Properties:{level:'12'}}"); ++ register(141, "{Name:'minecraft:water',Properties:{level:'13'}}", "{Name:'minecraft:flowing_water',Properties:{level:'13'}}"); ++ register(142, "{Name:'minecraft:water',Properties:{level:'14'}}", "{Name:'minecraft:flowing_water',Properties:{level:'14'}}"); ++ register(143, "{Name:'minecraft:water',Properties:{level:'15'}}", "{Name:'minecraft:flowing_water',Properties:{level:'15'}}"); ++ register(144, "{Name:'minecraft:water',Properties:{level:'0'}}", "{Name:'minecraft:water',Properties:{level:'0'}}"); ++ register(145, "{Name:'minecraft:water',Properties:{level:'1'}}", "{Name:'minecraft:water',Properties:{level:'1'}}"); ++ register(146, "{Name:'minecraft:water',Properties:{level:'2'}}", "{Name:'minecraft:water',Properties:{level:'2'}}"); ++ register(147, "{Name:'minecraft:water',Properties:{level:'3'}}", "{Name:'minecraft:water',Properties:{level:'3'}}"); ++ register(148, "{Name:'minecraft:water',Properties:{level:'4'}}", "{Name:'minecraft:water',Properties:{level:'4'}}"); ++ register(149, "{Name:'minecraft:water',Properties:{level:'5'}}", "{Name:'minecraft:water',Properties:{level:'5'}}"); ++ register(150, "{Name:'minecraft:water',Properties:{level:'6'}}", "{Name:'minecraft:water',Properties:{level:'6'}}"); ++ register(151, "{Name:'minecraft:water',Properties:{level:'7'}}", "{Name:'minecraft:water',Properties:{level:'7'}}"); ++ register(152, "{Name:'minecraft:water',Properties:{level:'8'}}", "{Name:'minecraft:water',Properties:{level:'8'}}"); ++ register(153, "{Name:'minecraft:water',Properties:{level:'9'}}", "{Name:'minecraft:water',Properties:{level:'9'}}"); ++ register(154, "{Name:'minecraft:water',Properties:{level:'10'}}", "{Name:'minecraft:water',Properties:{level:'10'}}"); ++ register(155, "{Name:'minecraft:water',Properties:{level:'11'}}", "{Name:'minecraft:water',Properties:{level:'11'}}"); ++ register(156, "{Name:'minecraft:water',Properties:{level:'12'}}", "{Name:'minecraft:water',Properties:{level:'12'}}"); ++ register(157, "{Name:'minecraft:water',Properties:{level:'13'}}", "{Name:'minecraft:water',Properties:{level:'13'}}"); ++ register(158, "{Name:'minecraft:water',Properties:{level:'14'}}", "{Name:'minecraft:water',Properties:{level:'14'}}"); ++ register(159, "{Name:'minecraft:water',Properties:{level:'15'}}", "{Name:'minecraft:water',Properties:{level:'15'}}"); ++ register(160, "{Name:'minecraft:lava',Properties:{level:'0'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'0'}}"); ++ register(161, "{Name:'minecraft:lava',Properties:{level:'1'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'1'}}"); ++ register(162, "{Name:'minecraft:lava',Properties:{level:'2'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'2'}}"); ++ register(163, "{Name:'minecraft:lava',Properties:{level:'3'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'3'}}"); ++ register(164, "{Name:'minecraft:lava',Properties:{level:'4'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'4'}}"); ++ register(165, "{Name:'minecraft:lava',Properties:{level:'5'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'5'}}"); ++ register(166, "{Name:'minecraft:lava',Properties:{level:'6'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'6'}}"); ++ register(167, "{Name:'minecraft:lava',Properties:{level:'7'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'7'}}"); ++ register(168, "{Name:'minecraft:lava',Properties:{level:'8'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'8'}}"); ++ register(169, "{Name:'minecraft:lava',Properties:{level:'9'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'9'}}"); ++ register(170, "{Name:'minecraft:lava',Properties:{level:'10'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'10'}}"); ++ register(171, "{Name:'minecraft:lava',Properties:{level:'11'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'11'}}"); ++ register(172, "{Name:'minecraft:lava',Properties:{level:'12'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'12'}}"); ++ register(173, "{Name:'minecraft:lava',Properties:{level:'13'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'13'}}"); ++ register(174, "{Name:'minecraft:lava',Properties:{level:'14'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'14'}}"); ++ register(175, "{Name:'minecraft:lava',Properties:{level:'15'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'15'}}"); ++ register(176, "{Name:'minecraft:lava',Properties:{level:'0'}}", "{Name:'minecraft:lava',Properties:{level:'0'}}"); ++ register(177, "{Name:'minecraft:lava',Properties:{level:'1'}}", "{Name:'minecraft:lava',Properties:{level:'1'}}"); ++ register(178, "{Name:'minecraft:lava',Properties:{level:'2'}}", "{Name:'minecraft:lava',Properties:{level:'2'}}"); ++ register(179, "{Name:'minecraft:lava',Properties:{level:'3'}}", "{Name:'minecraft:lava',Properties:{level:'3'}}"); ++ register(180, "{Name:'minecraft:lava',Properties:{level:'4'}}", "{Name:'minecraft:lava',Properties:{level:'4'}}"); ++ register(181, "{Name:'minecraft:lava',Properties:{level:'5'}}", "{Name:'minecraft:lava',Properties:{level:'5'}}"); ++ register(182, "{Name:'minecraft:lava',Properties:{level:'6'}}", "{Name:'minecraft:lava',Properties:{level:'6'}}"); ++ register(183, "{Name:'minecraft:lava',Properties:{level:'7'}}", "{Name:'minecraft:lava',Properties:{level:'7'}}"); ++ register(184, "{Name:'minecraft:lava',Properties:{level:'8'}}", "{Name:'minecraft:lava',Properties:{level:'8'}}"); ++ register(185, "{Name:'minecraft:lava',Properties:{level:'9'}}", "{Name:'minecraft:lava',Properties:{level:'9'}}"); ++ register(186, "{Name:'minecraft:lava',Properties:{level:'10'}}", "{Name:'minecraft:lava',Properties:{level:'10'}}"); ++ register(187, "{Name:'minecraft:lava',Properties:{level:'11'}}", "{Name:'minecraft:lava',Properties:{level:'11'}}"); ++ register(188, "{Name:'minecraft:lava',Properties:{level:'12'}}", "{Name:'minecraft:lava',Properties:{level:'12'}}"); ++ register(189, "{Name:'minecraft:lava',Properties:{level:'13'}}", "{Name:'minecraft:lava',Properties:{level:'13'}}"); ++ register(190, "{Name:'minecraft:lava',Properties:{level:'14'}}", "{Name:'minecraft:lava',Properties:{level:'14'}}"); ++ register(191, "{Name:'minecraft:lava',Properties:{level:'15'}}", "{Name:'minecraft:lava',Properties:{level:'15'}}"); ++ register(192, "{Name:'minecraft:sand'}", "{Name:'minecraft:sand',Properties:{variant:'sand'}}"); ++ register(193, "{Name:'minecraft:red_sand'}", "{Name:'minecraft:sand',Properties:{variant:'red_sand'}}"); ++ register(208, "{Name:'minecraft:gravel'}", "{Name:'minecraft:gravel'}"); ++ register(224, "{Name:'minecraft:gold_ore'}", "{Name:'minecraft:gold_ore'}"); ++ register(240, "{Name:'minecraft:iron_ore'}", "{Name:'minecraft:iron_ore'}"); ++ register(256, "{Name:'minecraft:coal_ore'}", "{Name:'minecraft:coal_ore'}"); ++ register(272, "{Name:'minecraft:oak_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'oak'}}"); ++ register(273, "{Name:'minecraft:spruce_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'spruce'}}"); ++ register(274, "{Name:'minecraft:birch_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'birch'}}"); ++ register(275, "{Name:'minecraft:jungle_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'jungle'}}"); ++ register(276, "{Name:'minecraft:oak_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'oak'}}"); ++ register(277, "{Name:'minecraft:spruce_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'spruce'}}"); ++ register(278, "{Name:'minecraft:birch_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'birch'}}"); ++ register(279, "{Name:'minecraft:jungle_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'jungle'}}"); ++ register(280, "{Name:'minecraft:oak_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'oak'}}"); ++ register(281, "{Name:'minecraft:spruce_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'spruce'}}"); ++ register(282, "{Name:'minecraft:birch_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'birch'}}"); ++ register(283, "{Name:'minecraft:jungle_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'jungle'}}"); ++ register(284, "{Name:'minecraft:oak_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'oak'}}"); ++ register(285, "{Name:'minecraft:spruce_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'spruce'}}"); ++ register(286, "{Name:'minecraft:birch_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'birch'}}"); ++ register(287, "{Name:'minecraft:jungle_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'jungle'}}"); ++ register(288, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'oak'}}"); ++ register(289, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'spruce'}}"); ++ register(290, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'birch'}}"); ++ register(291, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'jungle'}}"); ++ register(292, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'oak'}}"); ++ register(293, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'spruce'}}"); ++ register(294, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'birch'}}"); ++ register(295, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'jungle'}}"); ++ register(296, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'oak'}}"); ++ register(297, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'spruce'}}"); ++ register(298, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'birch'}}"); ++ register(299, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'jungle'}}"); ++ register(300, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'oak'}}"); ++ register(301, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'spruce'}}"); ++ register(302, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'birch'}}"); ++ register(303, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'jungle'}}"); ++ register(304, "{Name:'minecraft:sponge'}", "{Name:'minecraft:sponge',Properties:{wet:'false'}}"); ++ register(305, "{Name:'minecraft:wet_sponge'}", "{Name:'minecraft:sponge',Properties:{wet:'true'}}"); ++ register(320, "{Name:'minecraft:glass'}", "{Name:'minecraft:glass'}"); ++ register(336, "{Name:'minecraft:lapis_ore'}", "{Name:'minecraft:lapis_ore'}"); ++ register(352, "{Name:'minecraft:lapis_block'}", "{Name:'minecraft:lapis_block'}"); ++ register(368, "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'false'}}"); ++ register(369, "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'false'}}"); ++ register(370, "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'false'}}"); ++ register(371, "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'false'}}"); ++ register(372, "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'false'}}"); ++ register(373, "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'false'}}"); ++ register(376, "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'true'}}"); ++ register(377, "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'true'}}"); ++ register(378, "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'true'}}"); ++ register(379, "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'true'}}"); ++ register(380, "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'true'}}"); ++ register(381, "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'true'}}"); ++ register(384, "{Name:'minecraft:sandstone'}", "{Name:'minecraft:sandstone',Properties:{type:'sandstone'}}"); ++ register(385, "{Name:'minecraft:chiseled_sandstone'}", "{Name:'minecraft:sandstone',Properties:{type:'chiseled_sandstone'}}"); ++ register(386, "{Name:'minecraft:cut_sandstone'}", "{Name:'minecraft:sandstone',Properties:{type:'smooth_sandstone'}}"); ++ register(400, "{Name:'minecraft:note_block'}", "{Name:'minecraft:noteblock'}"); ++ register(416, "{Name:'minecraft:red_bed',Properties:{facing:'south',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'true',part:'foot'}}"); ++ register(417, "{Name:'minecraft:red_bed',Properties:{facing:'west',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'true',part:'foot'}}"); ++ register(418, "{Name:'minecraft:red_bed',Properties:{facing:'north',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'true',part:'foot'}}"); ++ register(419, "{Name:'minecraft:red_bed',Properties:{facing:'east',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'true',part:'foot'}}"); ++ register(424, "{Name:'minecraft:red_bed',Properties:{facing:'south',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'false',part:'head'}}"); ++ register(425, "{Name:'minecraft:red_bed',Properties:{facing:'west',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'false',part:'head'}}"); ++ register(426, "{Name:'minecraft:red_bed',Properties:{facing:'north',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'false',part:'head'}}"); ++ register(427, "{Name:'minecraft:red_bed',Properties:{facing:'east',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'false',part:'head'}}"); ++ register(428, "{Name:'minecraft:red_bed',Properties:{facing:'south',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'true',part:'head'}}"); ++ register(429, "{Name:'minecraft:red_bed',Properties:{facing:'west',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'true',part:'head'}}"); ++ register(430, "{Name:'minecraft:red_bed',Properties:{facing:'north',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'true',part:'head'}}"); ++ register(431, "{Name:'minecraft:red_bed',Properties:{facing:'east',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'true',part:'head'}}"); ++ register(432, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'north_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'north_south'}}"); ++ register(433, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'east_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'east_west'}}"); ++ register(434, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_east'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_east'}}"); ++ register(435, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_west'}}"); ++ register(436, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_north'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_north'}}"); ++ register(437, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_south'}}"); ++ register(440, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'north_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'north_south'}}"); ++ register(441, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'east_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'east_west'}}"); ++ register(442, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_east'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_east'}}"); ++ register(443, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_west'}}"); ++ register(444, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_north'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_north'}}"); ++ register(445, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_south'}}"); ++ register(448, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'north_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'north_south'}}"); ++ register(449, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'east_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'east_west'}}"); ++ register(450, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_east'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_east'}}"); ++ register(451, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_west'}}"); ++ register(452, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_north'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_north'}}"); ++ register(453, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_south'}}"); ++ register(456, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'north_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'north_south'}}"); ++ register(457, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'east_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'east_west'}}"); ++ register(458, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_east'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_east'}}"); ++ register(459, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_west'}}"); ++ register(460, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_north'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_north'}}"); ++ register(461, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_south'}}"); ++ register(464, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'down'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'down'}}"); ++ register(465, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'up'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'up'}}"); ++ register(466, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'north'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'north'}}"); ++ register(467, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'south'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'south'}}"); ++ register(468, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'west'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'west'}}"); ++ register(469, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'east'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'east'}}"); ++ register(472, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'down'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'down'}}"); ++ register(473, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'up'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'up'}}"); ++ register(474, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'north'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'north'}}"); ++ register(475, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'south'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'south'}}"); ++ register(476, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'west'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'west'}}"); ++ register(477, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'east'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'east'}}"); ++ register(480, "{Name:'minecraft:cobweb'}", "{Name:'minecraft:web'}"); ++ register(496, "{Name:'minecraft:dead_bush'}", "{Name:'minecraft:tallgrass',Properties:{type:'dead_bush'}}"); ++ register(497, "{Name:'minecraft:grass'}", "{Name:'minecraft:tallgrass',Properties:{type:'tall_grass'}}"); ++ register(498, "{Name:'minecraft:fern'}", "{Name:'minecraft:tallgrass',Properties:{type:'fern'}}"); ++ register(512, "{Name:'minecraft:dead_bush'}", "{Name:'minecraft:deadbush'}"); ++ register(528, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'down'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'down'}}"); ++ register(529, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'up'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'up'}}"); ++ register(530, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'north'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'north'}}"); ++ register(531, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'south'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'south'}}"); ++ register(532, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'west'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'west'}}"); ++ register(533, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'east'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'east'}}"); ++ register(536, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'down'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'down'}}"); ++ register(537, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'up'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'up'}}"); ++ register(538, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'north'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'north'}}"); ++ register(539, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'south'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'south'}}"); ++ register(540, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'west'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'west'}}"); ++ register(541, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'east'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'east'}}"); ++ register(544, "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'true',type:'normal'}}"); ++ register(545, "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'true',type:'normal'}}"); ++ register(546, "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'true',type:'normal'}}"); ++ register(547, "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'true',type:'normal'}}"); ++ register(548, "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'true',type:'normal'}}"); ++ register(549, "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'true',type:'normal'}}"); ++ register(552, "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'true',type:'sticky'}}"); ++ register(553, "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'true',type:'sticky'}}"); ++ register(554, "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'true',type:'sticky'}}"); ++ register(555, "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'true',type:'sticky'}}"); ++ register(556, "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'true',type:'sticky'}}"); ++ register(557, "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'true',type:'sticky'}}"); ++ register(560, "{Name:'minecraft:white_wool'}", "{Name:'minecraft:wool',Properties:{color:'white'}}"); ++ register(561, "{Name:'minecraft:orange_wool'}", "{Name:'minecraft:wool',Properties:{color:'orange'}}"); ++ register(562, "{Name:'minecraft:magenta_wool'}", "{Name:'minecraft:wool',Properties:{color:'magenta'}}"); ++ register(563, "{Name:'minecraft:light_blue_wool'}", "{Name:'minecraft:wool',Properties:{color:'light_blue'}}"); ++ register(564, "{Name:'minecraft:yellow_wool'}", "{Name:'minecraft:wool',Properties:{color:'yellow'}}"); ++ register(565, "{Name:'minecraft:lime_wool'}", "{Name:'minecraft:wool',Properties:{color:'lime'}}"); ++ register(566, "{Name:'minecraft:pink_wool'}", "{Name:'minecraft:wool',Properties:{color:'pink'}}"); ++ register(567, "{Name:'minecraft:gray_wool'}", "{Name:'minecraft:wool',Properties:{color:'gray'}}"); ++ register(568, "{Name:'minecraft:light_gray_wool'}", "{Name:'minecraft:wool',Properties:{color:'silver'}}"); ++ register(569, "{Name:'minecraft:cyan_wool'}", "{Name:'minecraft:wool',Properties:{color:'cyan'}}"); ++ register(570, "{Name:'minecraft:purple_wool'}", "{Name:'minecraft:wool',Properties:{color:'purple'}}"); ++ register(571, "{Name:'minecraft:blue_wool'}", "{Name:'minecraft:wool',Properties:{color:'blue'}}"); ++ register(572, "{Name:'minecraft:brown_wool'}", "{Name:'minecraft:wool',Properties:{color:'brown'}}"); ++ register(573, "{Name:'minecraft:green_wool'}", "{Name:'minecraft:wool',Properties:{color:'green'}}"); ++ register(574, "{Name:'minecraft:red_wool'}", "{Name:'minecraft:wool',Properties:{color:'red'}}"); ++ register(575, "{Name:'minecraft:black_wool'}", "{Name:'minecraft:wool',Properties:{color:'black'}}"); ++ register(576, "{Name:'minecraft:moving_piston',Properties:{facing:'down',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'down',type:'normal'}}"); ++ register(577, "{Name:'minecraft:moving_piston',Properties:{facing:'up',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'up',type:'normal'}}"); ++ register(578, "{Name:'minecraft:moving_piston',Properties:{facing:'north',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'north',type:'normal'}}"); ++ register(579, "{Name:'minecraft:moving_piston',Properties:{facing:'south',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'south',type:'normal'}}"); ++ register(580, "{Name:'minecraft:moving_piston',Properties:{facing:'west',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'west',type:'normal'}}"); ++ register(581, "{Name:'minecraft:moving_piston',Properties:{facing:'east',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'east',type:'normal'}}"); ++ register(584, "{Name:'minecraft:moving_piston',Properties:{facing:'down',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'down',type:'sticky'}}"); ++ register(585, "{Name:'minecraft:moving_piston',Properties:{facing:'up',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'up',type:'sticky'}}"); ++ register(586, "{Name:'minecraft:moving_piston',Properties:{facing:'north',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'north',type:'sticky'}}"); ++ register(587, "{Name:'minecraft:moving_piston',Properties:{facing:'south',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'south',type:'sticky'}}"); ++ register(588, "{Name:'minecraft:moving_piston',Properties:{facing:'west',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'west',type:'sticky'}}"); ++ register(589, "{Name:'minecraft:moving_piston',Properties:{facing:'east',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'east',type:'sticky'}}"); ++ register(592, "{Name:'minecraft:dandelion'}", "{Name:'minecraft:yellow_flower',Properties:{type:'dandelion'}}"); ++ register(608, "{Name:'minecraft:poppy'}", "{Name:'minecraft:red_flower',Properties:{type:'poppy'}}"); ++ register(609, "{Name:'minecraft:blue_orchid'}", "{Name:'minecraft:red_flower',Properties:{type:'blue_orchid'}}"); ++ register(610, "{Name:'minecraft:allium'}", "{Name:'minecraft:red_flower',Properties:{type:'allium'}}"); ++ register(611, "{Name:'minecraft:azure_bluet'}", "{Name:'minecraft:red_flower',Properties:{type:'houstonia'}}"); ++ register(612, "{Name:'minecraft:red_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'red_tulip'}}"); ++ register(613, "{Name:'minecraft:orange_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'orange_tulip'}}"); ++ register(614, "{Name:'minecraft:white_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'white_tulip'}}"); ++ register(615, "{Name:'minecraft:pink_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'pink_tulip'}}"); ++ register(616, "{Name:'minecraft:oxeye_daisy'}", "{Name:'minecraft:red_flower',Properties:{type:'oxeye_daisy'}}"); ++ register(624, "{Name:'minecraft:brown_mushroom'}", "{Name:'minecraft:brown_mushroom'}"); ++ register(640, "{Name:'minecraft:red_mushroom'}", "{Name:'minecraft:red_mushroom'}"); ++ register(656, "{Name:'minecraft:gold_block'}", "{Name:'minecraft:gold_block'}"); ++ register(672, "{Name:'minecraft:iron_block'}", "{Name:'minecraft:iron_block'}"); ++ register(688, "{Name:'minecraft:stone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'stone'}}"); ++ register(689, "{Name:'minecraft:sandstone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'sandstone'}}"); ++ register(690, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'wood_old'}}"); ++ register(691, "{Name:'minecraft:cobblestone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'cobblestone'}}"); ++ register(692, "{Name:'minecraft:brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'brick'}}"); ++ register(693, "{Name:'minecraft:stone_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'stone_brick'}}"); ++ register(694, "{Name:'minecraft:nether_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'nether_brick'}}"); ++ register(695, "{Name:'minecraft:quartz_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'quartz'}}"); ++ register(696, "{Name:'minecraft:smooth_stone'}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'stone'}}"); ++ register(697, "{Name:'minecraft:smooth_sandstone'}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'sandstone'}}"); ++ register(698, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'wood_old'}}"); ++ register(699, "{Name:'minecraft:cobblestone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'cobblestone'}}"); ++ register(700, "{Name:'minecraft:brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'brick'}}"); ++ register(701, "{Name:'minecraft:stone_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'stone_brick'}}"); ++ register(702, "{Name:'minecraft:nether_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'nether_brick'}}"); ++ register(703, "{Name:'minecraft:smooth_quartz'}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'quartz'}}"); ++ register(704, "{Name:'minecraft:stone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'stone'}}"); ++ register(705, "{Name:'minecraft:sandstone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'sandstone'}}"); ++ register(706, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'wood_old'}}"); ++ register(707, "{Name:'minecraft:cobblestone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'cobblestone'}}"); ++ register(708, "{Name:'minecraft:brick_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'brick'}}"); ++ register(709, "{Name:'minecraft:stone_brick_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'stone_brick'}}"); ++ register(710, "{Name:'minecraft:nether_brick_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'nether_brick'}}"); ++ register(711, "{Name:'minecraft:quartz_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'quartz'}}"); ++ register(712, "{Name:'minecraft:stone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'stone'}}"); ++ register(713, "{Name:'minecraft:sandstone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'sandstone'}}"); ++ register(714, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'wood_old'}}"); ++ register(715, "{Name:'minecraft:cobblestone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'cobblestone'}}"); ++ register(716, "{Name:'minecraft:brick_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'brick'}}"); ++ register(717, "{Name:'minecraft:stone_brick_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'stone_brick'}}"); ++ register(718, "{Name:'minecraft:nether_brick_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'nether_brick'}}"); ++ register(719, "{Name:'minecraft:quartz_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'quartz'}}"); ++ register(720, "{Name:'minecraft:bricks'}", "{Name:'minecraft:brick_block'}"); ++ register(736, "{Name:'minecraft:tnt',Properties:{unstable:'false'}}", "{Name:'minecraft:tnt',Properties:{explode:'false'}}"); ++ register(737, "{Name:'minecraft:tnt',Properties:{unstable:'true'}}", "{Name:'minecraft:tnt',Properties:{explode:'true'}}"); ++ register(752, "{Name:'minecraft:bookshelf'}", "{Name:'minecraft:bookshelf'}"); ++ register(768, "{Name:'minecraft:mossy_cobblestone'}", "{Name:'minecraft:mossy_cobblestone'}"); ++ register(784, "{Name:'minecraft:obsidian'}", "{Name:'minecraft:obsidian'}"); ++ register(801, "{Name:'minecraft:wall_torch',Properties:{facing:'east'}}", "{Name:'minecraft:torch',Properties:{facing:'east'}}"); ++ register(802, "{Name:'minecraft:wall_torch',Properties:{facing:'west'}}", "{Name:'minecraft:torch',Properties:{facing:'west'}}"); ++ register(803, "{Name:'minecraft:wall_torch',Properties:{facing:'south'}}", "{Name:'minecraft:torch',Properties:{facing:'south'}}"); ++ register(804, "{Name:'minecraft:wall_torch',Properties:{facing:'north'}}", "{Name:'minecraft:torch',Properties:{facing:'north'}}"); ++ register(805, "{Name:'minecraft:torch'}", "{Name:'minecraft:torch',Properties:{facing:'up'}}"); ++ register(816, "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(817, "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(818, "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(819, "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(820, "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(821, "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(822, "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(823, "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(824, "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(825, "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(826, "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(827, "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(828, "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(829, "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(830, "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(831, "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(832, "{Name:'minecraft:mob_spawner'}", "{Name:'minecraft:mob_spawner'}"); ++ register(848, "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(849, "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(850, "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(851, "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(852, "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(853, "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(854, "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(855, "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(866, "{Name:'minecraft:chest',Properties:{facing:'north',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'north'}}"); ++ register(867, "{Name:'minecraft:chest',Properties:{facing:'south',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'south'}}"); ++ register(868, "{Name:'minecraft:chest',Properties:{facing:'west',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'west'}}"); ++ register(869, "{Name:'minecraft:chest',Properties:{facing:'east',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'east'}}"); ++ register(880, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'up',west:'up'}}"); ++ register(881, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'up',west:'up'}}"); ++ register(882, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'up',west:'up'}}"); ++ register(883, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'up',west:'up'}}"); ++ register(884, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'up',west:'up'}}"); ++ register(885, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'up',west:'up'}}"); ++ register(886, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'up',west:'up'}}"); ++ register(887, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'up',west:'up'}}"); ++ register(888, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'up',west:'up'}}"); ++ register(889, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'up',west:'up'}}"); ++ register(890, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'up',west:'up'}}"); ++ register(891, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'up',west:'up'}}"); ++ register(892, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'up',west:'up'}}"); ++ register(893, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'up',west:'up'}}"); ++ register(894, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'up',west:'up'}}"); ++ register(895, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'up',west:'up'}}"); ++ register(896, "{Name:'minecraft:diamond_ore'}", "{Name:'minecraft:diamond_ore'}"); ++ register(912, "{Name:'minecraft:diamond_block'}", "{Name:'minecraft:diamond_block'}"); ++ register(928, "{Name:'minecraft:crafting_table'}", "{Name:'minecraft:crafting_table'}"); ++ register(944, "{Name:'minecraft:wheat',Properties:{age:'0'}}", "{Name:'minecraft:wheat',Properties:{age:'0'}}"); ++ register(945, "{Name:'minecraft:wheat',Properties:{age:'1'}}", "{Name:'minecraft:wheat',Properties:{age:'1'}}"); ++ register(946, "{Name:'minecraft:wheat',Properties:{age:'2'}}", "{Name:'minecraft:wheat',Properties:{age:'2'}}"); ++ register(947, "{Name:'minecraft:wheat',Properties:{age:'3'}}", "{Name:'minecraft:wheat',Properties:{age:'3'}}"); ++ register(948, "{Name:'minecraft:wheat',Properties:{age:'4'}}", "{Name:'minecraft:wheat',Properties:{age:'4'}}"); ++ register(949, "{Name:'minecraft:wheat',Properties:{age:'5'}}", "{Name:'minecraft:wheat',Properties:{age:'5'}}"); ++ register(950, "{Name:'minecraft:wheat',Properties:{age:'6'}}", "{Name:'minecraft:wheat',Properties:{age:'6'}}"); ++ register(951, "{Name:'minecraft:wheat',Properties:{age:'7'}}", "{Name:'minecraft:wheat',Properties:{age:'7'}}"); ++ register(960, "{Name:'minecraft:farmland',Properties:{moisture:'0'}}", "{Name:'minecraft:farmland',Properties:{moisture:'0'}}"); ++ register(961, "{Name:'minecraft:farmland',Properties:{moisture:'1'}}", "{Name:'minecraft:farmland',Properties:{moisture:'1'}}"); ++ register(962, "{Name:'minecraft:farmland',Properties:{moisture:'2'}}", "{Name:'minecraft:farmland',Properties:{moisture:'2'}}"); ++ register(963, "{Name:'minecraft:farmland',Properties:{moisture:'3'}}", "{Name:'minecraft:farmland',Properties:{moisture:'3'}}"); ++ register(964, "{Name:'minecraft:farmland',Properties:{moisture:'4'}}", "{Name:'minecraft:farmland',Properties:{moisture:'4'}}"); ++ register(965, "{Name:'minecraft:farmland',Properties:{moisture:'5'}}", "{Name:'minecraft:farmland',Properties:{moisture:'5'}}"); ++ register(966, "{Name:'minecraft:farmland',Properties:{moisture:'6'}}", "{Name:'minecraft:farmland',Properties:{moisture:'6'}}"); ++ register(967, "{Name:'minecraft:farmland',Properties:{moisture:'7'}}", "{Name:'minecraft:farmland',Properties:{moisture:'7'}}"); ++ register(978, "{Name:'minecraft:furnace',Properties:{facing:'north',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'north'}}"); ++ register(979, "{Name:'minecraft:furnace',Properties:{facing:'south',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'south'}}"); ++ register(980, "{Name:'minecraft:furnace',Properties:{facing:'west',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'west'}}"); ++ register(981, "{Name:'minecraft:furnace',Properties:{facing:'east',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'east'}}"); ++ register(994, "{Name:'minecraft:furnace',Properties:{facing:'north',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'north'}}"); ++ register(995, "{Name:'minecraft:furnace',Properties:{facing:'south',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'south'}}"); ++ register(996, "{Name:'minecraft:furnace',Properties:{facing:'west',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'west'}}"); ++ register(997, "{Name:'minecraft:furnace',Properties:{facing:'east',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'east'}}"); ++ register(1008, "{Name:'minecraft:sign',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'0'}}"); ++ register(1009, "{Name:'minecraft:sign',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'1'}}"); ++ register(1010, "{Name:'minecraft:sign',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'2'}}"); ++ register(1011, "{Name:'minecraft:sign',Properties:{rotation:'3'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'3'}}"); ++ register(1012, "{Name:'minecraft:sign',Properties:{rotation:'4'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'4'}}"); ++ register(1013, "{Name:'minecraft:sign',Properties:{rotation:'5'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'5'}}"); ++ register(1014, "{Name:'minecraft:sign',Properties:{rotation:'6'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'6'}}"); ++ register(1015, "{Name:'minecraft:sign',Properties:{rotation:'7'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'7'}}"); ++ register(1016, "{Name:'minecraft:sign',Properties:{rotation:'8'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'8'}}"); ++ register(1017, "{Name:'minecraft:sign',Properties:{rotation:'9'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'9'}}"); ++ register(1018, "{Name:'minecraft:sign',Properties:{rotation:'10'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'10'}}"); ++ register(1019, "{Name:'minecraft:sign',Properties:{rotation:'11'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'11'}}"); ++ register(1020, "{Name:'minecraft:sign',Properties:{rotation:'12'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'12'}}"); ++ register(1021, "{Name:'minecraft:sign',Properties:{rotation:'13'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'13'}}"); ++ register(1022, "{Name:'minecraft:sign',Properties:{rotation:'14'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'14'}}"); ++ register(1023, "{Name:'minecraft:sign',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'15'}}"); ++ register(1024, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1025, "{Name:'minecraft:oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1026, "{Name:'minecraft:oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1027, "{Name:'minecraft:oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1028, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1029, "{Name:'minecraft:oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1030, "{Name:'minecraft:oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1031, "{Name:'minecraft:oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1032, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1033, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(1034, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(1035, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(1036, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1037, "{Name:'minecraft:oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1038, "{Name:'minecraft:oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1039, "{Name:'minecraft:oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1042, "{Name:'minecraft:ladder',Properties:{facing:'north'}}", "{Name:'minecraft:ladder',Properties:{facing:'north'}}"); ++ register(1043, "{Name:'minecraft:ladder',Properties:{facing:'south'}}", "{Name:'minecraft:ladder',Properties:{facing:'south'}}"); ++ register(1044, "{Name:'minecraft:ladder',Properties:{facing:'west'}}", "{Name:'minecraft:ladder',Properties:{facing:'west'}}"); ++ register(1045, "{Name:'minecraft:ladder',Properties:{facing:'east'}}", "{Name:'minecraft:ladder',Properties:{facing:'east'}}"); ++ register(1056, "{Name:'minecraft:rail',Properties:{shape:'north_south'}}", "{Name:'minecraft:rail',Properties:{shape:'north_south'}}"); ++ register(1057, "{Name:'minecraft:rail',Properties:{shape:'east_west'}}", "{Name:'minecraft:rail',Properties:{shape:'east_west'}}"); ++ register(1058, "{Name:'minecraft:rail',Properties:{shape:'ascending_east'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_east'}}"); ++ register(1059, "{Name:'minecraft:rail',Properties:{shape:'ascending_west'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_west'}}"); ++ register(1060, "{Name:'minecraft:rail',Properties:{shape:'ascending_north'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_north'}}"); ++ register(1061, "{Name:'minecraft:rail',Properties:{shape:'ascending_south'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_south'}}"); ++ register(1062, "{Name:'minecraft:rail',Properties:{shape:'south_east'}}", "{Name:'minecraft:rail',Properties:{shape:'south_east'}}"); ++ register(1063, "{Name:'minecraft:rail',Properties:{shape:'south_west'}}", "{Name:'minecraft:rail',Properties:{shape:'south_west'}}"); ++ register(1064, "{Name:'minecraft:rail',Properties:{shape:'north_west'}}", "{Name:'minecraft:rail',Properties:{shape:'north_west'}}"); ++ register(1065, "{Name:'minecraft:rail',Properties:{shape:'north_east'}}", "{Name:'minecraft:rail',Properties:{shape:'north_east'}}"); ++ register(1072, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(1073, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(1074, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(1075, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(1076, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(1077, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(1078, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(1079, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(1090, "{Name:'minecraft:wall_sign',Properties:{facing:'north'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'north'}}"); ++ register(1091, "{Name:'minecraft:wall_sign',Properties:{facing:'south'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'south'}}"); ++ register(1092, "{Name:'minecraft:wall_sign',Properties:{facing:'west'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'west'}}"); ++ register(1093, "{Name:'minecraft:wall_sign',Properties:{facing:'east'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'east'}}"); ++ register(1104, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'west',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'down_x',powered:'false'}}"); ++ register(1105, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'east',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'east',powered:'false'}}"); ++ register(1106, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'west',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'west',powered:'false'}}"); ++ register(1107, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'south',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'south',powered:'false'}}"); ++ register(1108, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'north',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'north',powered:'false'}}"); ++ register(1109, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'north',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'up_z',powered:'false'}}"); ++ register(1110, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'west',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'up_x',powered:'false'}}"); ++ register(1111, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'north',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'down_z',powered:'false'}}"); ++ register(1112, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'west',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'down_x',powered:'true'}}"); ++ register(1113, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'east',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'east',powered:'true'}}"); ++ register(1114, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'west',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'west',powered:'true'}}"); ++ register(1115, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'south',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'south',powered:'true'}}"); ++ register(1116, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'north',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'north',powered:'true'}}"); ++ register(1117, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'north',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'up_z',powered:'true'}}"); ++ register(1118, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'west',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'up_x',powered:'true'}}"); ++ register(1119, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'north',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'down_z',powered:'true'}}"); ++ register(1120, "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'false'}}", "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'false'}}"); ++ register(1121, "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'true'}}", "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'true'}}"); ++ register(1136, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1137, "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1138, "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1139, "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1140, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1141, "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1142, "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1143, "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1144, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1145, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(1146, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(1147, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(1148, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1149, "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1150, "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1151, "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1152, "{Name:'minecraft:oak_pressure_plate',Properties:{powered:'false'}}", "{Name:'minecraft:wooden_pressure_plate',Properties:{powered:'false'}}"); ++ register(1153, "{Name:'minecraft:oak_pressure_plate',Properties:{powered:'true'}}", "{Name:'minecraft:wooden_pressure_plate',Properties:{powered:'true'}}"); ++ register(1168, "{Name:'minecraft:redstone_ore',Properties:{lit:'false'}}", "{Name:'minecraft:redstone_ore'}"); ++ register(1184, "{Name:'minecraft:redstone_ore',Properties:{lit:'true'}}", "{Name:'minecraft:lit_redstone_ore'}"); ++ register(1201, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'east',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'east'}}"); ++ register(1202, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'west',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'west'}}"); ++ register(1203, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'south',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'south'}}"); ++ register(1204, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'north',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'north'}}"); ++ register(1205, "{Name:'minecraft:redstone_torch',Properties:{lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'up'}}"); ++ register(1217, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'east',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'east'}}"); ++ register(1218, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'west',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'west'}}"); ++ register(1219, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'south',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'south'}}"); ++ register(1220, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'north',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'north'}}"); ++ register(1221, "{Name:'minecraft:redstone_torch',Properties:{lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'up'}}"); ++ register(1232, "{Name:'minecraft:stone_button',Properties:{face:'ceiling',facing:'north',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'down',powered:'false'}}"); ++ register(1233, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'east',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'east',powered:'false'}}"); ++ register(1234, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'west',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'west',powered:'false'}}"); ++ register(1235, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'south',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'south',powered:'false'}}"); ++ register(1236, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'north',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'north',powered:'false'}}"); ++ register(1237, "{Name:'minecraft:stone_button',Properties:{face:'floor',facing:'north',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'up',powered:'false'}}"); ++ register(1240, "{Name:'minecraft:stone_button',Properties:{face:'ceiling',facing:'north',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'down',powered:'true'}}"); ++ register(1241, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'east',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'east',powered:'true'}}"); ++ register(1242, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'west',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'west',powered:'true'}}"); ++ register(1243, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'south',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'south',powered:'true'}}"); ++ register(1244, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'north',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'north',powered:'true'}}"); ++ register(1245, "{Name:'minecraft:stone_button',Properties:{face:'floor',facing:'north',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'up',powered:'true'}}"); ++ register(1248, "{Name:'minecraft:snow',Properties:{layers:'1'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'1'}}"); ++ register(1249, "{Name:'minecraft:snow',Properties:{layers:'2'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'2'}}"); ++ register(1250, "{Name:'minecraft:snow',Properties:{layers:'3'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'3'}}"); ++ register(1251, "{Name:'minecraft:snow',Properties:{layers:'4'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'4'}}"); ++ register(1252, "{Name:'minecraft:snow',Properties:{layers:'5'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'5'}}"); ++ register(1253, "{Name:'minecraft:snow',Properties:{layers:'6'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'6'}}"); ++ register(1254, "{Name:'minecraft:snow',Properties:{layers:'7'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'7'}}"); ++ register(1255, "{Name:'minecraft:snow',Properties:{layers:'8'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'8'}}"); ++ register(1264, "{Name:'minecraft:ice'}", "{Name:'minecraft:ice'}"); ++ register(1280, "{Name:'minecraft:snow_block'}", "{Name:'minecraft:snow'}"); ++ register(1296, "{Name:'minecraft:cactus',Properties:{age:'0'}}", "{Name:'minecraft:cactus',Properties:{age:'0'}}"); ++ register(1297, "{Name:'minecraft:cactus',Properties:{age:'1'}}", "{Name:'minecraft:cactus',Properties:{age:'1'}}"); ++ register(1298, "{Name:'minecraft:cactus',Properties:{age:'2'}}", "{Name:'minecraft:cactus',Properties:{age:'2'}}"); ++ register(1299, "{Name:'minecraft:cactus',Properties:{age:'3'}}", "{Name:'minecraft:cactus',Properties:{age:'3'}}"); ++ register(1300, "{Name:'minecraft:cactus',Properties:{age:'4'}}", "{Name:'minecraft:cactus',Properties:{age:'4'}}"); ++ register(1301, "{Name:'minecraft:cactus',Properties:{age:'5'}}", "{Name:'minecraft:cactus',Properties:{age:'5'}}"); ++ register(1302, "{Name:'minecraft:cactus',Properties:{age:'6'}}", "{Name:'minecraft:cactus',Properties:{age:'6'}}"); ++ register(1303, "{Name:'minecraft:cactus',Properties:{age:'7'}}", "{Name:'minecraft:cactus',Properties:{age:'7'}}"); ++ register(1304, "{Name:'minecraft:cactus',Properties:{age:'8'}}", "{Name:'minecraft:cactus',Properties:{age:'8'}}"); ++ register(1305, "{Name:'minecraft:cactus',Properties:{age:'9'}}", "{Name:'minecraft:cactus',Properties:{age:'9'}}"); ++ register(1306, "{Name:'minecraft:cactus',Properties:{age:'10'}}", "{Name:'minecraft:cactus',Properties:{age:'10'}}"); ++ register(1307, "{Name:'minecraft:cactus',Properties:{age:'11'}}", "{Name:'minecraft:cactus',Properties:{age:'11'}}"); ++ register(1308, "{Name:'minecraft:cactus',Properties:{age:'12'}}", "{Name:'minecraft:cactus',Properties:{age:'12'}}"); ++ register(1309, "{Name:'minecraft:cactus',Properties:{age:'13'}}", "{Name:'minecraft:cactus',Properties:{age:'13'}}"); ++ register(1310, "{Name:'minecraft:cactus',Properties:{age:'14'}}", "{Name:'minecraft:cactus',Properties:{age:'14'}}"); ++ register(1311, "{Name:'minecraft:cactus',Properties:{age:'15'}}", "{Name:'minecraft:cactus',Properties:{age:'15'}}"); ++ register(1312, "{Name:'minecraft:clay'}", "{Name:'minecraft:clay'}"); ++ register(1328, "{Name:'minecraft:sugar_cane',Properties:{age:'0'}}", "{Name:'minecraft:reeds',Properties:{age:'0'}}"); ++ register(1329, "{Name:'minecraft:sugar_cane',Properties:{age:'1'}}", "{Name:'minecraft:reeds',Properties:{age:'1'}}"); ++ register(1330, "{Name:'minecraft:sugar_cane',Properties:{age:'2'}}", "{Name:'minecraft:reeds',Properties:{age:'2'}}"); ++ register(1331, "{Name:'minecraft:sugar_cane',Properties:{age:'3'}}", "{Name:'minecraft:reeds',Properties:{age:'3'}}"); ++ register(1332, "{Name:'minecraft:sugar_cane',Properties:{age:'4'}}", "{Name:'minecraft:reeds',Properties:{age:'4'}}"); ++ register(1333, "{Name:'minecraft:sugar_cane',Properties:{age:'5'}}", "{Name:'minecraft:reeds',Properties:{age:'5'}}"); ++ register(1334, "{Name:'minecraft:sugar_cane',Properties:{age:'6'}}", "{Name:'minecraft:reeds',Properties:{age:'6'}}"); ++ register(1335, "{Name:'minecraft:sugar_cane',Properties:{age:'7'}}", "{Name:'minecraft:reeds',Properties:{age:'7'}}"); ++ register(1336, "{Name:'minecraft:sugar_cane',Properties:{age:'8'}}", "{Name:'minecraft:reeds',Properties:{age:'8'}}"); ++ register(1337, "{Name:'minecraft:sugar_cane',Properties:{age:'9'}}", "{Name:'minecraft:reeds',Properties:{age:'9'}}"); ++ register(1338, "{Name:'minecraft:sugar_cane',Properties:{age:'10'}}", "{Name:'minecraft:reeds',Properties:{age:'10'}}"); ++ register(1339, "{Name:'minecraft:sugar_cane',Properties:{age:'11'}}", "{Name:'minecraft:reeds',Properties:{age:'11'}}"); ++ register(1340, "{Name:'minecraft:sugar_cane',Properties:{age:'12'}}", "{Name:'minecraft:reeds',Properties:{age:'12'}}"); ++ register(1341, "{Name:'minecraft:sugar_cane',Properties:{age:'13'}}", "{Name:'minecraft:reeds',Properties:{age:'13'}}"); ++ register(1342, "{Name:'minecraft:sugar_cane',Properties:{age:'14'}}", "{Name:'minecraft:reeds',Properties:{age:'14'}}"); ++ register(1343, "{Name:'minecraft:sugar_cane',Properties:{age:'15'}}", "{Name:'minecraft:reeds',Properties:{age:'15'}}"); ++ register(1344, "{Name:'minecraft:jukebox',Properties:{has_record:'false'}}", "{Name:'minecraft:jukebox',Properties:{has_record:'false'}}"); ++ register(1345, "{Name:'minecraft:jukebox',Properties:{has_record:'true'}}", "{Name:'minecraft:jukebox',Properties:{has_record:'true'}}"); ++ register(1360, "{Name:'minecraft:oak_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(1376, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'south'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'south'}}"); ++ register(1377, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'west'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'west'}}"); ++ register(1378, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'north'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'north'}}"); ++ register(1379, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'east'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'east'}}"); ++ register(1392, "{Name:'minecraft:netherrack'}", "{Name:'minecraft:netherrack'}"); ++ register(1408, "{Name:'minecraft:soul_sand'}", "{Name:'minecraft:soul_sand'}"); ++ register(1424, "{Name:'minecraft:glowstone'}", "{Name:'minecraft:glowstone'}"); ++ register(1441, "{Name:'minecraft:portal',Properties:{axis:'x'}}", "{Name:'minecraft:portal',Properties:{axis:'x'}}"); ++ register(1442, "{Name:'minecraft:portal',Properties:{axis:'z'}}", "{Name:'minecraft:portal',Properties:{axis:'z'}}"); ++ register(1456, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'south'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'south'}}"); ++ register(1457, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'west'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'west'}}"); ++ register(1458, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'north'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'north'}}"); ++ register(1459, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'east'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'east'}}"); ++ register(1472, "{Name:'minecraft:cake',Properties:{bites:'0'}}", "{Name:'minecraft:cake',Properties:{bites:'0'}}"); ++ register(1473, "{Name:'minecraft:cake',Properties:{bites:'1'}}", "{Name:'minecraft:cake',Properties:{bites:'1'}}"); ++ register(1474, "{Name:'minecraft:cake',Properties:{bites:'2'}}", "{Name:'minecraft:cake',Properties:{bites:'2'}}"); ++ register(1475, "{Name:'minecraft:cake',Properties:{bites:'3'}}", "{Name:'minecraft:cake',Properties:{bites:'3'}}"); ++ register(1476, "{Name:'minecraft:cake',Properties:{bites:'4'}}", "{Name:'minecraft:cake',Properties:{bites:'4'}}"); ++ register(1477, "{Name:'minecraft:cake',Properties:{bites:'5'}}", "{Name:'minecraft:cake',Properties:{bites:'5'}}"); ++ register(1478, "{Name:'minecraft:cake',Properties:{bites:'6'}}", "{Name:'minecraft:cake',Properties:{bites:'6'}}"); ++ register(1488, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'south',locked:'true'}}"); ++ register(1489, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'west',locked:'true'}}"); ++ register(1490, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'north',locked:'true'}}"); ++ register(1491, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'east',locked:'true'}}"); ++ register(1492, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'south',locked:'true'}}"); ++ register(1493, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'west',locked:'true'}}"); ++ register(1494, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'north',locked:'true'}}"); ++ register(1495, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'east',locked:'true'}}"); ++ register(1496, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'south',locked:'true'}}"); ++ register(1497, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'west',locked:'true'}}"); ++ register(1498, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'north',locked:'true'}}"); ++ register(1499, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'east',locked:'true'}}"); ++ register(1500, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'south',locked:'true'}}"); ++ register(1501, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'west',locked:'true'}}"); ++ register(1502, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'north',locked:'true'}}"); ++ register(1503, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'east',locked:'true'}}"); ++ register(1504, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'south',locked:'true'}}"); ++ register(1505, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'west',locked:'true'}}"); ++ register(1506, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'north',locked:'true'}}"); ++ register(1507, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'east',locked:'true'}}"); ++ register(1508, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'south',locked:'true'}}"); ++ register(1509, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'west',locked:'true'}}"); ++ register(1510, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'north',locked:'true'}}"); ++ register(1511, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'east',locked:'true'}}"); ++ register(1512, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'south',locked:'true'}}"); ++ register(1513, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'west',locked:'true'}}"); ++ register(1514, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'north',locked:'true'}}"); ++ register(1515, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'east',locked:'true'}}"); ++ register(1516, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'south',locked:'true'}}"); ++ register(1517, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'west',locked:'true'}}"); ++ register(1518, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'north',locked:'true'}}"); ++ register(1519, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'east',locked:'true'}}"); ++ register(1520, "{Name:'minecraft:white_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'white'}}"); ++ register(1521, "{Name:'minecraft:orange_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'orange'}}"); ++ register(1522, "{Name:'minecraft:magenta_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'magenta'}}"); ++ register(1523, "{Name:'minecraft:light_blue_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'light_blue'}}"); ++ register(1524, "{Name:'minecraft:yellow_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'yellow'}}"); ++ register(1525, "{Name:'minecraft:lime_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'lime'}}"); ++ register(1526, "{Name:'minecraft:pink_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'pink'}}"); ++ register(1527, "{Name:'minecraft:gray_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'gray'}}"); ++ register(1528, "{Name:'minecraft:light_gray_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'silver'}}"); ++ register(1529, "{Name:'minecraft:cyan_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'cyan'}}"); ++ register(1530, "{Name:'minecraft:purple_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'purple'}}"); ++ register(1531, "{Name:'minecraft:blue_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'blue'}}"); ++ register(1532, "{Name:'minecraft:brown_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'brown'}}"); ++ register(1533, "{Name:'minecraft:green_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'green'}}"); ++ register(1534, "{Name:'minecraft:red_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'red'}}"); ++ register(1535, "{Name:'minecraft:black_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'black'}}"); ++ register(1536, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}"); ++ register(1537, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}"); ++ register(1538, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}"); ++ register(1539, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}"); ++ register(1540, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}"); ++ register(1541, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}"); ++ register(1542, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}"); ++ register(1543, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}"); ++ register(1544, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'top',open:'false'}}"); ++ register(1545, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'top',open:'false'}}"); ++ register(1546, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'top',open:'false'}}"); ++ register(1547, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'top',open:'false'}}"); ++ register(1548, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'top',open:'true'}}"); ++ register(1549, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'top',open:'true'}}"); ++ register(1550, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'top',open:'true'}}"); ++ register(1551, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'top',open:'true'}}"); ++ register(1552, "{Name:'minecraft:infested_stone'}", "{Name:'minecraft:monster_egg',Properties:{variant:'stone'}}"); ++ register(1553, "{Name:'minecraft:infested_cobblestone'}", "{Name:'minecraft:monster_egg',Properties:{variant:'cobblestone'}}"); ++ register(1554, "{Name:'minecraft:infested_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'stone_brick'}}"); ++ register(1555, "{Name:'minecraft:infested_mossy_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'mossy_brick'}}"); ++ register(1556, "{Name:'minecraft:infested_cracked_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'cracked_brick'}}"); ++ register(1557, "{Name:'minecraft:infested_chiseled_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'chiseled_brick'}}"); ++ register(1568, "{Name:'minecraft:stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'stonebrick'}}"); ++ register(1569, "{Name:'minecraft:mossy_stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'mossy_stonebrick'}}"); ++ register(1570, "{Name:'minecraft:cracked_stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'cracked_stonebrick'}}"); ++ register(1571, "{Name:'minecraft:chiseled_stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'chiseled_stonebrick'}}"); ++ register(1584, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'all_inside'}}"); ++ register(1585, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'north_west'}}"); ++ register(1586, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'north'}}"); ++ register(1587, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'north_east'}}"); ++ register(1588, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'west'}}"); ++ register(1589, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'center'}}"); ++ register(1590, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'east'}}"); ++ register(1591, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'south_west'}}"); ++ register(1592, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'south'}}"); ++ register(1593, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'true',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'south_east'}}"); ++ register(1594, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'false',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'stem'}}"); ++ register(1595, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); ++ register(1596, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); ++ register(1597, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); ++ register(1598, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'all_outside'}}"); ++ register(1599, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'all_stem'}}"); ++ register(1600, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'all_inside'}}"); ++ register(1601, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'north_west'}}"); ++ register(1602, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'north'}}"); ++ register(1603, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'north_east'}}"); ++ register(1604, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'west'}}"); ++ register(1605, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'center'}}"); ++ register(1606, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'east'}}"); ++ register(1607, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'south_west'}}"); ++ register(1608, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'south'}}"); ++ register(1609, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'true',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'south_east'}}"); ++ register(1610, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'false',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'stem'}}"); ++ register(1611, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); ++ register(1612, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); ++ register(1613, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); ++ register(1614, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'all_outside'}}"); ++ register(1615, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'all_stem'}}"); ++ register(1616, "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(1632, "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(1648, "{Name:'minecraft:melon_block'}", "{Name:'minecraft:melon_block'}"); ++ register(1664, "{Name:'minecraft:pumpkin_stem',Properties:{age:'0'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'west'}}"); ++ register(1665, "{Name:'minecraft:pumpkin_stem',Properties:{age:'1'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'west'}}"); ++ register(1666, "{Name:'minecraft:pumpkin_stem',Properties:{age:'2'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'west'}}"); ++ register(1667, "{Name:'minecraft:pumpkin_stem',Properties:{age:'3'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'west'}}"); ++ register(1668, "{Name:'minecraft:pumpkin_stem',Properties:{age:'4'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'west'}}"); ++ register(1669, "{Name:'minecraft:pumpkin_stem',Properties:{age:'5'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'west'}}"); ++ register(1670, "{Name:'minecraft:pumpkin_stem',Properties:{age:'6'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'west'}}"); ++ register(1671, "{Name:'minecraft:pumpkin_stem',Properties:{age:'7'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'west'}}"); ++ register(1680, "{Name:'minecraft:melon_stem',Properties:{age:'0'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'west'}}"); ++ register(1681, "{Name:'minecraft:melon_stem',Properties:{age:'1'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'west'}}"); ++ register(1682, "{Name:'minecraft:melon_stem',Properties:{age:'2'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'west'}}"); ++ register(1683, "{Name:'minecraft:melon_stem',Properties:{age:'3'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'west'}}"); ++ register(1684, "{Name:'minecraft:melon_stem',Properties:{age:'4'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'west'}}"); ++ register(1685, "{Name:'minecraft:melon_stem',Properties:{age:'5'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'west'}}"); ++ register(1686, "{Name:'minecraft:melon_stem',Properties:{age:'6'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'west'}}"); ++ register(1687, "{Name:'minecraft:melon_stem',Properties:{age:'7'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'west'}}"); ++ register(1696, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'false'}}"); ++ register(1697, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'false'}}"); ++ register(1698, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'true'}}"); ++ register(1699, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'true'}}"); ++ register(1700, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'false'}}"); ++ register(1701, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'false'}}"); ++ register(1702, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'true'}}"); ++ register(1703, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(1704, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'false'}}"); ++ register(1705, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'false'}}"); ++ register(1706, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'true'}}"); ++ register(1707, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'true'}}"); ++ register(1708, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'false'}}"); ++ register(1709, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'false'}}"); ++ register(1710, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'true'}}"); ++ register(1711, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(1712, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); ++ register(1713, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); ++ register(1714, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); ++ register(1715, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); ++ register(1716, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); ++ register(1717, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); ++ register(1718, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); ++ register(1719, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); ++ register(1720, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); ++ register(1721, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); ++ register(1722, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); ++ register(1723, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); ++ register(1724, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); ++ register(1725, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); ++ register(1726, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); ++ register(1727, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); ++ register(1728, "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(1729, "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(1730, "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(1731, "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(1732, "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(1733, "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(1734, "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(1735, "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(1744, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(1745, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(1746, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(1747, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(1748, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(1749, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(1750, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(1751, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(1760, "{Name:'minecraft:mycelium',Properties:{snowy:'false'}}", "{Name:'minecraft:mycelium',Properties:{snowy:'false'}}", "{Name:'minecraft:mycelium',Properties:{snowy:'true'}}"); ++ register(1776, "{Name:'minecraft:lily_pad'}", "{Name:'minecraft:waterlily'}"); ++ register(1792, "{Name:'minecraft:nether_bricks'}", "{Name:'minecraft:nether_brick'}"); ++ register(1808, "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(1824, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(1825, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(1826, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(1827, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(1828, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(1829, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(1830, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(1831, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(1840, "{Name:'minecraft:nether_wart',Properties:{age:'0'}}", "{Name:'minecraft:nether_wart',Properties:{age:'0'}}"); ++ register(1841, "{Name:'minecraft:nether_wart',Properties:{age:'1'}}", "{Name:'minecraft:nether_wart',Properties:{age:'1'}}"); ++ register(1842, "{Name:'minecraft:nether_wart',Properties:{age:'2'}}", "{Name:'minecraft:nether_wart',Properties:{age:'2'}}"); ++ register(1843, "{Name:'minecraft:nether_wart',Properties:{age:'3'}}", "{Name:'minecraft:nether_wart',Properties:{age:'3'}}"); ++ register(1856, "{Name:'minecraft:enchanting_table'}", "{Name:'minecraft:enchanting_table'}"); ++ register(1872, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'false'}}"); ++ register(1873, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'false'}}"); ++ register(1874, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'false'}}"); ++ register(1875, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'false'}}"); ++ register(1876, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'true'}}"); ++ register(1877, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'true'}}"); ++ register(1878, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'true'}}"); ++ register(1879, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'true'}}"); ++ register(1888, "{Name:'minecraft:cauldron',Properties:{level:'0'}}", "{Name:'minecraft:cauldron',Properties:{level:'0'}}"); ++ register(1889, "{Name:'minecraft:cauldron',Properties:{level:'1'}}", "{Name:'minecraft:cauldron',Properties:{level:'1'}}"); ++ register(1890, "{Name:'minecraft:cauldron',Properties:{level:'2'}}", "{Name:'minecraft:cauldron',Properties:{level:'2'}}"); ++ register(1891, "{Name:'minecraft:cauldron',Properties:{level:'3'}}", "{Name:'minecraft:cauldron',Properties:{level:'3'}}"); ++ register(1904, "{Name:'minecraft:end_portal'}", "{Name:'minecraft:end_portal'}"); ++ register(1920, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'south'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'south'}}"); ++ register(1921, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'west'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'west'}}"); ++ register(1922, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'north'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'north'}}"); ++ register(1923, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'east'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'east'}}"); ++ register(1924, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'south'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'south'}}"); ++ register(1925, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'west'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'west'}}"); ++ register(1926, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'north'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'north'}}"); ++ register(1927, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'east'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'east'}}"); ++ register(1936, "{Name:'minecraft:end_stone'}", "{Name:'minecraft:end_stone'}"); ++ register(1952, "{Name:'minecraft:dragon_egg'}", "{Name:'minecraft:dragon_egg'}"); ++ register(1968, "{Name:'minecraft:redstone_lamp',Properties:{lit:'false'}}", "{Name:'minecraft:redstone_lamp'}"); ++ register(1984, "{Name:'minecraft:redstone_lamp',Properties:{lit:'true'}}", "{Name:'minecraft:lit_redstone_lamp'}"); ++ register(2000, "{Name:'minecraft:oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'oak'}}"); ++ register(2001, "{Name:'minecraft:spruce_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'spruce'}}"); ++ register(2002, "{Name:'minecraft:birch_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'birch'}}"); ++ register(2003, "{Name:'minecraft:jungle_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'jungle'}}"); ++ register(2004, "{Name:'minecraft:acacia_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'acacia'}}"); ++ register(2005, "{Name:'minecraft:dark_oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'dark_oak'}}"); ++ register(2016, "{Name:'minecraft:oak_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'oak'}}"); ++ register(2017, "{Name:'minecraft:spruce_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'spruce'}}"); ++ register(2018, "{Name:'minecraft:birch_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'birch'}}"); ++ register(2019, "{Name:'minecraft:jungle_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'jungle'}}"); ++ register(2020, "{Name:'minecraft:acacia_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'acacia'}}"); ++ register(2021, "{Name:'minecraft:dark_oak_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'dark_oak'}}"); ++ register(2024, "{Name:'minecraft:oak_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'oak'}}"); ++ register(2025, "{Name:'minecraft:spruce_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'spruce'}}"); ++ register(2026, "{Name:'minecraft:birch_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'birch'}}"); ++ register(2027, "{Name:'minecraft:jungle_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'jungle'}}"); ++ register(2028, "{Name:'minecraft:acacia_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'acacia'}}"); ++ register(2029, "{Name:'minecraft:dark_oak_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'dark_oak'}}"); ++ register(2032, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'south'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'south'}}"); ++ register(2033, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'west'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'west'}}"); ++ register(2034, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'north'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'north'}}"); ++ register(2035, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'east'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'east'}}"); ++ register(2036, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'south'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'south'}}"); ++ register(2037, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'west'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'west'}}"); ++ register(2038, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'north'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'north'}}"); ++ register(2039, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'east'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'east'}}"); ++ register(2040, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'south'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'south'}}"); ++ register(2041, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'west'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'west'}}"); ++ register(2042, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'north'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'north'}}"); ++ register(2043, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'east'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'east'}}"); ++ register(2048, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2049, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2050, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2051, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2052, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2053, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2054, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2055, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2064, "{Name:'minecraft:emerald_ore'}", "{Name:'minecraft:emerald_ore'}"); ++ register(2082, "{Name:'minecraft:ender_chest',Properties:{facing:'north'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'north'}}"); ++ register(2083, "{Name:'minecraft:ender_chest',Properties:{facing:'south'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'south'}}"); ++ register(2084, "{Name:'minecraft:ender_chest',Properties:{facing:'west'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'west'}}"); ++ register(2085, "{Name:'minecraft:ender_chest',Properties:{facing:'east'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'east'}}"); ++ register(2096, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'false'}}"); ++ register(2097, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'false'}}"); ++ register(2098, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'false'}}"); ++ register(2099, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'false'}}"); ++ register(2100, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'false'}}"); ++ register(2101, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'false'}}"); ++ register(2102, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'false'}}"); ++ register(2103, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'false'}}"); ++ register(2104, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'true'}}"); ++ register(2105, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'true'}}"); ++ register(2106, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'true'}}"); ++ register(2107, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'true'}}"); ++ register(2108, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'true'}}"); ++ register(2109, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'true'}}"); ++ register(2110, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'true'}}"); ++ register(2111, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'true'}}"); ++ register(2112, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); ++ register(2113, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); ++ register(2114, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); ++ register(2115, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}"); ++ register(2116, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); ++ register(2117, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); ++ register(2118, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); ++ register(2119, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}"); ++ register(2120, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); ++ register(2121, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); ++ register(2122, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); ++ register(2123, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}"); ++ register(2124, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); ++ register(2125, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); ++ register(2126, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); ++ register(2128, "{Name:'minecraft:emerald_block'}", "{Name:'minecraft:emerald_block'}"); ++ register(2144, "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2145, "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2146, "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2147, "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2148, "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2149, "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2150, "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2151, "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2160, "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2161, "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2162, "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2163, "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2164, "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2165, "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2166, "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2167, "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2176, "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2177, "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2178, "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2179, "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2180, "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2181, "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2182, "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2183, "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2192, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'down'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'down'}}"); ++ register(2193, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'up'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'up'}}"); ++ register(2194, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'north'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'north'}}"); ++ register(2195, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'south'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'south'}}"); ++ register(2196, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'west'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'west'}}"); ++ register(2197, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'east'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'east'}}"); ++ register(2200, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'down'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'down'}}"); ++ register(2201, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'up'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'up'}}"); ++ register(2202, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'north'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'north'}}"); ++ register(2203, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'south'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'south'}}"); ++ register(2204, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'west'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'west'}}"); ++ register(2205, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'east'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'east'}}"); ++ register(2208, "{Name:'minecraft:beacon'}", "{Name:'minecraft:beacon'}"); ++ register(2224, "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'cobblestone',west:'true'}}"); ++ register(2225, "{Name:'minecraft:mossy_cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}"); ++ // There are a few changes made to flower pot here, notably handling how legacy data is handled. ++ // The TE itself should contain the target item and from there the proper state can be determined. However, there are ++ // blocks that do not contain a TE. So we need to make sure there is a default to fall on. ++ // I simply followed the legacy handling from BlockFlowerPot from 1.8.8 to find what legacy data mapped to what. ++ // It's better than defaulting everything to a cactus. ++ register(2240, "{Name:'minecraft:flower_pot'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'0'}}"); ++ register(2241, "{Name:'minecraft:potted_poppy'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'1'}}"); ++ register(2242, "{Name:'minecraft:potted_dandelion'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'2'}}"); ++ register(2243, "{Name:'minecraft:potted_oak_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'3'}}"); ++ register(2244, "{Name:'minecraft:potted_spruce_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'4'}}"); ++ register(2245, "{Name:'minecraft:potted_birch_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'5'}}"); ++ register(2246, "{Name:'minecraft:potted_jungle_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'6'}}"); ++ register(2247, "{Name:'minecraft:potted_red_mushroom'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'7'}}"); ++ register(2248, "{Name:'minecraft:potted_brown_mushroom'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'8'}}"); ++ register(2249, "{Name:'minecraft:potted_cactus'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'9'}}"); ++ register(2250, "{Name:'minecraft:potted_dead_bush'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'10'}}"); ++ register(2251, "{Name:'minecraft:potted_fern'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'11'}}"); ++ register(2252, "{Name:'minecraft:potted_acacia_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'12'}}"); ++ register(2253, "{Name:'minecraft:potted_dark_oak_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'13'}}"); ++ register(2254, "{Name:'minecraft:flower_pot'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'14'}}"); ++ register(2255, "{Name:'minecraft:flower_pot'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'15'}}"); ++ register(2256, "{Name:'minecraft:carrots',Properties:{age:'0'}}", "{Name:'minecraft:carrots',Properties:{age:'0'}}"); ++ register(2257, "{Name:'minecraft:carrots',Properties:{age:'1'}}", "{Name:'minecraft:carrots',Properties:{age:'1'}}"); ++ register(2258, "{Name:'minecraft:carrots',Properties:{age:'2'}}", "{Name:'minecraft:carrots',Properties:{age:'2'}}"); ++ register(2259, "{Name:'minecraft:carrots',Properties:{age:'3'}}", "{Name:'minecraft:carrots',Properties:{age:'3'}}"); ++ register(2260, "{Name:'minecraft:carrots',Properties:{age:'4'}}", "{Name:'minecraft:carrots',Properties:{age:'4'}}"); ++ register(2261, "{Name:'minecraft:carrots',Properties:{age:'5'}}", "{Name:'minecraft:carrots',Properties:{age:'5'}}"); ++ register(2262, "{Name:'minecraft:carrots',Properties:{age:'6'}}", "{Name:'minecraft:carrots',Properties:{age:'6'}}"); ++ register(2263, "{Name:'minecraft:carrots',Properties:{age:'7'}}", "{Name:'minecraft:carrots',Properties:{age:'7'}}"); ++ register(2272, "{Name:'minecraft:potatoes',Properties:{age:'0'}}", "{Name:'minecraft:potatoes',Properties:{age:'0'}}"); ++ register(2273, "{Name:'minecraft:potatoes',Properties:{age:'1'}}", "{Name:'minecraft:potatoes',Properties:{age:'1'}}"); ++ register(2274, "{Name:'minecraft:potatoes',Properties:{age:'2'}}", "{Name:'minecraft:potatoes',Properties:{age:'2'}}"); ++ register(2275, "{Name:'minecraft:potatoes',Properties:{age:'3'}}", "{Name:'minecraft:potatoes',Properties:{age:'3'}}"); ++ register(2276, "{Name:'minecraft:potatoes',Properties:{age:'4'}}", "{Name:'minecraft:potatoes',Properties:{age:'4'}}"); ++ register(2277, "{Name:'minecraft:potatoes',Properties:{age:'5'}}", "{Name:'minecraft:potatoes',Properties:{age:'5'}}"); ++ register(2278, "{Name:'minecraft:potatoes',Properties:{age:'6'}}", "{Name:'minecraft:potatoes',Properties:{age:'6'}}"); ++ register(2279, "{Name:'minecraft:potatoes',Properties:{age:'7'}}", "{Name:'minecraft:potatoes',Properties:{age:'7'}}"); ++ register(2288, "{Name:'minecraft:oak_button',Properties:{face:'ceiling',facing:'north',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'down',powered:'false'}}"); ++ register(2289, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'east',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'east',powered:'false'}}"); ++ register(2290, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'west',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'west',powered:'false'}}"); ++ register(2291, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'south',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'south',powered:'false'}}"); ++ register(2292, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'north',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'north',powered:'false'}}"); ++ register(2293, "{Name:'minecraft:oak_button',Properties:{face:'floor',facing:'north',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'up',powered:'false'}}"); ++ register(2296, "{Name:'minecraft:oak_button',Properties:{face:'ceiling',facing:'north',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'down',powered:'true'}}"); ++ register(2297, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'east',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'east',powered:'true'}}"); ++ register(2298, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'west',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'west',powered:'true'}}"); ++ register(2299, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'south',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'south',powered:'true'}}"); ++ register(2300, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'north',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'north',powered:'true'}}"); ++ register(2301, "{Name:'minecraft:oak_button',Properties:{face:'floor',facing:'north',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'up',powered:'true'}}"); ++ register(2304, "{Name:'%%FILTER_ME%%',Properties:{facing:'down',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'down',nodrop:'false'}}"); ++ register(2305, "{Name:'%%FILTER_ME%%',Properties:{facing:'up',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'up',nodrop:'false'}}"); ++ register(2306, "{Name:'%%FILTER_ME%%',Properties:{facing:'north',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'north',nodrop:'false'}}"); ++ register(2307, "{Name:'%%FILTER_ME%%',Properties:{facing:'south',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'south',nodrop:'false'}}"); ++ register(2308, "{Name:'%%FILTER_ME%%',Properties:{facing:'west',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'west',nodrop:'false'}}"); ++ register(2309, "{Name:'%%FILTER_ME%%',Properties:{facing:'east',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'east',nodrop:'false'}}"); ++ register(2312, "{Name:'%%FILTER_ME%%',Properties:{facing:'down',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'down',nodrop:'true'}}"); ++ register(2313, "{Name:'%%FILTER_ME%%',Properties:{facing:'up',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'up',nodrop:'true'}}"); ++ register(2314, "{Name:'%%FILTER_ME%%',Properties:{facing:'north',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'north',nodrop:'true'}}"); ++ register(2315, "{Name:'%%FILTER_ME%%',Properties:{facing:'south',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'south',nodrop:'true'}}"); ++ register(2316, "{Name:'%%FILTER_ME%%',Properties:{facing:'west',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'west',nodrop:'true'}}"); ++ register(2317, "{Name:'%%FILTER_ME%%',Properties:{facing:'east',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'east',nodrop:'true'}}"); ++ register(2320, "{Name:'minecraft:anvil',Properties:{facing:'south'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'south'}}"); ++ register(2321, "{Name:'minecraft:anvil',Properties:{facing:'west'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'west'}}"); ++ register(2322, "{Name:'minecraft:anvil',Properties:{facing:'north'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'north'}}"); ++ register(2323, "{Name:'minecraft:anvil',Properties:{facing:'east'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'east'}}"); ++ register(2324, "{Name:'minecraft:chipped_anvil',Properties:{facing:'south'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'south'}}"); ++ register(2325, "{Name:'minecraft:chipped_anvil',Properties:{facing:'west'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'west'}}"); ++ register(2326, "{Name:'minecraft:chipped_anvil',Properties:{facing:'north'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'north'}}"); ++ register(2327, "{Name:'minecraft:chipped_anvil',Properties:{facing:'east'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'east'}}"); ++ register(2328, "{Name:'minecraft:damaged_anvil',Properties:{facing:'south'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'south'}}"); ++ register(2329, "{Name:'minecraft:damaged_anvil',Properties:{facing:'west'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'west'}}"); ++ register(2330, "{Name:'minecraft:damaged_anvil',Properties:{facing:'north'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'north'}}"); ++ register(2331, "{Name:'minecraft:damaged_anvil',Properties:{facing:'east'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'east'}}"); ++ register(2338, "{Name:'minecraft:trapped_chest',Properties:{facing:'north',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'north'}}"); ++ register(2339, "{Name:'minecraft:trapped_chest',Properties:{facing:'south',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'south'}}"); ++ register(2340, "{Name:'minecraft:trapped_chest',Properties:{facing:'west',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'west'}}"); ++ register(2341, "{Name:'minecraft:trapped_chest',Properties:{facing:'east',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'east'}}"); ++ register(2352, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'0'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'0'}}"); ++ register(2353, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'1'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'1'}}"); ++ register(2354, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'2'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'2'}}"); ++ register(2355, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'3'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'3'}}"); ++ register(2356, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'4'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'4'}}"); ++ register(2357, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'5'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'5'}}"); ++ register(2358, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'6'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'6'}}"); ++ register(2359, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'7'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'7'}}"); ++ register(2360, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'8'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'8'}}"); ++ register(2361, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'9'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'9'}}"); ++ register(2362, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'10'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'10'}}"); ++ register(2363, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'11'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'11'}}"); ++ register(2364, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'12'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'12'}}"); ++ register(2365, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'13'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'13'}}"); ++ register(2366, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'14'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'14'}}"); ++ register(2367, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'15'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'15'}}"); ++ register(2368, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'0'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'0'}}"); ++ register(2369, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'1'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'1'}}"); ++ register(2370, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'2'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'2'}}"); ++ register(2371, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'3'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'3'}}"); ++ register(2372, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'4'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'4'}}"); ++ register(2373, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'5'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'5'}}"); ++ register(2374, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'6'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'6'}}"); ++ register(2375, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'7'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'7'}}"); ++ register(2376, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'8'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'8'}}"); ++ register(2377, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'9'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'9'}}"); ++ register(2378, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'10'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'10'}}"); ++ register(2379, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'11'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'11'}}"); ++ register(2380, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'12'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'12'}}"); ++ register(2381, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'13'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'13'}}"); ++ register(2382, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'14'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'14'}}"); ++ register(2383, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'15'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'15'}}"); ++ register(2384, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}"); ++ register(2385, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}"); ++ register(2386, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}"); ++ register(2387, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}"); ++ register(2388, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}"); ++ register(2389, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}"); ++ register(2390, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}"); ++ register(2391, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}"); ++ register(2392, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}"); ++ register(2393, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}"); ++ register(2394, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}"); ++ register(2395, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}"); ++ register(2396, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}"); ++ register(2397, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}"); ++ register(2398, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}"); ++ register(2399, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}"); ++ register(2400, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}"); ++ register(2401, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}"); ++ register(2402, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}"); ++ register(2403, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}"); ++ register(2404, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}"); ++ register(2405, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}"); ++ register(2406, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}"); ++ register(2407, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}"); ++ register(2408, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}"); ++ register(2409, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}"); ++ register(2410, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}"); ++ register(2411, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}"); ++ register(2412, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}"); ++ register(2413, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}"); ++ register(2414, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}"); ++ register(2415, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}"); ++ register(2416, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'0'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'0'}}"); ++ register(2417, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'1'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'1'}}"); ++ register(2418, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'2'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'2'}}"); ++ register(2419, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'3'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'3'}}"); ++ register(2420, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'4'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'4'}}"); ++ register(2421, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'5'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'5'}}"); ++ register(2422, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'6'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'6'}}"); ++ register(2423, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'7'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'7'}}"); ++ register(2424, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'8'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'8'}}"); ++ register(2425, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'9'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'9'}}"); ++ register(2426, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'10'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'10'}}"); ++ register(2427, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'11'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'11'}}"); ++ register(2428, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'12'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'12'}}"); ++ register(2429, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'13'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'13'}}"); ++ register(2430, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'14'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'14'}}"); ++ register(2431, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'15'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'15'}}"); ++ register(2432, "{Name:'minecraft:redstone_block'}", "{Name:'minecraft:redstone_block'}"); ++ register(2448, "{Name:'minecraft:nether_quartz_ore'}", "{Name:'minecraft:quartz_ore'}"); ++ register(2464, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'down'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'down'}}"); ++ register(2466, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'north'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'north'}}"); ++ register(2467, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'south'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'south'}}"); ++ register(2468, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'west'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'west'}}"); ++ register(2469, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'east'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'east'}}"); ++ register(2472, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'down'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'down'}}"); ++ register(2474, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'north'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'north'}}"); ++ register(2475, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'south'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'south'}}"); ++ register(2476, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'west'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'west'}}"); ++ register(2477, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'east'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'east'}}"); ++ register(2480, "{Name:'minecraft:quartz_block'}", "{Name:'minecraft:quartz_block',Properties:{variant:'default'}}"); ++ register(2481, "{Name:'minecraft:chiseled_quartz_block'}", "{Name:'minecraft:quartz_block',Properties:{variant:'chiseled'}}"); ++ register(2482, "{Name:'minecraft:quartz_pillar',Properties:{axis:'y'}}", "{Name:'minecraft:quartz_block',Properties:{variant:'lines_y'}}"); ++ register(2483, "{Name:'minecraft:quartz_pillar',Properties:{axis:'x'}}", "{Name:'minecraft:quartz_block',Properties:{variant:'lines_x'}}"); ++ register(2484, "{Name:'minecraft:quartz_pillar',Properties:{axis:'z'}}", "{Name:'minecraft:quartz_block',Properties:{variant:'lines_z'}}"); ++ register(2496, "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2497, "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2498, "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2499, "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2500, "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2501, "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2502, "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2503, "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2512, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'north_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'north_south'}}"); ++ register(2513, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'east_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'east_west'}}"); ++ register(2514, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_east'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_east'}}"); ++ register(2515, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_west'}}"); ++ register(2516, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_north'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_north'}}"); ++ register(2517, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_south'}}"); ++ register(2520, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'north_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'north_south'}}"); ++ register(2521, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'east_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'east_west'}}"); ++ register(2522, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_east'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_east'}}"); ++ register(2523, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_west'}}"); ++ register(2524, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_north'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_north'}}"); ++ register(2525, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_south'}}"); ++ register(2528, "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'false'}}"); ++ register(2529, "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'false'}}"); ++ register(2530, "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'false'}}"); ++ register(2531, "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'false'}}"); ++ register(2532, "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'false'}}"); ++ register(2533, "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'false'}}"); ++ register(2536, "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'true'}}"); ++ register(2537, "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'true'}}"); ++ register(2538, "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'true'}}"); ++ register(2539, "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'true'}}"); ++ register(2540, "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'true'}}"); ++ register(2541, "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'true'}}"); ++ register(2544, "{Name:'minecraft:white_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'white'}}"); ++ register(2545, "{Name:'minecraft:orange_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'orange'}}"); ++ register(2546, "{Name:'minecraft:magenta_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'magenta'}}"); ++ register(2547, "{Name:'minecraft:light_blue_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'light_blue'}}"); ++ register(2548, "{Name:'minecraft:yellow_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'yellow'}}"); ++ register(2549, "{Name:'minecraft:lime_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'lime'}}"); ++ register(2550, "{Name:'minecraft:pink_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'pink'}}"); ++ register(2551, "{Name:'minecraft:gray_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'gray'}}"); ++ register(2552, "{Name:'minecraft:light_gray_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'silver'}}"); ++ register(2553, "{Name:'minecraft:cyan_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'cyan'}}"); ++ register(2554, "{Name:'minecraft:purple_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'purple'}}"); ++ register(2555, "{Name:'minecraft:blue_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'blue'}}"); ++ register(2556, "{Name:'minecraft:brown_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'brown'}}"); ++ register(2557, "{Name:'minecraft:green_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'green'}}"); ++ register(2558, "{Name:'minecraft:red_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'red'}}"); ++ register(2559, "{Name:'minecraft:black_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'black'}}"); ++ register(2560, "{Name:'minecraft:white_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2561, "{Name:'minecraft:orange_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2562, "{Name:'minecraft:magenta_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2563, "{Name:'minecraft:light_blue_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2564, "{Name:'minecraft:yellow_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2565, "{Name:'minecraft:lime_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2566, "{Name:'minecraft:pink_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2567, "{Name:'minecraft:gray_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2568, "{Name:'minecraft:light_gray_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2569, "{Name:'minecraft:cyan_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2570, "{Name:'minecraft:purple_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2571, "{Name:'minecraft:blue_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2572, "{Name:'minecraft:brown_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2573, "{Name:'minecraft:green_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2574, "{Name:'minecraft:red_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2575, "{Name:'minecraft:black_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2576, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'true',variant:'acacia'}}"); ++ register(2577, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'true',variant:'dark_oak'}}"); ++ register(2580, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'false',variant:'acacia'}}"); ++ register(2581, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'false',variant:'dark_oak'}}"); ++ register(2584, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'true',variant:'acacia'}}"); ++ register(2585, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'true',variant:'dark_oak'}}"); ++ register(2588, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'false',variant:'acacia'}}"); ++ register(2589, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'false',variant:'dark_oak'}}"); ++ register(2592, "{Name:'minecraft:acacia_log',Properties:{axis:'y'}}", "{Name:'minecraft:log2',Properties:{axis:'y',variant:'acacia'}}"); ++ register(2593, "{Name:'minecraft:dark_oak_log',Properties:{axis:'y'}}", "{Name:'minecraft:log2',Properties:{axis:'y',variant:'dark_oak'}}"); ++ register(2596, "{Name:'minecraft:acacia_log',Properties:{axis:'x'}}", "{Name:'minecraft:log2',Properties:{axis:'x',variant:'acacia'}}"); ++ register(2597, "{Name:'minecraft:dark_oak_log',Properties:{axis:'x'}}", "{Name:'minecraft:log2',Properties:{axis:'x',variant:'dark_oak'}}"); ++ register(2600, "{Name:'minecraft:acacia_log',Properties:{axis:'z'}}", "{Name:'minecraft:log2',Properties:{axis:'z',variant:'acacia'}}"); ++ register(2601, "{Name:'minecraft:dark_oak_log',Properties:{axis:'z'}}", "{Name:'minecraft:log2',Properties:{axis:'z',variant:'dark_oak'}}"); ++ register(2604, "{Name:'minecraft:acacia_bark'}", "{Name:'minecraft:log2',Properties:{axis:'none',variant:'acacia'}}"); ++ register(2605, "{Name:'minecraft:dark_oak_bark'}", "{Name:'minecraft:log2',Properties:{axis:'none',variant:'dark_oak'}}"); ++ register(2608, "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2609, "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2610, "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2611, "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2612, "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2613, "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2614, "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2615, "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2624, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2625, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2626, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2627, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2628, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2629, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2630, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2631, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2640, "{Name:'minecraft:slime_block'}", "{Name:'minecraft:slime'}"); ++ register(2656, "{Name:'minecraft:barrier'}", "{Name:'minecraft:barrier'}"); ++ register(2672, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}"); ++ register(2673, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}"); ++ register(2674, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}"); ++ register(2675, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}"); ++ register(2676, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}"); ++ register(2677, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}"); ++ register(2678, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}"); ++ register(2679, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}"); ++ register(2680, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'false'}}"); ++ register(2681, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'false'}}"); ++ register(2682, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'false'}}"); ++ register(2683, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'false'}}"); ++ register(2684, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'true'}}"); ++ register(2685, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'true'}}"); ++ register(2686, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'true'}}"); ++ register(2687, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'true'}}"); ++ register(2688, "{Name:'minecraft:prismarine'}", "{Name:'minecraft:prismarine',Properties:{variant:'prismarine'}}"); ++ register(2689, "{Name:'minecraft:prismarine_bricks'}", "{Name:'minecraft:prismarine',Properties:{variant:'prismarine_bricks'}}"); ++ register(2690, "{Name:'minecraft:dark_prismarine'}", "{Name:'minecraft:prismarine',Properties:{variant:'dark_prismarine'}}"); ++ register(2704, "{Name:'minecraft:sea_lantern'}", "{Name:'minecraft:sea_lantern'}"); ++ register(2720, "{Name:'minecraft:hay_block',Properties:{axis:'y'}}", "{Name:'minecraft:hay_block',Properties:{axis:'y'}}"); ++ register(2724, "{Name:'minecraft:hay_block',Properties:{axis:'x'}}", "{Name:'minecraft:hay_block',Properties:{axis:'x'}}"); ++ register(2728, "{Name:'minecraft:hay_block',Properties:{axis:'z'}}", "{Name:'minecraft:hay_block',Properties:{axis:'z'}}"); ++ register(2736, "{Name:'minecraft:white_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'white'}}"); ++ register(2737, "{Name:'minecraft:orange_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'orange'}}"); ++ register(2738, "{Name:'minecraft:magenta_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'magenta'}}"); ++ register(2739, "{Name:'minecraft:light_blue_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'light_blue'}}"); ++ register(2740, "{Name:'minecraft:yellow_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'yellow'}}"); ++ register(2741, "{Name:'minecraft:lime_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'lime'}}"); ++ register(2742, "{Name:'minecraft:pink_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'pink'}}"); ++ register(2743, "{Name:'minecraft:gray_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'gray'}}"); ++ register(2744, "{Name:'minecraft:light_gray_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'silver'}}"); ++ register(2745, "{Name:'minecraft:cyan_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'cyan'}}"); ++ register(2746, "{Name:'minecraft:purple_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'purple'}}"); ++ register(2747, "{Name:'minecraft:blue_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'blue'}}"); ++ register(2748, "{Name:'minecraft:brown_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'brown'}}"); ++ register(2749, "{Name:'minecraft:green_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'green'}}"); ++ register(2750, "{Name:'minecraft:red_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'red'}}"); ++ register(2751, "{Name:'minecraft:black_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'black'}}"); ++ register(2752, "{Name:'minecraft:terracotta'}", "{Name:'minecraft:hardened_clay'}"); ++ register(2768, "{Name:'minecraft:coal_block'}", "{Name:'minecraft:coal_block'}"); ++ register(2784, "{Name:'minecraft:packed_ice'}", "{Name:'minecraft:packed_ice'}"); ++ register(2800, "{Name:'minecraft:sunflower',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'sunflower'}}"); ++ register(2801, "{Name:'minecraft:lilac',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'syringa'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'syringa'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'syringa'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'syringa'}}"); ++ register(2802, "{Name:'minecraft:tall_grass',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'double_grass'}}"); ++ register(2803, "{Name:'minecraft:large_fern',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'double_fern'}}"); ++ register(2804, "{Name:'minecraft:rose_bush',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'double_rose'}}"); ++ register(2805, "{Name:'minecraft:peony',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'paeonia'}}"); ++ register(2808, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'syringa'}}"); ++ register(2809, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'syringa'}}"); ++ register(2810, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'syringa'}}"); ++ register(2811, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'syringa'}}"); ++ register(2816, "{Name:'minecraft:white_banner',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'0'}}"); ++ register(2817, "{Name:'minecraft:white_banner',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'1'}}"); ++ register(2818, "{Name:'minecraft:white_banner',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'2'}}"); ++ register(2819, "{Name:'minecraft:white_banner',Properties:{rotation:'3'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'3'}}"); ++ register(2820, "{Name:'minecraft:white_banner',Properties:{rotation:'4'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'4'}}"); ++ register(2821, "{Name:'minecraft:white_banner',Properties:{rotation:'5'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'5'}}"); ++ register(2822, "{Name:'minecraft:white_banner',Properties:{rotation:'6'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'6'}}"); ++ register(2823, "{Name:'minecraft:white_banner',Properties:{rotation:'7'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'7'}}"); ++ register(2824, "{Name:'minecraft:white_banner',Properties:{rotation:'8'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'8'}}"); ++ register(2825, "{Name:'minecraft:white_banner',Properties:{rotation:'9'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'9'}}"); ++ register(2826, "{Name:'minecraft:white_banner',Properties:{rotation:'10'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'10'}}"); ++ register(2827, "{Name:'minecraft:white_banner',Properties:{rotation:'11'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'11'}}"); ++ register(2828, "{Name:'minecraft:white_banner',Properties:{rotation:'12'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'12'}}"); ++ register(2829, "{Name:'minecraft:white_banner',Properties:{rotation:'13'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'13'}}"); ++ register(2830, "{Name:'minecraft:white_banner',Properties:{rotation:'14'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'14'}}"); ++ register(2831, "{Name:'minecraft:white_banner',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'15'}}"); ++ register(2834, "{Name:'minecraft:white_wall_banner',Properties:{facing:'north'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'north'}}"); ++ register(2835, "{Name:'minecraft:white_wall_banner',Properties:{facing:'south'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'south'}}"); ++ register(2836, "{Name:'minecraft:white_wall_banner',Properties:{facing:'west'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'west'}}"); ++ register(2837, "{Name:'minecraft:white_wall_banner',Properties:{facing:'east'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'east'}}"); ++ register(2848, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'0'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'0'}}"); ++ register(2849, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'1'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'1'}}"); ++ register(2850, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'2'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'2'}}"); ++ register(2851, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'3'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'3'}}"); ++ register(2852, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'4'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'4'}}"); ++ register(2853, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'5'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'5'}}"); ++ register(2854, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'6'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'6'}}"); ++ register(2855, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'7'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'7'}}"); ++ register(2856, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'8'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'8'}}"); ++ register(2857, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'9'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'9'}}"); ++ register(2858, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'10'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'10'}}"); ++ register(2859, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'11'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'11'}}"); ++ register(2860, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'12'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'12'}}"); ++ register(2861, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'13'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'13'}}"); ++ register(2862, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'14'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'14'}}"); ++ register(2863, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'15'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'15'}}"); ++ register(2864, "{Name:'minecraft:red_sandstone'}", "{Name:'minecraft:red_sandstone',Properties:{type:'red_sandstone'}}"); ++ register(2865, "{Name:'minecraft:chiseled_red_sandstone'}", "{Name:'minecraft:red_sandstone',Properties:{type:'chiseled_red_sandstone'}}"); ++ register(2866, "{Name:'minecraft:cut_red_sandstone'}", "{Name:'minecraft:red_sandstone',Properties:{type:'smooth_red_sandstone'}}"); ++ register(2880, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2881, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2882, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2883, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2884, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2885, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2886, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2887, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2896, "{Name:'minecraft:red_sandstone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab2',Properties:{seamless:'false',variant:'red_sandstone'}}"); ++ register(2904, "{Name:'minecraft:smooth_red_sandstone'}", "{Name:'minecraft:double_stone_slab2',Properties:{seamless:'true',variant:'red_sandstone'}}"); ++ register(2912, "{Name:'minecraft:red_sandstone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab2',Properties:{half:'bottom',variant:'red_sandstone'}}"); ++ register(2920, "{Name:'minecraft:red_sandstone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab2',Properties:{half:'top',variant:'red_sandstone'}}"); ++ register(2928, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2929, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2930, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2931, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2932, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2933, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2934, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2935, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2936, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2937, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2938, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2939, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2940, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2941, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2942, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2943, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2944, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2945, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2946, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2947, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2948, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2949, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2950, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2951, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2952, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2953, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2954, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2955, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2956, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2957, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2958, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2959, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2960, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2961, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2962, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2963, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2964, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2965, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2966, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2967, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2968, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2969, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2970, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2971, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2972, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2973, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2974, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2975, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2976, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2977, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2978, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2979, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2980, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2981, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2982, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2983, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2984, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2985, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2986, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2987, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2988, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2989, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2990, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2991, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2992, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2993, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2994, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2995, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2996, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2997, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2998, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2999, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); ++ register(3000, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); ++ register(3001, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); ++ register(3002, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); ++ register(3003, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); ++ register(3004, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); ++ register(3005, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); ++ register(3006, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); ++ register(3007, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); ++ register(3008, "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(3024, "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(3040, "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(3056, "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(3072, "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(3088, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3089, "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3090, "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3091, "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3092, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3093, "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3094, "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3095, "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3096, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(3097, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(3098, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(3099, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(3104, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3105, "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3106, "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3107, "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3108, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3109, "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3110, "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3111, "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3112, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(3113, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(3114, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(3115, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(3120, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3121, "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3122, "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3123, "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3124, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3125, "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3126, "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3127, "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3128, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(3129, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(3130, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(3131, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(3136, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3137, "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3138, "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3139, "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3140, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3141, "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3142, "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3143, "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3144, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(3145, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(3146, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(3147, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(3152, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3153, "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3154, "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3155, "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3156, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3157, "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3158, "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3159, "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3160, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(3161, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(3162, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(3163, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(3168, "{Name:'minecraft:end_rod',Properties:{facing:'down'}}", "{Name:'minecraft:end_rod',Properties:{facing:'down'}}"); ++ register(3169, "{Name:'minecraft:end_rod',Properties:{facing:'up'}}", "{Name:'minecraft:end_rod',Properties:{facing:'up'}}"); ++ register(3170, "{Name:'minecraft:end_rod',Properties:{facing:'north'}}", "{Name:'minecraft:end_rod',Properties:{facing:'north'}}"); ++ register(3171, "{Name:'minecraft:end_rod',Properties:{facing:'south'}}", "{Name:'minecraft:end_rod',Properties:{facing:'south'}}"); ++ register(3172, "{Name:'minecraft:end_rod',Properties:{facing:'west'}}", "{Name:'minecraft:end_rod',Properties:{facing:'west'}}"); ++ register(3173, "{Name:'minecraft:end_rod',Properties:{facing:'east'}}", "{Name:'minecraft:end_rod',Properties:{facing:'east'}}"); ++ register(3184, "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(3200, "{Name:'minecraft:chorus_flower',Properties:{age:'0'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'0'}}"); ++ register(3201, "{Name:'minecraft:chorus_flower',Properties:{age:'1'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'1'}}"); ++ register(3202, "{Name:'minecraft:chorus_flower',Properties:{age:'2'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'2'}}"); ++ register(3203, "{Name:'minecraft:chorus_flower',Properties:{age:'3'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'3'}}"); ++ register(3204, "{Name:'minecraft:chorus_flower',Properties:{age:'4'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'4'}}"); ++ register(3205, "{Name:'minecraft:chorus_flower',Properties:{age:'5'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'5'}}"); ++ register(3216, "{Name:'minecraft:purpur_block'}", "{Name:'minecraft:purpur_block'}"); ++ register(3232, "{Name:'minecraft:purpur_pillar',Properties:{axis:'y'}}", "{Name:'minecraft:purpur_pillar',Properties:{axis:'y'}}"); ++ register(3236, "{Name:'minecraft:purpur_pillar',Properties:{axis:'x'}}", "{Name:'minecraft:purpur_pillar',Properties:{axis:'x'}}"); ++ register(3240, "{Name:'minecraft:purpur_pillar',Properties:{axis:'z'}}", "{Name:'minecraft:purpur_pillar',Properties:{axis:'z'}}"); ++ register(3248, "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(3249, "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(3250, "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(3251, "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(3252, "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(3253, "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(3254, "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(3255, "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(3264, "{Name:'minecraft:purpur_slab',Properties:{type:'double'}}", "{Name:'minecraft:purpur_double_slab',Properties:{variant:'default'}}"); ++ register(3280, "{Name:'minecraft:purpur_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:purpur_slab',Properties:{half:'bottom',variant:'default'}}"); ++ register(3288, "{Name:'minecraft:purpur_slab',Properties:{type:'top'}}", "{Name:'minecraft:purpur_slab',Properties:{half:'top',variant:'default'}}"); ++ register(3296, "{Name:'minecraft:end_stone_bricks'}", "{Name:'minecraft:end_bricks'}"); ++ register(3312, "{Name:'minecraft:beetroots',Properties:{age:'0'}}", "{Name:'minecraft:beetroots',Properties:{age:'0'}}"); ++ register(3313, "{Name:'minecraft:beetroots',Properties:{age:'1'}}", "{Name:'minecraft:beetroots',Properties:{age:'1'}}"); ++ register(3314, "{Name:'minecraft:beetroots',Properties:{age:'2'}}", "{Name:'minecraft:beetroots',Properties:{age:'2'}}"); ++ register(3315, "{Name:'minecraft:beetroots',Properties:{age:'3'}}", "{Name:'minecraft:beetroots',Properties:{age:'3'}}"); ++ register(3328, "{Name:'minecraft:grass_path'}", "{Name:'minecraft:grass_path'}"); ++ register(3344, "{Name:'minecraft:end_gateway'}", "{Name:'minecraft:end_gateway'}"); ++ register(3360, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'down'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'down'}}"); ++ register(3361, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'up'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'up'}}"); ++ register(3362, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'north'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'north'}}"); ++ register(3363, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'south'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'south'}}"); ++ register(3364, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'west'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'west'}}"); ++ register(3365, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'east'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'east'}}"); ++ register(3368, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'down'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'down'}}"); ++ register(3369, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'up'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'up'}}"); ++ register(3370, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'north'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'north'}}"); ++ register(3371, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'south'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'south'}}"); ++ register(3372, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'west'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'west'}}"); ++ register(3373, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'east'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'east'}}"); ++ register(3376, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'down'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'down'}}"); ++ register(3377, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'up'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'up'}}"); ++ register(3378, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'north'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'north'}}"); ++ register(3379, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'south'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'south'}}"); ++ register(3380, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'west'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'west'}}"); ++ register(3381, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'east'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'east'}}"); ++ register(3384, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'down'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'down'}}"); ++ register(3385, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'up'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'up'}}"); ++ register(3386, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'north'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'north'}}"); ++ register(3387, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'south'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'south'}}"); ++ register(3388, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'west'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'west'}}"); ++ register(3389, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'east'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'east'}}"); ++ register(3392, "{Name:'minecraft:frosted_ice',Properties:{age:'0'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'0'}}"); ++ register(3393, "{Name:'minecraft:frosted_ice',Properties:{age:'1'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'1'}}"); ++ register(3394, "{Name:'minecraft:frosted_ice',Properties:{age:'2'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'2'}}"); ++ register(3395, "{Name:'minecraft:frosted_ice',Properties:{age:'3'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'3'}}"); ++ register(3408, "{Name:'minecraft:magma_block'}", "{Name:'minecraft:magma'}"); ++ register(3424, "{Name:'minecraft:nether_wart_block'}", "{Name:'minecraft:nether_wart_block'}"); ++ register(3440, "{Name:'minecraft:red_nether_bricks'}", "{Name:'minecraft:red_nether_brick'}"); ++ register(3456, "{Name:'minecraft:bone_block',Properties:{axis:'y'}}", "{Name:'minecraft:bone_block',Properties:{axis:'y'}}"); ++ register(3460, "{Name:'minecraft:bone_block',Properties:{axis:'x'}}", "{Name:'minecraft:bone_block',Properties:{axis:'x'}}"); ++ register(3464, "{Name:'minecraft:bone_block',Properties:{axis:'z'}}", "{Name:'minecraft:bone_block',Properties:{axis:'z'}}"); ++ register(3472, "{Name:'minecraft:structure_void'}", "{Name:'minecraft:structure_void'}"); ++ register(3488, "{Name:'minecraft:observer',Properties:{facing:'down',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'down',powered:'false'}}"); ++ register(3489, "{Name:'minecraft:observer',Properties:{facing:'up',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'up',powered:'false'}}"); ++ register(3490, "{Name:'minecraft:observer',Properties:{facing:'north',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'north',powered:'false'}}"); ++ register(3491, "{Name:'minecraft:observer',Properties:{facing:'south',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'south',powered:'false'}}"); ++ register(3492, "{Name:'minecraft:observer',Properties:{facing:'west',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'west',powered:'false'}}"); ++ register(3493, "{Name:'minecraft:observer',Properties:{facing:'east',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'east',powered:'false'}}"); ++ register(3496, "{Name:'minecraft:observer',Properties:{facing:'down',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'down',powered:'true'}}"); ++ register(3497, "{Name:'minecraft:observer',Properties:{facing:'up',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'up',powered:'true'}}"); ++ register(3498, "{Name:'minecraft:observer',Properties:{facing:'north',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'north',powered:'true'}}"); ++ register(3499, "{Name:'minecraft:observer',Properties:{facing:'south',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'south',powered:'true'}}"); ++ register(3500, "{Name:'minecraft:observer',Properties:{facing:'west',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'west',powered:'true'}}"); ++ register(3501, "{Name:'minecraft:observer',Properties:{facing:'east',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'east',powered:'true'}}"); ++ register(3504, "{Name:'minecraft:white_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'down'}}"); ++ register(3505, "{Name:'minecraft:white_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'up'}}"); ++ register(3506, "{Name:'minecraft:white_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'north'}}"); ++ register(3507, "{Name:'minecraft:white_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'south'}}"); ++ register(3508, "{Name:'minecraft:white_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'west'}}"); ++ register(3509, "{Name:'minecraft:white_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'east'}}"); ++ register(3520, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'down'}}"); ++ register(3521, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'up'}}"); ++ register(3522, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'north'}}"); ++ register(3523, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'south'}}"); ++ register(3524, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'west'}}"); ++ register(3525, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'east'}}"); ++ register(3536, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'down'}}"); ++ register(3537, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'up'}}"); ++ register(3538, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'north'}}"); ++ register(3539, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'south'}}"); ++ register(3540, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'west'}}"); ++ register(3541, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'east'}}"); ++ register(3552, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'down'}}"); ++ register(3553, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'up'}}"); ++ register(3554, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'north'}}"); ++ register(3555, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'south'}}"); ++ register(3556, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'west'}}"); ++ register(3557, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'east'}}"); ++ register(3568, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'down'}}"); ++ register(3569, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'up'}}"); ++ register(3570, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'north'}}"); ++ register(3571, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'south'}}"); ++ register(3572, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'west'}}"); ++ register(3573, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'east'}}"); ++ register(3584, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'down'}}"); ++ register(3585, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'up'}}"); ++ register(3586, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'north'}}"); ++ register(3587, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'south'}}"); ++ register(3588, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'west'}}"); ++ register(3589, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'east'}}"); ++ register(3600, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'down'}}"); ++ register(3601, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'up'}}"); ++ register(3602, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'north'}}"); ++ register(3603, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'south'}}"); ++ register(3604, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'west'}}"); ++ register(3605, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'east'}}"); ++ register(3616, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'down'}}"); ++ register(3617, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'up'}}"); ++ register(3618, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'north'}}"); ++ register(3619, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'south'}}"); ++ register(3620, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'west'}}"); ++ register(3621, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'east'}}"); ++ register(3632, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'down'}}"); ++ register(3633, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'up'}}"); ++ register(3634, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'north'}}"); ++ register(3635, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'south'}}"); ++ register(3636, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'west'}}"); ++ register(3637, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'east'}}"); ++ register(3648, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'down'}}"); ++ register(3649, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'up'}}"); ++ register(3650, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'north'}}"); ++ register(3651, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'south'}}"); ++ register(3652, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'west'}}"); ++ register(3653, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'east'}}"); ++ register(3664, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'down'}}"); ++ register(3665, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'up'}}"); ++ register(3666, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'north'}}"); ++ register(3667, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'south'}}"); ++ register(3668, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'west'}}"); ++ register(3669, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'east'}}"); ++ register(3680, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'down'}}"); ++ register(3681, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'up'}}"); ++ register(3682, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'north'}}"); ++ register(3683, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'south'}}"); ++ register(3684, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'west'}}"); ++ register(3685, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'east'}}"); ++ register(3696, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'down'}}"); ++ register(3697, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'up'}}"); ++ register(3698, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'north'}}"); ++ register(3699, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'south'}}"); ++ register(3700, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'west'}}"); ++ register(3701, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'east'}}"); ++ register(3712, "{Name:'minecraft:green_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'down'}}"); ++ register(3713, "{Name:'minecraft:green_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'up'}}"); ++ register(3714, "{Name:'minecraft:green_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'north'}}"); ++ register(3715, "{Name:'minecraft:green_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'south'}}"); ++ register(3716, "{Name:'minecraft:green_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'west'}}"); ++ register(3717, "{Name:'minecraft:green_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'east'}}"); ++ register(3728, "{Name:'minecraft:red_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'down'}}"); ++ register(3729, "{Name:'minecraft:red_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'up'}}"); ++ register(3730, "{Name:'minecraft:red_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'north'}}"); ++ register(3731, "{Name:'minecraft:red_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'south'}}"); ++ register(3732, "{Name:'minecraft:red_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'west'}}"); ++ register(3733, "{Name:'minecraft:red_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'east'}}"); ++ register(3744, "{Name:'minecraft:black_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'down'}}"); ++ register(3745, "{Name:'minecraft:black_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'up'}}"); ++ register(3746, "{Name:'minecraft:black_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'north'}}"); ++ register(3747, "{Name:'minecraft:black_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'south'}}"); ++ register(3748, "{Name:'minecraft:black_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'west'}}"); ++ register(3749, "{Name:'minecraft:black_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'east'}}"); ++ register(3760, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3761, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3762, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3763, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3776, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3777, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3778, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3779, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3792, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3793, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3794, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3795, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3808, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3809, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3810, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3811, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3824, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3825, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3826, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3827, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3840, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3841, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3842, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3843, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3856, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3857, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3858, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3859, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3872, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3873, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3874, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3875, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3888, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3889, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3890, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3891, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3904, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3905, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3906, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3907, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3920, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3921, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3922, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3923, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3936, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3937, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3938, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3939, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3952, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3953, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3954, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3955, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3968, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3969, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3970, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3971, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3984, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3985, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3986, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3987, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(4000, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(4001, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(4002, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(4003, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(4016, "{Name:'minecraft:white_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'white'}}"); ++ register(4017, "{Name:'minecraft:orange_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'orange'}}"); ++ register(4018, "{Name:'minecraft:magenta_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'magenta'}}"); ++ register(4019, "{Name:'minecraft:light_blue_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'light_blue'}}"); ++ register(4020, "{Name:'minecraft:yellow_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'yellow'}}"); ++ register(4021, "{Name:'minecraft:lime_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'lime'}}"); ++ register(4022, "{Name:'minecraft:pink_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'pink'}}"); ++ register(4023, "{Name:'minecraft:gray_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'gray'}}"); ++ register(4024, "{Name:'minecraft:light_gray_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'silver'}}"); ++ register(4025, "{Name:'minecraft:cyan_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'cyan'}}"); ++ register(4026, "{Name:'minecraft:purple_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'purple'}}"); ++ register(4027, "{Name:'minecraft:blue_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'blue'}}"); ++ register(4028, "{Name:'minecraft:brown_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'brown'}}"); ++ register(4029, "{Name:'minecraft:green_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'green'}}"); ++ register(4030, "{Name:'minecraft:red_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'red'}}"); ++ register(4031, "{Name:'minecraft:black_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'black'}}"); ++ register(4032, "{Name:'minecraft:white_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'white'}}"); ++ register(4033, "{Name:'minecraft:orange_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'orange'}}"); ++ register(4034, "{Name:'minecraft:magenta_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'magenta'}}"); ++ register(4035, "{Name:'minecraft:light_blue_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'light_blue'}}"); ++ register(4036, "{Name:'minecraft:yellow_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'yellow'}}"); ++ register(4037, "{Name:'minecraft:lime_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'lime'}}"); ++ register(4038, "{Name:'minecraft:pink_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'pink'}}"); ++ register(4039, "{Name:'minecraft:gray_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'gray'}}"); ++ register(4040, "{Name:'minecraft:light_gray_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'silver'}}"); ++ register(4041, "{Name:'minecraft:cyan_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'cyan'}}"); ++ register(4042, "{Name:'minecraft:purple_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'purple'}}"); ++ register(4043, "{Name:'minecraft:blue_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'blue'}}"); ++ register(4044, "{Name:'minecraft:brown_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'brown'}}"); ++ register(4045, "{Name:'minecraft:green_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'green'}}"); ++ register(4046, "{Name:'minecraft:red_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'red'}}"); ++ register(4047, "{Name:'minecraft:black_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'black'}}"); ++ register(4080, "{Name:'minecraft:structure_block',Properties:{mode:'save'}}", "{Name:'minecraft:structure_block',Properties:{mode:'save'}}"); ++ register(4081, "{Name:'minecraft:structure_block',Properties:{mode:'load'}}", "{Name:'minecraft:structure_block',Properties:{mode:'load'}}"); ++ register(4082, "{Name:'minecraft:structure_block',Properties:{mode:'corner'}}", "{Name:'minecraft:structure_block',Properties:{mode:'corner'}}"); ++ register(4083, "{Name:'minecraft:structure_block',Properties:{mode:'data'}}", "{Name:'minecraft:structure_block',Properties:{mode:'data'}}"); ++ finalizeMaps(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java +new file mode 100644 +index 0000000000000000000000000000000000000000..86f6aa3e3fa886976809f350fc5eb16f6a026ed9 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java +@@ -0,0 +1,533 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.helpers; ++ ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++ ++public final class HelperItemNameV102 { ++ ++ // This class is responsible for mapping the id -> string update in itemstacks and potions ++ ++ private static final Int2ObjectOpenHashMap ITEM_NAMES = new Int2ObjectOpenHashMap() { ++ @Override ++ public String put(final int k, final String o) { ++ final String ret = super.put(k, o); ++ ++ if (ret != null) { ++ throw new IllegalStateException("Mapping already exists for " + k + ": prev: " + ret + ", new: " + o); ++ } ++ ++ return ret; ++ } ++ }; ++ ++ static { ++ ITEM_NAMES.put(0, "minecraft:air"); ++ ITEM_NAMES.put(1, "minecraft:stone"); ++ ITEM_NAMES.put(2, "minecraft:grass"); ++ ITEM_NAMES.put(3, "minecraft:dirt"); ++ ITEM_NAMES.put(4, "minecraft:cobblestone"); ++ ITEM_NAMES.put(5, "minecraft:planks"); ++ ITEM_NAMES.put(6, "minecraft:sapling"); ++ ITEM_NAMES.put(7, "minecraft:bedrock"); ++ ITEM_NAMES.put(8, "minecraft:flowing_water"); ++ ITEM_NAMES.put(9, "minecraft:water"); ++ ITEM_NAMES.put(10, "minecraft:flowing_lava"); ++ ITEM_NAMES.put(11, "minecraft:lava"); ++ ITEM_NAMES.put(12, "minecraft:sand"); ++ ITEM_NAMES.put(13, "minecraft:gravel"); ++ ITEM_NAMES.put(14, "minecraft:gold_ore"); ++ ITEM_NAMES.put(15, "minecraft:iron_ore"); ++ ITEM_NAMES.put(16, "minecraft:coal_ore"); ++ ITEM_NAMES.put(17, "minecraft:log"); ++ ITEM_NAMES.put(18, "minecraft:leaves"); ++ ITEM_NAMES.put(19, "minecraft:sponge"); ++ ITEM_NAMES.put(20, "minecraft:glass"); ++ ITEM_NAMES.put(21, "minecraft:lapis_ore"); ++ ITEM_NAMES.put(22, "minecraft:lapis_block"); ++ ITEM_NAMES.put(23, "minecraft:dispenser"); ++ ITEM_NAMES.put(24, "minecraft:sandstone"); ++ ITEM_NAMES.put(25, "minecraft:noteblock"); ++ ITEM_NAMES.put(27, "minecraft:golden_rail"); ++ ITEM_NAMES.put(28, "minecraft:detector_rail"); ++ ITEM_NAMES.put(29, "minecraft:sticky_piston"); ++ ITEM_NAMES.put(30, "minecraft:web"); ++ ITEM_NAMES.put(31, "minecraft:tallgrass"); ++ ITEM_NAMES.put(32, "minecraft:deadbush"); ++ ITEM_NAMES.put(33, "minecraft:piston"); ++ ITEM_NAMES.put(35, "minecraft:wool"); ++ ITEM_NAMES.put(37, "minecraft:yellow_flower"); ++ ITEM_NAMES.put(38, "minecraft:red_flower"); ++ ITEM_NAMES.put(39, "minecraft:brown_mushroom"); ++ ITEM_NAMES.put(40, "minecraft:red_mushroom"); ++ ITEM_NAMES.put(41, "minecraft:gold_block"); ++ ITEM_NAMES.put(42, "minecraft:iron_block"); ++ ITEM_NAMES.put(43, "minecraft:double_stone_slab"); ++ ITEM_NAMES.put(44, "minecraft:stone_slab"); ++ ITEM_NAMES.put(45, "minecraft:brick_block"); ++ ITEM_NAMES.put(46, "minecraft:tnt"); ++ ITEM_NAMES.put(47, "minecraft:bookshelf"); ++ ITEM_NAMES.put(48, "minecraft:mossy_cobblestone"); ++ ITEM_NAMES.put(49, "minecraft:obsidian"); ++ ITEM_NAMES.put(50, "minecraft:torch"); ++ ITEM_NAMES.put(51, "minecraft:fire"); ++ ITEM_NAMES.put(52, "minecraft:mob_spawner"); ++ ITEM_NAMES.put(53, "minecraft:oak_stairs"); ++ ITEM_NAMES.put(54, "minecraft:chest"); ++ ITEM_NAMES.put(56, "minecraft:diamond_ore"); ++ ITEM_NAMES.put(57, "minecraft:diamond_block"); ++ ITEM_NAMES.put(58, "minecraft:crafting_table"); ++ ITEM_NAMES.put(60, "minecraft:farmland"); ++ ITEM_NAMES.put(61, "minecraft:furnace"); ++ ITEM_NAMES.put(62, "minecraft:lit_furnace"); ++ ITEM_NAMES.put(65, "minecraft:ladder"); ++ ITEM_NAMES.put(66, "minecraft:rail"); ++ ITEM_NAMES.put(67, "minecraft:stone_stairs"); ++ ITEM_NAMES.put(69, "minecraft:lever"); ++ ITEM_NAMES.put(70, "minecraft:stone_pressure_plate"); ++ ITEM_NAMES.put(72, "minecraft:wooden_pressure_plate"); ++ ITEM_NAMES.put(73, "minecraft:redstone_ore"); ++ ITEM_NAMES.put(76, "minecraft:redstone_torch"); ++ ITEM_NAMES.put(77, "minecraft:stone_button"); ++ ITEM_NAMES.put(78, "minecraft:snow_layer"); ++ ITEM_NAMES.put(79, "minecraft:ice"); ++ ITEM_NAMES.put(80, "minecraft:snow"); ++ ITEM_NAMES.put(81, "minecraft:cactus"); ++ ITEM_NAMES.put(82, "minecraft:clay"); ++ ITEM_NAMES.put(84, "minecraft:jukebox"); ++ ITEM_NAMES.put(85, "minecraft:fence"); ++ ITEM_NAMES.put(86, "minecraft:pumpkin"); ++ ITEM_NAMES.put(87, "minecraft:netherrack"); ++ ITEM_NAMES.put(88, "minecraft:soul_sand"); ++ ITEM_NAMES.put(89, "minecraft:glowstone"); ++ ITEM_NAMES.put(90, "minecraft:portal"); ++ ITEM_NAMES.put(91, "minecraft:lit_pumpkin"); ++ ITEM_NAMES.put(95, "minecraft:stained_glass"); ++ ITEM_NAMES.put(96, "minecraft:trapdoor"); ++ ITEM_NAMES.put(97, "minecraft:monster_egg"); ++ ITEM_NAMES.put(98, "minecraft:stonebrick"); ++ ITEM_NAMES.put(99, "minecraft:brown_mushroom_block"); ++ ITEM_NAMES.put(100, "minecraft:red_mushroom_block"); ++ ITEM_NAMES.put(101, "minecraft:iron_bars"); ++ ITEM_NAMES.put(102, "minecraft:glass_pane"); ++ ITEM_NAMES.put(103, "minecraft:melon_block"); ++ ITEM_NAMES.put(106, "minecraft:vine"); ++ ITEM_NAMES.put(107, "minecraft:fence_gate"); ++ ITEM_NAMES.put(108, "minecraft:brick_stairs"); ++ ITEM_NAMES.put(109, "minecraft:stone_brick_stairs"); ++ ITEM_NAMES.put(110, "minecraft:mycelium"); ++ ITEM_NAMES.put(111, "minecraft:waterlily"); ++ ITEM_NAMES.put(112, "minecraft:nether_brick"); ++ ITEM_NAMES.put(113, "minecraft:nether_brick_fence"); ++ ITEM_NAMES.put(114, "minecraft:nether_brick_stairs"); ++ ITEM_NAMES.put(116, "minecraft:enchanting_table"); ++ ITEM_NAMES.put(119, "minecraft:end_portal"); ++ ITEM_NAMES.put(120, "minecraft:end_portal_frame"); ++ ITEM_NAMES.put(121, "minecraft:end_stone"); ++ ITEM_NAMES.put(122, "minecraft:dragon_egg"); ++ ITEM_NAMES.put(123, "minecraft:redstone_lamp"); ++ ITEM_NAMES.put(125, "minecraft:double_wooden_slab"); ++ ITEM_NAMES.put(126, "minecraft:wooden_slab"); ++ ITEM_NAMES.put(127, "minecraft:cocoa"); ++ ITEM_NAMES.put(128, "minecraft:sandstone_stairs"); ++ ITEM_NAMES.put(129, "minecraft:emerald_ore"); ++ ITEM_NAMES.put(130, "minecraft:ender_chest"); ++ ITEM_NAMES.put(131, "minecraft:tripwire_hook"); ++ ITEM_NAMES.put(133, "minecraft:emerald_block"); ++ ITEM_NAMES.put(134, "minecraft:spruce_stairs"); ++ ITEM_NAMES.put(135, "minecraft:birch_stairs"); ++ ITEM_NAMES.put(136, "minecraft:jungle_stairs"); ++ ITEM_NAMES.put(137, "minecraft:command_block"); ++ ITEM_NAMES.put(138, "minecraft:beacon"); ++ ITEM_NAMES.put(139, "minecraft:cobblestone_wall"); ++ ITEM_NAMES.put(141, "minecraft:carrots"); ++ ITEM_NAMES.put(142, "minecraft:potatoes"); ++ ITEM_NAMES.put(143, "minecraft:wooden_button"); ++ ITEM_NAMES.put(145, "minecraft:anvil"); ++ ITEM_NAMES.put(146, "minecraft:trapped_chest"); ++ ITEM_NAMES.put(147, "minecraft:light_weighted_pressure_plate"); ++ ITEM_NAMES.put(148, "minecraft:heavy_weighted_pressure_plate"); ++ ITEM_NAMES.put(151, "minecraft:daylight_detector"); ++ ITEM_NAMES.put(152, "minecraft:redstone_block"); ++ ITEM_NAMES.put(153, "minecraft:quartz_ore"); ++ ITEM_NAMES.put(154, "minecraft:hopper"); ++ ITEM_NAMES.put(155, "minecraft:quartz_block"); ++ ITEM_NAMES.put(156, "minecraft:quartz_stairs"); ++ ITEM_NAMES.put(157, "minecraft:activator_rail"); ++ ITEM_NAMES.put(158, "minecraft:dropper"); ++ ITEM_NAMES.put(159, "minecraft:stained_hardened_clay"); ++ ITEM_NAMES.put(160, "minecraft:stained_glass_pane"); ++ ITEM_NAMES.put(161, "minecraft:leaves2"); ++ ITEM_NAMES.put(162, "minecraft:log2"); ++ ITEM_NAMES.put(163, "minecraft:acacia_stairs"); ++ ITEM_NAMES.put(164, "minecraft:dark_oak_stairs"); ++ ITEM_NAMES.put(170, "minecraft:hay_block"); ++ ITEM_NAMES.put(171, "minecraft:carpet"); ++ ITEM_NAMES.put(172, "minecraft:hardened_clay"); ++ ITEM_NAMES.put(173, "minecraft:coal_block"); ++ ITEM_NAMES.put(174, "minecraft:packed_ice"); ++ ITEM_NAMES.put(175, "minecraft:double_plant"); ++ ITEM_NAMES.put(256, "minecraft:iron_shovel"); ++ ITEM_NAMES.put(257, "minecraft:iron_pickaxe"); ++ ITEM_NAMES.put(258, "minecraft:iron_axe"); ++ ITEM_NAMES.put(259, "minecraft:flint_and_steel"); ++ ITEM_NAMES.put(260, "minecraft:apple"); ++ ITEM_NAMES.put(261, "minecraft:bow"); ++ ITEM_NAMES.put(262, "minecraft:arrow"); ++ ITEM_NAMES.put(263, "minecraft:coal"); ++ ITEM_NAMES.put(264, "minecraft:diamond"); ++ ITEM_NAMES.put(265, "minecraft:iron_ingot"); ++ ITEM_NAMES.put(266, "minecraft:gold_ingot"); ++ ITEM_NAMES.put(267, "minecraft:iron_sword"); ++ ITEM_NAMES.put(268, "minecraft:wooden_sword"); ++ ITEM_NAMES.put(269, "minecraft:wooden_shovel"); ++ ITEM_NAMES.put(270, "minecraft:wooden_pickaxe"); ++ ITEM_NAMES.put(271, "minecraft:wooden_axe"); ++ ITEM_NAMES.put(272, "minecraft:stone_sword"); ++ ITEM_NAMES.put(273, "minecraft:stone_shovel"); ++ ITEM_NAMES.put(274, "minecraft:stone_pickaxe"); ++ ITEM_NAMES.put(275, "minecraft:stone_axe"); ++ ITEM_NAMES.put(276, "minecraft:diamond_sword"); ++ ITEM_NAMES.put(277, "minecraft:diamond_shovel"); ++ ITEM_NAMES.put(278, "minecraft:diamond_pickaxe"); ++ ITEM_NAMES.put(279, "minecraft:diamond_axe"); ++ ITEM_NAMES.put(280, "minecraft:stick"); ++ ITEM_NAMES.put(281, "minecraft:bowl"); ++ ITEM_NAMES.put(282, "minecraft:mushroom_stew"); ++ ITEM_NAMES.put(283, "minecraft:golden_sword"); ++ ITEM_NAMES.put(284, "minecraft:golden_shovel"); ++ ITEM_NAMES.put(285, "minecraft:golden_pickaxe"); ++ ITEM_NAMES.put(286, "minecraft:golden_axe"); ++ ITEM_NAMES.put(287, "minecraft:string"); ++ ITEM_NAMES.put(288, "minecraft:feather"); ++ ITEM_NAMES.put(289, "minecraft:gunpowder"); ++ ITEM_NAMES.put(290, "minecraft:wooden_hoe"); ++ ITEM_NAMES.put(291, "minecraft:stone_hoe"); ++ ITEM_NAMES.put(292, "minecraft:iron_hoe"); ++ ITEM_NAMES.put(293, "minecraft:diamond_hoe"); ++ ITEM_NAMES.put(294, "minecraft:golden_hoe"); ++ ITEM_NAMES.put(295, "minecraft:wheat_seeds"); ++ ITEM_NAMES.put(296, "minecraft:wheat"); ++ ITEM_NAMES.put(297, "minecraft:bread"); ++ ITEM_NAMES.put(298, "minecraft:leather_helmet"); ++ ITEM_NAMES.put(299, "minecraft:leather_chestplate"); ++ ITEM_NAMES.put(300, "minecraft:leather_leggings"); ++ ITEM_NAMES.put(301, "minecraft:leather_boots"); ++ ITEM_NAMES.put(302, "minecraft:chainmail_helmet"); ++ ITEM_NAMES.put(303, "minecraft:chainmail_chestplate"); ++ ITEM_NAMES.put(304, "minecraft:chainmail_leggings"); ++ ITEM_NAMES.put(305, "minecraft:chainmail_boots"); ++ ITEM_NAMES.put(306, "minecraft:iron_helmet"); ++ ITEM_NAMES.put(307, "minecraft:iron_chestplate"); ++ ITEM_NAMES.put(308, "minecraft:iron_leggings"); ++ ITEM_NAMES.put(309, "minecraft:iron_boots"); ++ ITEM_NAMES.put(310, "minecraft:diamond_helmet"); ++ ITEM_NAMES.put(311, "minecraft:diamond_chestplate"); ++ ITEM_NAMES.put(312, "minecraft:diamond_leggings"); ++ ITEM_NAMES.put(313, "minecraft:diamond_boots"); ++ ITEM_NAMES.put(314, "minecraft:golden_helmet"); ++ ITEM_NAMES.put(315, "minecraft:golden_chestplate"); ++ ITEM_NAMES.put(316, "minecraft:golden_leggings"); ++ ITEM_NAMES.put(317, "minecraft:golden_boots"); ++ ITEM_NAMES.put(318, "minecraft:flint"); ++ ITEM_NAMES.put(319, "minecraft:porkchop"); ++ ITEM_NAMES.put(320, "minecraft:cooked_porkchop"); ++ ITEM_NAMES.put(321, "minecraft:painting"); ++ ITEM_NAMES.put(322, "minecraft:golden_apple"); ++ ITEM_NAMES.put(323, "minecraft:sign"); ++ ITEM_NAMES.put(324, "minecraft:wooden_door"); ++ ITEM_NAMES.put(325, "minecraft:bucket"); ++ ITEM_NAMES.put(326, "minecraft:water_bucket"); ++ ITEM_NAMES.put(327, "minecraft:lava_bucket"); ++ ITEM_NAMES.put(328, "minecraft:minecart"); ++ ITEM_NAMES.put(329, "minecraft:saddle"); ++ ITEM_NAMES.put(330, "minecraft:iron_door"); ++ ITEM_NAMES.put(331, "minecraft:redstone"); ++ ITEM_NAMES.put(332, "minecraft:snowball"); ++ ITEM_NAMES.put(333, "minecraft:boat"); ++ ITEM_NAMES.put(334, "minecraft:leather"); ++ ITEM_NAMES.put(335, "minecraft:milk_bucket"); ++ ITEM_NAMES.put(336, "minecraft:brick"); ++ ITEM_NAMES.put(337, "minecraft:clay_ball"); ++ ITEM_NAMES.put(338, "minecraft:reeds"); ++ ITEM_NAMES.put(339, "minecraft:paper"); ++ ITEM_NAMES.put(340, "minecraft:book"); ++ ITEM_NAMES.put(341, "minecraft:slime_ball"); ++ ITEM_NAMES.put(342, "minecraft:chest_minecart"); ++ ITEM_NAMES.put(343, "minecraft:furnace_minecart"); ++ ITEM_NAMES.put(344, "minecraft:egg"); ++ ITEM_NAMES.put(345, "minecraft:compass"); ++ ITEM_NAMES.put(346, "minecraft:fishing_rod"); ++ ITEM_NAMES.put(347, "minecraft:clock"); ++ ITEM_NAMES.put(348, "minecraft:glowstone_dust"); ++ ITEM_NAMES.put(349, "minecraft:fish"); ++ ITEM_NAMES.put(350, "minecraft:cooked_fish"); // Fix typo, the game never recognized cooked_fished ++ ITEM_NAMES.put(351, "minecraft:dye"); ++ ITEM_NAMES.put(352, "minecraft:bone"); ++ ITEM_NAMES.put(353, "minecraft:sugar"); ++ ITEM_NAMES.put(354, "minecraft:cake"); ++ ITEM_NAMES.put(355, "minecraft:bed"); ++ ITEM_NAMES.put(356, "minecraft:repeater"); ++ ITEM_NAMES.put(357, "minecraft:cookie"); ++ ITEM_NAMES.put(358, "minecraft:filled_map"); ++ ITEM_NAMES.put(359, "minecraft:shears"); ++ ITEM_NAMES.put(360, "minecraft:melon"); ++ ITEM_NAMES.put(361, "minecraft:pumpkin_seeds"); ++ ITEM_NAMES.put(362, "minecraft:melon_seeds"); ++ ITEM_NAMES.put(363, "minecraft:beef"); ++ ITEM_NAMES.put(364, "minecraft:cooked_beef"); ++ ITEM_NAMES.put(365, "minecraft:chicken"); ++ ITEM_NAMES.put(366, "minecraft:cooked_chicken"); ++ ITEM_NAMES.put(367, "minecraft:rotten_flesh"); ++ ITEM_NAMES.put(368, "minecraft:ender_pearl"); ++ ITEM_NAMES.put(369, "minecraft:blaze_rod"); ++ ITEM_NAMES.put(370, "minecraft:ghast_tear"); ++ ITEM_NAMES.put(371, "minecraft:gold_nugget"); ++ ITEM_NAMES.put(372, "minecraft:nether_wart"); ++ ITEM_NAMES.put(373, "minecraft:potion"); ++ ITEM_NAMES.put(374, "minecraft:glass_bottle"); ++ ITEM_NAMES.put(375, "minecraft:spider_eye"); ++ ITEM_NAMES.put(376, "minecraft:fermented_spider_eye"); ++ ITEM_NAMES.put(377, "minecraft:blaze_powder"); ++ ITEM_NAMES.put(378, "minecraft:magma_cream"); ++ ITEM_NAMES.put(379, "minecraft:brewing_stand"); ++ ITEM_NAMES.put(380, "minecraft:cauldron"); ++ ITEM_NAMES.put(381, "minecraft:ender_eye"); ++ ITEM_NAMES.put(382, "minecraft:speckled_melon"); ++ ITEM_NAMES.put(383, "minecraft:spawn_egg"); ++ ITEM_NAMES.put(384, "minecraft:experience_bottle"); ++ ITEM_NAMES.put(385, "minecraft:fire_charge"); ++ ITEM_NAMES.put(386, "minecraft:writable_book"); ++ ITEM_NAMES.put(387, "minecraft:written_book"); ++ ITEM_NAMES.put(388, "minecraft:emerald"); ++ ITEM_NAMES.put(389, "minecraft:item_frame"); ++ ITEM_NAMES.put(390, "minecraft:flower_pot"); ++ ITEM_NAMES.put(391, "minecraft:carrot"); ++ ITEM_NAMES.put(392, "minecraft:potato"); ++ ITEM_NAMES.put(393, "minecraft:baked_potato"); ++ ITEM_NAMES.put(394, "minecraft:poisonous_potato"); ++ ITEM_NAMES.put(395, "minecraft:map"); ++ ITEM_NAMES.put(396, "minecraft:golden_carrot"); ++ ITEM_NAMES.put(397, "minecraft:skull"); ++ ITEM_NAMES.put(398, "minecraft:carrot_on_a_stick"); ++ ITEM_NAMES.put(399, "minecraft:nether_star"); ++ ITEM_NAMES.put(400, "minecraft:pumpkin_pie"); ++ ITEM_NAMES.put(401, "minecraft:fireworks"); ++ ITEM_NAMES.put(402, "minecraft:firework_charge"); ++ ITEM_NAMES.put(403, "minecraft:enchanted_book"); ++ ITEM_NAMES.put(404, "minecraft:comparator"); ++ ITEM_NAMES.put(405, "minecraft:netherbrick"); ++ ITEM_NAMES.put(406, "minecraft:quartz"); ++ ITEM_NAMES.put(407, "minecraft:tnt_minecart"); ++ ITEM_NAMES.put(408, "minecraft:hopper_minecart"); ++ ITEM_NAMES.put(417, "minecraft:iron_horse_armor"); ++ ITEM_NAMES.put(418, "minecraft:golden_horse_armor"); ++ ITEM_NAMES.put(419, "minecraft:diamond_horse_armor"); ++ ITEM_NAMES.put(420, "minecraft:lead"); ++ ITEM_NAMES.put(421, "minecraft:name_tag"); ++ ITEM_NAMES.put(422, "minecraft:command_block_minecart"); ++ ITEM_NAMES.put(2256, "minecraft:record_13"); ++ ITEM_NAMES.put(2257, "minecraft:record_cat"); ++ ITEM_NAMES.put(2258, "minecraft:record_blocks"); ++ ITEM_NAMES.put(2259, "minecraft:record_chirp"); ++ ITEM_NAMES.put(2260, "minecraft:record_far"); ++ ITEM_NAMES.put(2261, "minecraft:record_mall"); ++ ITEM_NAMES.put(2262, "minecraft:record_mellohi"); ++ ITEM_NAMES.put(2263, "minecraft:record_stal"); ++ ITEM_NAMES.put(2264, "minecraft:record_strad"); ++ ITEM_NAMES.put(2265, "minecraft:record_ward"); ++ ITEM_NAMES.put(2266, "minecraft:record_11"); ++ ITEM_NAMES.put(2267, "minecraft:record_wait"); ++ // https://github.com/starlis/empirecraft/commit/2da59d1901407fc0c135ef0458c0fe9b016570b3 ++ // It's likely that this is a result of old CB/Spigot behavior still writing ids into items as ints. ++ // These ids do not appear to be used by regular MC anyways, so I do not see the harm of porting it here. ++ // Extras can be added if needed ++ String[] extra = new String[4_000]; ++ // EMC start ++ extra[409] = "minecraft:prismarine_shard"; ++ extra[410] = "minecraft:prismarine_crystals"; ++ extra[411] = "minecraft:rabbit"; ++ extra[412] = "minecraft:cooked_rabbit"; ++ extra[413] = "minecraft:rabbit_stew"; ++ extra[414] = "minecraft:rabbit_foot"; ++ extra[415] = "minecraft:rabbit_hide"; ++ extra[416] = "minecraft:armor_stand"; ++ extra[423] = "minecraft:mutton"; ++ extra[424] = "minecraft:cooked_mutton"; ++ extra[425] = "minecraft:banner"; ++ extra[426] = "minecraft:end_crystal"; ++ extra[427] = "minecraft:spruce_door"; ++ extra[428] = "minecraft:birch_door"; ++ extra[429] = "minecraft:jungle_door"; ++ extra[430] = "minecraft:acacia_door"; ++ extra[431] = "minecraft:dark_oak_door"; ++ extra[432] = "minecraft:chorus_fruit"; ++ extra[433] = "minecraft:chorus_fruit_popped"; ++ extra[434] = "minecraft:beetroot"; ++ extra[435] = "minecraft:beetroot_seeds"; ++ extra[436] = "minecraft:beetroot_soup"; ++ extra[437] = "minecraft:dragon_breath"; ++ extra[438] = "minecraft:splash_potion"; ++ extra[439] = "minecraft:spectral_arrow"; ++ extra[440] = "minecraft:tipped_arrow"; ++ extra[441] = "minecraft:lingering_potion"; ++ extra[442] = "minecraft:shield"; ++ extra[443] = "minecraft:elytra"; ++ extra[444] = "minecraft:spruce_boat"; ++ extra[445] = "minecraft:birch_boat"; ++ extra[446] = "minecraft:jungle_boat"; ++ extra[447] = "minecraft:acacia_boat"; ++ extra[448] = "minecraft:dark_oak_boat"; ++ extra[449] = "minecraft:totem_of_undying"; ++ extra[450] = "minecraft:shulker_shell"; ++ extra[452] = "minecraft:iron_nugget"; ++ extra[453] = "minecraft:knowledge_book"; ++ // EMC end ++ ++ // dump extra into map ++ for (int i = 0; i < extra.length; ++i) { ++ if (extra[i] != null) { ++ ITEM_NAMES.put(i, extra[i]); ++ } ++ } ++ } ++ ++ private static final String[] POTION_NAMES = new String[128]; ++ static { ++ POTION_NAMES[0] = "minecraft:water"; ++ POTION_NAMES[1] = "minecraft:regeneration"; ++ POTION_NAMES[2] = "minecraft:swiftness"; ++ POTION_NAMES[3] = "minecraft:fire_resistance"; ++ POTION_NAMES[4] = "minecraft:poison"; ++ POTION_NAMES[5] = "minecraft:healing"; ++ POTION_NAMES[6] = "minecraft:night_vision"; ++ POTION_NAMES[7] = null; ++ POTION_NAMES[8] = "minecraft:weakness"; ++ POTION_NAMES[9] = "minecraft:strength"; ++ POTION_NAMES[10] = "minecraft:slowness"; ++ POTION_NAMES[11] = "minecraft:leaping"; ++ POTION_NAMES[12] = "minecraft:harming"; ++ POTION_NAMES[13] = "minecraft:water_breathing"; ++ POTION_NAMES[14] = "minecraft:invisibility"; ++ POTION_NAMES[15] = null; ++ POTION_NAMES[16] = "minecraft:awkward"; ++ POTION_NAMES[17] = "minecraft:regeneration"; ++ POTION_NAMES[18] = "minecraft:swiftness"; ++ POTION_NAMES[19] = "minecraft:fire_resistance"; ++ POTION_NAMES[20] = "minecraft:poison"; ++ POTION_NAMES[21] = "minecraft:healing"; ++ POTION_NAMES[22] = "minecraft:night_vision"; ++ POTION_NAMES[23] = null; ++ POTION_NAMES[24] = "minecraft:weakness"; ++ POTION_NAMES[25] = "minecraft:strength"; ++ POTION_NAMES[26] = "minecraft:slowness"; ++ POTION_NAMES[27] = "minecraft:leaping"; ++ POTION_NAMES[28] = "minecraft:harming"; ++ POTION_NAMES[29] = "minecraft:water_breathing"; ++ POTION_NAMES[30] = "minecraft:invisibility"; ++ POTION_NAMES[31] = null; ++ POTION_NAMES[32] = "minecraft:thick"; ++ POTION_NAMES[33] = "minecraft:strong_regeneration"; ++ POTION_NAMES[34] = "minecraft:strong_swiftness"; ++ POTION_NAMES[35] = "minecraft:fire_resistance"; ++ POTION_NAMES[36] = "minecraft:strong_poison"; ++ POTION_NAMES[37] = "minecraft:strong_healing"; ++ POTION_NAMES[38] = "minecraft:night_vision"; ++ POTION_NAMES[39] = null; ++ POTION_NAMES[40] = "minecraft:weakness"; ++ POTION_NAMES[41] = "minecraft:strong_strength"; ++ POTION_NAMES[42] = "minecraft:slowness"; ++ POTION_NAMES[43] = "minecraft:strong_leaping"; ++ POTION_NAMES[44] = "minecraft:strong_harming"; ++ POTION_NAMES[45] = "minecraft:water_breathing"; ++ POTION_NAMES[46] = "minecraft:invisibility"; ++ POTION_NAMES[47] = null; ++ POTION_NAMES[48] = null; ++ POTION_NAMES[49] = "minecraft:strong_regeneration"; ++ POTION_NAMES[50] = "minecraft:strong_swiftness"; ++ POTION_NAMES[51] = "minecraft:fire_resistance"; ++ POTION_NAMES[52] = "minecraft:strong_poison"; ++ POTION_NAMES[53] = "minecraft:strong_healing"; ++ POTION_NAMES[54] = "minecraft:night_vision"; ++ POTION_NAMES[55] = null; ++ POTION_NAMES[56] = "minecraft:weakness"; ++ POTION_NAMES[57] = "minecraft:strong_strength"; ++ POTION_NAMES[58] = "minecraft:slowness"; ++ POTION_NAMES[59] = "minecraft:strong_leaping"; ++ POTION_NAMES[60] = "minecraft:strong_harming"; ++ POTION_NAMES[61] = "minecraft:water_breathing"; ++ POTION_NAMES[62] = "minecraft:invisibility"; ++ POTION_NAMES[63] = null; ++ POTION_NAMES[64] = "minecraft:mundane"; ++ POTION_NAMES[65] = "minecraft:long_regeneration"; ++ POTION_NAMES[66] = "minecraft:long_swiftness"; ++ POTION_NAMES[67] = "minecraft:long_fire_resistance"; ++ POTION_NAMES[68] = "minecraft:long_poison"; ++ POTION_NAMES[69] = "minecraft:healing"; ++ POTION_NAMES[70] = "minecraft:long_night_vision"; ++ POTION_NAMES[71] = null; ++ POTION_NAMES[72] = "minecraft:long_weakness"; ++ POTION_NAMES[73] = "minecraft:long_strength"; ++ POTION_NAMES[74] = "minecraft:long_slowness"; ++ POTION_NAMES[75] = "minecraft:long_leaping"; ++ POTION_NAMES[76] = "minecraft:harming"; ++ POTION_NAMES[77] = "minecraft:long_water_breathing"; ++ POTION_NAMES[78] = "minecraft:long_invisibility"; ++ POTION_NAMES[79] = null; ++ POTION_NAMES[80] = "minecraft:awkward"; ++ POTION_NAMES[81] = "minecraft:long_regeneration"; ++ POTION_NAMES[82] = "minecraft:long_swiftness"; ++ POTION_NAMES[83] = "minecraft:long_fire_resistance"; ++ POTION_NAMES[84] = "minecraft:long_poison"; ++ POTION_NAMES[85] = "minecraft:healing"; ++ POTION_NAMES[86] = "minecraft:long_night_vision"; ++ POTION_NAMES[87] = null; ++ POTION_NAMES[88] = "minecraft:long_weakness"; ++ POTION_NAMES[89] = "minecraft:long_strength"; ++ POTION_NAMES[90] = "minecraft:long_slowness"; ++ POTION_NAMES[91] = "minecraft:long_leaping"; ++ POTION_NAMES[92] = "minecraft:harming"; ++ POTION_NAMES[93] = "minecraft:long_water_breathing"; ++ POTION_NAMES[94] = "minecraft:long_invisibility"; ++ POTION_NAMES[95] = null; ++ POTION_NAMES[96] = "minecraft:thick"; ++ POTION_NAMES[97] = "minecraft:regeneration"; ++ POTION_NAMES[98] = "minecraft:swiftness"; ++ POTION_NAMES[99] = "minecraft:long_fire_resistance"; ++ POTION_NAMES[100] = "minecraft:poison"; ++ POTION_NAMES[101] = "minecraft:strong_healing"; ++ POTION_NAMES[102] = "minecraft:long_night_vision"; ++ POTION_NAMES[103] = null; ++ POTION_NAMES[104] = "minecraft:long_weakness"; ++ POTION_NAMES[105] = "minecraft:strength"; ++ POTION_NAMES[106] = "minecraft:long_slowness"; ++ POTION_NAMES[107] = "minecraft:leaping"; ++ POTION_NAMES[108] = "minecraft:strong_harming"; ++ POTION_NAMES[109] = "minecraft:long_water_breathing"; ++ POTION_NAMES[110] = "minecraft:long_invisibility"; ++ POTION_NAMES[111] = null; ++ POTION_NAMES[112] = null; ++ POTION_NAMES[113] = "minecraft:regeneration"; ++ POTION_NAMES[114] = "minecraft:swiftness"; ++ POTION_NAMES[115] = "minecraft:long_fire_resistance"; ++ POTION_NAMES[116] = "minecraft:poison"; ++ POTION_NAMES[117] = "minecraft:strong_healing"; ++ POTION_NAMES[118] = "minecraft:long_night_vision"; ++ POTION_NAMES[119] = null; ++ POTION_NAMES[120] = "minecraft:long_weakness"; ++ POTION_NAMES[121] = "minecraft:strength"; ++ POTION_NAMES[122] = "minecraft:long_slowness"; ++ POTION_NAMES[123] = "minecraft:leaping"; ++ POTION_NAMES[124] = "minecraft:strong_harming"; ++ POTION_NAMES[125] = "minecraft:long_water_breathing"; ++ POTION_NAMES[126] = "minecraft:long_invisibility"; ++ POTION_NAMES[127] = null; ++ } ++ ++ // ret is nullable, you are supposed to log when it does not exist, NOT HIDE IT! ++ public static String getNameFromId(final int id) { ++ return ITEM_NAMES.get(id); ++ } ++ ++ public static String getPotionNameFromId(final short id) { ++ return POTION_NAMES[id & 127]; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5008c6d28b7f9b730bfaf257a264edcb45c78487 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java +@@ -0,0 +1,79 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.helpers; ++ ++public final class HelperSpawnEggNameV105 { ++ ++ private static final String[] ID_TO_STRING = new String[256]; ++ static { ++ ID_TO_STRING[1] = "Item"; ++ ID_TO_STRING[2] = "XPOrb"; ++ ID_TO_STRING[7] = "ThrownEgg"; ++ ID_TO_STRING[8] = "LeashKnot"; ++ ID_TO_STRING[9] = "Painting"; ++ ID_TO_STRING[10] = "Arrow"; ++ ID_TO_STRING[11] = "Snowball"; ++ ID_TO_STRING[12] = "Fireball"; ++ ID_TO_STRING[13] = "SmallFireball"; ++ ID_TO_STRING[14] = "ThrownEnderpearl"; ++ ID_TO_STRING[15] = "EyeOfEnderSignal"; ++ ID_TO_STRING[16] = "ThrownPotion"; ++ ID_TO_STRING[17] = "ThrownExpBottle"; ++ ID_TO_STRING[18] = "ItemFrame"; ++ ID_TO_STRING[19] = "WitherSkull"; ++ ID_TO_STRING[20] = "PrimedTnt"; ++ ID_TO_STRING[21] = "FallingSand"; ++ ID_TO_STRING[22] = "FireworksRocketEntity"; ++ ID_TO_STRING[23] = "TippedArrow"; ++ ID_TO_STRING[24] = "SpectralArrow"; ++ ID_TO_STRING[25] = "ShulkerBullet"; ++ ID_TO_STRING[26] = "DragonFireball"; ++ ID_TO_STRING[30] = "ArmorStand"; ++ ID_TO_STRING[41] = "Boat"; ++ ID_TO_STRING[42] = "MinecartRideable"; ++ ID_TO_STRING[43] = "MinecartChest"; ++ ID_TO_STRING[44] = "MinecartFurnace"; ++ ID_TO_STRING[45] = "MinecartTNT"; ++ ID_TO_STRING[46] = "MinecartHopper"; ++ ID_TO_STRING[47] = "MinecartSpawner"; ++ ID_TO_STRING[40] = "MinecartCommandBlock"; ++ ID_TO_STRING[48] = "Mob"; ++ ID_TO_STRING[49] = "Monster"; ++ ID_TO_STRING[50] = "Creeper"; ++ ID_TO_STRING[51] = "Skeleton"; ++ ID_TO_STRING[52] = "Spider"; ++ ID_TO_STRING[53] = "Giant"; ++ ID_TO_STRING[54] = "Zombie"; ++ ID_TO_STRING[55] = "Slime"; ++ ID_TO_STRING[56] = "Ghast"; ++ ID_TO_STRING[57] = "PigZombie"; ++ ID_TO_STRING[58] = "Enderman"; ++ ID_TO_STRING[59] = "CaveSpider"; ++ ID_TO_STRING[60] = "Silverfish"; ++ ID_TO_STRING[61] = "Blaze"; ++ ID_TO_STRING[62] = "LavaSlime"; ++ ID_TO_STRING[63] = "EnderDragon"; ++ ID_TO_STRING[64] = "WitherBoss"; ++ ID_TO_STRING[65] = "Bat"; ++ ID_TO_STRING[66] = "Witch"; ++ ID_TO_STRING[67] = "Endermite"; ++ ID_TO_STRING[68] = "Guardian"; ++ ID_TO_STRING[69] = "Shulker"; ++ ID_TO_STRING[90] = "Pig"; ++ ID_TO_STRING[91] = "Sheep"; ++ ID_TO_STRING[92] = "Cow"; ++ ID_TO_STRING[93] = "Chicken"; ++ ID_TO_STRING[94] = "Squid"; ++ ID_TO_STRING[95] = "Wolf"; ++ ID_TO_STRING[96] = "MushroomCow"; ++ ID_TO_STRING[97] = "SnowMan"; ++ ID_TO_STRING[98] = "Ozelot"; ++ ID_TO_STRING[99] = "VillagerGolem"; ++ ID_TO_STRING[100] = "EntityHorse"; ++ ID_TO_STRING[101] = "Rabbit"; ++ ID_TO_STRING[120] = "Villager"; ++ ID_TO_STRING[200] = "EnderCrystal"; ++ } ++ ++ public static String getSpawnNameFromId(final short id) { ++ return ID_TO_STRING[id & 255]; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..051b44ea226db849faf821567f0b5321d360fe45 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java +@@ -0,0 +1,89 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.helpers; ++ ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.function.Function; ++ ++public final class RenameHelper { ++ ++ // assumes no two or more entries are renamed to a single value, otherwise result will be only one of them will win ++ // and there is no defined winner in such a case ++ public static void renameKeys(final MapType data, final Function renamer) { ++ if (data == null) { ++ return; ++ } ++ ++ List newKeys = null; ++ List newValues = null; ++ boolean needsRename = false; ++ for (final String key : data.keys()) { ++ final String renamed = renamer.apply(key); ++ if (renamed != null) { ++ newKeys = new ArrayList<>(); ++ newValues = new ArrayList<>(); ++ newValues.add(data.getGeneric(key)); ++ newKeys.add(renamed); ++ data.remove(key); ++ needsRename = true; ++ break; ++ } ++ } ++ ++ if (!needsRename) { ++ return; ++ } ++ ++ for (final String key : new ArrayList<>(data.keys())) { ++ final String renamed = renamer.apply(key); ++ ++ if (renamed != null) { ++ newValues.add(data.getGeneric(key)); ++ newKeys.add(renamed); ++ data.remove(key); ++ } ++ } ++ ++ // insert new keys ++ for (int i = 0, len = newKeys.size(); i < len; ++i) { ++ final String key = newKeys.get(i); ++ final Object value = newValues.get(i); ++ ++ data.setGeneric(key, value); ++ } ++ } ++ ++ // Clobbers anything in toKey if fromKey exists ++ public static void renameSingle(final MapType data, final String fromKey, final String toKey) { ++ if (data == null) { ++ return; ++ } ++ ++ final Object value = data.getGeneric(fromKey); ++ if (value != null) { ++ data.remove(fromKey); ++ data.setGeneric(toKey, value); ++ } ++ } ++ ++ public static void renameString(final MapType data, final String key, final Function renamer) { ++ if (data == null) { ++ return; ++ } ++ ++ final String value = data.getString(key); ++ if (value == null) { ++ return; ++ } ++ ++ final String renamed = renamer.apply(value); ++ if (renamed == null) { ++ return; ++ } ++ ++ data.setString(key, renamed); ++ } ++ ++ private RenameHelper() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..94569f0ccff0d3a09eafd4ba73572d9db0a0ac5b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.itemname; ++ ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import java.util.function.Function; ++ ++public final class ConverterAbstractItemRename { ++ ++ private ConverterAbstractItemRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.ITEM_NAME, renamer); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java +new file mode 100644 +index 0000000000000000000000000000000000000000..21176b8b96be6cb93d3dc1a74ae9f53f1ad4740c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java +@@ -0,0 +1,460 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.itemstack; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.mojang.logging.LogUtils; ++import org.slf4j.Logger; ++import java.util.Arrays; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++ ++public final class ConverterFlattenItemStack extends DataConverter, MapType> { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ // Map of "id.damage" -> "flattened id" ++ private static final Map FLATTEN_MAP = new HashMap<>(); ++ static { ++ FLATTEN_MAP.put("minecraft:stone.0", "minecraft:stone"); ++ FLATTEN_MAP.put("minecraft:stone.1", "minecraft:granite"); ++ FLATTEN_MAP.put("minecraft:stone.2", "minecraft:polished_granite"); ++ FLATTEN_MAP.put("minecraft:stone.3", "minecraft:diorite"); ++ FLATTEN_MAP.put("minecraft:stone.4", "minecraft:polished_diorite"); ++ FLATTEN_MAP.put("minecraft:stone.5", "minecraft:andesite"); ++ FLATTEN_MAP.put("minecraft:stone.6", "minecraft:polished_andesite"); ++ FLATTEN_MAP.put("minecraft:dirt.0", "minecraft:dirt"); ++ FLATTEN_MAP.put("minecraft:dirt.1", "minecraft:coarse_dirt"); ++ FLATTEN_MAP.put("minecraft:dirt.2", "minecraft:podzol"); ++ FLATTEN_MAP.put("minecraft:leaves.0", "minecraft:oak_leaves"); ++ FLATTEN_MAP.put("minecraft:leaves.1", "minecraft:spruce_leaves"); ++ FLATTEN_MAP.put("minecraft:leaves.2", "minecraft:birch_leaves"); ++ FLATTEN_MAP.put("minecraft:leaves.3", "minecraft:jungle_leaves"); ++ FLATTEN_MAP.put("minecraft:leaves2.0", "minecraft:acacia_leaves"); ++ FLATTEN_MAP.put("minecraft:leaves2.1", "minecraft:dark_oak_leaves"); ++ FLATTEN_MAP.put("minecraft:log.0", "minecraft:oak_log"); ++ FLATTEN_MAP.put("minecraft:log.1", "minecraft:spruce_log"); ++ FLATTEN_MAP.put("minecraft:log.2", "minecraft:birch_log"); ++ FLATTEN_MAP.put("minecraft:log.3", "minecraft:jungle_log"); ++ FLATTEN_MAP.put("minecraft:log2.0", "minecraft:acacia_log"); ++ FLATTEN_MAP.put("minecraft:log2.1", "minecraft:dark_oak_log"); ++ FLATTEN_MAP.put("minecraft:sapling.0", "minecraft:oak_sapling"); ++ FLATTEN_MAP.put("minecraft:sapling.1", "minecraft:spruce_sapling"); ++ FLATTEN_MAP.put("minecraft:sapling.2", "minecraft:birch_sapling"); ++ FLATTEN_MAP.put("minecraft:sapling.3", "minecraft:jungle_sapling"); ++ FLATTEN_MAP.put("minecraft:sapling.4", "minecraft:acacia_sapling"); ++ FLATTEN_MAP.put("minecraft:sapling.5", "minecraft:dark_oak_sapling"); ++ FLATTEN_MAP.put("minecraft:planks.0", "minecraft:oak_planks"); ++ FLATTEN_MAP.put("minecraft:planks.1", "minecraft:spruce_planks"); ++ FLATTEN_MAP.put("minecraft:planks.2", "minecraft:birch_planks"); ++ FLATTEN_MAP.put("minecraft:planks.3", "minecraft:jungle_planks"); ++ FLATTEN_MAP.put("minecraft:planks.4", "minecraft:acacia_planks"); ++ FLATTEN_MAP.put("minecraft:planks.5", "minecraft:dark_oak_planks"); ++ FLATTEN_MAP.put("minecraft:sand.0", "minecraft:sand"); ++ FLATTEN_MAP.put("minecraft:sand.1", "minecraft:red_sand"); ++ FLATTEN_MAP.put("minecraft:quartz_block.0", "minecraft:quartz_block"); ++ FLATTEN_MAP.put("minecraft:quartz_block.1", "minecraft:chiseled_quartz_block"); ++ FLATTEN_MAP.put("minecraft:quartz_block.2", "minecraft:quartz_pillar"); ++ FLATTEN_MAP.put("minecraft:anvil.0", "minecraft:anvil"); ++ FLATTEN_MAP.put("minecraft:anvil.1", "minecraft:chipped_anvil"); ++ FLATTEN_MAP.put("minecraft:anvil.2", "minecraft:damaged_anvil"); ++ FLATTEN_MAP.put("minecraft:wool.0", "minecraft:white_wool"); ++ FLATTEN_MAP.put("minecraft:wool.1", "minecraft:orange_wool"); ++ FLATTEN_MAP.put("minecraft:wool.2", "minecraft:magenta_wool"); ++ FLATTEN_MAP.put("minecraft:wool.3", "minecraft:light_blue_wool"); ++ FLATTEN_MAP.put("minecraft:wool.4", "minecraft:yellow_wool"); ++ FLATTEN_MAP.put("minecraft:wool.5", "minecraft:lime_wool"); ++ FLATTEN_MAP.put("minecraft:wool.6", "minecraft:pink_wool"); ++ FLATTEN_MAP.put("minecraft:wool.7", "minecraft:gray_wool"); ++ FLATTEN_MAP.put("minecraft:wool.8", "minecraft:light_gray_wool"); ++ FLATTEN_MAP.put("minecraft:wool.9", "minecraft:cyan_wool"); ++ FLATTEN_MAP.put("minecraft:wool.10", "minecraft:purple_wool"); ++ FLATTEN_MAP.put("minecraft:wool.11", "minecraft:blue_wool"); ++ FLATTEN_MAP.put("minecraft:wool.12", "minecraft:brown_wool"); ++ FLATTEN_MAP.put("minecraft:wool.13", "minecraft:green_wool"); ++ FLATTEN_MAP.put("minecraft:wool.14", "minecraft:red_wool"); ++ FLATTEN_MAP.put("minecraft:wool.15", "minecraft:black_wool"); ++ FLATTEN_MAP.put("minecraft:carpet.0", "minecraft:white_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.1", "minecraft:orange_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.2", "minecraft:magenta_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.3", "minecraft:light_blue_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.4", "minecraft:yellow_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.5", "minecraft:lime_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.6", "minecraft:pink_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.7", "minecraft:gray_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.8", "minecraft:light_gray_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.9", "minecraft:cyan_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.10", "minecraft:purple_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.11", "minecraft:blue_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.12", "minecraft:brown_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.13", "minecraft:green_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.14", "minecraft:red_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.15", "minecraft:black_carpet"); ++ FLATTEN_MAP.put("minecraft:hardened_clay.0", "minecraft:terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.0", "minecraft:white_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.1", "minecraft:orange_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.2", "minecraft:magenta_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.3", "minecraft:light_blue_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.4", "minecraft:yellow_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.5", "minecraft:lime_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.6", "minecraft:pink_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.7", "minecraft:gray_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.8", "minecraft:light_gray_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.9", "minecraft:cyan_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.10", "minecraft:purple_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.11", "minecraft:blue_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.12", "minecraft:brown_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.13", "minecraft:green_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.14", "minecraft:red_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.15", "minecraft:black_terracotta"); ++ FLATTEN_MAP.put("minecraft:silver_glazed_terracotta.0", "minecraft:light_gray_glazed_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_glass.0", "minecraft:white_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.1", "minecraft:orange_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.2", "minecraft:magenta_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.3", "minecraft:light_blue_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.4", "minecraft:yellow_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.5", "minecraft:lime_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.6", "minecraft:pink_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.7", "minecraft:gray_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.8", "minecraft:light_gray_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.9", "minecraft:cyan_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.10", "minecraft:purple_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.11", "minecraft:blue_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.12", "minecraft:brown_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.13", "minecraft:green_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.14", "minecraft:red_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.15", "minecraft:black_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.0", "minecraft:white_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.1", "minecraft:orange_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.2", "minecraft:magenta_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.3", "minecraft:light_blue_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.4", "minecraft:yellow_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.5", "minecraft:lime_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.6", "minecraft:pink_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.7", "minecraft:gray_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.8", "minecraft:light_gray_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.9", "minecraft:cyan_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.10", "minecraft:purple_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.11", "minecraft:blue_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.12", "minecraft:brown_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.13", "minecraft:green_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.14", "minecraft:red_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.15", "minecraft:black_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:prismarine.0", "minecraft:prismarine"); ++ FLATTEN_MAP.put("minecraft:prismarine.1", "minecraft:prismarine_bricks"); ++ FLATTEN_MAP.put("minecraft:prismarine.2", "minecraft:dark_prismarine"); ++ FLATTEN_MAP.put("minecraft:concrete.0", "minecraft:white_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.1", "minecraft:orange_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.2", "minecraft:magenta_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.3", "minecraft:light_blue_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.4", "minecraft:yellow_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.5", "minecraft:lime_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.6", "minecraft:pink_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.7", "minecraft:gray_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.8", "minecraft:light_gray_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.9", "minecraft:cyan_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.10", "minecraft:purple_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.11", "minecraft:blue_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.12", "minecraft:brown_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.13", "minecraft:green_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.14", "minecraft:red_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.15", "minecraft:black_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.0", "minecraft:white_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.1", "minecraft:orange_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.2", "minecraft:magenta_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.3", "minecraft:light_blue_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.4", "minecraft:yellow_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.5", "minecraft:lime_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.6", "minecraft:pink_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.7", "minecraft:gray_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.8", "minecraft:light_gray_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.9", "minecraft:cyan_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.10", "minecraft:purple_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.11", "minecraft:blue_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.12", "minecraft:brown_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.13", "minecraft:green_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.14", "minecraft:red_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.15", "minecraft:black_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:cobblestone_wall.0", "minecraft:cobblestone_wall"); ++ FLATTEN_MAP.put("minecraft:cobblestone_wall.1", "minecraft:mossy_cobblestone_wall"); ++ FLATTEN_MAP.put("minecraft:sandstone.0", "minecraft:sandstone"); ++ FLATTEN_MAP.put("minecraft:sandstone.1", "minecraft:chiseled_sandstone"); ++ FLATTEN_MAP.put("minecraft:sandstone.2", "minecraft:cut_sandstone"); ++ FLATTEN_MAP.put("minecraft:red_sandstone.0", "minecraft:red_sandstone"); ++ FLATTEN_MAP.put("minecraft:red_sandstone.1", "minecraft:chiseled_red_sandstone"); ++ FLATTEN_MAP.put("minecraft:red_sandstone.2", "minecraft:cut_red_sandstone"); ++ FLATTEN_MAP.put("minecraft:stonebrick.0", "minecraft:stone_bricks"); ++ FLATTEN_MAP.put("minecraft:stonebrick.1", "minecraft:mossy_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:stonebrick.2", "minecraft:cracked_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:stonebrick.3", "minecraft:chiseled_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:monster_egg.0", "minecraft:infested_stone"); ++ FLATTEN_MAP.put("minecraft:monster_egg.1", "minecraft:infested_cobblestone"); ++ FLATTEN_MAP.put("minecraft:monster_egg.2", "minecraft:infested_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:monster_egg.3", "minecraft:infested_mossy_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:monster_egg.4", "minecraft:infested_cracked_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:monster_egg.5", "minecraft:infested_chiseled_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:yellow_flower.0", "minecraft:dandelion"); ++ FLATTEN_MAP.put("minecraft:red_flower.0", "minecraft:poppy"); ++ FLATTEN_MAP.put("minecraft:red_flower.1", "minecraft:blue_orchid"); ++ FLATTEN_MAP.put("minecraft:red_flower.2", "minecraft:allium"); ++ FLATTEN_MAP.put("minecraft:red_flower.3", "minecraft:azure_bluet"); ++ FLATTEN_MAP.put("minecraft:red_flower.4", "minecraft:red_tulip"); ++ FLATTEN_MAP.put("minecraft:red_flower.5", "minecraft:orange_tulip"); ++ FLATTEN_MAP.put("minecraft:red_flower.6", "minecraft:white_tulip"); ++ FLATTEN_MAP.put("minecraft:red_flower.7", "minecraft:pink_tulip"); ++ FLATTEN_MAP.put("minecraft:red_flower.8", "minecraft:oxeye_daisy"); ++ FLATTEN_MAP.put("minecraft:double_plant.0", "minecraft:sunflower"); ++ FLATTEN_MAP.put("minecraft:double_plant.1", "minecraft:lilac"); ++ FLATTEN_MAP.put("minecraft:double_plant.2", "minecraft:tall_grass"); ++ FLATTEN_MAP.put("minecraft:double_plant.3", "minecraft:large_fern"); ++ FLATTEN_MAP.put("minecraft:double_plant.4", "minecraft:rose_bush"); ++ FLATTEN_MAP.put("minecraft:double_plant.5", "minecraft:peony"); ++ FLATTEN_MAP.put("minecraft:deadbush.0", "minecraft:dead_bush"); ++ FLATTEN_MAP.put("minecraft:tallgrass.0", "minecraft:dead_bush"); ++ FLATTEN_MAP.put("minecraft:tallgrass.1", "minecraft:grass"); ++ FLATTEN_MAP.put("minecraft:tallgrass.2", "minecraft:fern"); ++ FLATTEN_MAP.put("minecraft:sponge.0", "minecraft:sponge"); ++ FLATTEN_MAP.put("minecraft:sponge.1", "minecraft:wet_sponge"); ++ FLATTEN_MAP.put("minecraft:purpur_slab.0", "minecraft:purpur_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.0", "minecraft:stone_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.1", "minecraft:sandstone_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.2", "minecraft:petrified_oak_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.3", "minecraft:cobblestone_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.4", "minecraft:brick_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.5", "minecraft:stone_brick_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.6", "minecraft:nether_brick_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.7", "minecraft:quartz_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab2.0", "minecraft:red_sandstone_slab"); ++ FLATTEN_MAP.put("minecraft:wooden_slab.0", "minecraft:oak_slab"); ++ FLATTEN_MAP.put("minecraft:wooden_slab.1", "minecraft:spruce_slab"); ++ FLATTEN_MAP.put("minecraft:wooden_slab.2", "minecraft:birch_slab"); ++ FLATTEN_MAP.put("minecraft:wooden_slab.3", "minecraft:jungle_slab"); ++ FLATTEN_MAP.put("minecraft:wooden_slab.4", "minecraft:acacia_slab"); ++ FLATTEN_MAP.put("minecraft:wooden_slab.5", "minecraft:dark_oak_slab"); ++ FLATTEN_MAP.put("minecraft:coal.0", "minecraft:coal"); ++ FLATTEN_MAP.put("minecraft:coal.1", "minecraft:charcoal"); ++ FLATTEN_MAP.put("minecraft:fish.0", "minecraft:cod"); ++ FLATTEN_MAP.put("minecraft:fish.1", "minecraft:salmon"); ++ FLATTEN_MAP.put("minecraft:fish.2", "minecraft:clownfish"); ++ FLATTEN_MAP.put("minecraft:fish.3", "minecraft:pufferfish"); ++ FLATTEN_MAP.put("minecraft:cooked_fish.0", "minecraft:cooked_cod"); ++ FLATTEN_MAP.put("minecraft:cooked_fish.1", "minecraft:cooked_salmon"); ++ FLATTEN_MAP.put("minecraft:skull.0", "minecraft:skeleton_skull"); ++ FLATTEN_MAP.put("minecraft:skull.1", "minecraft:wither_skeleton_skull"); ++ FLATTEN_MAP.put("minecraft:skull.2", "minecraft:zombie_head"); ++ FLATTEN_MAP.put("minecraft:skull.3", "minecraft:player_head"); ++ FLATTEN_MAP.put("minecraft:skull.4", "minecraft:creeper_head"); ++ FLATTEN_MAP.put("minecraft:skull.5", "minecraft:dragon_head"); ++ FLATTEN_MAP.put("minecraft:golden_apple.0", "minecraft:golden_apple"); ++ FLATTEN_MAP.put("minecraft:golden_apple.1", "minecraft:enchanted_golden_apple"); ++ FLATTEN_MAP.put("minecraft:fireworks.0", "minecraft:firework_rocket"); ++ FLATTEN_MAP.put("minecraft:firework_charge.0", "minecraft:firework_star"); ++ FLATTEN_MAP.put("minecraft:dye.0", "minecraft:ink_sac"); ++ FLATTEN_MAP.put("minecraft:dye.1", "minecraft:rose_red"); ++ FLATTEN_MAP.put("minecraft:dye.2", "minecraft:cactus_green"); ++ FLATTEN_MAP.put("minecraft:dye.3", "minecraft:cocoa_beans"); ++ FLATTEN_MAP.put("minecraft:dye.4", "minecraft:lapis_lazuli"); ++ FLATTEN_MAP.put("minecraft:dye.5", "minecraft:purple_dye"); ++ FLATTEN_MAP.put("minecraft:dye.6", "minecraft:cyan_dye"); ++ FLATTEN_MAP.put("minecraft:dye.7", "minecraft:light_gray_dye"); ++ FLATTEN_MAP.put("minecraft:dye.8", "minecraft:gray_dye"); ++ FLATTEN_MAP.put("minecraft:dye.9", "minecraft:pink_dye"); ++ FLATTEN_MAP.put("minecraft:dye.10", "minecraft:lime_dye"); ++ FLATTEN_MAP.put("minecraft:dye.11", "minecraft:dandelion_yellow"); ++ FLATTEN_MAP.put("minecraft:dye.12", "minecraft:light_blue_dye"); ++ FLATTEN_MAP.put("minecraft:dye.13", "minecraft:magenta_dye"); ++ FLATTEN_MAP.put("minecraft:dye.14", "minecraft:orange_dye"); ++ FLATTEN_MAP.put("minecraft:dye.15", "minecraft:bone_meal"); ++ FLATTEN_MAP.put("minecraft:silver_shulker_box.0", "minecraft:light_gray_shulker_box"); ++ FLATTEN_MAP.put("minecraft:fence.0", "minecraft:oak_fence"); ++ FLATTEN_MAP.put("minecraft:fence_gate.0", "minecraft:oak_fence_gate"); ++ FLATTEN_MAP.put("minecraft:wooden_door.0", "minecraft:oak_door"); ++ FLATTEN_MAP.put("minecraft:boat.0", "minecraft:oak_boat"); ++ FLATTEN_MAP.put("minecraft:lit_pumpkin.0", "minecraft:jack_o_lantern"); ++ FLATTEN_MAP.put("minecraft:pumpkin.0", "minecraft:carved_pumpkin"); ++ FLATTEN_MAP.put("minecraft:trapdoor.0", "minecraft:oak_trapdoor"); ++ FLATTEN_MAP.put("minecraft:nether_brick.0", "minecraft:nether_bricks"); ++ FLATTEN_MAP.put("minecraft:red_nether_brick.0", "minecraft:red_nether_bricks"); ++ FLATTEN_MAP.put("minecraft:netherbrick.0", "minecraft:nether_brick"); ++ FLATTEN_MAP.put("minecraft:wooden_button.0", "minecraft:oak_button"); ++ FLATTEN_MAP.put("minecraft:wooden_pressure_plate.0", "minecraft:oak_pressure_plate"); ++ FLATTEN_MAP.put("minecraft:noteblock.0", "minecraft:note_block"); ++ FLATTEN_MAP.put("minecraft:bed.0", "minecraft:white_bed"); ++ FLATTEN_MAP.put("minecraft:bed.1", "minecraft:orange_bed"); ++ FLATTEN_MAP.put("minecraft:bed.2", "minecraft:magenta_bed"); ++ FLATTEN_MAP.put("minecraft:bed.3", "minecraft:light_blue_bed"); ++ FLATTEN_MAP.put("minecraft:bed.4", "minecraft:yellow_bed"); ++ FLATTEN_MAP.put("minecraft:bed.5", "minecraft:lime_bed"); ++ FLATTEN_MAP.put("minecraft:bed.6", "minecraft:pink_bed"); ++ FLATTEN_MAP.put("minecraft:bed.7", "minecraft:gray_bed"); ++ FLATTEN_MAP.put("minecraft:bed.8", "minecraft:light_gray_bed"); ++ FLATTEN_MAP.put("minecraft:bed.9", "minecraft:cyan_bed"); ++ FLATTEN_MAP.put("minecraft:bed.10", "minecraft:purple_bed"); ++ FLATTEN_MAP.put("minecraft:bed.11", "minecraft:blue_bed"); ++ FLATTEN_MAP.put("minecraft:bed.12", "minecraft:brown_bed"); ++ FLATTEN_MAP.put("minecraft:bed.13", "minecraft:green_bed"); ++ FLATTEN_MAP.put("minecraft:bed.14", "minecraft:red_bed"); ++ FLATTEN_MAP.put("minecraft:bed.15", "minecraft:black_bed"); ++ FLATTEN_MAP.put("minecraft:banner.15", "minecraft:white_banner"); ++ FLATTEN_MAP.put("minecraft:banner.14", "minecraft:orange_banner"); ++ FLATTEN_MAP.put("minecraft:banner.13", "minecraft:magenta_banner"); ++ FLATTEN_MAP.put("minecraft:banner.12", "minecraft:light_blue_banner"); ++ FLATTEN_MAP.put("minecraft:banner.11", "minecraft:yellow_banner"); ++ FLATTEN_MAP.put("minecraft:banner.10", "minecraft:lime_banner"); ++ FLATTEN_MAP.put("minecraft:banner.9", "minecraft:pink_banner"); ++ FLATTEN_MAP.put("minecraft:banner.8", "minecraft:gray_banner"); ++ FLATTEN_MAP.put("minecraft:banner.7", "minecraft:light_gray_banner"); ++ FLATTEN_MAP.put("minecraft:banner.6", "minecraft:cyan_banner"); ++ FLATTEN_MAP.put("minecraft:banner.5", "minecraft:purple_banner"); ++ FLATTEN_MAP.put("minecraft:banner.4", "minecraft:blue_banner"); ++ FLATTEN_MAP.put("minecraft:banner.3", "minecraft:brown_banner"); ++ FLATTEN_MAP.put("minecraft:banner.2", "minecraft:green_banner"); ++ FLATTEN_MAP.put("minecraft:banner.1", "minecraft:red_banner"); ++ FLATTEN_MAP.put("minecraft:banner.0", "minecraft:black_banner"); ++ FLATTEN_MAP.put("minecraft:grass.0", "minecraft:grass_block"); ++ FLATTEN_MAP.put("minecraft:brick_block.0", "minecraft:bricks"); ++ FLATTEN_MAP.put("minecraft:end_bricks.0", "minecraft:end_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:golden_rail.0", "minecraft:powered_rail"); ++ FLATTEN_MAP.put("minecraft:magma.0", "minecraft:magma_block"); ++ FLATTEN_MAP.put("minecraft:quartz_ore.0", "minecraft:nether_quartz_ore"); ++ FLATTEN_MAP.put("minecraft:reeds.0", "minecraft:sugar_cane"); ++ FLATTEN_MAP.put("minecraft:slime.0", "minecraft:slime_block"); ++ FLATTEN_MAP.put("minecraft:stone_stairs.0", "minecraft:cobblestone_stairs"); ++ FLATTEN_MAP.put("minecraft:waterlily.0", "minecraft:lily_pad"); ++ FLATTEN_MAP.put("minecraft:web.0", "minecraft:cobweb"); ++ FLATTEN_MAP.put("minecraft:snow.0", "minecraft:snow_block"); ++ FLATTEN_MAP.put("minecraft:snow_layer.0", "minecraft:snow"); ++ FLATTEN_MAP.put("minecraft:record_11.0", "minecraft:music_disc_11"); ++ FLATTEN_MAP.put("minecraft:record_13.0", "minecraft:music_disc_13"); ++ FLATTEN_MAP.put("minecraft:record_blocks.0", "minecraft:music_disc_blocks"); ++ FLATTEN_MAP.put("minecraft:record_cat.0", "minecraft:music_disc_cat"); ++ FLATTEN_MAP.put("minecraft:record_chirp.0", "minecraft:music_disc_chirp"); ++ FLATTEN_MAP.put("minecraft:record_far.0", "minecraft:music_disc_far"); ++ FLATTEN_MAP.put("minecraft:record_mall.0", "minecraft:music_disc_mall"); ++ FLATTEN_MAP.put("minecraft:record_mellohi.0", "minecraft:music_disc_mellohi"); ++ FLATTEN_MAP.put("minecraft:record_stal.0", "minecraft:music_disc_stal"); ++ FLATTEN_MAP.put("minecraft:record_strad.0", "minecraft:music_disc_strad"); ++ FLATTEN_MAP.put("minecraft:record_wait.0", "minecraft:music_disc_wait"); ++ FLATTEN_MAP.put("minecraft:record_ward.0", "minecraft:music_disc_ward"); ++ } ++ ++ // maps out ids requiring flattening ++ private static final Set IDS_REQUIRING_FLATTENING = new HashSet<>(); ++ static { ++ for (final String key : FLATTEN_MAP.keySet()) { ++ IDS_REQUIRING_FLATTENING.add(key.substring(0, key.indexOf('.'))); ++ } ++ } ++ ++ // Damage tag is moved from the ItemStack base tag to the ItemStack tag, and we only want to migrate that ++ // for items that actually require it for damage purposes (Remember, old damage was used to differentiate item types) ++ // It should be noted that this ID set should not be included in the flattening map, because damage for these items ++ // is actual damage and not a subtype specifier ++ private static final Set ITEMS_WITH_DAMAGE = new HashSet<>(Arrays.asList( ++ "minecraft:bow", ++ "minecraft:carrot_on_a_stick", ++ "minecraft:chainmail_boots", ++ "minecraft:chainmail_chestplate", ++ "minecraft:chainmail_helmet", ++ "minecraft:chainmail_leggings", ++ "minecraft:diamond_axe", ++ "minecraft:diamond_boots", ++ "minecraft:diamond_chestplate", ++ "minecraft:diamond_helmet", ++ "minecraft:diamond_hoe", ++ "minecraft:diamond_leggings", ++ "minecraft:diamond_pickaxe", ++ "minecraft:diamond_shovel", ++ "minecraft:diamond_sword", ++ "minecraft:elytra", ++ "minecraft:fishing_rod", ++ "minecraft:flint_and_steel", ++ "minecraft:golden_axe", ++ "minecraft:golden_boots", ++ "minecraft:golden_chestplate", ++ "minecraft:golden_helmet", ++ "minecraft:golden_hoe", ++ "minecraft:golden_leggings", ++ "minecraft:golden_pickaxe", ++ "minecraft:golden_shovel", ++ "minecraft:golden_sword", ++ "minecraft:iron_axe", ++ "minecraft:iron_boots", ++ "minecraft:iron_chestplate", ++ "minecraft:iron_helmet", ++ "minecraft:iron_hoe", ++ "minecraft:iron_leggings", ++ "minecraft:iron_pickaxe", ++ "minecraft:iron_shovel", ++ "minecraft:iron_sword", ++ "minecraft:leather_boots", ++ "minecraft:leather_chestplate", ++ "minecraft:leather_helmet", ++ "minecraft:leather_leggings", ++ "minecraft:shears", ++ "minecraft:shield", ++ "minecraft:stone_axe", ++ "minecraft:stone_hoe", ++ "minecraft:stone_pickaxe", ++ "minecraft:stone_shovel", ++ "minecraft:stone_sword", ++ "minecraft:wooden_axe", ++ "minecraft:wooden_hoe", ++ "minecraft:wooden_pickaxe", ++ "minecraft:wooden_shovel", ++ "minecraft:wooden_sword" ++ )); ++ ++ public ConverterFlattenItemStack() { ++ super(MCVersions.V17W47A, 4); ++ } ++ ++ public static String flattenItem(final String oldName, final int data) { ++ if (IDS_REQUIRING_FLATTENING.contains(oldName)) { ++ final String flattened = FLATTEN_MAP.get(oldName + '.' + data); ++ return flattened == null ? FLATTEN_MAP.get(oldName.concat(".0")) : flattened; ++ } else { ++ return null; ++ } ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String id = data.getString("id"); ++ ++ if (id == null) { ++ return null; ++ } ++ ++ final int damage = data.getInt("Damage"); ++ data.remove("Damage"); ++ ++ if (IDS_REQUIRING_FLATTENING.contains(id)) { ++ String remap = FLATTEN_MAP.get(id + '.' + damage); ++ if (remap == null) { ++ remap = FLATTEN_MAP.get(id.concat(".0")); ++ // this shouldn't be null ++ } ++ if (remap != null) { ++ data.setString("id", remap); ++ } else { ++ LOGGER.warn("Item '" + id + "' requires flattening but found no mapping for it! (ConverterFlattenItemStack)"); ++ } ++ } ++ ++ if (damage != 0 && ITEMS_WITH_DAMAGE.contains(id)) { ++ // migrate damage ++ MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ tag = Types.NBT.createEmptyMap(); ++ data.setMap("tag", tag); ++ } ++ tag.setInt("Damage", damage); ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4fa31e40b0a6f571a853299b4e242de921ccbda0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java +@@ -0,0 +1,87 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.itemstack; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class ConverterFlattenSpawnEgg extends DataConverter, MapType> { ++ ++ private static final Map ENTITY_ID_TO_NEW_EGG_ID = new HashMap<>(); ++ static { ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:bat", "minecraft:bat_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:blaze", "minecraft:blaze_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:cave_spider", "minecraft:cave_spider_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:chicken", "minecraft:chicken_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:cow", "minecraft:cow_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:creeper", "minecraft:creeper_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:donkey", "minecraft:donkey_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:elder_guardian", "minecraft:elder_guardian_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:enderman", "minecraft:enderman_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:endermite", "minecraft:endermite_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:evocation_illager", "minecraft:evocation_illager_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:ghast", "minecraft:ghast_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:guardian", "minecraft:guardian_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:ender_dragon", "minecraft:ender_dragon_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:horse", "minecraft:horse_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:husk", "minecraft:husk_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:iron_golem", "minecraft:iron_golem_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:llama", "minecraft:llama_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:magma_cube", "minecraft:magma_cube_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:mooshroom", "minecraft:mooshroom_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:mule", "minecraft:mule_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:ocelot", "minecraft:ocelot_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:pufferfish", "minecraft:pufferfish_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:parrot", "minecraft:parrot_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:pig", "minecraft:pig_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:polar_bear", "minecraft:polar_bear_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:rabbit", "minecraft:rabbit_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:sheep", "minecraft:sheep_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:shulker", "minecraft:shulker_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:silverfish", "minecraft:silverfish_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:skeleton", "minecraft:skeleton_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:skeleton_horse", "minecraft:skeleton_horse_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:slime", "minecraft:slime_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:snow_golem", "minecraft:snow_golem_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:spider", "minecraft:spider_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:squid", "minecraft:squid_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:stray", "minecraft:stray_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:turtle", "minecraft:turtle_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:vex", "minecraft:vex_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:villager", "minecraft:villager_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:vindication_illager", "minecraft:vindication_illager_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:witch", "minecraft:witch_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:wither", "minecraft:wither_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:wither_skeleton", "minecraft:wither_skeleton_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:wolf", "minecraft:wolf_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie", "minecraft:zombie_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie_horse", "minecraft:zombie_horse_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie_pigman", "minecraft:zombie_pigman_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie_villager", "minecraft:zombie_villager_spawn_egg"); ++ } ++ ++ public ConverterFlattenSpawnEgg(final int version, final int versionStep) { ++ super(version, versionStep); ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final MapType entityTag = tag.getMap("EntityTag"); ++ if (entityTag == null) { ++ return null; ++ } ++ ++ final String id = entityTag.getString("id"); ++ if (id != null) { ++ data.setString("id", ENTITY_ID_TO_NEW_EGG_ID.getOrDefault(id, "minecraft:pig_spawn_egg")); ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4c537b661b7a28193add3267ec2d639add49423b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java +@@ -0,0 +1,46 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.leveldat; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import java.util.Set; ++ ++public final class ConverterRemoveFeatureFlag extends DataConverter, MapType> { ++ ++ private final Set flags; ++ ++ public ConverterRemoveFeatureFlag(final int toVersion, final Set flags) { ++ this(toVersion, 0, flags); ++ } ++ ++ public ConverterRemoveFeatureFlag(final int toVersion, final int versionStep, final Set flags) { ++ super(toVersion, versionStep); ++ this.flags = flags; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType enabledFeatures = data.getList("enabled_features", ObjectType.STRING); ++ if (enabledFeatures == null) { ++ return null; ++ } ++ ++ ListType removedFeatures = null; ++ ++ for (int i = 0; i < enabledFeatures.size(); ++i) { ++ final String flag = enabledFeatures.getString(i); ++ if (!this.flags.contains(flag)) { ++ continue; ++ } ++ enabledFeatures.remove(i--); ++ ++ if (removedFeatures == null) { ++ removedFeatures = data.getOrCreateList("removed_features", ObjectType.STRING); ++ } ++ removedFeatures.addString(flag); ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..769dd8447976b66dcfc36283ede4ae16f1e4206d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java +@@ -0,0 +1,28 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.options; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.ArrayList; ++import java.util.function.Function; ++ ++public final class ConverterAbstractOptionsRename { ++ ++ private ConverterAbstractOptionsRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ RenameHelper.renameKeys(data, renamer); ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..57e210bf2bb189b15a32899011c4800b19668a5e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java +@@ -0,0 +1,53 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.poi; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import java.util.function.Function; ++ ++public final class ConverterAbstractPOIRename { ++ ++ private ConverterAbstractPOIRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType sections = data.getMap("Sections"); ++ if (sections == null) { ++ return null; ++ } ++ ++ for (final String key : sections.keys()) { ++ final MapType section = sections.getMap(key); ++ ++ final ListType records = section.getList("Records", ObjectType.MAP); ++ ++ if (records == null) { ++ continue; ++ } ++ ++ for (int i = 0, len = records.size(); i < len; ++i) { ++ final MapType record = records.getMap(i); ++ ++ final String type = record.getString("type"); ++ if (type != null) { ++ final String converted = renamer.apply(type); ++ if (converted != null) { ++ record.setString("type", converted); ++ } ++ } ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java +new file mode 100644 +index 0000000000000000000000000000000000000000..36aa9c3eedb3f2e2f577efed3622fed74268bce1 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java +@@ -0,0 +1,53 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.poi; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import java.util.function.Predicate; ++ ++public final class ConverterPoiDelete extends DataConverter, MapType> { ++ ++ private final Predicate delete; ++ ++ public ConverterPoiDelete(final int toVersion, final Predicate delete) { ++ super(toVersion); ++ this.delete = delete; ++ } ++ ++ public ConverterPoiDelete(final int toVersion, final int versionStep, final Predicate delete) { ++ super(toVersion, versionStep); ++ this.delete = delete; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType sections = data.getMap("Sections"); ++ if (sections == null) { ++ return null; ++ } ++ ++ for (final String key : sections.keys()) { ++ final MapType section = sections.getMap(key); ++ ++ final ListType records = section.getList("Records", ObjectType.MAP); ++ ++ if (records == null) { ++ continue; ++ } ++ ++ for (int i = 0; i < records.size();) { ++ final MapType record = records.getMap(i); ++ ++ final String type = record.getString("type"); ++ if (type != null && this.delete.test(type)) { ++ records.remove(i); ++ continue; ++ } ++ ++i; ++ } ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8f35cbbd78a629712f9ae3cd5d180269f015a11d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.recipe; ++ ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import java.util.function.Function; ++ ++public final class ConverterAbstractRecipeRename { ++ ++ private ConverterAbstractRecipeRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.RECIPE, renamer); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a1985c85aa9193699d7d20e6f4f11b6e9744ee70 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java +@@ -0,0 +1,66 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.stats; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.ArrayList; ++import java.util.function.Function; ++ ++public final class ConverterAbstractStatsRename { ++ ++ private ConverterAbstractStatsRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ MCTypeRegistry.OBJECTIVE.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType criteriaType = data.getMap("CriteriaType"); ++ if (criteriaType == null) { ++ return null; ++ } ++ ++ final String type = criteriaType.getString("type"); ++ if (!"minecraft:custom".equals(type)) { ++ return null; ++ } ++ ++ final String id = criteriaType.getString("id"); ++ if (id == null) { ++ return null; ++ } ++ ++ final String rename = renamer.apply(id); ++ if (rename != null) { ++ criteriaType.setString("id", rename); ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.STATS.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType stats = data.getMap("stats"); ++ ++ if (stats == null) { ++ return null; ++ } ++ ++ final MapType custom = stats.getMap("minecraft:custom"); ++ if (custom == null) { ++ return null; ++ } ++ ++ RenameHelper.renameKeys(custom, renamer); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java +new file mode 100644 +index 0000000000000000000000000000000000000000..891be75bf5c4af56e839c88b26f0a828554ae5c4 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java +@@ -0,0 +1,321 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.stats; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenItemStack; ++import ca.spottedleaf.dataconverter.minecraft.versions.V1451; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.ImmutableSet; ++import org.apache.commons.lang3.StringUtils; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++ ++public final class ConverterFlattenStats { ++ ++ private static final int VERSION = MCVersions.V17W47A; ++ private static final int VERSION_STEP = 6; ++ ++ private static final Set SPECIAL_OBJECTIVE_CRITERIA = new HashSet<>( ++ Set.of( ++ "dummy", ++ "trigger", ++ "deathCount", ++ "playerKillCount", ++ "totalKillCount", ++ "health", ++ "food", ++ "air", ++ "armor", ++ "xp", ++ "level", ++ "killedByTeam.aqua", ++ "killedByTeam.black", ++ "killedByTeam.blue", ++ "killedByTeam.dark_aqua", ++ "killedByTeam.dark_blue", ++ "killedByTeam.dark_gray", ++ "killedByTeam.dark_green", ++ "killedByTeam.dark_purple", ++ "killedByTeam.dark_red", ++ "killedByTeam.gold", ++ "killedByTeam.gray", ++ "killedByTeam.green", ++ "killedByTeam.light_purple", ++ "killedByTeam.red", ++ "killedByTeam.white", ++ "killedByTeam.yellow", ++ "teamkill.aqua", ++ "teamkill.black", ++ "teamkill.blue", ++ "teamkill.dark_aqua", ++ "teamkill.dark_blue", ++ "teamkill.dark_gray", ++ "teamkill.dark_green", ++ "teamkill.dark_purple", ++ "teamkill.dark_red", ++ "teamkill.gold", ++ "teamkill.gray", ++ "teamkill.green", ++ "teamkill.light_purple", ++ "teamkill.red", ++ "teamkill.white", ++ "teamkill.yellow" ++ ) ++ ); ++ ++ private static final Set SKIP = new HashSet<>( ++ ImmutableSet.builder() ++ .add("stat.craftItem.minecraft.spawn_egg") ++ .add("stat.useItem.minecraft.spawn_egg") ++ .add("stat.breakItem.minecraft.spawn_egg") ++ .add("stat.pickup.minecraft.spawn_egg") ++ .add("stat.drop.minecraft.spawn_egg") ++ .build() ++ ); ++ ++ private static final Map CUSTOM_MAP = new HashMap<>( ++ ImmutableMap.builder() ++ .put("stat.leaveGame", "minecraft:leave_game") ++ .put("stat.playOneMinute", "minecraft:play_one_minute") ++ .put("stat.timeSinceDeath", "minecraft:time_since_death") ++ .put("stat.sneakTime", "minecraft:sneak_time") ++ .put("stat.walkOneCm", "minecraft:walk_one_cm") ++ .put("stat.crouchOneCm", "minecraft:crouch_one_cm") ++ .put("stat.sprintOneCm", "minecraft:sprint_one_cm") ++ .put("stat.swimOneCm", "minecraft:swim_one_cm") ++ .put("stat.fallOneCm", "minecraft:fall_one_cm") ++ .put("stat.climbOneCm", "minecraft:climb_one_cm") ++ .put("stat.flyOneCm", "minecraft:fly_one_cm") ++ .put("stat.diveOneCm", "minecraft:dive_one_cm") ++ .put("stat.minecartOneCm", "minecraft:minecart_one_cm") ++ .put("stat.boatOneCm", "minecraft:boat_one_cm") ++ .put("stat.pigOneCm", "minecraft:pig_one_cm") ++ .put("stat.horseOneCm", "minecraft:horse_one_cm") ++ .put("stat.aviateOneCm", "minecraft:aviate_one_cm") ++ .put("stat.jump", "minecraft:jump") ++ .put("stat.drop", "minecraft:drop") ++ .put("stat.damageDealt", "minecraft:damage_dealt") ++ .put("stat.damageTaken", "minecraft:damage_taken") ++ .put("stat.deaths", "minecraft:deaths") ++ .put("stat.mobKills", "minecraft:mob_kills") ++ .put("stat.animalsBred", "minecraft:animals_bred") ++ .put("stat.playerKills", "minecraft:player_kills") ++ .put("stat.fishCaught", "minecraft:fish_caught") ++ .put("stat.talkedToVillager", "minecraft:talked_to_villager") ++ .put("stat.tradedWithVillager", "minecraft:traded_with_villager") ++ .put("stat.cakeSlicesEaten", "minecraft:eat_cake_slice") ++ .put("stat.cauldronFilled", "minecraft:fill_cauldron") ++ .put("stat.cauldronUsed", "minecraft:use_cauldron") ++ .put("stat.armorCleaned", "minecraft:clean_armor") ++ .put("stat.bannerCleaned", "minecraft:clean_banner") ++ .put("stat.brewingstandInteraction", "minecraft:interact_with_brewingstand") ++ .put("stat.beaconInteraction", "minecraft:interact_with_beacon") ++ .put("stat.dropperInspected", "minecraft:inspect_dropper") ++ .put("stat.hopperInspected", "minecraft:inspect_hopper") ++ .put("stat.dispenserInspected", "minecraft:inspect_dispenser") ++ .put("stat.noteblockPlayed", "minecraft:play_noteblock") ++ .put("stat.noteblockTuned", "minecraft:tune_noteblock") ++ .put("stat.flowerPotted", "minecraft:pot_flower") ++ .put("stat.trappedChestTriggered", "minecraft:trigger_trapped_chest") ++ .put("stat.enderchestOpened", "minecraft:open_enderchest") ++ .put("stat.itemEnchanted", "minecraft:enchant_item") ++ .put("stat.recordPlayed", "minecraft:play_record") ++ .put("stat.furnaceInteraction", "minecraft:interact_with_furnace") ++ .put("stat.craftingTableInteraction", "minecraft:interact_with_crafting_table") ++ .put("stat.chestOpened", "minecraft:open_chest") ++ .put("stat.sleepInBed", "minecraft:sleep_in_bed") ++ .put("stat.shulkerBoxOpened", "minecraft:open_shulker_box") ++ .build() ++ ); ++ ++ private static final String BLOCK_KEY = "stat.mineBlock"; ++ private static final String NEW_BLOCK_KEY = "minecraft:mined"; ++ ++ private static final Map ITEM_KEYS = new HashMap<>( ++ ImmutableMap.builder() ++ .put("stat.craftItem", "minecraft:crafted") ++ .put("stat.useItem", "minecraft:used") ++ .put("stat.breakItem", "minecraft:broken") ++ .put("stat.pickup", "minecraft:picked_up") ++ .put("stat.drop", "minecraft:dropped") ++ .build() ++ ); ++ ++ private static final Map ENTITY_KEYS = new HashMap<>( ++ ImmutableMap.builder() ++ .put("stat.entityKilledBy", "minecraft:killed_by") ++ .put("stat.killEntity", "minecraft:killed") ++ .build() ++ ); ++ ++ private static final Map ENTITIES = new HashMap<>( ++ ImmutableMap.builder() ++ .put("Bat", "minecraft:bat") ++ .put("Blaze", "minecraft:blaze") ++ .put("CaveSpider", "minecraft:cave_spider") ++ .put("Chicken", "minecraft:chicken") ++ .put("Cow", "minecraft:cow") ++ .put("Creeper", "minecraft:creeper") ++ .put("Donkey", "minecraft:donkey") ++ .put("ElderGuardian", "minecraft:elder_guardian") ++ .put("Enderman", "minecraft:enderman") ++ .put("Endermite", "minecraft:endermite") ++ .put("EvocationIllager", "minecraft:evocation_illager") ++ .put("Ghast", "minecraft:ghast") ++ .put("Guardian", "minecraft:guardian") ++ .put("Horse", "minecraft:horse") ++ .put("Husk", "minecraft:husk") ++ .put("Llama", "minecraft:llama") ++ .put("LavaSlime", "minecraft:magma_cube") ++ .put("MushroomCow", "minecraft:mooshroom") ++ .put("Mule", "minecraft:mule") ++ .put("Ozelot", "minecraft:ocelot") ++ .put("Parrot", "minecraft:parrot") ++ .put("Pig", "minecraft:pig") ++ .put("PolarBear", "minecraft:polar_bear") ++ .put("Rabbit", "minecraft:rabbit") ++ .put("Sheep", "minecraft:sheep") ++ .put("Shulker", "minecraft:shulker") ++ .put("Silverfish", "minecraft:silverfish") ++ .put("SkeletonHorse", "minecraft:skeleton_horse") ++ .put("Skeleton", "minecraft:skeleton") ++ .put("Slime", "minecraft:slime") ++ .put("Spider", "minecraft:spider") ++ .put("Squid", "minecraft:squid") ++ .put("Stray", "minecraft:stray") ++ .put("Vex", "minecraft:vex") ++ .put("Villager", "minecraft:villager") ++ .put("VindicationIllager", "minecraft:vindication_illager") ++ .put("Witch", "minecraft:witch") ++ .put("WitherSkeleton", "minecraft:wither_skeleton") ++ .put("Wolf", "minecraft:wolf") ++ .put("ZombieHorse", "minecraft:zombie_horse") ++ .put("PigZombie", "minecraft:zombie_pigman") ++ .put("ZombieVillager", "minecraft:zombie_villager") ++ .put("Zombie", "minecraft:zombie") ++ .build() ++ ); ++ ++ private static final String NEW_CUSTOM_KEY = "minecraft:custom"; ++ ++ private ConverterFlattenStats() {} ++ ++ private static String upgradeItem(final String itemName) { ++ return ConverterFlattenItemStack.flattenItem(itemName, 0); ++ } ++ ++ private static String upgradeBlock(final String block) { ++ return HelperBlockFlatteningV1450.getNewBlockName(block); ++ } ++ ++ private static record StatType(String category, String key) {} ++ ++ private static StatType convertLegacyKey(final String key) { ++ if (SKIP.contains(key)) { ++ return null; ++ } ++ ++ final String custom = CUSTOM_MAP.get(key); ++ if (custom != null) { ++ return new StatType(NEW_CUSTOM_KEY, custom); ++ } ++ ++ final int i = StringUtils.ordinalIndexOf(key, ".", 2); ++ if (i < 0) { ++ return null; ++ } ++ ++ final String stat = key.substring(0, i); ++ ++ if (BLOCK_KEY.equals(stat)) { ++ return new StatType(NEW_BLOCK_KEY, upgradeBlock(key.substring(i + 1).replace('.', ':'))); ++ } ++ ++ final String itemStat = ITEM_KEYS.get(stat); ++ ++ if (itemStat != null) { ++ final String itemId = key.substring(i + 1).replace('.', ':'); ++ final String flattenedItem = upgradeItem(itemId); ++ ++ return new StatType(itemStat, flattenedItem == null ? itemId : flattenedItem); ++ } ++ ++ final String entityStat = ENTITY_KEYS.get(stat); ++ if (entityStat != null) { ++ final String entityId = key.substring(i + 1).replace('.', ':'); ++ ++ return new StatType(entityStat, ENTITIES.getOrDefault(entityId, entityId)); ++ } ++ ++ return null; ++ } ++ ++ public static DataConverter, MapType> makeStatsConverter() { ++ return new DataConverter<>(VERSION, VERSION_STEP) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType stats = Types.NBT.createEmptyMap(); ++ ++ for (final String statKey : data.keys()) { ++ final Number value = data.getNumber(statKey); ++ if (value == null) { ++ continue; ++ } ++ ++ final StatType converted = convertLegacyKey(statKey); ++ ++ if (converted == null) { ++ continue; ++ } ++ ++ stats.getOrCreateMap(converted.category()).setGeneric(converted.key(), value); ++ } ++ ++ data.clear(); ++ data.setMap("stats", stats); ++ ++ return null; ++ } ++ }; ++ } ++ ++ public static DataConverter, MapType> makeObjectiveConverter() { ++ return new DataConverter<>(VERSION, VERSION_STEP) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ convertCriteriaName(data, "CriteriaName"); ++ ++ // We also need to update CriteriaType that is created by the data hook in V1451, ++ // otherwise that data hook will overwrite our CriteriaName ++ final MapType criteriaType = data.getMap("CriteriaType"); ++ if (criteriaType != null) { ++ if ("_special".equals(criteriaType.getString("type"))) { ++ convertCriteriaName(criteriaType, "id"); ++ } ++ } ++ ++ return null; ++ } ++ ++ private void convertCriteriaName(final MapType data, final String key) { ++ final String criteriaName = data.getString(key); ++ ++ if (criteriaName == null) { ++ return; ++ } ++ ++ if (SPECIAL_OBJECTIVE_CRITERIA.contains(criteriaName)) { ++ return; ++ } ++ ++ final StatType converted = convertLegacyKey(criteriaName); ++ data.setString(key, converted == null ? "dummy" : V1451.packWithDot(converted.category()) + ":" + V1451.packWithDot(converted.key())); ++ } ++ }; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ab05dda0cc2083418443d0dee23ccc0a6f754ea0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java +@@ -0,0 +1,34 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.tileentity; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.function.Function; ++ ++public final class ConverterAbstractTileEntityRename { ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ MCTypeRegistry.TILE_ENTITY.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String id = data.getString("id"); ++ if (id == null) { ++ return null; ++ } ++ ++ final String converted = renamer.apply(id); ++ ++ if (converted != null) { ++ data.setString("id", converted); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b2c2b4c4ae83f14639fa53e38f2c75ccd284c2d2 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java +@@ -0,0 +1,166 @@ ++package ca.spottedleaf.dataconverter.minecraft.datatypes; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; ++import ca.spottedleaf.dataconverter.minecraft.MCVersionRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap; ++ ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++ ++public class IDDataType extends MCDataType { ++ ++ protected final Map>>> walkersById = new HashMap<>(); ++ ++ public IDDataType(final String name) { ++ super(name); ++ } ++ ++ public void addConverterForId(final String id, final DataConverter, MapType> converter) { ++ this.addStructureConverter(new DataConverter<>(converter.getToVersion(), converter.getVersionStep()) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!id.equals(data.getString("id"))) { ++ return null; ++ } ++ return converter.convert(data, sourceVersion, toVersion); ++ } ++ }); ++ } ++ ++ public void addWalker(final int minVersion, final String id, final DataWalker walker) { ++ this.addWalker(minVersion, 0, id, walker); ++ } ++ ++ public void addWalker(final int minVersion, final int versionStep, final String id, final DataWalker walker) { ++ this.walkersById.computeIfAbsent(id, (final String keyInMap) -> { ++ return new Long2ObjectArraySortedMap<>(); ++ }).computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(walker); ++ } ++ ++ public void copyWalkers(final int minVersion, final String fromId, final String toId) { ++ this.copyWalkers(minVersion, 0, fromId, toId); ++ } ++ ++ public void copyWalkers(final int minVersion, final int versionStep, final String fromId, final String toId) { ++ final long version = DataConverter.encodeVersions(minVersion, versionStep); ++ final Long2ObjectArraySortedMap>> walkersForId = this.walkersById.get(fromId); ++ if (walkersForId == null) { ++ return; ++ } ++ ++ final List> nearest = walkersForId.getFloor(version); ++ ++ if (nearest == null) { ++ return; ++ } ++ ++ for (final DataWalker walker : nearest) { ++ this.addWalker(minVersion, versionStep, toId, walker); ++ } ++ } ++ ++ @Override ++ public MapType convert(MapType data, final long fromVersion, final long toVersion) { ++ MapType ret = null; ++ ++ final List, MapType>> converters = this.structureConverters; ++ for (int i = 0, len = converters.size(); i < len; ++i) { ++ final DataConverter, MapType> converter = converters.get(i); ++ final long converterVersion = converter.getEncodedVersion(); ++ ++ if (converterVersion <= fromVersion) { ++ continue; ++ } ++ ++ if (converterVersion > toVersion) { ++ break; ++ } ++ ++ List, MapType>> hooks = this.structureHooks.getFloor(converterVersion); ++ ++ if (hooks != null) { ++ for (int k = 0, klen = hooks.size(); k < klen; ++k) { ++ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ ++ final MapType replace = converter.convert(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ ++ // possibly new data format, update hooks ++ hooks = this.structureHooks.getFloor(toVersion); ++ ++ if (hooks != null) { ++ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { ++ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); ++ if (postReplace != null) { ++ ret = data = postReplace; ++ } ++ } ++ } ++ } ++ ++ final List, MapType>> hooks = this.structureHooks.getFloor(toVersion); ++ ++ // run pre hooks ++ ++ if (hooks != null) { ++ for (int k = 0, klen = hooks.size(); k < klen; ++k) { ++ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ ++ // run all walkers ++ ++ final List> walkers = this.structureWalkers.getFloor(toVersion); ++ if (walkers != null) { ++ for (int i = 0, len = walkers.size(); i < len; ++i) { ++ final MapType replace = walkers.get(i).walk(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ ++ final Long2ObjectArraySortedMap>> walkersByVersion = this.walkersById.get(data.getString("id")); ++ if (walkersByVersion != null) { ++ final List> walkersForId = walkersByVersion.getFloor(toVersion); ++ if (walkersForId != null) { ++ for (int i = 0, len = walkersForId.size(); i < len; ++i) { ++ final MapType replace = walkersForId.get(i).walk(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ } ++ ++ // run post hooks ++ ++ if (hooks != null) { ++ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { ++ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); ++ if (postReplace != null) { ++ ret = data = postReplace; ++ } ++ } ++ } ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..76a6e3efa5c69150e8f5e0063cb6357bed1bffae +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java +@@ -0,0 +1,129 @@ ++package ca.spottedleaf.dataconverter.minecraft.datatypes; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataType; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; ++import ca.spottedleaf.dataconverter.minecraft.MCVersionRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap; ++import java.util.ArrayList; ++import java.util.List; ++ ++public class MCDataType extends DataType, MapType> { ++ ++ public final String name; ++ ++ protected final ArrayList, MapType>> structureConverters = new ArrayList<>(); ++ protected final Long2ObjectArraySortedMap>> structureWalkers = new Long2ObjectArraySortedMap<>(); ++ protected final Long2ObjectArraySortedMap, MapType>>> structureHooks = new Long2ObjectArraySortedMap<>(); ++ ++ public MCDataType(final String name) { ++ this.name = name; ++ } ++ ++ public void addStructureConverter(final DataConverter, MapType> converter) { ++ MCVersionRegistry.checkVersion(converter.getEncodedVersion()); ++ this.structureConverters.add(converter); ++ this.structureConverters.sort(DataConverter.LOWEST_VERSION_COMPARATOR); ++ } ++ ++ public void addStructureWalker(final int minVersion, final DataWalker walker) { ++ this.addStructureWalker(minVersion, 0, walker); ++ } ++ ++ public void addStructureWalker(final int minVersion, final int versionStep, final DataWalker walker) { ++ this.structureWalkers.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(walker); ++ } ++ ++ public void addStructureHook(final int minVersion, final DataHook, MapType> hook) { ++ this.addStructureHook(minVersion, 0, hook); ++ } ++ ++ public void addStructureHook(final int minVersion, final int versionStep, final DataHook, MapType> hook) { ++ this.structureHooks.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(hook); ++ } ++ ++ @Override ++ public MapType convert(MapType data, final long fromVersion, final long toVersion) { ++ MapType ret = null; ++ ++ final List, MapType>> converters = this.structureConverters; ++ for (int i = 0, len = converters.size(); i < len; ++i) { ++ final DataConverter, MapType> converter = converters.get(i); ++ final long converterVersion = converter.getEncodedVersion(); ++ ++ if (converterVersion <= fromVersion) { ++ continue; ++ } ++ ++ if (converterVersion > toVersion) { ++ break; ++ } ++ ++ List, MapType>> hooks = this.structureHooks.getFloor(converterVersion); ++ ++ if (hooks != null) { ++ for (int k = 0, klen = hooks.size(); k < klen; ++k) { ++ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ ++ final MapType replace = converter.convert(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ ++ // possibly new data format, update hooks ++ hooks = this.structureHooks.getFloor(toVersion); ++ ++ if (hooks != null) { ++ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { ++ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); ++ if (postReplace != null) { ++ ret = data = postReplace; ++ } ++ } ++ } ++ } ++ ++ final List, MapType>> hooks = this.structureHooks.getFloor(toVersion); ++ ++ if (hooks != null) { ++ for (int k = 0, klen = hooks.size(); k < klen; ++k) { ++ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ ++ final List> walkers = this.structureWalkers.getFloor(toVersion); ++ if (walkers != null) { ++ for (int i = 0, len = walkers.size(); i < len; ++i) { ++ final MapType replace = walkers.get(i).walk(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ ++ if (hooks != null) { ++ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { ++ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); ++ if (postReplace != null) { ++ ret = data = postReplace; ++ } ++ } ++ } ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bc7303964625d740b902dda72d9543c5f4120475 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java +@@ -0,0 +1,275 @@ ++package ca.spottedleaf.dataconverter.minecraft.datatypes; ++ ++import ca.spottedleaf.dataconverter.minecraft.versions.*; ++import com.mojang.logging.LogUtils; ++import org.slf4j.Logger; ++ ++public final class MCTypeRegistry { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ public static final MCDataType LEVEL = new MCDataType("Level"); ++ public static final MCDataType PLAYER = new MCDataType("Player"); ++ public static final MCDataType CHUNK = new MCDataType("Chunk"); ++ public static final MCDataType HOTBAR = new MCDataType("CreativeHotbar"); ++ public static final MCDataType OPTIONS = new MCDataType("Options"); ++ public static final MCDataType STRUCTURE = new MCDataType("Structure"); ++ public static final MCDataType STATS = new MCDataType("Stats"); ++ public static final MCDataType ADVANCEMENTS = new MCDataType("Advancements"); ++ public static final MCDataType POI_CHUNK = new MCDataType("PoiChunk"); ++ public static final MCDataType ENTITY_CHUNK = new MCDataType("EntityChunk"); ++ public static final IDDataType TILE_ENTITY = new IDDataType("TileEntity"); ++ public static final IDDataType ITEM_STACK = new IDDataType("ItemStack"); ++ public static final MCDataType BLOCK_STATE = new MCDataType("BlockState"); ++ public static final MCValueType ENTITY_NAME = new MCValueType("EntityName"); ++ public static final IDDataType ENTITY = new IDDataType("Entity"); ++ public static final MCValueType BLOCK_NAME = new MCValueType("BlockName"); ++ public static final MCValueType ITEM_NAME = new MCValueType("ItemName"); ++ public static final MCDataType UNTAGGED_SPAWNER = new MCDataType("Spawner"); ++ public static final MCDataType STRUCTURE_FEATURE = new MCDataType("StructureFeature"); ++ public static final MCDataType OBJECTIVE = new MCDataType("Objective"); ++ public static final MCDataType TEAM = new MCDataType("Team"); ++ public static final MCValueType RECIPE = new MCValueType("RecipeName"); ++ public static final MCValueType BIOME = new MCValueType("Biome"); ++ public static final MCDataType WORLD_GEN_SETTINGS = new MCDataType("WorldGenSettings"); ++ public static final MCValueType GAME_EVENT_NAME = new MCValueType("GameEventName"); ++ ++ public static final MCValueType MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST = new MCValueType("MultiNoiseBiomeSourceParameterList"); ++ ++ public static final MCDataType SAVED_DATA_RANDOM_SEQUENCES = new MCDataType("SavedData/RandomSequences"); ++ public static final MCDataType SAVED_DATA_SCOREBOARD = new MCDataType("SavedData/Scoreboard"); ++ public static final MCDataType SAVED_DATA_STRUCTURE_FEATURE_INDICES = new MCDataType("SavedData/StructureFeatureIndices"); ++ public static final MCDataType SAVED_DATA_MAP_DATA = new MCDataType("SavedData/MapData"); ++ public static final MCDataType SAVED_DATA_RAIDS = new MCDataType("SavedData/Raids"); ++ ++ static { ++ try { ++ registerAll(); ++ } catch (final ThreadDeath thr) { ++ throw thr; ++ } catch (final Throwable thr) { ++ LOGGER.error(LogUtils.FATAL_MARKER, "Failed to register data converters", thr); ++ throw new RuntimeException(thr); ++ } ++ } ++ ++ private static void registerAll() { ++ // General notes: ++ // - Structure converters run before everything. ++ // - ID specific converters run after structure converters. ++ // - Structure walkers run after id specific converters. ++ // - ID specific walkers run after structure walkers. ++ ++ V99.register(); // all legacy data before converters existed ++ V100.register(); // first version with version id ++ V101.register(); ++ V102.register(); ++ V105.register(); ++ V106.register(); ++ V107.register(); ++ V108.register(); ++ V109.register(); ++ V110.register(); ++ V111.register(); ++ V113.register(); ++ V135.register(); ++ V143.register(); ++ V147.register(); ++ V165.register(); ++ V501.register(); ++ V502.register(); ++ V505.register(); ++ V700.register(); ++ V701.register(); ++ V702.register(); ++ V703.register(); ++ V704.register(); ++ V705.register(); ++ V804.register(); ++ V806.register(); ++ V808.register(); ++ V813.register(); ++ V816.register(); ++ V820.register(); ++ V1022.register(); ++ V1125.register(); ++ // END OF LEGACY DATA CONVERTERS ++ ++ // V1.13 ++ V1344.register(); ++ V1446.register(); ++ // START THE FLATTENING ++ V1450.register(); ++ V1451.register(); ++ // END THE FLATTENING ++ ++ V1456.register(); ++ V1458.register(); ++ V1460.register(); ++ V1466.register(); ++ V1470.register(); ++ V1474.register(); ++ V1475.register(); ++ V1480.register(); ++ // V1481 is adding simple block entity ++ V1483.register(); ++ V1484.register(); ++ V1486.register(); ++ V1487.register(); ++ V1488.register(); ++ V1490.register(); ++ V1492.register(); ++ V1494.register(); ++ V1496.register(); ++ V1500.register(); ++ V1501.register(); ++ V1502.register(); ++ V1506.register(); ++ V1510.register(); ++ V1514.register(); ++ V1515.register(); ++ V1624.register(); ++ // V1.14 ++ V1800.register(); ++ V1801.register(); ++ V1802.register(); ++ V1803.register(); ++ V1904.register(); ++ V1905.register(); ++ V1906.register(); ++ // V1909 is just adding a simple block entity (jigsaw) ++ V1911.register(); ++ V1914.register(); ++ V1917.register(); ++ V1918.register(); ++ V1920.register(); ++ V1925.register(); ++ V1928.register(); ++ V1929.register(); ++ V1931.register(); ++ V1936.register(); ++ V1946.register(); ++ V1948.register(); ++ V1953.register(); ++ V1955.register(); ++ V1961.register(); ++ V1963.register(); ++ // V1.15 ++ V2100.register(); ++ V2202.register(); ++ V2209.register(); ++ V2211.register(); ++ V2218.register(); ++ // V1.16 ++ V2501.register(); ++ V2502.register(); ++ V2503.register(); ++ V2505.register(); ++ V2508.register(); ++ V2509.register(); ++ V2511.register(); ++ V2514.register(); ++ V2516.register(); ++ V2518.register(); ++ V2519.register(); ++ V2522.register(); ++ V2523.register(); ++ V2527.register(); ++ V2528.register(); ++ V2529.register(); ++ V2531.register(); ++ V2533.register(); ++ V2535.register(); ++ V2538.register(); ++ V2550.register(); ++ V2551.register(); ++ V2552.register(); ++ V2553.register(); ++ V2558.register(); ++ V2568.register(); ++ // V1.17 ++ // WARN: Mojang registers V2671 under 2571, but that version predates 1.16.5? So it looks like a typo... ++ // I changed it to 2671, just so that it's after 1.16.5, but even then this looks misplaced... Thankfully this is ++ // the first datafixer, and all it does is add a walker, so I think even if the version here is just wrong it will ++ // work. ++ V2671.register(); ++ V2679.register(); ++ V2680.register(); ++ V2684.register(); ++ V2686.register(); ++ V2688.register(); ++ V2690.register(); ++ V2691.register(); ++ V2693.register(); ++ V2696.register(); ++ V2700.register(); ++ V2701.register(); ++ V2702.register(); ++ // In reference to V2671, why the fuck is goat being registered again? For this obvious reason, V2704 is absent. ++ V2707.register(); ++ V2710.register(); ++ V2717.register(); ++ // V1.18 ++ V2825.register(); ++ V2831.register(); ++ V2832.register(); ++ V2833.register(); ++ V2838.register(); ++ V2841.register(); ++ V2842.register(); ++ V2843.register(); ++ V2846.register(); ++ V2852.register(); ++ V2967.register(); ++ V2970.register(); ++ // V1.19 ++ // V3076 is registering a simple tile entity (sculk_catalyst) ++ V3077.register(); ++ V3078.register(); ++ V3081.register(); ++ V3082.register(); ++ V3083.register(); ++ V3084.register(); ++ V3086.register(); ++ V3087.register(); ++ V3088.register(); ++ V3090.register(); ++ V3093.register(); ++ V3094.register(); ++ V3097.register(); ++ V3108.register(); ++ V3201.register(); ++ // V3202 registers a simple tile entity ++ V3203.register(); ++ V3204.register(); ++ V3209.register(); ++ V3214.register(); ++ V3319.register(); ++ V3322.register(); ++ V3325.register(); ++ V3326.register(); ++ V3327.register(); ++ V3328.register(); ++ // V1.20 ++ V3438.register(); ++ V3439.register(); ++ V3440.register(); ++ V3441.register(); ++ V3447.register(); ++ V3448.register(); ++ V3450.register(); ++ V3451.register(); ++ V3459.register(); ++ V3564.register(); ++ V3565.register(); ++ V3566.register(); ++ V3568.register(); ++ V3682.register(); ++ V3683.register(); ++ V3685.register(); ++ V3689.register(); ++ V3692.register(); ++ } ++ ++ private MCTypeRegistry() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..13c1381261909ef672fbeb665907f01f2d5c1ced +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java +@@ -0,0 +1,86 @@ ++package ca.spottedleaf.dataconverter.minecraft.datatypes; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataType; ++import ca.spottedleaf.dataconverter.minecraft.MCVersionRegistry; ++import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap; ++import java.util.ArrayList; ++import java.util.List; ++ ++public class MCValueType extends DataType { ++ ++ public final String name; ++ ++ protected final ArrayList> converters = new ArrayList<>(); ++ protected final Long2ObjectArraySortedMap>> structureHooks = new Long2ObjectArraySortedMap<>(); ++ ++ public MCValueType(final String name) { ++ this.name = name; ++ } ++ ++ public void addStructureHook(final int minVersion, final DataHook hook) { ++ this.addStructureHook(minVersion, 0, hook); ++ } ++ ++ public void addStructureHook(final int minVersion, final int versionStep, final DataHook hook) { ++ this.structureHooks.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(hook); ++ } ++ ++ public void addConverter(final DataConverter converter) { ++ MCVersionRegistry.checkVersion(converter.getEncodedVersion()); ++ this.converters.add(converter); ++ this.converters.sort(DataConverter.LOWEST_VERSION_COMPARATOR); ++ } ++ ++ @Override ++ public Object convert(final Object data, final long fromVersion, final long toVersion) { ++ Object ret = null; ++ final List> converters = this.converters; ++ ++ for (int i = 0, len = converters.size(); i < len; ++i) { ++ final DataConverter converter = converters.get(i); ++ final long converterVersion = converter.getEncodedVersion(); ++ ++ if (converterVersion <= fromVersion) { ++ continue; ++ } ++ ++ if (converterVersion > toVersion) { ++ break; ++ } ++ ++ List> hooks = this.structureHooks.getFloor(converterVersion); ++ ++ if (hooks != null) { ++ for (int k = 0, klen = hooks.size(); k < klen; ++k) { ++ final Object replace = hooks.get(k).preHook(ret == null ? data : ret, fromVersion, toVersion); ++ if (replace != null) { ++ ret = replace; ++ } ++ } ++ } ++ ++ final Object converted = converter.convert(ret == null ? data : ret, fromVersion, toVersion); ++ if (converted != null) { ++ ret = converted; ++ } ++ ++ // possibly new data format, update hooks ++ hooks = this.structureHooks.getFloor(toVersion); ++ ++ if (hooks != null) { ++ for (int k = 0, klen = hooks.size(); k < klen; ++k) { ++ final Object replace = hooks.get(k).postHook(ret == null ? data : ret, fromVersion, toVersion); ++ if (replace != null) { ++ ret = replace; ++ } ++ } ++ } ++ } ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f7dced8a47ebdd262ae815ff9bc453312343ce49 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java +@@ -0,0 +1,29 @@ ++package ca.spottedleaf.dataconverter.minecraft.hooks; ++ ++import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.util.NamespaceUtil; ++ ++public class DataHookEnforceNamespacedID implements DataHook, MapType> { ++ ++ private final String path; ++ ++ public DataHookEnforceNamespacedID() { ++ this("id"); ++ } ++ ++ public DataHookEnforceNamespacedID(final String path) { ++ this.path = path; ++ } ++ ++ @Override ++ public MapType preHook(final MapType data, final long fromVersion, final long toVersion) { ++ NamespaceUtil.enforceForPath(data, this.path); ++ return null; ++ } ++ ++ @Override ++ public MapType postHook(final MapType data, final long fromVersion, final long toVersion) { ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7f88487e7db589070512fafef1eb243ae29a379a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.hooks; ++ ++import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; ++import ca.spottedleaf.dataconverter.util.NamespaceUtil; ++ ++public class DataHookValueTypeEnforceNamespaced implements DataHook { ++ ++ @Override ++ public Object preHook(final Object data, final long fromVersion, final long toVersion) { ++ if (data instanceof String) { ++ return NamespaceUtil.correctNamespaceOrNull((String)data); ++ } ++ return null; ++ } ++ ++ @Override ++ public Object postHook(final Object data, final long fromVersion, final long toVersion) { ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..952c64fbc4b21a2d85d66c5183e8094a584e0d17 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java +@@ -0,0 +1,27 @@ ++package ca.spottedleaf.dataconverter.minecraft.util; ++ ++import com.google.gson.JsonObject; ++import net.minecraft.util.GsonHelper; ++ ++public final class ComponentUtils { ++ ++ public static final String EMPTY = createPlainTextComponent(""); ++ ++ public static String createPlainTextComponent(final String text) { ++ final JsonObject ret = new JsonObject(); ++ ++ ret.addProperty("text", text); ++ ++ return GsonHelper.toStableString(ret); ++ } ++ ++ public static String createTranslatableComponent(final String key) { ++ final JsonObject ret = new JsonObject(); ++ ++ ret.addProperty("translate", key); ++ ++ return GsonHelper.toStableString(ret); ++ } ++ ++ private ComponentUtils() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7e8f42eb57c12c885a1c17eafab1c9d9be4d8963 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java +@@ -0,0 +1,159 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V100 { ++ ++ protected static final int VERSION = MCVersions.V15W32A; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType equipment = data.getList("Equipment", ObjectType.MAP); ++ data.remove("Equipment"); ++ ++ if (equipment != null) { ++ if (equipment.size() > 0 && data.getListUnchecked("HandItems") == null) { ++ final ListType handItems = Types.NBT.createEmptyList(); ++ data.setList("HandItems", handItems); ++ handItems.addMap(equipment.getMap(0)); ++ handItems.addMap(Types.NBT.createEmptyMap()); ++ } ++ ++ if (equipment.size() > 1 && data.getListUnchecked("ArmorItems") == null) { ++ final ListType armorItems = Types.NBT.createEmptyList(); ++ data.setList("ArmorItems", armorItems); ++ for (int i = 1; i < Math.min(equipment.size(), 5); ++i) { ++ armorItems.addMap(equipment.getMap(i)); ++ } ++ } ++ } ++ ++ final ListType dropChances = data.getList("DropChances", ObjectType.FLOAT); ++ data.remove("DropChances"); ++ ++ if (dropChances != null) { ++ if (data.getListUnchecked("HandDropChances") == null) { ++ final ListType handDropChances = Types.NBT.createEmptyList(); ++ data.setList("HandDropChances", handDropChances); ++ if (0 < dropChances.size()) { ++ handDropChances.addFloat(dropChances.getFloat(0)); ++ } else { ++ handDropChances.addFloat(0.0F); ++ } ++ handDropChances.addFloat(0.0F); ++ } ++ ++ if (data.getListUnchecked("ArmorDropChances") == null) { ++ final ListType armorDropChances = Types.NBT.createEmptyList(); ++ data.setList("ArmorDropChances", armorDropChances); ++ for (int i = 1; i < 5; ++i) { ++ if (i < dropChances.size()) { ++ armorDropChances.addFloat(dropChances.getFloat(i)); ++ } else { ++ armorDropChances.addFloat(0.0F); ++ } ++ } ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ registerMob("ArmorStand"); ++ registerMob("Creeper"); ++ registerMob("Skeleton"); ++ registerMob("Spider"); ++ registerMob("Giant"); ++ registerMob("Zombie"); ++ registerMob("Slime"); ++ registerMob("Ghast"); ++ registerMob("PigZombie"); ++ registerMob("Enderman"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Enderman", new DataWalkerBlockNames("carried")); ++ registerMob("CaveSpider"); ++ registerMob("Silverfish"); ++ registerMob("Blaze"); ++ registerMob("LavaSlime"); ++ registerMob("EnderDragon"); ++ registerMob("WitherBoss"); ++ registerMob("Bat"); ++ registerMob("Witch"); ++ registerMob("Endermite"); ++ registerMob("Guardian"); ++ registerMob("Pig"); ++ registerMob("Sheep"); ++ registerMob("Cow"); ++ registerMob("Chicken"); ++ registerMob("Squid"); ++ registerMob("Wolf"); ++ registerMob("MushroomCow"); ++ registerMob("SnowMan"); ++ registerMob("Ozelot"); ++ registerMob("VillagerGolem"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItems("ArmorItem", "SaddleItem")); ++ registerMob("Rabbit"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); ++ ++ final MapType offers = data.getMap("Offers"); ++ if (offers != null) { ++ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); ++ if (recipes != null) { ++ for (int i = 0, len = recipes.size(); i < len; ++i) { ++ final MapType recipe = recipes.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); ++ } ++ } ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); ++ ++ return null; ++ }); ++ registerMob("Shulker"); ++ ++ MCTypeRegistry.STRUCTURE.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final ListType entities = data.getList("entities", ObjectType.MAP); ++ if (entities != null) { ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, entities.getMap(i), "nbt", fromVersion, toVersion); ++ } ++ } ++ ++ final ListType blocks = data.getList("blocks", ObjectType.MAP); ++ if (blocks != null) { ++ for (int i = 0, len = blocks.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.TILE_ENTITY, blocks.getMap(i), "nbt", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, data, "palette", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++ ++ private V100() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java +new file mode 100644 +index 0000000000000000000000000000000000000000..98fbbf59ca00dbf58982d93f561f7d5f5b81951f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java +@@ -0,0 +1,73 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.gson.JsonParseException; ++import net.minecraft.network.chat.CommonComponents; ++import net.minecraft.network.chat.Component; ++import net.minecraft.util.GsonHelper; ++import net.minecraft.util.datafix.fixes.BlockEntitySignTextStrictJsonFix; ++ ++public final class V101 { ++ ++ protected static final int VERSION = MCVersions.V15W32A + 1; ++ ++ protected static void updateLine(final MapType data, final String path) { ++ final String textString = data.getString(path); ++ if (textString == null || textString.isEmpty() || "null".equals(textString)) { ++ data.setString(path, Component.Serializer.toJson(CommonComponents.EMPTY)); ++ return; ++ } ++ ++ Component component = null; ++ ++ if (textString.charAt(0) == '"' && textString.charAt(textString.length() - 1) == '"' ++ || textString.charAt(0) == '{' && textString.charAt(textString.length() - 1) == '}') { ++ try { ++ component = GsonHelper.fromNullableJson(BlockEntitySignTextStrictJsonFix.GSON, textString, Component.class, true); ++ if (component == null) { ++ component = CommonComponents.EMPTY; ++ } ++ } catch (final JsonParseException ignored) {} ++ ++ if (component == null) { ++ try { ++ component = Component.Serializer.fromJson(textString); ++ } catch (final JsonParseException ignored) {} ++ } ++ ++ if (component == null) { ++ try { ++ component = Component.Serializer.fromJsonLenient(textString); ++ } catch (final JsonParseException ignored) {} ++ } ++ ++ if (component == null) { ++ component = Component.literal(textString); ++ } ++ } else { ++ component = Component.literal(textString); ++ } ++ ++ data.setString(path, Component.Serializer.toJson(component)); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("Sign", new DataConverter<>(VERSION) { ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateLine(data, "Text1"); ++ updateLine(data, "Text2"); ++ updateLine(data, "Text3"); ++ updateLine(data, "Text4"); ++ return null; ++ } ++ }); ++ } ++ ++ private V101() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java +new file mode 100644 +index 0000000000000000000000000000000000000000..76374ca99efdf898dee0829fd8eb5fac26dc9a22 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java +@@ -0,0 +1,87 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.mojang.logging.LogUtils; ++import org.slf4j.Logger; ++ ++public final class V102 { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V15W32A + 2; ++ ++ public static void register() { ++ // V102 -> V15W32A + 2 ++ // V102 schema only modifies ITEM_STACK to have only a string ID, but our ITEM_NAME is generic (int or String) so we don't ++ // actually need to update the walker ++ ++ MCTypeRegistry.ITEM_NAME.addConverter(new DataConverter<>(VERSION) { ++ @Override ++ public Object convert(final Object data, final long sourceVersion, final long toVersion) { ++ if (!(data instanceof Number)) { ++ return null; ++ } ++ final int id = ((Number)data).intValue(); ++ final String remap = HelperItemNameV102.getNameFromId(id); ++ if (remap == null) { ++ LOGGER.warn("Unknown legacy integer id (V102) " + id); ++ } ++ return remap == null ? HelperItemNameV102.getNameFromId(0) : remap; ++ } ++ }); ++ ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!data.hasKey("id", ObjectType.NUMBER)) { ++ return null; ++ } ++ ++ final int id = data.getInt("id"); ++ ++ String remap = HelperItemNameV102.getNameFromId(id); ++ if (remap == null) { ++ LOGGER.warn("Unknown legacy integer id (V102) " + id); ++ remap = HelperItemNameV102.getNameFromId(0); ++ } ++ ++ data.setString("id", remap); ++ ++ return null; ++ } ++ }); ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:potion", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final short damage = data.getShort("Damage"); ++ if (damage != 0) { ++ data.setShort("Damage", (short)0); ++ } ++ MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ tag = Types.NBT.createEmptyMap(); ++ data.setMap("tag", tag); ++ } ++ ++ if (!tag.hasKey("Potion", ObjectType.STRING)) { ++ final String converted = HelperItemNameV102.getPotionNameFromId(damage); ++ tag.setString("Potion", converted == null ? "minecraft:water" : converted); ++ if ((damage & 16384) == 16384) { ++ data.setString("id", "minecraft:splash_potion"); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V102() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e251ead28d7d90937ae5871ffac489c1161e6e87 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java +@@ -0,0 +1,45 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1022 { ++ ++ protected static final int VERSION = MCVersions.V17W06A; ++ ++ public static void register() { ++ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType rootVehicle = data.getMap("RootVehicle"); ++ if (rootVehicle != null) { ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, rootVehicle, "Entity", fromVersion, toVersion); ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "EnderItems", fromVersion, toVersion); ++ ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "ShoulderEntityLeft", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "ShoulderEntityRight", fromVersion, toVersion); ++ ++ final MapType recipeBook = data.getMap("recipeBook"); ++ if (recipeBook != null) { ++ WalkerUtils.convertList(MCTypeRegistry.RECIPE, recipeBook, "recipes", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.RECIPE, recipeBook, "toBeDisplayed", fromVersion, toVersion); ++ } ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.HOTBAR.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ for (final String key : data.keys()) { ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, key, fromVersion, toVersion); ++ } ++ ++ return null; ++ }); ++ } ++ ++ private V1022() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java +new file mode 100644 +index 0000000000000000000000000000000000000000..544f6a54041147a8c9ee3ff52c31c480a3696924 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java +@@ -0,0 +1,49 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperSpawnEggNameV105; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V105 { ++ ++ protected static final int VERSION = MCVersions.V15W32C + 1; ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:spawn_egg", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ tag = Types.NBT.createEmptyMap(); ++ } ++ ++ final short damage = data.getShort("Damage"); ++ if (damage != 0) { ++ data.setShort("Damage", (short)0); ++ } ++ ++ MapType entityTag = tag.getMap("EntityTag"); ++ if (entityTag == null) { ++ entityTag = Types.NBT.createEmptyMap(); ++ } ++ ++ if (!entityTag.hasKey("id", ObjectType.STRING)) { ++ final String converted = HelperSpawnEggNameV105.getSpawnNameFromId(damage); ++ if (converted != null) { ++ entityTag.setString("id", converted); ++ tag.setMap("EntityTag", entityTag); ++ data.setMap("tag", tag); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V105() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java +new file mode 100644 +index 0000000000000000000000000000000000000000..951838b0f4f2b4ed82d707706ef15d779f3f41eb +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java +@@ -0,0 +1,84 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V106 { ++ ++ protected static final int VERSION = MCVersions.V15W32C + 2; ++ ++ public static void register() { ++ // V106 -> V15W32C + 2 ++ ++ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ // While all converters for spawners check the id for this version, we don't because spawners exist in minecarts. ooops! Loading a chunk ++ // with a minecart spawner from 1.7.10 in 1.16.5 vanilla will fail to convert! Clearly there was a mistake in how they ++ // used and applied spawner converters. In anycase, do not check the id - we are not guaranteed to be a tile ++ // entity. We can be a regular old minecart spawner. And we know we are a spawner because this is only called from data walkers. ++ ++ final String entityId = data.getString("EntityId"); ++ if (entityId != null) { ++ data.remove("EntityId"); ++ MapType spawnData = data.getMap("SpawnData"); ++ if (spawnData == null) { ++ spawnData = Types.NBT.createEmptyMap(); ++ data.setMap("SpawnData", spawnData); ++ } ++ spawnData.setString("id", entityId.isEmpty() ? "Pig" : entityId); ++ } ++ ++ final ListType spawnPotentials = data.getList("SpawnPotentials", ObjectType.MAP); ++ if (spawnPotentials != null) { ++ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { ++ // convert to standard entity format (it's not a coincidence a walker for spawners is only added ++ // in this version) ++ final MapType spawn = spawnPotentials.getMap(i); ++ final String spawnType = spawn.getString("Type"); ++ if (spawnType == null) { ++ continue; ++ } ++ spawn.remove("Type"); ++ ++ MapType properties = spawn.getMap("Properties"); ++ if (properties == null) { ++ properties = Types.NBT.createEmptyMap(); ++ } else { ++ spawn.remove("Properties"); ++ } ++ ++ properties.setString("id", spawnType); ++ ++ spawn.setMap("Entity", properties); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final ListType spawnPotentials = data.getList("SpawnPotentials", ObjectType.MAP); ++ if (spawnPotentials != null) { ++ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { ++ final MapType spawnPotential = spawnPotentials.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, spawnPotential, "Entity", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "SpawnData", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++ ++ private V106() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java +new file mode 100644 +index 0000000000000000000000000000000000000000..aa8c8d22ee2a77604d923b62f5a93ede9b3f333f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java +@@ -0,0 +1,44 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V107 { ++ ++ protected static final int VERSION = MCVersions.V15W32C + 3; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("Minecart", new DataConverter<>(VERSION) { ++ protected final String[] MINECART_IDS = new String[] { ++ "MinecartRideable", // 0 ++ "MinecartChest", // 1 ++ "MinecartFurnace", // 2 ++ "MinecartTNT", // 3 ++ "MinecartSpawner", // 4 ++ "MinecartHopper", // 5 ++ "MinecartCommandBlock" // 6 ++ }; ++ // Vanilla does not use all of the IDs here. The legacy (pre DFU) code does, so I'm going to use them. ++ // No harm in catching more cases here. ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ String newId = "MinecartRideable"; // dfl ++ final int type = data.getInt("Type"); ++ data.remove("Type"); ++ ++ if (type >= 0 && type < MINECART_IDS.length) { ++ newId = MINECART_IDS[type]; ++ } ++ data.setString("id", newId); ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V107() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6bc4e2939bd26538492a7b94b743957d56ddc575 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java +@@ -0,0 +1,48 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.mojang.logging.LogUtils; ++import org.slf4j.Logger; ++ ++import java.util.UUID; ++ ++public final class V108 { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V15W32C + 4; ++ ++ public static void register() { ++ // Convert String UUID into UUIDMost and UUIDLeast ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String uuidString = data.getString("UUID"); ++ ++ if (uuidString == null) { ++ return null; ++ } ++ data.remove("UUID"); ++ ++ final UUID uuid; ++ try { ++ uuid = UUID.fromString(uuidString); ++ } catch (final Exception ex) { ++ LOGGER.warn("Failed to parse UUID for legacy entity (V108): " + uuidString, ex); ++ return null; ++ } ++ ++ data.setLong("UUIDMost", uuid.getMostSignificantBits()); ++ data.setLong("UUIDLeast", uuid.getLeastSignificantBits()); ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V108() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5e0e52fa2d8988ca973f8a97b2374a8c3d4ef80c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java +@@ -0,0 +1,83 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.common.collect.Sets; ++import java.util.Set; ++ ++public final class V109 { ++ ++ protected static final int VERSION = MCVersions.V15W32C + 5; ++ ++ // DFU declares this exact field but leaves it unused. Not sure why, legacy conversion system checked if the ID matched. ++ // I'm going to leave it here unused as well, just in case it's needed in the future. ++ protected static final Set ENTITIES = Sets.newHashSet( ++ "ArmorStand", ++ "Bat", ++ "Blaze", ++ "CaveSpider", ++ "Chicken", ++ "Cow", ++ "Creeper", ++ "EnderDragon", ++ "Enderman", ++ "Endermite", ++ "EntityHorse", ++ "Ghast", ++ "Giant", ++ "Guardian", ++ "LavaSlime", ++ "MushroomCow", ++ "Ozelot", ++ "Pig", ++ "PigZombie", ++ "Rabbit", ++ "Sheep", ++ "Shulker", ++ "Silverfish", ++ "Skeleton", ++ "Slime", ++ "SnowMan", ++ "Spider", ++ "Squid", ++ "Villager", ++ "VillagerGolem", ++ "Witch", ++ "WitherBoss", ++ "Wolf", ++ "Zombie" ++ ); ++ ++ public static void register() { ++ // Converts health to be in float, and cleans up whatever the hell was going on with HealF and Health... ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final Number healF = data.getNumber("HealF"); ++ final Number heal = data.getNumber("Health"); ++ ++ final float newHealth; ++ ++ if (healF != null) { ++ data.remove("HealF"); ++ newHealth = healF.floatValue(); ++ } else { ++ if (heal == null) { ++ return null; ++ } ++ ++ newHealth = heal.floatValue(); ++ } ++ ++ data.setFloat("Health", newHealth); ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V109() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9771810a1f1cbf760fd9a8a5fd575f6052f40ea9 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java +@@ -0,0 +1,39 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V110 { ++ ++ protected static final int VERSION = MCVersions.V15W32C + 6; ++ ++ public static void register() { ++ // Moves the Saddle boolean to be an actual saddle item. Note: The data walker for the SaddleItem exists ++ // in V99, it doesn't need to be added here. ++ MCTypeRegistry.ENTITY.addConverterForId("EntityHorse", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!data.getBoolean("Saddle") || data.hasKey("SaddleItem", ObjectType.MAP)) { ++ return null; ++ } ++ ++ final MapType saddleItem = Types.NBT.createEmptyMap(); ++ data.remove("Saddle"); ++ data.setMap("SaddleItem", saddleItem); ++ ++ saddleItem.setString("id", "minecraft:saddle"); ++ saddleItem.setByte("Count", (byte)1); ++ saddleItem.setShort("Damage", (short)0); ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V110() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5bae7effda7761a3f2a0a2ce550d867cb2c18b99 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java +@@ -0,0 +1,67 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V111 { ++ ++ protected static final int VERSION = MCVersions.V15W33B; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("Painting", new EntityRotationFix("Painting")); ++ MCTypeRegistry.ENTITY.addConverterForId("ItemFrame", new EntityRotationFix("ItemFrame")); ++ } ++ ++ private V111() {} ++ ++ protected static final class EntityRotationFix extends DataConverter, MapType> { ++ ++ private static final int[][] DIRECTIONS = new int[][] { ++ {0, 0, 1}, ++ {-1, 0, 0}, ++ {0, 0, -1}, ++ {1, 0, 0} ++ }; ++ ++ protected final String id; ++ ++ public EntityRotationFix(final String id) { ++ super(VERSION); ++ this.id = id; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getNumber("Facing") != null) { ++ return null; ++ } ++ ++ final Number direction = data.getNumber("Direction"); ++ final int facing; ++ if (direction != null) { ++ data.remove("Direction"); ++ facing = direction.intValue() % DIRECTIONS.length; ++ final int[] offsets = DIRECTIONS[facing]; ++ data.setInt("TileX", data.getInt("TileX") + offsets[0]); ++ data.setInt("TileY", data.getInt("TileY") + offsets[1]); ++ data.setInt("TileZ", data.getInt("TileZ") + offsets[2]); ++ if ("ItemFrame".equals(data.getString("id"))) { ++ final Number rotation = data.getNumber("ItemRotation"); ++ if (rotation != null) { ++ data.setByte("ItemRotation", (byte)(rotation.byteValue() * 2)); ++ } ++ } ++ } else { ++ facing = data.getByte("Dir") % DIRECTIONS.length; ++ data.remove("Dir"); ++ } ++ ++ data.setByte("Facing", (byte)facing); ++ ++ return null; ++ } ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a6d3c28e8c97b53f388c03ccad3449390937d3b2 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java +@@ -0,0 +1,102 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V1125 { ++ ++ protected static final int VERSION = MCVersions.V17W15A; ++ protected static final int BED_BLOCK_ID = 416; ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ final int chunkX = level.getInt("xPos"); ++ final int chunkZ = level.getInt("zPos"); ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections == null) { ++ return null; ++ } ++ ++ ListType tileEntities = level.getList("TileEntities", ObjectType.MAP); ++ if (tileEntities == null) { ++ tileEntities = Types.NBT.createEmptyList(); ++ level.setList("TileEntities", tileEntities); ++ } ++ ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ final byte sectionY = section.getByte("Y"); ++ final byte[] blocks = section.getBytes("Blocks"); ++ ++ if (blocks == null) { ++ continue; ++ } ++ ++ for (int blockIndex = 0; blockIndex < blocks.length; ++blockIndex) { ++ if (BED_BLOCK_ID != ((blocks[blockIndex] & 255) << 4)) { ++ continue; ++ } ++ ++ final int localX = blockIndex & 15; ++ final int localZ = (blockIndex >> 4) & 15; ++ final int localY = (blockIndex >> 8) & 15; ++ ++ final MapType newTile = Types.NBT.createEmptyMap(); ++ newTile.setString("id", "minecraft:bed"); ++ newTile.setInt("x", localX + (chunkX << 4)); ++ newTile.setInt("y", localY + (sectionY << 4)); ++ newTile.setInt("z", localZ + (chunkZ << 4)); ++ newTile.setShort("color", (short)14); // Red ++ ++ tileEntities.addMap(newTile); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:bed", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getShort("Damage") == 0) { ++ data.setShort("Damage", (short)14); // Red ++ } ++ ++ return null; ++ } ++ }); ++ ++ ++ MCTypeRegistry.ADVANCEMENTS.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertKeys(MCTypeRegistry.BIOME, data.getMap("minecraft:adventure/adventuring_time"), "criteria", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, data.getMap("minecraft:adventure/kill_a_mob"), "criteria", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, data.getMap("minecraft:adventure/kill_all_mobs"), "criteria", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, data.getMap("minecraft:adventure/bred_all_animals"), "criteria", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ // Enforce namespacing for ids ++ MCTypeRegistry.BIOME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); ++ } ++ ++ private V1125() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8b93bba2b084e20d346461f53d2f7662c3d6238b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java +@@ -0,0 +1,41 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V113 { ++ ++ protected static final int VERSION = MCVersions.V15W33C + 1; ++ ++ protected static void checkList(final MapType data, final String id, final int requiredLength) { ++ final ListType list = data.getList(id, ObjectType.FLOAT); ++ if (list != null && list.size() == requiredLength) { ++ for (int i = 0; i < requiredLength; ++i) { ++ if (list.getFloat(i) != 0.0F) { ++ return; ++ } ++ } ++ } ++ ++ data.remove(id); ++ } ++ ++ public static void register() { ++ // Removes "HandDropChances" and "ArmorDropChances" if they're empty. ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ checkList(data, "HandDropChances", 2); ++ checkList(data, "ArmorDropChances", 4); ++ return null; ++ } ++ }); ++ } ++ ++ private V113() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ac390a6111ba1a4aae3d5726747f60f4929fa254 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java +@@ -0,0 +1,177 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++ ++public final class V1344 { ++ ++ protected static final int VERSION = MCVersions.V1_12_2 + 1; ++ ++ private static final Int2ObjectOpenHashMap BUTTON_ID_TO_NAME = new Int2ObjectOpenHashMap<>(); ++ static { ++ BUTTON_ID_TO_NAME.put(0, "key.unknown"); ++ BUTTON_ID_TO_NAME.put(11, "key.0"); ++ BUTTON_ID_TO_NAME.put(2, "key.1"); ++ BUTTON_ID_TO_NAME.put(3, "key.2"); ++ BUTTON_ID_TO_NAME.put(4, "key.3"); ++ BUTTON_ID_TO_NAME.put(5, "key.4"); ++ BUTTON_ID_TO_NAME.put(6, "key.5"); ++ BUTTON_ID_TO_NAME.put(7, "key.6"); ++ BUTTON_ID_TO_NAME.put(8, "key.7"); ++ BUTTON_ID_TO_NAME.put(9, "key.8"); ++ BUTTON_ID_TO_NAME.put(10, "key.9"); ++ BUTTON_ID_TO_NAME.put(30, "key.a"); ++ BUTTON_ID_TO_NAME.put(40, "key.apostrophe"); ++ BUTTON_ID_TO_NAME.put(48, "key.b"); ++ BUTTON_ID_TO_NAME.put(43, "key.backslash"); ++ BUTTON_ID_TO_NAME.put(14, "key.backspace"); ++ BUTTON_ID_TO_NAME.put(46, "key.c"); ++ BUTTON_ID_TO_NAME.put(58, "key.caps.lock"); ++ BUTTON_ID_TO_NAME.put(51, "key.comma"); ++ BUTTON_ID_TO_NAME.put(32, "key.d"); ++ BUTTON_ID_TO_NAME.put(211, "key.delete"); ++ BUTTON_ID_TO_NAME.put(208, "key.down"); ++ BUTTON_ID_TO_NAME.put(18, "key.e"); ++ BUTTON_ID_TO_NAME.put(207, "key.end"); ++ BUTTON_ID_TO_NAME.put(28, "key.enter"); ++ BUTTON_ID_TO_NAME.put(13, "key.equal"); ++ BUTTON_ID_TO_NAME.put(1, "key.escape"); ++ BUTTON_ID_TO_NAME.put(33, "key.f"); ++ BUTTON_ID_TO_NAME.put(59, "key.f1"); ++ BUTTON_ID_TO_NAME.put(68, "key.f10"); ++ BUTTON_ID_TO_NAME.put(87, "key.f11"); ++ BUTTON_ID_TO_NAME.put(88, "key.f12"); ++ BUTTON_ID_TO_NAME.put(100, "key.f13"); ++ BUTTON_ID_TO_NAME.put(101, "key.f14"); ++ BUTTON_ID_TO_NAME.put(102, "key.f15"); ++ BUTTON_ID_TO_NAME.put(103, "key.f16"); ++ BUTTON_ID_TO_NAME.put(104, "key.f17"); ++ BUTTON_ID_TO_NAME.put(105, "key.f18"); ++ BUTTON_ID_TO_NAME.put(113, "key.f19"); ++ BUTTON_ID_TO_NAME.put(60, "key.f2"); ++ BUTTON_ID_TO_NAME.put(61, "key.f3"); ++ BUTTON_ID_TO_NAME.put(62, "key.f4"); ++ BUTTON_ID_TO_NAME.put(63, "key.f5"); ++ BUTTON_ID_TO_NAME.put(64, "key.f6"); ++ BUTTON_ID_TO_NAME.put(65, "key.f7"); ++ BUTTON_ID_TO_NAME.put(66, "key.f8"); ++ BUTTON_ID_TO_NAME.put(67, "key.f9"); ++ BUTTON_ID_TO_NAME.put(34, "key.g"); ++ BUTTON_ID_TO_NAME.put(41, "key.grave.accent"); ++ BUTTON_ID_TO_NAME.put(35, "key.h"); ++ BUTTON_ID_TO_NAME.put(199, "key.home"); ++ BUTTON_ID_TO_NAME.put(23, "key.i"); ++ BUTTON_ID_TO_NAME.put(210, "key.insert"); ++ BUTTON_ID_TO_NAME.put(36, "key.j"); ++ BUTTON_ID_TO_NAME.put(37, "key.k"); ++ BUTTON_ID_TO_NAME.put(82, "key.keypad.0"); ++ BUTTON_ID_TO_NAME.put(79, "key.keypad.1"); ++ BUTTON_ID_TO_NAME.put(80, "key.keypad.2"); ++ BUTTON_ID_TO_NAME.put(81, "key.keypad.3"); ++ BUTTON_ID_TO_NAME.put(75, "key.keypad.4"); ++ BUTTON_ID_TO_NAME.put(76, "key.keypad.5"); ++ BUTTON_ID_TO_NAME.put(77, "key.keypad.6"); ++ BUTTON_ID_TO_NAME.put(71, "key.keypad.7"); ++ BUTTON_ID_TO_NAME.put(72, "key.keypad.8"); ++ BUTTON_ID_TO_NAME.put(73, "key.keypad.9"); ++ BUTTON_ID_TO_NAME.put(78, "key.keypad.add"); ++ BUTTON_ID_TO_NAME.put(83, "key.keypad.decimal"); ++ BUTTON_ID_TO_NAME.put(181, "key.keypad.divide"); ++ BUTTON_ID_TO_NAME.put(156, "key.keypad.enter"); ++ BUTTON_ID_TO_NAME.put(141, "key.keypad.equal"); ++ BUTTON_ID_TO_NAME.put(55, "key.keypad.multiply"); ++ BUTTON_ID_TO_NAME.put(74, "key.keypad.subtract"); ++ BUTTON_ID_TO_NAME.put(38, "key.l"); ++ BUTTON_ID_TO_NAME.put(203, "key.left"); ++ BUTTON_ID_TO_NAME.put(56, "key.left.alt"); ++ BUTTON_ID_TO_NAME.put(26, "key.left.bracket"); ++ BUTTON_ID_TO_NAME.put(29, "key.left.control"); ++ BUTTON_ID_TO_NAME.put(42, "key.left.shift"); ++ BUTTON_ID_TO_NAME.put(219, "key.left.win"); ++ BUTTON_ID_TO_NAME.put(50, "key.m"); ++ BUTTON_ID_TO_NAME.put(12, "key.minus"); ++ BUTTON_ID_TO_NAME.put(49, "key.n"); ++ BUTTON_ID_TO_NAME.put(69, "key.num.lock"); ++ BUTTON_ID_TO_NAME.put(24, "key.o"); ++ BUTTON_ID_TO_NAME.put(25, "key.p"); ++ BUTTON_ID_TO_NAME.put(209, "key.page.down"); ++ BUTTON_ID_TO_NAME.put(201, "key.page.up"); ++ BUTTON_ID_TO_NAME.put(197, "key.pause"); ++ BUTTON_ID_TO_NAME.put(52, "key.period"); ++ BUTTON_ID_TO_NAME.put(183, "key.print.screen"); ++ BUTTON_ID_TO_NAME.put(16, "key.q"); ++ BUTTON_ID_TO_NAME.put(19, "key.r"); ++ BUTTON_ID_TO_NAME.put(205, "key.right"); ++ BUTTON_ID_TO_NAME.put(184, "key.right.alt"); ++ BUTTON_ID_TO_NAME.put(27, "key.right.bracket"); ++ BUTTON_ID_TO_NAME.put(157, "key.right.control"); ++ BUTTON_ID_TO_NAME.put(54, "key.right.shift"); ++ BUTTON_ID_TO_NAME.put(220, "key.right.win"); ++ BUTTON_ID_TO_NAME.put(31, "key.s"); ++ BUTTON_ID_TO_NAME.put(70, "key.scroll.lock"); ++ BUTTON_ID_TO_NAME.put(39, "key.semicolon"); ++ BUTTON_ID_TO_NAME.put(53, "key.slash"); ++ BUTTON_ID_TO_NAME.put(57, "key.space"); ++ BUTTON_ID_TO_NAME.put(20, "key.t"); ++ BUTTON_ID_TO_NAME.put(15, "key.tab"); ++ BUTTON_ID_TO_NAME.put(22, "key.u"); ++ BUTTON_ID_TO_NAME.put(200, "key.up"); ++ BUTTON_ID_TO_NAME.put(47, "key.v"); ++ BUTTON_ID_TO_NAME.put(17, "key.w"); ++ BUTTON_ID_TO_NAME.put(45, "key.x"); ++ BUTTON_ID_TO_NAME.put(21, "key.y"); ++ BUTTON_ID_TO_NAME.put(44, "key.z"); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ for (final String key : data.keys()) { ++ if (!key.startsWith("key_")) { ++ continue; ++ } ++ final String value = data.getString(key); ++ final int code; ++ try { ++ code = Integer.parseInt(value); ++ } catch (final NumberFormatException ex) { ++ continue; ++ } ++ ++ final String newEntry; ++ ++ if (code < 0) { ++ final int mouseCode = code + 100; ++ switch (mouseCode) { ++ case 0: ++ newEntry = "key.mouse.left"; ++ break; ++ case 1: ++ newEntry = "key.mouse.right"; ++ break; ++ case 2: ++ newEntry = "key.mouse.middle"; ++ break; ++ default: ++ newEntry = "key.mouse." + (mouseCode + 1); ++ break; ++ } ++ } else { ++ newEntry = BUTTON_ID_TO_NAME.getOrDefault(code, "key.unknown"); ++ } ++ ++ // No CMEs occur for existing entries in maps. ++ data.setString(key, newEntry); ++ } ++ return null; ++ } ++ }); ++ } ++ ++ private V1344() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java +new file mode 100644 +index 0000000000000000000000000000000000000000..764a56fda5ee909ac47a0c1b3b581c8c26deb591 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java +@@ -0,0 +1,61 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V135 { ++ ++ protected static final int VERSION = MCVersions.V15W40B + 1; ++ ++ public static void register() { ++ // In this update they changed the "Riding" value to be "Passengers", which is now a list. So it added ++ // support for multiple entities riding. Of course, Riding and Passenger are opposites - so it also will ++ // switch the data layout to be from highest rider to lowest rider, in terms of depth. ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(MapType data, final long sourceVersion, final long toVersion) { ++ MapType ret = null; ++ while (data.hasKey("Riding", ObjectType.MAP)) { ++ final MapType riding = data.getMap("Riding"); ++ data.remove("Riding"); ++ ++ final ListType passengers = Types.NBT.createEmptyList(); ++ riding.setList("Passengers", passengers); ++ passengers.addMap(data); ++ ++ ret = data = riding; ++ } ++ ++ return ret; ++ } ++ }); ++ ++ ++ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, new DataWalkerItemLists("Inventory", "EnderItems")); ++ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType rootVehicle = data.getMap("RootVehicle"); ++ if (rootVehicle != null) { ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, rootVehicle, "Entity", fromVersion, toVersion); ++ } ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.ENTITY.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "Passengers", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ } ++ ++ private V135() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java +new file mode 100644 +index 0000000000000000000000000000000000000000..451e837349e10f1e76ac7d9f5d49cbe0ff630f4d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++ ++public final class V143 { ++ ++ protected static final int VERSION = MCVersions.V15W44B; ++ ++ public static void register() { ++ ConverterAbstractEntityRename.register(VERSION, (final String input) -> { ++ return "TippedArrow".equals(input) ? "Arrow" : null; ++ }); ++ } ++ ++ private V143() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fc0ece569baed94bbf3cbbaa21a397fdc37e51e8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java +@@ -0,0 +1,36 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1446 { ++ ++ protected static final int VERSION = MCVersions.V17W43B + 1; ++ ++ public static void register() { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ for (final String key : data.keys()) { ++ if (!key.startsWith("key_")) { ++ continue; ++ } ++ ++ final String value = data.getString(key); ++ ++ if (value.startsWith("key.mouse") || value.startsWith("scancode.")) { ++ continue; ++ } ++ ++ data.setString(key, "key.keyboard." + value.substring("key.".length())); ++ } ++ return null; ++ } ++ }); ++ } ++ ++ private V1446() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java +new file mode 100644 +index 0000000000000000000000000000000000000000..711222cd33ee557b7f3d1f6ae73ad45d1caf6768 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java +@@ -0,0 +1,25 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1450 { ++ ++ protected static final int VERSION = MCVersions.V17W46A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType ret = HelperBlockFlatteningV1450.flattenNBT(data); ++ return ret == data ? null : ret.copy(); // copy to avoid problems with later state datafixers ++ } ++ }); ++ } ++ ++ private V1450() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java +new file mode 100644 +index 0000000000000000000000000000000000000000..aff3f47430cc85d12080c116610e4328e31acbe6 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java +@@ -0,0 +1,513 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.chunk.ConverterFlattenChunk; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenItemStack; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenSpawnEgg; ++import ca.spottedleaf.dataconverter.minecraft.converters.stats.ConverterFlattenStats; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterFlattenEntity; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity.DataWalkerTileEntities; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.common.base.Splitter; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.util.datafix.fixes.BlockStateData; ++import net.minecraft.util.datafix.fixes.EntityBlockStateFix; ++import org.apache.commons.lang3.math.NumberUtils; ++import java.util.Iterator; ++import java.util.List; ++import java.util.stream.Collectors; ++import java.util.stream.StreamSupport; ++ ++public final class V1451 { ++ ++ protected static final int VERSION = MCVersions.V17W47A; ++ ++ public static String packWithDot(final String string) { ++ final ResourceLocation resourceLocation = ResourceLocation.tryParse(string); ++ return resourceLocation != null ? resourceLocation.getNamespace() + "." + resourceLocation.getPath() : string; ++ } ++ ++ public static void register() { ++ // V0 ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, 0, "minecraft:trapped_chest", new DataWalkerItemLists("Items")); ++ ++ // V1 ++ MCTypeRegistry.CHUNK.addStructureConverter(new ConverterFlattenChunk()); ++ ++ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, 1, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); ++ ++ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); ++ if (tileTicks != null) { ++ for (int i = 0, len = tileTicks.size(); i < len; ++i) { ++ final MapType tileTick = tileTicks.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); ++ } ++ } ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section, "Palette", fromVersion, toVersion); ++ } ++ } ++ ++ return null; ++ }); ++ ++ // V2 ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:piston", new DataConverter<>(VERSION, 2) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int blockId = data.getInt("blockId"); ++ final int blockData = data.getInt("blockData") & 15; ++ ++ data.remove("blockId"); ++ data.remove("blockData"); ++ ++ data.setMap("blockState", HelperBlockFlatteningV1450.getNBTForId((blockId << 4) | blockData).copy()); // copy to avoid problems with later state datafixers ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, 2, "minecraft:piston", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "blockState")); ++ ++ // V3 ++ ConverterFlattenEntity.register(); ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:filled_map", new DataConverter<>(VERSION, 3) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ tag = Types.NBT.createEmptyMap(); ++ data.setMap("tag", tag); ++ } ++ ++ if (!tag.hasKey("map", ObjectType.NUMBER)) { // This if is from CB. as usual, no documentation from CB. I'm guessing it just wants to avoid possibly overwriting it. seems fine. ++ tag.setInt("map", data.getInt("Damage")); ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:potion", new DataWalkerItems("Potion")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:arrow", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:enderman", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:enderman", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "carriedBlockState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:falling_block", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "BlockState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:falling_block", new DataWalkerTileEntities("TileEntityData")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:spectral_arrow", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:chest_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:chest_minecart", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:commandblock_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:furnace_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:hopper_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:hopper_minecart", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:spawner_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:spawner_minecart", (final MapType data, final long fromVersion, final long toVersion) -> { ++ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); ++ return null; ++ }); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:tnt_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ ++ // V4 ++ MCTypeRegistry.BLOCK_NAME.addConverter(new DataConverter<>(VERSION, 4) { ++ @Override ++ public Object convert(final Object data, final long sourceVersion, final long toVersion) { ++ if (data instanceof Number) { ++ return HelperBlockFlatteningV1450.getNameForId(((Number)data).intValue()); ++ } else if (data instanceof String) { ++ return HelperBlockFlatteningV1450.getNewBlockName((String)data); // structure hook ensured data is namespaced ++ } ++ return null; ++ } ++ }); ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new ConverterFlattenItemStack()); ++ ++ // V5 ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:spawn_egg", new ConverterFlattenSpawnEgg(VERSION, 5)); ++ /* This datafixer has been disabled because the collar colour handler did not change from 1.12 -> 1.13 at all. ++ // So clearly somebody fucked up. This fixes wolf colours incorrectly converting between versions ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:wolf", new DataConverter<>(VERSION, 5) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final Number colour = data.getNumber("CollarColor"); ++ ++ if (colour != null) { ++ data.setByte("CollarColor", (byte)(15 - colour.intValue())); ++ } ++ ++ return null; ++ } ++ }); ++ */ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:banner", new DataConverter<>(VERSION, 5) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final Number base = data.getNumber("Base"); ++ if (base != null) { ++ data.setInt("Base", 15 - base.intValue()); ++ } ++ ++ final ListType patterns = data.getList("Patterns", ObjectType.MAP); ++ if (patterns != null) { ++ for (int i = 0, len = patterns.size(); i < len; ++i) { ++ final MapType pattern = patterns.getMap(i); ++ final Number colour = pattern.getNumber("Color"); ++ if (colour != null) { ++ pattern.setInt("Color", 15 - colour.intValue()); ++ } ++ } ++ } ++ ++ return null; ++ } ++ }); ++ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION, 5) { ++ private final Splitter SPLITTER = Splitter.on(';').limit(5); ++ private final Splitter LAYER_SPLITTER = Splitter.on(','); ++ private final Splitter OLD_AMOUNT_SPLITTER = Splitter.on('x').limit(2); ++ private final Splitter AMOUNT_SPLITTER = Splitter.on('*').limit(2); ++ private final Splitter BLOCK_SPLITTER = Splitter.on(':').limit(3); ++ ++ // idk man i just copy and pasted this one ++ private String fixGeneratorSettings(final String generatorSettings) { ++ if (generatorSettings.isEmpty()) { ++ return "minecraft:bedrock,2*minecraft:dirt,minecraft:grass_block;1;village"; ++ } else { ++ Iterator iterator = SPLITTER.split(generatorSettings).iterator(); ++ String string2 = (String)iterator.next(); ++ int j; ++ String string4; ++ if (iterator.hasNext()) { ++ j = NumberUtils.toInt(string2, 0); ++ string4 = (String)iterator.next(); ++ } else { ++ j = 0; ++ string4 = string2; ++ } ++ ++ if (j >= 0 && j <= 3) { ++ StringBuilder stringBuilder = new StringBuilder(); ++ Splitter splitter = j < 3 ? OLD_AMOUNT_SPLITTER : AMOUNT_SPLITTER; ++ stringBuilder.append((String) StreamSupport.stream(LAYER_SPLITTER.split(string4).spliterator(), false).map((stringx) -> { ++ List list = splitter.splitToList(stringx); ++ int k; ++ String string3; ++ if (list.size() == 2) { ++ k = NumberUtils.toInt((String)list.get(0)); ++ string3 = (String)list.get(1); ++ } else { ++ k = 1; ++ string3 = (String)list.get(0); ++ } ++ ++ List list2 = BLOCK_SPLITTER.splitToList(string3); ++ int l = ((String)list2.get(0)).equals("minecraft") ? 1 : 0; ++ String string5 = (String)list2.get(l); ++ int m = j == 3 ? EntityBlockStateFix.getBlockId("minecraft:" + string5) : NumberUtils.toInt(string5, 0); ++ int n = l + 1; ++ int o = list2.size() > n ? NumberUtils.toInt((String)list2.get(n), 0) : 0; ++ return (k == 1 ? "" : k + "*") + BlockStateData.getTag(m << 4 | o).get("Name").asString(""); ++ }).collect(Collectors.joining(","))); ++ ++ while(iterator.hasNext()) { ++ stringBuilder.append(';').append((String)iterator.next()); ++ } ++ ++ return stringBuilder.toString(); ++ } else { ++ return "minecraft:bedrock,2*minecraft:dirt,minecraft:grass_block;1;village"; ++ } ++ } ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!"flat".equalsIgnoreCase(data.getString("generatorName"))) { ++ return null; ++ } ++ ++ final String generatorOptions = data.getString("generatorOptions"); ++ if (generatorOptions == null) { ++ return null; ++ } ++ ++ data.setString("generatorOptions", this.fixGeneratorSettings(generatorOptions)); ++ ++ return null; ++ } ++ }); ++ ++ // V6 ++ MCTypeRegistry.STATS.addStructureConverter(ConverterFlattenStats.makeStatsConverter()); ++ MCTypeRegistry.OBJECTIVE.addStructureConverter(ConverterFlattenStats.makeObjectiveConverter()); ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jukebox", new DataConverter<>(VERSION, 6) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int record = data.getInt("Record"); ++ if (record <= 0) { ++ return null; ++ } ++ ++ data.remove("Record"); ++ ++ final String newItemId = ConverterFlattenItemStack.flattenItem(HelperItemNameV102.getNameFromId(record), 0); ++ if (newItemId == null) { ++ return null; ++ } ++ ++ final MapType recordItem = Types.NBT.createEmptyMap(); ++ data.setMap("RecordItem", recordItem); ++ ++ recordItem.setString("id", newItemId); ++ recordItem.setByte("Count", (byte)1); ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.STATS.addStructureWalker(VERSION, 6, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType stats = data.getMap("stats"); ++ if (stats == null) { ++ return null; ++ } ++ ++ WalkerUtils.convertKeys(MCTypeRegistry.BLOCK_NAME, stats, "minecraft:mined", fromVersion, toVersion); ++ ++ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:crafted", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:used", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:broken", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:picked_up", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:dropped", fromVersion, toVersion); ++ ++ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, stats, "minecraft:killed", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, stats, "minecraft:killed_by", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.OBJECTIVE.addStructureHook(VERSION, 6, new DataHook<>() { ++ @Override ++ public MapType preHook(final MapType data, final long fromVersion, final long toVersion) { ++ // unpack ++ final String criteriaName = data.getString("CriteriaName"); ++ String type; ++ String id; ++ ++ if (criteriaName != null) { ++ final int index = criteriaName.indexOf(':'); ++ if (index < 0) { ++ type = "_special"; ++ id = criteriaName; ++ } else { ++ try { ++ type = ResourceLocation.of(criteriaName.substring(0, index), '.').toString(); ++ id = ResourceLocation.of(criteriaName.substring(index + 1), '.').toString(); ++ } catch (final Exception ex) { ++ type = "_special"; ++ id = criteriaName; ++ } ++ } ++ } else { ++ type = null; ++ id = null; ++ } ++ ++ if (type != null && id != null) { ++ final MapType criteriaType = Types.NBT.createEmptyMap(); ++ data.setMap("CriteriaType", criteriaType); ++ ++ criteriaType.setString("type", type); ++ criteriaType.setString("id", id); ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public MapType postHook(final MapType data, final long fromVersion, final long toVersion) { ++ // repack ++ final MapType criteriaType = data.getMap("CriteriaType"); ++ ++ final String newName; ++ if (criteriaType == null) { ++ newName = null; ++ } else { ++ final String type = criteriaType.getString("type"); ++ final String id = criteriaType.getString("id"); ++ if (type != null && id != null) { ++ if ("_special".equals(type)) { ++ newName = id; ++ } else { ++ newName = packWithDot(type) + ":" + packWithDot(id); ++ } ++ } else { ++ newName = null; ++ } ++ } ++ ++ if (newName != null) { ++ data.remove("CriteriaType"); ++ data.setString("CriteriaName", newName); ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.OBJECTIVE.addStructureWalker(VERSION, 6, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType criteriaType = data.getMap("CriteriaType"); ++ if (criteriaType == null) { ++ return null; ++ } ++ ++ final String type = criteriaType.getString("type"); ++ ++ if (type == null) { ++ return null; ++ } ++ ++ switch (type) { ++ case "minecraft:mined": { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, criteriaType, "id", fromVersion, toVersion); ++ break; ++ } ++ ++ case "minecraft:crafted": ++ case "minecraft:used": ++ case "minecraft:broken": ++ case "minecraft:picked_up": ++ case "minecraft:dropped": { ++ WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, criteriaType, "id", fromVersion, toVersion); ++ break; ++ } ++ ++ case "minecraft:killed": ++ case "minecraft:killed_by": { ++ WalkerUtils.convert(MCTypeRegistry.ENTITY_NAME, criteriaType, "id", fromVersion, toVersion); ++ break; ++ } ++ } ++ ++ return null; ++ }); ++ ++ ++ // V7 ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION, 7) { ++ private static void convertToBlockState(final MapType data, final String path) { ++ final Number number = data.getNumber(path); ++ if (number == null) { ++ return; ++ } ++ ++ data.setMap(path, HelperBlockFlatteningV1450.getNBTForId(number.intValue() << 4).copy()); // copy to avoid problems with later state datafixers ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType children = data.getList("Children", ObjectType.MAP); ++ if (children == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = children.size(); i < len; ++i) { ++ final MapType child = children.getMap(i); ++ ++ final String id = child.getString("id"); ++ ++ switch (id) { ++ case "ViF": ++ convertToBlockState(child, "CA"); ++ convertToBlockState(child, "CB"); ++ break; ++ case "ViDF": ++ convertToBlockState(child, "CA"); ++ convertToBlockState(child, "CB"); ++ convertToBlockState(child, "CC"); ++ convertToBlockState(child, "CD"); ++ break; ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ // convert villagers to trade with pumpkins and not the carved pumpkin ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION, 7) { ++ private static void convertPumpkin(final MapType data, final String path) { ++ final MapType item = data.getMap(path); ++ if (item == null) { ++ return; ++ } ++ ++ final String id = item.getString("id"); ++ ++ if (id.equals("minecraft:carved_pumpkin")) { ++ item.setString("id", "minecraft:pumpkin"); ++ } ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType offers = data.getMap("Offers"); ++ if (offers != null) { ++ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); ++ if (recipes != null) { ++ for (int i = 0, len = recipes.size(); i < len; ++i) { ++ final MapType recipe = recipes.getMap(i); ++ ++ convertPumpkin(recipe, "buy"); ++ convertPumpkin(recipe, "buyB"); ++ convertPumpkin(recipe, "sell"); ++ } ++ } ++ } ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureWalker(VERSION, 7, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final ListType list = data.getList("Children", ObjectType.MAP); ++ if (list == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = list.size(); i < len; ++i) { ++ final MapType child = list.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CA", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CB", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CC", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CD", fromVersion, toVersion); ++ } ++ ++ return null; ++ }); ++ } ++ ++ private V1451() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8ca5b9d7292ba9c81f7f0fdfb6ca8fd17f796990 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java +@@ -0,0 +1,38 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1456 { ++ ++ protected static final int VERSION = MCVersions.V17W49B + 1; ++ ++ protected static byte direction2dTo3d(final byte old) { ++ switch (old) { ++ case 0: ++ return 3; ++ case 1: ++ return 4; ++ case 2: ++ default: ++ return 2; ++ case 3: ++ return 5; ++ } ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:item_frame", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.setByte("Facing", direction2dTo3d(data.getByte("Facing"))); ++ return null; ++ } ++ }); ++ } ++ ++ private V1456() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a7cc825c1e5d01ef3eb2fce0931230d936286cb0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java +@@ -0,0 +1,88 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1458 { ++ ++ protected static final int VERSION = MCVersions.V17W50A + 1; ++ ++ public static MapType updateCustomName(final MapType data) { ++ final String customName = data.getString("CustomName", ""); ++ ++ if (customName.isEmpty()) { ++ data.remove("CustomName"); ++ } else { ++ data.setString("CustomName", ComponentUtils.createPlainTextComponent(customName)); ++ } ++ ++ return null; ++ } ++ ++ public static void register() { ++ // From CB ++ MCTypeRegistry.PLAYER.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ return updateCustomName(data); ++ } ++ }); ++ ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if ("minecraft:commandblock_minecart".equals(data.getString("id"))) { ++ return null; ++ } ++ ++ return updateCustomName(data); ++ } ++ }); ++ ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final MapType display = tag.getMap("display"); ++ if (display == null) { ++ return null; ++ } ++ ++ final String name = display.getString("Name"); ++ if (name != null) { ++ display.setString("Name", ComponentUtils.createPlainTextComponent(name)); ++ } else { ++ final String localisedName = display.getString("LocName"); ++ if (localisedName != null) { ++ display.setString("Name", ComponentUtils.createTranslatableComponent(localisedName)); ++ display.remove("LocName"); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.TILE_ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if ("minecraft:command_block".equals(data.getString("id"))) { ++ return null; ++ } ++ ++ return updateCustomName(data); ++ } ++ }); ++ ++ } ++ ++ private V1458() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f68b561b2bb750d5f632f17e538337fa38108472 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java +@@ -0,0 +1,53 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++import net.minecraft.resources.ResourceLocation; ++import java.util.HashMap; ++import java.util.Locale; ++import java.util.Map; ++ ++public final class V1460 { ++ ++ private static final Map MOTIVE_REMAP = new HashMap<>(); ++ ++ static { ++ MOTIVE_REMAP.put("donkeykong", "donkey_kong"); ++ MOTIVE_REMAP.put("burningskull", "burning_skull"); ++ MOTIVE_REMAP.put("skullandroses", "skull_and_roses"); ++ }; ++ ++ protected static final int VERSION = MCVersions.V18W01A + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ private static void registerThrowableProjectile(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerBlockNames("inTile")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:painting", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ String motive = data.getString("Motive"); ++ if (motive != null) { ++ motive = motive.toLowerCase(Locale.ROOT); ++ data.setString("Motive", new ResourceLocation(MOTIVE_REMAP.getOrDefault(motive, motive)).toString()); ++ } ++ return null; ++ } ++ }); ++ ++ // No idea why so many type redefines exist here in Vanilla. nothing about the data structure changed, it's literally a copy of ++ // the existing types. ++ } ++ ++ private V1460() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c4fa8e36fb68a610106cee8bae1af243e51fae2e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java +@@ -0,0 +1,143 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V1466 { ++ ++ protected static final int VERSION = MCVersions.V18W06A; ++ ++ protected static short packOffsetCoordinates(final int x, final int y, final int z) { ++ return (short)((x & 15) | ((y & 15) << 4) | ((z & 15) << 8)); ++ } ++ ++ public static void register() { ++ // There is a rather critical change I've made to this converter: changing the chunk status determination. ++ // In Vanilla, this is determined by whether the terrain has been populated and whether the chunk is lit. ++ // For reference, here is the full status progression (at the time of 18w06a): ++ // empty -> base -> carved -> decorated -> lighted -> mobs_spawned -> finalized -> fullchunk -> postprocessed ++ // So one of those must be picked. ++ // If the chunk is lit and terrain is populated, the Vanilla converter will set the status to "mobs_spawned." ++ // If it is anything else, it will be "empty" ++ // I've changed it to the following: if terrain is populated, it is set to at least decorated. If it is populated ++ // and lit, it is set to "mobs_spawned" ++ // But what if it is not populated? If it is not populated, ignore the lit field - obviously that's just broken. ++ // It can't be lit and not populated. ++ // Let's take a look at chunk generation logic for a chunk that is not populated, or even near a populated chunk. ++ // It actually will generate a chunk up to the "carved" stage. It generates the base terrain, (i.e using noise ++ // to figure out where stone is, dirt, grass) and it will generate caves. Nothing else though. No populators. ++ // So "carved" is the correct stage to use, not empty. Setting it to empty would clobber chunk data, when we don't ++ // need to. If it is populated, at least set it to decorated. If it is lit and populated, set it to mobs_spawned. Else, ++ // it is carved. ++ // This change also fixes the random light check "bug" (really this is Mojang's fault for fucking up the status conversion here) ++ // caused by spigot, which would not set the lit value for some chunks. Now those chunks will not be regenerated. ++ ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ final boolean terrainPopulated = level.getBoolean("TerrainPopulated"); ++ final boolean lightPopulated = level.getBoolean("LightPopulated") || level.getNumber("LightPopulated") == null; ++ final String newStatus = !terrainPopulated ? "carved" : (lightPopulated ? "mobs_spawned" : "decorated"); ++ ++ level.setString("Status", newStatus); ++ level.setBoolean("hasLegacyStructureData", true); ++ ++ // convert biome byte[] into int[] ++ final byte[] biomes = level.getBytes("Biomes"); ++ if (biomes != null) { ++ final int[] newBiomes = new int[256]; ++ for (int i = 0, len = Math.min(newBiomes.length, biomes.length); i < len; ++i) { ++ newBiomes[i] = biomes[i] & 255; ++ } ++ level.setInts("Biomes", newBiomes); ++ } ++ ++ // ProtoChunks have their own dedicated tick list, so we must convert the TileTicks to that. ++ final ListType ticks = level.getList("TileTicks", ObjectType.MAP); ++ if (ticks != null) { ++ final ListType sections = Types.NBT.createEmptyList(); ++ final ListType[] sectionAccess = new ListType[16]; ++ for (int i = 0; i < sectionAccess.length; ++i) { ++ sections.addList(sectionAccess[i] = Types.NBT.createEmptyList()); ++ } ++ level.setList("ToBeTicked", sections); ++ ++ for (int i = 0, len = ticks.size(); i < len; ++i) { ++ final MapType tick = ticks.getMap(i); ++ ++ final int x = tick.getInt("x"); ++ final int y = tick.getInt("y"); ++ final int z = tick.getInt("z"); ++ final short coordinate = packOffsetCoordinates(x, y, z); ++ ++ sectionAccess[y >> 4].addShort(coordinate); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ ++ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); ++ ++ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); ++ if (tileTicks != null) { ++ for (int i = 0, len = tileTicks.size(); i < len; ++i) { ++ final MapType tileTick = tileTicks.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); ++ } ++ } ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section, "Palette", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, level.getMap("Structures"), "Starts", fromVersion, toVersion); ++ ++ return null; ++ }); ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final ListType list = data.getList("Children", ObjectType.MAP); ++ if (list != null) { ++ for (int i = 0, len = list.size(); i < len; ++i) { ++ final MapType child = list.getMap(i); ++ ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CA", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CB", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CC", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CD", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convert(MCTypeRegistry.BIOME, data, "biome", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++ ++ private V1466() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java +new file mode 100644 +index 0000000000000000000000000000000000000000..68dd3ce7709a998bc50a5080fe9c805b71a88365 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java +@@ -0,0 +1,27 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V147 { ++ ++ protected static final int VERSION = MCVersions.V15W46A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("ArmorStand", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getBoolean("Silent") && !data.getBoolean("Marker")) { ++ data.remove("Silent"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V147() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d29c79b44f619891ed07d7f13a63c960dfa985cd +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java +@@ -0,0 +1,33 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++ ++public final class V1470 { ++ ++ protected static final int VERSION = MCVersions.V18W08A; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:turtle"); ++ registerMob("minecraft:cod_mob"); ++ registerMob("minecraft:tropical_fish"); ++ registerMob("minecraft:salmon_mob"); ++ registerMob("minecraft:puffer_fish"); ++ registerMob("minecraft:phantom"); ++ registerMob("minecraft:dolphin"); ++ registerMob("minecraft:drowned"); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:trident", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:trident", new DataWalkerItems("Trident")); ++ } ++ ++ private V1470() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4cf1085b4392c9b348ebe65590cdbf287a908a38 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java +@@ -0,0 +1,36 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1474 { ++ ++ protected static final int VERSION = MCVersions.V18W10B; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getInt("Color") == 10) { ++ data.setByte("Color", (byte)16); ++ } ++ return null; ++ } ++ }); ++ // data hooks ensure the inputs are namespaced ++ ConverterAbstractBlockRename.register(VERSION, (final String old) -> { ++ return "minecraft:purple_shulker_box".equals(old) ? "minecraft:shulker_box" : null; ++ }); ++ ConverterAbstractItemRename.register(VERSION, (final String old) -> { ++ return "minecraft:purple_shulker_box".equals(old) ? "minecraft:shulker_box" : null; ++ }); ++ ++ } ++ ++ private V1474() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java +new file mode 100644 +index 0000000000000000000000000000000000000000..40b64efb8717b2de0ff13af87bcc99119c9f7c9d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V1475 { ++ ++ protected static final int VERSION = MCVersions.V18W10B + 1; ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, ++ ImmutableMap.of( ++ "minecraft:flowing_water", "minecraft:water", ++ "minecraft:flowing_lava", "minecraft:lava" ++ )::get); ++ } ++ ++ private V1475() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e4777730c6969d5a964daedce99c06a858615bc7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java +@@ -0,0 +1,45 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V1480 { ++ ++ protected static final int VERSION = MCVersions.V18W14A + 1; ++ ++ public static final Map RENAMED_IDS = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:blue_coral", "minecraft:tube_coral_block") ++ .put("minecraft:pink_coral", "minecraft:brain_coral_block") ++ .put("minecraft:purple_coral", "minecraft:bubble_coral_block") ++ .put("minecraft:red_coral", "minecraft:fire_coral_block") ++ .put("minecraft:yellow_coral", "minecraft:horn_coral_block") ++ .put("minecraft:blue_coral_plant", "minecraft:tube_coral") ++ .put("minecraft:pink_coral_plant", "minecraft:brain_coral") ++ .put("minecraft:purple_coral_plant", "minecraft:bubble_coral") ++ .put("minecraft:red_coral_plant", "minecraft:fire_coral") ++ .put("minecraft:yellow_coral_plant", "minecraft:horn_coral") ++ .put("minecraft:blue_coral_fan", "minecraft:tube_coral_fan") ++ .put("minecraft:pink_coral_fan", "minecraft:brain_coral_fan") ++ .put("minecraft:purple_coral_fan", "minecraft:bubble_coral_fan") ++ .put("minecraft:red_coral_fan", "minecraft:fire_coral_fan") ++ .put("minecraft:yellow_coral_fan", "minecraft:horn_coral_fan") ++ .put("minecraft:blue_dead_coral", "minecraft:dead_tube_coral") ++ .put("minecraft:pink_dead_coral", "minecraft:dead_brain_coral") ++ .put("minecraft:purple_dead_coral", "minecraft:dead_bubble_coral") ++ .put("minecraft:red_dead_coral", "minecraft:dead_fire_coral") ++ .put("minecraft:yellow_dead_coral", "minecraft:dead_horn_coral") ++ .build() ++ ); ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, RENAMED_IDS::get); ++ ConverterAbstractItemRename.register(VERSION, RENAMED_IDS::get); ++ } ++ ++ private V1480() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e6fb5d6870514a509f7f1aa5343ed7e762af8a72 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java +@@ -0,0 +1,31 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V1483 { ++ ++ protected static final int VERSION = MCVersions.V18W16A; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of( ++ "minecraft:puffer_fish", "minecraft:pufferfish" ++ )::get); ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:puffer_fish_spawn_egg", "minecraft:pufferfish_spawn_egg" ++ )::get); ++ ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:puffer_fish", "minecraft:pufferfish"); ++ } ++ ++ private V1483() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f5b9c166304930e095bfc00e8f6b93edb706df48 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java +@@ -0,0 +1,73 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V1484 { ++ ++ protected static final int VERSION = MCVersions.V18W19A; ++ ++ public static void register() { ++ final Map renamed = ImmutableMap.of( ++ "minecraft:sea_grass", "minecraft:seagrass", ++ "minecraft:tall_sea_grass", "minecraft:tall_seagrass" ++ ); ++ ++ ConverterAbstractItemRename.register(VERSION, renamed::get); ++ ConverterAbstractBlockRename.register(VERSION, renamed::get); ++ ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ ++ if (level == null) { ++ return null; ++ } ++ ++ final MapType heightmaps = level.getMap("Heightmaps"); ++ ++ if (heightmaps == null) { ++ return null; ++ } ++ ++ final Object liquid = heightmaps.getGeneric("LIQUID"); ++ if (liquid != null) { ++ heightmaps.remove("LIQUID"); ++ heightmaps.setGeneric("WORLD_SURFACE_WG", liquid); ++ } ++ ++ final Object solid = heightmaps.getGeneric("SOLID"); ++ if (solid != null) { ++ heightmaps.remove("SOLID"); ++ heightmaps.setGeneric("OCEAN_FLOOR_WG", solid); ++ heightmaps.setGeneric("OCEAN_FLOOR", solid); ++ } ++ ++ final Object light = heightmaps.getGeneric("LIGHT"); ++ if (light != null) { ++ heightmaps.remove("LIGHT"); ++ heightmaps.setGeneric("LIGHT_BLOCKING", light); ++ } ++ ++ final Object rain = heightmaps.getGeneric("RAIN"); ++ if (rain != null) { ++ heightmaps.remove("RAIN"); ++ heightmaps.setGeneric("MOTION_BLOCKING", rain); ++ heightmaps.setGeneric("MOTION_BLOCKING_NO_LEAVES", rain); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V1484() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e87551aa76c1434c2c81da828cd69a9bf32b5e04 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java +@@ -0,0 +1,44 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V1486 { ++ ++ protected static final int VERSION = MCVersions.V18W19B + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static final Map RENAMED_ENTITY_IDS = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:salmon_mob", "minecraft:salmon") ++ .put("minecraft:cod_mob", "minecraft:cod") ++ .build() ++ ); ++ public static final Map RENAMED_ITEM_IDS = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:salmon_mob_spawn_egg", "minecraft:salmon_spawn_egg") ++ .put("minecraft:cod_mob_spawn_egg", "minecraft:cod_spawn_egg") ++ .build() ++ ); ++ ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:cod_mob", "minecraft:cod"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:salmon_mob", "minecraft:salmon"); ++ ++ ConverterAbstractEntityRename.register(VERSION, RENAMED_ENTITY_IDS::get); ++ ConverterAbstractItemRename.register(VERSION, RENAMED_ITEM_IDS::get); ++ } ++ ++ private V1486() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dba4a64f61b27f1eb820e0e0a3fddb81517acc16 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java +@@ -0,0 +1,25 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V1487 { ++ ++ protected static final int VERSION = MCVersions.V18W19B + 2; ++ ++ public static void register() { ++ final Map remap = ImmutableMap.of( ++ "minecraft:prismarine_bricks_slab", "minecraft:prismarine_brick_slab", ++ "minecraft:prismarine_bricks_stairs", "minecraft:prismarine_brick_stairs" ++ ); ++ ++ ConverterAbstractItemRename.register(VERSION, remap::get); ++ ConverterAbstractBlockRename.register(VERSION, remap::get); ++ } ++ ++ private V1487() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cf3579524a9ba96f2065d98bca928bf920da081c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java +@@ -0,0 +1,88 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V1488 { ++ ++ protected static final int VERSION = MCVersions.V18W19B + 3; ++ ++ protected static boolean isIglooPiece(final MapType piece) { ++ return "Iglu".equals(piece.getString("id")); ++ } ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( ++ "minecraft:kelp_top", "minecraft:kelp", ++ "minecraft:kelp", "minecraft:kelp_plant" ++ )::get); ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:kelp_top", "minecraft:kelp" ++ )::get); ++ ++ // Don't ask me why in V1458 they wrote the converter to NOT do command blocks and THEN in THIS version ++ // to ONLY do command blocks. I don't know. ++ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:command_block", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ return V1458.updateCustomName(data); ++ } ++ }); ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:commandblock_minecart", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ return V1458.updateCustomName(data); ++ } ++ }); ++ ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType children = data.getList("Children", ObjectType.MAP); ++ boolean isIgloo; ++ if (children != null) { ++ isIgloo = true; ++ for (int i = 0, len = children.size(); i < len; ++i) { ++ if (!isIglooPiece(children.getMap(i))) { ++ isIgloo = false; ++ break; ++ } ++ } ++ } else { ++ isIgloo = false; ++ } ++ ++ if (isIgloo) { ++ data.remove("Children"); ++ data.setString("id", "Igloo"); ++ return null; ++ } ++ ++ if (children != null) { ++ for (int i = 0; i < children.size();) { ++ final MapType child = children.getMap(i); ++ if (isIglooPiece(child)) { ++ children.remove(i); ++ continue; ++ } ++ ++i; ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V1488() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cb3afa0634cb47cbd5b324a66d140375b1c23e07 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java +@@ -0,0 +1,25 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V1490 { ++ ++ protected static final int VERSION = MCVersions.V18W20A + 1; ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( ++ "minecraft:melon_block", "minecraft:melon" ++ )::get); ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:melon_block", "minecraft:melon", ++ "minecraft:melon", "minecraft:melon_slice", ++ "minecraft:speckled_melon", "minecraft:glistering_melon_slice" ++ )::get); ++ } ++ ++ private V1490() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b8732c2035ee0659173a8299cc2b0a5f86ace7b0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java +@@ -0,0 +1,152 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.google.common.collect.ImmutableMap; ++import com.mojang.datafixers.util.Pair; ++ ++public final class V1492 { ++ ++ private static final ImmutableMap>> RENAMES = ImmutableMap.>>builder() ++ .put("EndCity", Pair.of( ++ "ECP", ++ ImmutableMap.builder() ++ .put("second_floor", "second_floor_1") ++ .put("third_floor", "third_floor_1") ++ .put("third_floor_c", "third_floor_2") ++ .build() ++ ) ++ ) ++ ++ .put("Mansion", Pair.of( ++ "WMP", ++ ImmutableMap.builder() ++ .put("carpet_south", "carpet_south_1") ++ .put("carpet_west", "carpet_west_1") ++ .put("indoors_door", "indoors_door_1") ++ .put("indoors_wall", "indoors_wall_1") ++ .build() ++ ) ++ ) ++ ++ .put("Igloo", Pair.of( ++ "Iglu", ++ ImmutableMap.builder() ++ .put("minecraft:igloo/igloo_bottom", "minecraft:igloo/bottom") ++ .put("minecraft:igloo/igloo_middle", "minecraft:igloo/middle") ++ .put("minecraft:igloo/igloo_top", "minecraft:igloo/top") ++ .build() ++ ) ++ ) ++ .put("Ocean_Ruin", Pair.of( ++ "ORP", ++ ImmutableMap.builder() ++ .put("minecraft:ruin/big_ruin1_brick", "minecraft:underwater_ruin/big_brick_1") ++ .put("minecraft:ruin/big_ruin2_brick", "minecraft:underwater_ruin/big_brick_2") ++ .put("minecraft:ruin/big_ruin3_brick", "minecraft:underwater_ruin/big_brick_3") ++ .put("minecraft:ruin/big_ruin8_brick", "minecraft:underwater_ruin/big_brick_8") ++ .put("minecraft:ruin/big_ruin1_cracked", "minecraft:underwater_ruin/big_cracked_1") ++ .put("minecraft:ruin/big_ruin2_cracked", "minecraft:underwater_ruin/big_cracked_2") ++ .put("minecraft:ruin/big_ruin3_cracked", "minecraft:underwater_ruin/big_cracked_3") ++ .put("minecraft:ruin/big_ruin8_cracked", "minecraft:underwater_ruin/big_cracked_8") ++ .put("minecraft:ruin/big_ruin1_mossy", "minecraft:underwater_ruin/big_mossy_1") ++ .put("minecraft:ruin/big_ruin2_mossy", "minecraft:underwater_ruin/big_mossy_2") ++ .put("minecraft:ruin/big_ruin3_mossy", "minecraft:underwater_ruin/big_mossy_3") ++ .put("minecraft:ruin/big_ruin8_mossy", "minecraft:underwater_ruin/big_mossy_8") ++ .put("minecraft:ruin/big_ruin_warm4", "minecraft:underwater_ruin/big_warm_4") ++ .put("minecraft:ruin/big_ruin_warm5", "minecraft:underwater_ruin/big_warm_5") ++ .put("minecraft:ruin/big_ruin_warm6", "minecraft:underwater_ruin/big_warm_6") ++ .put("minecraft:ruin/big_ruin_warm7", "minecraft:underwater_ruin/big_warm_7") ++ .put("minecraft:ruin/ruin1_brick", "minecraft:underwater_ruin/brick_1") ++ .put("minecraft:ruin/ruin2_brick", "minecraft:underwater_ruin/brick_2") ++ .put("minecraft:ruin/ruin3_brick", "minecraft:underwater_ruin/brick_3") ++ .put("minecraft:ruin/ruin4_brick", "minecraft:underwater_ruin/brick_4") ++ .put("minecraft:ruin/ruin5_brick", "minecraft:underwater_ruin/brick_5") ++ .put("minecraft:ruin/ruin6_brick", "minecraft:underwater_ruin/brick_6") ++ .put("minecraft:ruin/ruin7_brick", "minecraft:underwater_ruin/brick_7") ++ .put("minecraft:ruin/ruin8_brick", "minecraft:underwater_ruin/brick_8") ++ .put("minecraft:ruin/ruin1_cracked", "minecraft:underwater_ruin/cracked_1") ++ .put("minecraft:ruin/ruin2_cracked", "minecraft:underwater_ruin/cracked_2") ++ .put("minecraft:ruin/ruin3_cracked", "minecraft:underwater_ruin/cracked_3") ++ .put("minecraft:ruin/ruin4_cracked", "minecraft:underwater_ruin/cracked_4") ++ .put("minecraft:ruin/ruin5_cracked", "minecraft:underwater_ruin/cracked_5") ++ .put("minecraft:ruin/ruin6_cracked", "minecraft:underwater_ruin/cracked_6") ++ .put("minecraft:ruin/ruin7_cracked", "minecraft:underwater_ruin/cracked_7") ++ .put("minecraft:ruin/ruin8_cracked", "minecraft:underwater_ruin/cracked_8") ++ .put("minecraft:ruin/ruin1_mossy", "minecraft:underwater_ruin/mossy_1") ++ .put("minecraft:ruin/ruin2_mossy", "minecraft:underwater_ruin/mossy_2") ++ .put("minecraft:ruin/ruin3_mossy", "minecraft:underwater_ruin/mossy_3") ++ .put("minecraft:ruin/ruin4_mossy", "minecraft:underwater_ruin/mossy_4") ++ .put("minecraft:ruin/ruin5_mossy", "minecraft:underwater_ruin/mossy_5") ++ .put("minecraft:ruin/ruin6_mossy", "minecraft:underwater_ruin/mossy_6") ++ .put("minecraft:ruin/ruin7_mossy", "minecraft:underwater_ruin/mossy_7") ++ .put("minecraft:ruin/ruin8_mossy", "minecraft:underwater_ruin/mossy_8") ++ .put("minecraft:ruin/ruin_warm1", "minecraft:underwater_ruin/warm_1") ++ .put("minecraft:ruin/ruin_warm2", "minecraft:underwater_ruin/warm_2") ++ .put("minecraft:ruin/ruin_warm3", "minecraft:underwater_ruin/warm_3") ++ .put("minecraft:ruin/ruin_warm4", "minecraft:underwater_ruin/warm_4") ++ .put("minecraft:ruin/ruin_warm5", "minecraft:underwater_ruin/warm_5") ++ .put("minecraft:ruin/ruin_warm6", "minecraft:underwater_ruin/warm_6") ++ .put("minecraft:ruin/ruin_warm7", "minecraft:underwater_ruin/warm_7") ++ .put("minecraft:ruin/ruin_warm8", "minecraft:underwater_ruin/warm_8") ++ .put("minecraft:ruin/big_brick_1", "minecraft:underwater_ruin/big_brick_1") ++ .put("minecraft:ruin/big_brick_2", "minecraft:underwater_ruin/big_brick_2") ++ .put("minecraft:ruin/big_brick_3", "minecraft:underwater_ruin/big_brick_3") ++ .put("minecraft:ruin/big_brick_8", "minecraft:underwater_ruin/big_brick_8") ++ .put("minecraft:ruin/big_mossy_1", "minecraft:underwater_ruin/big_mossy_1") ++ .put("minecraft:ruin/big_mossy_2", "minecraft:underwater_ruin/big_mossy_2") ++ .put("minecraft:ruin/big_mossy_3", "minecraft:underwater_ruin/big_mossy_3") ++ .put("minecraft:ruin/big_mossy_8", "minecraft:underwater_ruin/big_mossy_8") ++ .put("minecraft:ruin/big_cracked_1", "minecraft:underwater_ruin/big_cracked_1") ++ .put("minecraft:ruin/big_cracked_2", "minecraft:underwater_ruin/big_cracked_2") ++ .put("minecraft:ruin/big_cracked_3", "minecraft:underwater_ruin/big_cracked_3") ++ .put("minecraft:ruin/big_cracked_8", "minecraft:underwater_ruin/big_cracked_8") ++ .put("minecraft:ruin/big_warm_4", "minecraft:underwater_ruin/big_warm_4") ++ .put("minecraft:ruin/big_warm_5", "minecraft:underwater_ruin/big_warm_5") ++ .put("minecraft:ruin/big_warm_6", "minecraft:underwater_ruin/big_warm_6") ++ .put("minecraft:ruin/big_warm_7", "minecraft:underwater_ruin/big_warm_7") ++ .build() ++ ) ++ ) ++ ++ .build(); ++ ++ protected static final int VERSION = MCVersions.V18W20B + 1; ++ ++ public static void register() { ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType children = data.getList("Children", ObjectType.MAP); ++ if (children == null) { ++ return null; ++ } ++ ++ final String id = data.getString("id"); ++ ++ final Pair> renames = RENAMES.get(id); ++ if (renames == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = children.size(); i < len; ++i) { ++ final MapType child = children.getMap(i); ++ ++ if (renames.getFirst().equals(child.getString("id"))) { ++ final String template = child.getString("Template", ""); ++ child.setString("Template", renames.getSecond().getOrDefault(template, template)); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V1492() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java +new file mode 100644 +index 0000000000000000000000000000000000000000..50411042a83d58c4c36768a8f5196b4b41b4d095 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java +@@ -0,0 +1,89 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++ ++public final class V1494 { ++ ++ protected static final int VERSION = MCVersions.V18W20C + 1; ++ ++ private static final Int2ObjectOpenHashMap ENCH_ID_TO_NAME = new Int2ObjectOpenHashMap<>(); ++ static { ++ ENCH_ID_TO_NAME.put(0, "minecraft:protection"); ++ ENCH_ID_TO_NAME.put(1, "minecraft:fire_protection"); ++ ENCH_ID_TO_NAME.put(2, "minecraft:feather_falling"); ++ ENCH_ID_TO_NAME.put(3, "minecraft:blast_protection"); ++ ENCH_ID_TO_NAME.put(4, "minecraft:projectile_protection"); ++ ENCH_ID_TO_NAME.put(5, "minecraft:respiration"); ++ ENCH_ID_TO_NAME.put(6, "minecraft:aqua_affinity"); ++ ENCH_ID_TO_NAME.put(7, "minecraft:thorns"); ++ ENCH_ID_TO_NAME.put(8, "minecraft:depth_strider"); ++ ENCH_ID_TO_NAME.put(9, "minecraft:frost_walker"); ++ ENCH_ID_TO_NAME.put(10, "minecraft:binding_curse"); ++ ENCH_ID_TO_NAME.put(16, "minecraft:sharpness"); ++ ENCH_ID_TO_NAME.put(17, "minecraft:smite"); ++ ENCH_ID_TO_NAME.put(18, "minecraft:bane_of_arthropods"); ++ ENCH_ID_TO_NAME.put(19, "minecraft:knockback"); ++ ENCH_ID_TO_NAME.put(20, "minecraft:fire_aspect"); ++ ENCH_ID_TO_NAME.put(21, "minecraft:looting"); ++ ENCH_ID_TO_NAME.put(22, "minecraft:sweeping"); ++ ENCH_ID_TO_NAME.put(32, "minecraft:efficiency"); ++ ENCH_ID_TO_NAME.put(33, "minecraft:silk_touch"); ++ ENCH_ID_TO_NAME.put(34, "minecraft:unbreaking"); ++ ENCH_ID_TO_NAME.put(35, "minecraft:fortune"); ++ ENCH_ID_TO_NAME.put(48, "minecraft:power"); ++ ENCH_ID_TO_NAME.put(49, "minecraft:punch"); ++ ENCH_ID_TO_NAME.put(50, "minecraft:flame"); ++ ENCH_ID_TO_NAME.put(51, "minecraft:infinity"); ++ ENCH_ID_TO_NAME.put(61, "minecraft:luck_of_the_sea"); ++ ENCH_ID_TO_NAME.put(62, "minecraft:lure"); ++ ENCH_ID_TO_NAME.put(65, "minecraft:loyalty"); ++ ENCH_ID_TO_NAME.put(66, "minecraft:impaling"); ++ ENCH_ID_TO_NAME.put(67, "minecraft:riptide"); ++ ENCH_ID_TO_NAME.put(68, "minecraft:channeling"); ++ ENCH_ID_TO_NAME.put(70, "minecraft:mending"); ++ ENCH_ID_TO_NAME.put(71, "minecraft:vanishing_curse"); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final ListType enchants = tag.getList("ench", ObjectType.MAP); ++ if (enchants != null) { ++ tag.remove("ench"); ++ tag.setList("Enchantments", enchants); ++ ++ for (int i = 0, len = enchants.size(); i < len; ++i) { ++ final MapType enchant = enchants.getMap(i); ++ enchant.setString("id", ENCH_ID_TO_NAME.getOrDefault(enchant.getInt("id"), "null")); ++ } ++ } ++ ++ final ListType storedEnchants = tag.getList("StoredEnchantments", ObjectType.MAP); ++ if (storedEnchants != null) { ++ for (int i = 0, len = storedEnchants.size(); i < len; ++i) { ++ final MapType enchant = storedEnchants.getMap(i); ++ enchant.setString("id", ENCH_ID_TO_NAME.getOrDefault(enchant.getInt("id"), "null")); ++ } ++ } ++ ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V1494() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c56d50c552d4609474f5b3b6b0b8be8b575764ea +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java +@@ -0,0 +1,370 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.mojang.datafixers.DataFixUtils; ++import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; ++import it.unimi.dsi.fastutil.ints.IntIterator; ++import it.unimi.dsi.fastutil.ints.IntOpenHashSet; ++import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import net.minecraft.util.datafix.PackedBitStorage; ++import java.util.Arrays; ++import java.util.HashSet; ++import java.util.Set; ++ ++public final class V1496 { ++ ++ private V1496() {} ++ ++ protected static final int VERSION = MCVersions.V18W21B; ++ ++ private static final int[][] DIRECTIONS = new int[][] { ++ new int[] {-1, 0, 0}, ++ new int[] {1, 0, 0}, ++ new int[] {0, -1, 0}, ++ new int[] {0, 1, 0}, ++ new int[] {0, 0, -1}, ++ new int[] {0, 0, 1} ++ }; ++ ++ private static final Object2IntOpenHashMap LEAVES_TO_ID = new Object2IntOpenHashMap<>(); ++ static { ++ LEAVES_TO_ID.put("minecraft:acacia_leaves", 0); ++ LEAVES_TO_ID.put("minecraft:birch_leaves", 1); ++ LEAVES_TO_ID.put("minecraft:dark_oak_leaves", 2); ++ LEAVES_TO_ID.put("minecraft:jungle_leaves", 3); ++ LEAVES_TO_ID.put("minecraft:oak_leaves", 4); ++ LEAVES_TO_ID.put("minecraft:spruce_leaves", 5); ++ } ++ ++ private static final Set LOGS = new HashSet<>( ++ Arrays.asList( ++ "minecraft:acacia_bark", ++ "minecraft:birch_bark", ++ "minecraft:dark_oak_bark", ++ "minecraft:jungle_bark", ++ "minecraft:oak_bark", ++ "minecraft:spruce_bark", ++ "minecraft:acacia_log", ++ "minecraft:birch_log", ++ "minecraft:dark_oak_log", ++ "minecraft:jungle_log", ++ "minecraft:oak_log", ++ "minecraft:spruce_log", ++ "minecraft:stripped_acacia_log", ++ "minecraft:stripped_birch_log", ++ "minecraft:stripped_dark_oak_log", ++ "minecraft:stripped_jungle_log", ++ "minecraft:stripped_oak_log", ++ "minecraft:stripped_spruce_log" ++ ) ++ ); ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ final ListType sectionsNBT = level.getList("Sections", ObjectType.MAP); ++ if (sectionsNBT == null) { ++ return null; ++ } ++ ++ int newSides = 0; ++ ++ final LeavesSection[] sections = new LeavesSection[16]; ++ boolean skippable = true; ++ for (int i = 0, len = sectionsNBT.size(); i < len; ++i) { ++ final LeavesSection section = new LeavesSection(sectionsNBT.getMap(i)); ++ sections[section.sectionY] = section; ++ ++ skippable &= section.isSkippable(); ++ } ++ ++ if (skippable) { ++ return null; ++ } ++ ++ final IntOpenHashSet[] positionsByDistance = new IntOpenHashSet[7]; ++ for (int i = 0; i < positionsByDistance.length; ++i) { ++ positionsByDistance[i] = new IntOpenHashSet(); ++ } ++ ++ for (final LeavesSection section : sections) { ++ if (section == null || section.isSkippable()) { ++ continue; ++ } ++ ++ for (int index = 0; index < 4096; ++index) { ++ final int block = section.getBlock(index); ++ if (section.isLog(block)) { ++ positionsByDistance[0].add(section.getSectionY() << 12 | index); ++ } else if (section.isLeaf(block)) { ++ int x = getX(index); ++ int z = getZ(index); ++ newSides |= getSideMask(x == 0, x == 15, z == 0, z == 15); ++ } ++ } ++ } ++ ++ // this is basically supposed to recalculate the distances, because a higher cap was added ++ for (int distance = 1; distance < 7; ++distance) { ++ final IntOpenHashSet positionsLess = positionsByDistance[distance - 1]; ++ final IntOpenHashSet positionsEqual = positionsByDistance[distance]; ++ ++ for (final IntIterator iterator = positionsLess.iterator(); iterator.hasNext();) { ++ final int position = iterator.nextInt(); ++ final int fromX = getX(position); ++ final int fromY = getY(position); ++ final int fromZ = getZ(position); ++ ++ for (final int[] direction : DIRECTIONS) { ++ final int toX = fromX + direction[0]; ++ final int toY = fromY + direction[1]; ++ final int toZ = fromZ + direction[2]; ++ ++ if (!(toX >= 0 && toX <= 15 && toZ >= 0 && toZ <= 15 && toY >= 0 && toY <= 255)) { ++ continue; ++ } ++ ++ final LeavesSection toSection = sections[toY >> 4]; ++ if (toSection == null || toSection.isSkippable()) { ++ continue; ++ } ++ ++ final int sectionLocalIndex = getIndex(toX, toY & 15, toZ); ++ final int toBlock = toSection.getBlock(sectionLocalIndex); ++ ++ if (toSection.isLeaf(toBlock)) { ++ final int newDistance = toSection.getDistance(toBlock); ++ if (newDistance > distance) { ++ toSection.setDistance(sectionLocalIndex, toBlock, distance); ++ positionsEqual.add(getIndex(toX, toY, toZ)); ++ } ++ } ++ } ++ } ++ } ++ ++ // done updating blocks, now just update the blockstates and palette ++ for (int i = 0, len = sectionsNBT.size(); i < len; ++i) { ++ final MapType sectionNBT = sectionsNBT.getMap(i); ++ final int y = sectionNBT.getInt("Y"); ++ final LeavesSection section = sections[y]; ++ ++ section.writeInto(sectionNBT); ++ } ++ ++ // if sides changed during process, update it now ++ if (newSides != 0) { ++ MapType upgradeData = level.getMap("UpgradeData"); ++ if (upgradeData == null) { ++ level.setMap("UpgradeData", upgradeData = Types.NBT.createEmptyMap()); ++ } ++ ++ upgradeData.setByte("Sides", (byte)(upgradeData.getByte("Sides") | newSides)); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ public static int getIndex(final int x, final int y, final int z) { ++ return y << 8 | z << 4 | x; ++ } ++ ++ public static int getX(final int index) { ++ return index & 15; ++ } ++ ++ public static int getY(final int index) { ++ return index >> 8 & 255; ++ } ++ ++ public static int getZ(final int index) { ++ return index >> 4 & 15; ++ } ++ ++ public static int getSideMask(final boolean noLeft, final boolean noRight, final boolean noBack, final boolean noForward) { ++ final int ret; ++ ++ if (noBack) { ++ if (noRight) { ++ ret = 2; ++ } else if (noLeft) { ++ ret = 128; ++ } else { ++ ret = 1; ++ } ++ } else if (noForward) { ++ if (noLeft) { ++ ret = 32; ++ } else if (noRight) { ++ ret = 8; ++ } else { ++ ret = 16; ++ } ++ } else if (noRight) { ++ ret = 4; ++ } else if (noLeft) { ++ ret = 64; ++ } else { ++ ret = 0; ++ } ++ ++ return ret; ++ } ++ ++ public abstract static class Section { ++ protected final ListType palette; ++ protected final int sectionY; ++ protected PackedBitStorage storage; ++ ++ public Section(final MapType section) { ++ this.palette = section.getList("Palette", ObjectType.MAP); ++ this.sectionY = section.getInt("Y"); ++ this.readStorage(section); ++ } ++ ++ protected void readStorage(final MapType section) { ++ if (this.initSkippable()) { ++ this.storage = null; ++ } else { ++ final long[] states = section.getLongs("BlockStates"); ++ final int bits = Math.max(4, DataFixUtils.ceillog2(this.palette.size())); ++ this.storage = new PackedBitStorage(bits, 4096, states); ++ } ++ } ++ ++ public void writeInto(final MapType section) { ++ if (this.isSkippable()) { ++ return; ++ } ++ ++ section.setList("Palette", this.palette); ++ section.setLongs("BlockStates", this.storage.getRaw()); ++ } ++ ++ public boolean isSkippable() { ++ return this.storage == null; ++ } ++ ++ public int getBlock(final int index) { ++ return this.storage.get(index); ++ } ++ ++ protected int getStateId(final String name, final boolean persistent, final int distance) { ++ return LEAVES_TO_ID.getInt(name) << 5 | (persistent ? 16 : 0) | distance; ++ } ++ ++ protected int getSectionY() { ++ return this.sectionY; ++ } ++ ++ protected abstract boolean initSkippable(); ++ } ++ ++ public static final class LeavesSection extends Section { ++ private IntOpenHashSet leaveIds; ++ private IntOpenHashSet logIds; ++ private Int2IntOpenHashMap stateToIdMap; ++ ++ public LeavesSection(final MapType section) { ++ super(section); ++ } ++ ++ @Override ++ protected boolean initSkippable() { ++ this.leaveIds = new IntOpenHashSet(); ++ this.logIds = new IntOpenHashSet(); ++ this.stateToIdMap = new Int2IntOpenHashMap(); ++ this.stateToIdMap.defaultReturnValue(-1); ++ ++ for(int i = 0; i < this.palette.size(); ++i) { ++ final MapType blockState = this.palette.getMap(i); ++ final String name = blockState.getString("Name", ""); ++ if (LEAVES_TO_ID.containsKey(name)) { ++ final MapType properties = blockState.getMap("Properties"); ++ final boolean notDecayable = properties != null && "false".equals(properties.getString("decayable")); ++ ++ this.leaveIds.add(i); ++ this.stateToIdMap.put(this.getStateId(name, notDecayable, 7), i); ++ this.palette.setMap(i, this.makeNewLeafTag(name, notDecayable, 7)); ++ } ++ ++ if (LOGS.contains(name)) { ++ this.logIds.add(i); ++ } ++ } ++ ++ return this.leaveIds.isEmpty() && this.logIds.isEmpty(); ++ } ++ ++ private MapType makeNewLeafTag(final String name, final boolean notDecayable, final int distance) { ++ final MapType properties = Types.NBT.createEmptyMap(); ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setString("Name", name); ++ ret.setMap("Properties", properties); ++ ++ properties.setString("persistent", Boolean.toString(notDecayable)); ++ properties.setString("distance", Integer.toString(distance)); ++ ++ return ret; ++ } ++ ++ public boolean isLog(final int id) { ++ return this.logIds.contains(id); ++ } ++ ++ public boolean isLeaf(final int id) { ++ return this.leaveIds.contains(id); ++ } ++ ++ // only call for logs or leaves, will throw otherwise! ++ private int getDistance(final int id) { ++ if (this.isLog(id)) { ++ return 0; ++ } ++ ++ return Integer.parseInt(this.palette.getMap(id).getMap("Properties").getString("distance")); ++ } ++ ++ private void setDistance(final int index, final int id, final int distance) { ++ final MapType state = this.palette.getMap(id); ++ final String name = state.getString("Name"); ++ final boolean persistent = "true".equals(state.getMap("Properties").getString("persistent")); ++ final int newState = this.getStateId(name, persistent, distance); ++ int newStateId; ++ if ((newStateId = this.stateToIdMap.get(newState)) == -1) { ++ newStateId = this.palette.size(); ++ this.leaveIds.add(newStateId); ++ this.stateToIdMap.put(newState, newStateId); ++ this.palette.addMap(this.makeNewLeafTag(name, persistent, distance)); ++ } ++ ++ if (1 << this.storage.getBits() <= newStateId) { ++ // need to widen storage ++ final PackedBitStorage newStorage = new PackedBitStorage(this.storage.getBits() + 1, 4096); ++ ++ for(int i = 0; i < 4096; ++i) { ++ newStorage.set(i, this.storage.get(i)); ++ } ++ ++ this.storage = newStorage; ++ } ++ ++ this.storage.set(index, newStateId); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9208152e2a158470f37b0eb022478e8e5287c12b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java +@@ -0,0 +1,24 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1500 { ++ ++ protected static final int VERSION = MCVersions.V18W22C + 1; ++ ++ private V1500() {} ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("DUMMY", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.setBoolean("keepPacked", true); ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3a1bf6d864e10273c87209bbcdbdb889aec1103d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java +@@ -0,0 +1,78 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V1501 { ++ ++ protected static final int VERSION = MCVersions.V1_13_PRE1; ++ ++ private static final Map RENAMES = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:recipes/brewing/speckled_melon", "minecraft:recipes/brewing/glistering_melon_slice") ++ .put("minecraft:recipes/building_blocks/black_stained_hardened_clay", "minecraft:recipes/building_blocks/black_terracotta") ++ .put("minecraft:recipes/building_blocks/blue_stained_hardened_clay", "minecraft:recipes/building_blocks/blue_terracotta") ++ .put("minecraft:recipes/building_blocks/brown_stained_hardened_clay", "minecraft:recipes/building_blocks/brown_terracotta") ++ .put("minecraft:recipes/building_blocks/cyan_stained_hardened_clay", "minecraft:recipes/building_blocks/cyan_terracotta") ++ .put("minecraft:recipes/building_blocks/gray_stained_hardened_clay", "minecraft:recipes/building_blocks/gray_terracotta") ++ .put("minecraft:recipes/building_blocks/green_stained_hardened_clay", "minecraft:recipes/building_blocks/green_terracotta") ++ .put("minecraft:recipes/building_blocks/light_blue_stained_hardened_clay", "minecraft:recipes/building_blocks/light_blue_terracotta") ++ .put("minecraft:recipes/building_blocks/light_gray_stained_hardened_clay", "minecraft:recipes/building_blocks/light_gray_terracotta") ++ .put("minecraft:recipes/building_blocks/lime_stained_hardened_clay", "minecraft:recipes/building_blocks/lime_terracotta") ++ .put("minecraft:recipes/building_blocks/magenta_stained_hardened_clay", "minecraft:recipes/building_blocks/magenta_terracotta") ++ .put("minecraft:recipes/building_blocks/orange_stained_hardened_clay", "minecraft:recipes/building_blocks/orange_terracotta") ++ .put("minecraft:recipes/building_blocks/pink_stained_hardened_clay", "minecraft:recipes/building_blocks/pink_terracotta") ++ .put("minecraft:recipes/building_blocks/purple_stained_hardened_clay", "minecraft:recipes/building_blocks/purple_terracotta") ++ .put("minecraft:recipes/building_blocks/red_stained_hardened_clay", "minecraft:recipes/building_blocks/red_terracotta") ++ .put("minecraft:recipes/building_blocks/white_stained_hardened_clay", "minecraft:recipes/building_blocks/white_terracotta") ++ .put("minecraft:recipes/building_blocks/yellow_stained_hardened_clay", "minecraft:recipes/building_blocks/yellow_terracotta") ++ .put("minecraft:recipes/building_blocks/acacia_wooden_slab", "minecraft:recipes/building_blocks/acacia_slab") ++ .put("minecraft:recipes/building_blocks/birch_wooden_slab", "minecraft:recipes/building_blocks/birch_slab") ++ .put("minecraft:recipes/building_blocks/dark_oak_wooden_slab", "minecraft:recipes/building_blocks/dark_oak_slab") ++ .put("minecraft:recipes/building_blocks/jungle_wooden_slab", "minecraft:recipes/building_blocks/jungle_slab") ++ .put("minecraft:recipes/building_blocks/oak_wooden_slab", "minecraft:recipes/building_blocks/oak_slab") ++ .put("minecraft:recipes/building_blocks/spruce_wooden_slab", "minecraft:recipes/building_blocks/spruce_slab") ++ .put("minecraft:recipes/building_blocks/brick_block", "minecraft:recipes/building_blocks/bricks") ++ .put("minecraft:recipes/building_blocks/chiseled_stonebrick", "minecraft:recipes/building_blocks/chiseled_stone_bricks") ++ .put("minecraft:recipes/building_blocks/end_bricks", "minecraft:recipes/building_blocks/end_stone_bricks") ++ .put("minecraft:recipes/building_blocks/lit_pumpkin", "minecraft:recipes/building_blocks/jack_o_lantern") ++ .put("minecraft:recipes/building_blocks/magma", "minecraft:recipes/building_blocks/magma_block") ++ .put("minecraft:recipes/building_blocks/melon_block", "minecraft:recipes/building_blocks/melon") ++ .put("minecraft:recipes/building_blocks/mossy_stonebrick", "minecraft:recipes/building_blocks/mossy_stone_bricks") ++ .put("minecraft:recipes/building_blocks/nether_brick", "minecraft:recipes/building_blocks/nether_bricks") ++ .put("minecraft:recipes/building_blocks/pillar_quartz_block", "minecraft:recipes/building_blocks/quartz_pillar") ++ .put("minecraft:recipes/building_blocks/red_nether_brick", "minecraft:recipes/building_blocks/red_nether_bricks") ++ .put("minecraft:recipes/building_blocks/snow", "minecraft:recipes/building_blocks/snow_block") ++ .put("minecraft:recipes/building_blocks/smooth_red_sandstone", "minecraft:recipes/building_blocks/cut_red_sandstone") ++ .put("minecraft:recipes/building_blocks/smooth_sandstone", "minecraft:recipes/building_blocks/cut_sandstone") ++ .put("minecraft:recipes/building_blocks/stonebrick", "minecraft:recipes/building_blocks/stone_bricks") ++ .put("minecraft:recipes/building_blocks/stone_stairs", "minecraft:recipes/building_blocks/cobblestone_stairs") ++ .put("minecraft:recipes/building_blocks/string_to_wool", "minecraft:recipes/building_blocks/white_wool_from_string") ++ .put("minecraft:recipes/decorations/fence", "minecraft:recipes/decorations/oak_fence") ++ .put("minecraft:recipes/decorations/purple_shulker_box", "minecraft:recipes/decorations/shulker_box") ++ .put("minecraft:recipes/decorations/slime", "minecraft:recipes/decorations/slime_block") ++ .put("minecraft:recipes/decorations/snow_layer", "minecraft:recipes/decorations/snow") ++ .put("minecraft:recipes/misc/bone_meal_from_block", "minecraft:recipes/misc/bone_meal_from_bone_block") ++ .put("minecraft:recipes/misc/bone_meal_from_bone", "minecraft:recipes/misc/bone_meal") ++ .put("minecraft:recipes/misc/gold_ingot_from_block", "minecraft:recipes/misc/gold_ingot_from_gold_block") ++ .put("minecraft:recipes/misc/iron_ingot_from_block", "minecraft:recipes/misc/iron_ingot_from_iron_block") ++ .put("minecraft:recipes/redstone/fence_gate", "minecraft:recipes/redstone/oak_fence_gate") ++ .put("minecraft:recipes/redstone/noteblock", "minecraft:recipes/redstone/note_block") ++ .put("minecraft:recipes/redstone/trapdoor", "minecraft:recipes/redstone/oak_trapdoor") ++ .put("minecraft:recipes/redstone/wooden_button", "minecraft:recipes/redstone/oak_button") ++ .put("minecraft:recipes/redstone/wooden_door", "minecraft:recipes/redstone/oak_door") ++ .put("minecraft:recipes/redstone/wooden_pressure_plate", "minecraft:recipes/redstone/oak_pressure_plate") ++ .put("minecraft:recipes/transportation/boat", "minecraft:recipes/transportation/oak_boat") ++ .put("minecraft:recipes/transportation/golden_rail", "minecraft:recipes/transportation/powered_rail") ++ .build() ++ ); ++ ++ private V1501() {} ++ ++ public static void register() { ++ ConverterAbstractAdvancementsRename.register(VERSION, RENAMES::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java +new file mode 100644 +index 0000000000000000000000000000000000000000..514bb43a219f4d731e7d51804fa2595b9c56da96 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java +@@ -0,0 +1,77 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.recipe.ConverterAbstractRecipeRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V1502 { ++ ++ protected static final int VERSION = MCVersions.V1_13_PRE2; ++ ++ private static final Map RECIPES_UPDATES = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:acacia_wooden_slab", "minecraft:acacia_slab") ++ .put("minecraft:birch_wooden_slab", "minecraft:birch_slab") ++ .put("minecraft:black_stained_hardened_clay", "minecraft:black_terracotta") ++ .put("minecraft:blue_stained_hardened_clay", "minecraft:blue_terracotta") ++ .put("minecraft:boat", "minecraft:oak_boat") ++ .put("minecraft:bone_meal_from_block", "minecraft:bone_meal_from_bone_block") ++ .put("minecraft:bone_meal_from_bone", "minecraft:bone_meal") ++ .put("minecraft:brick_block", "minecraft:bricks") ++ .put("minecraft:brown_stained_hardened_clay", "minecraft:brown_terracotta") ++ .put("minecraft:chiseled_stonebrick", "minecraft:chiseled_stone_bricks") ++ .put("minecraft:cyan_stained_hardened_clay", "minecraft:cyan_terracotta") ++ .put("minecraft:dark_oak_wooden_slab", "minecraft:dark_oak_slab") ++ .put("minecraft:end_bricks", "minecraft:end_stone_bricks") ++ .put("minecraft:fence_gate", "minecraft:oak_fence_gate") ++ .put("minecraft:fence", "minecraft:oak_fence") ++ .put("minecraft:golden_rail", "minecraft:powered_rail") ++ .put("minecraft:gold_ingot_from_block", "minecraft:gold_ingot_from_gold_block") ++ .put("minecraft:gray_stained_hardened_clay", "minecraft:gray_terracotta") ++ .put("minecraft:green_stained_hardened_clay", "minecraft:green_terracotta") ++ .put("minecraft:iron_ingot_from_block", "minecraft:iron_ingot_from_iron_block") ++ .put("minecraft:jungle_wooden_slab", "minecraft:jungle_slab") ++ .put("minecraft:light_blue_stained_hardened_clay", "minecraft:light_blue_terracotta") ++ .put("minecraft:light_gray_stained_hardened_clay", "minecraft:light_gray_terracotta") ++ .put("minecraft:lime_stained_hardened_clay", "minecraft:lime_terracotta") ++ .put("minecraft:lit_pumpkin", "minecraft:jack_o_lantern") ++ .put("minecraft:magenta_stained_hardened_clay", "minecraft:magenta_terracotta") ++ .put("minecraft:magma", "minecraft:magma_block") ++ .put("minecraft:melon_block", "minecraft:melon") ++ .put("minecraft:mossy_stonebrick", "minecraft:mossy_stone_bricks") ++ .put("minecraft:noteblock", "minecraft:note_block") ++ .put("minecraft:oak_wooden_slab", "minecraft:oak_slab") ++ .put("minecraft:orange_stained_hardened_clay", "minecraft:orange_terracotta") ++ .put("minecraft:pillar_quartz_block", "minecraft:quartz_pillar") ++ .put("minecraft:pink_stained_hardened_clay", "minecraft:pink_terracotta") ++ .put("minecraft:purple_shulker_box", "minecraft:shulker_box") ++ .put("minecraft:purple_stained_hardened_clay", "minecraft:purple_terracotta") ++ .put("minecraft:red_nether_brick", "minecraft:red_nether_bricks") ++ .put("minecraft:red_stained_hardened_clay", "minecraft:red_terracotta") ++ .put("minecraft:slime", "minecraft:slime_block") ++ .put("minecraft:smooth_red_sandstone", "minecraft:cut_red_sandstone") ++ .put("minecraft:smooth_sandstone", "minecraft:cut_sandstone") ++ .put("minecraft:snow_layer", "minecraft:snow") ++ .put("minecraft:snow", "minecraft:snow_block") ++ .put("minecraft:speckled_melon", "minecraft:glistering_melon_slice") ++ .put("minecraft:spruce_wooden_slab", "minecraft:spruce_slab") ++ .put("minecraft:stonebrick", "minecraft:stone_bricks") ++ .put("minecraft:stone_stairs", "minecraft:cobblestone_stairs") ++ .put("minecraft:string_to_wool", "minecraft:white_wool_from_string") ++ .put("minecraft:trapdoor", "minecraft:oak_trapdoor") ++ .put("minecraft:white_stained_hardened_clay", "minecraft:white_terracotta") ++ .put("minecraft:wooden_button", "minecraft:oak_button") ++ .put("minecraft:wooden_door", "minecraft:oak_door") ++ .put("minecraft:wooden_pressure_plate", "minecraft:oak_pressure_plate") ++ .put("minecraft:yellow_stained_hardened_clay", "minecraft:yellow_terracotta") ++ .build() ++ ); ++ ++ private V1502() {} ++ ++ public static void register() { ++ ConverterAbstractRecipeRename.register(VERSION, RECIPES_UPDATES::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ef679762aec326e5e1310390bca46971b548e7cd +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java +@@ -0,0 +1,219 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.json.JsonMapType; ++import ca.spottedleaf.dataconverter.types.json.JsonTypeUtil; ++import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; ++import com.google.common.base.Splitter; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++import com.mojang.datafixers.util.Pair; ++import com.mojang.serialization.Dynamic; ++import com.mojang.serialization.DynamicOps; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.Tag; ++import net.minecraft.util.GsonHelper; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.stream.Collectors; ++ ++public final class V1506 { ++ ++ protected static final int VERSION = MCVersions.V1_13_PRE4 + 2; ++ ++ static final Map MAP = new HashMap<>(); ++ static { ++ MAP.put("0", "minecraft:ocean"); ++ MAP.put("1", "minecraft:plains"); ++ MAP.put("2", "minecraft:desert"); ++ MAP.put("3", "minecraft:mountains"); ++ MAP.put("4", "minecraft:forest"); ++ MAP.put("5", "minecraft:taiga"); ++ MAP.put("6", "minecraft:swamp"); ++ MAP.put("7", "minecraft:river"); ++ MAP.put("8", "minecraft:nether"); ++ MAP.put("9", "minecraft:the_end"); ++ MAP.put("10", "minecraft:frozen_ocean"); ++ MAP.put("11", "minecraft:frozen_river"); ++ MAP.put("12", "minecraft:snowy_tundra"); ++ MAP.put("13", "minecraft:snowy_mountains"); ++ MAP.put("14", "minecraft:mushroom_fields"); ++ MAP.put("15", "minecraft:mushroom_field_shore"); ++ MAP.put("16", "minecraft:beach"); ++ MAP.put("17", "minecraft:desert_hills"); ++ MAP.put("18", "minecraft:wooded_hills"); ++ MAP.put("19", "minecraft:taiga_hills"); ++ MAP.put("20", "minecraft:mountain_edge"); ++ MAP.put("21", "minecraft:jungle"); ++ MAP.put("22", "minecraft:jungle_hills"); ++ MAP.put("23", "minecraft:jungle_edge"); ++ MAP.put("24", "minecraft:deep_ocean"); ++ MAP.put("25", "minecraft:stone_shore"); ++ MAP.put("26", "minecraft:snowy_beach"); ++ MAP.put("27", "minecraft:birch_forest"); ++ MAP.put("28", "minecraft:birch_forest_hills"); ++ MAP.put("29", "minecraft:dark_forest"); ++ MAP.put("30", "minecraft:snowy_taiga"); ++ MAP.put("31", "minecraft:snowy_taiga_hills"); ++ MAP.put("32", "minecraft:giant_tree_taiga"); ++ MAP.put("33", "minecraft:giant_tree_taiga_hills"); ++ MAP.put("34", "minecraft:wooded_mountains"); ++ MAP.put("35", "minecraft:savanna"); ++ MAP.put("36", "minecraft:savanna_plateau"); ++ MAP.put("37", "minecraft:badlands"); ++ MAP.put("38", "minecraft:wooded_badlands_plateau"); ++ MAP.put("39", "minecraft:badlands_plateau"); ++ MAP.put("40", "minecraft:small_end_islands"); ++ MAP.put("41", "minecraft:end_midlands"); ++ MAP.put("42", "minecraft:end_highlands"); ++ MAP.put("43", "minecraft:end_barrens"); ++ MAP.put("44", "minecraft:warm_ocean"); ++ MAP.put("45", "minecraft:lukewarm_ocean"); ++ MAP.put("46", "minecraft:cold_ocean"); ++ MAP.put("47", "minecraft:deep_warm_ocean"); ++ MAP.put("48", "minecraft:deep_lukewarm_ocean"); ++ MAP.put("49", "minecraft:deep_cold_ocean"); ++ MAP.put("50", "minecraft:deep_frozen_ocean"); ++ MAP.put("127", "minecraft:the_void"); ++ MAP.put("129", "minecraft:sunflower_plains"); ++ MAP.put("130", "minecraft:desert_lakes"); ++ MAP.put("131", "minecraft:gravelly_mountains"); ++ MAP.put("132", "minecraft:flower_forest"); ++ MAP.put("133", "minecraft:taiga_mountains"); ++ MAP.put("134", "minecraft:swamp_hills"); ++ MAP.put("140", "minecraft:ice_spikes"); ++ MAP.put("149", "minecraft:modified_jungle"); ++ MAP.put("151", "minecraft:modified_jungle_edge"); ++ MAP.put("155", "minecraft:tall_birch_forest"); ++ MAP.put("156", "minecraft:tall_birch_hills"); ++ MAP.put("157", "minecraft:dark_forest_hills"); ++ MAP.put("158", "minecraft:snowy_taiga_mountains"); ++ MAP.put("160", "minecraft:giant_spruce_taiga"); ++ MAP.put("161", "minecraft:giant_spruce_taiga_hills"); ++ MAP.put("162", "minecraft:modified_gravelly_mountains"); ++ MAP.put("163", "minecraft:shattered_savanna"); ++ MAP.put("164", "minecraft:shattered_savanna_plateau"); ++ MAP.put("165", "minecraft:eroded_badlands"); ++ MAP.put("166", "minecraft:modified_wooded_badlands_plateau"); ++ MAP.put("167", "minecraft:modified_badlands_plateau"); ++ } ++ ++ private V1506() {} ++ ++ public static void register() { ++ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String generatorOptions = data.getString("generatorOptions"); ++ final String generatorName = data.getString("generatorName"); ++ if ("flat".equalsIgnoreCase(generatorName)) { ++ data.setMap("generatorOptions", V1506.convert(generatorOptions == null ? "" : generatorOptions)); ++ } else if ("buffet".equalsIgnoreCase(generatorName) && generatorOptions != null) { ++ data.setMap("generatorOptions", JsonTypeUtil.convertJsonToNBT(new JsonMapType(GsonHelper.parse(generatorOptions, true), false))); ++ } ++ return null; ++ } ++ }); ++ } ++ ++ private static MapType convert(final String param0) { ++ final Dynamic dynamic = convert(param0, NbtOps.INSTANCE); ++ ++ return new NBTMapType((CompoundTag)dynamic.getValue()); ++ } ++ ++ // Yeah I ain't touching that. This is basically magic value hell. ++ private static Dynamic convert(final String generatorSettings, final DynamicOps ops) { ++ final Iterator splitSettings = Splitter.on(';').split(generatorSettings).iterator(); ++ String biome = "minecraft:plains"; ++ final Map> structures = Maps.newHashMap(); ++ final List> layers; ++ if (!generatorSettings.isEmpty() && splitSettings.hasNext()) { ++ layers = getLayersInfoFromString(splitSettings.next()); ++ if (!layers.isEmpty()) { ++ // biome is next ++ if (splitSettings.hasNext()) { ++ biome = MAP.getOrDefault(splitSettings.next(), "minecraft:plains"); ++ } ++ ++ // structures is next ++ if (splitSettings.hasNext()) { ++ final String[] structuresSplit = splitSettings.next().toLowerCase(Locale.ROOT).split(","); ++ ++ for (final String structureString : structuresSplit) { ++ final String[] structureInfo = structureString.split("\\(", 2); ++ if (!structureInfo[0].isEmpty()) { ++ structures.put(structureInfo[0], Maps.newHashMap()); ++ if (structureInfo.length > 1 && structureInfo[1].endsWith(")") && structureInfo[1].length() > 1) { ++ // I can't even guess the mappings for these. Not worth my time, it will work regardless of the mappings ++ final String[] var7 = structureInfo[1].substring(0, structureInfo[1].length() - 1).split(" "); ++ ++ for (final String var8 : var7) { ++ String[] var9 = var8.split("=", 2); ++ if (var9.length == 2) { ++ structures.get(structureInfo[0]).put(var9[0], var9[1]); ++ } ++ } ++ } ++ } ++ } ++ } else { ++ structures.put("village", Maps.newHashMap()); ++ } ++ } ++ } else { ++ layers = Lists.newArrayList(); ++ layers.add(Pair.of(1, "minecraft:bedrock")); ++ layers.add(Pair.of(2, "minecraft:dirt")); ++ layers.add(Pair.of(1, "minecraft:grass_block")); ++ structures.put("village", Maps.newHashMap()); ++ } ++ ++ final T layerTag = ops.createList(layers.stream().map((param1x) -> ops.createMap(ImmutableMap.of(ops.createString("height"), ops.createInt(param1x.getFirst()), ops.createString("block"), ops.createString(param1x.getSecond()))))); ++ final T structuresTag = ops.createMap(structures.entrySet().stream().map((param1x) -> Pair.of(ops.createString(param1x.getKey().toLowerCase(Locale.ROOT)), ops.createMap(param1x.getValue().entrySet().stream().map((param1xx) -> Pair.of(ops.createString(param1xx.getKey()), ops.createString(param1xx.getValue()))).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond))))).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond))); ++ return new Dynamic<>(ops, ops.createMap(ImmutableMap.of(ops.createString("layers"), layerTag, ops.createString("biome"), ops.createString(biome), ops.createString("structures"), structuresTag))); ++ } ++ ++ private static Pair getLayerInfoFromString(final String layerString) { ++ final String[] split = layerString.split("\\*", 2); ++ int layerCount; ++ if (split.length == 2) { ++ try { ++ layerCount = Integer.parseInt(split[0]); ++ } catch (final NumberFormatException ex) { ++ return null; ++ } ++ } else { ++ layerCount = 1; ++ } ++ ++ final String blockName = split[split.length - 1]; ++ return Pair.of(layerCount, blockName); ++ } ++ ++ private static List> getLayersInfoFromString(final String layersString) { ++ final List> ret = new ArrayList<>(); ++ final String[] layers = layersString.split(","); ++ ++ for (final String layerString : layers) { ++ final Pair layer = getLayerInfoFromString(layerString); ++ if (layer == null) { ++ return Collections.emptyList(); ++ } ++ ++ ret.add(layer); ++ } ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java +new file mode 100644 +index 0000000000000000000000000000000000000000..97f92a4ee54364616181a2803351481df224d3dc +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java +@@ -0,0 +1,111 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.recipe.ConverterAbstractRecipeRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.stats.ConverterAbstractStatsRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V1510 { ++ ++ public static final Map RENAMED_ENTITY_IDS = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:commandblock_minecart", "minecraft:command_block_minecart") ++ .put("minecraft:ender_crystal", "minecraft:end_crystal") ++ .put("minecraft:snowman", "minecraft:snow_golem") ++ .put("minecraft:evocation_illager", "minecraft:evoker") ++ .put("minecraft:evocation_fangs", "minecraft:evoker_fangs") ++ .put("minecraft:illusion_illager", "minecraft:illusioner") ++ .put("minecraft:vindication_illager", "minecraft:vindicator") ++ .put("minecraft:villager_golem", "minecraft:iron_golem") ++ .put("minecraft:xp_orb", "minecraft:experience_orb") ++ .put("minecraft:xp_bottle", "minecraft:experience_bottle") ++ .put("minecraft:eye_of_ender_signal", "minecraft:eye_of_ender") ++ .put("minecraft:fireworks_rocket", "minecraft:firework_rocket") ++ .build() ++ ); ++ ++ public static final Map RENAMED_BLOCKS = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:portal", "minecraft:nether_portal") ++ .put("minecraft:oak_bark", "minecraft:oak_wood") ++ .put("minecraft:spruce_bark", "minecraft:spruce_wood") ++ .put("minecraft:birch_bark", "minecraft:birch_wood") ++ .put("minecraft:jungle_bark", "minecraft:jungle_wood") ++ .put("minecraft:acacia_bark", "minecraft:acacia_wood") ++ .put("minecraft:dark_oak_bark", "minecraft:dark_oak_wood") ++ .put("minecraft:stripped_oak_bark", "minecraft:stripped_oak_wood") ++ .put("minecraft:stripped_spruce_bark", "minecraft:stripped_spruce_wood") ++ .put("minecraft:stripped_birch_bark", "minecraft:stripped_birch_wood") ++ .put("minecraft:stripped_jungle_bark", "minecraft:stripped_jungle_wood") ++ .put("minecraft:stripped_acacia_bark", "minecraft:stripped_acacia_wood") ++ .put("minecraft:stripped_dark_oak_bark", "minecraft:stripped_dark_oak_wood") ++ .put("minecraft:mob_spawner", "minecraft:spawner") ++ .build() ++ ); ++ ++ public static final Map RENAMED_ITEMS = new HashMap<>( ++ ImmutableMap.builder() ++ .putAll(RENAMED_BLOCKS) ++ .put("minecraft:clownfish", "minecraft:tropical_fish") ++ .put("minecraft:chorus_fruit_popped", "minecraft:popped_chorus_fruit") ++ .put("minecraft:evocation_illager_spawn_egg", "minecraft:evoker_spawn_egg") ++ .put("minecraft:vindication_illager_spawn_egg", "minecraft:vindicator_spawn_egg") ++ .build() ++ ); ++ ++ private static final Map RECIPES_UPDATES = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:acacia_bark", "minecraft:acacia_wood") ++ .put("minecraft:birch_bark", "minecraft:birch_wood") ++ .put("minecraft:dark_oak_bark", "minecraft:dark_oak_wood") ++ .put("minecraft:jungle_bark", "minecraft:jungle_wood") ++ .put("minecraft:oak_bark", "minecraft:oak_wood") ++ .put("minecraft:spruce_bark", "minecraft:spruce_wood") ++ .build() ++ ); ++ ++ protected static final int VERSION = MCVersions.V1_13_PRE4 + 6; ++ ++ private V1510() {} ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, RENAMED_BLOCKS::get); ++ ConverterAbstractItemRename.register(VERSION, RENAMED_ITEMS::get); ++ ConverterAbstractRecipeRename.register(VERSION, RECIPES_UPDATES::get); ++ ++ ConverterAbstractEntityRename.register(VERSION, (String input) -> { ++ if (input.startsWith("minecraft:bred_")) { ++ input = "minecraft:".concat(input.substring("minecraft:bred_".length())); ++ } ++ ++ return RENAMED_ENTITY_IDS.get(input); ++ }); ++ ++ ConverterAbstractStatsRename.register(VERSION, new HashMap<>( ++ ImmutableMap.of( ++ "minecraft:swim_one_cm", "minecraft:walk_on_water_one_cm", ++ "minecraft:dive_one_cm", "minecraft:walk_under_water_one_cm" ++ ) ++ )::get); ++ ++ ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:commandblock_minecart", "minecraft:command_block_minecart"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:ender_crystal", "minecraft:end_crystal"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:snowman", "minecraft:snow_golem"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:evocation_illager", "minecraft:evoker"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:evocation_fangs", "minecraft:evoker_fangs"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:illusion_illager", "minecraft:illusioner"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:vindication_illager", "minecraft:vindicator"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:villager_golem", "minecraft:iron_golem"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:xp_orb", "minecraft:experience_orb"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:xp_bottle", "minecraft:experience_bottle"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:eye_of_ender_signal", "minecraft:eye_of_ender"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:fireworks_rocket", "minecraft:firework_rocket"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2a159164a6ce37d9c0900d4e8d95c26a38bea041 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java +@@ -0,0 +1,68 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1514 { ++ ++ protected static final int VERSION = MCVersions.V1_13_PRE7 + 1; ++ ++ private V1514() {} ++ ++ public static void register() { ++ MCTypeRegistry.OBJECTIVE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String displayName = data.getString("DisplayName"); ++ if (displayName == null) { ++ return null; ++ } ++ ++ final String update = ComponentUtils.createPlainTextComponent(displayName); ++ ++ data.setString("DisplayName", update); ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.TEAM.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String displayName = data.getString("DisplayName"); ++ if (displayName == null) { ++ return null; ++ } ++ ++ final String update = ComponentUtils.createPlainTextComponent(displayName); ++ ++ data.setString("DisplayName", update); ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.OBJECTIVE.addStructureConverter(new DataConverter<>(VERSION) { ++ private static String getRenderType(String string) { ++ return string.equals("health") ? "hearts" : "integer"; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String renderType = data.getString("RenderType"); ++ if (renderType != null) { ++ return null; ++ } ++ ++ final String criteriaName = data.getString("CriteriaName", ""); ++ ++ data.setString("RenderType", getRenderType(criteriaName)); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java +new file mode 100644 +index 0000000000000000000000000000000000000000..82e3aec90f868cc1def5462bff801527daaa4362 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java +@@ -0,0 +1,28 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V1515 { ++ ++ protected static final int VERSION = MCVersions.V1_13_PRE7 + 2; ++ ++ public static final Map RENAMED_BLOCK_IDS = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:tube_coral_fan", "minecraft:tube_coral_wall_fan") ++ .put("minecraft:brain_coral_fan", "minecraft:brain_coral_wall_fan") ++ .put("minecraft:bubble_coral_fan", "minecraft:bubble_coral_wall_fan") ++ .put("minecraft:fire_coral_fan", "minecraft:fire_coral_wall_fan") ++ .put("minecraft:horn_coral_fan", "minecraft:horn_coral_wall_fan") ++ .build() ++ ); ++ ++ private V1515() {} ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, RENAMED_BLOCK_IDS::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7b304031e1e8af120c6535e599c2ee4fdbce1682 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java +@@ -0,0 +1,110 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.mojang.logging.LogUtils; ++import it.unimi.dsi.fastutil.ints.IntOpenHashSet; ++import org.slf4j.Logger; ++ ++public final class V1624 { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V18W32A + 1; ++ ++ private V1624() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ ++ if (level == null) { ++ return null; ++ } ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections == null) { ++ return null; ++ } ++ ++ final IntOpenHashSet positionsToLook = new IntOpenHashSet(); ++ ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final TrappedChestSection section = new TrappedChestSection(sections.getMap(i)); ++ if (section.isSkippable()) { ++ continue; ++ } ++ ++ for (int index = 0; index < 4096; ++index) { ++ if (section.isTrappedChest(section.getBlock(index))) { ++ positionsToLook.add(section.getSectionY() << 12 | index); ++ } ++ } ++ } ++ ++ final int chunkX = level.getInt("xPos"); ++ final int chunkZ = level.getInt("zPos"); ++ ++ final ListType tileEntities = level.getList("TileEntities", ObjectType.MAP); ++ ++ if (tileEntities != null) { ++ for (int i = 0, len = tileEntities.size(); i < len; ++i) { ++ final MapType tile = tileEntities.getMap(i); ++ ++ final int x = tile.getInt("x"); ++ final int y = tile.getInt("y"); ++ final int z = tile.getInt("z"); ++ ++ final int index = V1496.getIndex(x - (chunkX << 4), y, z - (chunkZ << 4)); ++ if (!positionsToLook.contains(index)) { ++ continue; ++ } ++ ++ final String id = tile.getString("id"); ++ if (!"minecraft:chest".equals(id)) { ++ LOGGER.warn("Block Entity ({},{},{}) was expected to be a chest (V1624)", x, y, z); ++ } ++ ++ tile.setString("id", "minecraft:trapped_chest"); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ public static final class TrappedChestSection extends V1496.Section { ++ ++ private IntOpenHashSet chestIds; ++ ++ public TrappedChestSection(final MapType section) { ++ super(section); ++ } ++ ++ @Override ++ protected boolean initSkippable() { ++ this.chestIds = new IntOpenHashSet(); ++ ++ for (int i = 0; i < this.palette.size(); ++i) { ++ final MapType blockState = this.palette.getMap(i); ++ final String name = blockState.getString("Name"); ++ if ("minecraft:trapped_chest".equals(name)) { ++ this.chestIds.add(i); ++ } ++ } ++ ++ return this.chestIds.isEmpty(); ++ } ++ ++ public boolean isTrappedChest(final int id) { ++ return this.chestIds.contains(id); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java +new file mode 100644 +index 0000000000000000000000000000000000000000..20eebfbbf913c92886a21fa4790c64cca8d8ba88 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java +@@ -0,0 +1,79 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.gson.JsonParseException; ++import net.minecraft.network.chat.CommonComponents; ++import net.minecraft.network.chat.Component; ++import net.minecraft.util.GsonHelper; ++import net.minecraft.util.datafix.fixes.BlockEntitySignTextStrictJsonFix; ++import org.apache.commons.lang3.StringUtils; ++ ++public final class V165 { ++ ++ protected static final int VERSION = MCVersions.V1_9_PRE2; ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final ListType pages = tag.getList("pages", ObjectType.STRING); ++ if (pages == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = pages.size(); i < len; ++i) { ++ final String page = pages.getString(i); ++ Component component = null; ++ ++ if (!"null".equals(page) && !StringUtils.isEmpty(page)) { ++ if (page.charAt(0) == '"' && page.charAt(page.length() - 1) == '"' || page.charAt(0) == '{' && page.charAt(page.length() - 1) == '}') { ++ try { ++ component = GsonHelper.fromNullableJson(BlockEntitySignTextStrictJsonFix.GSON, page, Component.class, true); ++ if (component == null) { ++ component = CommonComponents.EMPTY; ++ } ++ } catch (final JsonParseException ignored) {} ++ ++ if (component == null) { ++ try { ++ component = Component.Serializer.fromJson(page); ++ } catch (final JsonParseException ignored) {} ++ } ++ ++ if (component == null) { ++ try { ++ component = Component.Serializer.fromJsonLenient(page); ++ } catch (JsonParseException ignored) {} ++ } ++ ++ if (component == null) { ++ component = Component.literal(page); ++ } ++ } else { ++ component = Component.literal(page); ++ } ++ } else { ++ component = CommonComponents.EMPTY; ++ } ++ ++ pages.setString(i, Component.Serializer.toJson(component)); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V165() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1be2154d9f9e8f33266aa745790f3bff30260f6f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java +@@ -0,0 +1,36 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V1800 { ++ ++ protected static final int VERSION = MCVersions.V1_13_2 + 169; ++ ++ public static final Map RENAMED_ITEM_IDS = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:cactus_green", "minecraft:green_dye") ++ .put("minecraft:rose_red", "minecraft:red_dye") ++ .put("minecraft:dandelion_yellow", "minecraft:yellow_dye") ++ .build() ++ ); ++ ++ private V1800() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, RENAMED_ITEM_IDS::get); ++ ++ registerMob("minecraft:panda"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:pillager", new DataWalkerItemLists("Inventory", "ArmorItems", "HandItems")); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a4e2fa4ed6ede8d1783b5591ef1b5ebf3cd28bf0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V1801 { ++ ++ protected static final int VERSION = MCVersions.V1_13_2 + 170; ++ ++ private V1801() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:illager_beast"); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cd5110ef3c18662871020456b60edfb3aeb67166 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java +@@ -0,0 +1,25 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V1802 { ++ ++ protected static final int VERSION = MCVersions.V1_13_2 + 171; ++ ++ private V1802() {} ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( ++ "minecraft:stone_slab", "minecraft:smooth_stone_slab", ++ "minecraft:sign", "minecraft:oak_sign", "minecraft:wall_sign", "minecraft:oak_wall_sign" ++ )::get); ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:stone_slab", "minecraft:smooth_stone_slab", ++ "minecraft:sign", "minecraft:oak_sign" ++ )::get); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9ca850f1bfc9138c68a127a9c90fd33ca81e5dbc +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java +@@ -0,0 +1,47 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V1803 { ++ ++ protected static final int VERSION = MCVersions.V1_13_2 + 172; ++ ++ private V1803() {} ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ ++ if (tag == null) { ++ return null; ++ } ++ ++ final MapType display = tag.getMap("display"); ++ ++ if (display == null) { ++ return null; ++ } ++ ++ final ListType lore = display.getList("Lore", ObjectType.STRING); ++ if (lore == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = lore.size(); i < len; ++i) { ++ lore.setString(i, ComponentUtils.createPlainTextComponent(lore.getString(i))); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java +new file mode 100644 +index 0000000000000000000000000000000000000000..09955e0c2245d8d42ce6ae664ae81e97db8a85f2 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java +@@ -0,0 +1,44 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1904 { ++ ++ protected static final int VERSION = MCVersions.V18W43C + 1; ++ ++ private V1904() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:ocelot", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int catType = data.getInt("CatType"); ++ ++ if (catType == 0) { ++ final String owner = data.getString("Owner"); ++ final String ownerUUID = data.getString("OwnerUUID"); ++ if ((owner != null && owner.length() > 0) || (ownerUUID != null && ownerUUID.length() > 0)) { ++ data.setBoolean("Trusting", true); ++ } ++ } else if (catType > 0 && catType < 4) { ++ data.setString("id", "minecraft:cat"); ++ data.setString("OwnerUUID", data.getString("OwnerUUID", "")); ++ } ++ ++ return null; ++ } ++ }); ++ ++ registerMob("minecraft:cat"); ++ } ++ ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2eeec0d9cbd35ff20ba239ea7fd9c2f52f7e4f9e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java +@@ -0,0 +1,35 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1905 { ++ ++ protected static final int VERSION = MCVersions.V18W43C + 2; ++ ++ private V1905() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ ++ if (level == null) { ++ return null; ++ } ++ ++ final String status = level.getString("Status"); ++ ++ if ("postprocessed".equals(status)) { ++ level.setString("Status", "fullchunk"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6358c2e0861a3743a3ea6d46a644870892256a79 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++ ++public final class V1906 { ++ ++ protected static final int VERSION = MCVersions.V18W43C + 3; ++ ++ private V1906() {} ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:barrel", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:smoker", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:blast_furnace", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:lectern", new DataWalkerItems("Book")); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c3207b60967225f875b7cf763c2c6634d6886a34 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java +@@ -0,0 +1,51 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V1911 { ++ ++ protected static final int VERSION = MCVersions.V18W46A + 1; ++ ++ private static final Map CHUNK_STATUS_REMAP = new HashMap<>( ++ ImmutableMap.builder() ++ .put("structure_references", "empty") ++ .put("biomes", "empty") ++ .put("base", "surface") ++ .put("carved", "carvers") ++ .put("liquid_carved", "liquid_carvers") ++ .put("decorated", "features") ++ .put("lighted", "light") ++ .put("mobs_spawned", "spawn") ++ .put("finalized", "heightmaps") ++ .put("fullchunk", "full") ++ .build() ++ ); ++ ++ ++ private V1911() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ ++ if (level == null) { ++ return null; ++ } ++ ++ final String status = level.getString("Status", "empty"); ++ level.setString("Status", CHUNK_STATUS_REMAP.getOrDefault(status, "empty")); ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8f5a48c4824080827d2dad057ae70dfd7a11818f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java +@@ -0,0 +1,27 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1914 { ++ ++ protected static final int VERSION = MCVersions.V18W48A; ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:chest", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String lootTable = data.getString("LootTable"); ++ ++ if ("minecraft:chests/village_blacksmith".equals(lootTable)) { ++ data.setString("LootTable", "minecraft:chests/village/village_weaponsmith"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java +new file mode 100644 +index 0000000000000000000000000000000000000000..71538d858a681c91f7193003e0808cdb4fd1f847 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java +@@ -0,0 +1,26 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1917 { ++ ++ protected static final int VERSION = MCVersions.V18W49A + 1; ++ ++ private V1917() {} ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getInt("CatType") == 9) { ++ data.setInt("CatType", 10); ++ } ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java +new file mode 100644 +index 0000000000000000000000000000000000000000..28fc06da723792e9abc4999376c0941f9a835aff +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java +@@ -0,0 +1,65 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V1918 { ++ ++ protected static final int VERSION = MCVersions.V18W49A + 2; ++ ++ private V1918() {} ++ ++ private static String getProfessionString(final int professionId, final int careerId) { ++ if (professionId == 0) { ++ if (careerId == 2) { ++ return "minecraft:fisherman"; ++ } else if (careerId == 3) { ++ return "minecraft:shepherd"; ++ } else { ++ return careerId == 4 ? "minecraft:fletcher" : "minecraft:farmer"; ++ } ++ } else if (professionId == 1) { ++ return careerId == 2 ? "minecraft:cartographer" : "minecraft:librarian"; ++ } else if (professionId == 2) { ++ return "minecraft:cleric"; ++ } else if (professionId == 3) { ++ if (careerId == 2) { ++ return "minecraft:weaponsmith"; ++ } else { ++ return careerId == 3 ? "minecraft:toolsmith" : "minecraft:armorer"; ++ } ++ } else if (professionId == 4) { ++ return careerId == 2 ? "minecraft:leatherworker" : "minecraft:butcher"; ++ } else { ++ return professionId == 5 ? "minecraft:nitwit" : "minecraft:none"; ++ } ++ } ++ ++ public static void register() { ++ final DataConverter, MapType> converter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int profession = data.getInt("Profession"); ++ final int career = data.getInt("Career"); ++ final int careerLevel = data.getInt("CareerLevel", 1); ++ data.remove("Profession"); ++ data.remove("Career"); ++ data.remove("CareerLevel"); ++ ++ final MapType villagerData = Types.NBT.createEmptyMap(); ++ data.setMap("VillagerData", villagerData); ++ villagerData.setString("type", "minecraft:plains"); ++ villagerData.setString("profession", getProfessionString(profession, career)); ++ villagerData.setInt("level", careerLevel); ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", converter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", converter); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java +new file mode 100644 +index 0000000000000000000000000000000000000000..224d35620e9d9e65f0642fdb13f80fcb2667a2ee +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java +@@ -0,0 +1,75 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.util.NamespaceUtil; ++ ++public final class V1920 { ++ ++ protected static final int VERSION = MCVersions.V18W50A + 1; ++ ++ private V1920() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ final MapType structures = level.getMap("Structures"); ++ if (structures == null) { ++ return null; ++ } ++ ++ final MapType starts = structures.getMap("Starts"); ++ if (starts != null) { ++ final MapType village = starts.getMap("New_Village"); ++ if (village != null) { ++ starts.remove("New_Village"); ++ starts.setMap("Village", village); ++ } else { ++ starts.remove("Village"); ++ } ++ } ++ ++ final MapType references = structures.getMap("References"); ++ if (references != null) { ++ final MapType newVillage = references.getMap("New_Village"); ++ // I believe Mojang had a typo here, removing Village from references only made sense ++ // if the new village didn't exist. DFU removes it whether or not it exists, but still relocates ++ // New_Village to Village first. It doesn't make sense to me to relocate it just to remove it, so it ++ // must be a typo. ++ if (newVillage == null) { ++ references.remove("Village"); ++ } else { ++ references.remove("New_Village"); ++ references.setMap("Village", newVillage); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String id = data.getString("id"); ++ ++ if ("minecraft:new_village".equals(NamespaceUtil.correctNamespace(id))) { ++ data.setString("id", "minecraft:village"); ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:campfire", new DataWalkerItemLists("Items")); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9a063df2f4d09bd561f5a538c440b43b1c0fb581 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java +@@ -0,0 +1,29 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V1925 { ++ ++ protected static final int VERSION = MCVersions.V19W03C + 1; ++ ++ public static void register() { ++ MCTypeRegistry.SAVED_DATA_MAP_DATA.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { ++ final MapType data = root.getMap("data"); ++ if (data == null) { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ret.setMap("data", root); ++ ++ return ret; ++ } ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java +new file mode 100644 +index 0000000000000000000000000000000000000000..86caefba615a917d3530112edcc083caaaea4bb9 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java +@@ -0,0 +1,30 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V1928 { ++ ++ protected static final int VERSION = MCVersions.V19W04B + 1; ++ ++ private V1928() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of( ++ "minecraft:illager_beast", "minecraft:ravager" ++ )::get); ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:illager_beast_spawn_egg", "minecraft:ravager_spawn_egg" ++ )::get); ++ ++ registerMob("minecraft:ravager"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d58c32aa89e416e40cfef7c5840b772dd4991173 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java +@@ -0,0 +1,49 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V1929 { ++ ++ protected static final int VERSION = MCVersions.V19W04B + 2; ++ ++ private V1929() {} ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:wandering_trader", (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); ++ ++ final MapType offers = data.getMap("Offers"); ++ if (offers != null) { ++ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); ++ if (recipes != null) { ++ for (int i = 0, len = recipes.size(); i < len; ++i) { ++ final MapType recipe = recipes.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); ++ } ++ } ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); ++ ++ return null; ++ }); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:trader_llama", (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, data, "SaddleItem", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, data, "DecorItem", fromVersion, toVersion); ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Items", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java +new file mode 100644 +index 0000000000000000000000000000000000000000..da845df91d42f1059490d749b235758850ce7254 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V1931 { ++ ++ protected static final int VERSION = MCVersions.V19W06A; ++ ++ private V1931() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:fox"); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4e7b22874f17f531b583146db3aa4e57bdd5f27c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java +@@ -0,0 +1,37 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1936 { ++ ++ protected static final int VERSION = MCVersions.V19W09A + 1; ++ ++ private V1936() {} ++ ++ public static void register() { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String chatOpacity = data.getString("chatOpacity"); ++ if (chatOpacity != null) { ++ // Vanilla uses createDouble here, but options is always string -> string. I presume they made ++ // a mistake with this converter. ++ data.setString("textBackgroundOpacity", Double.toString(calculateBackground(chatOpacity))); ++ } ++ return null; ++ } ++ }); ++ } ++ ++ private static double calculateBackground(final String opacity) { ++ try { ++ final double d = 0.9D * Double.parseDouble(opacity) + 0.1D; ++ return d / 2.0D; ++ } catch (final NumberFormatException ex) { ++ return 0.5D; ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java +new file mode 100644 +index 0000000000000000000000000000000000000000..00e4bd45b04feee990d9d3414c34c0966e65e4ea +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java +@@ -0,0 +1,42 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V1946 { ++ ++ protected static final int VERSION = MCVersions.V19W14B + 1; ++ ++ private V1946() {} ++ ++ public static void register() { ++ MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType sections = Types.NBT.createEmptyMap(); ++ data.setMap("Sections", sections); ++ ++ for (int y = 0; y < 16; ++y) { ++ final String key = Integer.toString(y); ++ final Object records = data.getGeneric(key); ++ ++ if (records == null) { ++ continue; ++ } ++ ++ data.remove(key); ++ ++ final MapType section = Types.NBT.createEmptyMap(); ++ section.setGeneric("Records", records); ++ sections.setMap(key, section); // integer keys convert to string in DFU (at least for NBT ops) ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6b4af1fe7c53e6122d7db952770d14a753f8cab3 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java +@@ -0,0 +1,39 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1948 { ++ ++ protected static final int VERSION = MCVersions.V1_14_PRE2; ++ ++ private V1948() {} ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:white_banner", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final MapType display = tag.getMap("display"); ++ if (display == null) { ++ return null; ++ } ++ ++ final String name = display.getString("Name"); ++ if (name == null) { ++ return null; ++ } ++ ++ display.setString("Name", name.replace("\"translate\":\"block.minecraft.illager_banner\"", "\"translate\":\"block.minecraft.ominous_banner\"")); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a73471e8f3df3b22349b2f842c3e98c2ff8bb5e1 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java +@@ -0,0 +1,27 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1953 { ++ ++ protected static final int VERSION = MCVersions.V1_14 + 1; ++ ++ private V1953() {} ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:banner", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String name = data.getString("CustomName"); ++ if (name != null) { ++ data.setString("CustomName", name.replace("\"translate\":\"block.minecraft.illager_banner\"", "\"translate\":\"block.minecraft.ominous_banner\"")); ++ } ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java +new file mode 100644 +index 0000000000000000000000000000000000000000..33bfe82709b507c4fd57199f5d8a44d131718d7f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java +@@ -0,0 +1,94 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import net.minecraft.util.Mth; ++ ++public final class V1955 { ++ ++ protected static final int VERSION = MCVersions.V1_14_1_PRE1; ++ ++ private static final int[] LEVEL_XP_THRESHOLDS = new int[] { ++ 0, ++ 10, ++ 50, ++ 100, ++ 150 ++ }; ++ ++ private V1955() {} ++ ++ static int getMinXpPerLevel(final int level) { ++ return LEVEL_XP_THRESHOLDS[Mth.clamp(level - 1, 0, LEVEL_XP_THRESHOLDS.length - 1)]; ++ } ++ ++ static void addLevel(final MapType data, final int level) { ++ MapType villagerData = data.getMap("VillagerData"); ++ if (villagerData == null) { ++ villagerData = Types.NBT.createEmptyMap(); ++ data.setMap("VillagerData", villagerData); ++ } ++ villagerData.setInt("level", level); ++ } ++ ++ static void addXpFromLevel(final MapType data, final int level) { ++ data.setInt("Xp", getMinXpPerLevel(level)); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType villagerData = data.getMap("VillagerData"); ++ int level = villagerData == null ? 0 : villagerData.getInt("level"); ++ if (level == 0 || level == 1) { ++ // count recipes ++ final MapType offers = data.getMap("Offers"); ++ final ListType recipes = offers == null ? null : offers.getList("Recipes", ObjectType.MAP); ++ final int recipeCount; ++ if (recipes != null) { ++ recipeCount = recipes.size(); ++ } else { ++ recipeCount = 0; ++ } ++ ++ level = Mth.clamp(recipeCount / 2, 1, 5); ++ if (level > 1) { ++ addLevel(data, level); ++ } ++ } ++ ++ if (!data.hasKey("Xp", ObjectType.NUMBER)) { ++ addXpFromLevel(data, level); ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final Number xp = data.getNumber("Xp"); ++ if (xp == null) { ++ final int level; ++ final MapType villagerData = data.getMap("VillagerData"); ++ if (villagerData == null) { ++ level = 1; ++ } else { ++ level = villagerData.getInt("level", 1); ++ } ++ ++ data.setInt("Xp", getMinXpPerLevel(level)); ++ } ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8bc6a8734034942a81b282b8766b21fddbe2b304 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java +@@ -0,0 +1,30 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1961 { ++ ++ protected static final int VERSION = MCVersions.V1_14_2_PRE3 + 1; ++ ++ private V1961() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ level.remove("isLightOn"); ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5e4e7299cec1d3809d1b55ae460e64ec6d2bb477 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java +@@ -0,0 +1,40 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V1963 { ++ ++ protected static final int VERSION = MCVersions.V1_14_2; ++ ++ private V1963() {} ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType gossips = data.getList("Gossips", ObjectType.MAP); ++ if (gossips == null) { ++ return null; ++ } ++ ++ for (int i = 0; i < gossips.size();) { ++ final MapType gossip = gossips.getMap(i); ++ if ("golem".equals(gossip.getString("Type"))) { ++ gossips.remove(i); ++ continue; ++ } ++ ++ ++i; ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d905043c4f3071e8dc340ac2afe2b81aac345e1e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java +@@ -0,0 +1,46 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.recipe.ConverterAbstractRecipeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V2100 { ++ ++ protected static final int VERSION = MCVersions.V1_14_4 + 124; ++ protected static final Map RECIPE_RENAMES = ImmutableMap.of( ++ "minecraft:sugar", "sugar_from_sugar_cane" ++ ); ++ ++ private V2100() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ ConverterAbstractRecipeRename.register(VERSION, RECIPE_RENAMES::get); ++ ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of( ++ "minecraft:recipes/misc/sugar", "minecraft:recipes/misc/sugar_from_sugar_cane" ++ )::get); ++ ++ registerMob("minecraft:bee"); ++ registerMob("minecraft:bee_stinger"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:beehive", (data, fromVersion, toVersion) -> { ++ final ListType bees = data.getList("Bees", ObjectType.MAP); ++ if (bees != null) { ++ for (int i = 0, len = bees.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, bees.getMap(i), "EntityData", fromVersion, toVersion); ++ } ++ } ++ ++ return null; ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0bb378ac8e8d0a087359361281644a7f39cecfbe +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java +@@ -0,0 +1,50 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2202 { ++ ++ protected static final int VERSION = MCVersions.V19W35A + 1; ++ ++ private V2202() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ final int[] oldBiomes = level.getInts("Biomes"); ++ ++ if (oldBiomes == null || oldBiomes.length != 256) { ++ return null; ++ } ++ ++ final int[] newBiomes = new int[1024]; ++ level.setInts("Biomes", newBiomes); ++ ++ int n; ++ for(n = 0; n < 4; ++n) { ++ for(int j = 0; j < 4; ++j) { ++ int k = (j << 2) + 2; ++ int l = (n << 2) + 2; ++ int m = l << 4 | k; ++ newBiomes[n << 2 | j] = oldBiomes[m]; ++ } ++ } ++ ++ for(n = 1; n < 64; ++n) { ++ System.arraycopy(newBiomes, 0, newBiomes, n * 16, 16); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6054cd31cb2e140f05b92736405a7d073be5cf5c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java +@@ -0,0 +1,26 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.poi.ConverterAbstractPOIRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V2209 { ++ ++ protected static final int VERSION = MCVersions.V19W40A + 1; ++ ++ private V2209() {} ++ ++ public static void register() { ++ final Map renamedIds = ImmutableMap.of( ++ "minecraft:bee_hive", "minecraft:beehive" ++ ); ++ ++ ConverterAbstractBlockRename.register(VERSION, renamedIds::get); ++ ConverterAbstractItemRename.register(VERSION, renamedIds::get); ++ ConverterAbstractPOIRename.register(VERSION, renamedIds::get); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6cff6d723616e0a38811872f7b5d28799240ddfe +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java +@@ -0,0 +1,32 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V2211 { ++ ++ protected static final int VERSION = MCVersions.V19W41A + 1; ++ ++ private V2211() {} ++ ++ public static void register() { ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!data.hasKey("references", ObjectType.NUMBER)) { ++ return null; ++ } ++ ++ final int references = data.getInt("references"); ++ if (references <= 0) { ++ data.setInt("references", 1); ++ } ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e06a98a01086c9d6eb9fc80a151f0403247b0033 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java +@@ -0,0 +1,33 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2218 { ++ ++ protected static final int VERSION = MCVersions.V1_15_PRE1; ++ ++ private V2218() {} ++ ++ public static void register() { ++ MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType sections = data.getMap("Sections"); ++ if (sections == null) { ++ return null; ++ } ++ ++ for (final String key : sections.keys()) { ++ final MapType section = sections.getMap(key); ++ ++ section.remove("Valid"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c8901f95e1076ae8be220c03efd83ce9bd18d2a8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java +@@ -0,0 +1,65 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V2501 { ++ ++ protected static final int VERSION = MCVersions.V1_15_2 + 271; ++ ++ private V2501() {} ++ ++ private static void registerFurnace(final String id) { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, (data, fromVersion, toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Items", fromVersion, toVersion); ++ ++ WalkerUtils.convertKeys(MCTypeRegistry.RECIPE, data, "RecipesUsed", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++ ++ public static void register() { ++ final DataConverter, MapType> converter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int recipesUsedSize = data.getInt("RecipesUsedSize"); ++ data.remove("RecipesUsedSize"); ++ ++ if (recipesUsedSize <= 0) { ++ return null; ++ } ++ ++ final MapType newRecipes = Types.NBT.createEmptyMap(); ++ data.setMap("RecipesUsed", newRecipes); ++ ++ for (int i = 0; i < recipesUsedSize; ++i) { ++ final String recipeKey = data.getString("RecipeLocation" + i); ++ data.remove("RecipeLocation" + i); ++ final int recipeAmount = data.getInt("RecipeAmount" + i); ++ data.remove("RecipeAmount" + i); ++ ++ if (i <= 0 || recipeKey == null) { ++ continue; ++ } ++ ++ newRecipes.setInt(recipeKey, recipeAmount); ++ } ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:furnace", converter); ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:blast_furnace", converter); ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:smoker", converter); ++ ++ registerFurnace("minecraft:furnace"); ++ registerFurnace("minecraft:smoker"); ++ registerFurnace("minecraft:blast_furnace"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7075f7beb8aacfdadd68f4353d2cf32edaa1905d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2502 { ++ ++ protected static final int VERSION = MCVersions.V1_15_2 + 272; ++ ++ private V2502() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:hoglin"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cd1bca807236b917244bbacd8df6f25fd3c42407 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java +@@ -0,0 +1,67 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.ImmutableSet; ++import java.util.Set; ++ ++public final class V2503 { ++ ++ protected static final int VERSION = MCVersions.V1_15_2 + 273; ++ ++ private static final Set WALL_BLOCKS = ImmutableSet.of( ++ "minecraft:andesite_wall", ++ "minecraft:brick_wall", ++ "minecraft:cobblestone_wall", ++ "minecraft:diorite_wall", ++ "minecraft:end_stone_brick_wall", ++ "minecraft:granite_wall", ++ "minecraft:mossy_cobblestone_wall", ++ "minecraft:mossy_stone_brick_wall", ++ "minecraft:nether_brick_wall", ++ "minecraft:prismarine_wall", ++ "minecraft:red_nether_brick_wall", ++ "minecraft:red_sandstone_wall", ++ "minecraft:sandstone_wall", ++ "minecraft:stone_brick_wall" ++ ); ++ ++ private V2503() {} ++ ++ private static void changeWallProperty(final MapType properties, final String path) { ++ final String property = properties.getString(path); ++ if (property != null) { ++ properties.setString(path, "true".equals(property) ? "low" : "none"); ++ } ++ } ++ ++ public static void register() { ++ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!WALL_BLOCKS.contains(data.getString("Name"))) { ++ return null; ++ } ++ ++ final MapType properties = data.getMap("Properties"); ++ if (properties == null) { ++ return null; ++ } ++ ++ changeWallProperty(properties, "east"); ++ changeWallProperty(properties, "west"); ++ changeWallProperty(properties, "north"); ++ changeWallProperty(properties, "south"); ++ ++ return null; ++ } ++ }); ++ ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of( ++ "minecraft:recipes/misc/composter", "minecraft:recipes/decorations/composter" ++ )::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java +new file mode 100644 +index 0000000000000000000000000000000000000000..350f5cca06d2a864b5a3cc028753fd6489a28ad5 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java +@@ -0,0 +1,49 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V2505 { ++ ++ protected static final int VERSION = MCVersions.V20W06A + 1; ++ ++ private V2505() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType brain = data.getMap("Brain"); ++ if (brain == null) { ++ return null; ++ } ++ ++ final MapType memories = brain.getMap("memories"); ++ if (memories == null) { ++ return null; ++ } ++ ++ for (final String key : memories.keys()) { ++ final Object value = memories.getGeneric(key); ++ ++ final MapType wrapped = Types.NBT.createEmptyMap(); ++ wrapped.setGeneric("value", value); ++ ++ memories.setMap(key, wrapped); ++ } ++ ++ return null; ++ } ++ }); ++ ++ registerMob("minecraft:piglin"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8cfd380e870ceea4892b8cd7bf855a6e5dc8cc6f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java +@@ -0,0 +1,24 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V2508 { ++ ++ protected static final int VERSION = MCVersions.V20W08A + 1; ++ ++ private V2508() {} ++ ++ public static void register() { ++ final Map remap = ImmutableMap.of( ++ "minecraft:warped_fungi", "minecraft:warped_fungus", ++ "minecraft:crimson_fungi", "minecraft:crimson_fungus" ++ ); ++ ++ ConverterAbstractBlockRename.register(VERSION, remap::get); ++ ConverterAbstractItemRename.register(VERSION, remap::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1fa93dec8a81719a19c0f7db589778935ce44d56 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java +@@ -0,0 +1,30 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2509 { ++ ++ protected static final int VERSION = MCVersions.V20W08A + 2; ++ ++ private V2509() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:zombie_pigman_spawn_egg", "minecraft:zombified_piglin_spawn_egg" ++ )::get); ++ ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of( ++ "minecraft:zombie_pigman", "minecraft:zombified_piglin" ++ )::get); ++ ++ registerMob("minecraft:zombified_piglin"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java +new file mode 100644 +index 0000000000000000000000000000000000000000..183ab7ed77e30bf87e71e5f682a59fc3a64a7672 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java +@@ -0,0 +1,97 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V2511 { ++ ++ protected static final int VERSION = MCVersions.V20W09A + 1; ++ ++ private V2511() {} ++ ++ private static int[] createUUIDArray(final long most, final long least) { ++ return new int[] { ++ (int)(most >>> 32), ++ (int)most, ++ (int)(least >>> 32), ++ (int)least ++ }; ++ } ++ ++ private static void setUUID(final MapType data, final long most, final long least) { ++ if (most != 0L && least != 0L) { ++ data.setInts("OwnerUUID", createUUIDArray(most, least)); ++ } ++ } ++ ++ public static void register() { ++ final DataConverter, MapType> throwableConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType owner = data.getMap("owner"); ++ data.remove("owner"); ++ if (owner == null) { ++ return null; ++ } ++ ++ setUUID(data, owner.getLong("M"), owner.getLong("L")); ++ ++ return null; ++ } ++ }; ++ final DataConverter, MapType> potionConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType potion = data.getMap("Potion"); ++ data.remove("Potion"); ++ ++ data.setMap("Item", potion == null ? Types.NBT.createEmptyMap() : potion); ++ ++ return null; ++ } ++ }; ++ final DataConverter, MapType> llamaSpitConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType owner = data.getMap("Owner"); ++ data.remove("Owner"); ++ if (owner == null) { ++ return null; ++ } ++ ++ setUUID(data, owner.getLong("OwnerUUIDMost"), owner.getLong("OwnerUUIDLeast")); ++ ++ return null; ++ } ++ }; ++ final DataConverter, MapType> arrowConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ setUUID(data, data.getLong("OwnerUUIDMost"), data.getLong("OwnerUUIDLeast")); ++ ++ data.remove("OwnerUUIDMost"); ++ data.remove("OwnerUUIDLeast"); ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:egg", throwableConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:ender_pearl", throwableConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:experience_bottle", throwableConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:snowball", throwableConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:potion", throwableConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:potion", potionConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:llama_spit", llamaSpitConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:arrow", arrowConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:spectral_arrow", arrowConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:trident", arrowConverter); ++ ++ // Vanilla migrates the potion item but does not change the schema. ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:potion", new DataWalkerItems("Item")); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a76bb0d8d27a734b397eefd587d1baf79bc55c82 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java +@@ -0,0 +1,590 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.common.collect.Sets; ++import java.util.Set; ++import java.util.UUID; ++ ++public final class V2514 { ++ ++ protected static final int VERSION = MCVersions.V20W11A + 1; ++ ++ private static final Set ABSTRACT_HORSES = Sets.newHashSet(); ++ private static final Set TAMEABLE_ANIMALS = Sets.newHashSet(); ++ private static final Set ANIMALS = Sets.newHashSet(); ++ private static final Set MOBS = Sets.newHashSet(); ++ private static final Set LIVING_ENTITIES = Sets.newHashSet(); ++ private static final Set PROJECTILES = Sets.newHashSet(); ++ static { ++ ABSTRACT_HORSES.add("minecraft:donkey"); ++ ABSTRACT_HORSES.add("minecraft:horse"); ++ ABSTRACT_HORSES.add("minecraft:llama"); ++ ABSTRACT_HORSES.add("minecraft:mule"); ++ ABSTRACT_HORSES.add("minecraft:skeleton_horse"); ++ ABSTRACT_HORSES.add("minecraft:trader_llama"); ++ ABSTRACT_HORSES.add("minecraft:zombie_horse"); ++ ++ TAMEABLE_ANIMALS.add("minecraft:cat"); ++ TAMEABLE_ANIMALS.add("minecraft:parrot"); ++ TAMEABLE_ANIMALS.add("minecraft:wolf"); ++ ++ ANIMALS.add("minecraft:bee"); ++ ANIMALS.add("minecraft:chicken"); ++ ANIMALS.add("minecraft:cow"); ++ ANIMALS.add("minecraft:fox"); ++ ANIMALS.add("minecraft:mooshroom"); ++ ANIMALS.add("minecraft:ocelot"); ++ ANIMALS.add("minecraft:panda"); ++ ANIMALS.add("minecraft:pig"); ++ ANIMALS.add("minecraft:polar_bear"); ++ ANIMALS.add("minecraft:rabbit"); ++ ANIMALS.add("minecraft:sheep"); ++ ANIMALS.add("minecraft:turtle"); ++ ANIMALS.add("minecraft:hoglin"); ++ ++ MOBS.add("minecraft:bat"); ++ MOBS.add("minecraft:blaze"); ++ MOBS.add("minecraft:cave_spider"); ++ MOBS.add("minecraft:cod"); ++ MOBS.add("minecraft:creeper"); ++ MOBS.add("minecraft:dolphin"); ++ MOBS.add("minecraft:drowned"); ++ MOBS.add("minecraft:elder_guardian"); ++ MOBS.add("minecraft:ender_dragon"); ++ MOBS.add("minecraft:enderman"); ++ MOBS.add("minecraft:endermite"); ++ MOBS.add("minecraft:evoker"); ++ MOBS.add("minecraft:ghast"); ++ MOBS.add("minecraft:giant"); ++ MOBS.add("minecraft:guardian"); ++ MOBS.add("minecraft:husk"); ++ MOBS.add("minecraft:illusioner"); ++ MOBS.add("minecraft:magma_cube"); ++ MOBS.add("minecraft:pufferfish"); ++ MOBS.add("minecraft:zombified_piglin"); ++ MOBS.add("minecraft:salmon"); ++ MOBS.add("minecraft:shulker"); ++ MOBS.add("minecraft:silverfish"); ++ MOBS.add("minecraft:skeleton"); ++ MOBS.add("minecraft:slime"); ++ MOBS.add("minecraft:snow_golem"); ++ MOBS.add("minecraft:spider"); ++ MOBS.add("minecraft:squid"); ++ MOBS.add("minecraft:stray"); ++ MOBS.add("minecraft:tropical_fish"); ++ MOBS.add("minecraft:vex"); ++ MOBS.add("minecraft:villager"); ++ MOBS.add("minecraft:iron_golem"); ++ MOBS.add("minecraft:vindicator"); ++ MOBS.add("minecraft:pillager"); ++ MOBS.add("minecraft:wandering_trader"); ++ MOBS.add("minecraft:witch"); ++ MOBS.add("minecraft:wither"); ++ MOBS.add("minecraft:wither_skeleton"); ++ MOBS.add("minecraft:zombie"); ++ MOBS.add("minecraft:zombie_villager"); ++ MOBS.add("minecraft:phantom"); ++ MOBS.add("minecraft:ravager"); ++ MOBS.add("minecraft:piglin"); ++ ++ LIVING_ENTITIES.add("minecraft:armor_stand"); ++ ++ PROJECTILES.add("minecraft:arrow"); ++ PROJECTILES.add("minecraft:dragon_fireball"); ++ PROJECTILES.add("minecraft:firework_rocket"); ++ PROJECTILES.add("minecraft:fireball"); ++ PROJECTILES.add("minecraft:llama_spit"); ++ PROJECTILES.add("minecraft:small_fireball"); ++ PROJECTILES.add("minecraft:snowball"); ++ PROJECTILES.add("minecraft:spectral_arrow"); ++ PROJECTILES.add("minecraft:egg"); ++ PROJECTILES.add("minecraft:ender_pearl"); ++ PROJECTILES.add("minecraft:experience_bottle"); ++ PROJECTILES.add("minecraft:potion"); ++ PROJECTILES.add("minecraft:trident"); ++ PROJECTILES.add("minecraft:wither_skull"); ++ } ++ ++ static int[] createUUIDArray(final long most, final long least) { ++ return new int[] { ++ (int)(most >>> 32), ++ (int)most, ++ (int)(least >>> 32), ++ (int)least ++ }; ++ } ++ ++ static int[] createUUIDFromString(final MapType data, final String path) { ++ if (data == null) { ++ return null; ++ } ++ ++ final String uuidString = data.getString(path); ++ if (uuidString == null) { ++ return null; ++ } ++ ++ try { ++ final UUID uuid = UUID.fromString(uuidString); ++ return createUUIDArray(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()); ++ } catch (final IllegalArgumentException ignore) { ++ return null; ++ } ++ } ++ ++ static int[] createUUIDFromLongs(final MapType data, final String most, final String least) { ++ if (data == null) { ++ return null; ++ } ++ ++ final long mostBits = data.getLong(most); ++ final long leastBits = data.getLong(least); ++ ++ return (mostBits != 0 || leastBits != 0) ? createUUIDArray(mostBits, leastBits) : null; ++ } ++ ++ static void replaceUUIDString(final MapType data, final String oldPath, final String newPath) { ++ final int[] newUUID = createUUIDFromString(data, oldPath); ++ if (newUUID != null) { ++ data.remove(oldPath); ++ data.setInts(newPath, newUUID); ++ } ++ } ++ ++ static void replaceUUIDMLTag(final MapType data, final String oldPath, final String newPath) { ++ final int[] uuid = createUUIDFromLongs(data.getMap(oldPath), "M", "L"); ++ if (uuid != null) { ++ data.remove(oldPath); ++ data.setInts(newPath, uuid); ++ } ++ } ++ ++ static void replaceUUIDLeastMost(final MapType data, final String prefix, final String newPath) { ++ final String mostPath = prefix.concat("Most"); ++ final String leastPath = prefix.concat("Least"); ++ ++ final int[] uuid = createUUIDFromLongs(data, mostPath, leastPath); ++ if (uuid != null) { ++ data.remove(mostPath); ++ data.remove(leastPath); ++ data.setInts(newPath, uuid); ++ } ++ } ++ ++ private V2514() {} ++ ++ private static void updatePiglin(final MapType data) { ++ final MapType brain = data.getMap("Brain"); ++ if (brain == null) { ++ return; ++ } ++ ++ final MapType memories = brain.getMap("memories"); ++ if (memories == null) { ++ return; ++ } ++ ++ final MapType angryAt = memories.getMap("minecraft:angry_at"); ++ ++ replaceUUIDString(angryAt, "value", "value"); ++ } ++ ++ private static void updateEvokerFangs(final MapType data) { ++ replaceUUIDLeastMost(data, "OwnerUUID", "Owner"); ++ } ++ ++ private static void updateZombieVillager(final MapType data) { ++ replaceUUIDLeastMost(data, "ConversionPlayer", "ConversionPlayer"); ++ } ++ ++ private static void updateAreaEffectCloud(final MapType data) { ++ replaceUUIDLeastMost(data, "OwnerUUID", "Owner"); ++ } ++ ++ private static void updateShulkerBullet(final MapType data) { ++ replaceUUIDMLTag(data, "Owner", "Owner"); ++ replaceUUIDMLTag(data, "Target", "Target"); ++ } ++ ++ private static void updateItem(final MapType data) { ++ replaceUUIDMLTag(data, "Owner", "Owner"); ++ replaceUUIDMLTag(data, "Thrower", "Thrower"); ++ } ++ ++ private static void updateFox(final MapType data) { ++ final ListType trustedUUIDS = data.getList("TrustedUUIDs", ObjectType.MAP); ++ if (trustedUUIDS == null) { ++ return; ++ } ++ ++ final ListType newUUIDs = Types.NBT.createEmptyList(); ++ data.remove("TrustedUUIDs"); ++ data.setList("Trusted", newUUIDs); ++ ++ for (int i = 0, len = trustedUUIDS.size(); i < len; ++i) { ++ final MapType uuid = trustedUUIDS.getMap(i); ++ final int[] newUUID = createUUIDFromLongs(uuid, "M", "L"); ++ if (newUUID != null) { ++ newUUIDs.addIntArray(newUUID); ++ } ++ } ++ } ++ ++ private static void updateHurtBy(final MapType data) { ++ replaceUUIDString(data, "HurtBy", "HurtBy"); ++ } ++ ++ private static void updateAnimalOwner(final MapType data) { ++ updateAnimal(data); ++ ++ replaceUUIDString(data, "OwnerUUID", "Owner"); ++ } ++ ++ private static void updateAnimal(final MapType data) { ++ updateMob(data); ++ ++ replaceUUIDLeastMost(data, "LoveCause", "LoveCause"); ++ } ++ ++ private static void updateMob(final MapType data) { ++ updateLivingEntity(data); ++ ++ final MapType leash = data.getMap("Leash"); ++ if (leash == null) { ++ return; ++ } ++ ++ replaceUUIDLeastMost(leash, "UUID", "UUID"); ++ } ++ ++ private static void updateLivingEntity(final MapType data) { ++ final ListType attributes = data.getList("Attributes", ObjectType.MAP); ++ if (attributes == null) { ++ return; ++ } ++ ++ for (int i = 0, len = attributes.size(); i < len; ++i) { ++ final MapType attribute = attributes.getMap(i); ++ ++ final ListType modifiers = attribute.getList("Modifiers", ObjectType.MAP); ++ if (modifiers == null) { ++ continue; ++ } ++ ++ for (int k = 0; k < modifiers.size(); ++k) { ++ replaceUUIDLeastMost(modifiers.getMap(k), "UUID", "UUID"); ++ } ++ } ++ } ++ ++ private static void updateProjectile(final MapType data) { ++ final Object ownerUUID = data.getGeneric("OwnerUUID"); ++ if (ownerUUID != null) { ++ data.remove("OwnerUUID"); ++ data.setGeneric("Owner", ownerUUID); ++ } ++ } ++ ++ private static void updateEntityUUID(final MapType data) { ++ replaceUUIDLeastMost(data, "UUID", "UUID"); ++ } ++ ++ public static void register() { ++ // Entity UUID fixes ++ ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateEntityUUID(data); ++ return null; ++ } ++ }); ++ ++ final DataConverter, MapType> animalOwnerConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateAnimalOwner(data); ++ return null; ++ } ++ }; ++ final DataConverter, MapType> animalConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateAnimal(data); ++ return null; ++ } ++ }; ++ final DataConverter, MapType> mobConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateMob(data); ++ return null; ++ } ++ }; ++ final DataConverter, MapType> livingEntityConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateLivingEntity(data); ++ return null; ++ } ++ }; ++ final DataConverter, MapType> projectileConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateProjectile(data); ++ return null; ++ } ++ }; ++ for (final String id : ABSTRACT_HORSES) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, animalOwnerConverter); ++ } ++ for (final String id : TAMEABLE_ANIMALS) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, animalOwnerConverter); ++ } ++ for (final String id : ANIMALS) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, animalConverter); ++ } ++ for (final String id : MOBS) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, mobConverter); ++ } ++ for (final String id : LIVING_ENTITIES) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, livingEntityConverter); ++ } ++ for (final String id : PROJECTILES) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, projectileConverter); ++ } ++ ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:bee", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateHurtBy(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombified_piglin", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateHurtBy(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:fox", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateFox(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:item", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateItem(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker_bullet", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateShulkerBullet(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:area_effect_cloud", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateAreaEffectCloud(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateZombieVillager(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:evoker_fangs", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateEvokerFangs(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:piglin", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updatePiglin(data); ++ return null; ++ } ++ }); ++ ++ ++ // Update TE ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:conduit", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ replaceUUIDMLTag(data, "target_uuid", "Target"); ++ return null; ++ } ++ }); ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:skull", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType owner = data.getMap("Owner"); ++ if (owner == null) { ++ return null; ++ } ++ ++ data.remove("Owner"); ++ ++ replaceUUIDString(owner, "Id", "Id"); ++ ++ data.setMap("SkullOwner", owner); ++ ++ return null; ++ } ++ }); ++ ++ // Player UUID ++ MCTypeRegistry.PLAYER.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateLivingEntity(data); ++ updateEntityUUID(data); ++ ++ final MapType rootVehicle = data.getMap("RootVehicle"); ++ if (rootVehicle == null) { ++ return null; ++ } ++ ++ replaceUUIDLeastMost(rootVehicle, "Attach", "Attach"); ++ ++ return null; ++ } ++ }); ++ ++ // Level.dat ++ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ replaceUUIDString(data, "WanderingTraderId", "WanderingTraderId"); ++ ++ final MapType dimensionData = data.getMap("DimensionData"); ++ if (dimensionData != null) { ++ for (final String key : dimensionData.keys()) { ++ final MapType dimension = dimensionData.getMap(key); ++ ++ final MapType dragonFight = dimension.getMap("DragonFight"); ++ if (dragonFight == null) { ++ continue; ++ } ++ ++ replaceUUIDLeastMost(dragonFight, "DragonUUID", "Dragon"); ++ } ++ } ++ ++ final MapType customBossEvents = data.getMap("CustomBossEvents"); ++ if (customBossEvents != null) { ++ for (final String key : customBossEvents.keys()) { ++ final MapType customBossEvent = customBossEvents.getMap(key); ++ ++ final ListType players = customBossEvent.getList("Players", ObjectType.MAP); ++ if (players == null) { ++ continue; ++ } ++ ++ final ListType newPlayers = Types.NBT.createEmptyList(); ++ customBossEvent.setList("Players", newPlayers); ++ ++ for (int i = 0, len = players.size(); i < len; ++i) { ++ final int[] newUUID = createUUIDFromLongs(players.getMap(i), "M", "L"); ++ if (newUUID != null) { ++ newPlayers.addIntArray(newUUID); ++ } ++ } ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.SAVED_DATA_RAIDS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { ++ final MapType data = root.getMap("data"); ++ if (data == null) { ++ return null; ++ } ++ ++ final ListType raids = data.getList("Raids", ObjectType.MAP); ++ if (raids == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = raids.size(); i < len; ++i) { ++ final MapType raid = raids.getMap(i); ++ ++ final ListType heros = raid.getList("HeroesOfTheVillage", ObjectType.MAP); ++ ++ if (heros == null) { ++ continue; ++ } ++ ++ final ListType newHeros = Types.NBT.createEmptyList(); ++ raid.setList("HeroesOfTheVillage", newHeros); ++ ++ for (int k = 0, klen = heros.size(); k < klen; ++k) { ++ final MapType uuidOld = heros.getMap(i); ++ final int[] uuidNew = createUUIDFromLongs(uuidOld, "UUIDMost", "UUIDLeast"); ++ if (uuidNew != null) { ++ newHeros.addIntArray(uuidNew); ++ } ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ updateAttributeModifiers(tag); ++ ++ if ("minecraft:player_head".equals(data.getString("id"))) { ++ updateSkullOwner(tag); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private static void updateAttributeModifiers(final MapType tag) { ++ final ListType attributes = tag.getList("AttributeModifiers", ObjectType.MAP); ++ if (attributes == null) { ++ return; ++ } ++ ++ for (int i = 0, len = attributes.size(); i < len; ++i) { ++ replaceUUIDLeastMost(attributes.getMap(i), "UUID", "UUID"); ++ } ++ } ++ ++ private static void updateSkullOwner(final MapType tag) { ++ replaceUUIDString(tag.getMap("SkullOwner"), "Id", "Id"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java +new file mode 100644 +index 0000000000000000000000000000000000000000..40bf0a1788520bbf1d66da53b6532bdd8af246f4 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java +@@ -0,0 +1,37 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V2516 { ++ ++ protected static final int VERSION = MCVersions.V20W12A + 1; ++ ++ private V2516() {} ++ ++ public static void register() { ++ final DataConverter, MapType> gossipUUIDConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType gossips = data.getList("Gossips", ObjectType.MAP); ++ ++ if (gossips == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = gossips.size(); i < len; ++i) { ++ V2514.replaceUUIDLeastMost(gossips.getMap(i), "Target", "Target"); ++ } ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", gossipUUIDConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", gossipUUIDConverter); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4d8692bbb497b1e6f0aa655e8f9d1fb743864f7d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java +@@ -0,0 +1,66 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V2518 { ++ ++ protected static final int VERSION = MCVersions.V20W12A + 3; ++ ++ private static final Map FACING_RENAMES = new HashMap<>( ++ ImmutableMap.builder() ++ .put("down", "down_south") ++ .put("up", "up_north") ++ .put("north", "north_up") ++ .put("south", "south_up") ++ .put("west", "west_up") ++ .put("east", "east_up") ++ .build() ++ ); ++ ++ ++ private V2518() {} ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jigsaw", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String type = data.getString("attachement_type", "minecraft:empty"); ++ final String pool = data.getString("target_pool", "minecraft:empty"); ++ data.remove("attachement_type"); ++ data.remove("target_pool"); ++ ++ data.setString("name", type); ++ data.setString("target", type); ++ data.setString("pool", pool); ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!"minecraft:jigsaw".equals(data.getString("Name"))) { ++ return null; ++ } ++ ++ final MapType properties = data.getMap("Properties"); ++ if (properties == null) { ++ return null; ++ } ++ ++ final String facing = properties.getString("facing", "north"); ++ properties.remove("facing"); ++ properties.setString("orientation", FACING_RENAMES.getOrDefault(facing, facing)); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java +new file mode 100644 +index 0000000000000000000000000000000000000000..47a8bb340e72e48fd237d4001b55c81b1182a72f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2519 { ++ ++ protected static final int VERSION = MCVersions.V20W12A + 4; ++ ++ private V2519() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:strider"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3a91600427cb013cdfc03b084017b6f32d9e05fa +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2522 { ++ ++ protected static final int VERSION = MCVersions.V20W13B + 1; ++ ++ private V2522() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:zoglin"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2c64d65c8cb34fbdd79688697e9a7f5ecb615bff +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java +@@ -0,0 +1,95 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V2523 { ++ ++ protected static final int VERSION = MCVersions.V20W13B + 2; ++ ++ private static final Map RENAMES = new HashMap<>( ++ ImmutableMap.builder() ++ .put("generic.maxHealth", "generic.max_health") ++ .put("Max Health", "generic.max_health") ++ .put("zombie.spawnReinforcements", "zombie.spawn_reinforcements") ++ .put("Spawn Reinforcements Chance", "zombie.spawn_reinforcements") ++ .put("horse.jumpStrength", "horse.jump_strength") ++ .put("Jump Strength", "horse.jump_strength") ++ .put("generic.followRange", "generic.follow_range") ++ .put("Follow Range", "generic.follow_range") ++ .put("generic.knockbackResistance", "generic.knockback_resistance") ++ .put("Knockback Resistance", "generic.knockback_resistance") ++ .put("generic.movementSpeed", "generic.movement_speed") ++ .put("Movement Speed", "generic.movement_speed") ++ .put("generic.flyingSpeed", "generic.flying_speed") ++ .put("Flying Speed", "generic.flying_speed") ++ .put("generic.attackDamage", "generic.attack_damage") ++ .put("generic.attackKnockback", "generic.attack_knockback") ++ .put("generic.attackSpeed", "generic.attack_speed") ++ .put("generic.armorToughness", "generic.armor_toughness") ++ .build() ++ ); ++ ++ private V2523() {} ++ ++ private static void updateName(final MapType data, final String path) { ++ if (data == null) { ++ return; ++ } ++ ++ final String name = data.getString(path); ++ if (name != null) { ++ final String renamed = RENAMES.get(name); ++ if (renamed != null) { ++ data.setString(path, renamed); ++ } ++ } ++ } ++ ++ public static void register() { ++ final DataConverter, MapType> entityConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType attributes = data.getList("Attributes", ObjectType.MAP); ++ ++ if (attributes == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = attributes.size(); i < len; ++i) { ++ updateName(attributes.getMap(i), "Name"); ++ } ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.ENTITY.addStructureConverter(entityConverter); ++ MCTypeRegistry.PLAYER.addStructureConverter(entityConverter); ++ ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType attributes = data.getList("AttributeModifiers", ObjectType.MAP); ++ ++ if (attributes == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = attributes.size(); i < len; ++i) { ++ updateName(attributes.getMap(i), "AttributeName"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5e951f91d03f95ed671bb7403592960690c65879 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java +@@ -0,0 +1,123 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.mojang.datafixers.DataFixUtils; ++import net.minecraft.util.Mth; ++ ++public final class V2527 { ++ ++ protected static final int VERSION = MCVersions.V20W16A + 1; ++ ++ private V2527() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ ++ if (level == null) { ++ return null; ++ } ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ final ListType palette = section.getList("Palette", ObjectType.MAP); ++ ++ if (palette == null) { ++ continue; ++ } ++ ++ final int bits = Math.max(4, DataFixUtils.ceillog2(palette.size())); ++ ++ if (Mth.isPowerOfTwo(bits)) { ++ // fits perfectly ++ continue; ++ } ++ ++ final long[] states = section.getLongs("BlockStates"); ++ if (states == null) { ++ // wat ++ continue; ++ } ++ ++ section.setLongs("BlockStates", addPadding(4096, bits, states)); ++ } ++ } ++ ++ final MapType heightMaps = level.getMap("Heightmaps"); ++ if (heightMaps != null) { ++ for (final String key : heightMaps.keys()) { ++ final long[] old = heightMaps.getLongs(key); ++ heightMaps.setLongs(key, addPadding(256, 9, old)); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ public static long[] addPadding(final int indices, final int bits, final long[] old) { ++ int k = old.length; ++ if (k == 0) { ++ return old; ++ } else { ++ long l = (1L << bits) - 1L; ++ int m = 64 / bits; ++ int n = (indices + m - 1) / m; ++ long[] padded = new long[n]; ++ int o = 0; ++ int p = 0; ++ long q = 0L; ++ int r = 0; ++ long s = old[0]; ++ long t = k > 1 ? old[1] : 0L; ++ ++ for(int u = 0; u < indices; ++u) { ++ int v = u * bits; ++ int w = v >> 6; ++ int x = (u + 1) * bits - 1 >> 6; ++ int y = v ^ w << 6; ++ if (w != r) { ++ s = t; ++ t = w + 1 < k ? old[w + 1] : 0L; ++ r = w; ++ } ++ ++ long ab; ++ int ac; ++ if (w == x) { ++ ab = s >>> y & l; ++ } else { ++ ac = 64 - y; ++ ab = (s >>> y | t << ac) & l; ++ } ++ ++ ac = p + bits; ++ if (ac >= 64) { ++ padded[o++] = q; ++ q = ab; ++ p = bits; ++ } else { ++ q |= ab << p; ++ p = ac; ++ } ++ } ++ ++ if (q != 0L) { ++ padded[o] = q; ++ } ++ ++ return padded; ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f7f2e3f75a9f8a5533fe010bc23e8384878d7ce8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java +@@ -0,0 +1,25 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2528 { ++ ++ protected static final int VERSION = MCVersions.V20W16A + 2; ++ ++ private V2528() {} ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:soul_fire_torch", "minecraft:soul_torch", ++ "minecraft:soul_fire_lantern", "minecraft:soul_lantern" ++ )::get); ++ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( ++ "minecraft:soul_fire_torch", "minecraft:soul_torch", ++ "minecraft:soul_fire_wall_torch", "minecraft:soul_wall_torch", ++ "minecraft:soul_fire_lantern", "minecraft:soul_lantern" ++ )::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f239dbf4beb87efd1fff4e5d8d6f041fa8687f01 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java +@@ -0,0 +1,25 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2529 { ++ ++ protected static final int VERSION = MCVersions.V20W17A; ++ ++ private V2529() {} ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:strider", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getBoolean("NoGravity")) { ++ data.setBoolean("NoGravity", false); ++ } ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7783d75578d29e09029b26c8c8c0b053c5526eb9 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java +@@ -0,0 +1,63 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2531 { ++ ++ protected static final int VERSION = MCVersions.V20W17A + 2; ++ ++ private V2531() {} ++ ++ private static boolean isConnected(final String facing) { ++ return !"none".equals(facing); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!"minecraft:redstone_wire".equals(data.getString("Name"))) { ++ return null; ++ } ++ ++ final MapType properties = data.getMap("Properties"); ++ ++ if (properties == null) { ++ return null; ++ } ++ ++ ++ final String east = properties.getString("east", "none"); ++ final String west = properties.getString("west", "none"); ++ final String north = properties.getString("north", "none"); ++ final String south = properties.getString("south", "none"); ++ ++ final boolean connectedX = isConnected(east) || isConnected(west); ++ final boolean connectedZ = isConnected(north) || isConnected(south); ++ ++ final String newEast = !isConnected(east) && !connectedZ ? "side" : east; ++ final String newWest = !isConnected(west) && !connectedZ ? "side" : west; ++ final String newNorth = !isConnected(north) && !connectedX ? "side" : north; ++ final String newSouth = !isConnected(south) && !connectedX ? "side" : south; ++ ++ if (properties.hasKey("east")) { ++ properties.setString("east", newEast); ++ } ++ if (properties.hasKey("west")) { ++ properties.setString("west", newWest); ++ } ++ if (properties.hasKey("north")) { ++ properties.setString("north", newNorth); ++ } ++ if (properties.hasKey("south")) { ++ properties.setString("south", newSouth); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ece1cd5afab80a8271b2ebac95dcc0a6239cd42c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java +@@ -0,0 +1,43 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V2533 { ++ ++ protected static final int VERSION = MCVersions.V20W18A + 1; ++ ++ private V2533() {} ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType attributes = data.getList("Attributes", ObjectType.MAP); ++ ++ if (attributes == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = attributes.size(); i < len; ++i) { ++ final MapType attribute = attributes.getMap(i); ++ ++ if (!"generic.follow_range".equals(attribute.getString("Name"))) { ++ continue; ++ } ++ ++ if (attribute.getDouble("Base") == 16.0) { ++ attribute.setDouble("Base", 48.0); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9648299bb96c20c783bb7c7010173a0f007584e0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java +@@ -0,0 +1,34 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V2535 { ++ ++ protected static final int VERSION = MCVersions.V20W19A + 1; ++ ++ private V2535() {} ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ // Mojang uses doubles for whatever reason... rotation is in FLOAT. by using double here ++ // the entity load will just ignore rotation and set it to 0... ++ final ListType rotation = data.getList("Rotation", ObjectType.FLOAT); ++ ++ if (rotation == null || rotation.size() == 0) { ++ return null; ++ } ++ ++ rotation.setFloat(0, rotation.getFloat(0) - 180.0F); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java +new file mode 100644 +index 0000000000000000000000000000000000000000..17ec2cdecdd794739f5eca5242b3a12211adf1bc +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java +@@ -0,0 +1,41 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2538 { ++ ++ private static final int VERSION = MCVersions.V20W20B + 1; ++ private static final String[] MERGE_KEYS = new String[] { ++ "RandomSeed", ++ "generatorName", ++ "generatorOptions", ++ "generatorVersion", ++ "legacy_custom_options", ++ "MapFeatures", ++ "BonusChest" ++ }; ++ ++ public static void register() { ++ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType worldGenSettings = data.getOrCreateMap("WorldGenSettings"); ++ ++ for (final String key : MERGE_KEYS) { ++ final Object value = data.getGeneric(key); ++ if (value == null) { ++ continue; ++ } ++ ++ data.remove(key); ++ worldGenSettings.setGeneric(key, value); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java +new file mode 100644 +index 0000000000000000000000000000000000000000..682b6f16c23ac9ce1a683bac6d36e5d07804b35d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java +@@ -0,0 +1,344 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.common.collect.ImmutableMap; ++import org.apache.commons.lang3.math.NumberUtils; ++import java.util.HashMap; ++import java.util.Locale; ++import java.util.Map; ++ ++public final class V2550 { ++ ++ protected static final int VERSION = MCVersions.V20W20B + 13; ++ ++ private static final Map DEFAULTS = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:village", new StructureFeatureConfiguration(32, 8, 10387312)) ++ .put("minecraft:desert_pyramid", new StructureFeatureConfiguration(32, 8, 14357617)) ++ .put("minecraft:igloo", new StructureFeatureConfiguration(32, 8, 14357618)) ++ .put("minecraft:jungle_pyramid", new StructureFeatureConfiguration(32, 8, 14357619)) ++ .put("minecraft:swamp_hut", new StructureFeatureConfiguration(32, 8, 14357620)) ++ .put("minecraft:pillager_outpost", new StructureFeatureConfiguration(32, 8, 165745296)) ++ .put("minecraft:monument", new StructureFeatureConfiguration(32, 5, 10387313)) ++ .put("minecraft:endcity", new StructureFeatureConfiguration(20, 11, 10387313)) ++ .put("minecraft:mansion", new StructureFeatureConfiguration(80, 20, 10387319)) ++ .build() ++ ); ++ ++ record StructureFeatureConfiguration(int spacing, int separation, int salt) { ++ ++ public MapType serialize() { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setInt("spacing", this.spacing); ++ ret.setInt("separation", this.separation); ++ ret.setInt("salt", this.salt); ++ ++ return ret; ++ } ++ } ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final long seed = data.getLong("RandomSeed"); ++ String generatorName = data.getString("generatorName"); ++ if (generatorName != null) { ++ generatorName = generatorName.toLowerCase(Locale.ROOT); ++ } ++ String legacyCustomOptions = data.getString("legacy_custom_options"); ++ if (legacyCustomOptions == null) { ++ legacyCustomOptions = "customized".equals(generatorName) ? data.getString("generatorOptions") : null; ++ } ++ ++ final MapType generator; ++ boolean caves = false; ++ ++ if ("customized".equals(generatorName) || generatorName == null) { ++ generator = defaultOverworld(seed); ++ } else { ++ switch (generatorName) { ++ case "flat": { ++ final MapType generatorOptions = data.getMap("generatorOptions"); ++ ++ final MapType structures = fixFlatStructures(generatorOptions); ++ final MapType settings = Types.NBT.createEmptyMap(); ++ generator = Types.NBT.createEmptyMap(); ++ generator.setString("type", "minecraft:flat"); ++ generator.setMap("settings", settings); ++ ++ settings.setMap("structures", structures); ++ ++ ListType layers = generatorOptions.getList("layers", ObjectType.MAP); ++ if (layers == null) { ++ layers = Types.NBT.createEmptyList(); ++ ++ final int[] heights = new int[] { 1, 2, 1 }; ++ final String[] blocks = new String[] { "minecraft:bedrock", "minecraft:dirt", "minecraft:grass_block" }; ++ for (int i = 0; i < 3; ++i) { ++ final MapType layer = Types.NBT.createEmptyMap(); ++ layer.setInt("height", heights[i]); ++ layer.setString("block", blocks[i]); ++ layers.addMap(layer); ++ } ++ } ++ ++ settings.setList("layers", layers); ++ settings.setString("biome", generatorOptions.getString("biome", "minecraft:plains")); ++ ++ break; ++ } ++ ++ case "debug_all_block_states": { ++ generator = Types.NBT.createEmptyMap(); ++ generator.setString("type", "minecraft:debug"); ++ break; ++ } ++ ++ case "buffet": { ++ final MapType generatorOptions = data.getMap("generatorOptions"); ++ final MapType chunkGenerator = generatorOptions == null ? null : generatorOptions.getMap("chunk_generator"); ++ final String chunkGeneratorType = chunkGenerator == null ? null : chunkGenerator.getString("type"); ++ ++ final String newType; ++ if ("minecraft:caves".equals(chunkGeneratorType)) { ++ newType = "minecraft:caves"; ++ caves = true; ++ } else if ("minecraft:floating_islands".equals(chunkGeneratorType)) { ++ newType = "minecraft:floating_islands"; ++ } else { ++ newType = "minecraft:overworld"; ++ } ++ ++ MapType biomeSource = generatorOptions == null ? null : generatorOptions.getMap("biome_source"); ++ if (biomeSource == null) { ++ biomeSource = Types.NBT.createEmptyMap(); ++ biomeSource.setString("type", "minecraft:fixed"); ++ } ++ ++ if ("minecraft:fixed".equals(biomeSource.getString("type"))) { ++ final MapType options = biomeSource.getMap("options"); ++ final ListType biomes = options == null ? null : options.getList("biomes", ObjectType.STRING); ++ final String biome = biomes == null || biomes.size() == 0 ? "minecraft:ocean" : biomes.getString(0); ++ biomeSource.remove("options"); ++ biomeSource.setString("biome", biome); ++ } ++ ++ generator = noise(seed, newType, biomeSource); ++ break; ++ } ++ ++ default: { ++ boolean defaultGen = generatorName.equals("default"); ++ boolean default11Gen = generatorName.equals("default_1_1") || defaultGen && data.getInt("generatorVersion") == 0; ++ boolean amplified = generatorName.equals("amplified"); ++ boolean largeBiomes = generatorName.equals("largebiomes"); ++ ++ generator = noise(seed, amplified ? "minecraft:amplified" : "minecraft:overworld", ++ vanillaBiomeSource(seed, default11Gen, largeBiomes)); ++ break; ++ } ++ } ++ } ++ ++ final boolean mapFeatures = data.getBoolean("MapFeatures", true); ++ final boolean bonusChest = data.getBoolean("BonusChest", false); ++ ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setLong("seed", seed); ++ ret.setBoolean("generate_features", mapFeatures); ++ ret.setBoolean("bonus_chest", bonusChest); ++ ret.setMap("dimensions", vanillaLevels(seed, generator, caves)); ++ if (legacyCustomOptions != null) { ++ ret.setString("legacy_custom_options", legacyCustomOptions); ++ } ++ ++ return ret; ++ } ++ }); ++ } ++ ++ public static MapType noise(final long seed, final String worldType, final MapType biomeSource) { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setString("type", "minecraft:noise"); ++ ret.setMap("biome_source", biomeSource); ++ ret.setLong("seed", seed); ++ ret.setString("settings", worldType); ++ ++ return ret; ++ } ++ ++ public static MapType vanillaBiomeSource(final long seed, final boolean default11Gen, final boolean largeBiomes) { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setString("type", "minecraft:vanilla_layered"); ++ ret.setLong("seed", seed); ++ ret.setBoolean("large_biomes", largeBiomes); ++ if (default11Gen) { ++ ret.setBoolean("legacy_biome_init_layer", default11Gen); ++ } ++ ++ return ret; ++ } ++ ++ public static MapType fixFlatStructures(final MapType generatorOptions) { ++ int distance = 32; ++ int spread = 3; ++ int count = 128; ++ boolean stronghold = false; ++ final Map newStructures = new HashMap<>(); ++ ++ if (generatorOptions == null) { ++ stronghold = true; ++ newStructures.put("minecraft:village", DEFAULTS.get("minecraft:village")); ++ } ++ ++ final MapType oldStructures = generatorOptions == null ? null : generatorOptions.getMap("structures"); ++ if (oldStructures != null) { ++ for (final String structureName : oldStructures.keys()) { ++ final MapType structureValues = oldStructures.getMap(structureName); ++ if (structureValues == null) { ++ continue; ++ } ++ ++ for (final String structureValueKey : structureValues.keys()) { ++ final String structureValue = structureValues.getString(structureValueKey); ++ ++ if ("stronghold".equals(structureName)) { ++ stronghold = true; ++ switch (structureValueKey) { ++ case "distance": ++ distance = getInt(structureValue, distance, 1); ++ break; ++ case "spread": ++ spread = getInt(structureValue, spread, 1); ++ break; ++ case "count": ++ count = getInt(structureValue, count, 1); ++ break; ++ } ++ } else { ++ switch (structureValueKey) { ++ case "distance": ++ switch (structureName) { ++ case "village": ++ setSpacing(newStructures, "minecraft:village", structureValue, 9); ++ break; ++ case "biome_1": ++ setSpacing(newStructures, "minecraft:desert_pyramid", structureValue, 9); ++ setSpacing(newStructures, "minecraft:igloo", structureValue, 9); ++ setSpacing(newStructures, "minecraft:jungle_pyramid", structureValue, 9); ++ setSpacing(newStructures, "minecraft:swamp_hut", structureValue, 9); ++ setSpacing(newStructures, "minecraft:pillager_outpost", structureValue, 9); ++ break; ++ case "endcity": ++ setSpacing(newStructures, "minecraft:endcity", structureValue, 1); ++ break; ++ case "mansion": ++ setSpacing(newStructures, "minecraft:mansion", structureValue, 1); ++ break; ++ default: ++ break; ++ } ++ case "separation": ++ if ("oceanmonument".equals(structureName)) { ++ final StructureFeatureConfiguration structure = newStructures.getOrDefault("minecraft:monument", DEFAULTS.get("minecraft:monument")); ++ final int newSpacing = getInt(structureValue, structure.separation, 1); ++ newStructures.put("minecraft:monument", new StructureFeatureConfiguration(newSpacing, structure.separation, structure.salt)); ++ } ++ ++ break; ++ case "spacing": ++ if ("oceanmonument".equals(structureName)) { ++ setSpacing(newStructures, "minecraft:monument", structureValue, 1); ++ } ++ ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ final MapType ret = Types.NBT.createEmptyMap(); ++ final MapType structuresSerialized = Types.NBT.createEmptyMap(); ++ ret.setMap("structures", structuresSerialized); ++ for (final String key : newStructures.keySet()) { ++ structuresSerialized.setMap(key, newStructures.get(key).serialize()); ++ } ++ ++ if (stronghold) { ++ final MapType strongholdData = Types.NBT.createEmptyMap(); ++ ret.setMap("stronghold", strongholdData); ++ ++ strongholdData.setInt("distance", distance); ++ strongholdData.setInt("spread", spread); ++ strongholdData.setInt("count", count); ++ } ++ ++ return ret; ++ } ++ ++ public static MapType vanillaLevels(final long seed, final MapType generator, final boolean caves) { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ final MapType overworld = Types.NBT.createEmptyMap(); ++ final MapType nether = Types.NBT.createEmptyMap(); ++ final MapType end = Types.NBT.createEmptyMap(); ++ ++ ret.setMap("minecraft:overworld", overworld); ++ ret.setMap("minecraft:the_nether", nether); ++ ret.setMap("minecraft:the_end", end); ++ ++ // overworld ++ overworld.setString("type", caves ? "minecraft:overworld_caves" : "minecraft:overworld"); ++ overworld.setMap("generator", generator); ++ ++ // nether ++ nether.setString("type", "minecraft:the_nether"); ++ final MapType netherBiomeSource = Types.NBT.createEmptyMap(); ++ netherBiomeSource.setString("type", "minecraft:multi_noise"); ++ netherBiomeSource.setLong("seed", seed); ++ netherBiomeSource.setString("preset", "minecraft:nether"); ++ ++ nether.setMap("generator", noise(seed, "minecraft:nether", netherBiomeSource)); ++ ++ // end ++ end.setString("type", "minecraft:the_end"); ++ final MapType endBiomeSource = Types.NBT.createEmptyMap(); ++ endBiomeSource.setString("type", "minecraft:the_end"); ++ endBiomeSource.setLong("seed", seed); ++ end.setMap("generator", noise(seed,"minecraft:end", endBiomeSource)); ++ ++ return ret; ++ } ++ ++ public static MapType defaultOverworld(final long seed) { ++ return noise(seed, "minecraft:overworld", vanillaBiomeSource(seed, false, false)); ++ } ++ ++ private static int getInt(final String value, final int dfl) { ++ return NumberUtils.toInt(value, dfl); ++ } ++ ++ private static int getInt(final String value, final int dfl, final int minVal) { ++ return Math.max(minVal, getInt(value, dfl)); ++ } ++ ++ private static void setSpacing(final Map structures, final String structureName, ++ final String value, final int minVal) { ++ final StructureFeatureConfiguration structure = structures.getOrDefault(structureName, DEFAULTS.get(structureName)); ++ final int newSpacing = getInt(value, structure.spacing, minVal); ++ ++ structures.put(structureName, new StructureFeatureConfiguration(newSpacing, structure.separation, structure.salt)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ac0c4475556fe5202a6aa5724cb47b35c0cc9c00 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java +@@ -0,0 +1,101 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V2551 { ++ ++ protected static final int VERSION = MCVersions.V20W20B + 14; ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType dimensions = data.getMap("dimensions"); ++ ++ if (dimensions == null) { ++ return null; ++ } ++ ++ for (final String dimension : dimensions.keys()) { ++ final MapType dimensionData = dimensions.getMap(dimension); ++ if (dimensionData == null) { ++ continue; ++ } ++ ++ final MapType generator = dimensionData.getMap("generator"); ++ if (generator == null) { ++ continue; ++ } ++ ++ final String type = generator.getString("type"); ++ if (type == null) { ++ continue; ++ } ++ ++ switch (type) { ++ case "minecraft:flat": { ++ final MapType settings = generator.getMap("settings"); ++ if (settings == null) { ++ continue; ++ } ++ ++ WalkerUtils.convert(MCTypeRegistry.BIOME, settings, "biome", fromVersion, toVersion); ++ ++ final ListType layers = settings.getList("layers", ObjectType.MAP); ++ if (layers != null) { ++ for (int i = 0, len = layers.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, layers.getMap(i), "block", fromVersion, toVersion); ++ } ++ } ++ ++ break; ++ } ++ case "minecraft:noise": { ++ final MapType settings = generator.getMap("settings"); ++ if (settings != null) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_block", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_fluid", fromVersion, toVersion); ++ } ++ ++ final MapType biomeSource = generator.getMap("biome_source"); ++ if (biomeSource != null) { ++ final String biomeSourceType = biomeSource.getString("type", ""); ++ switch (biomeSourceType) { ++ case "minecraft:fixed": { ++ WalkerUtils.convert(MCTypeRegistry.BIOME, biomeSource, "biome", fromVersion, toVersion); ++ break; ++ } ++ ++ case "minecraft:multi_noise": { ++ // Vanilla's schema is wrong. It should be DSL.fields("biomes", DSL.list(DSL.fields("biome"))) ++ // But it just contains the list part. That obviously can never be the case, because ++ // the root object is a compound, not a list. ++ ++ final ListType biomes = biomeSource.getList("biomes", ObjectType.MAP); ++ if (biomes != null) { ++ for (int i = 0, len = biomes.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BIOME, biomes.getMap(i), "biome", fromVersion, toVersion); ++ } ++ } ++ break; ++ } ++ ++ case "minecraft:checkerboard": { ++ WalkerUtils.convertList(MCTypeRegistry.BIOME, biomeSource, "biomes", fromVersion, toVersion); ++ break; ++ } ++ } ++ } ++ ++ break; ++ } ++ } ++ } ++ ++ return null; ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c06aa2099494f82bad2c1f212b2db07405f8f6ff +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2552 { ++ ++ protected static final int VERSION = MCVersions.V20W20B + 15; ++ ++ private V2552() {} ++ ++ public static void register() { ++ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, ImmutableMap.of( ++ "minecraft:nether", "minecraft:nether_wastes" ++ )::get); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java +new file mode 100644 +index 0000000000000000000000000000000000000000..492f615ff66d7f7ea820492dadefec2dd65cc051 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java +@@ -0,0 +1,78 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V2553 { ++ ++ protected static final int VERSION = MCVersions.V20W20B + 16; ++ ++ public static final Map BIOME_RENAMES = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:extreme_hills", "minecraft:mountains") ++ .put("minecraft:swampland", "minecraft:swamp") ++ .put("minecraft:hell", "minecraft:nether_wastes") ++ .put("minecraft:sky", "minecraft:the_end") ++ .put("minecraft:ice_flats", "minecraft:snowy_tundra") ++ .put("minecraft:ice_mountains", "minecraft:snowy_mountains") ++ .put("minecraft:mushroom_island", "minecraft:mushroom_fields") ++ .put("minecraft:mushroom_island_shore", "minecraft:mushroom_field_shore") ++ .put("minecraft:beaches", "minecraft:beach") ++ .put("minecraft:forest_hills", "minecraft:wooded_hills") ++ .put("minecraft:smaller_extreme_hills", "minecraft:mountain_edge") ++ .put("minecraft:stone_beach", "minecraft:stone_shore") ++ .put("minecraft:cold_beach", "minecraft:snowy_beach") ++ .put("minecraft:roofed_forest", "minecraft:dark_forest") ++ .put("minecraft:taiga_cold", "minecraft:snowy_taiga") ++ .put("minecraft:taiga_cold_hills", "minecraft:snowy_taiga_hills") ++ .put("minecraft:redwood_taiga", "minecraft:giant_tree_taiga") ++ .put("minecraft:redwood_taiga_hills", "minecraft:giant_tree_taiga_hills") ++ .put("minecraft:extreme_hills_with_trees", "minecraft:wooded_mountains") ++ .put("minecraft:savanna_rock", "minecraft:savanna_plateau") ++ .put("minecraft:mesa", "minecraft:badlands") ++ .put("minecraft:mesa_rock", "minecraft:wooded_badlands_plateau") ++ .put("minecraft:mesa_clear_rock", "minecraft:badlands_plateau") ++ .put("minecraft:sky_island_low", "minecraft:small_end_islands") ++ .put("minecraft:sky_island_medium", "minecraft:end_midlands") ++ .put("minecraft:sky_island_high", "minecraft:end_highlands") ++ .put("minecraft:sky_island_barren", "minecraft:end_barrens") ++ .put("minecraft:void", "minecraft:the_void") ++ .put("minecraft:mutated_plains", "minecraft:sunflower_plains") ++ .put("minecraft:mutated_desert", "minecraft:desert_lakes") ++ .put("minecraft:mutated_extreme_hills", "minecraft:gravelly_mountains") ++ .put("minecraft:mutated_forest", "minecraft:flower_forest") ++ .put("minecraft:mutated_taiga", "minecraft:taiga_mountains") ++ .put("minecraft:mutated_swampland", "minecraft:swamp_hills") ++ .put("minecraft:mutated_ice_flats", "minecraft:ice_spikes") ++ .put("minecraft:mutated_jungle", "minecraft:modified_jungle") ++ .put("minecraft:mutated_jungle_edge", "minecraft:modified_jungle_edge") ++ .put("minecraft:mutated_birch_forest", "minecraft:tall_birch_forest") ++ .put("minecraft:mutated_birch_forest_hills", "minecraft:tall_birch_hills") ++ .put("minecraft:mutated_roofed_forest", "minecraft:dark_forest_hills") ++ .put("minecraft:mutated_taiga_cold", "minecraft:snowy_taiga_mountains") ++ .put("minecraft:mutated_redwood_taiga", "minecraft:giant_spruce_taiga") ++ .put("minecraft:mutated_redwood_taiga_hills", "minecraft:giant_spruce_taiga_hills") ++ .put("minecraft:mutated_extreme_hills_with_trees", "minecraft:modified_gravelly_mountains") ++ .put("minecraft:mutated_savanna", "minecraft:shattered_savanna") ++ .put("minecraft:mutated_savanna_rock", "minecraft:shattered_savanna_plateau") ++ .put("minecraft:mutated_mesa", "minecraft:eroded_badlands") ++ .put("minecraft:mutated_mesa_rock", "minecraft:modified_wooded_badlands_plateau") ++ .put("minecraft:mutated_mesa_clear_rock", "minecraft:modified_badlands_plateau") ++ .put("minecraft:warm_deep_ocean", "minecraft:deep_warm_ocean") ++ .put("minecraft:lukewarm_deep_ocean", "minecraft:deep_lukewarm_ocean") ++ .put("minecraft:cold_deep_ocean", "minecraft:deep_cold_ocean") ++ .put("minecraft:frozen_deep_ocean", "minecraft:deep_frozen_ocean") ++ .build() ++ ); ++ ++ ++ private V2553() {} ++ ++ public static void register() { ++ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, BIOME_RENAMES::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0e228fd642cbca13e8682950b5f0ec4e3e8a4da7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java +@@ -0,0 +1,45 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.options.ConverterAbstractOptionsRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2558 { ++ ++ protected static final int VERSION = MCVersions.V1_16_PRE2 + 1; ++ ++ private V2558() {} ++ ++ public static void register() { ++ ConverterAbstractOptionsRename.register(VERSION, ImmutableMap.of( ++ "key_key.swapHands", "key_key.swapOffhand" ++ )::get); ++ ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ MapType dimensions = data.getMap("dimensions"); ++ if (dimensions == null) { ++ dimensions = Types.NBT.createEmptyMap(); ++ data.setMap("dimensions", dimensions); ++ } ++ ++ if (dimensions.isEmpty()) { ++ data.setMap("dimensions", recreateSettings(data)); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private static MapType recreateSettings(final MapType data) { ++ final long seed = data.getLong("seed"); ++ ++ return V2550.vanillaLevels(seed, V2550.defaultOverworld(seed), false); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5835159d7016fb4f05d137acba709fa7d8e8e752 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2568 { ++ ++ protected static final int VERSION = MCVersions.V1_16_1 + 1; ++ ++ private V2568() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:piglin_brute"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java +new file mode 100644 +index 0000000000000000000000000000000000000000..374d24db80f3ed8cd241e18d776c39a8da72d8fd +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2671 { ++ ++ protected static final int VERSION = MCVersions.V1_16_5 + 85; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:goat"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6c788f51be0439797bf9fc8711d4cf8e382f5c11 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java +@@ -0,0 +1,36 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2679 { ++ ++ protected static final int VERSION = MCVersions.V1_16_5 + 93; ++ ++ public static void register() { ++ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!"minecraft:cauldron".equals(data.getString("Name"))) { ++ return null; ++ } ++ ++ final MapType properties = data.getMap("Properties"); ++ ++ if (properties == null) { ++ return null; ++ } ++ ++ if (properties.getString("level", "0").equals("0")) { ++ data.remove("Properties"); ++ return null; ++ } else { ++ data.setString("Name", "minecraft:water_cauldron"); ++ return null; ++ } ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java +new file mode 100644 +index 0000000000000000000000000000000000000000..232a28c07c6341a996253282d9872d76b3fce0e3 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2680 { ++ ++ protected static final int VERSION = MCVersions.V1_16_5 + 94; ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:grass_path", "minecraft:dirt_path" ++ )::get); ++ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, ImmutableMap.of( ++ "minecraft:grass_path", "minecraft:dirt_path" ++ )::get); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4706a7cfb97d3d5c521914f8dfc894db277bcfe0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java +@@ -0,0 +1,14 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; ++ ++public final class V2684 { ++ ++ protected static final int VERSION = MCVersions.V20W48A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:sculk_sensor", new GameEventListenerWalker()); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f6a6f33d4f701f4188828994c8e56dea21950366 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2686 { ++ ++ protected static final int VERSION = MCVersions.V20W49A + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:axolotl"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6fcfcb66e1fd9291abad47e41ee076a7816b4244 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++ ++public final class V2688 { ++ ++ protected static final int VERSION = MCVersions.V20W51A + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:glow_squid"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:glow_item_frame", new DataWalkerItems("Item")); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c464ee01de5bcfcee9bcf18cc73ae8d6e0354b50 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java +@@ -0,0 +1,43 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V2690 { ++ ++ protected static final int VERSION = MCVersions.V21W05A; ++ ++ protected static final Map RENAMES = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:weathered_copper_block", "minecraft:oxidized_copper_block") ++ .put("minecraft:semi_weathered_copper_block", "minecraft:weathered_copper_block") ++ .put("minecraft:lightly_weathered_copper_block", "minecraft:exposed_copper_block") ++ .put("minecraft:weathered_cut_copper", "minecraft:oxidized_cut_copper") ++ .put("minecraft:semi_weathered_cut_copper", "minecraft:weathered_cut_copper") ++ .put("minecraft:lightly_weathered_cut_copper", "minecraft:exposed_cut_copper") ++ .put("minecraft:weathered_cut_copper_stairs", "minecraft:oxidized_cut_copper_stairs") ++ .put("minecraft:semi_weathered_cut_copper_stairs", "minecraft:weathered_cut_copper_stairs") ++ .put("minecraft:lightly_weathered_cut_copper_stairs", "minecraft:exposed_cut_copper_stairs") ++ .put("minecraft:weathered_cut_copper_slab", "minecraft:oxidized_cut_copper_slab") ++ .put("minecraft:semi_weathered_cut_copper_slab", "minecraft:weathered_cut_copper_slab") ++ .put("minecraft:lightly_weathered_cut_copper_slab", "minecraft:exposed_cut_copper_slab") ++ .put("minecraft:waxed_semi_weathered_copper", "minecraft:waxed_weathered_copper") ++ .put("minecraft:waxed_lightly_weathered_copper", "minecraft:waxed_exposed_copper") ++ .put("minecraft:waxed_semi_weathered_cut_copper", "minecraft:waxed_weathered_cut_copper") ++ .put("minecraft:waxed_lightly_weathered_cut_copper", "minecraft:waxed_exposed_cut_copper") ++ .put("minecraft:waxed_semi_weathered_cut_copper_stairs", "minecraft:waxed_weathered_cut_copper_stairs") ++ .put("minecraft:waxed_lightly_weathered_cut_copper_stairs", "minecraft:waxed_exposed_cut_copper_stairs") ++ .put("minecraft:waxed_semi_weathered_cut_copper_slab", "minecraft:waxed_weathered_cut_copper_slab") ++ .put("minecraft:waxed_lightly_weathered_cut_copper_slab", "minecraft:waxed_exposed_cut_copper_slab") ++ .build() ++ ); ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, RENAMES::get); ++ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d64cd0fa59e8581ac22b61d759658abe4b9e013b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java +@@ -0,0 +1,27 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V2691 { ++ ++ protected static final int VERSION = MCVersions.V21W05A + 1; ++ ++ protected static final Map RENAMES = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:waxed_copper", "minecraft:waxed_copper_block") ++ .put("minecraft:oxidized_copper_block", "minecraft:oxidized_copper") ++ .put("minecraft:weathered_copper_block", "minecraft:weathered_copper") ++ .put("minecraft:exposed_copper_block", "minecraft:exposed_copper") ++ .build() ++ ); ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, RENAMES::get); ++ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java +new file mode 100644 +index 0000000000000000000000000000000000000000..deac34afe6a3681db9a7630ad6526f71d4dd6e1f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java +@@ -0,0 +1,15 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.AddFlagIfAbsent; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++ ++public final class V2693 { ++ ++ protected static final int VERSION = MCVersions.V21W05B + 1; ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", false)); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9502cfd7a4dd536fb2e2fa114691faef30f757aa +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java +@@ -0,0 +1,40 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V2696 { ++ ++ protected static final int VERSION = MCVersions.V21W07A + 1; ++ ++ protected static final Map RENAMES = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:grimstone", "minecraft:deepslate") ++ .put("minecraft:grimstone_slab", "minecraft:cobbled_deepslate_slab") ++ .put("minecraft:grimstone_stairs", "minecraft:cobbled_deepslate_stairs") ++ .put("minecraft:grimstone_wall", "minecraft:cobbled_deepslate_wall") ++ .put("minecraft:polished_grimstone", "minecraft:polished_deepslate") ++ .put("minecraft:polished_grimstone_slab", "minecraft:polished_deepslate_slab") ++ .put("minecraft:polished_grimstone_stairs", "minecraft:polished_deepslate_stairs") ++ .put("minecraft:polished_grimstone_wall", "minecraft:polished_deepslate_wall") ++ .put("minecraft:grimstone_tiles", "minecraft:deepslate_tiles") ++ .put("minecraft:grimstone_tile_slab", "minecraft:deepslate_tile_slab") ++ .put("minecraft:grimstone_tile_stairs", "minecraft:deepslate_tile_stairs") ++ .put("minecraft:grimstone_tile_wall", "minecraft:deepslate_tile_wall") ++ .put("minecraft:grimstone_bricks", "minecraft:deepslate_bricks") ++ .put("minecraft:grimstone_brick_slab", "minecraft:deepslate_brick_slab") ++ .put("minecraft:grimstone_brick_stairs", "minecraft:deepslate_brick_stairs") ++ .put("minecraft:grimstone_brick_wall", "minecraft:deepslate_brick_wall") ++ .put("minecraft:chiseled_grimstone", "minecraft:chiseled_deepslate") ++ .build() ++ ); ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, RENAMES::get); ++ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c37142033061a3e4865686ee64d8f15f040d7d41 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java +@@ -0,0 +1,17 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2700 { ++ ++ protected static final int VERSION = MCVersions.V21W10A + 1; ++ ++ public static void register() { ++ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, ImmutableMap.of( ++ "minecraft:cave_vines_head", "minecraft:cave_vines", ++ "minecraft:cave_vines_body", "minecraft:cave_vines_plant" ++ )::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9d6b03410c4665e19a2a35226d11f77b2cae3bbf +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java +@@ -0,0 +1,203 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.google.common.collect.Sets; ++import java.util.Set; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++ ++public final class V2701 { ++ ++ protected static final int VERSION = MCVersions.V21W10A + 2; ++ ++ private static final Pattern INDEX_PATTERN = Pattern.compile("\\[(\\d+)\\]"); ++ ++ private static final Set PIECE_TYPE = Sets.newHashSet( ++ "minecraft:jigsaw", ++ "minecraft:nvi", ++ "minecraft:pcp", ++ "minecraft:bastionremnant", ++ "minecraft:runtime" ++ ); ++ private static final Set FEATURES = Sets.newHashSet( ++ "minecraft:tree", ++ "minecraft:flower", ++ "minecraft:block_pile", ++ "minecraft:random_patch" ++ ); ++ ++ public static void register() { ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType children = data.getList("Children", ObjectType.MAP); ++ ++ if (children == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = children.size(); i < len; ++i) { ++ final MapType child = children.getMap(i); ++ ++ if (!PIECE_TYPE.contains(child.getString("id"))) { ++ continue; ++ } ++ ++ final String poolElement = child.getString("pool_element"); ++ if (!"minecraft:feature_pool_element".equals(poolElement)) { ++ continue; ++ } ++ ++ final MapType feature = child.getMap("feature"); ++ if (feature == null) { ++ continue; ++ } ++ ++ final String replacement = convertToString(feature); ++ ++ if (replacement != null) { ++ child.setString("feature", replacement); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private static String getNestedString(final MapType root, final String... paths) { ++ if (paths.length == 0) { ++ throw new IllegalArgumentException("Missing path"); ++ } ++ ++ Object current = root.getGeneric(paths[0]); ++ ++ for (int i = 1; i < paths.length; ++i) { ++ final String path = paths[i]; ++ ++ final Matcher indexMatcher = INDEX_PATTERN.matcher(path); ++ if (!indexMatcher.matches()) { ++ current = (current instanceof MapType) ? ((MapType)current).getGeneric(path) : null; ++ if (current == null) { ++ break; ++ } ++ continue; ++ } ++ ++ final int index = Integer.parseInt(indexMatcher.group(1)); ++ if (!(current instanceof ListType)) { ++ current = null; ++ break; ++ } else { ++ final ListType list = (ListType)current; ++ if (index >= 0 && index < list.size()) { ++ current = list.getGeneric(index); ++ } else { ++ current = null; ++ break; ++ } ++ } ++ } ++ ++ return current instanceof String ? (String)current : ""; ++ } ++ ++ protected static String convertToString(final MapType feature) { ++ return getReplacement( ++ getNestedString(feature, "type"), ++ getNestedString(feature, "name"), ++ getNestedString(feature, "config", "state_provider", "type"), ++ getNestedString(feature, "config", "state_provider", "state", "Name"), ++ getNestedString(feature, "config", "state_provider", "entries", "[0]", "data", "Name"), ++ getNestedString(feature, "config", "foliage_placer", "type"), ++ getNestedString(feature, "config", "leaves_provider", "state", "Name") ++ ); ++ } ++ ++ private static String getReplacement(final String type, final String name, final String stateType, final String stateName, ++ final String firstEntryName, final String foliageName, final String leavesName) { ++ final String actualType; ++ if (!type.isEmpty()) { ++ actualType = type; ++ } else { ++ if (name.isEmpty()) { ++ return null; ++ } ++ ++ if ("minecraft:normal_tree".equals(name)) { ++ actualType = "minecraft:tree"; ++ } else { ++ actualType = name; ++ } ++ } ++ ++ if (FEATURES.contains(actualType)) { ++ if ("minecraft:random_patch".equals(actualType)) { ++ if ("minecraft:simple_state_provider".equals(stateType)) { ++ if ("minecraft:sweet_berry_bush".equals(stateName)) { ++ return "minecraft:patch_berry_bush"; ++ } ++ ++ if ("minecraft:cactus".equals(stateName)) { ++ return "minecraft:patch_cactus"; ++ } ++ } else if ("minecraft:weighted_state_provider".equals(stateType) && ("minecraft:grass".equals(firstEntryName) || "minecraft:fern".equals(firstEntryName))) { ++ return "minecraft:patch_taiga_grass"; ++ } ++ } else if ("minecraft:block_pile".equals(actualType)) { ++ if (!"minecraft:simple_state_provider".equals(stateType) && !"minecraft:rotated_block_provider".equals(stateType)) { ++ if ("minecraft:weighted_state_provider".equals(stateType)) { ++ if ("minecraft:packed_ice".equals(firstEntryName) || "minecraft:blue_ice".equals(firstEntryName)) { ++ return "minecraft:pile_ice"; ++ } ++ ++ if ("minecraft:jack_o_lantern".equals(firstEntryName) || "minecraft:pumpkin".equals(firstEntryName)) { ++ return "minecraft:pile_pumpkin"; ++ } ++ } ++ } else { ++ if ("minecraft:hay_block".equals(stateName)) { ++ return "minecraft:pile_hay"; ++ } ++ ++ if ("minecraft:melon".equals(stateName)) { ++ return "minecraft:pile_melon"; ++ } ++ ++ if ("minecraft:snow".equals(stateName)) { ++ return "minecraft:pile_snow"; ++ } ++ } ++ } else { ++ if ("minecraft:flower".equals(actualType)) { ++ return "minecraft:flower_plain"; ++ } ++ ++ if ("minecraft:tree".equals(actualType)) { ++ if ("minecraft:acacia_foliage_placer".equals(foliageName)) { ++ return "minecraft:acacia"; ++ } ++ ++ if ("minecraft:blob_foliage_placer".equals(foliageName) && "minecraft:oak_leaves".equals(leavesName)) { ++ return "minecraft:oak"; ++ } ++ ++ if ("minecraft:pine_foliage_placer".equals(foliageName)) { ++ return "minecraft:pine"; ++ } ++ ++ if ("minecraft:spruce_foliage_placer".equals(foliageName)) { ++ return "minecraft:spruce"; ++ } ++ } ++ } ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java +new file mode 100644 +index 0000000000000000000000000000000000000000..53e45b14c05dab35cd5725998458d47e28718075 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java +@@ -0,0 +1,33 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2702 { ++ ++ protected static final int VERSION = MCVersions.V21W10A + 3; ++ ++ public static void register() { ++ final DataConverter, MapType> arrowConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.hasKey("pickup")) { ++ return null; ++ } ++ ++ final boolean player = data.getBoolean("player", true); ++ data.remove("player"); ++ ++ data.setByte("pickup", player ? (byte)1 : (byte)0); ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:arrow", arrowConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:spectral_arrow", arrowConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:trident", arrowConverter); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java +new file mode 100644 +index 0000000000000000000000000000000000000000..74c1df97036059b3a5147f7cf94752ef4516a33d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.AddFlagIfAbsent; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2707 { ++ ++ protected static final int VERSION = MCVersions.V21W14A + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", true)); ++ ++ registerMob("minecraft:marker"); // ????????????? ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0d31b10a4f93c0eb8bff66dd062ffb950b2182ec +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java +@@ -0,0 +1,17 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.stats.ConverterAbstractStatsRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2710 { ++ ++ protected static final int VERSION = MCVersions.V21W15A + 1; ++ ++ public static void register() { ++ ConverterAbstractStatsRename.register(VERSION, ImmutableMap.of( ++ "minecraft:play_one_minute", "minecraft:play_time" ++ )::get); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8678ba95b5abe96b399a310623078f8827dfa0f0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2717 { ++ ++ protected static final int VERSION = MCVersions.V1_17_PRE1 + 1; ++ ++ public static void register() { ++ final ImmutableMap rename = ImmutableMap.of( ++ "minecraft:azalea_leaves_flowers", "minecraft:flowering_azalea_leaves" ++ ); ++ ConverterAbstractItemRename.register(VERSION, rename::get); ++ ConverterAbstractBlockRename.register(VERSION, rename::get); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c2d2b7c10e5b988b1111b20b778c475a12bef353 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java +@@ -0,0 +1,15 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.AddFlagIfAbsent; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++ ++public final class V2825 { ++ ++ protected static final int VERSION = MCVersions.V1_17_1 + 95; ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", false)); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d28ade80499dce882a9a84309a2a0da527fe01a0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java +@@ -0,0 +1,69 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V2831 { ++ ++ protected static final int VERSION = MCVersions.V1_17_1 + 101; ++ ++ public static void register() { ++ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureWalker(VERSION, (final MapType root, final long fromVersion, final long toVersion) -> { ++ final ListType spawnPotentials = root.getList("SpawnPotentials", ObjectType.MAP); ++ if (spawnPotentials != null) { ++ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { ++ final MapType spawnPotential = spawnPotentials.getMap(i); ++ ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, spawnPotential.getMap("data"), "entity", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, root.getMap("SpawnData"), "entity", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { ++ final MapType spawnData = root.getMap("SpawnData"); ++ if (spawnData != null) { ++ final MapType wrapped = Types.NBT.createEmptyMap(); ++ root.setMap("SpawnData", wrapped); ++ ++ wrapped.setMap("entity", spawnData); ++ } ++ ++ final ListType spawnPotentials = root.getList("SpawnPotentials", ObjectType.MAP); ++ if (spawnPotentials != null) { ++ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { ++ final MapType spawnPotential = spawnPotentials.getMap(i); ++ ++ // new format of weighted list (SpawnPotentials): ++ // root.data -> data ++ // root.weight -> weight ++ ++ final MapType entity = spawnPotential.getMap("Entity"); ++ final int weight = spawnPotential.getInt("Weight", 1); ++ spawnPotential.remove("Entity"); ++ spawnPotential.remove("Weight"); ++ spawnPotential.setInt("weight", weight); ++ ++ final MapType data = Types.NBT.createEmptyMap(); ++ spawnPotential.setMap("data", data); ++ ++ data.setMap("entity", entity); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1569cf06aa1dd73961d053a57722aac4c36a4148 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java +@@ -0,0 +1,927 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.mojang.logging.LogUtils; ++import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.ints.IntIterator; ++import it.unimi.dsi.fastutil.ints.IntOpenHashSet; ++import org.apache.commons.lang3.mutable.MutableBoolean; ++import org.slf4j.Logger; ++import java.util.Arrays; ++import java.util.BitSet; ++import java.util.HashSet; ++import java.util.Set; ++ ++public final class V2832 { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V1_17_1 + 102; ++ ++ private static final String[] BIOMES_BY_ID = new String[256]; // rip datapacks ++ static { ++ BIOMES_BY_ID[0] = "minecraft:ocean"; ++ BIOMES_BY_ID[1] = "minecraft:plains"; ++ BIOMES_BY_ID[2] = "minecraft:desert"; ++ BIOMES_BY_ID[3] = "minecraft:mountains"; ++ BIOMES_BY_ID[4] = "minecraft:forest"; ++ BIOMES_BY_ID[5] = "minecraft:taiga"; ++ BIOMES_BY_ID[6] = "minecraft:swamp"; ++ BIOMES_BY_ID[7] = "minecraft:river"; ++ BIOMES_BY_ID[8] = "minecraft:nether_wastes"; ++ BIOMES_BY_ID[9] = "minecraft:the_end"; ++ BIOMES_BY_ID[10] = "minecraft:frozen_ocean"; ++ BIOMES_BY_ID[11] = "minecraft:frozen_river"; ++ BIOMES_BY_ID[12] = "minecraft:snowy_tundra"; ++ BIOMES_BY_ID[13] = "minecraft:snowy_mountains"; ++ BIOMES_BY_ID[14] = "minecraft:mushroom_fields"; ++ BIOMES_BY_ID[15] = "minecraft:mushroom_field_shore"; ++ BIOMES_BY_ID[16] = "minecraft:beach"; ++ BIOMES_BY_ID[17] = "minecraft:desert_hills"; ++ BIOMES_BY_ID[18] = "minecraft:wooded_hills"; ++ BIOMES_BY_ID[19] = "minecraft:taiga_hills"; ++ BIOMES_BY_ID[20] = "minecraft:mountain_edge"; ++ BIOMES_BY_ID[21] = "minecraft:jungle"; ++ BIOMES_BY_ID[22] = "minecraft:jungle_hills"; ++ BIOMES_BY_ID[23] = "minecraft:jungle_edge"; ++ BIOMES_BY_ID[24] = "minecraft:deep_ocean"; ++ BIOMES_BY_ID[25] = "minecraft:stone_shore"; ++ BIOMES_BY_ID[26] = "minecraft:snowy_beach"; ++ BIOMES_BY_ID[27] = "minecraft:birch_forest"; ++ BIOMES_BY_ID[28] = "minecraft:birch_forest_hills"; ++ BIOMES_BY_ID[29] = "minecraft:dark_forest"; ++ BIOMES_BY_ID[30] = "minecraft:snowy_taiga"; ++ BIOMES_BY_ID[31] = "minecraft:snowy_taiga_hills"; ++ BIOMES_BY_ID[32] = "minecraft:giant_tree_taiga"; ++ BIOMES_BY_ID[33] = "minecraft:giant_tree_taiga_hills"; ++ BIOMES_BY_ID[34] = "minecraft:wooded_mountains"; ++ BIOMES_BY_ID[35] = "minecraft:savanna"; ++ BIOMES_BY_ID[36] = "minecraft:savanna_plateau"; ++ BIOMES_BY_ID[37] = "minecraft:badlands"; ++ BIOMES_BY_ID[38] = "minecraft:wooded_badlands_plateau"; ++ BIOMES_BY_ID[39] = "minecraft:badlands_plateau"; ++ BIOMES_BY_ID[40] = "minecraft:small_end_islands"; ++ BIOMES_BY_ID[41] = "minecraft:end_midlands"; ++ BIOMES_BY_ID[42] = "minecraft:end_highlands"; ++ BIOMES_BY_ID[43] = "minecraft:end_barrens"; ++ BIOMES_BY_ID[44] = "minecraft:warm_ocean"; ++ BIOMES_BY_ID[45] = "minecraft:lukewarm_ocean"; ++ BIOMES_BY_ID[46] = "minecraft:cold_ocean"; ++ BIOMES_BY_ID[47] = "minecraft:deep_warm_ocean"; ++ BIOMES_BY_ID[48] = "minecraft:deep_lukewarm_ocean"; ++ BIOMES_BY_ID[49] = "minecraft:deep_cold_ocean"; ++ BIOMES_BY_ID[50] = "minecraft:deep_frozen_ocean"; ++ BIOMES_BY_ID[127] = "minecraft:the_void"; ++ BIOMES_BY_ID[129] = "minecraft:sunflower_plains"; ++ BIOMES_BY_ID[130] = "minecraft:desert_lakes"; ++ BIOMES_BY_ID[131] = "minecraft:gravelly_mountains"; ++ BIOMES_BY_ID[132] = "minecraft:flower_forest"; ++ BIOMES_BY_ID[133] = "minecraft:taiga_mountains"; ++ BIOMES_BY_ID[134] = "minecraft:swamp_hills"; ++ BIOMES_BY_ID[140] = "minecraft:ice_spikes"; ++ BIOMES_BY_ID[149] = "minecraft:modified_jungle"; ++ BIOMES_BY_ID[151] = "minecraft:modified_jungle_edge"; ++ BIOMES_BY_ID[155] = "minecraft:tall_birch_forest"; ++ BIOMES_BY_ID[156] = "minecraft:tall_birch_hills"; ++ BIOMES_BY_ID[157] = "minecraft:dark_forest_hills"; ++ BIOMES_BY_ID[158] = "minecraft:snowy_taiga_mountains"; ++ BIOMES_BY_ID[160] = "minecraft:giant_spruce_taiga"; ++ BIOMES_BY_ID[161] = "minecraft:giant_spruce_taiga_hills"; ++ BIOMES_BY_ID[162] = "minecraft:modified_gravelly_mountains"; ++ BIOMES_BY_ID[163] = "minecraft:shattered_savanna"; ++ BIOMES_BY_ID[164] = "minecraft:shattered_savanna_plateau"; ++ BIOMES_BY_ID[165] = "minecraft:eroded_badlands"; ++ BIOMES_BY_ID[166] = "minecraft:modified_wooded_badlands_plateau"; ++ BIOMES_BY_ID[167] = "minecraft:modified_badlands_plateau"; ++ BIOMES_BY_ID[168] = "minecraft:bamboo_jungle"; ++ BIOMES_BY_ID[169] = "minecraft:bamboo_jungle_hills"; ++ BIOMES_BY_ID[170] = "minecraft:soul_sand_valley"; ++ BIOMES_BY_ID[171] = "minecraft:crimson_forest"; ++ BIOMES_BY_ID[172] = "minecraft:warped_forest"; ++ BIOMES_BY_ID[173] = "minecraft:basalt_deltas"; ++ BIOMES_BY_ID[174] = "minecraft:dripstone_caves"; ++ BIOMES_BY_ID[175] = "minecraft:lush_caves"; ++ BIOMES_BY_ID[177] = "minecraft:meadow"; ++ BIOMES_BY_ID[178] = "minecraft:grove"; ++ BIOMES_BY_ID[179] = "minecraft:snowy_slopes"; ++ BIOMES_BY_ID[180] = "minecraft:snowcapped_peaks"; ++ BIOMES_BY_ID[181] = "minecraft:lofty_peaks"; ++ BIOMES_BY_ID[182] = "minecraft:stony_peaks"; ++ } ++ ++ private static final String[] HEIGHTMAP_TYPES = new String[] { ++ "WORLD_SURFACE_WG", ++ "WORLD_SURFACE", ++ "WORLD_SURFACE_IGNORE_SNOW", ++ "OCEAN_FLOOR_WG", ++ "OCEAN_FLOOR", ++ "MOTION_BLOCKING", ++ "MOTION_BLOCKING_NO_LEAVES" ++ }; ++ ++ private static final Set STATUS_IS_OR_AFTER_SURFACE = new HashSet<>(Arrays.asList( ++ "surface", ++ "carvers", ++ "liquid_carvers", ++ "features", ++ "light", ++ "spawn", ++ "heightmaps", ++ "full" ++ )); ++ private static final Set STATUS_IS_OR_AFTER_NOISE = new HashSet<>(Arrays.asList( ++ "noise", ++ "surface", ++ "carvers", ++ "liquid_carvers", ++ "features", ++ "light", ++ "spawn", ++ "heightmaps", ++ "full" ++ )); ++ private static final Set BLOCKS_BEFORE_FEATURE_STATUS = new HashSet<>(Arrays.asList( ++ "minecraft:air", ++ "minecraft:basalt", ++ "minecraft:bedrock", ++ "minecraft:blackstone", ++ "minecraft:calcite", ++ "minecraft:cave_air", ++ "minecraft:coarse_dirt", ++ "minecraft:crimson_nylium", ++ "minecraft:dirt", ++ "minecraft:end_stone", ++ "minecraft:grass_block", ++ "minecraft:gravel", ++ "minecraft:ice", ++ "minecraft:lava", ++ "minecraft:mycelium", ++ "minecraft:nether_wart_block", ++ "minecraft:netherrack", ++ "minecraft:orange_terracotta", ++ "minecraft:packed_ice", ++ "minecraft:podzol", ++ "minecraft:powder_snow", ++ "minecraft:red_sand", ++ "minecraft:red_sandstone", ++ "minecraft:sand", ++ "minecraft:sandstone", ++ "minecraft:snow_block", ++ "minecraft:soul_sand", ++ "minecraft:soul_soil", ++ "minecraft:stone", ++ "minecraft:terracotta", ++ "minecraft:warped_nylium", ++ "minecraft:warped_wart_block", ++ "minecraft:water", ++ "minecraft:white_terracotta" ++ )); ++ ++ private static int getObjectsPerValue(final long[] val) { ++ return (4096 + val.length - 1) / (val.length); // expression is invalid if it returns > 64 ++ } ++ ++ private static long[] resize(final long[] val, final int oldBitsPerObject, final int newBitsPerObject) { ++ final long oldMask = (1L << oldBitsPerObject) - 1; // works even if bitsPerObject == 64 ++ final long newMask = (1L << newBitsPerObject) - 1; ++ final int oldObjectsPerValue = 64 / oldBitsPerObject; ++ final int newObjectsPerValue = 64 / newBitsPerObject; ++ ++ if (newBitsPerObject == oldBitsPerObject) { ++ return val; ++ } ++ ++ final int items = 4096; ++ ++ final long[] ret = new long[(items + newObjectsPerValue - 1) / newObjectsPerValue]; ++ ++ final int expectedSize = ((items + oldObjectsPerValue - 1) / oldObjectsPerValue); ++ if (val.length != expectedSize) { ++ throw new IllegalStateException("Expected size: " + expectedSize + ", got: " + val.length); ++ } ++ ++ int shift = 0; ++ int idx = 0; ++ long newCurr = 0L; ++ ++ int currItem = 0; ++ for (int i = 0; i < val.length; ++i) { ++ final long oldCurr = val[i]; ++ ++ for (int objIdx = 0; currItem < items && objIdx + oldBitsPerObject <= 64; objIdx += oldBitsPerObject, ++currItem) { ++ final long value = (oldCurr >> objIdx) & oldMask; ++ ++ if ((value & newMask) != value) { ++ throw new IllegalStateException("Old data storage has values that cannot be moved into new palette (would erase data)!"); ++ } ++ ++ newCurr |= value << shift; ++ shift += newBitsPerObject; ++ ++ if (shift + newBitsPerObject > 64) { // will next write overflow? ++ // must move to next idx ++ ret[idx++] = newCurr; ++ shift = 0; ++ newCurr = 0L; ++ } ++ } ++ } ++ ++ // don't forget to write the last one ++ if (shift != 0) { ++ ret[idx] = newCurr; ++ } ++ ++ return ret; ++ } ++ ++ private static void fixLithiumChunks(final MapType data) { ++ // See https://github.com/CaffeineMC/lithium-fabric/issues/279 ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return; ++ } ++ ++ final int chunkX = level.getInt("xPos"); ++ final int chunkZ = level.getInt("zPos"); ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections == null) { ++ return; ++ } ++ ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ final int sectionY = section.getInt("Y"); ++ ++ final ListType palette = section.getList("Palette", ObjectType.MAP); ++ final long[] blockStates = section.getLongs("BlockStates"); ++ ++ if (palette == null || blockStates == null) { ++ continue; ++ } ++ ++ final int expectedBits = Math.max(4, ceilLog2(palette.size())); ++ final int gotObjectsPerValue = getObjectsPerValue(blockStates); ++ final int gotBits = 64 / gotObjectsPerValue; ++ ++ if (expectedBits == gotBits) { ++ continue; ++ } ++ ++ try { ++ section.setLongs("BlockStates", resize(blockStates, gotBits, expectedBits)); ++ } catch (final Exception ex) { ++ LOGGER.error("Failed to rewrite mismatched palette and data storage for section y: " + sectionY ++ + " for chunk [" + chunkX + "," + chunkZ + "], palette entries: " + palette.size() + ", data storage size: " ++ + blockStates.length, ++ ex ++ ); ++ } ++ } ++ } ++ ++ public static void register() { ++ // See V2551 for the layout of world gen settings ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ // converters were added to older versions note whether the world has increased height already or not ++ final boolean noHeightFlag = !data.hasKey("has_increased_height_already"); ++ final boolean hasIncreasedHeight = data.getBoolean("has_increased_height_already", true); ++ data.remove("has_increased_height_already"); ++ ++ final MapType dimensions = data.getMap("dimensions"); ++ if (dimensions == null) { ++ // wat ++ return null; ++ } ++ ++ // only care about overworld ++ final MapType overworld = dimensions.getMap("minecraft:overworld"); ++ if (overworld == null) { ++ // wat ++ return null; ++ } ++ ++ final MapType generator = overworld.getMap("generator"); ++ if (generator == null) { ++ // wat ++ return null; ++ } ++ ++ final String type = generator.getString("type", ""); ++ switch (type) { ++ case "minecraft:noise": { ++ final MapType biomeSource = generator.getMap("biome_source"); ++ final String sourceType = biomeSource.getString("type"); ++ ++ boolean largeBiomes = false; ++ ++ if ("minecraft:vanilla_layered".equals(sourceType) || (noHeightFlag && "minecraft:multi_noise".equals(sourceType))) { ++ largeBiomes = biomeSource.getBoolean("large_biomes"); ++ ++ final MapType newBiomeSource = Types.NBT.createEmptyMap(); ++ generator.setMap("biome_source", newBiomeSource); ++ ++ newBiomeSource.setString("preset", "minecraft:overworld"); ++ newBiomeSource.setString("type", "minecraft:multi_noise"); ++ } ++ ++ if (largeBiomes) { ++ if ("minecraft:overworld".equals(generator.getString("settings"))) { ++ generator.setString("settings", "minecraft:large_biomes"); ++ } ++ } ++ ++ break; ++ } ++ case "minecraft:flat": { ++ if (!hasIncreasedHeight) { ++ final MapType settings = generator.getMap("settings"); ++ if (settings == null) { ++ break; ++ } ++ ++ updateLayers(settings.getList("layers", ObjectType.MAP)); ++ } ++ break; ++ } ++ default: ++ // do nothing ++ break; ++ } ++ ++ return null; ++ } ++ }); ++ ++ ++ // It looks like DFU will only support worlds in the old height format or the new one, any custom one isn't supported ++ // and by not supported I mean it will just treat it as the old format... maybe at least throw in that case? ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ // The below covers padPaletteEntries - this was written BEFORE that code was added to the datafixer - ++ // and this still works, so I'm keeping it. Don't fix what isn't broken. ++ fixLithiumChunks(data); // See https://github.com/CaffeineMC/lithium-fabric/issues/279 ++ ++ final MapType level = data.getMap("Level"); ++ ++ if (level == null) { ++ return null; ++ } ++ ++ final MapType context = data.getMap("__context"); // Passed through by ChunkStorage ++ final String dimension = context == null ? "" : context.getString("dimension", ""); ++ final String generator = context == null ? "" : context.getString("generator", ""); ++ final boolean isOverworld = "minecraft:overworld".equals(dimension); ++ final int minSection = isOverworld ? -4 : 0; ++ final MutableBoolean isAlreadyExtended = new MutableBoolean(); ++ ++ final MapType[] newBiomes = createBiomeSections(level, isOverworld, minSection, isAlreadyExtended); ++ final MapType wrappedEmptyBlockPalette = getEmptyBlockPalette(); ++ ++ ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections == null) { ++ level.setList("Sections", sections = Types.NBT.createEmptyList()); ++ } ++ ++ // must update sections for two things: ++ // 1. the biomes are now stored per section, so we must insert the biomes palette into each section (and create them if they don't exist) ++ // 2. each section must now have block states (or at least DFU is ensuring they do, but current code does not require) ++ V2841.SimplePaletteReader bottomSection = null; ++ final Set allBlocks = new HashSet<>(); ++ final IntOpenHashSet existingSections = new IntOpenHashSet(); ++ ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ final int y = section.getInt("Y"); ++ final int sectionIndex = y - minSection; ++ ++ existingSections.add(y); ++ ++ // add in relevant biome section ++ if (sectionIndex >= 0 && sectionIndex < newBiomes.length) { ++ // exclude out of bounds sections (i.e the light sections above and below the world) ++ section.setMap("biomes", newBiomes[sectionIndex]); ++ } ++ ++ // update palette ++ final ListType palette = section.getList("Palette", ObjectType.MAP); ++ final long[] blockStates = section.getLongs("BlockStates"); ++ ++ section.remove("Palette"); ++ section.remove("BlockStates"); ++ ++ if (palette != null) { ++ for (int j = 0, len2 = palette.size(); j < len2; ++j) { ++ allBlocks.add(V2841.getBlockId(palette.getMap(j))); ++ } ++ } ++ ++ final MapType palettedContainer; ++ if (palette != null && blockStates != null) { ++ // only if both exist, same as DFU, same as legacy chunk loading code ++ section.setMap("block_states", palettedContainer = wrapPaletteOptimised(palette, blockStates)); ++ } else { ++ section.setMap("block_states", palettedContainer = wrappedEmptyBlockPalette.copy()); // must write a palette now, copy so that later edits do not edit them all ++ } ++ ++ if (section.getInt("Y", Integer.MAX_VALUE) == 0) { ++ bottomSection = new V2841.SimplePaletteReader(palettedContainer.getList("palette", ObjectType.MAP), palettedContainer.getLongs("data")); ++ } ++ } ++ ++ // all existing sections updated, now we must create new sections just for the biomes migration ++ for (int sectionIndex = 0; sectionIndex < newBiomes.length; ++sectionIndex) { ++ final int sectionY = sectionIndex + minSection; ++ if (!existingSections.add(sectionY)) { ++ // exists already ++ continue; ++ } ++ ++ final MapType newSection = Types.NBT.createEmptyMap(); ++ sections.addMap(newSection); ++ ++ newSection.setByte("Y", (byte)sectionY); ++ // must write a palette now, copy so that later edits do not edit them all ++ newSection.setMap("block_states", wrappedEmptyBlockPalette.copy()); ++ ++ newSection.setGeneric("biomes", newBiomes[sectionIndex]); ++ } ++ ++ // update status so interpolation can take place ++ predictChunkStatusBeforeSurface(level, allBlocks); ++ ++ // done with sections, update the rest of the chunk ++ updateChunkData(level, isOverworld, isAlreadyExtended.getValue(), "minecraft:noise".equals(generator), bottomSection); ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType dimensions = data.getMap("dimensions"); ++ ++ if (dimensions == null) { ++ return null; ++ } ++ ++ for (final String dimension : dimensions.keys()) { ++ final MapType dimensionData = dimensions.getMap(dimension); ++ if (dimensionData == null) { ++ continue; ++ } ++ ++ final MapType generator = dimensionData.getMap("generator"); ++ if (generator == null) { ++ continue; ++ } ++ ++ final String type = generator.getString("type"); ++ if (type == null) { ++ continue; ++ } ++ ++ switch (type) { ++ case "minecraft:flat": { ++ final MapType settings = generator.getMap("settings"); ++ if (settings == null) { ++ continue; ++ } ++ ++ WalkerUtils.convert(MCTypeRegistry.BIOME, settings, "biome", fromVersion, toVersion); ++ ++ final ListType layers = settings.getList("layers", ObjectType.MAP); ++ if (layers != null) { ++ for (int i = 0, len = layers.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, layers.getMap(i), "block", fromVersion, toVersion); ++ } ++ } ++ ++ break; ++ } ++ case "minecraft:noise": { ++ final MapType settings = generator.getMap("settings"); ++ if (settings != null) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_block", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_fluid", fromVersion, toVersion); ++ } ++ ++ final MapType biomeSource = generator.getMap("biome_source"); ++ if (biomeSource != null) { ++ final String biomeSourceType = biomeSource.getString("type", ""); ++ switch (biomeSourceType) { ++ case "minecraft:fixed": { ++ WalkerUtils.convert(MCTypeRegistry.BIOME, biomeSource, "biome", fromVersion, toVersion); ++ break; ++ } ++ ++ case "minecraft:multi_noise": { ++ WalkerUtils.convert(MCTypeRegistry.MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST, biomeSource, "preset", fromVersion, toVersion); ++ ++ // Vanilla's schema is _still_ wrong. It should be DSL.fields("biomes", DSL.list(DSL.fields("biome"))) ++ // But it just contains the list part. That obviously can never be the case, because ++ // the root object is a compound, not a list. ++ ++ final ListType biomes = biomeSource.getList("biomes", ObjectType.MAP); ++ if (biomes != null) { ++ for (int i = 0, len = biomes.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BIOME, biomes.getMap(i), "biome", fromVersion, toVersion); ++ } ++ } ++ ++ break; ++ } ++ ++ case "minecraft:checkerboard": { ++ WalkerUtils.convertList(MCTypeRegistry.BIOME, biomeSource, "biomes", fromVersion, toVersion); ++ break; ++ } ++ } ++ } ++ ++ break; ++ } ++ } ++ } ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); ++ ++ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); ++ if (tileTicks != null) { ++ for (int i = 0, len = tileTicks.size(); i < len; ++i) { ++ final MapType tileTick = tileTicks.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); ++ } ++ } ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ WalkerUtils.convertList(MCTypeRegistry.BIOME, section.getMap("biomes"), "palette", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section.getMap("block_states"), "palette", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, level.getMap("Structures"), "Starts", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++ ++ private static void predictChunkStatusBeforeSurface(final MapType level, final Set chunkBlocks) { ++ final String status = level.getString("Status", "empty"); ++ if (STATUS_IS_OR_AFTER_SURFACE.contains(status)) { ++ return; ++ } ++ ++ chunkBlocks.remove("minecraft:air"); ++ final boolean chunkNotEmpty = !chunkBlocks.isEmpty(); ++ chunkBlocks.removeAll(BLOCKS_BEFORE_FEATURE_STATUS); ++ final boolean chunkFeatureStatus = !chunkBlocks.isEmpty(); ++ ++ final String update; ++ if (chunkFeatureStatus) { ++ update = "liquid_carvers"; ++ } else if (!"noise".equals(status) && !chunkNotEmpty) { ++ update = "biomes".equals(status) ? "structure_references" : status; ++ } else { ++ update = "noise"; ++ } ++ ++ level.setString("Status", update); ++ } ++ ++ private static MapType getEmptyBlockPalette() { ++ final MapType airBlockState = Types.NBT.createEmptyMap(); ++ airBlockState.setString("Name", "minecraft:air"); ++ ++ final ListType emptyBlockPalette = Types.NBT.createEmptyList(); ++ emptyBlockPalette.addMap(airBlockState); ++ ++ return V2832.wrapPalette(emptyBlockPalette); ++ } ++ ++ private static void shiftUpgradeData(final MapType upgradeData, final int shift) { ++ if (upgradeData == null) { ++ return; ++ } ++ ++ final MapType indices = upgradeData.getMap("Indices"); ++ if (indices == null) { ++ return; ++ } ++ ++ RenameHelper.renameKeys(indices, (final String input) -> { ++ return Integer.toString(Integer.parseInt(input) + shift); ++ }); ++ } ++ ++ private static void updateChunkData(final MapType level, final boolean wantExtendedHeight, final boolean isAlreadyExtended, ++ final boolean onNoiseGenerator, final V2841.SimplePaletteReader bottomSection) { ++ level.remove("Biomes"); ++ if (!wantExtendedHeight) { ++ padCarvingMasks(level, 16, 0); ++ return; ++ } ++ ++ if (isAlreadyExtended) { ++ padCarvingMasks(level, 24, 0); ++ return; ++ } ++ ++ offsetHeightmaps(level); ++ // Difference from DFU: Still convert the Lights data. Just because it's being removed in a later version doesn't mean ++ // that it should be removed here. ++ // Generally, converters act only on the current version to bring it to the next. This principle allows the converter ++ // for the next version to assume that it acts on its current version, not some in-between of the current version ++ // and some future version that did not exist at the time it was written. This allows converters to be written and tested ++ // only with knowledge of the current version and the next version. ++ addEmptyListPadding(level, "Lights"); ++ addEmptyListPadding(level, "LiquidsToBeTicked"); ++ addEmptyListPadding(level, "PostProcessing"); ++ addEmptyListPadding(level, "ToBeTicked"); ++ shiftUpgradeData(level.getMap("UpgradeData"), 4); // https://bugs.mojang.com/browse/MC-238076 - fixed now, Mojang fix is identical. No change required. ++ padCarvingMasks(level, 24, 4); ++ ++ if (!onNoiseGenerator) { ++ return; ++ } ++ ++ final String status = level.getString("Status"); ++ if (status == null || "empty".equals(status)) { ++ return; ++ } ++ ++ final MapType blendingData = Types.NBT.createEmptyMap(); ++ level.setMap("blending_data", blendingData); ++ ++ blendingData.setBoolean("old_noise", STATUS_IS_OR_AFTER_NOISE.contains(status)); ++ ++ if (bottomSection == null) { ++ return; ++ } ++ ++ final BitSet missingBedrock = new BitSet(256); ++ boolean hasBedrock = status.equals("noise"); ++ ++ for (int z = 0; z <= 15; ++z) { ++ for (int x = 0; x <= 15; ++x) { ++ final MapType state = bottomSection.getState(x, 0, z); ++ final String blockId = V2841.getBlockId(state); ++ final boolean isBedrock = state != null && "minecraft:bedrock".equals(blockId); ++ final boolean isAir = state != null && "minecraft:air".equals(blockId); ++ if (isAir) { ++ missingBedrock.set((z << 4) | x); ++ } ++ ++ hasBedrock |= isBedrock; ++ } ++ } ++ ++ if (hasBedrock && missingBedrock.cardinality() != missingBedrock.size()) { ++ final String targetStatus = "full".equals(status) ? "heightmaps" : status; ++ ++ final MapType belowZeroRetrogen = Types.NBT.createEmptyMap(); ++ level.setMap("below_zero_retrogen", belowZeroRetrogen); ++ ++ belowZeroRetrogen.setString("target_status", targetStatus); ++ belowZeroRetrogen.setLongs("missing_bedrock", missingBedrock.toLongArray()); ++ ++ level.setString("Status", "empty"); ++ } ++ ++ level.setBoolean("isLightOn", false); ++ } ++ ++ private static void padCarvingMasks(final MapType level, final int newSize, final int offset) { ++ final MapType carvingMasks = level.getMap("CarvingMasks"); ++ if (carvingMasks == null) { ++ // if empty, DFU still writes ++ level.setMap("CarvingMasks", Types.NBT.createEmptyMap()); ++ return; ++ } ++ ++ for (final String key : carvingMasks.keys()) { ++ final long[] old = BitSet.valueOf(carvingMasks.getBytes(key)).toLongArray(); ++ final long[] newVal = new long[64 * newSize]; ++ ++ System.arraycopy(old, 0, newVal, 64 * offset, old.length); ++ ++ carvingMasks.setLongs(key, newVal); // no CME: key exists already ++ } ++ } ++ ++ private static void addEmptyListPadding(final MapType level, final String path) { ++ ListType list = level.getListUnchecked(path); ++ if (list != null && list.size() == 24) { ++ return; ++ } ++ ++ if (list == null) { ++ // difference from DFU: Don't create the damn thing! ++ return; ++ } ++ ++ ++ // offset the section array to the new format ++ for (int i = 0; i < 4; ++i) { ++ // always create new copies, so that modifying one doesn't modify ALL of them! ++ list.addList(0, Types.NBT.createEmptyList()); // add below ++ list.addList(Types.NBT.createEmptyList()); // add above ++ } ++ } ++ ++ private static void offsetHeightmaps(final MapType level) { ++ final MapType heightmaps = level.getMap("Heightmaps"); ++ if (heightmaps == null) { ++ return; ++ } ++ ++ for (final String key : HEIGHTMAP_TYPES) { ++ offsetHeightmap(heightmaps.getLongs(key)); ++ } ++ } ++ ++ private static void offsetHeightmap(final long[] heightmap) { ++ if (heightmap == null) { ++ return; ++ } ++ ++ // heightmaps are configured to have 9 bits per value, with 256 total values ++ // heightmaps are also relative to the lowest position ++ for (int idx = 0, len = heightmap.length; idx < len; ++idx) { ++ long curr = heightmap[idx]; ++ long next = 0L; ++ ++ for (int objIdx = 0; objIdx + 9 <= 64; objIdx += 9) { ++ final long value = (curr >> objIdx) & 511L; ++ if (value != 0L) { ++ final long offset = Math.min(511L, value + 64L); ++ ++ next |= (offset << objIdx); ++ } ++ } ++ ++ heightmap[idx] = next; ++ } ++ } ++ ++ private static MapType[] createBiomeSections(final MapType level, final boolean wantExtendedHeight, ++ final int minSection, final MutableBoolean isAlreadyExtended) { ++ final MapType[] ret = new MapType[wantExtendedHeight ? 24 : 16]; ++ ++ final int[] biomes = level.getInts("Biomes"); ++ ++ if (biomes != null && biomes.length == 1536) { // magic value for 24 sections of biomes (24 * 4^3) ++ isAlreadyExtended.setValue(true); ++ for (int sectionIndex = 0; sectionIndex < 24; ++sectionIndex) { ++ ret[sectionIndex] = createBiomeSection(biomes, sectionIndex * 64, -1); // -1 is all 1s ++ } ++ } else if (biomes != null && biomes.length == 1024) { // magic value for 24 sections of biomes (16 * 4^3) ++ for (int sectionY = 0; sectionY < 16; ++sectionY) { ++ ret[sectionY - minSection] = createBiomeSection(biomes, sectionY * 64, -1); // -1 is all 1s ++ } ++ ++ if (wantExtendedHeight) { ++ // must set the new sections at top and bottom ++ final MapType bottomCopy = createBiomeSection(biomes, 0, 15); // just want the biomes at y = 0 ++ final MapType topCopy = createBiomeSection(biomes, 1008, 15); // just want the biomes at y = 252 ++ ++ for (int sectionIndex = 0; sectionIndex < 4; ++sectionIndex) { ++ ret[sectionIndex] = bottomCopy.copy(); // copy palette so that later possible modifications don't trash all sections ++ } ++ ++ for (int sectionIndex = 20; sectionIndex < 24; ++sectionIndex) { ++ ret[sectionIndex] = topCopy.copy(); // copy palette so that later possible modifications don't trash all sections ++ } ++ } ++ } else { ++ final ListType palette = Types.NBT.createEmptyList(); ++ palette.addString("minecraft:plains"); ++ ++ for (int i = 0; i < ret.length; ++i) { ++ ret[i] = wrapPalette(palette.copy()); // copy palette so that later possible modifications don't trash all sections ++ } ++ } ++ ++ return ret; ++ } ++ ++ private static MapType createBiomeSection(final int[] biomes, final int offset, final int mask) { ++ final Int2IntLinkedOpenHashMap paletteId = new Int2IntLinkedOpenHashMap(); ++ ++ for (int idx = 0; idx < 64; ++idx) { ++ final int biome = biomes[offset + (idx & mask)]; ++ paletteId.putIfAbsent(biome, paletteId.size()); ++ } ++ ++ final ListType paletteString = Types.NBT.createEmptyList(); ++ for (final IntIterator iterator = paletteId.keySet().iterator(); iterator.hasNext();) { ++ final int biomeId = iterator.nextInt(); ++ final String biome = biomeId >= 0 && biomeId < BIOMES_BY_ID.length ? BIOMES_BY_ID[biomeId] : null; ++ paletteString.addString(biome == null ? "minecraft:plains" : biome); ++ } ++ ++ final int bitsPerObject = ceilLog2(paletteString.size()); ++ if (bitsPerObject == 0) { ++ return wrapPalette(paletteString); ++ } ++ ++ // manually create packed integer data ++ final int objectsPerValue = 64 / bitsPerObject; ++ final long[] packed = new long[(64 + objectsPerValue - 1) / objectsPerValue]; ++ ++ int shift = 0; ++ int idx = 0; ++ long curr = 0; ++ ++ for (int biome_idx = 0; biome_idx < 64; ++biome_idx) { ++ final int biome = biomes[offset + (biome_idx & mask)]; ++ ++ curr |= ((long)paletteId.get(biome)) << shift; ++ ++ shift += bitsPerObject; ++ ++ if (shift + bitsPerObject > 64) { // will next write overflow? ++ // must move to next idx ++ packed[idx++] = curr; ++ shift = 0; ++ curr = 0L; ++ } ++ } ++ ++ // don't forget to write the last one ++ if (shift != 0) { ++ packed[idx] = curr; ++ } ++ ++ return wrapPalette(paletteString, packed); ++ } ++ ++ private static MapType wrapPalette(final ListType palette) { ++ return wrapPalette(palette, null); ++ } ++ ++ private static MapType wrapPalette(final ListType palette, final long[] blockStates) { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ret.setList("palette", palette); ++ if (blockStates != null) { ++ ret.setLongs("data", blockStates); ++ } ++ ++ return ret; ++ } ++ ++ private static MapType wrapPaletteOptimised(final ListType palette, final long[] blockStates) { ++ if (palette.size() == 1) { ++ return wrapPalette(palette); ++ } ++ ++ return wrapPalette(palette, blockStates); ++ } ++ ++ public static int ceilLog2(final int value) { ++ return value == 0 ? 0 : Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ private static void updateLayers(final ListType layers) { ++ if (layers == null) { ++ return; ++ } ++ ++ layers.addMap(0, createEmptyLayer()); // add at the bottom ++ } ++ ++ private static MapType createEmptyLayer() { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ret.setInt("height", 64); ++ ret.setString("block", "minecraft:air"); ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4bdac86810c51e9f87ea82ba9f6c6d8ae8ce2bdf +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java +@@ -0,0 +1,30 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2833 { ++ ++ protected static final int VERSION = MCVersions.V1_17_1 + 103; ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType dimensions = data.getMap("dimensions"); ++ ++ for (final String dimensionKey : dimensions.keys()) { ++ final MapType dimension = dimensions.getMap(dimensionKey); ++ if (!dimension.hasKey("type")) { ++ throw new IllegalStateException("Unable load old custom worlds."); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ad25696e94da4706a9a59c89ba896b35894b285d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java +@@ -0,0 +1,60 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V2838 { ++ ++ protected static final int VERSION = MCVersions.V21W40A; ++ ++ public static final Map BIOME_UPDATE = new HashMap<>( ++ ImmutableMap.builder() ++ .put("minecraft:badlands_plateau", "minecraft:badlands") ++ .put("minecraft:bamboo_jungle_hills", "minecraft:bamboo_jungle") ++ .put("minecraft:birch_forest_hills", "minecraft:birch_forest") ++ .put("minecraft:dark_forest_hills", "minecraft:dark_forest") ++ .put("minecraft:desert_hills", "minecraft:desert") ++ .put("minecraft:desert_lakes", "minecraft:desert") ++ .put("minecraft:giant_spruce_taiga_hills", "minecraft:old_growth_spruce_taiga") ++ .put("minecraft:giant_spruce_taiga", "minecraft:old_growth_spruce_taiga") ++ .put("minecraft:giant_tree_taiga_hills", "minecraft:old_growth_pine_taiga") ++ .put("minecraft:giant_tree_taiga", "minecraft:old_growth_pine_taiga") ++ .put("minecraft:gravelly_mountains", "minecraft:windswept_gravelly_hills") ++ .put("minecraft:jungle_edge", "minecraft:sparse_jungle") ++ .put("minecraft:jungle_hills", "minecraft:jungle") ++ .put("minecraft:modified_badlands_plateau", "minecraft:badlands") ++ .put("minecraft:modified_gravelly_mountains", "minecraft:windswept_gravelly_hills") ++ .put("minecraft:modified_jungle_edge", "minecraft:sparse_jungle") ++ .put("minecraft:modified_jungle", "minecraft:jungle") ++ .put("minecraft:modified_wooded_badlands_plateau", "minecraft:wooded_badlands") ++ .put("minecraft:mountain_edge", "minecraft:windswept_hills") ++ .put("minecraft:mountains", "minecraft:windswept_hills") ++ .put("minecraft:mushroom_field_shore", "minecraft:mushroom_fields") ++ .put("minecraft:shattered_savanna", "minecraft:windswept_savanna") ++ .put("minecraft:shattered_savanna_plateau", "minecraft:windswept_savanna") ++ .put("minecraft:snowy_mountains", "minecraft:snowy_plains") ++ .put("minecraft:snowy_taiga_hills", "minecraft:snowy_taiga") ++ .put("minecraft:snowy_taiga_mountains", "minecraft:snowy_taiga") ++ .put("minecraft:snowy_tundra", "minecraft:snowy_plains") ++ .put("minecraft:stone_shore", "minecraft:stony_shore") ++ .put("minecraft:swamp_hills", "minecraft:swamp") ++ .put("minecraft:taiga_hills", "minecraft:taiga") ++ .put("minecraft:taiga_mountains", "minecraft:taiga") ++ .put("minecraft:tall_birch_forest", "minecraft:old_growth_birch_forest") ++ .put("minecraft:tall_birch_hills", "minecraft:old_growth_birch_forest") ++ .put("minecraft:wooded_badlands_plateau", "minecraft:wooded_badlands") ++ .put("minecraft:wooded_hills", "minecraft:forest") ++ .put("minecraft:wooded_mountains", "minecraft:windswept_forest") ++ .put("minecraft:lofty_peaks", "minecraft:jagged_peaks") ++ .put("minecraft:snowcapped_peaks", "minecraft:frozen_peaks") ++ .build() ++ ); ++ ++ public static void register() { ++ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, BIOME_UPDATE::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java +new file mode 100644 +index 0000000000000000000000000000000000000000..41b41ff084662bbc2e323713473e4e13b8e50cd7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java +@@ -0,0 +1,205 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import ca.spottedleaf.dataconverter.util.IntegerUtil; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++import java.util.Arrays; ++import java.util.HashSet; ++import java.util.Set; ++ ++public final class V2841 { ++ ++ protected static final int VERSION = MCVersions.V21W42A + 1; ++ ++ protected static final Set ALWAYS_WATERLOGGED = new HashSet<>(Arrays.asList( ++ "minecraft:bubble_column", ++ "minecraft:kelp", ++ "minecraft:kelp_plant", ++ "minecraft:seagrass", ++ "minecraft:tall_seagrass" ++ )); ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { ++ final MapType level = root.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ { ++ // Why it's renamed here and not the next data version is beyond me. ++ final MapType liquidTicks = level.getMap("LiquidTicks"); ++ if (liquidTicks != null) { ++ level.remove("LiquidTicks"); ++ level.setMap("fluid_ticks", liquidTicks); ++ } ++ } ++ ++ final Int2ObjectOpenHashMap sectionBlocks = new Int2ObjectOpenHashMap<>(); ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ int minSection = 0; // TODO wtf is this ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ final int sectionY = section.getInt("Y"); ++ if (sectionY < minSection && section.hasKey("biomes")) { ++ minSection = sectionY; ++ } ++ ++ final MapType blockStates = section.getMap("block_states"); ++ if (blockStates == null) { ++ continue; ++ } ++ ++ sectionBlocks.put(sectionY, new SimplePaletteReader(section.getList("palette", ObjectType.MAP), section.getLongs("data"))); ++ } ++ } ++ ++ level.setByte("yPos", (byte)minSection); // TODO ??????????????????????????????????????? ++ ++ if (level.hasKey("fluid_ticks") || level.hasKey("TileTicks")) { ++ return null; ++ } ++ ++ final int sectionX = level.getInt("xPos"); ++ final int sectionZ = level.getInt("zPos"); ++ ++ final ListType fluidTicks = level.getList("LiquidsToBeTicked", ObjectType.LIST); ++ final ListType blockTicks = level.getList("ToBeTicked", ObjectType.LIST); ++ level.remove("LiquidsToBeTicked"); ++ level.remove("ToBeTicked"); ++ ++ level.setList("fluid_ticks", migrateTickList(fluidTicks, false, sectionBlocks, sectionX, minSection, sectionZ)); ++ level.setList("TileTicks", migrateTickList(blockTicks, true, sectionBlocks, sectionX, minSection, sectionZ)); ++ ++ return null; ++ } ++ }); ++ } ++ ++ public static ListType migrateTickList(final ListType ticks, final boolean blockTicks, final Int2ObjectOpenHashMap sectionBlocks, ++ final int sectionX, final int minSection, final int sectionZ) { ++ final ListType ret = Types.NBT.createEmptyList(); ++ ++ if (ticks == null) { ++ return ret; ++ } ++ ++ for (int sectionIndex = 0, totalSections = ticks.size(); sectionIndex < totalSections; ++sectionIndex) { ++ final int sectionY = sectionIndex + minSection; ++ final ListType sectionTicks = ticks.getList(sectionIndex); ++ final SimplePaletteReader palette = sectionBlocks.get(sectionY); ++ ++ for (int i = 0, len = sectionTicks.size(); i < len; ++i) { ++ final int localIndex = sectionTicks.getShort(i) & 0xFFFF; ++ final MapType blockState = palette == null ? null : palette.getState(localIndex); ++ final String subjectId = blockTicks ? getBlockId(blockState) : getLiquidId(blockState); ++ ++ ret.addMap(createNewTick(subjectId, localIndex, sectionX, sectionY, sectionZ)); ++ } ++ } ++ ++ return ret; ++ } ++ ++ public static MapType createNewTick(final String subjectId, final int localIndex, final int sectionX, final int sectionY, final int sectionZ) { ++ final int newX = (localIndex & 15) + (sectionX << 4); ++ final int newZ = ((localIndex >> 4) & 15) + (sectionZ << 4); ++ final int newY = ((localIndex >> 8) & 15) + (sectionY << 4); ++ ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setString("i", subjectId); ++ ret.setInt("x", newX); ++ ret.setInt("y", newY); ++ ret.setInt("z", newZ); ++ ret.setInt("t", 0); ++ ret.setInt("p", 0); ++ ++ return ret; ++ } ++ ++ public static String getBlockId(final MapType blockState) { ++ return blockState == null ? "minecraft:air" : blockState.getString("Name", "minecraft:air"); ++ } ++ ++ private static String getLiquidId(final MapType blockState) { ++ if (blockState == null) { ++ return "minecraft:empty"; ++ } ++ ++ final String name = blockState.getString("Name"); ++ if (ALWAYS_WATERLOGGED.contains(name)) { ++ return "minecraft:water"; ++ } ++ ++ final MapType properties = blockState.getMap("Properties"); ++ if ("minecraft:water".equals(name)) { ++ return properties != null && properties.getInt("level") == 0 ? "minecraft:water" : "minecraft:flowing_water"; ++ } else if ("minecraft:lava".equals(name)) { ++ return properties != null && properties.getInt("level") == 0 ? "minecraft:lava" : "minecraft:flowing_lava"; ++ } ++ ++ return (properties != null && properties.getBoolean("waterlogged")) ? "minecraft:water" : "minecraft:empty"; ++ } ++ ++ public static final class SimplePaletteReader { ++ ++ public final ListType palette; ++ public final long[] data; ++ private final int bitsPerValue; ++ private final long mask; ++ private final int valuesPerLong; ++ ++ public SimplePaletteReader(final ListType palette, final long[] data) { ++ this.palette = palette == null ? null : (palette.size() == 0 ? null : palette); ++ this.data = data; ++ this.bitsPerValue = Math.max(4, IntegerUtil.ceilLog2(this.palette == null ? 0 : this.palette.size())); ++ this.mask = (1L << this.bitsPerValue) - 1L; ++ this.valuesPerLong = (int)(64L / this.bitsPerValue); ++ } ++ ++ public MapType getState(final int x, final int y, final int z) { ++ final int index = x | (z << 4) | (y << 8); ++ return this.getState(index); ++ } ++ ++ public MapType getState(final int index) { ++ final ListType palette = this.palette; ++ if (palette == null) { ++ return null; ++ } ++ ++ final int paletteSize = palette.size(); ++ if (paletteSize == 1) { ++ return palette.getMap(0); ++ } ++ ++ // x86 div computes mod. no loss here using mod ++ // if needed, can compute magic mul and shift for div values using IntegerUtil ++ final int dataIndex = index / this.valuesPerLong; ++ final int localIndex = (index % this.valuesPerLong) * this.bitsPerValue; ++ final long[] data = this.data; ++ if (dataIndex < 0 || dataIndex >= data.length) { ++ return null; ++ } ++ ++ final long value = data[dataIndex]; ++ final int paletteIndex = (int)((value >>> localIndex) & this.mask); ++ if (paletteIndex < 0 || paletteIndex >= paletteSize) { ++ return null; ++ } ++ ++ return palette.getMap(paletteIndex); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f06e24bb87baf01b1386fb7a6af1ea04f4d6f2ef +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java +@@ -0,0 +1,76 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V2842 { ++ ++ protected static final int VERSION = MCVersions.V21W42A + 2; ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { ++ final MapType level = root.getMap("Level"); ++ root.remove("Level"); ++ ++ if (!root.isEmpty()) { ++ for (final String key : root.keys()) { ++ if (level.hasKey(key)) { ++ // Don't clobber level's data ++ continue; ++ } ++ level.setGeneric(key, root.getGeneric(key)); ++ } ++ } ++ ++ // Rename top level first ++ RenameHelper.renameSingle(level, "TileEntities", "block_entities"); ++ RenameHelper.renameSingle(level, "TileTicks", "block_ticks"); ++ RenameHelper.renameSingle(level, "Entities", "entities"); ++ RenameHelper.renameSingle(level, "Sections", "sections"); ++ RenameHelper.renameSingle(level, "Structures", "structures"); ++ ++ // 2nd level ++ final MapType structures = level.getMap("structures"); ++ if (structures != null) { ++ RenameHelper.renameSingle(structures, "Starts", "starts"); ++ } ++ ++ return level; // Level is now root tag. ++ } ++ }); ++ ++ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "entities", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, data, "block_entities", fromVersion, toVersion); ++ ++ final ListType blockTicks = data.getList("block_ticks", ObjectType.MAP); ++ if (blockTicks != null) { ++ for (int i = 0, len = blockTicks.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, blockTicks.getMap(i), "i", fromVersion, toVersion); ++ } ++ } ++ ++ final ListType sections = data.getList("sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ WalkerUtils.convertList(MCTypeRegistry.BIOME, section.getMap("biomes"), "palette", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section.getMap("block_states"), "palette", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, data.getMap("structures"), "starts", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java +new file mode 100644 +index 0000000000000000000000000000000000000000..69783c4ca366e4895d4f1c5909656b4f41fee3a1 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java +@@ -0,0 +1,105 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import java.util.Map; ++ ++public final class V2843 { ++ ++ protected static final int VERSION = MCVersions.V21W42A + 3; ++ ++ public static void register() { ++ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, Map.of("minecraft:deep_warm_ocean", "minecraft:warm_ocean")::get); ++ ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ private static void moveOutOfBoundTicks(final ListType ticks, final MapType chunkRoot, final int chunkX, final int chunkZ, final String intoKey) { ++ if (ticks == null) { ++ return; ++ } ++ ++ ListType outOfBounds = null; ++ for (int i = 0, len = ticks.size(); i < len; ++i) { ++ final MapType tick = ticks.getMap(i); ++ final int x = tick.getInt("x"); ++ final int z = tick.getInt("z"); ++ // anything > 1 is lost, anything less than 1 stays. ++ if (Math.max(Math.abs(chunkX - (x >> 4)), Math.abs(chunkZ - (z >> 4))) == 1) { ++ // DFU doesn't remove, so neither do we. ++ if (outOfBounds == null) { ++ outOfBounds = Types.NBT.createEmptyList(); ++ } ++ outOfBounds.addMap(tick.copy()); ++ } ++ } ++ ++ if (outOfBounds != null) { ++ MapType upgradeData = chunkRoot.getMap("UpgradeData"); ++ if (upgradeData == null) { ++ chunkRoot.setMap("UpgradeData", upgradeData = Types.NBT.createEmptyMap()); ++ } ++ upgradeData.setList(intoKey, outOfBounds); ++ } ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ // After renames, so use new names ++ final int x = data.getInt("xPos"); ++ final int z = data.getInt("zPos"); ++ ++ moveOutOfBoundTicks(data.getList("block_ticks", ObjectType.MAP), data, x, z, "neighbor_block_ticks"); ++ moveOutOfBoundTicks(data.getList("fluid_ticks", ObjectType.MAP), data, x, z, "neighbor_fluid_ticks"); ++ ++ return null; ++ } ++ }); ++ ++ // DFU is missing schema for UpgradeData block names ++ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "entities", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, data, "block_entities", fromVersion, toVersion); ++ ++ final ListType blockTicks = data.getList("block_ticks", ObjectType.MAP); ++ if (blockTicks != null) { ++ for (int i = 0, len = blockTicks.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, blockTicks.getMap(i), "i", fromVersion, toVersion); ++ } ++ } ++ ++ final MapType upgradeData = data.getMap("UpgradeData"); ++ if (upgradeData != null) { ++ // Even though UpgradeData will retrieve the block from the World when the type no longer exists, ++ // the type from the world may not match what was actually queued. So, even though it may look like we ++ // can skip the walker here, we actually don't if we want to be thorough. ++ final ListType neighbourBlockTicks = upgradeData.getList("neighbor_block_ticks", ObjectType.MAP); ++ if (neighbourBlockTicks != null) { ++ for (int i = 0, len = neighbourBlockTicks.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, neighbourBlockTicks.getMap(i), "i", fromVersion, toVersion); ++ } ++ } ++ } ++ ++ final ListType sections = data.getList("sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ WalkerUtils.convertList(MCTypeRegistry.BIOME, section.getMap("biomes"), "palette", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section.getMap("block_states"), "palette", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, data.getMap("structures"), "starts", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java +new file mode 100644 +index 0000000000000000000000000000000000000000..236327249d2b95b799b90172d457601167492249 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2846 { ++ ++ protected static final int VERSION = MCVersions.V21W44A + 1; ++ ++ public static void register() { ++ ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of( ++ "minecraft:husbandry/play_jukebox_in_meadows", "minecraft:adventure/play_jukebox_in_meadows", ++ "minecraft:adventure/caves_and_cliff", "minecraft:adventure/fall_from_world_height", ++ "minecraft:adventure/ride_strider_in_overworld_lava", "minecraft:nether/ride_strider_in_overworld_lava" ++ )::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java +new file mode 100644 +index 0000000000000000000000000000000000000000..94ab7be8c34d2ebb557df5a0864130f7f12c2185 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java +@@ -0,0 +1,29 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2852 { ++ ++ protected static final int VERSION = MCVersions.V1_18_PRE5 + 1; ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType dimensions = data.getMap("dimensions"); ++ ++ for (final String dimensionKey : dimensions.keys()) { ++ final MapType dimension = dimensions.getMap(dimensionKey); ++ if (!dimension.hasKey("type")) { ++ throw new IllegalStateException("Unable load old custom worlds."); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7af7bf450080f65b8b7d7a8d2f941846c029e504 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java +@@ -0,0 +1,56 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2967 { ++ ++ protected static final int VERSION = MCVersions.V22W05A; ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType dimensions = data.getMap("dimensions"); ++ ++ if (dimensions == null) { ++ return null; ++ } ++ ++ for (final String dimension : dimensions.keys()) { ++ final MapType dimensionData = dimensions.getMap(dimension); ++ if (dimensionData == null) { ++ continue; ++ } ++ ++ final MapType generator = dimensionData.getMap("generator"); ++ if (generator == null) { ++ continue; ++ } ++ ++ final MapType settings = generator.getMap("settings"); ++ if (settings == null) { ++ continue; ++ } ++ ++ final MapType structures = settings.getMap("structures"); ++ if (structures == null) { ++ continue; ++ } ++ ++ for (final String structureKey : structures.keys()) { ++ structures.getMap(structureKey).setString("type", "minecraft:random_spread"); ++ } ++ ++ final MapType stronghold = structures.getMap("stronghold"); ++ stronghold.setString("type", "minecraft:concentric_rings"); ++ structures.setMap("minecraft:stronghold", stronghold.copy()); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fa824cdf629caec745eff7c09eb4570c62263752 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java +@@ -0,0 +1,192 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.common.collect.ImmutableMap; ++import it.unimi.dsi.fastutil.objects.Object2IntMap; ++import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++ ++public final class V2970 { ++ ++ protected static final int VERSION = MCVersions.V22W07A + 1; ++ private static final Map CONVERSION_MAP = new HashMap<>( ++ ImmutableMap.builder() ++ .put("mineshaft", BiomeRemap.create(Map.of(List.of("minecraft:badlands", "minecraft:eroded_badlands", "minecraft:wooded_badlands"), "minecraft:mineshaft_mesa"), "minecraft:mineshaft")) ++ .put("shipwreck", BiomeRemap.create(Map.of(List.of("minecraft:beach", "minecraft:snowy_beach"), "minecraft:shipwreck_beached"), "minecraft:shipwreck")) ++ .put("ocean_ruin", BiomeRemap.create(Map.of(List.of("minecraft:warm_ocean", "minecraft:lukewarm_ocean", "minecraft:deep_lukewarm_ocean"), "minecraft:ocean_ruin_warm"), "minecraft:ocean_ruin_cold")) ++ .put("village", BiomeRemap.create(Map.of(List.of("minecraft:desert"), "minecraft:village_desert", List.of("minecraft:savanna"), "minecraft:village_savanna", List.of("minecraft:snowy_plains"), "minecraft:village_snowy", List.of("minecraft:taiga"), "minecraft:village_taiga"), "minecraft:village_plains")) ++ .put("ruined_portal", BiomeRemap.create(Map.of(List.of("minecraft:desert"), "minecraft:ruined_portal_desert", List.of("minecraft:badlands", "minecraft:eroded_badlands", "minecraft:wooded_badlands", "minecraft:windswept_hills", "minecraft:windswept_forest", "minecraft:windswept_gravelly_hills", "minecraft:savanna_plateau", "minecraft:windswept_savanna", "minecraft:stony_shore", "minecraft:meadow", "minecraft:frozen_peaks", "minecraft:jagged_peaks", "minecraft:stony_peaks", "minecraft:snowy_slopes"), "minecraft:ruined_portal_mountain", List.of("minecraft:bamboo_jungle", "minecraft:jungle", "minecraft:sparse_jungle"), "minecraft:ruined_portal_jungle", List.of("minecraft:deep_frozen_ocean", "minecraft:deep_cold_ocean", "minecraft:deep_ocean", "minecraft:deep_lukewarm_ocean", "minecraft:frozen_ocean", "minecraft:ocean", "minecraft:cold_ocean", "minecraft:lukewarm_ocean", "minecraft:warm_ocean"), "minecraft:ruined_portal_ocean"), "minecraft:ruined_portal")) // Fix MC-248814, ruined_portal_standard->ruined_portal ++ .put("pillager_outpost", BiomeRemap.create("minecraft:pillager_outpost")) ++ .put("mansion", BiomeRemap.create("minecraft:mansion")) ++ .put("jungle_pyramid", BiomeRemap.create("minecraft:jungle_pyramid")) ++ .put("desert_pyramid", BiomeRemap.create("minecraft:desert_pyramid")) ++ .put("igloo", BiomeRemap.create("minecraft:igloo")) ++ .put("swamp_hut", BiomeRemap.create("minecraft:swamp_hut")) ++ .put("stronghold", BiomeRemap.create("minecraft:stronghold")) ++ .put("monument", BiomeRemap.create("minecraft:monument")) ++ .put("fortress", BiomeRemap.create("minecraft:fortress")) ++ .put("endcity", BiomeRemap.create("minecraft:end_city")) ++ .put("buried_treasure", BiomeRemap.create("minecraft:buried_treasure")) ++ .put("nether_fossil", BiomeRemap.create("minecraft:nether_fossil")) ++ .put("bastion_remnant", BiomeRemap.create("minecraft:bastion_remnant")) ++ .build() ++ ); ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ private static Object2IntOpenHashMap countBiomes(final MapType chunk) { ++ final ListType sections = chunk.getList("sections", ObjectType.MAP); ++ if (sections == null) { ++ return null; ++ } ++ ++ final Object2IntOpenHashMap ret = new Object2IntOpenHashMap<>(); ++ ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ final MapType biomes = section.getMap("biomes"); ++ ++ if (biomes == null) { ++ continue; ++ } ++ ++ final ListType palette = biomes.getList("palette", ObjectType.STRING); ++ ++ if (palette == null) { ++ continue; ++ } ++ ++ for (int k = 0, len2 = palette.size(); k < len2; ++k) { ++ ret.addTo(palette.getString(k), 1); ++ } ++ } ++ ++ return ret; ++ } ++ ++ private static String getStructureConverted(String id, final Object2IntOpenHashMap biomeCount) { ++ id = id.toLowerCase(Locale.ROOT); ++ final BiomeRemap remap = CONVERSION_MAP.get(id); ++ if (remap == null) { ++ throw new IllegalArgumentException("Unknown structure " + id); ++ } ++ if (remap.biomeToNewStructure == null || biomeCount == null) { ++ return remap.dfl; ++ } ++ ++ final Object2IntOpenHashMap remapCount = new Object2IntOpenHashMap<>(); ++ ++ for (final Iterator> iterator = biomeCount.object2IntEntrySet().fastIterator(); iterator.hasNext();) { ++ final Object2IntMap.Entry entry = iterator.next(); ++ final String remappedStructure = remap.biomeToNewStructure.get(entry.getKey()); ++ if (remappedStructure != null) { ++ remapCount.addTo(remappedStructure, entry.getIntValue()); ++ } ++ } ++ ++ String converted = remap.dfl; ++ int maxCount = 0; ++ ++ for (final Iterator> iterator = remapCount.object2IntEntrySet().fastIterator(); iterator.hasNext();) { ++ final Object2IntMap.Entry entry = iterator.next(); ++ final int count = entry.getIntValue(); ++ if (count > maxCount) { ++ maxCount = count; ++ converted = entry.getKey(); ++ } ++ } ++ ++ return converted; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType structures = data.getMap("structures"); ++ if (structures == null || structures.isEmpty()) { ++ return null; ++ } ++ ++ final Object2IntOpenHashMap biomeCounts = countBiomes(data); ++ ++ final MapType starts = structures.getMap("starts"); ++ final MapType references = structures.getMap("References"); ++ ++ if (starts != null) { ++ final MapType newStarts = Types.NBT.createEmptyMap(); ++ structures.setMap("starts", newStarts); ++ ++ for (final String key : starts.keys()) { ++ final MapType value = starts.getMap(key); ++ if ("INVALID".equals(value.getString("id", "INVALID"))) { ++ continue; ++ } ++ ++ final String remapped = getStructureConverted(key, biomeCounts); ++ value.setString("id", remapped); ++ newStarts.setMap(remapped, value); ++ } ++ } ++ ++ // This TRULY is a guess, no idea what biomes the referent has. ++ if (references != null) { ++ final MapType newReferences = Types.NBT.createEmptyMap(); ++ structures.setMap("References", newReferences); ++ for (final String key : references.keys()) { ++ final long[] value = references.getLongs(key); ++ if (value.length == 0) { ++ continue; ++ } ++ ++ newReferences.setLongs(getStructureConverted(key, biomeCounts), value); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private static final class BiomeRemap { ++ ++ public final Map biomeToNewStructure; ++ public final String dfl; ++ ++ private BiomeRemap(final Map biomeToNewStructure, final String dfl) { ++ this.biomeToNewStructure = biomeToNewStructure; ++ this.dfl = dfl; ++ } ++ ++ public static BiomeRemap create(final String newId) { ++ return new BiomeRemap(null, newId); ++ } ++ ++ public static BiomeRemap create(final Map, String> biomeMap, final String newId) { ++ final Map biomeToNewStructure = new HashMap<>(); ++ ++ for (final Map.Entry, String> entry : biomeMap.entrySet()) { ++ final List biomes = entry.getKey(); ++ final String newBiomeStructure = entry.getValue(); ++ ++ for (int i = 0, len = biomes.size(); i < len; ++i) { ++ final String biome = biomes.get(i); ++ if (biomeToNewStructure.putIfAbsent(biome, newBiomeStructure) != null) { ++ throw new IllegalStateException("Duplicate biome remap: " + biome + " -> " + newBiomeStructure + ", but already mapped to " + biomeToNewStructure.get(biome)); ++ } ++ } ++ } ++ ++ return new BiomeRemap(biomeToNewStructure, newId); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java +new file mode 100644 +index 0000000000000000000000000000000000000000..97da66165f3e3788af0dfe667509ca7edb15b0a8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java +@@ -0,0 +1,38 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V3077 { ++ ++ protected static final int VERSION = MCVersions.V1_18_2 + 102; ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final boolean isLightOn = data.getBoolean("isLightOn"); ++ if (isLightOn) { ++ return null; ++ } ++ ++ final ListType sections = data.getList("sections", ObjectType.MAP); ++ if (sections == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ section.remove("BlockLight"); ++ section.remove("SkyLight"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4271eae27756eb5cbad77679dd562e676d644748 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V3078 { ++ ++ protected static final int VERSION = MCVersions.V1_18_2 + 103; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:frog"); ++ registerMob("minecraft:tadpole"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:sculk_shrieker", new GameEventListenerWalker()); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c0cd7cd8c2656713b97f83b7e02b65008b62c297 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V3081 { ++ ++ protected static final int VERSION = MCVersions.V22W11A + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:warden"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:warden", new GameEventListenerWalker()); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ab6ebf4d10842d20c20bcbcc76483d9cfe081862 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java +@@ -0,0 +1,14 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V3082 { ++ ++ protected static final int VERSION = MCVersions.V22W11A + 2; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:chest_boat", new DataWalkerItemLists("Items")); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b133d6a806d571b976d8e96b9ca1e3b6933af20 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V3083 { ++ ++ protected static final int VERSION = MCVersions.V22W12A + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:allay"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:allay", new GameEventListenerWalker()); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java +new file mode 100644 +index 0000000000000000000000000000000000000000..52d8510e00d2373226f35e77db6fc7a893ec0764 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java +@@ -0,0 +1,39 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.util.NamespaceUtil; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V3084 { ++ ++ protected static final int VERSION = MCVersions.V22W12A + 2; ++ ++ protected static final Map GAME_EVENT_RENAMES = new HashMap<>(ImmutableMap.builder() ++ .put("minecraft:block_press", "minecraft:block_activate") ++ .put("minecraft:block_switch", "minecraft:block_activate") ++ .put("minecraft:block_unpress", "minecraft:block_deactivate") ++ .put("minecraft:block_unswitch", "minecraft:block_deactivate") ++ .put("minecraft:drinking_finish", "minecraft:drink") ++ .put("minecraft:elytra_free_fall", "minecraft:elytra_glide") ++ .put("minecraft:entity_damaged", "minecraft:entity_damage") ++ .put("minecraft:entity_dying", "minecraft:entity_die") ++ .put("minecraft:entity_killed", "minecraft:entity_die") ++ .put("minecraft:mob_interact", "minecraft:entity_interact") ++ .put("minecraft:ravager_roar", "minecraft:entity_roar") ++ .put("minecraft:ring_bell", "minecraft:block_change") ++ .put("minecraft:shulker_close", "minecraft:container_close") ++ .put("minecraft:shulker_open", "minecraft:container_open") ++ .put("minecraft:wolf_shaking", "minecraft:entity_shake") ++ .build() ++ ); ++ ++ public static void register() { ++ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.GAME_EVENT_NAME, (final String name) -> { ++ return GAME_EVENT_RENAMES.get(NamespaceUtil.correctNamespace(name)); ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java +new file mode 100644 +index 0000000000000000000000000000000000000000..554df81bb4f1a66bce539b42493f3ea7d4dff153 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java +@@ -0,0 +1,51 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterCriteriaRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterEntityToVariant; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import com.google.common.collect.ImmutableMap; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V3086 { ++ ++ protected static final int VERSION = MCVersions.V22W13A + 1; ++ ++ protected static final Int2ObjectOpenHashMap CAT_ID_CONVERSION = new Int2ObjectOpenHashMap<>(); ++ static { ++ CAT_ID_CONVERSION.defaultReturnValue("minecraft:tabby"); ++ CAT_ID_CONVERSION.put(0, "minecraft:tabby"); ++ CAT_ID_CONVERSION.put(1, "minecraft:black"); ++ CAT_ID_CONVERSION.put(2, "minecraft:red"); ++ CAT_ID_CONVERSION.put(3, "minecraft:siamese"); ++ CAT_ID_CONVERSION.put(4, "minecraft:british"); ++ CAT_ID_CONVERSION.put(5, "minecraft:calico"); ++ CAT_ID_CONVERSION.put(6, "minecraft:persian"); ++ CAT_ID_CONVERSION.put(7, "minecraft:ragdoll"); ++ CAT_ID_CONVERSION.put(8, "minecraft:white"); ++ CAT_ID_CONVERSION.put(9, "minecraft:jellie"); ++ CAT_ID_CONVERSION.put(10, "minecraft:all_black"); ++ } ++ ++ protected static final Map CAT_ADVANCEMENTS_CONVERSION = new HashMap<>(ImmutableMap.builder() ++ .put("textures/entity/cat/tabby.png", "minecraft:tabby") ++ .put("textures/entity/cat/black.png", "minecraft:black") ++ .put("textures/entity/cat/red.png", "minecraft:red") ++ .put("textures/entity/cat/siamese.png", "minecraft:siamese") ++ .put("textures/entity/cat/british_shorthair.png", "minecraft:british") ++ .put("textures/entity/cat/calico.png", "minecraft:calico") ++ .put("textures/entity/cat/persian.png", "minecraft:persian") ++ .put("textures/entity/cat/ragdoll.png", "minecraft:ragdoll") ++ .put("textures/entity/cat/white.png", "minecraft:white") ++ .put("textures/entity/cat/jellie.png", "minecraft:jellie") ++ .put("textures/entity/cat/all_black.png", "minecraft:all_black") ++ .build() ++ ); ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new ConverterEntityToVariant(VERSION, "CatType", CAT_ID_CONVERSION::get)); ++ MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new ConverterCriteriaRename(VERSION, "minecraft:husbandry/complete_catalogue", CAT_ADVANCEMENTS_CONVERSION::get)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8cc7cadb921d52ebb5b8ed25078145536db5e7b5 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java +@@ -0,0 +1,22 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterEntityToVariant; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++ ++public final class V3087 { ++ ++ protected static final int VERSION = MCVersions.V22W13A + 2; ++ ++ protected static Int2ObjectOpenHashMap FROG_ID_CONVERSION = new Int2ObjectOpenHashMap<>(); ++ static { ++ FROG_ID_CONVERSION.put(0, "minecraft:temperate"); ++ FROG_ID_CONVERSION.put(1, "minecraft:warm"); ++ FROG_ID_CONVERSION.put(2, "minecraft:cold"); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:frog", new ConverterEntityToVariant(VERSION, "Variant", FROG_ID_CONVERSION::get)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fb1af7826dd01fd6f5cfe2ad11ba63b934675d31 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java +@@ -0,0 +1,23 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.chunk.ConverterAddBlendingData; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++ ++public final class V3088 { ++ ++ // this class originally targeted 3079 but was changed to target a later version without changing the converter, zero clue why ++ // this class then targeted 3088 but was changed to target 3441 ++ // to maintain integrity of the data version, I chose to extract the converter to a separate class and use it in both versions ++ // the reason it is important to never change old converters once released is that it creates _two_ versions under the same id. ++ // Consider the case where a user force upgrades their world, but does not load the chunk. Then, consider the case where ++ // the user does not force upgrade their world. Then, Mojang comes along and makes a decision like this and now both ++ // players load the chunk - they went through a different conversion process, which ultimately creates two versions. ++ // Unfortunately this fix doesn't exactly resolve it, as anyone running Mojang's converters will now be different ++ // from DataConverter's. It's broadly a dumb situation all around that could be avoided if Mojang wasn't being careless here. ++ protected static final int VERSION = MCVersions.V22W14A; ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new ConverterAddBlendingData(VERSION)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b3250a0b5ae2ab0aa5fffaace882052388861fd8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java +@@ -0,0 +1,23 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3090 { ++ ++ protected static final int VERSION = MCVersions.V22W15A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:painting", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ RenameHelper.renameSingle(data, "Motive", "variant"); ++ RenameHelper.renameSingle(data, "Facing", "facing"); ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8354c85fc4d92f36555c7de9dc0dffd1da05529a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java +@@ -0,0 +1,22 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3093 { ++ ++ protected static final int VERSION = MCVersions.V22W17A; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:goat", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.setBoolean("HasLeftHorn", true); ++ data.setBoolean("HasRightHorn", true); ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java +new file mode 100644 +index 0000000000000000000000000000000000000000..39540b5f76af1c7d51a51db9d711f32a3c7f624c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java +@@ -0,0 +1,42 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3094 { ++ ++ public static final int VERSION = MCVersions.V22W17A + 1; ++ ++ private static final String[] SOUND_VARIANT_TO_INSTRUMENT = new String[] { ++ "minecraft:ponder_goat_horn", ++ "minecraft:sing_goat_horn", ++ "minecraft:seek_goat_horn", ++ "minecraft:feel_goat_horn", ++ "minecraft:admire_goat_horn", ++ "minecraft:call_goat_horn", ++ "minecraft:yearn_goat_horn", ++ "minecraft:dream_goat_horn" ++ }; ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:goat_horn", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ ++ if (tag == null) { ++ return null; ++ } ++ ++ final int soundVariant = tag.getInt("SoundVariant"); ++ tag.remove("SoundVariant"); ++ ++ tag.setString("instrument", SOUND_VARIANT_TO_INSTRUMENT[soundVariant < 0 || soundVariant >= SOUND_VARIANT_TO_INSTRUMENT.length ? 0 : soundVariant]); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d5ac17b59c0dcc9baaeff022ecbf827c237cf9d6 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java +@@ -0,0 +1,61 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterCriteriaRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterEntityVariantRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.poi.ConverterPoiDelete; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++ ++public final class V3097 { ++ ++ private static final int VERSION = MCVersions.V22W19A + 1; ++ ++ public static void register() { ++ final DataConverter, MapType> removeFilteredBookText = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ tag.remove("filtered_title"); ++ tag.remove("filtered_pages"); ++ ++ return null; ++ } ++ }; ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:writable_book", removeFilteredBookText); ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:written_book", removeFilteredBookText); ++ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:sign", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.remove("FilteredText1"); ++ data.remove("FilteredText2"); ++ data.remove("FilteredText3"); ++ data.remove("FilteredText4"); ++ ++ return null; ++ } ++ }); ++ ++ final Map britishRenamer = new HashMap<>(Map.of( ++ "minecraft:british", "minecraft:british_shorthair" ++ )); ++ final Set poiRemove = new HashSet<>(Set.of( ++ "minecraft:unemployed", ++ "minecraft:nitwit" ++ )); ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new ConverterEntityVariantRename(VERSION, britishRenamer::get)); ++ MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new ConverterCriteriaRename(VERSION, "minecraft:husbandry/complete_catalogue", britishRenamer::get)); ++ MCTypeRegistry.POI_CHUNK.addStructureConverter(new ConverterPoiDelete(VERSION, poiRemove::contains)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java +new file mode 100644 +index 0000000000000000000000000000000000000000..381b49f2c50d46e52f7f9c8f6baede4e72eb343d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java +@@ -0,0 +1,27 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3108 { ++ ++ private static final int VERSION = MCVersions.V1_19_1_PRE1 + 1; ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType context = data.getMap("__context"); ++ if ("minecraft:overworld".equals(context == null ? null : context.getString("dimension"))) { ++ return null; ++ } ++ ++ data.remove("blending_data"); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java +new file mode 100644 +index 0000000000000000000000000000000000000000..24f661419cd08caa4f6d8b3ea66f0d484d07b5b9 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java +@@ -0,0 +1,33 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3201 { ++ ++ private static final int VERSION = MCVersions.V1_19_2 + 81; ++ ++ public static void register() { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { ++ private static void fixList(final MapType data, final String target) { ++ if (data == null) { ++ return; ++ } ++ final String curr = data.getString(target); ++ if (curr == null) { ++ return; ++ } ++ data.setString(target, curr.replace("\"programer_art\"", "\"programmer_art\"")); ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ fixList(data, "resourcePacks"); ++ fixList(data, "incompatibleResourcePacks"); ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java +new file mode 100644 +index 0000000000000000000000000000000000000000..84572b5fb3930e005acb4ea3bce0441267247a40 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V3203 { ++ ++ private static final int VERSION = MCVersions.V1_19_2 + 83; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:camel"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dabaf437021ce7309ca005c21afc82d88d6b04c6 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java +@@ -0,0 +1,14 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V3204 { ++ ++ private static final int VERSION = MCVersions.V1_19_2 + 84; ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:chiseled_bookshelf", new DataWalkerItemLists("Items")); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f3a109ff01a96a750967e8becdc2a3e20b71b6dd +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java +@@ -0,0 +1,16 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenSpawnEgg; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++ ++public final class V3209 { ++ ++ private static final int VERSION = MCVersions.V22W45A + 1; ++ ++ public static void register() { ++ // Note: This converter reads entity id from its sub data, but we need no breakpoint because entity ids are not ++ // remapped this version ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:pig_spawn_egg", new ConverterFlattenSpawnEgg(VERSION, 0)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b01ae34d2e238f217eb8de8b2d3502e959b5b3a7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java +@@ -0,0 +1,28 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3214 { ++ ++ private static final int VERSION = MCVersions.V1_19_3_PRE3 + 1; ++ ++ public static void register() { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String value = data.getString("ao"); ++ ++ if ("0".equals(value)) { ++ data.setString("ao", "false"); ++ } else if ("1".equals(value) || "2".equals(value)) { ++ data.setString("ao", "true"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7bda48947850559e5ccc92ea504a64996005f7d6 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3319 { ++ ++ private static final int VERSION = MCVersions.V1_19_3 + 101; ++ ++ public static void register() { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.setBoolean("onboardAccessibility", false); ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5520dffc15f76a26fca3a26568b85a577cac255c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java +@@ -0,0 +1,82 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++import java.util.HashSet; ++import java.util.Set; ++ ++public final class V3322 { ++ ++ private static final int VERSION = MCVersions.V23W04A + 1; ++ ++ private static final Set EFFECT_ITEM_TYPES = new HashSet<>( ++ Set.of( ++ "minecraft:potion", ++ "minecraft:splash_potion", ++ "minecraft:lingering_potion", ++ "minecraft:tipped_arrow" ++ ) ++ ); ++ ++ private static void updateEffectList(final MapType root, final String path) { ++ if (root == null) { ++ return; ++ } ++ ++ final ListType effects = root.getList(path, ObjectType.MAP); ++ ++ if (effects == null) { ++ return; ++ } ++ ++ for (int i = 0, len = effects.size(); i < len; ++i) { ++ final MapType data = effects.getMap(i); ++ final MapType factorData = data.getMap("FactorCalculationData"); ++ if (factorData == null) { ++ continue; ++ } ++ ++ final int timestamp = factorData.getInt("effect_changed_timestamp", -1); ++ factorData.remove("effect_changed_timestamp"); ++ ++ final int duration = data.getInt("Duration", -1); ++ ++ final int ticksActive = timestamp - duration; ++ factorData.setInt("ticks_active", ticksActive); ++ } ++ } ++ ++ public static void register() { ++ final DataConverter, MapType> entityEffectFix = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateEffectList(data, "Effects"); ++ updateEffectList(data, "ActiveEffects"); ++ updateEffectList(data, "CustomPotionEffects"); ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.PLAYER.addStructureConverter(entityEffectFix); ++ MCTypeRegistry.ENTITY.addStructureConverter(entityEffectFix); ++ ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String id = data.getString("id"); ++ if (!EFFECT_ITEM_TYPES.contains(id)) { ++ return null; ++ } ++ ++ updateEffectList(data.getMap("tag"), "CustomPotionEffects"); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4d451d496232a8f15f8daf3a2e56989155ff36a5 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java +@@ -0,0 +1,17 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++ ++public final class V3325 { ++ ++ private static final int VERSION = MCVersions.V23W05A + 2; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:item_display", new DataWalkerItems("item")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:block_display", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "block_state")); ++ // text_display is a simple entity ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e9decfa3a1f819354d3b3e6a1cb09b913609fe4d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V3326 { ++ ++ private static final int VERSION = MCVersions.V23W06A; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:sniffer"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8f12da18cedc50adedf08a4e12428e7e49788886 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java +@@ -0,0 +1,17 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerListPaths; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++ ++public final class V3327 { ++ ++ private static final int VERSION = MCVersions.V23W06A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:decorated_pot", new DataWalkerListPaths<>(MCTypeRegistry.ITEM_NAME, "shards")); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:decorated_pot", new DataWalkerItems("item")); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:suspicious_sand", new DataWalkerItems("item")); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java +new file mode 100644 +index 0000000000000000000000000000000000000000..67218286c8e7896641b331118e7794bb6a6c835e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java +@@ -0,0 +1,13 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++ ++public final class V3328 { ++ ++ private static final int VERSION = MCVersions.V23W06A + 2; ++ ++ public static void register() { ++ // registers simple entity "minecraft:interaction" ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b4584cb2b99abd8739f815c741ea2424fe583ac8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java +@@ -0,0 +1,45 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.tileentity.ConverterAbstractTileEntityRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V3438 { ++ ++ public static final int VERSION = MCVersions.V1_19_4 + 101; ++ ++ public static void register() { ++ // brushable block rename ++ MCTypeRegistry.TILE_ENTITY.copyWalkers(VERSION,"minecraft:suspicious_sand", "minecraft:brushable_block"); ++ ++ ConverterAbstractTileEntityRename.register(VERSION, new HashMap<>(Map.of( ++ "minecraft:suspicious_sand", "minecraft:brushable_block" ++ ))::get); ++ ++ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:brushable_block", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ RenameHelper.renameSingle(data, "loot_table", "LootTable"); ++ RenameHelper.renameSingle(data, "loot_table_seed", "LootTableSeed"); ++ return null; ++ } ++ }); ++ ++ ConverterAbstractItemRename.register(VERSION, new HashMap<>( ++ Map.of( ++ "minecraft:pottery_shard_archer", "minecraft:archer_pottery_shard", ++ "minecraft:pottery_shard_prize", "minecraft:prize_pottery_shard", ++ "minecraft:pottery_shard_arms_up", "minecraft:arms_up_pottery_shard", ++ "minecraft:pottery_shard_skull", "minecraft:skull_pottery_shard" ++ ) ++ )::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java +new file mode 100644 +index 0000000000000000000000000000000000000000..301f8582a38fc130bf48be785b7368ac5425e510 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java +@@ -0,0 +1,94 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3439 { ++ ++ private static final int VERSION = MCVersions.V1_19_4 + 102; ++ ++ public static void register() { ++ final DataConverter, MapType> signTileUpdater = new DataConverter<>(VERSION) { ++ private static final String DEFAULT_COLOR = "black"; ++ ++ private static ListType migrateToList(final MapType root, final String prefix) { ++ if (root == null) { ++ return null; ++ } ++ ++ final ListType ret = root.getTypeUtil().createEmptyList(); ++ ++ ret.addString(root.getString(prefix.concat("1"), ComponentUtils.EMPTY)); ++ ret.addString(root.getString(prefix.concat("2"), ComponentUtils.EMPTY)); ++ ret.addString(root.getString(prefix.concat("3"), ComponentUtils.EMPTY)); ++ ret.addString(root.getString(prefix.concat("4"), ComponentUtils.EMPTY)); ++ ++ return ret; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ // front text ++ final MapType frontText = data.getTypeUtil().createEmptyMap(); ++ data.setMap("front_text", frontText); ++ ++ final ListType frontMessages = migrateToList(data, "Text"); ++ frontText.setList("messages", frontMessages); ++ ++ ListType frontFilteredMessages = null; ++ ++ for (int i = 0; i < 4; ++i) { ++ final String filtered = data.getString("FilteredText" + i); ++ if (filtered == null) { ++ if (frontFilteredMessages != null) { ++ frontFilteredMessages.addString(frontMessages.getString(i)); ++ } ++ continue; ++ } ++ ++ if (frontFilteredMessages == null) { ++ frontFilteredMessages = data.getTypeUtil().createEmptyList(); ++ for (int k = 0; k < i; ++k) { ++ frontFilteredMessages.addString(frontMessages.getString(k)); ++ } ++ } ++ ++ frontFilteredMessages.addString(filtered); ++ } ++ ++ if (frontFilteredMessages != null) { ++ frontText.setList("filtered_messages", frontFilteredMessages); ++ } ++ ++ frontText.setString("color", data.getString("Color", DEFAULT_COLOR)); ++ frontText.setBoolean("has_glowing_text", data.getBoolean("GlowingText", false)); ++ frontText.setBoolean("_filtered_correct", true); ++ ++ // back text ++ final MapType backText = data.getTypeUtil().createEmptyMap(); ++ data.setMap("back_text", backText); ++ ++ final ListType blankMessages = data.getTypeUtil().createEmptyList(); ++ backText.setList("messages", blankMessages); ++ ++ for (int i = 0; i < 4; ++i) { ++ blankMessages.addString(ComponentUtils.EMPTY); ++ } ++ ++ backText.setString("color", DEFAULT_COLOR); ++ backText.setBoolean("has_glowing_text", false); ++ ++ // misc ++ data.setBoolean("is_waxed", false); ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:sign", signTileUpdater); ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:hanging_sign", signTileUpdater); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f4209b03ec7c126aa728704d58dc0399fd89c698 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java +@@ -0,0 +1,27 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.leveldat.ConverterRemoveFeatureFlag; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.util.NamespaceUtil; ++ ++import java.util.Arrays; ++import java.util.HashSet; ++ ++public final class V3440 { ++ ++ private static final int VERSION = MCVersions.V1_19_4 + 103; ++ ++ public static void register() { ++ // Note: MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST is namespaced string ++ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST, (final String in) -> { ++ return "minecraft:overworld_update_1_20".equals(NamespaceUtil.correctNamespace(in)) ? "minecraft:overworld" : null; ++ }); ++ MCTypeRegistry.LEVEL.addStructureConverter(new ConverterRemoveFeatureFlag(VERSION, new HashSet<>( ++ Arrays.asList( ++ "minecraft:update_1_20" ++ ) ++ ))); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e37f49012c960301273412ae79ca4e69d9d1ceb9 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java +@@ -0,0 +1,15 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.chunk.ConverterAddBlendingData; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++ ++public final class V3441 { ++ ++ private static final int VERSION = MCVersions.V1_19_4 + 104; ++ ++ public static void register() { ++ // See V3088 for why this converter is duplicated here and in V3088 ++ MCTypeRegistry.CHUNK.addStructureConverter(new ConverterAddBlendingData(VERSION)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f5a7b72755b53d4e406c95f5ea5857d7f94f19ad +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java +@@ -0,0 +1,47 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V3447 { ++ ++ private static final int VERSION = MCVersions.V23W14A + 2; ++ ++ public static void register() { ++ final String[] targets = new String[] { ++ "minecraft:angler_pottery_shard", ++ "minecraft:archer_pottery_shard", ++ "minecraft:arms_up_pottery_shard", ++ "minecraft:blade_pottery_shard", ++ "minecraft:brewer_pottery_shard", ++ "minecraft:burn_pottery_shard", ++ "minecraft:danger_pottery_shard", ++ "minecraft:explorer_pottery_shard", ++ "minecraft:friend_pottery_shard", ++ "minecraft:heart_pottery_shard", ++ "minecraft:heartbreak_pottery_shard", ++ "minecraft:howl_pottery_shard", ++ "minecraft:miner_pottery_shard", ++ "minecraft:mourner_pottery_shard", ++ "minecraft:plenty_pottery_shard", ++ "minecraft:prize_pottery_shard", ++ "minecraft:sheaf_pottery_shard", ++ "minecraft:shelter_pottery_shard", ++ "minecraft:skull_pottery_shard", ++ "minecraft:snort_pottery_shard" ++ }; ++ // shard->sherd ++ final Map rename = new HashMap<>(targets.length); ++ ++ for (final String target : targets) { ++ final String replace = target.replace("_pottery_shard", "_pottery_sherd"); ++ if (rename.put(target, replace) != null) { ++ throw new IllegalArgumentException("Duplicate target " + target); ++ } ++ } ++ ++ ConverterAbstractItemRename.register(VERSION, rename::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6861a732d551b4ee0a12eb1321a12f86d352ad0a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java +@@ -0,0 +1,26 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerListPaths; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3448 { ++ ++ private static final int VERSION = MCVersions.V23W14A + 3; ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:decorated_pot", new DataWalkerListPaths<>(MCTypeRegistry.ITEM_NAME, "sherds")); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:decorated_pot", new DataWalkerItems("item")); ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:decorated_pot", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ RenameHelper.renameSingle(data, "shards", "sherds"); ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java +new file mode 100644 +index 0000000000000000000000000000000000000000..27d7214108f82baaafad6e47b2d0c19282899b4b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.chunk.ConverterRenameStatus; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V3450 { ++ ++ private static final int VERSION = MCVersions.V23W16A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new ConverterRenameStatus(VERSION, new HashMap<>( ++ Map.of( ++ "minecraft:liquid_carvers", "minecraft:carvers", ++ "minecraft:heightmaps", "minecraft:spawn" ++ ) ++ )::get)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bec25939a78141d989414a5d4f33ce134f347bb5 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java +@@ -0,0 +1,36 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V3451 { ++ ++ private static final int VERSION = MCVersions.V23W16A + 2; ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.remove("isLightOn"); ++ ++ final ListType sections = data.getList("sections", ObjectType.MAP); ++ if (sections == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ section.remove("BlockLight"); ++ section.remove("SkyLight"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java +new file mode 100644 +index 0000000000000000000000000000000000000000..86509b2fa3c83dc485776d36b7bc2944b1f9a0b5 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java +@@ -0,0 +1,34 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3459 { ++ ++ private static final int VERSION = MCVersions.V1_20_PRE5 + 1; ++ ++ public static void register() { ++ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.hasKey("DragonFight")) { ++ return null; ++ } ++ ++ final MapType dimensionData = data.getMap("DimensionData"); ++ if (dimensionData == null) { ++ return null; ++ } ++ ++ final MapType endData = dimensionData.getMap("1"); ++ if (endData != null) { ++ data.setMap("DragonFight", endData.getMap("DragonFight", endData.getTypeUtil().createEmptyMap()).copy()); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2421a884780d29a1f7776db8cc1f6fd7316fd0de +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java +@@ -0,0 +1,91 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V3564 { ++ ++ private static final int VERSION = MCVersions.V1_20_1 + 99; ++ ++ public static void register() { ++ final DataConverter, MapType> converter = new DataConverter<>(VERSION) { ++ ++ private static final String[] LEGACY_FIELDS = new String[] { ++ "Text1", ++ "Text2", ++ "Text3", ++ "Text4", ++ ++ "FilteredText1", ++ "FilteredText2", ++ "FilteredText3", ++ "FilteredText4", ++ ++ "Color", ++ ++ "GlowingText" ++ }; ++ ++ ++ private static void updateText(final MapType text) { ++ if (text == null) { ++ return; ++ } ++ ++ if (text.getBoolean("_filtered_correct", false)) { ++ text.remove("_filtered_correct"); ++ return; ++ } ++ ++ final ListType filteredMessages = text.getList("filtered_messages", ObjectType.STRING); ++ ++ if (filteredMessages == null || filteredMessages.size() == 0) { ++ return; ++ } ++ ++ // should treat null here as empty list ++ final ListType messages = text.getList("messages", ObjectType.STRING); ++ ++ final ListType newFilteredList = filteredMessages.getTypeUtil().createEmptyList(); ++ boolean newFilteredIsEmpty = true; ++ ++ for (int i = 0, len = filteredMessages.size(); i < len; ++i) { ++ final String filtered = filteredMessages.getString(i); ++ final String message = messages != null && i < messages.size() ? messages.getString(i) : ComponentUtils.EMPTY; ++ ++ final String newFiltered = ComponentUtils.EMPTY.equals(filtered) ? message : filtered; ++ ++ newFilteredList.addString(newFiltered); ++ ++ newFilteredIsEmpty = newFilteredIsEmpty && ComponentUtils.EMPTY.equals(newFiltered); ++ } ++ ++ if (newFilteredIsEmpty) { ++ text.remove("filtered_messages"); ++ } else { ++ text.setList("filtered_messages", newFilteredList); ++ } ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateText(data.getMap("front_text")); ++ updateText(data.getMap("back_text")); ++ ++ for (final String toRemove : LEGACY_FIELDS) { ++ data.remove(toRemove); ++ } ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:sign", converter); ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:hanging_sign", converter); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d63c4c3a53bf9cd67acc5515e9176b972cd13ce7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java +@@ -0,0 +1,30 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3565 { ++ ++ private static final int VERSION = MCVersions.V1_20_1 + 100; ++ ++ public static void register() { ++ MCTypeRegistry.SAVED_DATA_RANDOM_SEQUENCES.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { ++ final MapType oldData = root.getMap("data"); ++ if (oldData == null) { ++ return null; ++ } ++ ++ final MapType newData = root.getTypeUtil().createEmptyMap(); ++ root.setMap("data", newData); ++ ++ newData.setMap("sequences", oldData); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2501759bc1c0313c0556d4f75a0c4a3d3b6f5449 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java +@@ -0,0 +1,56 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V3566 { ++ ++ private static final int VERSION = MCVersions.V1_20_1 + 101; ++ ++ public static void register() { ++ MCTypeRegistry.SAVED_DATA_SCOREBOARD.addStructureConverter(new DataConverter<>(VERSION) { ++ ++ private static final Map SLOT_RENAMES = new HashMap<>( ++ ImmutableMap.builder() ++ .put("slot_0", "list") ++ .put("slot_1", "sidebar") ++ .put("slot_2", "below_name") ++ .put("slot_3", "sidebar.team.black") ++ .put("slot_4", "sidebar.team.dark_blue") ++ .put("slot_5", "sidebar.team.dark_green") ++ .put("slot_6", "sidebar.team.dark_aqua") ++ .put("slot_7", "sidebar.team.dark_red") ++ .put("slot_8", "sidebar.team.dark_purple") ++ .put("slot_9", "sidebar.team.gold") ++ .put("slot_10", "sidebar.team.gray") ++ .put("slot_11", "sidebar.team.dark_gray") ++ .put("slot_12", "sidebar.team.blue") ++ .put("slot_13", "sidebar.team.green") ++ .put("slot_14", "sidebar.team.aqua") ++ .put("slot_15", "sidebar.team.red") ++ .put("slot_16", "sidebar.team.light_purple") ++ .put("slot_17", "sidebar.team.yellow") ++ .put("slot_18", "sidebar.team.white") ++ .build() ++ ); ++ ++ @Override ++ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { ++ final MapType data = root.getMap("data"); ++ if (data == null) { ++ return null; ++ } ++ ++ RenameHelper.renameKeys(data.getMap("DisplaySlots"), SLOT_RENAMES::get); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java +new file mode 100644 +index 0000000000000000000000000000000000000000..311a57529c5f95ce48631b48fefb4ebae401b3f7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java +@@ -0,0 +1,243 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++ ++public final class V3568 { ++ ++ private static final int VERSION = MCVersions.V23W31A + 1; ++ ++ private static final String[] EFFECT_ID_MAP = new String[34]; ++ static { ++ EFFECT_ID_MAP[1] = "minecraft:speed"; ++ EFFECT_ID_MAP[2] = "minecraft:slowness"; ++ EFFECT_ID_MAP[3] = "minecraft:haste"; ++ EFFECT_ID_MAP[4] = "minecraft:mining_fatigue"; ++ EFFECT_ID_MAP[5] = "minecraft:strength"; ++ EFFECT_ID_MAP[6] = "minecraft:instant_health"; ++ EFFECT_ID_MAP[7] = "minecraft:instant_damage"; ++ EFFECT_ID_MAP[8] = "minecraft:jump_boost"; ++ EFFECT_ID_MAP[9] = "minecraft:nausea"; ++ EFFECT_ID_MAP[10] = "minecraft:regeneration"; ++ EFFECT_ID_MAP[11] = "minecraft:resistance"; ++ EFFECT_ID_MAP[12] = "minecraft:fire_resistance"; ++ EFFECT_ID_MAP[13] = "minecraft:water_breathing"; ++ EFFECT_ID_MAP[14] = "minecraft:invisibility"; ++ EFFECT_ID_MAP[15] = "minecraft:blindness"; ++ EFFECT_ID_MAP[16] = "minecraft:night_vision"; ++ EFFECT_ID_MAP[17] = "minecraft:hunger"; ++ EFFECT_ID_MAP[18] = "minecraft:weakness"; ++ EFFECT_ID_MAP[19] = "minecraft:poison"; ++ EFFECT_ID_MAP[20] = "minecraft:wither"; ++ EFFECT_ID_MAP[21] = "minecraft:health_boost"; ++ EFFECT_ID_MAP[22] = "minecraft:absorption"; ++ EFFECT_ID_MAP[23] = "minecraft:saturation"; ++ EFFECT_ID_MAP[24] = "minecraft:glowing"; ++ EFFECT_ID_MAP[25] = "minecraft:levitation"; ++ EFFECT_ID_MAP[26] = "minecraft:luck"; ++ EFFECT_ID_MAP[27] = "minecraft:unluck"; ++ EFFECT_ID_MAP[28] = "minecraft:slow_falling"; ++ EFFECT_ID_MAP[29] = "minecraft:conduit_power"; ++ EFFECT_ID_MAP[30] = "minecraft:dolphins_grace"; ++ EFFECT_ID_MAP[31] = "minecraft:bad_omen"; ++ EFFECT_ID_MAP[32] = "minecraft:hero_of_the_village"; ++ EFFECT_ID_MAP[33] = "minecraft:darkness"; ++ } ++ private static final Set EFFECT_ITEMS = ++ new HashSet<>( ++ Set.of( ++ "minecraft:potion", ++ "minecraft:splash_potion", ++ "minecraft:lingering_potion", ++ "minecraft:tipped_arrow" ++ ) ++ ); ++ ++ private static String readLegacyEffect(final MapType data, final String path) { ++ final Number id = data.getNumber(path); ++ if (id == null) { ++ return null; ++ } ++ ++ final int castedId = id.intValue(); ++ return castedId >= 0 && castedId < EFFECT_ID_MAP.length ? EFFECT_ID_MAP[castedId] : null; ++ } ++ ++ private static void convertLegacyEffect(final MapType data, final String legacyPath, final String newPath) { ++ final Number id = data.getNumber(legacyPath); ++ data.remove(legacyPath); ++ ++ if (id == null) { ++ return; ++ } ++ ++ final int castedId = id.intValue(); ++ final String newId = castedId >= 0 && castedId < EFFECT_ID_MAP.length ? EFFECT_ID_MAP[castedId] : null; ++ ++ if (newId == null) { ++ return; ++ } ++ ++ data.setString(newPath, newId); ++ } ++ ++ private static final Map MOB_EFFECT_RENAMES = new HashMap<>(); ++ static { ++ MOB_EFFECT_RENAMES.put("Ambient", "ambient"); ++ MOB_EFFECT_RENAMES.put("Amplifier", "amplifier"); ++ MOB_EFFECT_RENAMES.put("Duration", "duration"); ++ MOB_EFFECT_RENAMES.put("ShowParticles", "show_particles"); ++ MOB_EFFECT_RENAMES.put("ShowIcon", "show_icon"); ++ MOB_EFFECT_RENAMES.put("FactorCalculationData", "factor_calculation_data"); ++ MOB_EFFECT_RENAMES.put("HiddenEffect", "hidden_effect"); ++ } ++ ++ private static void convertMobEffect(final MapType mobEffect) { ++ if (mobEffect == null) { ++ return; ++ } ++ ++ convertLegacyEffect(mobEffect, "Id", "id"); ++ ++ for (final Map.Entry rename : MOB_EFFECT_RENAMES.entrySet()) { ++ RenameHelper.renameSingle(mobEffect, rename.getKey(), rename.getValue()); ++ } ++ ++ convertMobEffect(mobEffect.getMap("hidden_effect")); ++ } ++ ++ private static void convertMobEffectList(final MapType data, final String oldPath, final String newPath) { ++ final ListType effects = data.getList(oldPath, ObjectType.MAP); ++ if (effects == null) { ++ return; ++ } ++ ++ for (int i = 0, len = effects.size(); i < len; ++i) { ++ convertMobEffect(effects.getMap(i)); ++ } ++ ++ data.remove(oldPath); ++ data.setList(newPath, effects); ++ } ++ ++ private static void removeAndSet(final MapType data, final String toRemovePath, ++ final String toSetPath, final Object toSet) { ++ data.remove(toRemovePath); ++ if (toSet != null) { ++ data.setGeneric(toSetPath, toSet); ++ } ++ } ++ ++ private static void updateSuspiciousStew(final MapType from, final MapType into) { ++ removeAndSet(into, "EffectId", "id", readLegacyEffect(from, "EffectId")); ++ removeAndSet(into, "EffectDuration", "duration", from.getGeneric("EffectDuration")); ++ } ++ ++ public static void register() { ++ final DataConverter, MapType> beaconConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ convertLegacyEffect(data, "Primary", "primary_effect"); ++ convertLegacyEffect(data, "Secondary", "secondary_effect"); ++ ++ return null; ++ } ++ }; ++ ++ final DataConverter, MapType> mooshroomConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType newEffect = data.getTypeUtil().createEmptyMap(); ++ updateSuspiciousStew(data, newEffect); ++ ++ data.remove("EffectId"); ++ data.remove("EffectDuration"); ++ ++ if (!newEffect.isEmpty()) { ++ final ListType stewEffects = data.getTypeUtil().createEmptyList(); ++ data.setList("stew_effects", stewEffects); ++ ++ stewEffects.addMap(newEffect); ++ } ++ ++ return null; ++ } ++ }; ++ final DataConverter, MapType> arrowConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ convertMobEffectList(data, "CustomPotionEffects", "custom_potion_effects"); ++ return null; ++ } ++ }; ++ final DataConverter, MapType> areaEffectCloudConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ convertMobEffectList(data, "Effects", "effects"); ++ return null; ++ } ++ }; ++ final DataConverter, MapType> livingEntityConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ convertMobEffectList(data, "ActiveEffects", "active_effects"); ++ return null; ++ } ++ }; ++ ++ final DataConverter, MapType> itemConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { ++ final String id = root.getString("id"); ++ ++ final MapType tag = root.getMap("tag"); ++ ++ if (tag == null) { ++ return null; ++ } ++ ++ if ("minecraft:suspicious_stew".equals(id)) { ++ RenameHelper.renameSingle(tag, "Effects", "effects"); ++ ++ final ListType effects = tag.getList("effects", ObjectType.MAP); ++ ++ if (effects != null) { ++ for (int i = 0, len = effects.size(); i < len; ++i) { ++ final MapType effect = effects.getMap(i); ++ updateSuspiciousStew(effect, effect); ++ } ++ } ++ ++ return null; ++ } ++ ++ if (EFFECT_ITEMS.contains(id)) { ++ convertMobEffectList(tag, "CustomPotionEffects", "custom_potion_effects"); ++ return null; ++ } ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:beacon", beaconConverter); ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:mooshroom", mooshroomConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:arrow", arrowConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:area_effect_cloud", areaEffectCloudConverter); ++ MCTypeRegistry.ENTITY.addStructureConverter(livingEntityConverter); ++ ++ MCTypeRegistry.PLAYER.addStructureConverter(livingEntityConverter); ++ ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(itemConverter); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java +new file mode 100644 +index 0000000000000000000000000000000000000000..43cb10e3f13f9a2ffd82af70c7cae3b845cfc413 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java +@@ -0,0 +1,14 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V3682 { ++ ++ private static final int VERSION = MCVersions.V23W41A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:crafter", new DataWalkerItemLists("Items")); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d52a5a17da2c20cdc1b39f6ba6b1dbfbb9a21a0f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java +@@ -0,0 +1,31 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3683 { ++ ++ private static final int VERSION = MCVersions.V23W41A + 2; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:tnt", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ RenameHelper.renameSingle(data, "Fuse", "fuse"); ++ ++ final MapType defaultState = data.getTypeUtil().createEmptyMap(); ++ data.setMap("block_state", defaultState); ++ ++ defaultState.setString("Name", "minecraft:tnt"); ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:tnt", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "block_state")); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e0eed7f2c636e5ff65ad4c8c49c0111c6f1c04f2 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java +@@ -0,0 +1,62 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++ ++public final class V3685 { ++ ++ private static final int VERSION = MCVersions.V23W42A + 1; ++ ++ private static String getType(final MapType arrow) { ++ return "minecraft:empty".equals(arrow.getString("Potion", "minecraft:empty")) ? "minecraft:arrow" : "minecraft:tipped_arrow"; ++ } ++ ++ private static MapType createItem(final TypeUtil util, final String id, final int count) { ++ final MapType ret = util.createEmptyMap(); ++ ++ ret.setString("id", id); ++ ret.setInt("Count", count); ++ ++ return ret; ++ } ++ ++ private static void registerArrowEntity(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); ++ // new: item ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItems("item")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:trident", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ RenameHelper.renameSingle(data, "Trident", "item"); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:arrow", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.setMap("item", createItem(data.getTypeUtil(), getType(data), 1)); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:spectral_arrow", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.setMap("item", createItem(data.getTypeUtil(), "minecraft:spectral_arrow", 1)); ++ return null; ++ } ++ }); ++ ++ registerArrowEntity("minecraft:trident"); ++ registerArrowEntity("minecraft:spectral_arrow"); ++ registerArrowEntity("minecraft:arrow"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java +new file mode 100644 +index 0000000000000000000000000000000000000000..427841b46b4fbb993aee6d8670d42eaf91f41793 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java +@@ -0,0 +1,37 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V3689 { ++ ++ private static final int VERSION = MCVersions.V23W44A + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:breeze"); ++ // minecraft:wind_charge is a simple entity ++ ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:trial_spawner", (final MapType data, final long fromVersion, final long toVersion) -> { ++ final ListType spawnPotentials = data.getList("spawn_potentials", ObjectType.MAP); ++ if (spawnPotentials != null) { ++ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, spawnPotentials.getMap(i).getMap("data"), "entity", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "spawn_data", fromVersion, toVersion); ++ return null; ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d49be320a8bc5f84ec1e0392257eede1a673bb27 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java +@@ -0,0 +1,23 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V3692 { ++ ++ private static final int VERSION = MCVersions.V23W46A + 1; ++ ++ private static final Map GRASS_RENAME = new HashMap<>( ++ Map.of( ++ "minecraft:grass", "minecraft:short_grass" ++ ) ++ ); ++ ++ public static void register() { ++ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, GRASS_RENAME::get); ++ ConverterAbstractItemRename.register(VERSION, GRASS_RENAME::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6ab2bf99d72983fc2742a1f6f2f7fa671611526d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V501 { ++ ++ protected static final int VERSION = MCVersions.V16W20A; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("PolarBear"); ++ } ++ ++ private V501() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java +new file mode 100644 +index 0000000000000000000000000000000000000000..febeab68a5eec229ecca4f9e7b82c9ca99b3dbe1 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java +@@ -0,0 +1,46 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.concurrent.ThreadLocalRandom; ++ ++public final class V502 { ++ ++ protected static final int VERSION = MCVersions.V16W20A + 1; ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, (final String name) -> { ++ return "minecraft:cooked_fished".equals(name) ? "minecraft:cooked_fish" : null; ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("Zombie", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!data.getBoolean("IsVillager")) { ++ return null; ++ } ++ ++ data.remove("IsVillager"); ++ ++ if (data.hasKey("ZombieType")) { ++ return null; ++ } ++ ++ int type = data.getInt("VillagerProfession", -1); ++ // Vanilla doesn't remove the profession tag, so we don't! ++ if (type < 0 || type >= 6) { ++ type = ThreadLocalRandom.current().nextInt(6); ++ } ++ ++ data.setInt("ZombieType", type); ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V502() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java +new file mode 100644 +index 0000000000000000000000000000000000000000..30769f902c7d694bce41ab319d0b9a87c6103f11 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java +@@ -0,0 +1,24 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V505 { ++ ++ protected static final int VERSION = MCVersions.V16W21B + 1; ++ ++ public static void register() { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.setString("useVbo", "true"); ++ return null; ++ } ++ }); ++ } ++ ++ private V505() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f46b1a0c4bc96d638853cc61e5703798dbf6b886 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java +@@ -0,0 +1,33 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V700 { ++ ++ protected static final int VERSION = MCVersions.V1_10_2 + 188; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("Guardian", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getBoolean("Elder")) { ++ data.setString("id", "ElderGuardian"); ++ } ++ data.remove("Elder"); ++ return null; ++ } ++ }); ++ ++ registerMob("ElderGuardian"); ++ } ++ ++ private V700() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java +new file mode 100644 +index 0000000000000000000000000000000000000000..42f173f426fb7d26e5ddb5a1c92c63b2e6a4930c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java +@@ -0,0 +1,43 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V701 { ++ ++ protected static final int VERSION = MCVersions.V1_10_2 + 189; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("Skeleton", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int type = data.getInt("SkeletonType"); ++ data.remove("SkeletonType"); ++ ++ switch (type) { ++ case 1: ++ data.setString("id", "WitherSkeleton"); ++ break; ++ case 2: ++ data.setString("id", "Stray"); ++ break; ++ } ++ ++ return null; ++ } ++ }); ++ ++ registerMob("WitherSkeleton"); ++ registerMob("Stray"); ++ } ++ ++ private V701() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5cc91edde9c8160f75165bcef554023246e0a224 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java +@@ -0,0 +1,53 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V702 { ++ ++ protected static final int VERSION = MCVersions.V1_10_2 + 190; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("Zombie", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int zombieType = data.getInt("ZombieType"); ++ data.remove("ZombieType"); ++ ++ switch (zombieType) { ++ case 0: ++ default: ++ break; ++ ++ case 1: ++ case 2: ++ case 3: ++ case 4: ++ case 5: ++ data.setString("id", "ZombieVillager"); ++ data.setInt("Profession", zombieType - 1); ++ break; ++ ++ case 6: ++ data.setString("id", "Husk"); ++ break; ++ } ++ ++ return null; ++ } ++ }); ++ ++ registerMob("ZombieVillager"); ++ registerMob( "Husk"); ++ } ++ ++ private V702() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java +new file mode 100644 +index 0000000000000000000000000000000000000000..88d9c0fcd88ccfd6d6b46ae050914079c816fa3f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java +@@ -0,0 +1,66 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V703 { ++ ++ protected static final int VERSION = MCVersions.V1_10_2 + 191; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("EntityHorse", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int type = data.getInt("Type"); ++ data.remove("Type"); ++ ++ switch (type) { ++ case 0: ++ default: ++ data.setString("id", "Horse"); ++ break; ++ ++ case 1: ++ data.setString("id", "Donkey"); ++ break; ++ ++ case 2: ++ data.setString("id", "Mule"); ++ break; ++ ++ case 3: ++ data.setString("id", "ZombieHorse"); ++ break; ++ ++ case 4: ++ data.setString("id", "SkeletonHorse"); ++ break; ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Horse", new DataWalkerItems("ArmorItem", "SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Horse", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Donkey", new DataWalkerItems("SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Donkey", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Mule", new DataWalkerItems("SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Mule", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "ZombieHorse", new DataWalkerItems("SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "ZombieHorse", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "SkeletonHorse", new DataWalkerItems("SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "SkeletonHorse", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ private V703() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ef080b7c625c977c1dd4fe179ac2ca40889720b2 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java +@@ -0,0 +1,394 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.item_name.DataWalkerItemNames; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.mojang.logging.LogUtils; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.world.item.BlockItem; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.EntityBlock; ++import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.entity.BlockEntityType; ++import org.slf4j.Logger; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V704 { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V1_10_2 + 192; ++ ++ public static final Map ITEM_ID_TO_TILE_ENTITY_ID = new HashMap<>() { ++ @Override ++ public String put(final String key, final String value) { ++ if (this.containsKey(key)) { ++ LOGGER.error("Duplicate item id to tile key: " + key); ++ throw new RuntimeException(); // only devs should see the consequence of this... at least start up the damn thing... ++ } ++ return super.put(key, value); ++ } ++ }; ++ static { ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:furnace", "minecraft:furnace"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lit_furnace", "minecraft:furnace"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chest", "minecraft:chest"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:trapped_chest", "minecraft:chest"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:ender_chest", "minecraft:ender_chest"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jukebox", "minecraft:jukebox"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dispenser", "minecraft:dispenser"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dropper", "minecraft:dropper"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mob_spawner", "minecraft:mob_spawner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:spawner", "minecraft:mob_spawner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:noteblock", "minecraft:noteblock"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brewing_stand", "minecraft:brewing_stand"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:enhanting_table", "minecraft:enchanting_table"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:command_block", "minecraft:command_block"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:beacon", "minecraft:beacon"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:skull", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector", "minecraft:daylight_detector"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:hopper", "minecraft:hopper"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:flower_pot", "minecraft:flower_pot"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:repeating_command_block", "minecraft:command_block"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chain_command_block", "minecraft:command_block"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:white_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:orange_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:magenta_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_blue_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:yellow_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lime_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:pink_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:gray_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:silver_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cyan_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:purple_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blue_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brown_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:green_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:red_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:black_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_gray_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:white_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:orange_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:magenta_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_blue_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:yellow_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lime_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:pink_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:gray_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:silver_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cyan_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:purple_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blue_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brown_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:green_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:red_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:black_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:piston_head", "minecraft:piston"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector_inverted", "minecraft:daylight_detector"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:unpowered_comparator", "minecraft:comparator"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:powered_comparator", "minecraft:comparator"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:structure_block", "minecraft:structure_block"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_portal", "minecraft:end_portal"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_gateway", "minecraft:end_gateway"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:shield", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:white_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:orange_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:magenta_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_blue_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:yellow_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lime_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:pink_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:gray_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:silver_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cyan_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:purple_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blue_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brown_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:green_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:red_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:black_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:oak_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:spruce_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:birch_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jungle_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:acacia_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dark_oak_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:crimson_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:warped_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:skeleton_skull", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wither_skeleton_skull", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:zombie_head", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:player_head", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:creeper_head", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dragon_head", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:barrel", "minecraft:barrel"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:conduit", "minecraft:conduit"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:smoker", "minecraft:smoker"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blast_furnace", "minecraft:blast_furnace"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lectern", "minecraft:lectern"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bell", "minecraft:bell"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jigsaw", "minecraft:jigsaw"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:campfire", "minecraft:campfire"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bee_nest", "minecraft:beehive"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:beehive", "minecraft:beehive"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sculk_sensor", "minecraft:sculk_sensor"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:decorated_pot", "minecraft:decorated_pot"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:crafter", "minecraft:crafter"); ++ ++ // These are missing from Vanilla (TODO check on update) ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:enchanting_table", "minecraft:enchanting_table"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:comparator", "minecraft:comparator"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_gray_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_gray_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:soul_campfire", "minecraft:campfire"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sculk_catalyst", "minecraft:sculk_catalyst"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mangrove_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sculk_shrieker", "minecraft:sculk_shrieker"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chiseled_bookshelf", "minecraft:chiseled_bookshelf"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bamboo_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:oak_hanging_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:spruce_hanging_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:birch_hanging_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jungle_hanging_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:acacia_hanging_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dark_oak_hanging_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mangrove_hanging_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bamboo_hanging_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:crimson_hanging_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:warped_hanging_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:piglin_head", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:suspicious_sand", "minecraft:brushable_block"); // note: this was renamed in the past, see special case in the itemstack walker ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cherry_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cherry_hanging_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:suspicious_gravel", "minecraft:brushable_block"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:calibrated_sculk_sensor", "minecraft:calibrated_sculk_sensor"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:trial_spawner", "minecraft:trial_spawner"); ++ } ++ ++ // This class is responsible for also integrity checking the item id to tile id map here, we just use the item registry to figure it out ++ ++ static { ++ for (final Item item : BuiltInRegistries.ITEM) { ++ if (!(item instanceof BlockItem)) { ++ continue; ++ } ++ ++ if (!(((BlockItem)item).getBlock() instanceof EntityBlock entityBlock)) { ++ continue; ++ } ++ ++ String possibleId; ++ try { ++ final BlockEntity entity = entityBlock.newBlockEntity(new BlockPos(0, 0, 0), ((Block)entityBlock).defaultBlockState()); ++ if (entity != null) { ++ possibleId = BlockEntityType.getKey(entity.getType()).toString(); ++ } else { ++ possibleId = null; ++ } ++ } catch (final Throwable th) { ++ possibleId = null; ++ } ++ ++ final String itemName = BuiltInRegistries.ITEM.getKey(item).toString(); ++ final String mappedTo = ITEM_ID_TO_TILE_ENTITY_ID.get(itemName); ++ if (mappedTo == null) { ++ LOGGER.error("Item id " + itemName + " does not contain tile mapping! (V704)"); ++ } else if (possibleId != null && !mappedTo.equals(possibleId)) { ++ final boolean chestCase = mappedTo.equals("minecraft:chest") && possibleId.equals("minecraft:trapped_chest"); ++ final boolean signCase = mappedTo.equals("minecraft:sign") && possibleId.equals("minecraft:hanging_sign"); ++ // save data is identical for the chest and sign case, so we don't care ++ // it's also important to note that there is no versioning for this map, so it is possible ++ // that mapping them correctly could cause issues converting old data ++ if (!chestCase && !signCase) { ++ LOGGER.error("Item id " + itemName + " is mapped to the wrong tile entity! Mapped to: " + mappedTo + ", expected: " + possibleId); ++ } ++ } ++ } ++ } ++ ++ protected static final Map TILE_ID_UPDATE = new HashMap<>(); ++ static { ++ TILE_ID_UPDATE.put("Airportal", "minecraft:end_portal"); ++ TILE_ID_UPDATE.put("Banner", "minecraft:banner"); ++ TILE_ID_UPDATE.put("Beacon", "minecraft:beacon"); ++ TILE_ID_UPDATE.put("Cauldron", "minecraft:brewing_stand"); ++ TILE_ID_UPDATE.put("Chest", "minecraft:chest"); ++ TILE_ID_UPDATE.put("Comparator", "minecraft:comparator"); ++ TILE_ID_UPDATE.put("Control", "minecraft:command_block"); ++ TILE_ID_UPDATE.put("DLDetector", "minecraft:daylight_detector"); ++ TILE_ID_UPDATE.put("Dropper", "minecraft:dropper"); ++ TILE_ID_UPDATE.put("EnchantTable", "minecraft:enchanting_table"); ++ TILE_ID_UPDATE.put("EndGateway", "minecraft:end_gateway"); ++ TILE_ID_UPDATE.put("EnderChest", "minecraft:ender_chest"); ++ TILE_ID_UPDATE.put("FlowerPot", "minecraft:flower_pot"); ++ TILE_ID_UPDATE.put("Furnace", "minecraft:furnace"); ++ TILE_ID_UPDATE.put("Hopper", "minecraft:hopper"); ++ TILE_ID_UPDATE.put("MobSpawner", "minecraft:mob_spawner"); ++ TILE_ID_UPDATE.put("Music", "minecraft:noteblock"); ++ TILE_ID_UPDATE.put("Piston", "minecraft:piston"); ++ TILE_ID_UPDATE.put("RecordPlayer", "minecraft:jukebox"); ++ TILE_ID_UPDATE.put("Sign", "minecraft:sign"); ++ TILE_ID_UPDATE.put("Skull", "minecraft:skull"); ++ TILE_ID_UPDATE.put("Structure", "minecraft:structure_block"); ++ TILE_ID_UPDATE.put("Trap", "minecraft:dispenser"); ++ } ++ ++ protected static void registerInventory(final String id) { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("Items")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String id = data.getString("id"); ++ if (id == null) { ++ return null; ++ } ++ ++ data.setString("id", TILE_ID_UPDATE.getOrDefault(id, id)); ++ return null; ++ } ++ }); ++ ++ ++ ++ registerInventory( "minecraft:furnace"); ++ registerInventory( "minecraft:chest"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:jukebox", new DataWalkerItems("RecordItem")); ++ registerInventory("minecraft:dispenser"); ++ registerInventory("minecraft:dropper"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:mob_spawner", (final MapType data, final long fromVersion, final long toVersion) -> { ++ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); ++ return null; ++ }); ++ registerInventory("minecraft:brewing_stand"); ++ registerInventory("minecraft:hopper"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:flower_pot", new DataWalkerItemNames("Item")); ++ ++ MCTypeRegistry.ITEM_STACK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, data, "id", fromVersion, toVersion); ++ ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ // only things here are in tag, if changed update if above ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, tag, "Items", fromVersion, toVersion); ++ ++ MapType entityTag = tag.getMap("EntityTag"); ++ if (entityTag != null) { ++ final String itemId = data.getString("id"); ++ final String entityId; ++ if ("minecraft:armor_stand".equals(itemId)) { ++ // The check for version id is changed here. For whatever reason, the legacy ++ // data converters used entity id "minecraft:armor_stand" when version was greater-than 514, ++ // but entity ids were not namespaced until V705! So somebody fucked up the legacy converters. ++ // DFU agrees with my analysis here, it will only set the entityId here to the namespaced variant ++ // with the V705 schema. ++ entityId = DataConverter.getVersion(fromVersion) < 705 ? "ArmorStand" : "minecraft:armor_stand"; ++ } else if (itemId != null && itemId.contains("_spawn_egg")) { ++ // V1451 changes spawn eggs to have the sub entity id be a part of the item id, but of course Mojang never ++ // bothered to write in logic to set the sub entity id, so we have to. ++ // format is ALWAYS :_spawn_egg post flattening ++ entityId = itemId.substring(0, itemId.indexOf("_spawn_egg")); ++ } else if ("minecraft:item_frame".equals(itemId)) { ++ // add missing item_frame entity id ++ // version check is same for armorstand, as both were namespaced at the same time ++ entityId = DataConverter.getVersion(fromVersion) < 705 ? "ItemFrame" : "minecraft:item_frame"; ++ } else if ("minecraft:glow_item_frame".equals(itemId)) { ++ // add missing glow_item_frame entity id ++ entityId = "minecraft:glow_item_frame"; ++ } else { ++ entityId = entityTag.getString("id"); ++ } ++ ++ final boolean removeId; ++ if (entityId == null) { ++ if (!"minecraft:air".equals(itemId)) { ++ LOGGER.warn("Unable to resolve Entity for ItemStack (V704): " + itemId); ++ } ++ removeId = false; ++ } else { ++ removeId = !entityTag.hasKey("id", ObjectType.STRING); ++ if (removeId) { ++ entityTag.setString("id", entityId); ++ } ++ } ++ ++ final MapType replace = MCTypeRegistry.ENTITY.convert(entityTag, fromVersion, toVersion); ++ ++ if (replace != null) { ++ entityTag = replace; ++ tag.setMap("EntityTag", entityTag); ++ } ++ if (removeId) { ++ entityTag.remove("id"); ++ } ++ } ++ ++ MapType blockEntityTag = tag.getMap("BlockEntityTag"); ++ if (blockEntityTag != null) { ++ final String itemId = data.getString("id"); ++ final String entityId; ++ if ("minecraft:suspicious_sand".equals(itemId) && fromVersion < V3438.VERSION) { ++ // renamed after this version, and since the id is a mapping to just string we need to special case this ++ entityId = "minecraft:suspicious_sand"; ++ } else { ++ entityId = ITEM_ID_TO_TILE_ENTITY_ID.get(itemId); ++ } ++ final boolean removeId; ++ if (entityId == null) { ++ if (!"minecraft:air".equals(itemId)) { ++ LOGGER.warn("Unable to resolve BlockEntity for ItemStack (V704): " + itemId); ++ } ++ removeId = false; ++ } else { ++ removeId = !blockEntityTag.hasKey("id", ObjectType.STRING); ++ if (removeId) { ++ blockEntityTag.setString("id", entityId); ++ } ++ } ++ final MapType replace = MCTypeRegistry.TILE_ENTITY.convert(blockEntityTag, fromVersion, toVersion); ++ if (replace != null) { ++ blockEntityTag = replace; ++ tag.setMap("BlockEntityTag", entityTag); ++ } ++ if (removeId) { ++ blockEntityTag.remove("id"); ++ } ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanDestroy", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanPlaceOn", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ // Enforce namespace for ids ++ MCTypeRegistry.TILE_ENTITY.addStructureHook(VERSION, new DataHookEnforceNamespacedID()); ++ } ++ ++ private V704() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e1d7013e49904dacc5e33d9c0b3f3ddb10e3d07a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java +@@ -0,0 +1,231 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID; ++import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced; ++import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity.DataWalkerTileEntities; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.mojang.logging.LogUtils; ++import org.slf4j.Logger; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V705 { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V1_10_2 + 193; ++ ++ protected static final Map ENTITY_ID_UPDATE = new HashMap<>(); ++ static { ++ ENTITY_ID_UPDATE.put("AreaEffectCloud", "minecraft:area_effect_cloud"); ++ ENTITY_ID_UPDATE.put("ArmorStand", "minecraft:armor_stand"); ++ ENTITY_ID_UPDATE.put("Arrow", "minecraft:arrow"); ++ ENTITY_ID_UPDATE.put("Bat", "minecraft:bat"); ++ ENTITY_ID_UPDATE.put("Blaze", "minecraft:blaze"); ++ ENTITY_ID_UPDATE.put("Boat", "minecraft:boat"); ++ ENTITY_ID_UPDATE.put("CaveSpider", "minecraft:cave_spider"); ++ ENTITY_ID_UPDATE.put("Chicken", "minecraft:chicken"); ++ ENTITY_ID_UPDATE.put("Cow", "minecraft:cow"); ++ ENTITY_ID_UPDATE.put("Creeper", "minecraft:creeper"); ++ ENTITY_ID_UPDATE.put("Donkey", "minecraft:donkey"); ++ ENTITY_ID_UPDATE.put("DragonFireball", "minecraft:dragon_fireball"); ++ ENTITY_ID_UPDATE.put("ElderGuardian", "minecraft:elder_guardian"); ++ ENTITY_ID_UPDATE.put("EnderCrystal", "minecraft:ender_crystal"); ++ ENTITY_ID_UPDATE.put("EnderDragon", "minecraft:ender_dragon"); ++ ENTITY_ID_UPDATE.put("Enderman", "minecraft:enderman"); ++ ENTITY_ID_UPDATE.put("Endermite", "minecraft:endermite"); ++ ENTITY_ID_UPDATE.put("EyeOfEnderSignal", "minecraft:eye_of_ender_signal"); ++ ENTITY_ID_UPDATE.put("FallingSand", "minecraft:falling_block"); ++ ENTITY_ID_UPDATE.put("Fireball", "minecraft:fireball"); ++ ENTITY_ID_UPDATE.put("FireworksRocketEntity", "minecraft:fireworks_rocket"); ++ ENTITY_ID_UPDATE.put("Ghast", "minecraft:ghast"); ++ ENTITY_ID_UPDATE.put("Giant", "minecraft:giant"); ++ ENTITY_ID_UPDATE.put("Guardian", "minecraft:guardian"); ++ ENTITY_ID_UPDATE.put("Horse", "minecraft:horse"); ++ ENTITY_ID_UPDATE.put("Husk", "minecraft:husk"); ++ ENTITY_ID_UPDATE.put("Item", "minecraft:item"); ++ ENTITY_ID_UPDATE.put("ItemFrame", "minecraft:item_frame"); ++ ENTITY_ID_UPDATE.put("LavaSlime", "minecraft:magma_cube"); ++ ENTITY_ID_UPDATE.put("LeashKnot", "minecraft:leash_knot"); ++ ENTITY_ID_UPDATE.put("MinecartChest", "minecraft:chest_minecart"); ++ ENTITY_ID_UPDATE.put("MinecartCommandBlock", "minecraft:commandblock_minecart"); ++ ENTITY_ID_UPDATE.put("MinecartFurnace", "minecraft:furnace_minecart"); ++ ENTITY_ID_UPDATE.put("MinecartHopper", "minecraft:hopper_minecart"); ++ ENTITY_ID_UPDATE.put("MinecartRideable", "minecraft:minecart"); ++ ENTITY_ID_UPDATE.put("MinecartSpawner", "minecraft:spawner_minecart"); ++ ENTITY_ID_UPDATE.put("MinecartTNT", "minecraft:tnt_minecart"); ++ ENTITY_ID_UPDATE.put("Mule", "minecraft:mule"); ++ ENTITY_ID_UPDATE.put("MushroomCow", "minecraft:mooshroom"); ++ ENTITY_ID_UPDATE.put("Ozelot", "minecraft:ocelot"); ++ ENTITY_ID_UPDATE.put("Painting", "minecraft:painting"); ++ ENTITY_ID_UPDATE.put("Pig", "minecraft:pig"); ++ ENTITY_ID_UPDATE.put("PigZombie", "minecraft:zombie_pigman"); ++ ENTITY_ID_UPDATE.put("PolarBear", "minecraft:polar_bear"); ++ ENTITY_ID_UPDATE.put("PrimedTnt", "minecraft:tnt"); ++ ENTITY_ID_UPDATE.put("Rabbit", "minecraft:rabbit"); ++ ENTITY_ID_UPDATE.put("Sheep", "minecraft:sheep"); ++ ENTITY_ID_UPDATE.put("Shulker", "minecraft:shulker"); ++ ENTITY_ID_UPDATE.put("ShulkerBullet", "minecraft:shulker_bullet"); ++ ENTITY_ID_UPDATE.put("Silverfish", "minecraft:silverfish"); ++ ENTITY_ID_UPDATE.put("Skeleton", "minecraft:skeleton"); ++ ENTITY_ID_UPDATE.put("SkeletonHorse", "minecraft:skeleton_horse"); ++ ENTITY_ID_UPDATE.put("Slime", "minecraft:slime"); ++ ENTITY_ID_UPDATE.put("SmallFireball", "minecraft:small_fireball"); ++ ENTITY_ID_UPDATE.put("SnowMan", "minecraft:snowman"); ++ ENTITY_ID_UPDATE.put("Snowball", "minecraft:snowball"); ++ ENTITY_ID_UPDATE.put("SpectralArrow", "minecraft:spectral_arrow"); ++ ENTITY_ID_UPDATE.put("Spider", "minecraft:spider"); ++ ENTITY_ID_UPDATE.put("Squid", "minecraft:squid"); ++ ENTITY_ID_UPDATE.put("Stray", "minecraft:stray"); ++ ENTITY_ID_UPDATE.put("ThrownEgg", "minecraft:egg"); ++ ENTITY_ID_UPDATE.put("ThrownEnderpearl", "minecraft:ender_pearl"); ++ ENTITY_ID_UPDATE.put("ThrownExpBottle", "minecraft:xp_bottle"); ++ ENTITY_ID_UPDATE.put("ThrownPotion", "minecraft:potion"); ++ ENTITY_ID_UPDATE.put("Villager", "minecraft:villager"); ++ ENTITY_ID_UPDATE.put("VillagerGolem", "minecraft:villager_golem"); ++ ENTITY_ID_UPDATE.put("Witch", "minecraft:witch"); ++ ENTITY_ID_UPDATE.put("WitherBoss", "minecraft:wither"); ++ ENTITY_ID_UPDATE.put("WitherSkeleton", "minecraft:wither_skeleton"); ++ ENTITY_ID_UPDATE.put("WitherSkull", "minecraft:wither_skull"); ++ ENTITY_ID_UPDATE.put("Wolf", "minecraft:wolf"); ++ ENTITY_ID_UPDATE.put("XPOrb", "minecraft:xp_orb"); ++ ENTITY_ID_UPDATE.put("Zombie", "minecraft:zombie"); ++ ENTITY_ID_UPDATE.put("ZombieHorse", "minecraft:zombie_horse"); ++ ENTITY_ID_UPDATE.put("ZombieVillager", "minecraft:zombie_villager"); ++ } ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ private static void registerThrowableProjectile(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerBlockNames("inTile")); ++ } ++ ++ public static void register() { ++ ConverterAbstractEntityRename.register(VERSION, ENTITY_ID_UPDATE::get); ++ ++ registerMob("minecraft:armor_stand"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:arrow", new DataWalkerBlockNames("inTile")); ++ registerMob("minecraft:bat"); ++ registerMob("minecraft:blaze"); ++ registerMob("minecraft:cave_spider"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:chest_minecart", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:chest_minecart", new DataWalkerItemLists("Items")); ++ registerMob("minecraft:chicken"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:commandblock_minecart", new DataWalkerBlockNames("DisplayTile")); ++ registerMob("minecraft:cow"); ++ registerMob("minecraft:creeper"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:donkey", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:donkey", new DataWalkerItems("SaddleItem")); ++ registerThrowableProjectile("minecraft:egg"); ++ registerMob("minecraft:elder_guardian"); ++ registerMob("minecraft:ender_dragon"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:enderman", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:enderman", new DataWalkerBlockNames("carried")); ++ registerMob("minecraft:endermite"); ++ registerThrowableProjectile("minecraft:ender_pearl"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:falling_block", new DataWalkerBlockNames("Block")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:falling_block", new DataWalkerTileEntities("TileEntityData")); ++ registerThrowableProjectile("minecraft:fireball"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:fireworks_rocket", new DataWalkerItems("FireworksItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:furnace_minecart", new DataWalkerBlockNames("DisplayTile")); ++ registerMob("minecraft:ghast"); ++ registerMob("minecraft:giant"); ++ registerMob("minecraft:guardian"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:hopper_minecart", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:hopper_minecart", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:horse", new DataWalkerItems("ArmorItem", "SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:horse", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ registerMob("minecraft:husk"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:item", new DataWalkerItems("Item")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:item_frame", new DataWalkerItems("Item")); ++ registerMob("minecraft:magma_cube"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:minecart", new DataWalkerBlockNames("DisplayTile")); ++ registerMob("minecraft:mooshroom"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:mule", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:mule", new DataWalkerItems("SaddleItem")); ++ registerMob("minecraft:ocelot"); ++ registerMob("minecraft:pig"); ++ registerMob("minecraft:polar_bear"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:potion", new DataWalkerItems("Potion")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:potion", new DataWalkerBlockNames("inTile")); ++ registerMob("minecraft:rabbit"); ++ registerMob("minecraft:sheep"); ++ registerMob("minecraft:shulker"); ++ registerMob("minecraft:silverfish"); ++ registerMob("minecraft:skeleton"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:skeleton_horse", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:skeleton_horse", new DataWalkerItems("SaddleItem")); ++ registerMob("minecraft:slime"); ++ registerThrowableProjectile("minecraft:small_fireball"); ++ registerThrowableProjectile("minecraft:snowball"); ++ registerMob("minecraft:snowman"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:spawner_minecart", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:spawner_minecart", (final MapType data, final long fromVersion, final long toVersion) -> { ++ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); ++ return null; ++ }); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:spectral_arrow", new DataWalkerBlockNames("inTile")); ++ registerMob("minecraft:spider"); ++ registerMob("minecraft:squid"); ++ registerMob("minecraft:stray"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:tnt_minecart", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:villager", (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); ++ ++ final MapType offers = data.getMap("Offers"); ++ if (offers != null) { ++ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); ++ if (recipes != null) { ++ for (int i = 0, len = recipes.size(); i < len; ++i) { ++ final MapType recipe = recipes.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); ++ } ++ } ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); ++ ++ return null; ++ }); ++ registerMob("minecraft:villager_golem"); ++ registerMob("minecraft:witch"); ++ registerMob("minecraft:wither"); ++ registerMob("minecraft:wither_skeleton"); ++ registerThrowableProjectile("minecraft:wither_skull"); ++ registerMob("minecraft:wolf"); ++ registerThrowableProjectile("minecraft:xp_bottle"); ++ registerMob("minecraft:zombie"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:zombie_horse", new DataWalkerItems("SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:zombie_horse", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ registerMob("minecraft:zombie_pigman"); ++ registerMob("minecraft:zombie_villager"); ++ registerMob("minecraft:evocation_illager"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:llama", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:llama", new DataWalkerItems("SaddleItem", "DecorItem")); ++ registerMob("minecraft:vex"); ++ registerMob("minecraft:vindication_illager"); ++ // Don't need to re-register itemstack walker, the V704 will correctly choose the right id for armorstand based on ++ // the source version ++ ++ // Enforce namespace for ids ++ MCTypeRegistry.ENTITY.addStructureHook(VERSION, new DataHookEnforceNamespacedID()); ++ MCTypeRegistry.ENTITY_NAME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); ++ } ++ ++ private V705() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0070a3d02a87b0f08cd5e74d4f106f3e97f6b4f8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java +@@ -0,0 +1,60 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V804 { ++ ++ protected static final int VERSION = MCVersions.V16W35A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:banner", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final MapType blockEntity = tag.getMap("BlockEntityTag"); ++ if (blockEntity == null) { ++ return null; ++ } ++ ++ if (!blockEntity.hasKey("Base", ObjectType.NUMBER)) { ++ return null; ++ } ++ ++ data.setShort("Damage", (short)(blockEntity.getShort("Base") & 15)); ++ ++ final MapType display = tag.getMap("display"); ++ if (display != null) { ++ final ListType lore = display.getList("Lore", ObjectType.STRING); ++ if (lore != null) { ++ if (lore.size() == 1 && "(+NBT)".equals(lore.getString(0))) { ++ return null; ++ } ++ } ++ } ++ ++ blockEntity.remove("Base"); ++ if (blockEntity.isEmpty()) { ++ tag.remove("BlockEntityTag"); ++ } ++ ++ if (tag.isEmpty()) { ++ data.remove("tag"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V804() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a2717b9d936872ec07141b0f3ae2a6eec81f2dbf +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java +@@ -0,0 +1,40 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V806 { ++ ++ protected static final int VERSION = MCVersions.V16W36A + 1; ++ ++ public static void register() { ++ final DataConverter, MapType> potionWaterUpdater = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ tag = Types.NBT.createEmptyMap(); ++ data.setMap("tag", tag); ++ } ++ ++ if (!tag.hasKey("Potion", ObjectType.STRING)) { ++ tag.setString("Potion", "minecraft:water"); ++ } ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:potion", potionWaterUpdater); ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:splash_potion", potionWaterUpdater); ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:lingering_potion", potionWaterUpdater); ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:tipped_arrow", potionWaterUpdater); ++ } ++ ++ private V806() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b058e5e9b34a9dd134ef93e7a397b5f1e4e11fbd +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java +@@ -0,0 +1,30 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V808 { ++ ++ protected static final int VERSION = MCVersions.V16W38A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION, 1) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!data.hasKey("Color", ObjectType.NUMBER)) { ++ data.setByte("Color", (byte)10); ++ } ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:shulker_box", new DataWalkerItemLists("Items")); ++ } ++ ++ private V808() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b6de7c32acd0adf78812edbbd184117661599c80 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java +@@ -0,0 +1,64 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V813 { ++ ++ protected static final int VERSION = MCVersions.V16W40A; ++ ++ public static final String[] SHULKER_ID_BY_COLOUR = new String[] { ++ "minecraft:white_shulker_box", ++ "minecraft:orange_shulker_box", ++ "minecraft:magenta_shulker_box", ++ "minecraft:light_blue_shulker_box", ++ "minecraft:yellow_shulker_box", ++ "minecraft:lime_shulker_box", ++ "minecraft:pink_shulker_box", ++ "minecraft:gray_shulker_box", ++ "minecraft:silver_shulker_box", ++ "minecraft:cyan_shulker_box", ++ "minecraft:purple_shulker_box", ++ "minecraft:blue_shulker_box", ++ "minecraft:brown_shulker_box", ++ "minecraft:green_shulker_box", ++ "minecraft:red_shulker_box", ++ "minecraft:black_shulker_box" ++ }; ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:shulker_box", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final MapType blockEntity = tag.getMap("BlockEntityTag"); ++ if (blockEntity == null) { ++ return null; ++ } ++ ++ final int color = blockEntity.getInt("Color"); ++ blockEntity.remove("Color"); ++ ++ data.setString("id", SHULKER_ID_BY_COLOUR[color % SHULKER_ID_BY_COLOUR.length]); ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:shulker_box", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.remove("Color"); ++ return null; ++ } ++ }); ++ } ++ ++ private V813() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4b427c128bd75d2dc8b36f0c377454385c029467 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java +@@ -0,0 +1,28 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.Locale; ++ ++public final class V816 { ++ ++ protected static final int VERSION = MCVersions.V16W43A; ++ ++ public static void register() { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String lang = data.getString("lang"); ++ if (lang != null) { ++ data.setString("lang", lang.toLowerCase(Locale.ROOT)); ++ } ++ return null; ++ } ++ }); ++ } ++ ++ private V816() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9e2ef3cea4fd382a75a4d787fe2e2ff509eb49fc +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java +@@ -0,0 +1,19 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V820 { ++ ++ protected static final int VERSION = MCVersions.V1_11 + 1; ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:totem", "minecraft:totem_of_undying" ++ )::get); ++ } ++ ++ private V820() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f81ef4f80d5f627624d535ae25d44edd523fa4bf +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java +@@ -0,0 +1,357 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID; ++import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced; ++import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.item_name.DataWalkerItemNames; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity.DataWalkerTileEntities; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.mojang.logging.LogUtils; ++import org.slf4j.Logger; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V99 { ++ ++ // Structure for all data before data upgrading was added to minecraft (pre 15w32a) ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V15W32A - 1; ++ ++ protected static final Map ITEM_ID_TO_TILE_ENTITY_ID = new HashMap<>(); ++ ++ static { ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:furnace", "Furnace"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lit_furnace", "Furnace"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chest", "Chest"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:trapped_chest", "Chest"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:ender_chest", "EnderChest"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jukebox", "RecordPlayer"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dispenser", "Trap"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dropper", "Dropper"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sign", "Sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mob_spawner", "MobSpawner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:noteblock", "Music"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brewing_stand", "Cauldron"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:enhanting_table", "EnchantTable"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:command_block", "CommandBlock"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:beacon", "Beacon"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:skull", "Skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector", "DLDetector"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:hopper", "Hopper"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:banner", "Banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:flower_pot", "FlowerPot"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:repeating_command_block", "CommandBlock"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chain_command_block", "CommandBlock"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_sign", "Sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_sign", "Sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:piston_head", "Piston"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector_inverted", "DLDetector"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:unpowered_comparator", "Comparator"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:powered_comparator", "Comparator"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_banner", "Banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_banner", "Banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:structure_block", "Structure"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_portal", "Airportal"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_gateway", "EndGateway"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:shield", "Banner"); ++ } ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("Equipment")); ++ } ++ ++ private static void registerProjectile(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerBlockNames("inTile")); ++ } ++ ++ private static void registerInventory(final String id) { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("Items")); ++ } ++ ++ public static void register() { ++ // entities ++ MCTypeRegistry.ENTITY.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "Riding", fromVersion, toVersion); ++ ++ return null; ++ }); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Item", new DataWalkerItems("Item")); ++ registerProjectile("ThrownEgg"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Arrow", new DataWalkerBlockNames("inTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "TippedArrow", new DataWalkerBlockNames("inTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "SpectralArrow", new DataWalkerBlockNames("inTile")); ++ registerProjectile("Snowball"); ++ registerProjectile("Fireball"); ++ registerProjectile("SmallFireball"); ++ registerProjectile("ThrownEnderpearl"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "ThrownPotion", new DataWalkerBlockNames("inTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "ThrownPotion", new DataWalkerItems("Potion")); ++ registerProjectile("ThrownExpBottle"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "ItemFrame", new DataWalkerItems("Item")); ++ registerProjectile("WitherSkull"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "FallingSand", new DataWalkerBlockNames("Block")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "FallingSand", new DataWalkerTileEntities("TileEntityData")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "FireworksRocketEntity", new DataWalkerItems("FireworksItem")); ++ // Note: Minecart is the generic entity. It can be subtyped via an int to become one of the specific minecarts ++ // (i.e rideable, chest, furnace, tnt, etc) ++ // Because of this, we add all walkers to the generic type, even though they might not be needed. ++ // Vanilla does not make the generic minecart convert spawners, but we do. ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Minecart", new DataWalkerBlockNames("DisplayTile")); // for all minecart types ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Minecart", new DataWalkerItemLists("Items")); // for chest types ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Minecart", (final MapType data, final long fromVersion, final long toVersion) -> { ++ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); ++ return null; ++ }); // for spawner type ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartRideable", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartChest", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartChest", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartFurnace", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartTNT", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartSpawner", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartSpawner", (final MapType data, final long fromVersion, final long toVersion) -> { ++ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); ++ return null; ++ }); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartHopper", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartHopper", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartCommandBlock", new DataWalkerBlockNames("DisplayTile")); ++ registerMob("ArmorStand"); ++ registerMob("Creeper"); ++ registerMob("Skeleton"); ++ registerMob("Spider"); ++ registerMob("Giant"); ++ registerMob("Zombie"); ++ registerMob("Slime"); ++ registerMob("Ghast"); ++ registerMob("PigZombie"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Enderman", new DataWalkerBlockNames("carried")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Enderman", new DataWalkerItemLists("Equipment")); ++ registerMob("CaveSpider"); ++ registerMob("Silverfish"); ++ registerMob("Blaze"); ++ registerMob("LavaSlime"); ++ registerMob("EnderDragon"); ++ registerMob("WitherBoss"); ++ registerMob("Bat"); ++ registerMob("Witch"); ++ registerMob("Endermite"); ++ registerMob("Guardian"); ++ registerMob("Pig"); ++ registerMob("Sheep"); ++ registerMob("Cow"); ++ registerMob("Chicken"); ++ registerMob("Squid"); ++ registerMob("Wolf"); ++ registerMob("MushroomCow"); ++ registerMob("SnowMan"); ++ registerMob("Ozelot"); ++ registerMob("VillagerGolem"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItemLists("Items", "Equipment")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItems("ArmorItem", "SaddleItem")); ++ registerMob("Rabbit"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", new DataWalkerItemLists("Inventory", "Equipment")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType offers = data.getMap("Offers"); ++ if (offers != null) { ++ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); ++ if (recipes != null) { ++ for (int i = 0; i < recipes.size(); ++i) { ++ final MapType recipe = recipes.getMap(i); ++ ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); ++ } ++ } ++ } ++ ++ return null; ++ }); ++ registerMob("Shulker"); ++ ++ // tile entities ++ ++ // Inventory -> new DataWalkerItemLists("Items") ++ registerInventory("Furnace"); ++ registerInventory("Chest"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "RecordPlayer", new DataWalkerItems("RecordItem")); ++ registerInventory("Trap"); ++ registerInventory("Dropper"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "MobSpawner", (final MapType data, final long fromVersion, final long toVersion) -> { ++ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); ++ return null; ++ }); ++ registerInventory("Cauldron"); ++ registerInventory("Hopper"); ++ // Note: Vanilla does not properly handle this case, it will not convert int ids! ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "FlowerPot", new DataWalkerItemNames("Item")); ++ ++ // rest ++ ++ MCTypeRegistry.ITEM_STACK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, data, "id", fromVersion, toVersion); ++ ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ // only things here are in tag, if changed update if above ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, tag, "Items", fromVersion, toVersion); ++ ++ MapType entityTag = tag.getMap("EntityTag"); ++ if (entityTag != null) { ++ String itemId = getStringId(data.getString("id")); ++ final String entityId; ++ if ("minecraft:armor_stand".equals(itemId)) { ++ // The check for version id is removed here. For whatever reason, the legacy ++ // data converters used entity id "minecraft:armor_stand" when version was greater-than 514, ++ // but entity ids were not namespaced until V705! So somebody fucked up the legacy converters. ++ // DFU agrees with my analysis here, it will only set the entityId here to the namespaced variant ++ // with the V705 schema. ++ entityId = "ArmorStand"; ++ } else if ("minecraft:item_frame".equals(itemId)) { ++ // add missing item_frame entity id ++ entityId = "ItemFrame"; ++ } else { ++ entityId = entityTag.getString("id"); ++ } ++ ++ final boolean removeId; ++ if (entityId == null) { ++ if (!"minecraft:air".equals(itemId)) { ++ LOGGER.warn("Unable to resolve Entity for ItemStack (V99): " + data.getGeneric("id")); ++ } ++ removeId = false; ++ } else { ++ removeId = !entityTag.hasKey("id", ObjectType.STRING); ++ if (removeId) { ++ entityTag.setString("id", entityId); ++ } ++ } ++ ++ final MapType replace = MCTypeRegistry.ENTITY.convert(entityTag, fromVersion, toVersion); ++ ++ if (replace != null) { ++ entityTag = replace; ++ tag.setMap("EntityTag", entityTag); ++ } ++ if (removeId) { ++ entityTag.remove("id"); ++ } ++ } ++ ++ MapType blockEntityTag = tag.getMap("BlockEntityTag"); ++ if (blockEntityTag != null) { ++ final String itemId = getStringId(data.getString("id")); ++ final String entityId = ITEM_ID_TO_TILE_ENTITY_ID.get(itemId); ++ final boolean removeId; ++ if (entityId == null) { ++ if (!"minecraft:air".equals(itemId)) { ++ LOGGER.warn("Unable to resolve BlockEntity for ItemStack (V99): " + data.getGeneric("id")); ++ } ++ removeId = false; ++ } else { ++ removeId = !blockEntityTag.hasKey("id", ObjectType.STRING); ++ blockEntityTag.setString("id", entityId); ++ } ++ final MapType replace = MCTypeRegistry.TILE_ENTITY.convert(blockEntityTag, fromVersion, toVersion); ++ if (replace != null) { ++ blockEntityTag = replace; ++ tag.setMap("BlockEntityTag", blockEntityTag); ++ } ++ if (removeId) { ++ blockEntityTag.remove("id"); ++ } ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanDestroy", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanPlaceOn", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, new DataWalkerItemLists("Inventory", "EnderItems")); ++ ++ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); ++ ++ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); ++ if (tileTicks != null) { ++ for (int i = 0, len = tileTicks.size(); i < len; ++i) { ++ final MapType tileTick = tileTicks.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); ++ } ++ } ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.ENTITY_CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "Entities", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.SAVED_DATA_SCOREBOARD.addStructureWalker(VERSION, (final MapType root, final long fromVersion, final long toVersion) -> { ++ final MapType data = root.getMap("data"); ++ if (data == null) { ++ return null; ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.OBJECTIVE, data, "Objectives", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TEAM, data, "Teams", fromVersion, toVersion); ++ ++ return null; ++ }); ++ MCTypeRegistry.SAVED_DATA_STRUCTURE_FEATURE_INDICES.addStructureWalker(VERSION, (final MapType root, final long fromVersion, final long toVersion) -> { ++ final MapType data = root.getMap("data"); ++ if (data == null) { ++ return null; ++ } ++ ++ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, data, "Features", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ ++ // Enforce namespacing for ids ++ MCTypeRegistry.BLOCK_NAME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); ++ MCTypeRegistry.ITEM_NAME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); ++ MCTypeRegistry.ITEM_STACK.addStructureHook(VERSION, new DataHookEnforceNamespacedID()); ++ ++ // Entity is absent; the String form is not yet namespaced, unlike the above. ++ } ++ ++ protected static String getStringId(final Object id) { ++ if (id instanceof String) { ++ return (String)id; ++ } else if (id instanceof Number) { ++ return HelperItemNameV102.getNameFromId(((Number)id).intValue()); ++ } else { ++ return null; ++ } ++ } ++ ++ private V99() { ++ throw new RuntimeException(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java +new file mode 100644 +index 0000000000000000000000000000000000000000..930e014858ef635ebe25f7f92dc81ba0eaac50a8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java +@@ -0,0 +1,11 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.block_name; ++ ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++ ++public final class DataWalkerBlockNames extends DataWalkerTypePaths { ++ ++ public DataWalkerBlockNames(final String... paths) { ++ super(MCTypeRegistry.BLOCK_NAME, paths); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e7655645f5d32026a609a8c7517827653c5c5e8b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java +@@ -0,0 +1,26 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.game_event; ++ ++import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class GameEventListenerWalker implements DataWalker { ++ ++ @Override ++ public MapType walk(final MapType data, final long fromVersion, final long toVersion) { ++ final MapType listener = data.getMap("listener"); ++ if (listener == null) { ++ return null; ++ } ++ ++ final MapType event = listener.getMap("event"); ++ if (event == null) { ++ return null; ++ } ++ ++ WalkerUtils.convert(MCTypeRegistry.GAME_EVENT_NAME, event, "game_event", fromVersion, toVersion); ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7c8f6a5034b48e1ec2c5925211f491115ca735aa +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java +@@ -0,0 +1,38 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.generic; ++ ++import ca.spottedleaf.dataconverter.converters.datatypes.DataType; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public class DataWalkerListPaths implements DataWalker { ++ ++ protected final DataType type; ++ protected final String[] paths; ++ ++ public DataWalkerListPaths(final DataType type, final String... paths) { ++ this.type = type; ++ this.paths = paths; ++ } ++ ++ @Override ++ public final MapType walk(final MapType data, final long fromVersion, final long toVersion) { ++ final DataType type = this.type; ++ for (final String path : this.paths) { ++ final ListType list = data.getListUnchecked(path); ++ if (list == null) { ++ continue; ++ } ++ ++ for (int i = 0, len = list.size(); i < len; ++i) { ++ final Object current = list.getGeneric(i); ++ final Object converted = type.convert((T)current, fromVersion, toVersion); ++ if (converted != null) { ++ list.setGeneric(i, converted); ++ } ++ } ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e66b4e0f7cdb032b545ace7ba852ad7979f3c96a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java +@@ -0,0 +1,34 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.generic; ++ ++import ca.spottedleaf.dataconverter.converters.datatypes.DataType; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public class DataWalkerTypePaths implements DataWalker { ++ ++ protected final DataType type; ++ protected final String[] paths; ++ ++ public DataWalkerTypePaths(final DataType type, final String... paths) { ++ this.type = type; ++ this.paths = paths; ++ } ++ ++ @Override ++ public final MapType walk(final MapType data, final long fromVersion, final long toVersion) { ++ for (final String path : this.paths) { ++ final Object current = data.getGeneric(path); ++ if (current == null) { ++ continue; ++ } ++ ++ final Object converted = this.type.convert((T)current, fromVersion, toVersion); ++ ++ if (converted != null) { ++ data.setGeneric(path, converted); ++ } ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1e81a1e46a9c0ffceb564a7b1fc4d1b51009f3f7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java +@@ -0,0 +1,127 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.generic; ++ ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCDataType; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCValueType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.ArrayList; ++ ++public final class WalkerUtils { ++ ++ public static void convert(final MCDataType type, final MapType data, final String path, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ final MapType map = data.getMap(path); ++ if (map != null) { ++ final MapType replace = type.convert(map, fromVersion, toVersion); ++ if (replace != null) { ++ data.setMap(path, replace); ++ } ++ } ++ } ++ ++ public static void convertList(final MCDataType type, final MapType data, final String path, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ final ListType list = data.getList(path, ObjectType.MAP); ++ if (list != null) { ++ for (int i = 0, len = list.size(); i < len; ++i) { ++ final MapType replace = type.convert(list.getMap(i), fromVersion, toVersion); ++ if (replace != null) { ++ list.setMap(i, replace); ++ } ++ } ++ } ++ } ++ ++ public static void convert(final MCValueType type, final MapType data, final String path, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ final Object value = data.getGeneric(path); ++ if (value != null) { ++ final Object converted = type.convert(value, fromVersion, toVersion); ++ if (converted != null) { ++ data.setGeneric(path, converted); ++ } ++ } ++ } ++ ++ public static void convert(final MCValueType type, final ListType data, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ for (int i = 0, len = data.size(); i < len; ++i) { ++ final Object value = data.getGeneric(i); ++ final Object converted = type.convert(value, fromVersion, toVersion); ++ if (converted != null) { ++ data.setGeneric(i, converted); ++ } ++ } ++ } ++ ++ public static void convertList(final MCValueType type, final MapType data, final String path, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ final ListType list = data.getListUnchecked(path); ++ if (list != null) { ++ convert(type, list, fromVersion, toVersion); ++ } ++ } ++ ++ public static void convertKeys(final MCValueType type, final MapType data, final String path, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ final MapType map = data.getMap(path); ++ if (map != null) { ++ convertKeys(type, map, fromVersion, toVersion); ++ } ++ } ++ ++ public static void convertKeys(final MCValueType type, final MapType data, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ RenameHelper.renameKeys(data, (final String input) -> { ++ return (String)type.convert(input, fromVersion, toVersion); ++ }); ++ } ++ ++ public static void convertValues(final MCDataType type, final MapType data, final String path, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ convertValues(type, data.getMap(path), fromVersion, toVersion); ++ } ++ ++ public static void convertValues(final MCDataType type, final MapType data, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ for (final String key : data.keys()) { ++ final MapType value = data.getMap(key); ++ if (value != null) { ++ final MapType replace = type.convert(value, fromVersion, toVersion); ++ if (replace != null) { ++ // no CME, key is in map already ++ data.setMap(key, replace); ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java +new file mode 100644 +index 0000000000000000000000000000000000000000..14e291efd864d97dcf83db01c09b9daaae1949bd +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java +@@ -0,0 +1,11 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.item_name; ++ ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++ ++public final class DataWalkerItemNames extends DataWalkerTypePaths { ++ ++ public DataWalkerItemNames(final String... paths) { ++ super(MCTypeRegistry.ITEM_NAME, paths); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5b4402c3cc4e68e9c591e8bbb4a2542d8e2214d4 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java +@@ -0,0 +1,12 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.itemstack; ++ ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerListPaths; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public class DataWalkerItemLists extends DataWalkerListPaths, MapType> { ++ ++ public DataWalkerItemLists(final String... paths) { ++ super(MCTypeRegistry.ITEM_STACK, paths); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java +new file mode 100644 +index 0000000000000000000000000000000000000000..04770e8378ac8784895cdfe400a47b0b601c2187 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java +@@ -0,0 +1,12 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.itemstack; ++ ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public class DataWalkerItems extends DataWalkerTypePaths, MapType> { ++ ++ public DataWalkerItems(final String... paths) { ++ super(MCTypeRegistry.ITEM_STACK, paths); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d9cc21bf41cb4b377752b684f8e59818cd620103 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java +@@ -0,0 +1,12 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity; ++ ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class DataWalkerTileEntities extends DataWalkerTypePaths, MapType> { ++ ++ public DataWalkerTileEntities(final String... paths) { ++ super(MCTypeRegistry.TILE_ENTITY, paths); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..19f7e95f754e8385bbe60fd2fb7fc95b6a4ebd7c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java +@@ -0,0 +1,272 @@ ++package ca.spottedleaf.dataconverter.types; ++ ++public interface ListType { ++ ++ public TypeUtil getTypeUtil(); ++ ++ @Override ++ public int hashCode(); ++ ++ @Override ++ public boolean equals(final Object other); ++ ++ // Provides a deep copy of this list ++ public ListType copy(); ++ ++ // returns NONE if no type has been assigned. if NONE, then this list is also empty. It is not true on the other hand that an empty list has no type. ++ public ObjectType getType(); ++ ++ public int size(); ++ ++ public void remove(final int index); ++ ++ public default Object getGeneric(final int index) { ++ switch (this.getType()) { ++ case NONE: ++ throw new IllegalStateException("List is empty and has no type"); ++ case BYTE: ++ return Byte.valueOf(this.getByte(index)); ++ case SHORT: ++ return Short.valueOf(this.getShort(index)); ++ case INT: ++ return Integer.valueOf(this.getInt(index)); ++ case LONG: ++ return Long.valueOf(this.getLong(index)); ++ case FLOAT: ++ return Float.valueOf(this.getFloat(index)); ++ case DOUBLE: ++ return Double.valueOf(this.getDouble(index)); ++ case NUMBER: ++ return this.getNumber(index); ++ case BYTE_ARRAY: ++ return this.getBytes(index); ++ case SHORT_ARRAY: ++ return this.getShorts(index); ++ case INT_ARRAY: ++ return this.getInts(index); ++ case LONG_ARRAY: ++ return this.getLongs(index); ++ case LIST: ++ return this.getList(index); ++ case MAP: ++ return this.getMap(index); ++ case STRING: ++ return this.getString(index); ++ default: ++ throw new UnsupportedOperationException(this.getType().name()); ++ } ++ } ++ ++ public default void setGeneric(final int index, final Object to) { ++ if (to instanceof Number) { ++ if (to instanceof Byte) { ++ this.setByte(index, ((Byte)to).byteValue()); ++ return; ++ } else if (to instanceof Short) { ++ this.setShort(index, ((Short)to).shortValue()); ++ return; ++ } else if (to instanceof Integer) { ++ this.setInt(index, ((Integer)to).intValue()); ++ return; ++ } else if (to instanceof Long) { ++ this.setLong(index, ((Long)to).longValue()); ++ return; ++ } else if (to instanceof Float) { ++ this.setFloat(index, ((Float)to).floatValue()); ++ return; ++ } else if (to instanceof Double) { ++ this.setDouble(index, ((Double)to).doubleValue()); ++ return; ++ } // else fall through to throw ++ } else if (to instanceof MapType) { ++ this.setMap(index, (MapType)to); ++ return; ++ } else if (to instanceof ListType) { ++ this.setList(index, (ListType)to); ++ return; ++ } else if (to instanceof String) { ++ this.setString(index, (String)to); ++ return; ++ } else if (to.getClass().isArray()) { ++ if (to instanceof byte[]) { ++ this.setBytes(index, (byte[])to); ++ return; ++ } else if (to instanceof short[]) { ++ this.setShorts(index, (short[])to); ++ return; ++ } else if (to instanceof int[]) { ++ this.setInts(index, (int[])to); ++ return; ++ } else if (to instanceof long[]) { ++ this.setLongs(index, (long[])to); ++ return; ++ } // else fall through to throw ++ } ++ ++ throw new IllegalArgumentException("Object " + to + " is not a valid type!"); ++ } ++ ++ // types here are strict. if the type on get does not match the underlying type, will throw. ++ ++ public Number getNumber(final int index); ++ ++ // if the value at index is a Number but not a byte, then returns the number casted to byte. If the value at the index is not a number, then throws ++ public byte getByte(final int index); ++ ++ public void setByte(final int index, final byte to); ++ ++ // if the value at index is a Number but not a short, then returns the number casted to short. If the value at the index is not a number, then throws ++ public short getShort(final int index); ++ ++ public void setShort(final int index, final short to); ++ ++ // if the value at index is a Number but not a int, then returns the number casted to int. If the value at the index is not a number, then throws ++ public int getInt(final int index); ++ ++ public void setInt(final int index, final int to); ++ ++ // if the value at index is a Number but not a long, then returns the number casted to long. If the value at the index is not a number, then throws ++ public long getLong(final int index); ++ ++ public void setLong(final int index, final long to); ++ ++ // if the value at index is a Number but not a float, then returns the number casted to float. If the value at the index is not a number, then throws ++ public float getFloat(final int index); ++ ++ public void setFloat(final int index, final float to); ++ ++ // if the value at index is a Number but not a double, then returns the number casted to double. If the value at the index is not a number, then throws ++ public double getDouble(final int index); ++ ++ public void setDouble(final int index, final double to); ++ ++ public byte[] getBytes(final int index); ++ ++ public void setBytes(final int index, final byte[] to); ++ ++ public short[] getShorts(final int index); ++ ++ public void setShorts(final int index, final short[] to); ++ ++ public int[] getInts(final int index); ++ ++ public void setInts(final int index, final int[] to); ++ ++ public long[] getLongs(final int index); ++ ++ public void setLongs(final int index, final long[] to); ++ ++ public ListType getList(final int index); ++ ++ public void setList(final int index, final ListType list); ++ ++ public MapType getMap(final int index); ++ ++ public void setMap(final int index, final MapType to); ++ ++ public String getString(final int index); ++ ++ public void setString(final int index, final String to); ++ ++ public default void addGeneric(final Object to) { ++ if (to instanceof Number) { ++ if (to instanceof Byte) { ++ this.addByte(((Byte)to).byteValue()); ++ return; ++ } else if (to instanceof Short) { ++ this.addShort(((Short)to).shortValue()); ++ return; ++ } else if (to instanceof Integer) { ++ this.addInt(((Integer)to).intValue()); ++ return; ++ } else if (to instanceof Long) { ++ this.addLong(((Long)to).longValue()); ++ return; ++ } else if (to instanceof Float) { ++ this.addFloat(((Float)to).floatValue()); ++ return; ++ } else if (to instanceof Double) { ++ this.addDouble(((Double)to).doubleValue()); ++ return; ++ } // else fall through to throw ++ } else if (to instanceof MapType) { ++ this.addMap((MapType)to); ++ return; ++ } else if (to instanceof ListType) { ++ this.addList((ListType)to); ++ return; ++ } else if (to instanceof String) { ++ this.addString((String)to); ++ return; ++ } else if (to.getClass().isArray()) { ++ if (to instanceof byte[]) { ++ this.addByteArray((byte[])to); ++ return; ++ } else if (to instanceof short[]) { ++ this.addShortArray((short[])to); ++ return; ++ } else if (to instanceof int[]) { ++ this.addIntArray((int[])to); ++ return; ++ } else if (to instanceof long[]) { ++ this.addLongArray((long[])to); ++ return; ++ } // else fall through to throw ++ } ++ ++ throw new IllegalArgumentException("Object " + to + " is not a valid type!"); ++ } ++ ++ public void addByte(final byte b); ++ ++ public void addByte(final int index, final byte b); ++ ++ public void addShort(final short s); ++ ++ public void addShort(final int index, final short s); ++ ++ public void addInt(final int i); ++ ++ public void addInt(final int index, final int i); ++ ++ public void addLong(final long l); ++ ++ public void addLong(final int index, final long l); ++ ++ public void addFloat(final float f); ++ ++ public void addFloat(final int index, final float f); ++ ++ public void addDouble(final double d); ++ ++ public void addDouble(final int index, final double d); ++ ++ public void addByteArray(final byte[] arr); ++ ++ public void addByteArray(final int index, final byte[] arr); ++ ++ public void addShortArray(final short[] arr); ++ ++ public void addShortArray(final int index, final short[] arr); ++ ++ public void addIntArray(final int[] arr); ++ ++ public void addIntArray(final int index, final int[] arr); ++ ++ public void addLongArray(final long[] arr); ++ ++ public void addLongArray(final int index, final long[] arr); ++ ++ public void addList(final ListType list); ++ ++ public void addList(final int index, final ListType list); ++ ++ public void addMap(final MapType map); ++ ++ public void addMap(final int index, final MapType map); ++ ++ public void addString(final String string); ++ ++ public void addString(final int index, final String string); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1bd7809b7ee198d1ceeb2756b44105e1b0de956e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java +@@ -0,0 +1,219 @@ ++package ca.spottedleaf.dataconverter.types; ++ ++import java.util.Set; ++ ++public interface MapType { ++ ++ public TypeUtil getTypeUtil(); ++ ++ @Override ++ public int hashCode(); ++ ++ @Override ++ public boolean equals(final Object other); ++ ++ public int size(); ++ ++ public boolean isEmpty(); ++ ++ public void clear(); ++ ++ public Set keys(); ++ ++ // Provides a deep copy of this map ++ public MapType copy(); ++ ++ public boolean hasKey(final K key); ++ ++ public boolean hasKey(final K key, final ObjectType type); ++ ++ public void remove(final K key); ++ ++ public Object getGeneric(final K key); ++ ++ // types here are not strict. if the key maps to a different type, default is always returned ++ // if default is not a parameter, then default is always null ++ ++ public Number getNumber(final K key); ++ ++ public Number getNumber(final K key, final Number dfl); ++ ++ public boolean getBoolean(final K key); ++ ++ public boolean getBoolean(final K key, final boolean dfl); ++ ++ public void setBoolean(final K key, final boolean val); ++ ++ // if the mapped value is a Number but not a byte, then the number is casted to byte. If the mapped value does not exist or is not a number, returns 0 ++ public byte getByte(final K key); ++ ++ // if the mapped value is a Number but not a byte, then the number is casted to byte. If the mapped value does not exist or is not a number, returns dfl ++ public byte getByte(final K key, final byte dfl); ++ ++ public void setByte(final K key, final byte val); ++ ++ // if the mapped value is a Number but not a short, then the number is casted to short. If the mapped value does not exist or is not a number, returns 0 ++ public short getShort(final K key); ++ ++ // if the mapped value is a Number but not a short, then the number is casted to short. If the mapped value does not exist or is not a number, returns dfl ++ public short getShort(final K key, final short dfl); ++ ++ public void setShort(final K key, final short val); ++ ++ // if the mapped value is a Number but not a int, then the number is casted to int. If the mapped value does not exist or is not a number, returns 0 ++ public int getInt(final K key); ++ ++ // if the mapped value is a Number but not a int, then the number is casted to int. If the mapped value does not exist or is not a number, returns dfl ++ public int getInt(final K key, final int dfl); ++ ++ public void setInt(final K key, final int val); ++ ++ // if the mapped value is a Number but not a long, then the number is casted to long. If the mapped value does not exist or is not a number, returns 0 ++ public long getLong(final K key); ++ ++ // if the mapped value is a Number but not a long, then the number is casted to long. If the mapped value does not exist or is not a number, returns dfl ++ public long getLong(final K key, final long dfl); ++ ++ public void setLong(final K key, final long val); ++ ++ // if the mapped value is a Number but not a float, then the number is casted to float. If the mapped value does not exist or is not a number, returns 0 ++ public float getFloat(final K key); ++ ++ // if the mapped value is a Number but not a float, then the number is casted to float. If the mapped value does not exist or is not a number, returns dfl ++ public float getFloat(final K key, final float dfl); ++ ++ public void setFloat(final K key, final float val); ++ ++ // if the mapped value is a Number but not a double, then the number is casted to double. If the mapped value does not exist or is not a number, returns 0 ++ public double getDouble(final K key); ++ ++ // if the mapped value is a Number but not a double, then the number is casted to double. If the mapped value does not exist or is not a number, returns dfl ++ public double getDouble(final K key, final double dfl); ++ ++ public void setDouble(final K key, final double val); ++ ++ public byte[] getBytes(final K key); ++ ++ public byte[] getBytes(final K key, final byte[] dfl); ++ ++ public void setBytes(final K key, final byte[] val); ++ ++ public short[] getShorts(final K key); ++ ++ public short[] getShorts(final K key, final short[] dfl); ++ ++ public void setShorts(final K key, final short[] val); ++ ++ public int[] getInts(final K key); ++ ++ public int[] getInts(final K key, final int[] dfl); ++ ++ public void setInts(final K key, final int[] val); ++ ++ public long[] getLongs(final K key); ++ ++ public long[] getLongs(final K key, final long[] dfl); ++ ++ public void setLongs(final K key, final long[] val); ++ ++ public ListType getListUnchecked(final K key); ++ ++ public ListType getListUnchecked(final K key, final ListType dfl); ++ ++ public default ListType getList(final K key, final ObjectType type) { ++ return this.getList(key, type, null); ++ } ++ ++ public default ListType getOrCreateList(final K key, final ObjectType type) { ++ ListType ret = this.getList(key, type); ++ if (ret == null) { ++ this.setList(key, ret = this.getTypeUtil().createEmptyList()); ++ } ++ ++ return ret; ++ } ++ ++ public default ListType getList(final K key, final ObjectType type, final ListType dfl) { ++ final ListType ret = this.getListUnchecked(key, null); ++ final ObjectType retType; ++ if (ret != null && ((retType = ret.getType()) == type || retType == ObjectType.UNDEFINED)) { ++ return ret; ++ } else { ++ return dfl; ++ } ++ } ++ ++ public void setList(final K key, final ListType val); ++ ++ public MapType getMap(final K key); ++ ++ public default MapType getOrCreateMap(final K key) { ++ MapType ret = this.getMap(key); ++ if (ret == null) { ++ this.setMap(key, ret = this.getTypeUtil().createEmptyMap()); ++ } ++ ++ return ret; ++ } ++ ++ public MapType getMap(final K key, final MapType dfl); ++ ++ public void setMap(final K key, final MapType val); ++ ++ public String getString(final K key); ++ ++ public String getString(final K key, final String dfl); ++ ++ public void setString(final K key, final String val); ++ ++ public default void setGeneric(final K key, final Object value) { ++ if (value instanceof Boolean) { ++ this.setBoolean(key, ((Boolean)value).booleanValue()); ++ } else if (value instanceof Number) { ++ if (value instanceof Byte) { ++ this.setByte(key, ((Byte)value).byteValue()); ++ return; ++ } else if (value instanceof Short) { ++ this.setShort(key, ((Short)value).shortValue()); ++ return; ++ } else if (value instanceof Integer) { ++ this.setInt(key, ((Integer)value).intValue()); ++ return; ++ } else if (value instanceof Long) { ++ this.setLong(key, ((Long)value).longValue()); ++ return; ++ } else if (value instanceof Float) { ++ this.setFloat(key, ((Float)value).floatValue()); ++ return; ++ } else if (value instanceof Double) { ++ this.setDouble(key, ((Double)value).doubleValue()); ++ return; ++ } // else fall through to throw ++ } else if (value instanceof MapType) { ++ this.setMap(key, (MapType)value); ++ return; ++ } else if (value instanceof ListType) { ++ this.setList(key, (ListType)value); ++ return; ++ } else if (value instanceof String) { ++ this.setString(key, (String)value); ++ return; ++ } else if (value.getClass().isArray()) { ++ if (value instanceof byte[]) { ++ this.setBytes(key, (byte[])value); ++ return; ++ } else if (value instanceof short[]) { ++ this.setShorts(key, (short[])value); ++ return; ++ } else if (value instanceof int[]) { ++ this.setInts(key, (int[])value); ++ return; ++ } else if (value instanceof long[]) { ++ this.setLongs(key, (long[])value); ++ return; ++ } // else fall through to throw ++ } ++ ++ throw new IllegalArgumentException("Object " + value + " is not a valid type!"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java b/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1aab91233ddb98c3af5d424bac120891f1ee16c7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java +@@ -0,0 +1,72 @@ ++package ca.spottedleaf.dataconverter.types; ++ ++public enum ObjectType { ++ NONE(null), ++ BYTE(Byte.class), ++ SHORT(Short.class), ++ INT(Integer.class), ++ LONG(Long.class), ++ FLOAT(Float.class), ++ DOUBLE(Double.class), ++ NUMBER(Number.class), ++ BYTE_ARRAY(byte[].class), ++ SHORT_ARRAY(short[].class), ++ INT_ARRAY(int[].class), ++ LONG_ARRAY(long[].class), ++ LIST(ListType.class), ++ MAP(MapType.class), ++ STRING(String.class), ++ UNDEFINED(null); ++ ++ private final Class clazz; ++ private final boolean isNumber; ++ ++ private ObjectType(final Class clazz) { ++ this.clazz = clazz; ++ this.isNumber = clazz != null && Number.class.isAssignableFrom(clazz); ++ } ++ ++ public boolean isNumber() { ++ return this.isNumber; ++ } ++ ++ public Class getObjectClass() { ++ return this.clazz; ++ } ++ ++ public static ObjectType getType(final Object object) { ++ if (object instanceof Number) { ++ if (object instanceof Byte) { ++ return BYTE; ++ } else if (object instanceof Short) { ++ return SHORT; ++ } else if (object instanceof Integer) { ++ return INT; ++ } else if (object instanceof Long) { ++ return LONG; ++ } else if (object instanceof Float) { ++ return FLOAT; ++ } else if (object instanceof Double) { ++ return DOUBLE; ++ } // else return null ++ } else if (object instanceof MapType) { ++ return MAP; ++ } else if (object instanceof ListType) { ++ return LIST; ++ } else if (object instanceof String) { ++ return STRING; ++ } else if (object.getClass().isArray()) { ++ if (object instanceof byte[]) { ++ return BYTE_ARRAY; ++ } else if (object instanceof short[]) { ++ return SHORT_ARRAY; ++ } else if (object instanceof int[]) { ++ return INT_ARRAY; ++ } else if (object instanceof long[]) { ++ return LONG_ARRAY; ++ } // else return null ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..156a2ea46f8f88a02e88b50d7bb7be82ecd41919 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java +@@ -0,0 +1,9 @@ ++package ca.spottedleaf.dataconverter.types; ++ ++public interface TypeUtil { ++ ++ public ListType createEmptyList(); ++ ++ public MapType createEmptyMap(); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/Types.java b/src/main/java/ca/spottedleaf/dataconverter/types/Types.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2ab9e3b579f20c9a189518496c522155630a36c4 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/Types.java +@@ -0,0 +1,15 @@ ++package ca.spottedleaf.dataconverter.types; ++ ++import ca.spottedleaf.dataconverter.types.json.JsonTypeCompressedUtil; ++import ca.spottedleaf.dataconverter.types.json.JsonTypeUtil; ++import ca.spottedleaf.dataconverter.types.nbt.NBTTypeUtil; ++ ++public interface Types { ++ ++ public static final TypeUtil NBT = new NBTTypeUtil(); ++ ++ public static final TypeUtil JSON = new JsonTypeUtil(); ++ ++ // why does this exist ++ public static final TypeUtil JSON_COMPRESSED = new JsonTypeCompressedUtil(); ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f6f57cb3a215876976b5eecae810b8b20925f2e2 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java +@@ -0,0 +1,415 @@ ++package ca.spottedleaf.dataconverter.types.json; ++ ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.gson.JsonArray; ++import com.google.gson.JsonElement; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonPrimitive; ++ ++public final class JsonListType implements ListType { ++ ++ protected final JsonArray array; ++ protected final boolean compressed; ++ ++ public JsonListType(final boolean compressed) { ++ this.array = new JsonArray(); ++ this.compressed = compressed; ++ } ++ ++ public JsonListType(final JsonArray array, final boolean compressed) { ++ this.array = array; ++ this.compressed = compressed; ++ } ++ ++ @Override ++ public TypeUtil getTypeUtil() { ++ return this.compressed ? Types.JSON_COMPRESSED : Types.JSON; ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ ++ if (obj == null || obj.getClass() != JsonListType.class) { ++ return false; ++ } ++ ++ return this.array.equals(((JsonListType)obj).array); ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.array.hashCode(); ++ } ++ ++ @Override ++ public String toString() { ++ return "JsonListType{" + ++ "array=" + this.array + ++ ", compressed=" + this.compressed + ++ '}'; ++ } ++ ++ public JsonArray getJson() { ++ return this.array; ++ } ++ ++ @Override ++ public ListType copy() { ++ return new JsonListType(JsonTypeUtil.copyJson(this.array), this.compressed); ++ } ++ ++ @Override ++ public ObjectType getType() { ++ return ObjectType.UNDEFINED; ++ } ++ ++ @Override ++ public int size() { ++ return this.array.size(); ++ } ++ ++ @Override ++ public void remove(final int index) { ++ this.array.remove(index); ++ } ++ ++ @Override ++ public Number getNumber(final int index) { ++ final JsonElement element = this.array.get(index); ++ if (element instanceof JsonPrimitive) { ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isNumber()) { ++ return primitive.getAsNumber(); ++ } else if (primitive.isBoolean()) { ++ return primitive.getAsBoolean() ? Byte.valueOf((byte)1) : Byte.valueOf((byte)0); ++ } else if (this.compressed && primitive.isString()) { ++ try { ++ return Integer.valueOf(Integer.parseInt(primitive.getAsString())); ++ } catch (final NumberFormatException ex) { ++ return null; ++ } ++ } ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public byte getByte(final int index) { ++ final Number number = this.getNumber(index); ++ ++ return number == null ? 0 : number.byteValue(); ++ } ++ ++ @Override ++ public void setByte(final int index, final byte to) { ++ this.array.set(index, new JsonPrimitive(Byte.valueOf(to))); ++ } ++ ++ @Override ++ public short getShort(final int index) { ++ final Number number = this.getNumber(index); ++ ++ return number == null ? 0 : number.shortValue(); ++ } ++ ++ @Override ++ public void setShort(final int index, final short to) { ++ this.array.set(index, new JsonPrimitive(Short.valueOf(to))); ++ } ++ ++ @Override ++ public int getInt(final int index) { ++ final Number number = this.getNumber(index); ++ ++ return number == null ? 0 : number.intValue(); ++ } ++ ++ @Override ++ public void setInt(final int index, final int to) { ++ this.array.set(index, new JsonPrimitive(Integer.valueOf(to))); ++ } ++ ++ @Override ++ public long getLong(final int index) { ++ final Number number = this.getNumber(index); ++ ++ return number == null ? 0 : number.longValue(); ++ } ++ ++ @Override ++ public void setLong(final int index, final long to) { ++ this.array.set(index, new JsonPrimitive(Long.valueOf(to))); ++ } ++ ++ @Override ++ public float getFloat(final int index) { ++ final Number number = this.getNumber(index); ++ ++ return number == null ? 0 : number.floatValue(); ++ } ++ ++ @Override ++ public void setFloat(final int index, final float to) { ++ this.array.set(index, new JsonPrimitive(Float.valueOf(to))); ++ } ++ ++ @Override ++ public double getDouble(final int index) { ++ final Number number = this.getNumber(index); ++ ++ return number == null ? 0 : number.doubleValue(); ++ } ++ ++ @Override ++ public void setDouble(final int index, final double to) { ++ this.array.set(index, new JsonPrimitive(Double.valueOf(to))); ++ } ++ ++ @Override ++ public byte[] getBytes(final int index) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setBytes(final int index, final byte[] to) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public short[] getShorts(final int index) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setShorts(final int index, final short[] to) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int[] getInts(final int index) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setInts(final int index, final int[] to) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public long[] getLongs(final int index) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setLongs(final int index, final long[] to) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public ListType getList(final int index) { ++ final JsonElement element = this.array.get(index); ++ if (element instanceof JsonArray) { ++ return new JsonListType((JsonArray)element, this.compressed); ++ } ++ return null; ++ } ++ ++ @Override ++ public void setList(final int index, final ListType list) { ++ this.array.set(index, ((JsonListType)list).array); ++ } ++ ++ @Override ++ public MapType getMap(final int index) { ++ final JsonElement element = this.array.get(index); ++ if (element instanceof JsonObject) { ++ return new JsonMapType((JsonObject)element, this.compressed); ++ } ++ return null; ++ } ++ ++ @Override ++ public void setMap(final int index, final MapType to) { ++ this.array.set(index, ((JsonMapType)to).map); ++ } ++ ++ @Override ++ public String getString(final int index) { ++ final JsonElement element = this.array.get(index); ++ if (element instanceof JsonPrimitive) { ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isString() || (this.compressed && primitive.isNumber())) { ++ return primitive.getAsString(); ++ } ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public void setString(final int index, final String to) { ++ this.array.set(index, new JsonPrimitive(to)); ++ } ++ ++ @Override ++ public void addByte(final byte b) { ++ this.array.add(Byte.valueOf(b)); ++ } ++ ++ @Override ++ public void addByte(final int index, final byte b) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addShort(final short s) { ++ this.array.add(Short.valueOf(s)); ++ } ++ ++ @Override ++ public void addShort(final int index, final short s) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addInt(final int i) { ++ this.array.add(Integer.valueOf(i)); ++ } ++ ++ @Override ++ public void addInt(final int index, final int i) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addLong(final long l) { ++ this.array.add(Long.valueOf(l)); ++ } ++ ++ @Override ++ public void addLong(final int index, final long l) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addFloat(final float f) { ++ this.array.add(Float.valueOf(f)); ++ } ++ ++ @Override ++ public void addFloat(final int index, final float f) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addDouble(final double d) { ++ this.array.add(Double.valueOf(d)); ++ } ++ ++ @Override ++ public void addDouble(final int index, final double d) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addByteArray(final byte[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addByteArray(final int index, final byte[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addShortArray(final short[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addShortArray(final int index, final short[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addIntArray(final int[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addIntArray(final int index, final int[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addLongArray(final long[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addLongArray(final int index, final long[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addList(final ListType list) { ++ this.array.add(((JsonListType)list).array); ++ } ++ ++ @Override ++ public void addList(final int index, final ListType list) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addMap(final MapType map) { ++ this.array.add(((JsonMapType)map).map); ++ } ++ ++ @Override ++ public void addMap(final int index, final MapType map) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addString(final String string) { ++ this.array.add(string); ++ } ++ ++ @Override ++ public void addString(final int index, final String string) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c89bcc4b9974dd65bad9b096cccf8a4369d47f4f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java +@@ -0,0 +1,450 @@ ++package ca.spottedleaf.dataconverter.types.json; ++ ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.gson.JsonArray; ++import com.google.gson.JsonElement; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonPrimitive; ++import java.util.LinkedHashSet; ++import java.util.Map; ++import java.util.Set; ++ ++public final class JsonMapType implements MapType { ++ ++ protected final JsonObject map; ++ protected final boolean compressed; ++ ++ public JsonMapType(final boolean compressed) { ++ this.map = new JsonObject(); ++ this.compressed = compressed; ++ } ++ ++ public JsonMapType(final JsonObject map, final boolean compressed) { ++ this.map = map; ++ this.compressed = compressed; ++ } ++ ++ @Override ++ public TypeUtil getTypeUtil() { ++ return this.compressed ? Types.JSON_COMPRESSED : Types.JSON; ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ ++ if (obj == null || obj.getClass() != JsonMapType.class) { ++ return false; ++ } ++ ++ return this.map.equals(((JsonMapType)obj).map); ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.map.hashCode(); ++ } ++ ++ @Override ++ public String toString() { ++ return "JsonMapType{" + ++ "map=" + this.map + ++ ", compressed=" + this.compressed + ++ '}'; ++ } ++ ++ public JsonObject getJson() { ++ return this.map; ++ } ++ ++ @Override ++ public int size() { ++ return this.map.entrySet().size(); ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return this.map.entrySet().isEmpty(); ++ } ++ ++ @Override ++ public void clear() { ++ this.map.entrySet().clear(); ++ } ++ ++ @Override ++ public Set keys() { ++ // ah shit. no keyset method ++ final Set keys = new LinkedHashSet<>(); ++ ++ for (final Map.Entry entry : this.map.entrySet()) { ++ keys.add(entry.getKey()); ++ } ++ ++ return keys; ++ } ++ ++ @Override ++ public MapType copy() { ++ return new JsonMapType(JsonTypeUtil.copyJson(this.map), this.compressed); ++ } ++ ++ @Override ++ public boolean hasKey(final String key) { ++ return this.map.has(key); ++ } ++ ++ @Override ++ public boolean hasKey(final String key, final ObjectType type) { ++ final JsonElement element = this.map.get(key); ++ if (element == null) { ++ return false; ++ } ++ ++ if (type == ObjectType.UNDEFINED) { ++ return true; ++ } ++ ++ if (element.isJsonArray()) { ++ return type == ObjectType.LIST; ++ } else if (element.isJsonObject()) { ++ return type == ObjectType.MAP; ++ } else if (element.isJsonNull()) { ++ return false; ++ } ++ ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isString()) { ++ return type == ObjectType.STRING || (this.compressed && type == ObjectType.NUMBER); ++ } else if (primitive.isBoolean()) { ++ return type.isNumber(); ++ } else { ++ // is number ++ final Number number = primitive.getAsNumber(); ++ if (number instanceof Byte) { ++ return type == ObjectType.BYTE || (this.compressed && type == ObjectType.STRING); ++ } else if (number instanceof Short) { ++ return type == ObjectType.SHORT || (this.compressed && type == ObjectType.STRING); ++ } else if (number instanceof Integer) { ++ return type == ObjectType.INT || (this.compressed && type == ObjectType.STRING); ++ } else if (number instanceof Long) { ++ return type == ObjectType.LONG || (this.compressed && type == ObjectType.STRING); ++ } else if (number instanceof Float) { ++ return type == ObjectType.FLOAT || (this.compressed && type == ObjectType.STRING); ++ } else { ++ return type == ObjectType.DOUBLE || (this.compressed && type == ObjectType.STRING); ++ } ++ } ++ } ++ ++ @Override ++ public void remove(final String key) { ++ this.map.remove(key); ++ } ++ ++ @Override ++ public Object getGeneric(final String key) { ++ final JsonElement element = this.map.get(key); ++ if (element == null || element.isJsonNull()) { ++ return null; ++ } else if (element.isJsonObject()) { ++ return new JsonMapType((JsonObject)element, this.compressed); ++ } else if (element.isJsonArray()) { ++ return new JsonListType((JsonArray)element, this.compressed); ++ } else { ++ // primitive ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isNumber()) { ++ return primitive.getAsNumber(); ++ } else if (primitive.isString()) { ++ return primitive.getAsString(); ++ } else if (primitive.isBoolean()) { ++ return Boolean.valueOf(primitive.getAsBoolean()); ++ } else { ++ throw new IllegalStateException("Unknown json object " + element); ++ } ++ } ++ } ++ ++ @Override ++ public Number getNumber(final String key) { ++ return this.getNumber(key, null); ++ } ++ ++ @Override ++ public Number getNumber(final String key, final Number dfl) { ++ final JsonElement element = this.map.get(key); ++ if (element instanceof JsonPrimitive) { ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isNumber()) { ++ return primitive.getAsNumber(); ++ } else if (primitive.isBoolean()) { ++ return primitive.getAsBoolean() ? Byte.valueOf((byte)1) : Byte.valueOf((byte)0); ++ } else if (this.compressed && primitive.isString()) { ++ try { ++ return Integer.valueOf(Integer.parseInt(primitive.getAsString())); ++ } catch (final NumberFormatException ex) { ++ return null; ++ } ++ } ++ } ++ ++ return dfl; ++ } ++ ++ @Override ++ public boolean getBoolean(final String key) { ++ return this.getBoolean(key, false); ++ } ++ ++ @Override ++ public boolean getBoolean(final String key, final boolean dfl) { ++ final JsonElement element = this.map.get(key); ++ if (element instanceof JsonPrimitive) { ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isNumber()) { ++ return primitive.getAsNumber().byteValue() != 0; ++ } else if (primitive.isBoolean()) { ++ return primitive.getAsBoolean(); ++ } ++ } ++ ++ return dfl; ++ } ++ ++ @Override ++ public void setBoolean(final String key, final boolean val) { ++ this.map.addProperty(key, Boolean.valueOf(val)); ++ } ++ ++ @Override ++ public byte getByte(final String key) { ++ return this.getByte(key, (byte)0); ++ } ++ ++ @Override ++ public byte getByte(final String key, final byte dfl) { ++ final Number ret = this.getNumber(key, null); ++ return ret == null ? dfl : ret.byteValue(); ++ } ++ ++ @Override ++ public void setByte(final String key, final byte val) { ++ this.map.addProperty(key, Byte.valueOf(val)); ++ } ++ ++ @Override ++ public short getShort(final String key) { ++ return this.getShort(key, (short)0); ++ } ++ ++ @Override ++ public short getShort(final String key, final short dfl) { ++ final Number ret = this.getNumber(key, null); ++ return ret == null ? dfl : ret.shortValue(); ++ } ++ ++ @Override ++ public void setShort(final String key, final short val) { ++ this.map.addProperty(key, Short.valueOf(val)); ++ } ++ ++ @Override ++ public int getInt(final String key) { ++ return this.getInt(key, 0); ++ } ++ ++ @Override ++ public int getInt(final String key, final int dfl) { ++ final Number ret = this.getNumber(key, null); ++ return ret == null ? dfl : ret.intValue(); ++ } ++ ++ @Override ++ public void setInt(final String key, final int val) { ++ this.map.addProperty(key, Integer.valueOf(val)); ++ } ++ ++ @Override ++ public long getLong(final String key) { ++ return this.getLong(key, 0L); ++ } ++ ++ @Override ++ public long getLong(final String key, final long dfl) { ++ final Number ret = this.getNumber(key, null); ++ return ret == null ? dfl : ret.longValue(); ++ } ++ ++ @Override ++ public void setLong(final String key, final long val) { ++ this.map.addProperty(key, Long.valueOf(val)); ++ } ++ ++ @Override ++ public float getFloat(final String key) { ++ return this.getFloat(key, 0.0F); ++ } ++ ++ @Override ++ public float getFloat(final String key, final float dfl) { ++ final Number ret = this.getNumber(key, null); ++ return ret == null ? dfl : ret.floatValue(); ++ } ++ ++ @Override ++ public void setFloat(final String key, final float val) { ++ this.map.addProperty(key, Float.valueOf(val)); ++ } ++ ++ @Override ++ public double getDouble(final String key) { ++ return this.getDouble(key, 0.0D); ++ } ++ ++ @Override ++ public double getDouble(final String key, final double dfl) { ++ final Number ret = this.getNumber(key, null); ++ return ret == null ? dfl : ret.doubleValue(); ++ } ++ ++ @Override ++ public void setDouble(final String key, final double val) { ++ this.map.addProperty(key, Double.valueOf(val)); ++ } ++ ++ @Override ++ public byte[] getBytes(final String key) { ++ return this.getBytes(key, null); ++ } ++ ++ @Override ++ public byte[] getBytes(final String key, final byte[] dfl) { ++ return dfl; ++ } ++ ++ @Override ++ public void setBytes(final String key, final byte[] val) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public short[] getShorts(final String key) { ++ return this.getShorts(key, null); ++ } ++ ++ @Override ++ public short[] getShorts(final String key, final short[] dfl) { ++ return dfl; ++ } ++ ++ @Override ++ public void setShorts(final String key, final short[] val) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int[] getInts(final String key) { ++ return this.getInts(key, null); ++ } ++ ++ @Override ++ public int[] getInts(final String key, final int[] dfl) { ++ return dfl; ++ } ++ ++ @Override ++ public void setInts(final String key, final int[] val) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public long[] getLongs(final String key) { ++ return this.getLongs(key, null); ++ } ++ ++ @Override ++ public long[] getLongs(final String key, final long[] dfl) { ++ return dfl; ++ } ++ ++ @Override ++ public void setLongs(final String key, final long[] val) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public ListType getListUnchecked(final String key) { ++ return this.getListUnchecked(key, null); ++ } ++ ++ @Override ++ public ListType getListUnchecked(final String key, final ListType dfl) { ++ final JsonElement element = this.map.get(key); ++ if (element instanceof JsonArray) { ++ return new JsonListType((JsonArray)element, this.compressed); ++ } ++ ++ return dfl; ++ } ++ ++ @Override ++ public void setList(final String key, final ListType val) { ++ this.map.add(key, ((JsonListType)val).getJson()); ++ } ++ ++ @Override ++ public MapType getMap(final String key) { ++ return this.getMap(key, null); ++ } ++ ++ @Override ++ public MapType getMap(final String key, final MapType dfl) { ++ final JsonElement element = this.map.get(key); ++ if (element instanceof JsonObject) { ++ return new JsonMapType((JsonObject)element, this.compressed); ++ } ++ ++ return dfl; ++ } ++ ++ @Override ++ public void setMap(final String key, final MapType val) { ++ this.map.add(key, ((JsonMapType)val).map); ++ } ++ ++ @Override ++ public String getString(final String key) { ++ return this.getString(key, null); ++ } ++ ++ @Override ++ public String getString(final String key, final String dfl) { ++ final JsonElement element = this.map.get(key); ++ if (element instanceof JsonPrimitive) { ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isString()) { ++ return primitive.getAsString(); ++ } else if (this.compressed && primitive.isNumber()) { ++ return primitive.getAsString(); ++ } ++ } ++ ++ return dfl; ++ } ++ ++ @Override ++ public void setString(final String key, final String val) { ++ this.map.addProperty(key, val); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9c3093b66b847b5248bde923243fce78842bf67f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.types.json; ++ ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++ ++public final class JsonTypeCompressedUtil implements TypeUtil { ++ ++ @Override ++ public ListType createEmptyList() { ++ return new JsonListType(true); ++ } ++ ++ @Override ++ public MapType createEmptyMap() { ++ return new JsonMapType(true); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9410ae68395a09c7710bdbb2ccc6acf6633cad23 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java +@@ -0,0 +1,81 @@ ++package ca.spottedleaf.dataconverter.types.json; ++ ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++import ca.spottedleaf.dataconverter.types.nbt.NBTListType; ++import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; ++import com.google.gson.JsonArray; ++import com.google.gson.JsonElement; ++import com.google.gson.JsonNull; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonPrimitive; ++import com.google.gson.internal.Streams; ++import com.google.gson.stream.JsonReader; ++import java.io.StringReader; ++import java.util.Map; ++ ++public final class JsonTypeUtil implements TypeUtil { ++ ++ @Override ++ public ListType createEmptyList() { ++ return new JsonListType(false); ++ } ++ ++ @Override ++ public MapType createEmptyMap() { ++ return new JsonMapType(false); ++ } ++ ++ public static T copyJson(final T from) { ++ // This is stupidly inefficient. However, deepCopy() is not exposed in this gson version. ++ final String out = from.toString(); ++ ++ return (T)Streams.parse(new JsonReader(new StringReader(out))); ++ } ++ ++ private static Object convertToGenericNBT(final JsonElement element, final boolean compressed) { ++ if (element instanceof JsonObject) { ++ return convertJsonToNBT(new JsonMapType((JsonObject)element, compressed)); ++ } else if (element instanceof JsonArray) { ++ return convertJsonToNBT(new JsonListType((JsonArray)element, compressed)); ++ } else if (element instanceof JsonNull) { ++ return null; ++ } else { ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isBoolean()) { ++ return primitive.getAsBoolean() ? Byte.valueOf((byte)1) : Byte.valueOf((byte)0); ++ } else if (primitive.isNumber()) { ++ return primitive.getAsNumber(); ++ } else if (primitive.isString()) { ++ return primitive.getAsString(); ++ } ++ } ++ ++ throw new IllegalStateException("Unrecognized type " + element); ++ } ++ ++ public static NBTMapType convertJsonToNBT(final JsonMapType json) { ++ final NBTMapType ret = new NBTMapType(); ++ for (final Map.Entry entry : json.map.entrySet()) { ++ final Object obj = convertToGenericNBT(entry.getValue(), json.compressed); ++ if (obj == null) { ++ continue; ++ } ++ ++ ret.setGeneric(entry.getKey(), obj); ++ } ++ ++ return ret; ++ } ++ ++ public static NBTListType convertJsonToNBT(final JsonListType json) { ++ final NBTListType ret = new NBTListType(); ++ ++ for (int i = 0, len = json.size(); i < len; ++i) { ++ ret.addGeneric(convertToGenericNBT(json.array.get(i), json.compressed)); ++ } ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bf4e9ea17222cfa8f7cee9e46775302c9c2e6328 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java +@@ -0,0 +1,440 @@ ++package ca.spottedleaf.dataconverter.types.nbt; ++ ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++import ca.spottedleaf.dataconverter.types.Types; ++import net.minecraft.nbt.ByteArrayTag; ++import net.minecraft.nbt.ByteTag; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.DoubleTag; ++import net.minecraft.nbt.FloatTag; ++import net.minecraft.nbt.IntArrayTag; ++import net.minecraft.nbt.IntTag; ++import net.minecraft.nbt.ListTag; ++import net.minecraft.nbt.LongArrayTag; ++import net.minecraft.nbt.LongTag; ++import net.minecraft.nbt.NumericTag; ++import net.minecraft.nbt.ShortTag; ++import net.minecraft.nbt.StringTag; ++import net.minecraft.nbt.Tag; ++ ++public final class NBTListType implements ListType { ++ ++ private final ListTag list; ++ ++ public NBTListType() { ++ this.list = new ListTag(); ++ } ++ ++ public NBTListType(final ListTag tag) { ++ this.list = tag; ++ } ++ ++ @Override ++ public TypeUtil getTypeUtil() { ++ return Types.NBT; ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ if (obj == null || obj.getClass() != NBTListType.class) { ++ return false; ++ } ++ ++ return this.list.equals(((NBTListType)obj).list); ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.list.hashCode(); ++ } ++ ++ @Override ++ public String toString() { ++ return "NBTListType{" + ++ "list=" + this.list + ++ '}'; ++ } ++ ++ public ListTag getTag() { ++ return this.list; ++ } ++ ++ @Override ++ public ListType copy() { ++ return new NBTListType(this.list.copy()); ++ } ++ ++ protected static ObjectType getType(final byte id) { ++ switch (id) { ++ case 0: // END ++ return ObjectType.NONE; ++ case 1: // BYTE ++ return ObjectType.BYTE; ++ case 2: // SHORT ++ return ObjectType.SHORT; ++ case 3: // INT ++ return ObjectType.INT; ++ case 4: // LONG ++ return ObjectType.LONG; ++ case 5: // FLOAT ++ return ObjectType.FLOAT; ++ case 6: // DOUBLE ++ return ObjectType.DOUBLE; ++ case 7: // BYTE_ARRAY ++ return ObjectType.BYTE_ARRAY; ++ case 8: // STRING ++ return ObjectType.STRING; ++ case 9: // LIST ++ return ObjectType.LIST; ++ case 10: // COMPOUND ++ return ObjectType.MAP; ++ case 11: // INT_ARRAY ++ return ObjectType.INT_ARRAY; ++ case 12: // LONG_ARRAY ++ return ObjectType.LONG_ARRAY; ++ default: ++ throw new IllegalStateException("Unknown type: " + id); ++ } ++ } ++ ++ @Override ++ public ObjectType getType() { ++ return getType(this.list.getElementType()); ++ } ++ ++ @Override ++ public int size() { ++ return this.list.size(); ++ } ++ ++ @Override ++ public void remove(final int index) { ++ this.list.remove(index); ++ } ++ ++ @Override ++ public Number getNumber(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsNumber(); ++ } ++ ++ @Override ++ public byte getByte(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsByte(); ++ } ++ ++ @Override ++ public void setByte(final int index, final byte to) { ++ this.list.set(index, ByteTag.valueOf(to)); ++ } ++ ++ @Override ++ public short getShort(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsShort(); ++ } ++ ++ @Override ++ public void setShort(final int index, final short to) { ++ this.list.set(index, ShortTag.valueOf(to)); ++ } ++ ++ @Override ++ public int getInt(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsInt(); ++ } ++ ++ @Override ++ public void setInt(final int index, final int to) { ++ this.list.set(index, IntTag.valueOf(to)); ++ } ++ ++ @Override ++ public long getLong(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsLong(); ++ } ++ ++ @Override ++ public void setLong(final int index, final long to) { ++ this.list.set(index, LongTag.valueOf(to)); ++ } ++ ++ @Override ++ public float getFloat(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsFloat(); ++ } ++ ++ @Override ++ public void setFloat(final int index, final float to) { ++ this.list.set(index, FloatTag.valueOf(to)); ++ } ++ ++ @Override ++ public double getDouble(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsDouble(); ++ } ++ ++ @Override ++ public void setDouble(final int index, final double to) { ++ this.list.set(index, DoubleTag.valueOf(to)); ++ } ++ ++ @Override ++ public byte[] getBytes(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof ByteArrayTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((ByteArrayTag)tag).getAsByteArray(); ++ } ++ ++ @Override ++ public void setBytes(final int index, final byte[] to) { ++ this.list.set(index, new ByteArrayTag(to)); ++ } ++ ++ @Override ++ public short[] getShorts(final int index) { ++ // NBT does not support shorts ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setShorts(final int index, final short[] to) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int[] getInts(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof IntArrayTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((IntArrayTag)tag).getAsIntArray(); ++ } ++ ++ @Override ++ public void setInts(final int index, final int[] to) { ++ this.list.set(index, new IntArrayTag(to)); ++ } ++ ++ @Override ++ public long[] getLongs(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof LongArrayTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((LongArrayTag)tag).getAsLongArray(); ++ } ++ ++ @Override ++ public void setLongs(final int index, final long[] to) { ++ this.list.set(index, new LongArrayTag(to)); ++ } ++ ++ @Override ++ public ListType getList(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof ListTag)) { ++ throw new IllegalStateException(); ++ } ++ return new NBTListType((ListTag)tag); ++ } ++ ++ @Override ++ public void setList(final int index, final ListType list) { ++ this.list.set(index, ((NBTListType)list).getTag()); ++ } ++ ++ @Override ++ public MapType getMap(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof CompoundTag)) { ++ throw new IllegalStateException(); ++ } ++ return new NBTMapType((CompoundTag)tag); ++ } ++ ++ @Override ++ public void setMap(final int index, final MapType to) { ++ this.list.set(index, ((NBTMapType)to).getTag()); ++ } ++ ++ @Override ++ public String getString(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof StringTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((StringTag)tag).getAsString(); ++ } ++ ++ @Override ++ public void setString(final int index, final String to) { ++ this.list.set(index, StringTag.valueOf(to)); ++ } ++ ++ @Override ++ public void addByte(final byte b) { ++ this.list.add(ByteTag.valueOf(b)); ++ } ++ ++ @Override ++ public void addByte(final int index, final byte b) { ++ this.list.add(index, ByteTag.valueOf(b)); ++ } ++ ++ @Override ++ public void addShort(final short s) { ++ this.list.add(ShortTag.valueOf(s)); ++ } ++ ++ @Override ++ public void addShort(final int index, final short s) { ++ this.list.add(index, ShortTag.valueOf(s)); ++ } ++ ++ @Override ++ public void addInt(final int i) { ++ this.list.add(IntTag.valueOf(i)); ++ } ++ ++ @Override ++ public void addInt(final int index, final int i) { ++ this.list.add(index, IntTag.valueOf(i)); ++ } ++ ++ @Override ++ public void addLong(final long l) { ++ this.list.add(LongTag.valueOf(l)); ++ } ++ ++ @Override ++ public void addLong(final int index, final long l) { ++ this.list.add(index, LongTag.valueOf(l)); ++ } ++ ++ @Override ++ public void addFloat(final float f) { ++ this.list.add(FloatTag.valueOf(f)); ++ } ++ ++ @Override ++ public void addFloat(final int index, final float f) { ++ this.list.add(index, FloatTag.valueOf(f)); ++ } ++ ++ @Override ++ public void addDouble(final double d) { ++ this.list.add(DoubleTag.valueOf(d)); ++ } ++ ++ @Override ++ public void addDouble(final int index, final double d) { ++ this.list.add(index, DoubleTag.valueOf(d)); ++ } ++ ++ @Override ++ public void addByteArray(final byte[] arr) { ++ this.list.add(new ByteArrayTag(arr)); ++ } ++ ++ @Override ++ public void addByteArray(final int index, final byte[] arr) { ++ this.list.add(index, new ByteArrayTag(arr)); ++ } ++ ++ @Override ++ public void addShortArray(final short[] arr) { ++ // NBT does not support short[] ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addShortArray(final int index, final short[] arr) { ++ // NBT does not support short[] ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addIntArray(final int[] arr) { ++ this.list.add(new IntArrayTag(arr)); ++ } ++ ++ @Override ++ public void addIntArray(final int index, final int[] arr) { ++ this.list.add(index, new IntArrayTag(arr)); ++ } ++ ++ @Override ++ public void addLongArray(final long[] arr) { ++ this.list.add(new LongArrayTag(arr)); ++ } ++ ++ @Override ++ public void addLongArray(final int index, final long[] arr) { ++ this.list.add(index, new LongArrayTag(arr)); ++ } ++ ++ @Override ++ public void addList(final ListType list) { ++ this.list.add(((NBTListType)list).getTag()); ++ } ++ ++ @Override ++ public void addList(final int index, final ListType list) { ++ this.list.add(index, ((NBTListType)list).getTag()); ++ } ++ ++ @Override ++ public void addMap(final MapType map) { ++ this.list.add(((NBTMapType)map).getTag()); ++ } ++ ++ @Override ++ public void addMap(final int index, final MapType map) { ++ this.list.add(index, ((NBTMapType)map).getTag()); ++ } ++ ++ @Override ++ public void addString(final String string) { ++ this.list.add(StringTag.valueOf(string)); ++ } ++ ++ @Override ++ public void addString(final int index, final String string) { ++ this.list.add(index, StringTag.valueOf(string)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..12880f93b53db1e60cbf13805e2eb08fee5fd203 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java +@@ -0,0 +1,440 @@ ++package ca.spottedleaf.dataconverter.types.nbt; ++ ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++import ca.spottedleaf.dataconverter.types.Types; ++import net.minecraft.nbt.ByteArrayTag; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.IntArrayTag; ++import net.minecraft.nbt.ListTag; ++import net.minecraft.nbt.LongArrayTag; ++import net.minecraft.nbt.NumericTag; ++import net.minecraft.nbt.StringTag; ++import net.minecraft.nbt.Tag; ++ ++import java.util.Set; ++ ++public final class NBTMapType implements MapType { ++ ++ private final CompoundTag map; ++ ++ public NBTMapType() { ++ this.map = new CompoundTag(); ++ } ++ ++ public NBTMapType(final CompoundTag tag) { ++ this.map = tag; ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ if (obj == null || obj.getClass() != NBTMapType.class) { ++ return false; ++ } ++ ++ return this.map.equals(((NBTMapType)obj).map); ++ } ++ ++ @Override ++ public TypeUtil getTypeUtil() { ++ return Types.NBT; ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.map.hashCode(); ++ } ++ ++ @Override ++ public String toString() { ++ return "NBTMapType{" + ++ "map=" + this.map + ++ '}'; ++ } ++ ++ @Override ++ public int size() { ++ return this.map.size(); ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return this.map.isEmpty(); ++ } ++ ++ @Override ++ public void clear() { ++ this.map.getAllKeys().clear(); ++ } ++ ++ @Override ++ public Set keys() { ++ return this.map.getAllKeys(); ++ } ++ ++ public CompoundTag getTag() { ++ return this.map; ++ } ++ ++ @Override ++ public MapType copy() { ++ return new NBTMapType(this.map.copy()); ++ } ++ ++ @Override ++ public boolean hasKey(final String key) { ++ return this.map.get(key) != null; ++ } ++ ++ @Override ++ public boolean hasKey(final String key, final ObjectType type) { ++ final Tag tag = this.map.get(key); ++ if (tag == null) { ++ return false; ++ } ++ ++ final ObjectType valueType = NBTListType.getType(tag.getId()); ++ ++ return valueType == type || (type == ObjectType.NUMBER && valueType.isNumber()); ++ } ++ ++ @Override ++ public void remove(final String key) { ++ this.map.remove(key); ++ } ++ ++ @Override ++ public Object getGeneric(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag == null) { ++ return null; ++ } ++ ++ switch (NBTListType.getType(tag.getId())) { ++ case BYTE: ++ case SHORT: ++ case INT: ++ case LONG: ++ case FLOAT: ++ case DOUBLE: ++ return ((NumericTag)tag).getAsNumber(); ++ case MAP: ++ return new NBTMapType((CompoundTag)tag); ++ case LIST: ++ return new NBTListType((ListTag)tag); ++ case STRING: ++ return ((StringTag)tag).getAsString(); ++ case BYTE_ARRAY: ++ return ((ByteArrayTag)tag).getAsByteArray(); ++ // Note: No short array tag! ++ case INT_ARRAY: ++ return ((IntArrayTag)tag).getAsIntArray(); ++ case LONG_ARRAY: ++ return ((LongArrayTag)tag).getAsLongArray(); ++ } ++ ++ throw new IllegalStateException("Unrecognized type " + tag); ++ } ++ ++ @Override ++ public Number getNumber(final String key) { ++ return this.getNumber(key, null); ++ } ++ ++ @Override ++ public Number getNumber(final String key, final Number dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsNumber(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public boolean getBoolean(final String key) { ++ return this.getByte(key) != 0; ++ } ++ ++ @Override ++ public boolean getBoolean(final String key, final boolean dfl) { ++ return this.getByte(key, dfl ? (byte)1 : (byte)0) != 0; ++ } ++ ++ @Override ++ public void setBoolean(final String key, final boolean val) { ++ this.setByte(key, val ? (byte)1 : (byte)0); ++ } ++ ++ @Override ++ public byte getByte(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsByte(); ++ } ++ return 0; ++ } ++ ++ @Override ++ public byte getByte(final String key, final byte dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsByte(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setByte(final String key, final byte val) { ++ this.map.putByte(key, val); ++ } ++ ++ @Override ++ public short getShort(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsShort(); ++ } ++ return 0; ++ } ++ ++ @Override ++ public short getShort(final String key, final short dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsShort(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setShort(final String key, final short val) { ++ this.map.putShort(key, val); ++ } ++ ++ @Override ++ public int getInt(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsInt(); ++ } ++ return 0; ++ } ++ ++ @Override ++ public int getInt(final String key, final int dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsInt(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setInt(final String key, final int val) { ++ this.map.putInt(key, val); ++ } ++ ++ @Override ++ public long getLong(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsLong(); ++ } ++ return 0; ++ } ++ ++ @Override ++ public long getLong(final String key, final long dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsLong(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setLong(final String key, final long val) { ++ this.map.putLong(key, val); ++ } ++ ++ @Override ++ public float getFloat(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsFloat(); ++ } ++ return 0; ++ } ++ ++ @Override ++ public float getFloat(final String key, final float dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsFloat(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setFloat(final String key, final float val) { ++ this.map.putFloat(key, val); ++ } ++ ++ @Override ++ public double getDouble(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsDouble(); ++ } ++ return 0; ++ } ++ ++ @Override ++ public double getDouble(final String key, final double dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsDouble(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setDouble(final String key, final double val) { ++ this.map.putDouble(key, val); ++ } ++ ++ @Override ++ public byte[] getBytes(final String key) { ++ return this.getBytes(key, null); ++ } ++ ++ @Override ++ public byte[] getBytes(final String key, final byte[] dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof ByteArrayTag) { ++ return ((ByteArrayTag)tag).getAsByteArray(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setBytes(final String key, final byte[] val) { ++ this.map.putByteArray(key, val); ++ } ++ ++ @Override ++ public short[] getShorts(final String key) { ++ return this.getShorts(key, null); ++ } ++ ++ @Override ++ public short[] getShorts(final String key, final short[] dfl) { ++ // NBT does not support short array ++ return dfl; ++ } ++ ++ @Override ++ public void setShorts(final String key, final short[] val) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int[] getInts(final String key) { ++ return this.getInts(key, null); ++ } ++ ++ @Override ++ public int[] getInts(final String key, final int[] dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof IntArrayTag) { ++ return ((IntArrayTag)tag).getAsIntArray(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setInts(final String key, final int[] val) { ++ this.map.putIntArray(key, val); ++ } ++ ++ @Override ++ public long[] getLongs(final String key) { ++ return this.getLongs(key, null); ++ } ++ ++ @Override ++ public long[] getLongs(final String key, final long[] dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof LongArrayTag) { ++ return ((LongArrayTag)tag).getAsLongArray(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setLongs(final String key, final long[] val) { ++ this.map.putLongArray(key, val); ++ } ++ ++ @Override ++ public ListType getListUnchecked(final String key) { ++ return this.getListUnchecked(key, null); ++ } ++ ++ @Override ++ public ListType getListUnchecked(final String key, final ListType dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof ListTag) { ++ return new NBTListType((ListTag)tag); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setList(final String key, final ListType val) { ++ this.map.put(key, ((NBTListType)val).getTag()); ++ } ++ ++ @Override ++ public MapType getMap(final String key) { ++ return this.getMap(key, null); ++ } ++ ++ @Override ++ public MapType getMap(final String key, final MapType dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof CompoundTag) { ++ return new NBTMapType((CompoundTag)tag); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setMap(final String key, final MapType val) { ++ this.map.put(key, ((NBTMapType)val).getTag()); ++ } ++ ++ @Override ++ public String getString(final String key) { ++ return this.getString(key, null); ++ } ++ ++ @Override ++ public String getString(final String key, final String dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof StringTag) { ++ return ((StringTag)tag).getAsString(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setString(final String key, final String val) { ++ this.map.putString(key, val); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..62c0f4073aff301bf5b3187e0d4446fd8d0ac475 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.types.nbt; ++ ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++ ++public final class NBTTypeUtil implements TypeUtil { ++ ++ @Override ++ public ListType createEmptyList() { ++ return new NBTListType(); ++ } ++ ++ @Override ++ public MapType createEmptyMap() { ++ return new NBTMapType(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6596de3d9ebae583c252aa061f0cfdf8778ea1a5 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java +@@ -0,0 +1,77 @@ ++package ca.spottedleaf.dataconverter.util; ++ ++import it.unimi.dsi.fastutil.ints.Int2IntFunction; ++ ++import java.util.Arrays; ++ ++public class Int2IntArraySortedMap { ++ ++ protected int[] key; ++ protected int[] val; ++ protected int size; ++ ++ public Int2IntArraySortedMap() { ++ this.key = new int[8]; ++ this.val = new int[8]; ++ } ++ ++ public int put(final int key, final int value) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ final int current = this.val[index]; ++ this.val[index] = value; ++ return current; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++this.size; ++ ++ this.key[insert] = key; ++ this.val[insert] = value; ++ ++ return 0; ++ } ++ ++ public int computeIfAbsent(final int key, final Int2IntFunction producer) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ return this.val[index]; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++this.size; ++ ++ this.key[insert] = key; ++ ++ return this.val[insert] = producer.apply(key); ++ } ++ ++ public int get(final int key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ return 0; ++ } ++ return this.val[index]; ++ } ++ ++ public int getFloor(final int key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ final int insert = -(index + 1) - 1; ++ return insert < 0 ? 0 : this.val[insert]; ++ } ++ return this.val[index]; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..de9d632489609136c712a9adaee941fd38fad440 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java +@@ -0,0 +1,74 @@ ++package ca.spottedleaf.dataconverter.util; ++ ++import java.util.Arrays; ++import java.util.function.IntFunction; ++ ++public class Int2ObjectArraySortedMap { ++ ++ protected int[] key; ++ protected V[] val; ++ protected int size; ++ ++ public Int2ObjectArraySortedMap() { ++ this.key = new int[8]; ++ this.val = (V[])new Object[8]; ++ } ++ ++ public V put(final int key, final V value) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ final V current = this.val[index]; ++ this.val[index] = value; ++ return current; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++ this.key[insert] = key; ++ this.val[insert] = value; ++ ++ return null; ++ } ++ ++ public V computeIfAbsent(final int key, final IntFunction producer) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ return this.val[index]; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++ this.key[insert] = key; ++ ++ return this.val[insert] = producer.apply(key); ++ } ++ ++ public V get(final int key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ return null; ++ } ++ return this.val[index]; ++ } ++ ++ public V getFloor(final int key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ final int insert = -(index + 1); ++ return this.val[insert]; ++ } ++ return this.val[index]; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4bbf38c812feeb30d2aa5f3fcf482bfcbed79d05 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java +@@ -0,0 +1,239 @@ ++package ca.spottedleaf.dataconverter.util; ++ ++public final class IntegerUtil { ++ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; ++ public static final long HIGH_BIT_U64 = Long.MIN_VALUE; ++ ++ public static int ceilLog2(final int value) { ++ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static long ceilLog2(final long value) { ++ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final int value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final long value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int roundCeilLog2(final int value) { ++ // optimized variant of 1 << (32 - leading(val - 1)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) ++ // HIGH_BIT_32 >>> (-1 + leading(val - 1)) ++ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static long roundCeilLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static int roundFloorLog2(final int value) { ++ // optimized variant of 1 << (31 - leading(val)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - 31 + leading(val)) ++ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); ++ } ++ ++ public static long roundFloorLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); ++ } ++ ++ public static boolean isPowerOfTwo(final int n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ public static boolean isPowerOfTwo(final long n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ public static int getTrailingBit(final int n) { ++ return -n & n; ++ } ++ ++ public static long getTrailingBit(final long n) { ++ return -n & n; ++ } ++ ++ public static int trailingZeros(final int n) { ++ return Integer.numberOfTrailingZeros(n); ++ } ++ ++ public static int trailingZeros(final long n) { ++ return Long.numberOfTrailingZeros(n); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorMultiple(final long numbers) { ++ return (int)(numbers >>> 32); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorShift(final long numbers) { ++ return (int)numbers; ++ } ++ ++ public static long getDivisorNumbers(final int d) { ++ final int ad = branchlessAbs(d); ++ ++ if (ad < 2) { ++ throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d); ++ } ++ ++ final int two31 = 0x80000000; ++ final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour ++ ++ /* ++ Signed usage: ++ int number; ++ long magic = getDivisorNumbers(div); ++ long mul = magic >>> 32; ++ int sign = number >> 31; ++ int result = (int)(((long)number * mul) >>> magic) - sign; ++ */ ++ /* ++ Unsigned usage: ++ int number; ++ long magic = getDivisorNumbers(div); ++ long mul = magic >>> 32; ++ int result = (int)(((long)number * mul) >>> magic); ++ */ ++ ++ int p = 31; ++ ++ // all these variables are UNSIGNED! ++ int t = two31 + (d >>> 31); ++ int anc = t - 1 - (int)((t & mask)%ad); ++ int q1 = (int)((two31 & mask)/(anc & mask)); ++ int r1 = two31 - q1*anc; ++ int q2 = (int)((two31 & mask)/(ad & mask)); ++ int r2 = two31 - q2*ad; ++ int delta; ++ ++ do { ++ p = p + 1; ++ q1 = 2*q1; // Update q1 = 2**p/|nc|. ++ r1 = 2*r1; // Update r1 = rem(2**p, |nc|). ++ if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) ++ q1 = q1 + 1; ++ r1 = r1 - anc; ++ } ++ q2 = 2*q2; // Update q2 = 2**p/|d|. ++ r2 = 2*r2; // Update r2 = rem(2**p, |d|). ++ if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) ++ q2 = q2 + 1; ++ r2 = r2 - ad; ++ } ++ delta = ad - r2; ++ } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); ++ ++ int magicNum = q2 + 1; ++ if (d < 0) { ++ magicNum = -magicNum; ++ } ++ int shift = p; ++ return ((long)magicNum << 32) | shift; ++ } ++ ++ public static int branchlessAbs(final int val) { ++ // -n = -1 ^ n + 1 ++ final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0 ++ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 ++ } ++ ++ public static long branchlessAbs(final long val) { ++ // -n = -1 ^ n + 1 ++ final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0 ++ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 ++ } ++ ++ //https://github.com/skeeto/hash-prospector for hash functions ++ ++ //score = ~590.47984224483832 ++ public static int hash0(int x) { ++ x *= 0x36935555; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ //score = ~310.01596637036749 ++ public static int hash1(int x) { ++ x ^= x >>> 15; ++ x *= 0x356aaaad; ++ x ^= x >>> 17; ++ return x; ++ } ++ ++ public static int hash2(int x) { ++ x ^= x >>> 16; ++ x *= 0x7feb352d; ++ x ^= x >>> 15; ++ x *= 0x846ca68b; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ public static int hash3(int x) { ++ x ^= x >>> 17; ++ x *= 0xed5ad4bb; ++ x ^= x >>> 11; ++ x *= 0xac4c1b51; ++ x ^= x >>> 15; ++ x *= 0x31848bab; ++ x ^= x >>> 14; ++ return x; ++ } ++ ++ //score = ~365.79959673201887 ++ public static long hash1(long x) { ++ x ^= x >>> 27; ++ x *= 0xb24924b71d2d354bL; ++ x ^= x >>> 28; ++ return x; ++ } ++ ++ //h2 hash ++ public static long hash2(long x) { ++ x ^= x >>> 32; ++ x *= 0xd6e8feb86659fd93L; ++ x ^= x >>> 32; ++ x *= 0xd6e8feb86659fd93L; ++ x ^= x >>> 32; ++ return x; ++ } ++ ++ public static long hash3(long x) { ++ x ^= x >>> 45; ++ x *= 0xc161abe5704b6c79L; ++ x ^= x >>> 41; ++ x *= 0xe3e5389aedbc90f7L; ++ x ^= x >>> 56; ++ x *= 0x1f9aba75a52db073L; ++ x ^= x >>> 53; ++ return x; ++ } ++ ++ private IntegerUtil() { ++ throw new RuntimeException(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..94705bb141b550589faa9a0408402d8636c61907 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java +@@ -0,0 +1,76 @@ ++package ca.spottedleaf.dataconverter.util; ++ ++import it.unimi.dsi.fastutil.longs.Long2IntFunction; ++import java.util.Arrays; ++ ++public class Long2IntArraySortedMap { ++ ++ protected long[] key; ++ protected int[] val; ++ protected int size; ++ ++ public Long2IntArraySortedMap() { ++ this.key = new long[8]; ++ this.val = new int[8]; ++ } ++ ++ public int put(final long key, final int value) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ final int current = this.val[index]; ++ this.val[index] = value; ++ return current; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++this.size; ++ ++ this.key[insert] = key; ++ this.val[insert] = value; ++ ++ return 0; ++ } ++ ++ public int computeIfAbsent(final long key, final Long2IntFunction producer) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ return this.val[index]; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++this.size; ++ ++ this.key[insert] = key; ++ ++ return this.val[insert] = producer.apply(key); ++ } ++ ++ public int get(final long key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ return 0; ++ } ++ return this.val[index]; ++ } ++ ++ public int getFloor(final long key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ final int insert = -(index + 1) - 1; ++ return insert < 0 ? 0 : this.val[insert]; ++ } ++ return this.val[index]; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6f634c8825589a23f46ad7b54354475c9a95bd1b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java +@@ -0,0 +1,76 @@ ++package ca.spottedleaf.dataconverter.util; ++ ++import java.util.Arrays; ++import java.util.function.LongFunction; ++ ++public class Long2ObjectArraySortedMap { ++ ++ protected long[] key; ++ protected V[] val; ++ protected int size; ++ ++ public Long2ObjectArraySortedMap() { ++ this.key = new long[8]; ++ this.val = (V[])new Object[8]; ++ } ++ ++ public V put(final long key, final V value) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ final V current = this.val[index]; ++ this.val[index] = value; ++ return current; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++this.size; ++ ++ this.key[insert] = key; ++ this.val[insert] = value; ++ ++ return null; ++ } ++ ++ public V computeIfAbsent(final long key, final LongFunction producer) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ return this.val[index]; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++this.size; ++ ++ this.key[insert] = key; ++ ++ return this.val[insert] = producer.apply(key); ++ } ++ ++ public V get(final long key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ return null; ++ } ++ return this.val[index]; ++ } ++ ++ public V getFloor(final long key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ final int insert = -(index + 1) - 1; ++ return insert < 0 ? null : this.val[insert]; ++ } ++ return this.val[index]; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java b/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5a6536377c9c1e1753e930ff2a6bb98ea57055c7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java +@@ -0,0 +1,39 @@ ++package ca.spottedleaf.dataconverter.util; ++ ++import ca.spottedleaf.dataconverter.types.MapType; ++import net.minecraft.resources.ResourceLocation; ++ ++public final class NamespaceUtil { ++ ++ private NamespaceUtil() {} ++ ++ public static void enforceForPath(final MapType data, final String path) { ++ if (data == null) { ++ return; ++ } ++ ++ final String id = data.getString(path); ++ if (id != null) { ++ final String replace = NamespaceUtil.correctNamespaceOrNull(id); ++ if (replace != null) { ++ data.setString(path, replace); ++ } ++ } ++ } ++ ++ public static String correctNamespace(final String value) { ++ if (value == null) { ++ return null; ++ } ++ final ResourceLocation resourceLocation = ResourceLocation.tryParse(value); ++ return resourceLocation != null ? resourceLocation.toString() : value; ++ } ++ ++ public static String correctNamespaceOrNull(final String value) { ++ if (value == null) { ++ return null; ++ } ++ final String correct = correctNamespace(value); ++ return correct.equals(value) ? null : correct; ++ } ++} +diff --git a/src/main/java/net/minecraft/data/structures/StructureUpdater.java b/src/main/java/net/minecraft/data/structures/StructureUpdater.java +index 2939fad9c86f358b317f815d6efff0f41f6a3ea8..3e4cd09fc37d72d22a0f966039d1e65b1d80cc84 100644 +--- a/src/main/java/net/minecraft/data/structures/StructureUpdater.java ++++ b/src/main/java/net/minecraft/data/structures/StructureUpdater.java +@@ -25,7 +25,7 @@ public class StructureUpdater implements SnbtToNbt.Filter { + LOGGER.warn("SNBT Too old, do not forget to update: {} < {}: {}", i, 3678, name); + } + +- CompoundTag compoundTag = DataFixTypes.STRUCTURE.updateToCurrentVersion(DataFixers.getDataFixer(), nbt, i); ++ CompoundTag compoundTag = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.STRUCTURE, nbt, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper + structureTemplate.load(BuiltInRegistries.BLOCK.asLookup(), compoundTag); + return structureTemplate.save(new CompoundTag()); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index 6743dca44e6552ad39aca757a24f3c4df400d83d..eebaf98bc0fa4696af59b2a79563beb73501a554 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -79,7 +79,7 @@ public class ChunkStorage implements AutoCloseable { + int i = ChunkStorage.getVersion(nbttagcompound); + + // CraftBukkit start +- if (i < 1466) { ++ if (false && i < 1466) { // Paper - no longer needed, data converter system handles it now + CompoundTag level = nbttagcompound.getCompound("Level"); + if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) { + ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource(); +@@ -91,7 +91,7 @@ public class ChunkStorage implements AutoCloseable { + // CraftBukkit end + + if (i < 1493) { +- nbttagcompound = DataFixTypes.CHUNK.update(this.fixerUpper, nbttagcompound, i, 1493); ++ ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, i, 1493); // Paper - replace chunk converter + if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) { + LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier); + +@@ -109,7 +109,7 @@ public class ChunkStorage implements AutoCloseable { + // Spigot end + + ChunkStorage.injectDatafixingContext(nbttagcompound, resourcekey, optional); +- nbttagcompound = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbttagcompound, Math.max(1493, i)); ++ nbttagcompound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, Math.max(1493, i), SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace chunk converter + if (i < SharedConstants.getCurrentVersion().getDataVersion().getVersion()) { + NbtUtils.addCurrentDataVersion(nbttagcompound); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java +index d80580574a9e5d1c850270d93807f3a66a9c76f8..98b3909b536f11eda9c481ffd74066ad0cdb0ebc 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java +@@ -115,7 +115,7 @@ public class EntityStorage implements EntityPersistentStorage { + + private CompoundTag upgradeChunkTag(CompoundTag chunkNbt) { + int i = NbtUtils.getDataVersion(chunkNbt, -1); +- return DataFixTypes.ENTITY_CHUNK.updateToCurrentVersion(this.fixerUpper, chunkNbt, i); ++ return ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY_CHUNK, chunkNbt, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - route to new converter system + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java +index 28e0f782c24afb3d8d2296bd0d3493a32ef66961..56f0e217276b01aed2f20a71f6849826285fc15b 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java +@@ -142,7 +142,14 @@ public class SectionStorage implements AutoCloseable { + int j = getVersion(dynamic); + int k = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); + boolean bl = j != k; +- Dynamic dynamic2 = this.type.update(this.fixerUpper, dynamic, j, k); ++ // Paper start - route to new converter system ++ Dynamic dynamic2; ++ if (this.type == net.minecraft.util.datafix.DataFixTypes.POI_CHUNK) { ++ dynamic2 = new Dynamic<>(dynamic.getOps(), (T)ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.POI_CHUNK, (CompoundTag)dynamic.getValue(), j, k)); ++ } else { ++ dynamic2 = this.type.update(this.fixerUpper, dynamic, j, k); ++ } ++ // Paper end - route to new converter system + OptionalDynamic optionalDynamic = dynamic2.get("Sections"); + + for(int l = this.levelHeightAccessor.getMinSection(); l < this.levelHeightAccessor.getMaxSection(); ++l) { +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java +index 15a9736a870055d639d03063c7cf67fd769fff36..1ca00340aaa201dd34e5c350d23ef53e126a0ca6 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java +@@ -115,7 +115,7 @@ public class StructureCheck { + + CompoundTag compoundTag2; + try { +- compoundTag2 = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, compoundTag, i); ++ compoundTag2 = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, compoundTag, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace chunk converter + } catch (Exception var12) { + LOGGER.warn("Failed to partially datafix chunk {}", pos, var12); + return StructureCheckResult.CHUNK_LOAD_NEEDED; +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java +index e534dac9d69147174f6b9e8ce7f27fde536351ce..270fd52ec733c89bd91155c8222936fafbcf94d6 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java +@@ -236,7 +236,7 @@ public class StructureTemplateManager { + public StructureTemplate readStructure(CompoundTag nbt) { + StructureTemplate structureTemplate = new StructureTemplate(); + int i = NbtUtils.getDataVersion(nbt, 500); +- structureTemplate.load(this.blockLookup, DataFixTypes.STRUCTURE.updateToCurrentVersion(this.fixerUpper, nbt, i)); ++ structureTemplate.load(this.blockLookup, ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.STRUCTURE, nbt, i, SharedConstants.getCurrentVersion().getDataVersion().getVersion())); // Paper + return structureTemplate; + } + +diff --git a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java +index 836bcea1c6a9de29b4a248220331f3a8c697204d..399da9d43aefbb95897df4697860d5bce5317152 100644 +--- a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java ++++ b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java +@@ -290,10 +290,10 @@ public class LevelStorageSource { + static Dynamic readLevelDataTagFixed(Path path, DataFixer dataFixer) throws IOException { + CompoundTag nbttagcompound = LevelStorageSource.readLevelDataTagRaw(path); + CompoundTag nbttagcompound1 = nbttagcompound.getCompound("Data"); +- int i = NbtUtils.getDataVersion(nbttagcompound1, -1); ++ int i = NbtUtils.getDataVersion(nbttagcompound1, -1); final int version = i; // Paper - obfuscation helpers + Dynamic dynamic = DataFixTypes.LEVEL.updateToCurrentVersion(dataFixer, new Dynamic(NbtOps.INSTANCE, nbttagcompound1), i); + Dynamic dynamic1 = dynamic.get("Player").orElseEmptyMap(); +- Dynamic dynamic2 = DataFixTypes.PLAYER.updateToCurrentVersion(dataFixer, dynamic1, i); ++ Dynamic dynamic2 = LevelStorageSource.dank(dynamic1, version); // Paper + + dynamic = dynamic.set("Player", dynamic2); + Dynamic dynamic3 = dynamic.get("WorldGenSettings").orElseEmptyMap(); +@@ -303,6 +303,12 @@ public class LevelStorageSource { + return dynamic; + } + ++ // Paper start ++ private static Dynamic dank(final Dynamic input, final int version) { ++ return new Dynamic<>(input.getOps(), (T) ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, (CompoundTag)input.getValue(), version, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion())); ++ } ++ // Paper end ++ + private LevelSummary readLevelSummary(LevelStorageSource.LevelDirectory save, boolean locked) { + Path path = save.dataFile(); + +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 49d39980054bce470ddaceeb6ab7fab83bf8dc54..63e187c65cb855031f286aad0d25ac4694f7a331 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -97,7 +97,7 @@ public class PlayerDataStorage { + // CraftBukkit end + int i = NbtUtils.getDataVersion(nbttagcompound, -1); + +- nbttagcompound = DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, nbttagcompound, i); ++ nbttagcompound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, nbttagcompound, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace player converter + player.load(nbttagcompound); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 6372f3a4fdf6e37ef785749ec40c3bd67b003b28..1562e9832df8bf3f81fb37983a303da5bd9ceee6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -515,8 +515,7 @@ public final class CraftMagicNumbers implements UnsafeValues { + + CompoundTag compound = deserializeNbtFromBytes(data); + final int dataVersion = compound.getInt("DataVersion"); +- compound = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue(); +- return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of(compound)); ++ return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of(ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ITEM_STACK, compound, dataVersion, this.getDataVersion()))); // Paper - rewrite dataconverter + } + + @Override +@@ -536,7 +535,7 @@ public final class CraftMagicNumbers implements UnsafeValues { + + CompoundTag compound = deserializeNbtFromBytes(data); + int dataVersion = compound.getInt("DataVersion"); +- compound = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ENTITY, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue(); ++ compound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY, compound, dataVersion, getDataVersion()); // Paper - rewrite dataconverter + if (!preserveUUID) { + // Generate a new UUID so we don't have to worry about deserializing the same entity twice + compound.remove("UUID"); diff --git a/patches/server/0984-Starlight.patch b/patches/server/0984-Starlight.patch new file mode 100644 index 000000000000..0604569c856b --- /dev/null +++ b/patches/server/0984-Starlight.patch @@ -0,0 +1,5422 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 28 Oct 2020 16:51:55 -0700 +Subject: [PATCH] Starlight + +See https://github.com/PaperMC/Starlight + +== AT == +public net.minecraft.server.level.ChunkHolder broadcast(Lnet/minecraft/network/protocol/Packet;Z)V + +diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java +new file mode 100644 +index 0000000000000000000000000000000000000000..09ddbbfdf656aa347830941abd7c994fac05d1c5 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java +@@ -0,0 +1,279 @@ ++package ca.spottedleaf.starlight.common.light; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.ImposterProtoChunk; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.LightChunkGetter; ++import net.minecraft.world.level.chunk.PalettedContainer; ++import net.minecraft.world.phys.shapes.Shapes; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Set; ++import java.util.stream.Collectors; ++ ++public final class BlockStarLightEngine extends StarLightEngine { ++ ++ public BlockStarLightEngine(final Level world) { ++ super(false, world); ++ } ++ ++ @Override ++ protected boolean[] getEmptinessMap(final ChunkAccess chunk) { ++ return chunk.getBlockEmptinessMap(); ++ } ++ ++ @Override ++ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) { ++ chunk.setBlockEmptinessMap(to); ++ } ++ ++ @Override ++ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) { ++ return chunk.getBlockNibbles(); ++ } ++ ++ @Override ++ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) { ++ chunk.setBlockNibbles(to); ++ } ++ ++ @Override ++ protected boolean canUseChunk(final ChunkAccess chunk) { ++ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect()); ++ } ++ ++ @Override ++ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) { ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (nibble != null) { ++ // de-initialisation is not as straightforward as with sky data, since deinit of block light is typically ++ // because a block was removed - which can decrease light. with sky data, block breaking can only result ++ // in increases, and thus the existing sky block check will actually correctly propagate light through ++ // a null section. so in order to propagate decreases correctly, we can do a couple of things: not remove ++ // the data section, or do edge checks on ALL axis (x, y, z). however I do not want edge checks running ++ // for clients at all, as they are expensive. so we don't remove the section, but to maintain the appearence ++ // of vanilla data management we "hide" them. ++ nibble.setHidden(); ++ } ++ } ++ ++ @Override ++ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { ++ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { ++ return; ++ } ++ ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (nibble == null) { ++ if (!initRemovedNibbles) { ++ throw new IllegalStateException(); ++ } else { ++ this.setNibbleInCache(chunkX, chunkY, chunkZ, new SWMRNibbleArray()); ++ } ++ } else { ++ nibble.setNonNull(); ++ } ++ } ++ ++ @Override ++ protected final void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) { ++ // blocks can change opacity ++ // blocks can change emitted light ++ // blocks can change direction of propagation ++ ++ final int encodeOffset = this.coordinateOffset; ++ final int emittedMask = this.emittedLightMask; ++ ++ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); ++ final BlockState blockState = this.getBlockState(worldX, worldY, worldZ); ++ final int emittedLevel = blockState.getLightEmission() & emittedMask; ++ ++ this.setLightLevel(worldX, worldY, worldZ, emittedLevel); ++ // this accounts for change in emitted light that would cause an increase ++ if (emittedLevel != 0) { ++ this.appendToIncreaseQueue( ++ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (emittedLevel & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) ++ ); ++ } ++ // this also accounts for a change in emitted light that would cause a decrease ++ // this also accounts for the change of direction of propagation (i.e old block was full transparent, new block is full opaque or vice versa) ++ // as it checks all neighbours (even if current level is 0) ++ this.appendToDecreaseQueue( ++ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (currentLevel & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ // always keep sided transparent false here, new block might be conditionally transparent which would ++ // prevent us from decreasing sources in the directions where the new block is opaque ++ // if it turns out we were wrong to de-propagate the source, the re-propagate logic WILL always ++ // catch that and fix it. ++ ); ++ // re-propagating neighbours (done by the decrease queue) will also account for opacity changes in this block ++ } ++ ++ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); ++ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); ++ ++ @Override ++ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, ++ final int expect) { ++ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); ++ int level = centerState.getLightEmission() & 0xF; ++ ++ if (level >= (15 - 1) || level > expect) { ++ return level; ++ } ++ ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ final BlockState conditionallyOpaqueState; ++ int opacity = centerState.getOpacityIfCached(); ++ ++ if (opacity == -1) { ++ this.recalcCenterPos.set(worldX, worldY, worldZ); ++ opacity = centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos); ++ if (centerState.isConditionallyFullOpaque()) { ++ conditionallyOpaqueState = centerState; ++ } else { ++ conditionallyOpaqueState = null; ++ } ++ } else if (opacity >= 15) { ++ return level; ++ } else { ++ conditionallyOpaqueState = null; ++ } ++ opacity = Math.max(1, opacity); ++ ++ for (final AxisDirection direction : AXIS_DIRECTIONS) { ++ final int offX = worldX + direction.x; ++ final int offY = worldY + direction.y; ++ final int offZ = worldZ + direction.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ ++ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); ++ ++ if ((neighbourLevel - 1) <= level) { ++ // don't need to test transparency, we know it wont affect the result. ++ continue; ++ } ++ ++ final BlockState neighbourState = this.getBlockState(offX, offY, offZ); ++ if (neighbourState.isConditionallyFullOpaque()) { ++ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that ++ // we don't read the blockstate because most of the time this is false, so using the faster ++ // known transparency lookup results in a net win ++ this.recalcNeighbourPos.set(offX, offY, offZ); ++ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); ++ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); ++ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { ++ // not allowed to propagate ++ continue; ++ } ++ } ++ ++ // passed transparency, ++ ++ final int calculated = neighbourLevel - opacity; ++ level = Math.max(calculated, level); ++ if (level > expect) { ++ return level; ++ } ++ } ++ ++ return level; ++ } ++ ++ @Override ++ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions) { ++ for (final BlockPos pos : positions) { ++ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ protected List getSources(final LightChunkGetter lightAccess, final ChunkAccess chunk) { ++ final List sources = new ArrayList<>(); ++ ++ final int offX = chunk.getPos().x << 4; ++ final int offZ = chunk.getPos().z << 4; ++ ++ final LevelChunkSection[] sections = chunk.getSections(); ++ for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) { ++ final LevelChunkSection section = sections[sectionY - this.minSection]; ++ if (section == null || section.hasOnlyAir()) { ++ // no sources in empty sections ++ continue; ++ } ++ if (!section.maybeHas((final BlockState state) -> { ++ return state.getLightEmission() > 0; ++ })) { ++ // no light sources in palette ++ continue; ++ } ++ final PalettedContainer states = section.states; ++ final int offY = sectionY << 4; ++ ++ for (int index = 0; index < (16 * 16 * 16); ++index) { ++ final BlockState state = states.get(index); ++ if (state.getLightEmission() <= 0) { ++ continue; ++ } ++ ++ // index = x | (z << 4) | (y << 8) ++ sources.add(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15))); ++ } ++ } ++ ++ return sources; ++ } ++ ++ @Override ++ public void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { ++ // setup sources ++ final int emittedMask = this.emittedLightMask; ++ final List positions = this.getSources(lightAccess, chunk); ++ for (int i = 0, len = positions.size(); i < len; ++i) { ++ final BlockPos pos = positions.get(i); ++ final BlockState blockState = this.getBlockState(pos.getX(), pos.getY(), pos.getZ()); ++ final int emittedLight = blockState.getLightEmission() & emittedMask; ++ ++ if (emittedLight <= this.getLightLevel(pos.getX(), pos.getY(), pos.getZ())) { ++ // some other source is brighter ++ continue; ++ } ++ ++ this.appendToIncreaseQueue( ++ ((pos.getX() + (pos.getZ() << 6) + (pos.getY() << (6 + 6)) + this.coordinateOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (emittedLight & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) ++ ); ++ ++ ++ // propagation wont set this for us ++ this.setLightLevel(pos.getX(), pos.getY(), pos.getZ(), emittedLight); ++ } ++ ++ if (needsEdgeChecks) { ++ // not required to propagate here, but this will reduce the hit of the edge checks ++ this.performLightIncrease(lightAccess); ++ ++ // verify neighbour edges ++ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); ++ } else { ++ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, this.maxLightSection); ++ ++ this.performLightIncrease(lightAccess); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4ffb4ffe01c4628d52742c5c0bbd35220eea6294 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java +@@ -0,0 +1,440 @@ ++package ca.spottedleaf.starlight.common.light; ++ ++import net.minecraft.world.level.chunk.DataLayer; ++import java.util.ArrayDeque; ++import java.util.Arrays; ++ ++// SWMR -> Single Writer Multi Reader Nibble Array ++public final class SWMRNibbleArray { ++ ++ /* ++ * Null nibble - nibble does not exist, and should not be written to. Just like vanilla - null ++ * nibbles are always 0 - and they are never written to directly. Only initialised/uninitialised ++ * nibbles can be written to. ++ * ++ * Uninitialised nibble - They are all 0, but the backing array isn't initialised. ++ * ++ * Initialised nibble - Has light data. ++ */ ++ ++ protected static final int INIT_STATE_NULL = 0; // null ++ protected static final int INIT_STATE_UNINIT = 1; // uninitialised ++ protected static final int INIT_STATE_INIT = 2; // initialised ++ protected static final int INIT_STATE_HIDDEN = 3; // initialised, but conversion to Vanilla data should be treated as if NULL ++ ++ public static final int ARRAY_SIZE = 16 * 16 * 16 / (8/4); // blocks / bytes per block ++ // this allows us to maintain only 1 byte array when we're not updating ++ static final ThreadLocal> WORKING_BYTES_POOL = ThreadLocal.withInitial(ArrayDeque::new); ++ ++ private static byte[] allocateBytes() { ++ final byte[] inPool = WORKING_BYTES_POOL.get().pollFirst(); ++ if (inPool != null) { ++ return inPool; ++ } ++ ++ return new byte[ARRAY_SIZE]; ++ } ++ ++ private static void freeBytes(final byte[] bytes) { ++ WORKING_BYTES_POOL.get().addFirst(bytes); ++ } ++ ++ public static SWMRNibbleArray fromVanilla(final DataLayer nibble) { ++ if (nibble == null) { ++ return new SWMRNibbleArray(null, true); ++ } else if (nibble.isEmpty()) { ++ return new SWMRNibbleArray(); ++ } else { ++ return new SWMRNibbleArray(nibble.getData().clone()); // make sure we don't write to the parameter later ++ } ++ } ++ ++ protected int stateUpdating; ++ protected volatile int stateVisible; ++ ++ protected byte[] storageUpdating; ++ protected boolean updatingDirty; // only returns whether storageUpdating is dirty ++ protected volatile byte[] storageVisible; ++ ++ public SWMRNibbleArray() { ++ this(null, false); // lazy init ++ } ++ ++ public SWMRNibbleArray(final byte[] bytes) { ++ this(bytes, false); ++ } ++ ++ public SWMRNibbleArray(final byte[] bytes, final boolean isNullNibble) { ++ if (bytes != null && bytes.length != ARRAY_SIZE) { ++ throw new IllegalArgumentException("Data of wrong length: " + bytes.length); ++ } ++ this.stateVisible = this.stateUpdating = bytes == null ? (isNullNibble ? INIT_STATE_NULL : INIT_STATE_UNINIT) : INIT_STATE_INIT; ++ this.storageUpdating = this.storageVisible = bytes; ++ } ++ ++ public SWMRNibbleArray(final byte[] bytes, final int state) { ++ if (bytes != null && bytes.length != ARRAY_SIZE) { ++ throw new IllegalArgumentException("Data of wrong length: " + bytes.length); ++ } ++ if (bytes == null && (state == INIT_STATE_INIT || state == INIT_STATE_HIDDEN)) { ++ throw new IllegalArgumentException("Data cannot be null and have state be initialised"); ++ } ++ this.stateUpdating = this.stateVisible = state; ++ this.storageUpdating = this.storageVisible = bytes; ++ } ++ ++ @Override ++ public String toString() { ++ StringBuilder stringBuilder = new StringBuilder(); ++ stringBuilder.append("State: "); ++ switch (this.stateVisible) { ++ case INIT_STATE_NULL: ++ stringBuilder.append("null"); ++ break; ++ case INIT_STATE_UNINIT: ++ stringBuilder.append("uninitialised"); ++ break; ++ case INIT_STATE_INIT: ++ stringBuilder.append("initialised"); ++ break; ++ case INIT_STATE_HIDDEN: ++ stringBuilder.append("hidden"); ++ break; ++ default: ++ stringBuilder.append("unknown"); ++ break; ++ } ++ stringBuilder.append("\nData:\n"); ++ ++ final byte[] data = this.storageVisible; ++ if (data != null) { ++ for (int i = 0; i < 4096; ++i) { ++ // Copied from NibbleArray#toString ++ final int level = ((data[i >>> 1] >>> ((i & 1) << 2)) & 0xF); ++ ++ stringBuilder.append(Integer.toHexString(level)); ++ if ((i & 15) == 15) { ++ stringBuilder.append("\n"); ++ } ++ ++ if ((i & 255) == 255) { ++ stringBuilder.append("\n"); ++ } ++ } ++ } else { ++ stringBuilder.append("null"); ++ } ++ ++ return stringBuilder.toString(); ++ } ++ ++ public SaveState getSaveState() { ++ synchronized (this) { ++ final int state = this.stateVisible; ++ final byte[] data = this.storageVisible; ++ if (state == INIT_STATE_NULL) { ++ return null; ++ } ++ if (state == INIT_STATE_UNINIT) { ++ return new SaveState(null, state); ++ } ++ final boolean zero = isAllZero(data); ++ if (zero) { ++ return state == INIT_STATE_INIT ? new SaveState(null, INIT_STATE_UNINIT) : null; ++ } else { ++ return new SaveState(data.clone(), state); ++ } ++ } ++ } ++ ++ protected static boolean isAllZero(final byte[] data) { ++ for (int i = 0; i < (ARRAY_SIZE >>> 4); ++i) { ++ byte whole = data[i << 4]; ++ ++ for (int k = 1; k < (1 << 4); ++k) { ++ whole |= data[(i << 4) | k]; ++ } ++ ++ if (whole != 0) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ ++ // operation type: updating on src, updating on other ++ public void extrudeLower(final SWMRNibbleArray other) { ++ if (other.stateUpdating == INIT_STATE_NULL) { ++ throw new IllegalArgumentException(); ++ } ++ ++ if (other.storageUpdating == null) { ++ this.setUninitialised(); ++ return; ++ } ++ ++ final byte[] src = other.storageUpdating; ++ final byte[] into; ++ ++ if (!this.updatingDirty) { ++ if (this.storageUpdating != null) { ++ into = this.storageUpdating = allocateBytes(); ++ } else { ++ this.storageUpdating = into = allocateBytes(); ++ this.stateUpdating = INIT_STATE_INIT; ++ } ++ this.updatingDirty = true; ++ } else { ++ into = this.storageUpdating; ++ } ++ ++ final int start = 0; ++ final int end = (15 | (15 << 4)) >>> 1; ++ ++ /* x | (z << 4) | (y << 8) */ ++ for (int y = 0; y <= 15; ++y) { ++ System.arraycopy(src, start, into, y << (8 - 1), end - start + 1); ++ } ++ } ++ ++ // operation type: updating ++ public void setFull() { ++ if (this.stateUpdating != INIT_STATE_HIDDEN) { ++ this.stateUpdating = INIT_STATE_INIT; ++ } ++ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)-1); ++ this.updatingDirty = true; ++ } ++ ++ // operation type: updating ++ public void setZero() { ++ if (this.stateUpdating != INIT_STATE_HIDDEN) { ++ this.stateUpdating = INIT_STATE_INIT; ++ } ++ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)0); ++ this.updatingDirty = true; ++ } ++ ++ // operation type: updating ++ public void setNonNull() { ++ if (this.stateUpdating == INIT_STATE_HIDDEN) { ++ this.stateUpdating = INIT_STATE_INIT; ++ return; ++ } ++ if (this.stateUpdating != INIT_STATE_NULL) { ++ return; ++ } ++ this.stateUpdating = INIT_STATE_UNINIT; ++ } ++ ++ // operation type: updating ++ public void setNull() { ++ this.stateUpdating = INIT_STATE_NULL; ++ if (this.updatingDirty && this.storageUpdating != null) { ++ freeBytes(this.storageUpdating); ++ } ++ this.storageUpdating = null; ++ this.updatingDirty = false; ++ } ++ ++ // operation type: updating ++ public void setUninitialised() { ++ this.stateUpdating = INIT_STATE_UNINIT; ++ if (this.storageUpdating != null && this.updatingDirty) { ++ freeBytes(this.storageUpdating); ++ } ++ this.storageUpdating = null; ++ this.updatingDirty = false; ++ } ++ ++ // operation type: updating ++ public void setHidden() { ++ if (this.stateUpdating == INIT_STATE_HIDDEN) { ++ return; ++ } ++ if (this.stateUpdating != INIT_STATE_INIT) { ++ this.setNull(); ++ } else { ++ this.stateUpdating = INIT_STATE_HIDDEN; ++ } ++ } ++ ++ // operation type: updating ++ public boolean isDirty() { ++ return this.stateUpdating != this.stateVisible || this.updatingDirty; ++ } ++ ++ // operation type: updating ++ public boolean isNullNibbleUpdating() { ++ return this.stateUpdating == INIT_STATE_NULL; ++ } ++ ++ // operation type: visible ++ public boolean isNullNibbleVisible() { ++ return this.stateVisible == INIT_STATE_NULL; ++ } ++ ++ // opeartion type: updating ++ public boolean isUninitialisedUpdating() { ++ return this.stateUpdating == INIT_STATE_UNINIT; ++ } ++ ++ // operation type: visible ++ public boolean isUninitialisedVisible() { ++ return this.stateVisible == INIT_STATE_UNINIT; ++ } ++ ++ // operation type: updating ++ public boolean isInitialisedUpdating() { ++ return this.stateUpdating == INIT_STATE_INIT; ++ } ++ ++ // operation type: visible ++ public boolean isInitialisedVisible() { ++ return this.stateVisible == INIT_STATE_INIT; ++ } ++ ++ // operation type: updating ++ public boolean isHiddenUpdating() { ++ return this.stateUpdating == INIT_STATE_HIDDEN; ++ } ++ ++ // operation type: updating ++ public boolean isHiddenVisible() { ++ return this.stateVisible == INIT_STATE_HIDDEN; ++ } ++ ++ // operation type: updating ++ protected void swapUpdatingAndMarkDirty() { ++ if (this.updatingDirty) { ++ return; ++ } ++ ++ if (this.storageUpdating == null) { ++ this.storageUpdating = allocateBytes(); ++ Arrays.fill(this.storageUpdating, (byte)0); ++ } else { ++ System.arraycopy(this.storageUpdating, 0, this.storageUpdating = allocateBytes(), 0, ARRAY_SIZE); ++ } ++ ++ if (this.stateUpdating != INIT_STATE_HIDDEN) { ++ this.stateUpdating = INIT_STATE_INIT; ++ } ++ this.updatingDirty = true; ++ } ++ ++ // operation type: updating ++ public boolean updateVisible() { ++ if (!this.isDirty()) { ++ return false; ++ } ++ ++ synchronized (this) { ++ if (this.stateUpdating == INIT_STATE_NULL || this.stateUpdating == INIT_STATE_UNINIT) { ++ this.storageVisible = null; ++ } else { ++ if (this.storageVisible == null) { ++ this.storageVisible = this.storageUpdating.clone(); ++ } else { ++ if (this.storageUpdating != this.storageVisible) { ++ System.arraycopy(this.storageUpdating, 0, this.storageVisible, 0, ARRAY_SIZE); ++ } ++ } ++ ++ if (this.storageUpdating != this.storageVisible) { ++ freeBytes(this.storageUpdating); ++ } ++ this.storageUpdating = this.storageVisible; ++ } ++ this.updatingDirty = false; ++ this.stateVisible = this.stateUpdating; ++ } ++ ++ return true; ++ } ++ ++ // operation type: visible ++ public DataLayer toVanillaNibble() { ++ synchronized (this) { ++ switch (this.stateVisible) { ++ case INIT_STATE_HIDDEN: ++ case INIT_STATE_NULL: ++ return null; ++ case INIT_STATE_UNINIT: ++ return new DataLayer(); ++ case INIT_STATE_INIT: ++ return new DataLayer(this.storageVisible.clone()); ++ default: ++ throw new IllegalStateException(); ++ } ++ } ++ } ++ ++ /* x | (z << 4) | (y << 8) */ ++ ++ // operation type: updating ++ public int getUpdating(final int x, final int y, final int z) { ++ return this.getUpdating((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); ++ } ++ ++ // operation type: updating ++ public int getUpdating(final int index) { ++ // indices range from 0 -> 4096 ++ final byte[] bytes = this.storageUpdating; ++ if (bytes == null) { ++ return 0; ++ } ++ final byte value = bytes[index >>> 1]; ++ ++ // if we are an even index, we want lower 4 bits ++ // if we are an odd index, we want upper 4 bits ++ return ((value >>> ((index & 1) << 2)) & 0xF); ++ } ++ ++ // operation type: visible ++ public int getVisible(final int x, final int y, final int z) { ++ return this.getVisible((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); ++ } ++ ++ // operation type: visible ++ public int getVisible(final int index) { ++ // indices range from 0 -> 4096 ++ final byte[] visibleBytes = this.storageVisible; ++ if (visibleBytes == null) { ++ return 0; ++ } ++ final byte value = visibleBytes[index >>> 1]; ++ ++ // if we are an even index, we want lower 4 bits ++ // if we are an odd index, we want upper 4 bits ++ return ((value >>> ((index & 1) << 2)) & 0xF); ++ } ++ ++ // operation type: updating ++ public void set(final int x, final int y, final int z, final int value) { ++ this.set((x & 15) | ((z & 15) << 4) | ((y & 15) << 8), value); ++ } ++ ++ // operation type: updating ++ public void set(final int index, final int value) { ++ if (!this.updatingDirty) { ++ this.swapUpdatingAndMarkDirty(); ++ } ++ final int shift = (index & 1) << 2; ++ final int i = index >>> 1; ++ ++ this.storageUpdating[i] = (byte)((this.storageUpdating[i] & (0xF0 >>> shift)) | (value << shift)); ++ } ++ ++ public static final class SaveState { ++ ++ public final byte[] data; ++ public final int state; ++ ++ public SaveState(final byte[] data, final int state) { ++ this.data = data; ++ this.state = state; ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5f771962afb44175d446f138c8e7453230f48c6c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java +@@ -0,0 +1,709 @@ ++package ca.spottedleaf.starlight.common.light; ++ ++import ca.spottedleaf.starlight.common.util.WorldUtil; ++import it.unimi.dsi.fastutil.shorts.ShortCollection; ++import it.unimi.dsi.fastutil.shorts.ShortIterator; ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.LightChunkGetter; ++import net.minecraft.world.phys.shapes.Shapes; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import java.util.Arrays; ++import java.util.Set; ++ ++public final class SkyStarLightEngine extends StarLightEngine { ++ ++ /* ++ Specification for managing the initialisation and de-initialisation of skylight nibble arrays: ++ ++ Skylight nibble initialisation requires that non-empty chunk sections have 1 radius nibbles non-null. ++ ++ This presents some problems, as vanilla is only guaranteed to have 0 radius neighbours loaded when editing blocks. ++ However starlight fixes this so that it has 1 radius loaded. Still, we don't actually have guarantees ++ that we have the necessary chunks loaded to de-initialise neighbour sections (but we do have enough to de-initialise ++ our own) - we need a radius of 2 to de-initialise neighbour nibbles. ++ How do we solve this? ++ ++ Each chunk will store the last known "emptiness" of sections for each of their 1 radius neighbour chunk sections. ++ If the chunk does not have full data, then its nibbles are NOT de-initialised. This is because obviously the ++ chunk did not go through the light stage yet - or its neighbours are not lit. In either case, once the last ++ known "emptiness" of neighbouring sections is filled with data, the chunk will run a full check of the data ++ to see if any of its nibbles need to be de-initialised. ++ ++ The emptiness map allows us to de-initialise neighbour nibbles if the neighbour has it filled with data, ++ and if it doesn't have data then we know it will correctly de-initialise once it fills up. ++ ++ Unlike vanilla, we store whether nibbles are uninitialised on disk - so we don't need any dumb hacking ++ around those. ++ */ ++ ++ protected final int[] heightMapBlockChange = new int[16 * 16]; ++ { ++ Arrays.fill(this.heightMapBlockChange, Integer.MIN_VALUE); // clear heightmap ++ } ++ ++ protected final boolean[] nullPropagationCheckCache; ++ ++ public SkyStarLightEngine(final Level world) { ++ super(true, world); ++ this.nullPropagationCheckCache = new boolean[WorldUtil.getTotalLightSections(world)]; ++ } ++ ++ @Override ++ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { ++ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { ++ return; ++ } ++ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (nibble == null) { ++ if (!initRemovedNibbles) { ++ throw new IllegalStateException(); ++ } else { ++ this.setNibbleInCache(chunkX, chunkY, chunkZ, nibble = new SWMRNibbleArray(null, true)); ++ } ++ } ++ this.initNibble(nibble, chunkX, chunkY, chunkZ, extrude); ++ } ++ ++ @Override ++ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) { ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (nibble != null) { ++ nibble.setNull(); ++ } ++ } ++ ++ protected final void initNibble(final SWMRNibbleArray currNibble, final int chunkX, final int chunkY, final int chunkZ, final boolean extrude) { ++ if (!currNibble.isNullNibbleUpdating()) { ++ // already initialised ++ return; ++ } ++ ++ final boolean[] emptinessMap = this.getEmptinessMap(chunkX, chunkZ); ++ ++ // are we above this chunk's lowest empty section? ++ int lowestY = this.minLightSection - 1; ++ for (int currY = this.maxSection; currY >= this.minSection; --currY) { ++ if (emptinessMap == null) { ++ // cannot delay nibble init for lit chunks, as we need to init to propagate into them. ++ final LevelChunkSection current = this.getChunkSection(chunkX, currY, chunkZ); ++ if (current == null || current.hasOnlyAir()) { ++ continue; ++ } ++ } else { ++ if (emptinessMap[currY - this.minSection]) { ++ continue; ++ } ++ } ++ ++ // should always be full lit here ++ lowestY = currY; ++ break; ++ } ++ ++ if (chunkY > lowestY) { ++ // we need to set this one to full ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ nibble.setNonNull(); ++ nibble.setFull(); ++ return; ++ } ++ ++ if (extrude) { ++ // this nibble is going to depend solely on the skylight data above it ++ // find first non-null data above (there does exist one, as we just found it above) ++ for (int currY = chunkY + 1; currY <= this.maxLightSection; ++currY) { ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, currY, chunkZ); ++ if (nibble != null && !nibble.isNullNibbleUpdating()) { ++ currNibble.setNonNull(); ++ currNibble.extrudeLower(nibble); ++ break; ++ } ++ } ++ } else { ++ currNibble.setNonNull(); ++ } ++ } ++ ++ protected final void rewriteNibbleCacheForSkylight(final ChunkAccess chunk) { ++ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { ++ final SWMRNibbleArray nibble = this.nibbleCache[index]; ++ if (nibble != null && nibble.isNullNibbleUpdating()) { ++ // stop propagation in these areas ++ this.nibbleCache[index] = null; ++ nibble.updateVisible(); ++ } ++ } ++ } ++ ++ // rets whether neighbours were init'd ++ ++ protected final boolean checkNullSection(final int chunkX, final int chunkY, final int chunkZ, ++ final boolean extrudeInitialised) { ++ // null chunk sections may have nibble neighbours in the horizontal 1 radius that are ++ // non-null. Propagation to these neighbours is necessary. ++ // What makes this easy is we know none of these neighbours are non-empty (otherwise ++ // this nibble would be initialised). So, we don't have to initialise ++ // the neighbours in the full 1 radius, because there's no worry that any "paths" ++ // to the neighbours on this horizontal plane are blocked. ++ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.nullPropagationCheckCache[chunkY - this.minLightSection]) { ++ return false; ++ } ++ this.nullPropagationCheckCache[chunkY - this.minLightSection] = true; ++ ++ // check horizontal neighbours ++ boolean needInitNeighbours = false; ++ neighbour_search: ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(dx + chunkX, chunkY, dz + chunkZ); ++ if (nibble != null && !nibble.isNullNibbleUpdating()) { ++ needInitNeighbours = true; ++ break neighbour_search; ++ } ++ } ++ } ++ ++ if (needInitNeighbours) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ this.initNibble(dx + chunkX, chunkY, dz + chunkZ, (dx | dz) == 0 ? extrudeInitialised : true, true); ++ } ++ } ++ } ++ ++ return needInitNeighbours; ++ } ++ ++ protected final int getLightLevelExtruded(final int worldX, final int worldY, final int worldZ) { ++ final int chunkX = worldX >> 4; ++ int chunkY = worldY >> 4; ++ final int chunkZ = worldZ >> 4; ++ ++ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (nibble != null) { ++ return nibble.getUpdating(worldX, worldY, worldZ); ++ } ++ ++ for (;;) { ++ if (++chunkY > this.maxLightSection) { ++ return 15; ++ } ++ ++ nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ ++ if (nibble != null) { ++ return nibble.getUpdating(worldX, 0, worldZ); ++ } ++ } ++ } ++ ++ @Override ++ protected boolean[] getEmptinessMap(final ChunkAccess chunk) { ++ return chunk.getSkyEmptinessMap(); ++ } ++ ++ @Override ++ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) { ++ chunk.setSkyEmptinessMap(to); ++ } ++ ++ @Override ++ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) { ++ return chunk.getSkyNibbles(); ++ } ++ ++ @Override ++ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) { ++ chunk.setSkyNibbles(to); ++ } ++ ++ @Override ++ protected boolean canUseChunk(final ChunkAccess chunk) { ++ // can only use chunks for sky stuff if their sections have been init'd ++ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect()); ++ } ++ ++ @Override ++ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, ++ final int toSection) { ++ Arrays.fill(this.nullPropagationCheckCache, false); ++ this.rewriteNibbleCacheForSkylight(chunk); ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ for (int y = toSection; y >= fromSection; --y) { ++ this.checkNullSection(chunkX, y, chunkZ, true); ++ } ++ ++ super.checkChunkEdges(lightAccess, chunk, fromSection, toSection); ++ } ++ ++ @Override ++ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) { ++ Arrays.fill(this.nullPropagationCheckCache, false); ++ this.rewriteNibbleCacheForSkylight(chunk); ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { ++ final int y = (int)iterator.nextShort(); ++ this.checkNullSection(chunkX, y, chunkZ, true); ++ } ++ ++ super.checkChunkEdges(lightAccess, chunk, sections); ++ } ++ ++ @Override ++ protected void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) { ++ // blocks can change opacity ++ // blocks can change direction of propagation ++ ++ // same logic applies from BlockStarLightEngine#checkBlock ++ ++ final int encodeOffset = this.coordinateOffset; ++ ++ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); ++ ++ if (currentLevel == 15) { ++ // must re-propagate clobbered source ++ this.appendToIncreaseQueue( ++ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (currentLevel & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the block is conditionally transparent ++ ); ++ } else { ++ this.setLightLevel(worldX, worldY, worldZ, 0); ++ } ++ ++ this.appendToDecreaseQueue( ++ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (currentLevel & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ ); ++ } ++ ++ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); ++ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); ++ ++ @Override ++ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, ++ final int expect) { ++ if (expect == 15) { ++ return expect; ++ } ++ ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); ++ int opacity = centerState.getOpacityIfCached(); ++ ++ final BlockState conditionallyOpaqueState; ++ if (opacity < 0) { ++ this.recalcCenterPos.set(worldX, worldY, worldZ); ++ opacity = Math.max(1, centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos)); ++ if (centerState.isConditionallyFullOpaque()) { ++ conditionallyOpaqueState = centerState; ++ } else { ++ conditionallyOpaqueState = null; ++ } ++ } else { ++ conditionallyOpaqueState = null; ++ opacity = Math.max(1, opacity); ++ } ++ ++ int level = 0; ++ ++ for (final AxisDirection direction : AXIS_DIRECTIONS) { ++ final int offX = worldX + direction.x; ++ final int offY = worldY + direction.y; ++ final int offZ = worldZ + direction.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ ++ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); ++ ++ if ((neighbourLevel - 1) <= level) { ++ // don't need to test transparency, we know it wont affect the result. ++ continue; ++ } ++ ++ final BlockState neighbourState = this.getBlockState(offX, offY, offZ); ++ ++ if (neighbourState.isConditionallyFullOpaque()) { ++ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that ++ // we don't read the blockstate because most of the time this is false, so using the faster ++ // known transparency lookup results in a net win ++ this.recalcNeighbourPos.set(offX, offY, offZ); ++ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); ++ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); ++ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { ++ // not allowed to propagate ++ continue; ++ } ++ } ++ ++ final int calculated = neighbourLevel - opacity; ++ level = Math.max(calculated, level); ++ if (level > expect) { ++ return level; ++ } ++ } ++ ++ return level; ++ } ++ ++ @Override ++ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions) { ++ this.rewriteNibbleCacheForSkylight(atChunk); ++ Arrays.fill(this.nullPropagationCheckCache, false); ++ ++ final BlockGetter world = lightAccess.getLevel(); ++ final int chunkX = atChunk.getPos().x; ++ final int chunkZ = atChunk.getPos().z; ++ final int heightMapOffset = chunkX * -16 + (chunkZ * (-16 * 16)); ++ ++ // setup heightmap for changes ++ for (final BlockPos pos : positions) { ++ final int index = pos.getX() + (pos.getZ() << 4) + heightMapOffset; ++ final int curr = this.heightMapBlockChange[index]; ++ if (pos.getY() > curr) { ++ this.heightMapBlockChange[index] = pos.getY(); ++ } ++ } ++ ++ // note: light sets are delayed while processing skylight source changes due to how ++ // nibbles are initialised, as we want to avoid clobbering nibble values so what when ++ // below nibbles are initialised they aren't reading from partially modified nibbles ++ ++ // now we can recalculate the sources for the changed columns ++ for (int index = 0; index < (16 * 16); ++index) { ++ final int maxY = this.heightMapBlockChange[index]; ++ if (maxY == Integer.MIN_VALUE) { ++ // not changed ++ continue; ++ } ++ this.heightMapBlockChange[index] = Integer.MIN_VALUE; // restore default for next caller ++ ++ final int columnX = (index & 15) | (chunkX << 4); ++ final int columnZ = (index >>> 4) | (chunkZ << 4); ++ ++ // try and propagate from the above y ++ // delay light set until after processing all sources to setup ++ final int maxPropagationY = this.tryPropagateSkylight(world, columnX, maxY, columnZ, true, true); ++ ++ // maxPropagationY is now the highest block that could not be propagated to ++ ++ // remove all sources below that are 15 ++ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; ++ final int encodeOffset = this.coordinateOffset; ++ ++ if (this.getLightLevelExtruded(columnX, maxPropagationY, columnZ) == 15) { ++ // ensure section is checked ++ this.checkNullSection(columnX >> 4, maxPropagationY >> 4, columnZ >> 4, true); ++ ++ for (int currY = maxPropagationY; currY >= (this.minLightSection << 4); --currY) { ++ if ((currY & 15) == 15) { ++ // ensure section is checked ++ this.checkNullSection(columnX >> 4, (currY >> 4), columnZ >> 4, true); ++ } ++ ++ // ensure section below is always checked ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(columnX >> 4, currY >> 4, columnZ >> 4); ++ if (nibble == null) { ++ // advance currY to the the top of the section below ++ currY = (currY) & (~15); ++ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually ++ // end up there ++ continue; ++ } ++ ++ if (nibble.getUpdating(columnX, currY, columnZ) != 15) { ++ break; ++ } ++ ++ // delay light set until after processing all sources to setup ++ this.appendToDecreaseQueue( ++ ((columnX + (columnZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (15L << (6 + 6 + 16)) ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ // do not set transparent blocks for the same reason we don't in the checkBlock method ++ ); ++ } ++ } ++ } ++ ++ // delayed light sets are processed here, and must be processed before checkBlock as checkBlock reads ++ // immediate light value ++ this.processDelayedIncreases(); ++ this.processDelayedDecreases(); ++ ++ for (final BlockPos pos : positions) { ++ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ protected final int[] heightMapGen = new int[32 * 32]; ++ ++ @Override ++ protected void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { ++ this.rewriteNibbleCacheForSkylight(chunk); ++ Arrays.fill(this.nullPropagationCheckCache, false); ++ ++ final BlockGetter world = lightAccess.getLevel(); ++ final ChunkPos chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ final LevelChunkSection[] sections = chunk.getSections(); ++ ++ int highestNonEmptySection = this.maxSection; ++ while (highestNonEmptySection == (this.minSection - 1) || ++ sections[highestNonEmptySection - this.minSection] == null || sections[highestNonEmptySection - this.minSection].hasOnlyAir()) { ++ this.checkNullSection(chunkX, highestNonEmptySection, chunkZ, false); ++ // try propagate FULL to neighbours ++ ++ // check neighbours to see if we need to propagate into them ++ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { ++ final int neighbourX = chunkX + direction.x; ++ final int neighbourZ = chunkZ + direction.z; ++ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(neighbourX, highestNonEmptySection, neighbourZ); ++ if (neighbourNibble == null) { ++ // unloaded neighbour ++ // most of the time we fall here ++ continue; ++ } ++ ++ // it looks like we need to propagate into the neighbour ++ ++ final int incX; ++ final int incZ; ++ final int startX; ++ final int startZ; ++ ++ if (direction.x != 0) { ++ // x direction ++ incX = 0; ++ incZ = 1; ++ ++ if (direction.x < 0) { ++ // negative ++ startX = chunkX << 4; ++ } else { ++ startX = chunkX << 4 | 15; ++ } ++ startZ = chunkZ << 4; ++ } else { ++ // z direction ++ incX = 1; ++ incZ = 0; ++ ++ if (direction.z < 0) { ++ // negative ++ startZ = chunkZ << 4; ++ } else { ++ startZ = chunkZ << 4 | 15; ++ } ++ startX = chunkX << 4; ++ } ++ ++ final int encodeOffset = this.coordinateOffset; ++ final long propagateDirection = 1L << direction.ordinal(); // we only want to check in this direction ++ ++ for (int currY = highestNonEmptySection << 4, maxY = currY | 15; currY <= maxY; ++currY) { ++ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { ++ this.appendToIncreaseQueue( ++ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (15L << (6 + 6 + 16)) // we know we're at full lit here ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ // no transparent flag, we know for a fact there are no blocks here that could be directionally transparent (as the section is EMPTY) ++ ); ++ } ++ } ++ } ++ ++ if (highestNonEmptySection-- == (this.minSection - 1)) { ++ break; ++ } ++ } ++ ++ if (highestNonEmptySection >= this.minSection) { ++ // fill out our other sources ++ final int minX = chunkPos.x << 4; ++ final int maxX = chunkPos.x << 4 | 15; ++ final int minZ = chunkPos.z << 4; ++ final int maxZ = chunkPos.z << 4 | 15; ++ final int startY = highestNonEmptySection << 4 | 15; ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ this.tryPropagateSkylight(world, currX, startY + 1, currZ, false, false); ++ } ++ } ++ } // else: apparently the chunk is empty ++ ++ if (needsEdgeChecks) { ++ // not required to propagate here, but this will reduce the hit of the edge checks ++ this.performLightIncrease(lightAccess); ++ ++ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { ++ this.checkNullSection(chunkX, y, chunkZ, false); ++ } ++ // no need to rewrite the nibble cache again ++ super.checkChunkEdges(lightAccess, chunk, this.minLightSection, highestNonEmptySection); ++ } else { ++ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { ++ this.checkNullSection(chunkX, y, chunkZ, false); ++ } ++ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, highestNonEmptySection); ++ ++ this.performLightIncrease(lightAccess); ++ } ++ } ++ ++ protected final void processDelayedIncreases() { ++ // copied from performLightIncrease ++ final long[] queue = this.increaseQueue; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ ++ for (int i = 0, len = this.increaseQueueInitialLength; i < len; ++i) { ++ final long queueValue = queue[i]; ++ ++ final int posX = ((int)queueValue & 63) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; ++ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; ++ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); ++ ++ this.setLightLevel(posX, posY, posZ, propagatedLightLevel); ++ } ++ } ++ ++ protected final void processDelayedDecreases() { ++ // copied from performLightDecrease ++ final long[] queue = this.decreaseQueue; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ ++ for (int i = 0, len = this.decreaseQueueInitialLength; i < len; ++i) { ++ final long queueValue = queue[i]; ++ ++ final int posX = ((int)queueValue & 63) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; ++ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; ++ ++ this.setLightLevel(posX, posY, posZ, 0); ++ } ++ } ++ ++ // delaying the light set is useful for block changes since they need to worry about initialising nibblearrays ++ // while also queueing light at the same time (initialising nibblearrays might depend on nibbles above, so ++ // clobbering the light values will result in broken propagation) ++ protected final int tryPropagateSkylight(final BlockGetter world, final int worldX, int startY, final int worldZ, ++ final boolean extrudeInitialised, final boolean delayLightSet) { ++ final BlockPos.MutableBlockPos mutablePos = this.mutablePos3; ++ final int encodeOffset = this.coordinateOffset; ++ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; // just don't check upwards. ++ ++ if (this.getLightLevelExtruded(worldX, startY + 1, worldZ) != 15) { ++ return startY; ++ } ++ ++ // ensure this section is always checked ++ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); ++ ++ BlockState above = this.getBlockState(worldX, startY + 1, worldZ); ++ ++ for (;startY >= (this.minLightSection << 4); --startY) { ++ if ((startY & 15) == 15) { ++ // ensure this section is always checked ++ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); ++ } ++ final BlockState current = this.getBlockState(worldX, startY, worldZ); ++ ++ final VoxelShape fromShape; ++ if (above.isConditionallyFullOpaque()) { ++ this.mutablePos2.set(worldX, startY + 1, worldZ); ++ fromShape = above.getFaceOcclusionShape(world, this.mutablePos2, AxisDirection.NEGATIVE_Y.nms); ++ if (Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { ++ // above wont let us propagate ++ break; ++ } ++ } else { ++ fromShape = Shapes.empty(); ++ } ++ ++ final int opacityIfCached = current.getOpacityIfCached(); ++ // does light propagate from the top down? ++ if (opacityIfCached != -1) { ++ if (opacityIfCached != 0) { ++ // we cannot propagate 15 through this ++ break; ++ } ++ // most of the time it falls here. ++ // add to propagate ++ // light set delayed until we determine if this nibble section is null ++ this.appendToIncreaseQueue( ++ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (15L << (6 + 6 + 16)) // we know we're at full lit here ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ ); ++ } else { ++ mutablePos.set(worldX, startY, worldZ); ++ long flags = 0L; ++ if (current.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = current.getFaceOcclusionShape(world, mutablePos, AxisDirection.POSITIVE_Y.nms); ++ ++ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { ++ // can't propagate here, we're done on this column. ++ break; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = current.getLightBlock(world, mutablePos); ++ if (opacity > 0) { ++ // let the queued value (if any) handle it from here. ++ break; ++ } ++ ++ // light set delayed until we determine if this nibble section is null ++ this.appendToIncreaseQueue( ++ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (15L << (6 + 6 + 16)) // we know we're at full lit here ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ | flags ++ ); ++ } ++ ++ above = current; ++ ++ if (this.getNibbleFromCache(worldX >> 4, startY >> 4, worldZ >> 4) == null) { ++ // we skip empty sections here, as this is just an easy way of making sure the above block ++ // can propagate through air. ++ ++ // nothing can propagate in null sections, remove the queue entry for it ++ --this.increaseQueueInitialLength; ++ ++ // advance currY to the the top of the section below ++ startY = (startY) & (~15); ++ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually ++ // end up there ++ ++ // make sure this is marked as AIR ++ above = AIR_BLOCK_STATE; ++ } else if (!delayLightSet) { ++ this.setLightLevel(worldX, startY, worldZ, 15); ++ } ++ } ++ ++ return startY; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ad1eeebe6de219143492b94da309cb54ae9e0a5b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java +@@ -0,0 +1,1572 @@ ++package ca.spottedleaf.starlight.common.light; ++ ++import ca.spottedleaf.starlight.common.util.CoordinateUtils; ++import ca.spottedleaf.starlight.common.util.IntegerUtil; ++import ca.spottedleaf.starlight.common.util.WorldUtil; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.shorts.ShortCollection; ++import it.unimi.dsi.fastutil.shorts.ShortIterator; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.core.SectionPos; ++import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.LevelHeightAccessor; ++import net.minecraft.world.level.LightLayer; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.LightChunkGetter; ++import net.minecraft.world.phys.shapes.Shapes; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.List; ++import java.util.Set; ++import java.util.function.Consumer; ++import java.util.function.IntConsumer; ++ ++public abstract class StarLightEngine { ++ ++ protected static final BlockState AIR_BLOCK_STATE = Blocks.AIR.defaultBlockState(); ++ ++ protected static final AxisDirection[] DIRECTIONS = AxisDirection.values(); ++ protected static final AxisDirection[] AXIS_DIRECTIONS = DIRECTIONS; ++ protected static final AxisDirection[] ONLY_HORIZONTAL_DIRECTIONS = new AxisDirection[] { ++ AxisDirection.POSITIVE_X, AxisDirection.NEGATIVE_X, ++ AxisDirection.POSITIVE_Z, AxisDirection.NEGATIVE_Z ++ }; ++ ++ protected static enum AxisDirection { ++ ++ // Declaration order is important and relied upon. Do not change without modifying propagation code. ++ POSITIVE_X(1, 0, 0), NEGATIVE_X(-1, 0, 0), ++ POSITIVE_Z(0, 0, 1), NEGATIVE_Z(0, 0, -1), ++ POSITIVE_Y(0, 1, 0), NEGATIVE_Y(0, -1, 0); ++ ++ static { ++ POSITIVE_X.opposite = NEGATIVE_X; NEGATIVE_X.opposite = POSITIVE_X; ++ POSITIVE_Z.opposite = NEGATIVE_Z; NEGATIVE_Z.opposite = POSITIVE_Z; ++ POSITIVE_Y.opposite = NEGATIVE_Y; NEGATIVE_Y.opposite = POSITIVE_Y; ++ } ++ ++ protected AxisDirection opposite; ++ ++ public final int x; ++ public final int y; ++ public final int z; ++ public final Direction nms; ++ public final long everythingButThisDirection; ++ public final long everythingButTheOppositeDirection; ++ ++ AxisDirection(final int x, final int y, final int z) { ++ this.x = x; ++ this.y = y; ++ this.z = z; ++ this.nms = Direction.fromDelta(x, y, z); ++ this.everythingButThisDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << this.ordinal())); ++ // positive is always even, negative is always odd. Flip the 1 bit to get the negative direction. ++ this.everythingButTheOppositeDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << (this.ordinal() ^ 1))); ++ } ++ ++ public AxisDirection getOpposite() { ++ return this.opposite; ++ } ++ } ++ ++ // I'd like to thank https://www.seedofandromeda.com/blogs/29-fast-flood-fill-lighting-in-a-blocky-voxel-game-pt-1 ++ // for explaining how light propagates via breadth-first search ++ ++ // While the above is a good start to understanding the general idea of what the general principles are, it's not ++ // exactly how the vanilla light engine should behave for minecraft. ++ ++ // similar to the above, except the chunk section indices vary from [-1, 1], or [0, 2] ++ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] ++ // index = x + (z * 5) + (y * 25) ++ // null index indicates the chunk section doesn't exist (empty or out of bounds) ++ protected final LevelChunkSection[] sectionCache; ++ ++ // the exact same as above, except for storing fast access to SWMRNibbleArray ++ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] ++ // index = x + (z * 5) + (y * 25) ++ protected final SWMRNibbleArray[] nibbleCache; ++ ++ // the exact same as above, except for storing fast access to nibbles to call change callbacks for ++ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] ++ // index = x + (z * 5) + (y * 25) ++ protected final boolean[] notifyUpdateCache; ++ ++ // always initialsed during start of lighting. ++ // index = x + (z * 5) ++ protected final ChunkAccess[] chunkCache = new ChunkAccess[5 * 5]; ++ ++ // index = x + (z * 5) ++ protected final boolean[][] emptinessMapCache = new boolean[5 * 5][]; ++ ++ protected final BlockPos.MutableBlockPos mutablePos1 = new BlockPos.MutableBlockPos(); ++ protected final BlockPos.MutableBlockPos mutablePos2 = new BlockPos.MutableBlockPos(); ++ protected final BlockPos.MutableBlockPos mutablePos3 = new BlockPos.MutableBlockPos(); ++ ++ protected int encodeOffsetX; ++ protected int encodeOffsetY; ++ protected int encodeOffsetZ; ++ ++ protected int coordinateOffset; ++ ++ protected int chunkOffsetX; ++ protected int chunkOffsetY; ++ protected int chunkOffsetZ; ++ ++ protected int chunkIndexOffset; ++ protected int chunkSectionIndexOffset; ++ ++ protected final boolean skylightPropagator; ++ protected final int emittedLightMask; ++ protected final boolean isClientSide; ++ ++ protected final Level world; ++ protected final int minLightSection; ++ protected final int maxLightSection; ++ protected final int minSection; ++ protected final int maxSection; ++ ++ protected StarLightEngine(final boolean skylightPropagator, final Level world) { ++ this.skylightPropagator = skylightPropagator; ++ this.emittedLightMask = skylightPropagator ? 0 : 0xF; ++ this.isClientSide = world.isClientSide; ++ this.world = world; ++ this.minLightSection = WorldUtil.getMinLightSection(world); ++ this.maxLightSection = WorldUtil.getMaxLightSection(world); ++ this.minSection = WorldUtil.getMinSection(world); ++ this.maxSection = WorldUtil.getMaxSection(world); ++ ++ this.sectionCache = new LevelChunkSection[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer ++ this.nibbleCache = new SWMRNibbleArray[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer ++ this.notifyUpdateCache = new boolean[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer ++ } ++ ++ protected final void setupEncodeOffset(final int centerX, final int centerY, final int centerZ) { ++ // 31 = center + encodeOffset ++ this.encodeOffsetX = 31 - centerX; ++ this.encodeOffsetY = (-(this.minLightSection - 1) << 4); // we want 0 to be the smallest encoded value ++ this.encodeOffsetZ = 31 - centerZ; ++ ++ // coordinateIndex = x | (z << 6) | (y << 12) ++ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << 6) + (this.encodeOffsetY << 12); ++ ++ // 2 = (centerX >> 4) + chunkOffset ++ this.chunkOffsetX = 2 - (centerX >> 4); ++ this.chunkOffsetY = -(this.minLightSection - 1); // lowest should be 0 ++ this.chunkOffsetZ = 2 - (centerZ >> 4); ++ ++ // chunk index = x + (5 * z) ++ this.chunkIndexOffset = this.chunkOffsetX + (5 * this.chunkOffsetZ); ++ ++ // chunk section index = x + (5 * z) + ((5*5) * y) ++ this.chunkSectionIndexOffset = this.chunkIndexOffset + ((5 * 5) * this.chunkOffsetY); ++ } ++ ++ protected final void setupCaches(final LightChunkGetter chunkProvider, final int centerX, final int centerY, final int centerZ, ++ final boolean relaxed, final boolean tryToLoadChunksFor2Radius) { ++ final int centerChunkX = centerX >> 4; ++ final int centerChunkY = centerY >> 4; ++ final int centerChunkZ = centerZ >> 4; ++ ++ this.setupEncodeOffset(centerChunkX * 16 + 7, centerChunkY * 16 + 7, centerChunkZ * 16 + 7); ++ ++ final int radius = tryToLoadChunksFor2Radius ? 2 : 1; ++ ++ for (int dz = -radius; dz <= radius; ++dz) { ++ for (int dx = -radius; dx <= radius; ++dx) { ++ final int cx = centerChunkX + dx; ++ final int cz = centerChunkZ + dz; ++ final boolean isTwoRadius = Math.max(IntegerUtil.branchlessAbs(dx), IntegerUtil.branchlessAbs(dz)) == 2; ++ final ChunkAccess chunk = (ChunkAccess)chunkProvider.getChunkForLighting(cx, cz); ++ ++ if (chunk == null) { ++ if (relaxed | isTwoRadius) { ++ continue; ++ } ++ throw new IllegalArgumentException("Trying to propagate light update before 1 radius neighbours ready"); ++ } ++ ++ if (!this.canUseChunk(chunk)) { ++ continue; ++ } ++ ++ this.setChunkInCache(cx, cz, chunk); ++ this.setEmptinessMapCache(cx, cz, this.getEmptinessMap(chunk)); ++ if (!isTwoRadius) { ++ this.setBlocksForChunkInCache(cx, cz, chunk.getSections()); ++ this.setNibblesForChunkInCache(cx, cz, this.getNibblesOnChunk(chunk)); ++ } ++ } ++ } ++ } ++ ++ protected final ChunkAccess getChunkInCache(final int chunkX, final int chunkZ) { ++ return this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; ++ } ++ ++ protected final void setChunkInCache(final int chunkX, final int chunkZ, final ChunkAccess chunk) { ++ this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = chunk; ++ } ++ ++ protected final LevelChunkSection getChunkSection(final int chunkX, final int chunkY, final int chunkZ) { ++ return this.sectionCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; ++ } ++ ++ protected final void setChunkSectionInCache(final int chunkX, final int chunkY, final int chunkZ, final LevelChunkSection section) { ++ this.sectionCache[chunkX + 5*chunkZ + 5*5*chunkY + this.chunkSectionIndexOffset] = section; ++ } ++ ++ protected final void setBlocksForChunkInCache(final int chunkX, final int chunkZ, final LevelChunkSection[] sections) { ++ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { ++ this.setChunkSectionInCache(chunkX, cy, chunkZ, ++ sections == null ? null : (cy >= this.minSection && cy <= this.maxSection ? sections[cy - this.minSection] : null)); ++ } ++ } ++ ++ protected final SWMRNibbleArray getNibbleFromCache(final int chunkX, final int chunkY, final int chunkZ) { ++ return this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; ++ } ++ ++ protected final SWMRNibbleArray[] getNibblesForChunkFromCache(final int chunkX, final int chunkZ) { ++ final SWMRNibbleArray[] ret = new SWMRNibbleArray[this.maxLightSection - this.minLightSection + 1]; ++ ++ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { ++ ret[cy - this.minLightSection] = this.nibbleCache[chunkX + 5*chunkZ + (cy * (5 * 5)) + this.chunkSectionIndexOffset]; ++ } ++ ++ return ret; ++ } ++ ++ protected final void setNibbleInCache(final int chunkX, final int chunkY, final int chunkZ, final SWMRNibbleArray nibble) { ++ this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset] = nibble; ++ } ++ ++ protected final void setNibblesForChunkInCache(final int chunkX, final int chunkZ, final SWMRNibbleArray[] nibbles) { ++ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { ++ this.setNibbleInCache(chunkX, cy, chunkZ, nibbles == null ? null : nibbles[cy - this.minLightSection]); ++ } ++ } ++ ++ protected final void updateVisible(final LightChunkGetter lightAccess) { ++ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { ++ final SWMRNibbleArray nibble = this.nibbleCache[index]; ++ if (!this.notifyUpdateCache[index] && (nibble == null || !nibble.isDirty())) { ++ continue; ++ } ++ ++ final int chunkX = (index % 5) - this.chunkOffsetX; ++ final int chunkZ = ((index / 5) % 5) - this.chunkOffsetZ; ++ final int ySections = (this.maxSection - this.minSection) + 1; ++ final int chunkY = ((index / (5*5)) % (ySections + 2 + 2)) - this.chunkOffsetY; ++ if ((nibble != null && nibble.updateVisible()) || this.notifyUpdateCache[index]) { ++ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, chunkY, chunkZ)); ++ } ++ } ++ } ++ ++ protected final void destroyCaches() { ++ Arrays.fill(this.sectionCache, null); ++ Arrays.fill(this.nibbleCache, null); ++ Arrays.fill(this.chunkCache, null); ++ Arrays.fill(this.emptinessMapCache, null); ++ if (this.isClientSide) { ++ Arrays.fill(this.notifyUpdateCache, false); ++ } ++ } ++ ++ protected final BlockState getBlockState(final int worldX, final int worldY, final int worldZ) { ++ final LevelChunkSection section = this.sectionCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; ++ ++ if (section != null) { ++ return section.hasOnlyAir() ? AIR_BLOCK_STATE : section.getBlockState(worldX & 15, worldY & 15, worldZ & 15); ++ } ++ ++ return AIR_BLOCK_STATE; ++ } ++ ++ protected final BlockState getBlockState(final int sectionIndex, final int localIndex) { ++ final LevelChunkSection section = this.sectionCache[sectionIndex]; ++ ++ if (section != null) { ++ return section.hasOnlyAir() ? AIR_BLOCK_STATE : section.states.get(localIndex); ++ } ++ ++ return AIR_BLOCK_STATE; ++ } ++ ++ protected final int getLightLevel(final int worldX, final int worldY, final int worldZ) { ++ final SWMRNibbleArray nibble = this.nibbleCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; ++ ++ return nibble == null ? 0 : nibble.getUpdating((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8)); ++ } ++ ++ protected final int getLightLevel(final int sectionIndex, final int localIndex) { ++ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; ++ ++ return nibble == null ? 0 : nibble.getUpdating(localIndex); ++ } ++ ++ protected final void setLightLevel(final int worldX, final int worldY, final int worldZ, final int level) { ++ final int sectionIndex = (worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset; ++ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; ++ ++ if (nibble != null) { ++ nibble.set((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8), level); ++ if (this.isClientSide) { ++ int cx1 = (worldX - 1) >> 4; ++ int cx2 = (worldX + 1) >> 4; ++ int cy1 = (worldY - 1) >> 4; ++ int cy2 = (worldY + 1) >> 4; ++ int cz1 = (worldZ - 1) >> 4; ++ int cz2 = (worldZ + 1) >> 4; ++ for (int x = cx1; x <= cx2; ++x) { ++ for (int y = cy1; y <= cy2; ++y) { ++ for (int z = cz1; z <= cz2; ++z) { ++ this.notifyUpdateCache[x + 5 * z + (5 * 5) * y + this.chunkSectionIndexOffset] = true; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ protected final void postLightUpdate(final int worldX, final int worldY, final int worldZ) { ++ if (this.isClientSide) { ++ int cx1 = (worldX - 1) >> 4; ++ int cx2 = (worldX + 1) >> 4; ++ int cy1 = (worldY - 1) >> 4; ++ int cy2 = (worldY + 1) >> 4; ++ int cz1 = (worldZ - 1) >> 4; ++ int cz2 = (worldZ + 1) >> 4; ++ for (int x = cx1; x <= cx2; ++x) { ++ for (int y = cy1; y <= cy2; ++y) { ++ for (int z = cz1; z <= cz2; ++z) { ++ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; ++ } ++ } ++ } ++ } ++ } ++ ++ protected final void setLightLevel(final int sectionIndex, final int localIndex, final int worldX, final int worldY, final int worldZ, final int level) { ++ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; ++ ++ if (nibble != null) { ++ nibble.set(localIndex, level); ++ if (this.isClientSide) { ++ int cx1 = (worldX - 1) >> 4; ++ int cx2 = (worldX + 1) >> 4; ++ int cy1 = (worldY - 1) >> 4; ++ int cy2 = (worldY + 1) >> 4; ++ int cz1 = (worldZ - 1) >> 4; ++ int cz2 = (worldZ + 1) >> 4; ++ for (int x = cx1; x <= cx2; ++x) { ++ for (int y = cy1; y <= cy2; ++y) { ++ for (int z = cz1; z <= cz2; ++z) { ++ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ protected final boolean[] getEmptinessMap(final int chunkX, final int chunkZ) { ++ return this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; ++ } ++ ++ protected final void setEmptinessMapCache(final int chunkX, final int chunkZ, final boolean[] emptinessMap) { ++ this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = emptinessMap; ++ } ++ ++ public static SWMRNibbleArray[] getFilledEmptyLight(final LevelHeightAccessor world) { ++ return getFilledEmptyLight(WorldUtil.getTotalLightSections(world)); ++ } ++ ++ private static SWMRNibbleArray[] getFilledEmptyLight(final int totalLightSections) { ++ final SWMRNibbleArray[] ret = new SWMRNibbleArray[totalLightSections]; ++ ++ for (int i = 0, len = ret.length; i < len; ++i) { ++ ret[i] = new SWMRNibbleArray(null, true); ++ } ++ ++ return ret; ++ } ++ ++ protected abstract boolean[] getEmptinessMap(final ChunkAccess chunk); ++ ++ protected abstract void setEmptinessMap(final ChunkAccess chunk, final boolean[] to); ++ ++ protected abstract SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk); ++ ++ protected abstract void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to); ++ ++ protected abstract boolean canUseChunk(final ChunkAccess chunk); ++ ++ public final void blocksChangedInChunk(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, ++ final Set positions, final Boolean[] changedSections) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); ++ try { ++ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); ++ if (chunk == null) { ++ return; ++ } ++ if (changedSections != null) { ++ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, changedSections, false); ++ if (ret != null) { ++ this.setEmptinessMap(chunk, ret); ++ } ++ } ++ if (!positions.isEmpty()) { ++ this.propagateBlockChanges(lightAccess, chunk, positions); ++ } ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ protected abstract void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions); ++ ++ protected abstract void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ); ++ ++ // if ret > expect, then the real value is at least ret (early returns if ret > expect, rather than calculating actual) ++ // if ret == expect, then expect is the correct light value for pos ++ // if ret < expect, then ret is the real light value ++ protected abstract int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, ++ final int expect); ++ ++ protected final int[] chunkCheckDelayedUpdatesCenter = new int[16 * 16]; ++ protected final int[] chunkCheckDelayedUpdatesNeighbour = new int[16 * 16]; ++ ++ protected void checkChunkEdge(final LightChunkGetter lightAccess, final ChunkAccess chunk, ++ final int chunkX, final int chunkY, final int chunkZ) { ++ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (currNibble == null) { ++ return; ++ } ++ ++ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { ++ final int neighbourOffX = direction.x; ++ final int neighbourOffZ = direction.z; ++ ++ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, ++ chunkY, chunkZ + neighbourOffZ); ++ ++ if (neighbourNibble == null) { ++ continue; ++ } ++ ++ if (!currNibble.isInitialisedUpdating() && !neighbourNibble.isInitialisedUpdating()) { ++ // both are zero, nothing to check. ++ continue; ++ } ++ ++ // this chunk ++ final int incX; ++ final int incZ; ++ final int startX; ++ final int startZ; ++ ++ if (neighbourOffX != 0) { ++ // x direction ++ incX = 0; ++ incZ = 1; ++ ++ if (direction.x < 0) { ++ // negative ++ startX = chunkX << 4; ++ } else { ++ startX = chunkX << 4 | 15; ++ } ++ startZ = chunkZ << 4; ++ } else { ++ // z direction ++ incX = 1; ++ incZ = 0; ++ ++ if (neighbourOffZ < 0) { ++ // negative ++ startZ = chunkZ << 4; ++ } else { ++ startZ = chunkZ << 4 | 15; ++ } ++ startX = chunkX << 4; ++ } ++ ++ int centerDelayedChecks = 0; ++ int neighbourDelayedChecks = 0; ++ for (int currY = chunkY << 4, maxY = currY | 15; currY <= maxY; ++currY) { ++ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { ++ final int neighbourX = currX + neighbourOffX; ++ final int neighbourZ = currZ + neighbourOffZ; ++ ++ final int currentIndex = (currX & 15) | ++ ((currZ & 15)) << 4 | ++ ((currY & 15) << 8); ++ final int currentLevel = currNibble.getUpdating(currentIndex); ++ ++ final int neighbourIndex = ++ (neighbourX & 15) | ++ ((neighbourZ & 15)) << 4 | ++ ((currY & 15) << 8); ++ final int neighbourLevel = neighbourNibble.getUpdating(neighbourIndex); ++ ++ // the checks are delayed because the checkBlock method clobbers light values - which then ++ // affect later calculate light value operations. While they don't affect it in a behaviourly significant ++ // way, they do have a negative performance impact due to simply queueing more values ++ ++ if (this.calculateLightValue(lightAccess, currX, currY, currZ, currentLevel) != currentLevel) { ++ this.chunkCheckDelayedUpdatesCenter[centerDelayedChecks++] = currentIndex; ++ } ++ ++ if (this.calculateLightValue(lightAccess, neighbourX, currY, neighbourZ, neighbourLevel) != neighbourLevel) { ++ this.chunkCheckDelayedUpdatesNeighbour[neighbourDelayedChecks++] = neighbourIndex; ++ } ++ } ++ } ++ ++ final int currentChunkOffX = chunkX << 4; ++ final int currentChunkOffZ = chunkZ << 4; ++ final int neighbourChunkOffX = (chunkX + direction.x) << 4; ++ final int neighbourChunkOffZ = (chunkZ + direction.z) << 4; ++ final int chunkOffY = chunkY << 4; ++ for (int i = 0, len = Math.max(centerDelayedChecks, neighbourDelayedChecks); i < len; ++i) { ++ // try to queue neighbouring data together ++ // index = x | (z << 4) | (y << 8) ++ if (i < centerDelayedChecks) { ++ final int value = this.chunkCheckDelayedUpdatesCenter[i]; ++ this.checkBlock(lightAccess, currentChunkOffX | (value & 15), ++ chunkOffY | (value >>> 8), ++ currentChunkOffZ | ((value >>> 4) & 0xF)); ++ } ++ if (i < neighbourDelayedChecks) { ++ final int value = this.chunkCheckDelayedUpdatesNeighbour[i]; ++ this.checkBlock(lightAccess, neighbourChunkOffX | (value & 15), ++ chunkOffY | (value >>> 8), ++ neighbourChunkOffZ | ((value >>> 4) & 0xF)); ++ } ++ } ++ } ++ } ++ ++ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) { ++ final ChunkPos chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { ++ this.checkChunkEdge(lightAccess, chunk, chunkX, iterator.nextShort(), chunkZ); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ // verifies that light levels on this chunks edges are consistent with this chunk's neighbours ++ // edges. if they are not, they are decreased (effectively performing the logic in checkBlock). ++ // This does not resolve skylight source problems. ++ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, final int toSection) { ++ final ChunkPos chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { ++ this.checkChunkEdge(lightAccess, chunk, chunkX, currSectionY, chunkZ); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ // pulls light from neighbours, and adds them into the increase queue. does not actually propagate. ++ protected final void propagateNeighbourLevels(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, final int toSection) { ++ final ChunkPos chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { ++ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ); ++ if (currNibble == null) { ++ continue; ++ } ++ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { ++ final int neighbourOffX = direction.x; ++ final int neighbourOffZ = direction.z; ++ ++ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, ++ currSectionY, chunkZ + neighbourOffZ); ++ ++ if (neighbourNibble == null || !neighbourNibble.isInitialisedUpdating()) { ++ // can't pull from 0 ++ continue; ++ } ++ ++ // neighbour chunk ++ final int incX; ++ final int incZ; ++ final int startX; ++ final int startZ; ++ ++ if (neighbourOffX != 0) { ++ // x direction ++ incX = 0; ++ incZ = 1; ++ ++ if (direction.x < 0) { ++ // negative ++ startX = (chunkX << 4) - 1; ++ } else { ++ startX = (chunkX << 4) + 16; ++ } ++ startZ = chunkZ << 4; ++ } else { ++ // z direction ++ incX = 1; ++ incZ = 0; ++ ++ if (neighbourOffZ < 0) { ++ // negative ++ startZ = (chunkZ << 4) - 1; ++ } else { ++ startZ = (chunkZ << 4) + 16; ++ } ++ startX = chunkX << 4; ++ } ++ ++ final long propagateDirection = 1L << direction.getOpposite().ordinal(); // we only want to check in this direction towards this chunk ++ final int encodeOffset = this.coordinateOffset; ++ ++ for (int currY = currSectionY << 4, maxY = currY | 15; currY <= maxY; ++currY) { ++ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { ++ final int level = neighbourNibble.getUpdating( ++ (currX & 15) ++ | ((currZ & 15) << 4) ++ | ((currY & 15) << 8) ++ ); ++ ++ if (level <= 1) { ++ // nothing to propagate ++ continue; ++ } ++ ++ this.appendToIncreaseQueue( ++ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((level & 0xFL) << (6 + 6 + 16)) ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the current block is transparent, must check. ++ ); ++ } ++ } ++ } ++ } ++ } ++ ++ public static Boolean[] getEmptySectionsForChunk(final ChunkAccess chunk) { ++ final LevelChunkSection[] sections = chunk.getSections(); ++ final Boolean[] ret = new Boolean[sections.length]; ++ ++ for (int i = 0; i < sections.length; ++i) { ++ if (sections[i] == null || sections[i].hasOnlyAir()) { ++ ret[i] = Boolean.TRUE; ++ } else { ++ ret[i] = Boolean.FALSE; ++ } ++ } ++ ++ return ret; ++ } ++ ++ public final void forceHandleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptinessChanges) { ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); ++ try { ++ // force current chunk into cache ++ this.setChunkInCache(chunkX, chunkZ, chunk); ++ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); ++ this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk)); ++ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); ++ ++ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); ++ if (ret != null) { ++ this.setEmptinessMap(chunk, ret); ++ } ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ public final void handleEmptySectionChanges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, ++ final Boolean[] emptinessChanges) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); ++ try { ++ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); ++ if (chunk == null) { ++ return; ++ } ++ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); ++ if (ret != null) { ++ this.setEmptinessMap(chunk, ret); ++ } ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ protected abstract void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles); ++ ++ protected abstract void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ); ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ // subclasses are guaranteed that this is always called before a changed block set ++ // newChunk specifies whether the changes describe a "first load" of a chunk or changes to existing, already loaded chunks ++ // rets non-null when the emptiness map changed and needs to be updated ++ protected final boolean[] handleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk, ++ final Boolean[] emptinessChanges, final boolean unlit) { ++ final Level world = (Level)lightAccess.getLevel(); ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ ++ boolean[] chunkEmptinessMap = this.getEmptinessMap(chunkX, chunkZ); ++ boolean[] ret = null; ++ final boolean needsInit = unlit || chunkEmptinessMap == null; ++ if (needsInit) { ++ this.setEmptinessMapCache(chunkX, chunkZ, ret = chunkEmptinessMap = new boolean[WorldUtil.getTotalSections(world)]); ++ } ++ ++ // update emptiness map ++ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { ++ Boolean valueBoxed = emptinessChanges[sectionIndex]; ++ if (valueBoxed == null) { ++ if (!needsInit) { ++ continue; ++ } ++ final LevelChunkSection section = this.getChunkSection(chunkX, sectionIndex + this.minSection, chunkZ); ++ emptinessChanges[sectionIndex] = valueBoxed = section == null || section.hasOnlyAir() ? Boolean.TRUE : Boolean.FALSE; ++ } ++ chunkEmptinessMap[sectionIndex] = valueBoxed.booleanValue(); ++ } ++ ++ // now init neighbour nibbles ++ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { ++ final Boolean valueBoxed = emptinessChanges[sectionIndex]; ++ final int sectionY = sectionIndex + this.minSection; ++ if (valueBoxed == null) { ++ continue; ++ } ++ ++ final boolean empty = valueBoxed.booleanValue(); ++ ++ if (empty) { ++ continue; ++ } ++ ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ // if we're not empty, we also need to initialise nibbles ++ // note: if we're unlit, we absolutely do not want to extrude, as light data isn't set up ++ final boolean extrude = (dx | dz) != 0 || !unlit; ++ for (int dy = 1; dy >= -1; --dy) { ++ this.initNibble(dx + chunkX, dy + sectionY, dz + chunkZ, extrude, false); ++ } ++ } ++ } ++ } ++ ++ // check for de-init and lazy-init ++ // lazy init is when chunks are being lit, so at the time they weren't loaded when their neighbours were running ++ // init checks. ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ // does this neighbour have 1 radius loaded? ++ boolean neighboursLoaded = true; ++ neighbour_loaded_search: ++ for (int dz2 = -1; dz2 <= 1; ++dz2) { ++ for (int dx2 = -1; dx2 <= 1; ++dx2) { ++ if (this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ) == null) { ++ neighboursLoaded = false; ++ break neighbour_loaded_search; ++ } ++ } ++ } ++ ++ for (int sectionY = this.maxLightSection; sectionY >= this.minLightSection; --sectionY) { ++ // check neighbours to see if we need to de-init this one ++ boolean allEmpty = true; ++ neighbour_search: ++ for (int dy2 = -1; dy2 <= 1; ++dy2) { ++ for (int dz2 = -1; dz2 <= 1; ++dz2) { ++ for (int dx2 = -1; dx2 <= 1; ++dx2) { ++ final int y = sectionY + dy2; ++ if (y < this.minSection || y > this.maxSection) { ++ // empty ++ continue; ++ } ++ final boolean[] emptinessMap = this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ); ++ if (emptinessMap != null) { ++ if (!emptinessMap[y - this.minSection]) { ++ allEmpty = false; ++ break neighbour_search; ++ } ++ } else { ++ final LevelChunkSection section = this.getChunkSection(dx + dx2 + chunkX, y, dz + dz2 + chunkZ); ++ if (section != null && !section.hasOnlyAir()) { ++ allEmpty = false; ++ break neighbour_search; ++ } ++ } ++ } ++ } ++ } ++ ++ if (allEmpty & neighboursLoaded) { ++ // can only de-init when neighbours are loaded ++ // de-init is fine to delay, as de-init is just an optimisation - it's not required for lighting ++ // to be correct ++ ++ // all were empty, so de-init ++ this.setNibbleNull(dx + chunkX, sectionY, dz + chunkZ); ++ } else if (!allEmpty) { ++ // must init ++ final boolean extrude = (dx | dz) != 0 || !unlit; ++ this.initNibble(dx + chunkX, sectionY, dz + chunkZ, extrude, false); ++ } ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); ++ try { ++ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); ++ if (chunk == null) { ++ return; ++ } ++ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, final ShortCollection sections) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); ++ try { ++ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); ++ if (chunk == null) { ++ return; ++ } ++ this.checkChunkEdges(lightAccess, chunk, sections); ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ // needsEdgeChecks applies when possibly loading vanilla data, which means we need to validate the current ++ // chunks light values with respect to neighbours ++ // subclasses should note that the emptiness changes are propagated BEFORE this is called, so this function ++ // does not need to detect empty chunks itself (and it should do no handling for them either!) ++ protected abstract void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks); ++ ++ public final void light(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptySections) { ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); ++ ++ try { ++ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.maxLightSection - this.minLightSection + 1); ++ // force current chunk into cache ++ this.setChunkInCache(chunkX, chunkZ, chunk); ++ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); ++ this.setNibblesForChunkInCache(chunkX, chunkZ, nibbles); ++ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); ++ ++ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptySections, true); ++ if (ret != null) { ++ this.setEmptinessMap(chunk, ret); ++ } ++ this.lightChunk(lightAccess, chunk, true); ++ this.setNibbles(chunk, nibbles); ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ public final void relightChunks(final LightChunkGetter lightAccess, final Set chunks, ++ final Consumer chunkLightCallback, final IntConsumer onComplete) { ++ // it's recommended for maximum performance that the set is ordered according to a BFS from the center of ++ // the region of chunks to relight ++ // it's required that tickets are added for each chunk to keep them loaded ++ final Long2ObjectOpenHashMap nibblesByChunk = new Long2ObjectOpenHashMap<>(); ++ final Long2ObjectOpenHashMap emptinessMapByChunk = new Long2ObjectOpenHashMap<>(); ++ ++ final int[] neighbourLightOrder = new int[] { ++ // d = 0 ++ 0, 0, ++ // d = 1 ++ -1, 0, ++ 0, -1, ++ 1, 0, ++ 0, 1, ++ // d = 2 ++ -1, 1, ++ 1, 1, ++ -1, -1, ++ 1, -1, ++ }; ++ ++ int lightCalls = 0; ++ ++ for (final ChunkPos chunkPos : chunks) { ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ final ChunkAccess chunk = (ChunkAccess)lightAccess.getChunkForLighting(chunkX, chunkZ); ++ if (chunk == null || !this.canUseChunk(chunk)) { ++ throw new IllegalStateException(); ++ } ++ ++ for (int i = 0, len = neighbourLightOrder.length; i < len; i += 2) { ++ final int dx = neighbourLightOrder[i]; ++ final int dz = neighbourLightOrder[i + 1]; ++ final int neighbourX = dx + chunkX; ++ final int neighbourZ = dz + chunkZ; ++ ++ final ChunkAccess neighbour = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX, neighbourZ); ++ if (neighbour == null || !this.canUseChunk(neighbour)) { ++ continue; ++ } ++ ++ if (nibblesByChunk.get(CoordinateUtils.getChunkKey(neighbourX, neighbourZ)) != null) { ++ // lit already called for neighbour, no need to light it now ++ continue; ++ } ++ ++ // light neighbour chunk ++ this.setupEncodeOffset(neighbourX * 16 + 7, 128, neighbourZ * 16 + 7); ++ try { ++ // insert all neighbouring chunks for this neighbour that we have data for ++ for (int dz2 = -1; dz2 <= 1; ++dz2) { ++ for (int dx2 = -1; dx2 <= 1; ++dx2) { ++ final int neighbourX2 = neighbourX + dx2; ++ final int neighbourZ2 = neighbourZ + dz2; ++ final long key = CoordinateUtils.getChunkKey(neighbourX2, neighbourZ2); ++ final ChunkAccess neighbour2 = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX2, neighbourZ2); ++ if (neighbour2 == null || !this.canUseChunk(neighbour2)) { ++ continue; ++ } ++ ++ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(key); ++ if (nibbles == null) { ++ // we haven't lit this chunk ++ continue; ++ } ++ ++ this.setChunkInCache(neighbourX2, neighbourZ2, neighbour2); ++ this.setBlocksForChunkInCache(neighbourX2, neighbourZ2, neighbour2.getSections()); ++ this.setNibblesForChunkInCache(neighbourX2, neighbourZ2, nibbles); ++ this.setEmptinessMapCache(neighbourX2, neighbourZ2, emptinessMapByChunk.get(key)); ++ } ++ } ++ ++ final long key = CoordinateUtils.getChunkKey(neighbourX, neighbourZ); ++ ++ // now insert the neighbour chunk and light it ++ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.world); ++ nibblesByChunk.put(key, nibbles); ++ ++ this.setChunkInCache(neighbourX, neighbourZ, neighbour); ++ this.setBlocksForChunkInCache(neighbourX, neighbourZ, neighbour.getSections()); ++ this.setNibblesForChunkInCache(neighbourX, neighbourZ, nibbles); ++ ++ final boolean[] neighbourEmptiness = this.handleEmptySectionChanges(lightAccess, neighbour, getEmptySectionsForChunk(neighbour), true); ++ emptinessMapByChunk.put(key, neighbourEmptiness); ++ if (chunks.contains(new ChunkPos(neighbourX, neighbourZ))) { ++ this.setEmptinessMap(neighbour, neighbourEmptiness); ++ } ++ ++ this.lightChunk(lightAccess, neighbour, false); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ // done lighting all neighbours, so the chunk is now fully lit ++ ++ // make sure nibbles are fully updated before calling back ++ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ for (final SWMRNibbleArray nibble : nibbles) { ++ nibble.updateVisible(); ++ } ++ ++ this.setNibbles(chunk, nibbles); ++ ++ for (int y = this.minLightSection; y <= this.maxLightSection; ++y) { ++ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, y, chunkX)); ++ } ++ ++ // now do callback ++ if (chunkLightCallback != null) { ++ chunkLightCallback.accept(chunkPos); ++ } ++ ++lightCalls; ++ } ++ ++ if (onComplete != null) { ++ onComplete.accept(lightCalls); ++ } ++ } ++ ++ // contains: ++ // lower (6 + 6 + 16) = 28 bits: encoded coordinate position (x | (z << 6) | (y << (6 + 6)))) ++ // next 4 bits: propagated light level (0, 15] ++ // next 6 bits: propagation direction bitset ++ // next 24 bits: unused ++ // last 3 bits: state flags ++ // state flags: ++ // whether the increase propagator needs to write the propagated level to the position, used to avoid cascading light ++ // updates for block sources ++ protected static final long FLAG_WRITE_LEVEL = Long.MIN_VALUE >>> 2; ++ // whether the propagation needs to check if its current level is equal to the expected level ++ // used only in increase propagation ++ protected static final long FLAG_RECHECK_LEVEL = Long.MIN_VALUE >>> 1; ++ // whether the propagation needs to consider if its block is conditionally transparent ++ protected static final long FLAG_HAS_SIDED_TRANSPARENT_BLOCKS = Long.MIN_VALUE; ++ ++ protected long[] increaseQueue = new long[16 * 16 * 16]; ++ protected int increaseQueueInitialLength; ++ protected long[] decreaseQueue = new long[16 * 16 * 16]; ++ protected int decreaseQueueInitialLength; ++ ++ protected final long[] resizeIncreaseQueue() { ++ return this.increaseQueue = Arrays.copyOf(this.increaseQueue, this.increaseQueue.length * 2); ++ } ++ ++ protected final long[] resizeDecreaseQueue() { ++ return this.decreaseQueue = Arrays.copyOf(this.decreaseQueue, this.decreaseQueue.length * 2); ++ } ++ ++ protected final void appendToIncreaseQueue(final long value) { ++ final int idx = this.increaseQueueInitialLength++; ++ long[] queue = this.increaseQueue; ++ if (idx >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ queue[idx] = value; ++ } else { ++ queue[idx] = value; ++ } ++ } ++ ++ protected final void appendToDecreaseQueue(final long value) { ++ final int idx = this.decreaseQueueInitialLength++; ++ long[] queue = this.decreaseQueue; ++ if (idx >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ queue[idx] = value; ++ } else { ++ queue[idx] = value; ++ } ++ } ++ ++ protected static final AxisDirection[][] OLD_CHECK_DIRECTIONS = new AxisDirection[1 << 6][]; ++ protected static final int ALL_DIRECTIONS_BITSET = (1 << 6) - 1; ++ static { ++ for (int i = 0; i < OLD_CHECK_DIRECTIONS.length; ++i) { ++ final List directions = new ArrayList<>(); ++ for (int bitset = i, len = Integer.bitCount(i), index = 0; index < len; ++index, bitset ^= IntegerUtil.getTrailingBit(bitset)) { ++ directions.add(AXIS_DIRECTIONS[IntegerUtil.trailingZeros(bitset)]); ++ } ++ OLD_CHECK_DIRECTIONS[i] = directions.toArray(new AxisDirection[0]); ++ } ++ } ++ ++ protected final void performLightIncrease(final LightChunkGetter lightAccess) { ++ final BlockGetter world = lightAccess.getLevel(); ++ long[] queue = this.increaseQueue; ++ int queueReadIndex = 0; ++ int queueLength = this.increaseQueueInitialLength; ++ this.increaseQueueInitialLength = 0; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ final int encodeOffset = this.coordinateOffset; ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ ++ while (queueReadIndex < queueLength) { ++ final long queueValue = queue[queueReadIndex++]; ++ ++ final int posX = ((int)queueValue & 63) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; ++ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; ++ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xFL); ++ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63L)]; ++ ++ if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { ++ if (this.getLightLevel(posX, posY, posZ) != propagatedLightLevel) { ++ // not at the level we expect, so something changed. ++ continue; ++ } ++ } else if ((queueValue & FLAG_WRITE_LEVEL) != 0L) { ++ // these are used to restore block sources after a propagation decrease ++ this.setLightLevel(posX, posY, posZ, propagatedLightLevel); ++ } ++ ++ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { ++ // we don't need to worry about our state here. ++ for (final AxisDirection propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; ++ final int currentLevel; ++ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { ++ continue; // already at the level we want or unloaded ++ } ++ ++ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); ++ if (blockState == null) { ++ continue; ++ } ++ final int opacityCached = blockState.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); ++ if (targetLevel > currentLevel) { ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); ++ continue; ++ } ++ } ++ continue; ++ } else { ++ this.mutablePos1.set(offX, offY, offZ); ++ long flags = 0; ++ if (blockState.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockState.getLightBlock(world, this.mutablePos1); ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); ++ if (targetLevel <= currentLevel) { ++ continue; ++ } ++ ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) ++ | (flags); ++ } ++ continue; ++ } ++ } ++ } else { ++ // we actually need to worry about our state here ++ final BlockState fromBlock = this.getBlockState(posX, posY, posZ); ++ this.mutablePos2.set(posX, posY, posZ); ++ for (final AxisDirection propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final VoxelShape fromShape = fromBlock.isConditionallyFullOpaque() ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); ++ ++ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { ++ continue; ++ } ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; ++ final int currentLevel; ++ ++ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { ++ continue; // already at the level we want ++ } ++ ++ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); ++ if (blockState == null) { ++ continue; ++ } ++ final int opacityCached = blockState.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); ++ if (targetLevel > currentLevel) { ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); ++ continue; ++ } ++ } ++ continue; ++ } else { ++ this.mutablePos1.set(offX, offY, offZ); ++ long flags = 0; ++ if (blockState.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockState.getLightBlock(world, this.mutablePos1); ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); ++ if (targetLevel <= currentLevel) { ++ continue; ++ } ++ ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) ++ | (flags); ++ } ++ continue; ++ } ++ } ++ } ++ } ++ } ++ ++ protected final void performLightDecrease(final LightChunkGetter lightAccess) { ++ final BlockGetter world = lightAccess.getLevel(); ++ long[] queue = this.decreaseQueue; ++ long[] increaseQueue = this.increaseQueue; ++ int queueReadIndex = 0; ++ int queueLength = this.decreaseQueueInitialLength; ++ this.decreaseQueueInitialLength = 0; ++ int increaseQueueLength = this.increaseQueueInitialLength; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ final int encodeOffset = this.coordinateOffset; ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ final int emittedMask = this.emittedLightMask; ++ ++ while (queueReadIndex < queueLength) { ++ final long queueValue = queue[queueReadIndex++]; ++ ++ final int posX = ((int)queueValue & 63) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; ++ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; ++ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); ++ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63)]; ++ ++ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { ++ // we don't need to worry about our state here. ++ for (final AxisDirection propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; ++ final int lightLevel; ++ ++ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { ++ // already at lowest (or unloaded), nothing we can do ++ continue; ++ } ++ ++ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); ++ if (blockState == null) { ++ continue; ++ } ++ final int opacityCached = blockState.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | FLAG_RECHECK_LEVEL; ++ continue; ++ } ++ final int emittedLight = blockState.getLightEmission() & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ // note: do not set recheck level, or else the propagation will fail ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (blockState.isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); ++ } ++ ++ currentNibble.set(localIndex, 0); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); ++ continue; ++ } ++ continue; ++ } else { ++ this.mutablePos1.set(offX, offY, offZ); ++ long flags = 0; ++ if (blockState.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockState.getLightBlock(world, this.mutablePos1); ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (FLAG_RECHECK_LEVEL | flags); ++ continue; ++ } ++ final int emittedLight = blockState.getLightEmission() & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ // note: do not set recheck level, or else the propagation will fail ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (flags | FLAG_WRITE_LEVEL); ++ } ++ ++ currentNibble.set(localIndex, 0); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) ++ | flags; ++ } ++ continue; ++ } ++ } ++ } else { ++ // we actually need to worry about our state here ++ final BlockState fromBlock = this.getBlockState(posX, posY, posZ); ++ this.mutablePos2.set(posX, posY, posZ); ++ for (final AxisDirection propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final VoxelShape fromShape = (fromBlock.isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); ++ ++ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { ++ continue; ++ } ++ ++ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; ++ final int lightLevel; ++ ++ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { ++ // already at lowest (or unloaded), nothing we can do ++ continue; ++ } ++ ++ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); ++ if (blockState == null) { ++ continue; ++ } ++ final int opacityCached = blockState.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | FLAG_RECHECK_LEVEL; ++ continue; ++ } ++ final int emittedLight = blockState.getLightEmission() & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ // note: do not set recheck level, or else the propagation will fail ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (blockState.isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); ++ } ++ ++ currentNibble.set(localIndex, 0); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); ++ continue; ++ } ++ continue; ++ } else { ++ this.mutablePos1.set(offX, offY, offZ); ++ long flags = 0; ++ if (blockState.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockState.getLightBlock(world, this.mutablePos1); ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (FLAG_RECHECK_LEVEL | flags); ++ continue; ++ } ++ final int emittedLight = blockState.getLightEmission() & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ // note: do not set recheck level, or else the propagation will fail ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (flags | FLAG_WRITE_LEVEL); ++ } ++ ++ currentNibble.set(localIndex, 0); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) ++ | flags; ++ } ++ continue; ++ } ++ } ++ } ++ } ++ ++ // propagate sources we clobbered ++ this.increaseQueueInitialLength = increaseQueueLength; ++ this.performLightIncrease(lightAccess); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java +new file mode 100644 +index 0000000000000000000000000000000000000000..499c069d64692872924963d3a7ac39664b20468d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java +@@ -0,0 +1,674 @@ ++package ca.spottedleaf.starlight.common.light; ++ ++import ca.spottedleaf.starlight.common.util.CoordinateUtils; ++import ca.spottedleaf.starlight.common.util.WorldUtil; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.shorts.ShortCollection; ++import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; ++import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.SectionPos; ++import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.TicketType; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.DataLayer; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LightChunkGetter; ++import net.minecraft.world.level.lighting.LayerLightEventListener; ++import net.minecraft.world.level.lighting.LevelLightEngine; ++import java.util.ArrayDeque; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Set; ++import java.util.concurrent.CompletableFuture; ++import java.util.function.Consumer; ++import java.util.function.IntConsumer; ++ ++public final class StarLightInterface { ++ ++ public static final TicketType CHUNK_WORK_TICKET = TicketType.create("starlight_chunk_work_ticket", (p1, p2) -> Long.compare(p1.toLong(), p2.toLong())); ++ ++ /** ++ * Can be {@code null}, indicating the light is all empty. ++ */ ++ protected final Level world; ++ protected final LightChunkGetter lightAccess; ++ ++ protected final ArrayDeque cachedSkyPropagators; ++ protected final ArrayDeque cachedBlockPropagators; ++ ++ protected final LightQueue lightQueue = new LightQueue(this); ++ ++ protected final LayerLightEventListener skyReader; ++ protected final LayerLightEventListener blockReader; ++ protected final boolean isClientSide; ++ ++ protected final int minSection; ++ protected final int maxSection; ++ protected final int minLightSection; ++ protected final int maxLightSection; ++ ++ public final LevelLightEngine lightEngine; ++ ++ private final boolean hasBlockLight; ++ private final boolean hasSkyLight; ++ ++ public StarLightInterface(final LightChunkGetter lightAccess, final boolean hasSkyLight, final boolean hasBlockLight, final LevelLightEngine lightEngine) { ++ this.lightAccess = lightAccess; ++ this.world = lightAccess == null ? null : (Level)lightAccess.getLevel(); ++ this.cachedSkyPropagators = hasSkyLight && lightAccess != null ? new ArrayDeque<>() : null; ++ this.cachedBlockPropagators = hasBlockLight && lightAccess != null ? new ArrayDeque<>() : null; ++ this.isClientSide = !(this.world instanceof ServerLevel); ++ if (this.world == null) { ++ this.minSection = -4; ++ this.maxSection = 19; ++ this.minLightSection = -5; ++ this.maxLightSection = 20; ++ } else { ++ this.minSection = WorldUtil.getMinSection(this.world); ++ this.maxSection = WorldUtil.getMaxSection(this.world); ++ this.minLightSection = WorldUtil.getMinLightSection(this.world); ++ this.maxLightSection = WorldUtil.getMaxLightSection(this.world); ++ } ++ this.lightEngine = lightEngine; ++ this.hasBlockLight = hasBlockLight; ++ this.hasSkyLight = hasSkyLight; ++ this.skyReader = !hasSkyLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() { ++ @Override ++ public void checkBlock(final BlockPos blockPos) { ++ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable()); ++ } ++ ++ @Override ++ public void propagateLightSources(final ChunkPos chunkPos) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public boolean hasLightWork() { ++ // not really correct... ++ return StarLightInterface.this.hasUpdates(); ++ } ++ ++ @Override ++ public int runLightUpdates() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setLightEnabled(final ChunkPos chunkPos, final boolean bl) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public DataLayer getDataLayerData(final SectionPos pos) { ++ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); ++ if (chunk == null || (!StarLightInterface.this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { ++ return null; ++ } ++ ++ final int sectionY = pos.getY(); ++ ++ if (sectionY > StarLightInterface.this.maxLightSection || sectionY < StarLightInterface.this.minLightSection) { ++ return null; ++ } ++ ++ if (chunk.getSkyEmptinessMap() == null) { ++ return null; ++ } ++ ++ return chunk.getSkyNibbles()[sectionY - StarLightInterface.this.minLightSection].toVanillaNibble(); ++ } ++ ++ @Override ++ public int getLightValue(final BlockPos blockPos) { ++ return StarLightInterface.this.getSkyLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4)); ++ } ++ ++ @Override ++ public void updateSectionStatus(final SectionPos pos, final boolean notReady) { ++ StarLightInterface.this.sectionChange(pos, notReady); ++ } ++ }; ++ this.blockReader = !hasBlockLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() { ++ @Override ++ public void checkBlock(final BlockPos blockPos) { ++ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable()); ++ } ++ ++ @Override ++ public void propagateLightSources(final ChunkPos chunkPos) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public boolean hasLightWork() { ++ // not really correct... ++ return StarLightInterface.this.hasUpdates(); ++ } ++ ++ @Override ++ public int runLightUpdates() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setLightEnabled(final ChunkPos chunkPos, final boolean bl) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public DataLayer getDataLayerData(final SectionPos pos) { ++ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); ++ ++ if (chunk == null || pos.getY() < StarLightInterface.this.minLightSection || pos.getY() > StarLightInterface.this.maxLightSection) { ++ return null; ++ } ++ ++ return chunk.getBlockNibbles()[pos.getY() - StarLightInterface.this.minLightSection].toVanillaNibble(); ++ } ++ ++ @Override ++ public int getLightValue(final BlockPos blockPos) { ++ return StarLightInterface.this.getBlockLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4)); ++ } ++ ++ @Override ++ public void updateSectionStatus(final SectionPos pos, final boolean notReady) { ++ StarLightInterface.this.sectionChange(pos, notReady); ++ } ++ }; ++ } ++ ++ public boolean hasSkyLight() { ++ return this.hasSkyLight; ++ } ++ ++ public boolean hasBlockLight() { ++ return this.hasBlockLight; ++ } ++ ++ public int getSkyLightValue(final BlockPos blockPos, final ChunkAccess chunk) { ++ if (!this.hasSkyLight) { ++ return 0; ++ } ++ final int x = blockPos.getX(); ++ int y = blockPos.getY(); ++ final int z = blockPos.getZ(); ++ ++ final int minSection = this.minSection; ++ final int maxSection = this.maxSection; ++ final int minLightSection = this.minLightSection; ++ final int maxLightSection = this.maxLightSection; ++ ++ if (chunk == null || (!this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { ++ return 15; ++ } ++ ++ int sectionY = y >> 4; ++ ++ if (sectionY > maxLightSection) { ++ return 15; ++ } ++ ++ if (sectionY < minLightSection) { ++ sectionY = minLightSection; ++ y = sectionY << 4; ++ } ++ ++ final SWMRNibbleArray[] nibbles = chunk.getSkyNibbles(); ++ final SWMRNibbleArray immediate = nibbles[sectionY - minLightSection]; ++ ++ if (!immediate.isNullNibbleVisible()) { ++ return immediate.getVisible(x, y, z); ++ } ++ ++ final boolean[] emptinessMap = chunk.getSkyEmptinessMap(); ++ ++ if (emptinessMap == null) { ++ return 15; ++ } ++ ++ // are we above this chunk's lowest empty section? ++ int lowestY = minLightSection - 1; ++ for (int currY = maxSection; currY >= minSection; --currY) { ++ if (emptinessMap[currY - minSection]) { ++ continue; ++ } ++ ++ // should always be full lit here ++ lowestY = currY; ++ break; ++ } ++ ++ if (sectionY > lowestY) { ++ return 15; ++ } ++ ++ // this nibble is going to depend solely on the skylight data above it ++ // find first non-null data above (there does exist one, as we just found it above) ++ for (int currY = sectionY + 1; currY <= maxLightSection; ++currY) { ++ final SWMRNibbleArray nibble = nibbles[currY - minLightSection]; ++ if (!nibble.isNullNibbleVisible()) { ++ return nibble.getVisible(x, 0, z); ++ } ++ } ++ ++ // should never reach here ++ return 15; ++ } ++ ++ public int getBlockLightValue(final BlockPos blockPos, final ChunkAccess chunk) { ++ if (!this.hasBlockLight) { ++ return 0; ++ } ++ final int y = blockPos.getY(); ++ final int cy = y >> 4; ++ ++ final int minLightSection = this.minLightSection; ++ final int maxLightSection = this.maxLightSection; ++ ++ if (cy < minLightSection || cy > maxLightSection) { ++ return 0; ++ } ++ ++ if (chunk == null) { ++ return 0; ++ } ++ ++ final SWMRNibbleArray nibble = chunk.getBlockNibbles()[cy - minLightSection]; ++ return nibble.getVisible(blockPos.getX(), y, blockPos.getZ()); ++ } ++ ++ public int getRawBrightness(final BlockPos pos, final int ambientDarkness) { ++ final ChunkAccess chunk = this.getAnyChunkNow(pos.getX() >> 4, pos.getZ() >> 4); ++ ++ final int sky = this.getSkyLightValue(pos, chunk) - ambientDarkness; ++ // Don't fetch the block light level if the skylight level is 15, since the value will never be higher. ++ if (sky == 15) return 15; ++ final int block = this.getBlockLightValue(pos, chunk); ++ return Math.max(sky, block); ++ } ++ ++ public LayerLightEventListener getSkyReader() { ++ return this.skyReader; ++ } ++ ++ public LayerLightEventListener getBlockReader() { ++ return this.blockReader; ++ } ++ ++ public boolean isClientSide() { ++ return this.isClientSide; ++ } ++ ++ public ChunkAccess getAnyChunkNow(final int chunkX, final int chunkZ) { ++ if (this.world == null) { ++ // empty world ++ return null; ++ } ++ ++ final ServerChunkCache chunkProvider = ((ServerLevel)this.world).getChunkSource(); ++ final LevelChunk fullLoaded = chunkProvider.getChunkAtIfLoadedImmediately(chunkX, chunkZ); ++ if (fullLoaded != null) { ++ return fullLoaded; ++ } ++ ++ return chunkProvider.getChunkAtImmediately(chunkX, chunkZ); ++ } ++ ++ public boolean hasUpdates() { ++ return !this.lightQueue.isEmpty(); ++ } ++ ++ public Level getWorld() { ++ return this.world; ++ } ++ ++ public LightChunkGetter getLightAccess() { ++ return this.lightAccess; ++ } ++ ++ protected final SkyStarLightEngine getSkyLightEngine() { ++ if (this.cachedSkyPropagators == null) { ++ return null; ++ } ++ final SkyStarLightEngine ret; ++ synchronized (this.cachedSkyPropagators) { ++ ret = this.cachedSkyPropagators.pollFirst(); ++ } ++ ++ if (ret == null) { ++ return new SkyStarLightEngine(this.world); ++ } ++ return ret; ++ } ++ ++ protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) { ++ if (this.cachedSkyPropagators == null) { ++ return; ++ } ++ synchronized (this.cachedSkyPropagators) { ++ this.cachedSkyPropagators.addFirst(engine); ++ } ++ } ++ ++ protected final BlockStarLightEngine getBlockLightEngine() { ++ if (this.cachedBlockPropagators == null) { ++ return null; ++ } ++ final BlockStarLightEngine ret; ++ synchronized (this.cachedBlockPropagators) { ++ ret = this.cachedBlockPropagators.pollFirst(); ++ } ++ ++ if (ret == null) { ++ return new BlockStarLightEngine(this.world); ++ } ++ return ret; ++ } ++ ++ protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) { ++ if (this.cachedBlockPropagators == null) { ++ return; ++ } ++ synchronized (this.cachedBlockPropagators) { ++ this.cachedBlockPropagators.addFirst(engine); ++ } ++ } ++ ++ public LightQueue.ChunkTasks blockChange(final BlockPos pos) { ++ if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world ++ return null; ++ } ++ ++ return this.lightQueue.queueBlockChange(pos); ++ } ++ ++ public LightQueue.ChunkTasks sectionChange(final SectionPos pos, final boolean newEmptyValue) { ++ if (this.world == null) { // empty world ++ return null; ++ } ++ ++ return this.lightQueue.queueSectionChange(pos, newEmptyValue); ++ } ++ ++ public void forceLoadInChunk(final ChunkAccess chunk, final Boolean[] emptySections) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); ++ } ++ if (blockEngine != null) { ++ blockEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void loadInChunk(final int chunkX, final int chunkZ, final Boolean[] emptySections) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); ++ } ++ if (blockEngine != null) { ++ blockEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void lightChunk(final ChunkAccess chunk, final Boolean[] emptySections) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.light(this.lightAccess, chunk, emptySections); ++ } ++ if (blockEngine != null) { ++ blockEngine.light(this.lightAccess, chunk, emptySections); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void relightChunks(final Set chunks, final Consumer chunkLightCallback, ++ final IntConsumer onComplete) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.relightChunks(this.lightAccess, chunks, blockEngine == null ? chunkLightCallback : null, ++ blockEngine == null ? onComplete : null); ++ } ++ if (blockEngine != null) { ++ blockEngine.relightChunks(this.lightAccess, chunks, chunkLightCallback, onComplete); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void checkChunkEdges(final int chunkX, final int chunkZ) { ++ this.checkSkyEdges(chunkX, chunkZ); ++ this.checkBlockEdges(chunkX, chunkZ); ++ } ++ ++ public void checkSkyEdges(final int chunkX, final int chunkZ) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ } ++ } ++ ++ public void checkBlockEdges(final int chunkX, final int chunkZ) { ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ try { ++ if (blockEngine != null) { ++ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); ++ } ++ } finally { ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void checkSkyEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ } ++ } ++ ++ public void checkBlockEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ try { ++ if (blockEngine != null) { ++ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); ++ } ++ } finally { ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void scheduleChunkLight(final ChunkPos pos, final Runnable run) { ++ this.lightQueue.queueChunkLighting(pos, run); ++ } ++ ++ public void removeChunkTasks(final ChunkPos pos) { ++ this.lightQueue.removeChunk(pos); ++ } ++ ++ public void propagateChanges() { ++ if (this.lightQueue.isEmpty()) { ++ return; ++ } ++ ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ LightQueue.ChunkTasks task; ++ while ((task = this.lightQueue.removeFirstTask()) != null) { ++ if (task.lightTasks != null) { ++ for (final Runnable run : task.lightTasks) { ++ run.run(); ++ } ++ } ++ ++ final long coordinate = task.chunkCoordinate; ++ final int chunkX = CoordinateUtils.getChunkX(coordinate); ++ final int chunkZ = CoordinateUtils.getChunkZ(coordinate); ++ ++ final Set positions = task.changedPositions; ++ final Boolean[] sectionChanges = task.changedSectionSet; ++ ++ if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) { ++ skyEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); ++ } ++ if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) { ++ blockEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); ++ } ++ ++ if (skyEngine != null && task.queuedEdgeChecksSky != null) { ++ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksSky); ++ } ++ if (blockEngine != null && task.queuedEdgeChecksBlock != null) { ++ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksBlock); ++ } ++ ++ task.onComplete.complete(null); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public static final class LightQueue { ++ ++ protected final Long2ObjectLinkedOpenHashMap chunkTasks = new Long2ObjectLinkedOpenHashMap<>(); ++ protected final StarLightInterface manager; ++ ++ public LightQueue(final StarLightInterface manager) { ++ this.manager = manager; ++ } ++ ++ public synchronized boolean isEmpty() { ++ return this.chunkTasks.isEmpty(); ++ } ++ ++ public synchronized LightQueue.ChunkTasks queueBlockChange(final BlockPos pos) { ++ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); ++ tasks.changedPositions.add(pos.immutable()); ++ return tasks; ++ } ++ ++ public synchronized LightQueue.ChunkTasks queueSectionChange(final SectionPos pos, final boolean newEmptyValue) { ++ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); ++ ++ if (tasks.changedSectionSet == null) { ++ tasks.changedSectionSet = new Boolean[this.manager.maxSection - this.manager.minSection + 1]; ++ } ++ tasks.changedSectionSet[pos.getY() - this.manager.minSection] = Boolean.valueOf(newEmptyValue); ++ ++ return tasks; ++ } ++ ++ public synchronized LightQueue.ChunkTasks queueChunkLighting(final ChunkPos pos, final Runnable lightTask) { ++ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); ++ if (tasks.lightTasks == null) { ++ tasks.lightTasks = new ArrayList<>(); ++ } ++ tasks.lightTasks.add(lightTask); ++ ++ return tasks; ++ } ++ ++ public synchronized LightQueue.ChunkTasks queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) { ++ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); ++ ++ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksSky; ++ if (queuedEdges == null) { ++ queuedEdges = tasks.queuedEdgeChecksSky = new ShortOpenHashSet(); ++ } ++ queuedEdges.addAll(sections); ++ ++ return tasks; ++ } ++ ++ public synchronized LightQueue.ChunkTasks queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) { ++ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); ++ ++ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksBlock; ++ if (queuedEdges == null) { ++ queuedEdges = tasks.queuedEdgeChecksBlock = new ShortOpenHashSet(); ++ } ++ queuedEdges.addAll(sections); ++ ++ return tasks; ++ } ++ ++ public void removeChunk(final ChunkPos pos) { ++ final ChunkTasks tasks; ++ synchronized (this) { ++ tasks = this.chunkTasks.remove(CoordinateUtils.getChunkKey(pos)); ++ } ++ if (tasks != null) { ++ tasks.onComplete.complete(null); ++ } ++ } ++ ++ public synchronized ChunkTasks removeFirstTask() { ++ if (this.chunkTasks.isEmpty()) { ++ return null; ++ } ++ return this.chunkTasks.removeFirst(); ++ } ++ ++ public static final class ChunkTasks { ++ ++ public final Set changedPositions = new ObjectOpenHashSet<>(); ++ public Boolean[] changedSectionSet; ++ public ShortOpenHashSet queuedEdgeChecksSky; ++ public ShortOpenHashSet queuedEdgeChecksBlock; ++ public List lightTasks; ++ ++ public boolean isTicketAdded = false; ++ public final CompletableFuture onComplete = new CompletableFuture<>(); ++ ++ public final long chunkCoordinate; ++ ++ public ChunkTasks(final long chunkCoordinate) { ++ this.chunkCoordinate = chunkCoordinate; ++ } ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java b/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..16a4a14e7ccf9e4d7fdf1166674fe8f529c06d39 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java +@@ -0,0 +1,128 @@ ++package ca.spottedleaf.starlight.common.util; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.SectionPos; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.ChunkPos; ++ ++public final class CoordinateUtils { ++ ++ // dx, dz are relative to the target chunk ++ // dx, dz in [-radius, radius] ++ public static int getNeighbourMappedIndex(final int dx, final int dz, final int radius) { ++ return (dx + radius) + (2 * radius + 1)*(dz + radius); ++ } ++ ++ // the chunk keys are compatible with vanilla ++ ++ public static long getChunkKey(final BlockPos pos) { ++ return ((long)(pos.getZ() >> 4) << 32) | ((pos.getX() >> 4) & 0xFFFFFFFFL); ++ } ++ ++ public static long getChunkKey(final Entity entity) { ++ return ((long)(Mth.floor(entity.getZ()) >> 4) << 32) | ((Mth.floor(entity.getX()) >> 4) & 0xFFFFFFFFL); ++ } ++ ++ public static long getChunkKey(final ChunkPos pos) { ++ return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL); ++ } ++ ++ public static long getChunkKey(final SectionPos pos) { ++ return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL); ++ } ++ ++ public static long getChunkKey(final int x, final int z) { ++ return ((long)z << 32) | (x & 0xFFFFFFFFL); ++ } ++ ++ public static int getChunkX(final long chunkKey) { ++ return (int)chunkKey; ++ } ++ ++ public static int getChunkZ(final long chunkKey) { ++ return (int)(chunkKey >>> 32); ++ } ++ ++ public static int getChunkCoordinate(final double blockCoordinate) { ++ return Mth.floor(blockCoordinate) >> 4; ++ } ++ ++ // the section keys are compatible with vanilla's ++ ++ static final int SECTION_X_BITS = 22; ++ static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1; ++ static final int SECTION_Y_BITS = 20; ++ static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1; ++ static final int SECTION_Z_BITS = 22; ++ static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1; ++ // format is y,z,x (in order of LSB to MSB) ++ static final int SECTION_Y_SHIFT = 0; ++ static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS; ++ static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS; ++ static final int SECTION_TO_BLOCK_SHIFT = 4; ++ ++ public static long getChunkSectionKey(final int x, final int y, final int z) { ++ return ((x & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getChunkSectionKey(final SectionPos pos) { ++ return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getChunkSectionKey(final ChunkPos pos, final int y) { ++ return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getChunkSectionKey(final BlockPos pos) { ++ return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | ++ ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | ++ (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); ++ } ++ ++ public static long getChunkSectionKey(final Entity entity) { ++ return ((Mth.lfloor(entity.getX()) << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | ++ ((Mth.lfloor(entity.getY()) >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | ++ ((Mth.lfloor(entity.getZ()) << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); ++ } ++ ++ public static int getChunkSectionX(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS)); ++ } ++ ++ public static int getChunkSectionY(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS)); ++ } ++ ++ public static int getChunkSectionZ(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS)); ++ } ++ ++ // the block coordinates are not necessarily compatible with vanilla's ++ ++ public static int getBlockCoordinate(final double blockCoordinate) { ++ return Mth.floor(blockCoordinate); ++ } ++ ++ public static long getBlockKey(final int x, final int y, final int z) { ++ return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54); ++ } ++ ++ public static long getBlockKey(final BlockPos pos) { ++ return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54); ++ } ++ ++ public static long getBlockKey(final Entity entity) { ++ return ((long)entity.getX() & 0x7FFFFFF) | (((long)entity.getZ() & 0x7FFFFFF) << 27) | ((long)entity.getY() << 54); ++ } ++ ++ private CoordinateUtils() { ++ throw new RuntimeException(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fabf1e97c019c7365212f40018dcd08d3b828113 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java +@@ -0,0 +1,242 @@ ++package ca.spottedleaf.starlight.common.util; ++ ++public final class IntegerUtil { ++ ++ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; ++ public static final long HIGH_BIT_U64 = Long.MIN_VALUE; ++ ++ public static int ceilLog2(final int value) { ++ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static long ceilLog2(final long value) { ++ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final int value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final long value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int roundCeilLog2(final int value) { ++ // optimized variant of 1 << (32 - leading(val - 1)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) ++ // HIGH_BIT_32 >>> (-1 + leading(val - 1)) ++ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static long roundCeilLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static int roundFloorLog2(final int value) { ++ // optimized variant of 1 << (31 - leading(val)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - 31 + leading(val)) ++ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); ++ } ++ ++ public static long roundFloorLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); ++ } ++ ++ public static boolean isPowerOfTwo(final int n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ public static boolean isPowerOfTwo(final long n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ public static int getTrailingBit(final int n) { ++ return -n & n; ++ } ++ ++ public static long getTrailingBit(final long n) { ++ return -n & n; ++ } ++ ++ public static int trailingZeros(final int n) { ++ return Integer.numberOfTrailingZeros(n); ++ } ++ ++ public static int trailingZeros(final long n) { ++ return Long.numberOfTrailingZeros(n); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorMultiple(final long numbers) { ++ return (int)(numbers >>> 32); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorShift(final long numbers) { ++ return (int)numbers; ++ } ++ ++ // copied from hacker's delight (signed division magic value) ++ // http://www.hackersdelight.org/hdcodetxt/magic.c.txt ++ public static long getDivisorNumbers(final int d) { ++ final int ad = branchlessAbs(d); ++ ++ if (ad < 2) { ++ throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d); ++ } ++ ++ final int two31 = 0x80000000; ++ final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour ++ ++ /* ++ Signed usage: ++ int number; ++ long magic = getDivisorNumbers(div); ++ long mul = magic >>> 32; ++ int sign = number >> 31; ++ int result = (int)(((long)number * mul) >>> magic) - sign; ++ */ ++ /* ++ Unsigned usage: ++ int number; ++ long magic = getDivisorNumbers(div); ++ long mul = magic >>> 32; ++ int result = (int)(((long)number * mul) >>> magic); ++ */ ++ ++ int p = 31; ++ ++ // all these variables are UNSIGNED! ++ int t = two31 + (d >>> 31); ++ int anc = t - 1 - (int)((t & mask)%ad); ++ int q1 = (int)((two31 & mask)/(anc & mask)); ++ int r1 = two31 - q1*anc; ++ int q2 = (int)((two31 & mask)/(ad & mask)); ++ int r2 = two31 - q2*ad; ++ int delta; ++ ++ do { ++ p = p + 1; ++ q1 = 2*q1; // Update q1 = 2**p/|nc|. ++ r1 = 2*r1; // Update r1 = rem(2**p, |nc|). ++ if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) ++ q1 = q1 + 1; ++ r1 = r1 - anc; ++ } ++ q2 = 2*q2; // Update q2 = 2**p/|d|. ++ r2 = 2*r2; // Update r2 = rem(2**p, |d|). ++ if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) ++ q2 = q2 + 1; ++ r2 = r2 - ad; ++ } ++ delta = ad - r2; ++ } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); ++ ++ int magicNum = q2 + 1; ++ if (d < 0) { ++ magicNum = -magicNum; ++ } ++ int shift = p; ++ return ((long)magicNum << 32) | shift; ++ } ++ ++ public static int branchlessAbs(final int val) { ++ // -n = -1 ^ n + 1 ++ final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0 ++ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 ++ } ++ ++ public static long branchlessAbs(final long val) { ++ // -n = -1 ^ n + 1 ++ final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0 ++ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 ++ } ++ ++ //https://github.com/skeeto/hash-prospector for hash functions ++ ++ //score = ~590.47984224483832 ++ public static int hash0(int x) { ++ x *= 0x36935555; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ //score = ~310.01596637036749 ++ public static int hash1(int x) { ++ x ^= x >>> 15; ++ x *= 0x356aaaad; ++ x ^= x >>> 17; ++ return x; ++ } ++ ++ public static int hash2(int x) { ++ x ^= x >>> 16; ++ x *= 0x7feb352d; ++ x ^= x >>> 15; ++ x *= 0x846ca68b; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ public static int hash3(int x) { ++ x ^= x >>> 17; ++ x *= 0xed5ad4bb; ++ x ^= x >>> 11; ++ x *= 0xac4c1b51; ++ x ^= x >>> 15; ++ x *= 0x31848bab; ++ x ^= x >>> 14; ++ return x; ++ } ++ ++ //score = ~365.79959673201887 ++ public static long hash1(long x) { ++ x ^= x >>> 27; ++ x *= 0xb24924b71d2d354bL; ++ x ^= x >>> 28; ++ return x; ++ } ++ ++ //h2 hash ++ public static long hash2(long x) { ++ x ^= x >>> 32; ++ x *= 0xd6e8feb86659fd93L; ++ x ^= x >>> 32; ++ x *= 0xd6e8feb86659fd93L; ++ x ^= x >>> 32; ++ return x; ++ } ++ ++ public static long hash3(long x) { ++ x ^= x >>> 45; ++ x *= 0xc161abe5704b6c79L; ++ x ^= x >>> 41; ++ x *= 0xe3e5389aedbc90f7L; ++ x ^= x >>> 56; ++ x *= 0x1f9aba75a52db073L; ++ x ^= x >>> 53; ++ return x; ++ } ++ ++ private IntegerUtil() { ++ throw new RuntimeException(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8d1fd22c8b8b0ea3afce6fc3e92057194f82669f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java +@@ -0,0 +1,192 @@ ++package ca.spottedleaf.starlight.common.util; ++ ++import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; ++import ca.spottedleaf.starlight.common.light.StarLightEngine; ++import com.mojang.logging.LogUtils; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.ListTag; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import org.slf4j.Logger; ++ ++public final class SaveUtil { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ private static final int STARLIGHT_LIGHT_VERSION = 9; ++ ++ public static int getLightVersion() { ++ return STARLIGHT_LIGHT_VERSION; ++ } ++ ++ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; ++ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; ++ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; ++ ++ public static void saveLightHook(final Level world, final ChunkAccess chunk, final CompoundTag nbt) { ++ try { ++ saveLightHookReal(world, chunk, nbt); ++ } catch (final Throwable ex) { ++ // failing to inject is not fatal so we catch anything here. if it fails, it will have correctly set lit to false ++ // for Vanilla to relight on load and it will not set our lit tag so we will relight on load ++ if (ex instanceof ThreadDeath) { ++ throw (ThreadDeath)ex; ++ } ++ LOGGER.warn("Failed to inject light data into save data for chunk " + chunk.getPos() + ", chunk light will be recalculated on its next load", ex); ++ } ++ } ++ ++ private static void saveLightHookReal(final Level world, final ChunkAccess chunk, final CompoundTag tag) { ++ if (tag == null) { ++ return; ++ } ++ ++ final int minSection = WorldUtil.getMinLightSection(world); ++ final int maxSection = WorldUtil.getMaxLightSection(world); ++ ++ SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles(); ++ SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles(); ++ ++ boolean lit = chunk.isLightCorrect() || !(world instanceof ServerLevel); ++ // diff start - store our tag for whether light data is init'd ++ if (lit) { ++ tag.putBoolean("isLightOn", false); ++ } ++ // diff end - store our tag for whether light data is init'd ++ ChunkStatus status = ChunkStatus.byName(tag.getString("Status")); ++ ++ CompoundTag[] sections = new CompoundTag[maxSection - minSection + 1]; ++ ++ ListTag sectionsStored = tag.getList("sections", 10); ++ ++ for (int i = 0; i < sectionsStored.size(); ++i) { ++ CompoundTag sectionStored = sectionsStored.getCompound(i); ++ int k = sectionStored.getByte("Y"); ++ ++ // strip light data ++ sectionStored.remove("BlockLight"); ++ sectionStored.remove("SkyLight"); ++ ++ if (!sectionStored.isEmpty()) { ++ sections[k - minSection] = sectionStored; ++ } ++ } ++ ++ if (lit && status.isOrAfter(ChunkStatus.LIGHT)) { ++ for (int i = minSection; i <= maxSection; ++i) { ++ SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState(); ++ SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState(); ++ if (blockNibble != null || skyNibble != null) { ++ CompoundTag section = sections[i - minSection]; ++ if (section == null) { ++ section = new CompoundTag(); ++ section.putByte("Y", (byte)i); ++ sections[i - minSection] = section; ++ } ++ ++ // we store under the same key so mod programs editing nbt ++ // can still read the data, hopefully. ++ // however, for compatibility we store chunks as unlit so vanilla ++ // is forced to re-light them if it encounters our data. It's too much of a burden ++ // to try and maintain compatibility with a broken and inferior skylight management system. ++ ++ if (blockNibble != null) { ++ if (blockNibble.data != null) { ++ section.putByteArray("BlockLight", blockNibble.data); ++ } ++ section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state); ++ } ++ ++ if (skyNibble != null) { ++ if (skyNibble.data != null) { ++ section.putByteArray("SkyLight", skyNibble.data); ++ } ++ section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state); ++ } ++ } ++ } ++ } ++ ++ // rewrite section list ++ sectionsStored.clear(); ++ for (CompoundTag section : sections) { ++ if (section != null) { ++ sectionsStored.add(section); ++ } ++ } ++ tag.put("sections", sectionsStored); ++ if (lit) { ++ tag.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // only mark as fully lit after we have successfully injected our data ++ } ++ } ++ ++ public static void loadLightHook(final Level world, final ChunkPos pos, final CompoundTag tag, final ChunkAccess into) { ++ try { ++ loadLightHookReal(world, pos, tag, into); ++ } catch (final Throwable ex) { ++ // failing to inject is not fatal so we catch anything here. if it fails, then we simply relight. Not a problem, we get correct ++ // lighting in both cases. ++ if (ex instanceof ThreadDeath) { ++ throw (ThreadDeath)ex; ++ } ++ LOGGER.warn("Failed to load light for chunk " + pos + ", light will be recalculated", ex); ++ } ++ } ++ ++ private static void loadLightHookReal(final Level world, final ChunkPos pos, final CompoundTag tag, final ChunkAccess into) { ++ if (into == null) { ++ return; ++ } ++ final int minSection = WorldUtil.getMinLightSection(world); ++ final int maxSection = WorldUtil.getMaxLightSection(world); ++ ++ into.setLightCorrect(false); // mark as unlit in case we fail parsing ++ ++ SWMRNibbleArray[] blockNibbles = StarLightEngine.getFilledEmptyLight(world); ++ SWMRNibbleArray[] skyNibbles = StarLightEngine.getFilledEmptyLight(world); ++ ++ ++ // start copy from the original method ++ boolean lit = tag.get("isLightOn") != null && tag.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION; ++ boolean canReadSky = world.dimensionType().hasSkyLight(); ++ ChunkStatus status = ChunkStatus.byName(tag.getString("Status")); ++ if (lit && status.isOrAfter(ChunkStatus.LIGHT)) { // diff - we add the status check here ++ ListTag sections = tag.getList("sections", 10); ++ ++ for (int i = 0; i < sections.size(); ++i) { ++ CompoundTag sectionData = sections.getCompound(i); ++ int y = sectionData.getByte("Y"); ++ ++ if (sectionData.contains("BlockLight", 7)) { ++ // this is where our diff is ++ blockNibbles[y - minSection] = new SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety ++ } else { ++ blockNibbles[y - minSection] = new SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG)); ++ } ++ ++ if (canReadSky) { ++ if (sectionData.contains("SkyLight", 7)) { ++ // we store under the same key so mod programs editing nbt ++ // can still read the data, hopefully. ++ // however, for compatibility we store chunks as unlit so vanilla ++ // is forced to re-light them if it encounters our data. It's too much of a burden ++ // to try and maintain compatibility with a broken and inferior skylight management system. ++ skyNibbles[y - minSection] = new SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety ++ } else { ++ skyNibbles[y - minSection] = new SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG)); ++ } ++ } ++ } ++ } ++ // end copy from vanilla ++ ++ into.setBlockNibbles(blockNibbles); ++ into.setSkyNibbles(skyNibbles); ++ into.setLightCorrect(lit); // now we set lit here, only after we've correctly parsed data ++ } ++ ++ private SaveUtil() {} ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dd995e25ae620ae36cd5eecb2fe10ad034ba50d2 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java +@@ -0,0 +1,47 @@ ++package ca.spottedleaf.starlight.common.util; ++ ++import net.minecraft.world.level.LevelHeightAccessor; ++ ++public final class WorldUtil { ++ ++ // min, max are inclusive ++ ++ public static int getMaxSection(final LevelHeightAccessor world) { ++ return world.getMaxSection() - 1; // getMaxSection() is exclusive ++ } ++ ++ public static int getMinSection(final LevelHeightAccessor world) { ++ return world.getMinSection(); ++ } ++ ++ public static int getMaxLightSection(final LevelHeightAccessor world) { ++ return getMaxSection(world) + 1; ++ } ++ ++ public static int getMinLightSection(final LevelHeightAccessor world) { ++ return getMinSection(world) - 1; ++ } ++ ++ ++ ++ public static int getTotalSections(final LevelHeightAccessor world) { ++ return getMaxSection(world) - getMinSection(world) + 1; ++ } ++ ++ public static int getTotalLightSections(final LevelHeightAccessor world) { ++ return getMaxLightSection(world) - getMinLightSection(world) + 1; ++ } ++ ++ public static int getMinBlockY(final LevelHeightAccessor world) { ++ return getMinSection(world) << 4; ++ } ++ ++ public static int getMaxBlockY(final LevelHeightAccessor world) { ++ return (getMaxSection(world) << 4) | 15; ++ } ++ ++ private WorldUtil() { ++ throw new RuntimeException(); ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 534d9c380f26d6cce3c99fa88ad2e15410535094..e47fb2aa5e885162cae5cbfc9f33ff7864bf538e 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -42,6 +42,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("dumpitem"), new DumpItemCommand()); + commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); + commands.put(Set.of("dumplisteners"), new DumpListenersCommand()); ++ commands.put(Set.of("fixlight"), new FixLightCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..463c6d8d5b114816ed9065558285945817c30385 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +@@ -0,0 +1,115 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.ArrayDeque; ++import java.util.Deque; ++import io.papermc.paper.util.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.ThreadedLevelLightEngine; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.LevelChunk; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.BLUE; ++import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class FixLightCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doFixLight(sender, args); ++ return true; ++ } ++ ++ private void doFixLight(final CommandSender sender, final String[] args) { ++ if (!(sender instanceof Player)) { ++ sender.sendMessage(text("Only players can use this command", RED)); ++ return; ++ } ++ @Nullable Runnable post = null; ++ int radius = 2; ++ if (args.length > 0) { ++ try { ++ final int parsed = Integer.parseInt(args[0]); ++ if (parsed < 0) { ++ sender.sendMessage(text("Radius cannot be negative!", RED)); ++ return; ++ } ++ final int maxRadius = 32; ++ radius = Math.min(maxRadius, parsed); ++ if (radius != parsed) { ++ post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED)); ++ } ++ } catch (final Exception e) { ++ sender.sendMessage(text("'" + args[0] + "' is not a valid number.", RED)); ++ return; ++ } ++ } ++ ++ CraftPlayer player = (CraftPlayer) sender; ++ ServerPlayer handle = player.getHandle(); ++ ServerLevel world = (ServerLevel) handle.level(); ++ ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); ++ this.starlightFixLight(handle, world, lightengine, radius, post); ++ } ++ ++ private void starlightFixLight( ++ final ServerPlayer sender, ++ final ServerLevel world, ++ final ThreadedLevelLightEngine lightengine, ++ final int radius, ++ final @Nullable Runnable done ++ ) { ++ final long start = System.nanoTime(); ++ final java.util.LinkedHashSet chunks = new java.util.LinkedHashSet<>(MCUtil.getSpiralOutChunks(sender.blockPosition(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos ++ ++ final int[] pending = new int[1]; ++ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext(); ) { ++ final ChunkPos chunkPos = iterator.next(); ++ ++ final @Nullable ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); ++ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.LIGHT)) { ++ // cannot relight this chunk ++ iterator.remove(); ++ continue; ++ } ++ ++ ++pending[0]; ++ } ++ ++ final int[] relitChunks = new int[1]; ++ lightengine.relight(chunks, ++ (final ChunkPos chunkPos) -> { ++ ++relitChunks[0]; ++ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( ++ text("Relit chunk ", BLUE), text(chunkPos.toString()), ++ text(", progress: ", BLUE), text((int) (Math.round(100.0 * (double) (relitChunks[0]) / (double) pending[0])) + "%") ++ )); ++ }, ++ (final int totalRelit) -> { ++ final long end = System.nanoTime(); ++ final long diff = Math.round(1.0e-6 * (end - start)); ++ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( ++ text("Relit ", BLUE), text(totalRelit), ++ text(" chunks. Took ", BLUE), text(diff + "ms") ++ )); ++ if (done != null) { ++ done.run(); ++ } ++ } ++ ); ++ sender.getBukkitEntity().sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks"))); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index facfdbb87e89f4db33ce13233c2ba4366d35c15b..807a6bb1026dac2c4cd0a50afe06fd62ce23558b 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -53,7 +53,7 @@ public class ChunkHolder { + private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage + private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage + private volatile CompletableFuture> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage +- private CompletableFuture chunkToSave; ++ public CompletableFuture chunkToSave; // Paper - public + @Nullable + private final DebugBuffer chunkToSaveHistory; + public int oldTicketLevel; +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 9dab2dd7fd77fa1006c903dc5d1f4f8339e10b91..3ae47b86b80f9156e71d1da83e492153f360d1b5 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -125,7 +125,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + private final LongSet entitiesInLevel; + public final ServerLevel level; + private final ThreadedLevelLightEngine lightEngine; +- private final BlockableEventLoop mainThreadExecutor; ++ public final BlockableEventLoop mainThreadExecutor; // Paper - public + public ChunkGenerator generator; + private final RandomState randomState; + private final ChunkGeneratorStructureState chunkGeneratorState; +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 4e1618462840a1378dbe6492696c97544815edf2..8e8e3896040241bba8fd15f4d6d046567847f741 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -380,7 +380,7 @@ public abstract class DistanceManager { + } + + public void removeTicketsOnClosing() { +- ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve ++ ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve + ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); + + while (objectiterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +index 785ca2c63fe47936ac4c0223dffd8971a295a37c..97662f8c8c125cb964d46b9095509a0da9796dba 100644 +--- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java ++++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +@@ -23,6 +23,17 @@ import net.minecraft.world.level.chunk.LightChunkGetter; + import net.minecraft.world.level.lighting.LevelLightEngine; + import org.slf4j.Logger; + ++// Paper start ++import ca.spottedleaf.starlight.common.light.StarLightEngine; ++import io.papermc.paper.util.CoordinateUtils; ++import java.util.function.Supplier; ++import net.minecraft.world.level.lighting.LayerLightEventListener; ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import net.minecraft.world.level.chunk.ChunkStatus; ++// Paper end ++ + public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable { + public static final int DEFAULT_BATCH_SIZE = 1000; + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -33,13 +44,161 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + private final int taskPerBatch = 1000; + private final AtomicBoolean scheduled = new AtomicBoolean(); + ++ // Paper start - replace light engine impl ++ protected final ca.spottedleaf.starlight.common.light.StarLightInterface theLightEngine; ++ public final boolean hasBlockLight; ++ public final boolean hasSkyLight; ++ // Paper end - replace light engine impl ++ + public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox processor, ProcessorHandle> executor) { +- super(chunkProvider, true, hasBlockLight); ++ super(chunkProvider, false, false); // Paper - destroy vanilla light engine state + this.chunkMap = chunkStorage; + this.sorterMailbox = executor; + this.taskMailbox = processor; ++ // Paper start - replace light engine impl ++ this.hasBlockLight = true; ++ this.hasSkyLight = hasBlockLight; // Nice variable name. ++ this.theLightEngine = new ca.spottedleaf.starlight.common.light.StarLightInterface(chunkProvider, this.hasSkyLight, this.hasBlockLight, this); ++ // Paper end - replace light engine impl ++ } ++ ++ // Paper start - replace light engine impl ++ protected final ChunkAccess getChunk(final int chunkX, final int chunkZ) { ++ return ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkAtImmediately(chunkX, chunkZ); ++ } ++ ++ protected long relightCounter; ++ ++ public int relight(java.util.Set chunks_param, ++ java.util.function.Consumer chunkLightCallback, ++ java.util.function.IntConsumer onComplete) { ++ if (!org.bukkit.Bukkit.isPrimaryThread()) { ++ throw new IllegalStateException("Must only be called on the main thread"); ++ } ++ ++ java.util.Set chunks = new java.util.LinkedHashSet<>(chunks_param); ++ // add tickets ++ java.util.Map ticketIds = new java.util.HashMap<>(); ++ int totalChunks = 0; ++ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext();) { ++ final ChunkPos chunkPos = iterator.next(); ++ ++ final ChunkAccess chunk = (ChunkAccess)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); ++ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { ++ // cannot relight this chunk ++ iterator.remove(); ++ continue; ++ } ++ ++ final Long id = Long.valueOf(this.relightCounter++); ++ ++ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().addTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, io.papermc.paper.util.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), id); ++ ticketIds.put(chunkPos, id); ++ ++ ++totalChunks; ++ } ++ ++ this.taskMailbox.tell(() -> { ++ this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> { ++ chunkLightCallback.accept(chunkPos); ++ ((java.util.concurrent.Executor)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().mainThreadProcessor).execute(() -> { ++ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()).broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunkPos, ThreadedLevelLightEngine.this, null, null), false); ++ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().removeTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, io.papermc.paper.util.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), ticketIds.get(chunkPos)); ++ }); ++ }, onComplete); ++ }); ++ this.tryScheduleUpdate(); ++ ++ return totalChunks; ++ } ++ ++ private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); ++ ++ private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, ++ final Supplier runnable) { ++ final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld(); ++ ++ final ChunkAccess center = this.theLightEngine.getAnyChunkNow(chunkX, chunkZ); ++ if (center == null || !center.getStatus().isOrAfter(ChunkStatus.LIGHT)) { ++ // do not accept updates in unlit chunks, unless we might be generating a chunk. thanks to the amazing ++ // chunk scheduling, we could be lighting and generating a chunk at the same time ++ return; ++ } ++ ++ if (center.getStatus() != ChunkStatus.FULL) { ++ // do not keep chunk loaded, we are probably in a gen thread ++ // if we proceed to add a ticket the chunk will be loaded, which is not what we want (avoid cascading gen) ++ runnable.get(); ++ return; ++ } ++ ++ if (!world.getChunkSource().chunkMap.mainThreadExecutor.isSameThread()) { ++ // ticket logic is not safe to run off-main, re-schedule ++ world.getChunkSource().chunkMap.mainThreadExecutor.execute(() -> { ++ this.queueTaskForSection(chunkX, chunkY, chunkZ, runnable); ++ }); ++ return; ++ } ++ ++ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ ++ final ca.spottedleaf.starlight.common.light.StarLightInterface.LightQueue.ChunkTasks updateFuture = runnable.get(); ++ ++ if (updateFuture == null) { ++ // not scheduled ++ return; ++ } ++ ++ if (updateFuture.isTicketAdded) { ++ // ticket already added ++ return; ++ } ++ updateFuture.isTicketAdded = true; ++ ++ final int references = this.chunksBeingWorkedOn.addTo(key, 1); ++ if (references == 0) { ++ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); ++ world.getChunkSource().addRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); ++ } ++ ++ updateFuture.onComplete.thenAcceptAsync((final Void ignore) -> { ++ final int newReferences = this.chunksBeingWorkedOn.get(key); ++ if (newReferences == 1) { ++ this.chunksBeingWorkedOn.remove(key); ++ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); ++ world.getChunkSource().removeRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); ++ } else { ++ this.chunksBeingWorkedOn.put(key, newReferences - 1); ++ } ++ }, world.getChunkSource().chunkMap.mainThreadExecutor).whenComplete((final Void ignore, final Throwable thr) -> { ++ if (thr != null) { ++ LOGGER.error("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr); ++ } ++ }); + } + ++ @Override ++ public boolean hasLightWork() { ++ // route to new light engine ++ return this.theLightEngine.hasUpdates(); ++ } ++ ++ @Override ++ public LayerLightEventListener getLayerListener(final LightLayer lightType) { ++ return lightType == LightLayer.BLOCK ? this.theLightEngine.getBlockReader() : this.theLightEngine.getSkyReader(); ++ } ++ ++ @Override ++ public int getRawBrightness(final BlockPos pos, final int ambientDarkness) { ++ // need to use new light hooks for this ++ final int sky = this.theLightEngine.getSkyReader().getLightValue(pos) - ambientDarkness; ++ // Don't fetch the block light level if the skylight level is 15, since the value will never be higher. ++ if (sky == 15) return 15; ++ final int block = this.theLightEngine.getBlockReader().getLightValue(pos); ++ return Math.max(sky, block); ++ } ++ // Paper end - replace light engine imp ++ + @Override + public void close() { + } +@@ -51,15 +210,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + @Override + public void checkBlock(BlockPos pos) { +- BlockPos blockPos = pos.immutable(); +- this.addTask(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { +- super.checkBlock(blockPos); +- }, () -> { +- return "checkBlock " + blockPos; +- })); ++ // Paper start - replace light engine impl ++ final BlockPos posCopy = pos.immutable(); ++ this.queueTaskForSection(posCopy.getX() >> 4, posCopy.getY() >> 4, posCopy.getZ() >> 4, () -> { ++ return this.theLightEngine.blockChange(posCopy); ++ }); ++ // Paper end - replace light engine impl + } + + protected void updateChunkStatus(ChunkPos pos) { ++ if (true) return; // Paper - replace light engine impl + this.addTask(pos.x, pos.z, () -> { + return 0; + }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { +@@ -82,17 +242,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + @Override + public void updateSectionStatus(SectionPos pos, boolean notReady) { +- this.addTask(pos.x(), pos.z(), () -> { +- return 0; +- }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { +- super.updateSectionStatus(pos, notReady); +- }, () -> { +- return "updateSectionStatus " + pos + " " + notReady; +- })); ++ // Paper start - replace light engine impl ++ this.queueTaskForSection(pos.getX(), pos.getY(), pos.getZ(), () -> { ++ return this.theLightEngine.sectionChange(pos, notReady); ++ }); ++ // Paper end - replace light engine impl + } + + @Override + public void propagateLightSources(ChunkPos chunkPos) { ++ if (true) return; // Paper - replace light engine impl + this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { + super.propagateLightSources(chunkPos); + }, () -> { +@@ -102,6 +261,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + @Override + public void setLightEnabled(ChunkPos pos, boolean retainData) { ++ if (true) return; // Paper - replace light engine impl + this.addTask(pos.x, pos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { + super.setLightEnabled(pos, retainData); + }, () -> { +@@ -111,6 +271,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + @Override + public void queueSectionData(LightLayer lightType, SectionPos pos, @Nullable DataLayer nibbles) { ++ if (true) return; // Paper - replace light engine impl + this.addTask(pos.x(), pos.z(), () -> { + return 0; + }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { +@@ -136,6 +297,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + @Override + public void retainData(ChunkPos pos, boolean retainData) { ++ if (true) return; // Paper - replace light engine impl + this.addTask(pos.x, pos.z, () -> { + return 0; + }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { +@@ -146,6 +308,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + } + + public CompletableFuture initializeLight(ChunkAccess chunk, boolean bl) { ++ if (true) return CompletableFuture.completedFuture(chunk); // Paper - replace light engine impl + ChunkPos chunkPos = chunk.getPos(); + this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { + LevelChunkSection[] levelChunkSections = chunk.getSections(); +@@ -171,6 +334,37 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + } + + public CompletableFuture lightChunk(ChunkAccess chunk, boolean excludeBlocks) { ++ // Paper start - replace light engine impl ++ if (true) { ++ boolean lit = excludeBlocks; ++ final ChunkPos chunkPos = chunk.getPos(); ++ ++ return CompletableFuture.supplyAsync(() -> { ++ final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk); ++ if (!lit) { ++ chunk.setLightCorrect(false); ++ this.theLightEngine.lightChunk(chunk, emptySections); ++ chunk.setLightCorrect(true); ++ } else { ++ this.theLightEngine.forceLoadInChunk(chunk, emptySections); ++ // can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have ++ // them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should ++ // catch what we miss here. ++ this.theLightEngine.checkChunkEdges(chunkPos.x, chunkPos.z); ++ } ++ ++ this.chunkMap.releaseLightTicket(chunkPos); ++ return chunk; ++ }, (runnable) -> { ++ this.theLightEngine.scheduleChunkLight(chunkPos, runnable); ++ this.tryScheduleUpdate(); ++ }).whenComplete((final ChunkAccess c, final Throwable throwable) -> { ++ if (throwable != null) { ++ LOGGER.error("Failed to light chunk " + chunkPos, throwable); ++ } ++ }); ++ } ++ // Paper end - replace light engine impl + ChunkPos chunkPos = chunk.getPos(); + chunk.setLightCorrect(false); + this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { +@@ -191,7 +385,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + } + + public void tryScheduleUpdate() { +- if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { ++ if (this.hasLightWork() && this.scheduled.compareAndSet(false, true)) { // Paper // Paper - rewrite light engine + this.taskMailbox.tell(() -> { + this.runUpdate(); + this.scheduled.set(false); +@@ -213,7 +407,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + } + + objectListIterator.back(j); +- super.runLightUpdates(); ++ this.theLightEngine.propagateChanges(); // Paper - rewrite light engine + + for(int var5 = 0; objectListIterator.hasNext() && var5 < i; ++var5) { + Pair pair2 = objectListIterator.next(); +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index 0d536d72ac918fbd403397ff369d10143ee9c204..6051e5f272838ef23276a90e21c2fc821ca155d1 100644 +--- a/src/main/java/net/minecraft/server/level/TicketType.java ++++ b/src/main/java/net/minecraft/server/level/TicketType.java +@@ -26,6 +26,7 @@ public class TicketType { + public static final TicketType UNKNOWN = TicketType.create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); + public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit + public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit ++ public static final TicketType CHUNK_RELIGHT = create("light_update", Long::compareTo); // Paper - ensure chunks stay loaded for lighting + + public static TicketType create(String name, Comparator argumentComparator) { + return new TicketType<>(name, argumentComparator, 0L); +diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +index 209596e89307b9e1d0ff4c465876d29fef4fc290..c3e7bd8865cc8990fc59f1ff0dfc1697cbb5ca49 100644 +--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java ++++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +@@ -107,6 +107,27 @@ public class WorldGenRegion implements WorldGenLevel { + } + } + ++ // Paper start - starlight ++ @Override ++ public int getBrightness(final net.minecraft.world.level.LightLayer lightLayer, final BlockPos blockPos) { ++ final ChunkAccess chunk = this.getChunk(blockPos.getX() >> 4, blockPos.getZ() >> 4); ++ if (!chunk.isLightCorrect()) { ++ return 0; ++ } ++ return this.getLightEngine().getLayerListener(lightLayer).getLightValue(blockPos); ++ } ++ ++ ++ @Override ++ public int getRawBrightness(final BlockPos blockPos, final int subtract) { ++ final ChunkAccess chunk = this.getChunk(blockPos.getX() >> 4, blockPos.getZ() >> 4); ++ if (!chunk.isLightCorrect()) { ++ return 0; ++ } ++ return this.getLightEngine().getRawBrightness(blockPos, subtract); ++ } ++ // Paper end - starlight ++ + public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) { + return this.level.getChunkSource().chunkMap.isOldChunkAround(chunkPos, checkRadius); + } +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 64300077fce6eb28b6bddd42b3467eaa4c80c9f5..e28ac8f7960f648099e5f3607530a406c72e5056 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -881,6 +881,7 @@ public abstract class BlockBehaviour implements FeatureElement { + this.spawnTerrainParticles = blockbase_info.spawnTerrainParticles; + this.instrument = blockbase_info.instrument; + this.replaceable = blockbase_info.replaceable; ++ this.conditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion; // Paper + } + // Paper start - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time + private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; +@@ -917,6 +918,18 @@ public abstract class BlockBehaviour implements FeatureElement { + return this.shapeExceedsCube; + } + // Paper end ++ // Paper start - starlight ++ protected int opacityIfCached = -1; ++ // ret -1 if opacity is dynamic, or -1 if the block is conditionally full opaque, else return opacity in [0, 15] ++ public final int getOpacityIfCached() { ++ return this.opacityIfCached; ++ } ++ ++ protected final boolean conditionallyFullOpaque; ++ public final boolean isConditionallyFullOpaque() { ++ return this.conditionallyFullOpaque; ++ } ++ // Paper end - starlight + + public void initCache() { + this.fluidState = ((Block) this.owner).getFluidState(this.asState()); +@@ -925,6 +938,7 @@ public abstract class BlockBehaviour implements FeatureElement { + this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState()); + } + this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here ++ this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - starlight - cache opacity for light + + this.legacySolid = this.calculateSolid(); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +index e5e562f75e7d4b6a750f192842940c5e3af81e7d..3e5addb60ae8f466dad09edb3ae1fc88fe2681e9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -74,7 +74,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom + @Nullable + protected BlendingData blendingData; + public final Map heightmaps = Maps.newEnumMap(Heightmap.Types.class); +- protected ChunkSkyLightSources skyLightSources; ++ // Paper - starlight - remove skyLightSources + private final Map structureStarts = Maps.newHashMap(); + private final Map structuresRefences = Maps.newHashMap(); + protected final Map pendingBlockEntities = Maps.newHashMap(); +@@ -86,8 +86,55 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom + private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); + public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY); + // CraftBukkit end ++ // Paper start - rewrite light engine ++ private volatile ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles; ++ ++ private volatile ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles; ++ ++ private volatile boolean[] skyEmptinessMap; ++ ++ private volatile boolean[] blockEmptinessMap; ++ ++ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { ++ return this.blockNibbles; ++ } ++ ++ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { ++ this.blockNibbles = nibbles; ++ } ++ ++ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { ++ return this.skyNibbles; ++ } ++ ++ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { ++ this.skyNibbles = nibbles; ++ } ++ ++ public boolean[] getSkyEmptinessMap() { ++ return this.skyEmptinessMap; ++ } ++ ++ public void setSkyEmptinessMap(final boolean[] emptinessMap) { ++ this.skyEmptinessMap = emptinessMap; ++ } ++ ++ public boolean[] getBlockEmptinessMap() { ++ return this.blockEmptinessMap; ++ } ++ ++ public void setBlockEmptinessMap(final boolean[] emptinessMap) { ++ this.blockEmptinessMap = emptinessMap; ++ } ++ // Paper end - rewrite light engine + + public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) { ++ // Paper start - rewrite light engine ++ if (!(this instanceof ImposterProtoChunk)) { ++ this.setBlockNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(heightLimitView)); ++ this.setSkyNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(heightLimitView)); ++ } ++ // Paper end - rewrite light engine + this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups + this.chunkPos = pos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key + this.upgradeData = upgradeData; +@@ -96,7 +143,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom + this.inhabitedTime = inhabitedTime; + this.postProcessing = new ShortList[heightLimitView.getSectionsCount()]; + this.blendingData = blendingData; +- this.skyLightSources = new ChunkSkyLightSources(heightLimitView); ++ // Paper - starlight - remove skyLightSources + if (sectionArray != null) { + if (this.sections.length == sectionArray.length) { + System.arraycopy(sectionArray, 0, this.sections, 0, this.sections.length); +@@ -507,12 +554,12 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom + } + + public void initializeLightSources() { +- this.skyLightSources.fillFrom(this); ++ // Paper - starlight - remove skyLightSources + } + + @Override + public ChunkSkyLightSources getSkyLightSources() { +- return this.skyLightSources; ++ return null; // Paper - starlight - remove skyLightSources + } + + public static record TicksToSave(SerializableTickContainer blocks, SerializableTickContainer fluids) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +index d5b1fd0ff3f64675f90dd9f7f328a106e0992d51..846ae3fd184a1d63b743aa25e045604576697c96 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +@@ -260,6 +260,17 @@ public class ChunkStatus { + return this.chunkType; + } + ++ // Paper start ++ public static ChunkStatus getStatus(String name) { ++ try { ++ // We need this otherwise we return EMPTY for invalid names ++ ResourceLocation key = new ResourceLocation(name); ++ return BuiltInRegistries.CHUNK_STATUS.getOptional(key).orElse(null); ++ } catch (Exception ex) { ++ return null; // invalid name ++ } ++ } ++ // Paper end + public static ChunkStatus byName(String id) { + return (ChunkStatus) BuiltInRegistries.CHUNK_STATUS.get(ResourceLocation.tryParse(id)); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java +index 2ee1658532cb00d7bcd1d11e03f19d21ca7f2a9e..ac754827172a4de600d0a57a7d11853481a2dbf2 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java +@@ -21,6 +21,40 @@ public class EmptyLevelChunk extends LevelChunk { + this.biome = biomeEntry; + } + ++ // Paper start - starlight ++ @Override ++ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { ++ return ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(this.getLevel()); ++ } ++ ++ @Override ++ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {} ++ ++ @Override ++ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { ++ return ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(this.getLevel()); ++ } ++ ++ @Override ++ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {} ++ ++ @Override ++ public boolean[] getSkyEmptinessMap() { ++ return null; ++ } ++ ++ @Override ++ public void setSkyEmptinessMap(final boolean[] emptinessMap) {} ++ ++ @Override ++ public boolean[] getBlockEmptinessMap() { ++ return null; ++ } ++ ++ @Override ++ public void setBlockEmptinessMap(final boolean[] emptinessMap) {} ++ // Paper end - starlight ++ + @Override + public BlockState getBlockState(BlockPos pos) { + return Blocks.VOID_AIR.defaultBlockState(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java +index 6bb508105641b5729572736c5c3f9bd6711e309a..60e760b42dd6471a229dfd45490dcf8c51979d35 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java +@@ -39,6 +39,48 @@ public class ImposterProtoChunk extends ProtoChunk { + this.allowWrites = propagateToWrapped; + } + ++ // Paper start - rewrite light engine ++ @Override ++ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { ++ return this.wrapped.getBlockNibbles(); ++ } ++ ++ @Override ++ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { ++ this.wrapped.setBlockNibbles(nibbles); ++ } ++ ++ @Override ++ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { ++ return this.wrapped.getSkyNibbles(); ++ } ++ ++ @Override ++ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { ++ this.wrapped.setSkyNibbles(nibbles); ++ } ++ ++ @Override ++ public boolean[] getSkyEmptinessMap() { ++ return this.wrapped.getSkyEmptinessMap(); ++ } ++ ++ @Override ++ public void setSkyEmptinessMap(final boolean[] emptinessMap) { ++ this.wrapped.setSkyEmptinessMap(emptinessMap); ++ } ++ ++ @Override ++ public boolean[] getBlockEmptinessMap() { ++ return this.wrapped.getBlockEmptinessMap(); ++ } ++ ++ @Override ++ public void setBlockEmptinessMap(final boolean[] emptinessMap) { ++ this.wrapped.setBlockEmptinessMap(emptinessMap); ++ } ++ // Paper end - rewrite light engine ++ + @Nullable + @Override + public BlockEntity getBlockEntity(BlockPos pos) { +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 af757309cb46af6df07872f7596b66df6d6f18d7..73e682bb3ef3b2e450ec8c594b5365c7a340615e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -220,6 +220,12 @@ public class LevelChunk extends ChunkAccess { + + public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) { + this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData()); ++ // Paper start - rewrite light engine ++ this.setBlockNibbles(protoChunk.getBlockNibbles()); ++ this.setSkyNibbles(protoChunk.getSkyNibbles()); ++ this.setSkyEmptinessMap(protoChunk.getSkyEmptinessMap()); ++ this.setBlockEmptinessMap(protoChunk.getBlockEmptinessMap()); ++ // Paper end - rewrite light engine + Iterator iterator = protoChunk.getBlockEntities().values().iterator(); + + while (iterator.hasNext()) { +@@ -246,7 +252,7 @@ public class LevelChunk extends ChunkAccess { + } + } + +- this.skyLightSources = protoChunk.skyLightSources; ++ // Paper - starlight - remove skyLightSources + this.setLightCorrect(protoChunk.isLightCorrect()); + this.unsaved = true; + this.needsDecoration = true; // CraftBukkit +@@ -437,7 +443,7 @@ public class LevelChunk extends ChunkAccess { + ProfilerFiller gameprofilerfiller = this.level.getProfiler(); + + gameprofilerfiller.push("updateSkyLightSources"); +- this.skyLightSources.update(this, j, i, l); ++ // Paper - starlight - remove skyLightSources + gameprofilerfiller.popPush("queueCheckLight"); + this.level.getChunkSource().getLightEngine().checkBlock(blockposition); + gameprofilerfiller.pop(); +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 dd62e257e16974a6d556a7f5e2d113a2cbc08981..dfae0918079425df92d958b04275be8ae60d4b60 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -143,7 +143,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + return this.get(this.strategy.getIndex(x, y, z)); + } + +- protected T get(int index) { ++ public T get(int index) { // Paper - public + PalettedContainer.Data data = this.data; + return data.palette.valueFor(data.storage.get(index)); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +index 38ec21faaa16df5485a81a581506700a5ab0a440..7da1ed9640211b0e064162dcdb0000538e7b30f3 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -130,7 +130,7 @@ public class ProtoChunk extends ChunkAccess { + } + + if (LightEngine.hasDifferentLightProperties(this, pos, blockState, state)) { +- this.skyLightSources.update(this, m, j, o); ++ // Paper - starlight - remove skyLightSources + this.lightEngine.checkBlock(pos); + } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 85de64c1e75e1323f8425fc53e525c215ff417ce..c6115477cc94bf47a5f459418a232412b7c358ba 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -88,6 +88,14 @@ public class ChunkSerializer { + private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion(); + private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); + // Paper end - Do not let the server load chunks from newer versions ++ // Paper start - replace light engine impl ++ private static final int STARLIGHT_LIGHT_VERSION = 9; ++ ++ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; ++ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; ++ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; ++ // Paper end - replace light engine impl ++ + public ChunkSerializer() {} + + // Paper start - guard against serializing mismatching coordinates +@@ -119,13 +127,20 @@ public class ChunkSerializer { + } + + UpgradeData chunkconverter = nbt.contains("UpgradeData", 10) ? new UpgradeData(nbt.getCompound("UpgradeData"), world) : UpgradeData.EMPTY; +- boolean flag = nbt.getBoolean("isLightOn"); ++ boolean flag = getStatus(nbt) != null && getStatus(nbt).isOrAfter(ChunkStatus.LIGHT) && nbt.get("isLightOn") != null && nbt.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION; // Paper + ListTag nbttaglist = nbt.getList("sections", 10); + int i = world.getSectionsCount(); + LevelChunkSection[] achunksection = new LevelChunkSection[i]; + boolean flag1 = world.dimensionType().hasSkyLight(); + ServerChunkCache chunkproviderserver = world.getChunkSource(); + LevelLightEngine levellightengine = chunkproviderserver.getLightEngine(); ++ // Paper start ++ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world); ++ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world); ++ final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world); ++ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world); ++ boolean canReadSky = world.dimensionType().hasSkyLight(); ++ // Paper end + Registry iregistry = world.registryAccess().registryOrThrow(Registries.BIOME); + Codec>> codec = ChunkSerializer.makeBiomeCodecRW(iregistry); // CraftBukkit - read/write + boolean flag2 = false; +@@ -133,7 +148,7 @@ public class ChunkSerializer { + DataResult dataresult; + + for (int j = 0; j < nbttaglist.size(); ++j) { +- CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); ++ CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); CompoundTag sectionData = nbttagcompound1; // Paper + byte b0 = nbttagcompound1.getByte("Y"); + int k = world.getSectionIndexFromSectionY(b0); + +@@ -176,19 +191,39 @@ public class ChunkSerializer { + boolean flag3 = nbttagcompound1.contains("BlockLight", 7); + boolean flag4 = flag1 && nbttagcompound1.contains("SkyLight", 7); + +- if (flag3 || flag4) { +- if (!flag2) { +- levellightengine.retainData(chunkPos, true); +- flag2 = true; +- } +- ++ // Paper start - rewrite the light engine ++ if (flag) { ++ try { ++ int y = sectionData.getByte("Y"); ++ // Paper end - rewrite the light engine + if (flag3) { +- levellightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("BlockLight"))); ++ // Paper start - rewrite the light engine ++ // this is where our diff is ++ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety ++ } else { ++ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG)); ++ // Paper end - rewrite the light engine + } + + if (flag4) { +- levellightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("SkyLight"))); ++ // Paper start - rewrite the light engine ++ // we store under the same key so mod programs editing nbt ++ // can still read the data, hopefully. ++ // however, for compatibility we store chunks as unlit so vanilla ++ // is forced to re-light them if it encounters our data. It's too much of a burden ++ // to try and maintain compatibility with a broken and inferior skylight management system. ++ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety ++ } else if (flag1) { ++ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG)); ++ // Paper end - rewrite the light engine ++ } ++ ++ // Paper start - rewrite the light engine ++ } catch (Exception ex) { ++ LOGGER.warn("Failed to load light data for chunk " + chunkPos + " in world '" + world.getWorld().getName() + "', light will be regenerated", ex); ++ flag = false; + } ++ // Paper end - rewrite light engine + } + } + +@@ -217,6 +252,8 @@ public class ChunkSerializer { + }, chunkPos); + + object1 = new LevelChunk(world.getLevel(), chunkPos, chunkconverter, levelchunkticks, levelchunkticks1, l, achunksection, ChunkSerializer.postLoadChunk(world, nbt), blendingdata); ++ ((LevelChunk)object1).setBlockNibbles(blockNibbles); // Paper - replace light impl ++ ((LevelChunk)object1).setSkyNibbles(skyNibbles); // Paper - replace light impl + } else { + ProtoChunkTicks protochunkticklist = ProtoChunkTicks.load(nbt.getList("block_ticks", 10), (s) -> { + return BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(s)); +@@ -225,6 +262,8 @@ public class ChunkSerializer { + return BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(s)); + }, chunkPos); + ProtoChunk protochunk = new ProtoChunk(chunkPos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world, iregistry, blendingdata); ++ protochunk.setBlockNibbles(blockNibbles); // Paper - replace light impl ++ protochunk.setSkyNibbles(skyNibbles); // Paper - replace light impl + + object1 = protochunk; + protochunk.setInhabitedTime(l); +@@ -346,6 +385,12 @@ public class ChunkSerializer { + // CraftBukkit end + + public static CompoundTag write(ServerLevel world, ChunkAccess chunk) { ++ // Paper start - rewrite light impl ++ final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world); ++ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world); ++ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles(); ++ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles(); ++ // Paper end - rewrite light impl + ChunkPos chunkcoordintpair = chunk.getPos(); + CompoundTag nbttagcompound = NbtUtils.addCurrentDataVersion(new CompoundTag()); + +@@ -395,11 +440,14 @@ public class ChunkSerializer { + for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) { + int j = chunk.getSectionIndexFromSectionY(i); + boolean flag1 = j >= 0 && j < achunksection.length; +- DataLayer nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); +- DataLayer nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); ++ // Paper - replace light engine + +- if (flag1 || nibblearray != null || nibblearray1 != null) { +- CompoundTag nbttagcompound1 = new CompoundTag(); ++ // Paper start - replace light engine ++ ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState(); ++ ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState(); ++ if (flag1 || blockNibble != null || skyNibble != null) { ++ // Paper end - replace light engine ++ CompoundTag nbttagcompound1 = new CompoundTag(); CompoundTag section = nbttagcompound1; // Paper + + if (flag1) { + LevelChunkSection chunksection = achunksection[j]; +@@ -414,13 +462,27 @@ public class ChunkSerializer { + nbttagcompound1.put("biomes", (Tag) dataresult1.getOrThrow(false, logger1::error)); + } + +- if (nibblearray != null && !nibblearray.isEmpty()) { +- nbttagcompound1.putByteArray("BlockLight", nibblearray.getData()); ++ // Paper start ++ // we store under the same key so mod programs editing nbt ++ // can still read the data, hopefully. ++ // however, for compatibility we store chunks as unlit so vanilla ++ // is forced to re-light them if it encounters our data. It's too much of a burden ++ // to try and maintain compatibility with a broken and inferior skylight management system. ++ ++ if (blockNibble != null) { ++ if (blockNibble.data != null) { ++ section.putByteArray("BlockLight", blockNibble.data); ++ } ++ section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state); + } + +- if (nibblearray1 != null && !nibblearray1.isEmpty()) { +- nbttagcompound1.putByteArray("SkyLight", nibblearray1.getData()); ++ if (skyNibble != null) { ++ if (skyNibble.data != null) { ++ section.putByteArray("SkyLight", skyNibble.data); ++ } ++ section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state); + } ++ // Paper end + + if (!nbttagcompound1.isEmpty()) { + nbttagcompound1.putByte("Y", (byte) i); +@@ -431,7 +493,8 @@ public class ChunkSerializer { + + nbttagcompound.put("sections", nbttaglist); + if (flag) { +- nbttagcompound.putBoolean("isLightOn", true); ++ nbttagcompound.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // Paper ++ nbttagcompound.putBoolean("isLightOn", false); // Paper - set to false but still store, this allows us to detect --eraseCache (as eraseCache _removes_) + } + + ListTag nbttaglist1 = new ListTag(); +@@ -505,6 +568,17 @@ public class ChunkSerializer { + })); + } + ++ // Paper start ++ public static @Nullable ChunkStatus getStatus(@Nullable CompoundTag compound) { ++ if (compound == null) { ++ return null; ++ } ++ ++ // Note: Copied from below ++ return ChunkStatus.getStatus(compound.getString("Status")); ++ } ++ // Paper end ++ + public static ChunkStatus.ChunkType getChunkTypeFromTag(@Nullable CompoundTag nbt) { + return nbt != null ? ChunkStatus.byName(nbt.getString("Status")).getChunkType() : ChunkStatus.ChunkType.PROTOCHUNK; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 18dbaf5d73898756086b94d06d08f8384224709a..6379f26776e2e267b84fe8f8392b53d7d89eb5ad 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -496,12 +496,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + } + +- for (final ChunkPos pos : chunksToRelight) { +- final ChunkAccess chunk = serverChunkCache.getChunk(pos.x, pos.z, false); +- if (chunk != null) { +- serverChunkCache.getLightEngine().lightChunk(chunk, false); +- } +- } ++ serverChunkCache.getLightEngine().relight(chunksToRelight, pos -> {}, relit -> {}); // Paper - Starlight + + return true; + // Paper end - implement regenerate chunk method diff --git a/patches/server/0984-Validate-ResourceLocation-in-NBT-reading.patch b/patches/server/0984-Validate-ResourceLocation-in-NBT-reading.patch deleted file mode 100644 index 28c0b0a4c933..000000000000 --- a/patches/server/0984-Validate-ResourceLocation-in-NBT-reading.patch +++ /dev/null @@ -1,112 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Thu, 4 Jan 2024 13:49:14 +0100 -Subject: [PATCH] Validate ResourceLocation in NBT reading - - -diff --git a/src/main/java/net/minecraft/nbt/NbtUtils.java b/src/main/java/net/minecraft/nbt/NbtUtils.java -index ba0726157417cdde1c9bca93a9e37e68d9b2286d..e3a3f19a6e63fd42e29c418e5a7439972484d492 100644 ---- a/src/main/java/net/minecraft/nbt/NbtUtils.java -+++ b/src/main/java/net/minecraft/nbt/NbtUtils.java -@@ -230,8 +230,10 @@ public final class NbtUtils { - if (!nbt.contains("Name", 8)) { - return Blocks.AIR.defaultBlockState(); - } else { -- ResourceLocation resourceLocation = new ResourceLocation(nbt.getString("Name")); -- Optional> optional = blockLookup.get(ResourceKey.create(Registries.BLOCK, resourceLocation)); -+ // Paper start - Validate resource location -+ ResourceLocation resourceLocation = ResourceLocation.tryParse(nbt.getString("Name")); -+ Optional> optional = resourceLocation != null ? blockLookup.get(ResourceKey.create(Registries.BLOCK, resourceLocation)) : Optional.empty(); -+ // Paper end - Validate resource location - if (optional.isEmpty()) { - return Blocks.AIR.defaultBlockState(); - } else { -diff --git a/src/main/java/net/minecraft/resources/ResourceLocation.java b/src/main/java/net/minecraft/resources/ResourceLocation.java -index 5f9dcab27a07969c93555ad0892683c62cbebc8c..a4d875df936b6de16f0233482b03af05b427a79f 100644 ---- a/src/main/java/net/minecraft/resources/ResourceLocation.java -+++ b/src/main/java/net/minecraft/resources/ResourceLocation.java -@@ -31,6 +31,13 @@ public class ResourceLocation implements Comparable { - private final String path; - - protected ResourceLocation(String namespace, String path, @Nullable ResourceLocation.Dummy extraData) { -+ // Paper start - Validate ResourceLocation -+ // Check for the max network string length (capped at Short.MAX_VALUE) as well as the max bytes of a StringTag (length written as an unsigned short) -+ final String resourceLocation = namespace + ":" + path; -+ if (resourceLocation.length() > Short.MAX_VALUE || io.netty.buffer.ByteBufUtil.utf8MaxBytes(resourceLocation) > 2 * Short.MAX_VALUE + 1) { -+ throw new ResourceLocationException("Resource location too long: " + resourceLocation); -+ } -+ // Paper end - Validate ResourceLocation - this.namespace = namespace; - this.path = path; - } -diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index 8ba573bb4099ee5b27b61f333e72d794c48d5f29..69bdf3f2ee731e59e8d454816a9ca72cb49c0fe0 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -614,7 +614,7 @@ public class EntityType implements FeatureElement, EntityTypeT - } - - public static Optional> by(CompoundTag nbt) { -- return BuiltInRegistries.ENTITY_TYPE.getOptional(new ResourceLocation(nbt.getString("id"))); -+ return BuiltInRegistries.ENTITY_TYPE.getOptional(ResourceLocation.tryParse(nbt.getString("id"))); // Paper - Validate ResourceLocation - } - - @Nullable -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index b4568e0dcb33aa516c1bcd7338e7f9220ac71ff6..a55985205cbd5d318a15552816ce44560d323559 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -870,12 +870,13 @@ public abstract class LivingEntity extends Entity implements Attackable { - - if (nbt.contains("SleepingX", 99) && nbt.contains("SleepingY", 99) && nbt.contains("SleepingZ", 99)) { - BlockPos blockposition = new BlockPos(nbt.getInt("SleepingX"), nbt.getInt("SleepingY"), nbt.getInt("SleepingZ")); -- -+ if (this.position().distanceToSqr(blockposition.getX(), blockposition.getY(), blockposition.getZ()) < 16 * 16) { // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong - this.setSleepingPos(blockposition); - this.entityData.set(LivingEntity.DATA_POSE, Pose.SLEEPING); - if (!this.firstTick) { - this.setPosToBed(blockposition); - } -+ } // Paper - The sleeping pos will always also set the actual pos, so a desync suggests something is wrong - } - - if (nbt.contains("Brain", 10)) { -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 026654c4d3a910f0dbfed5475f23137086618242..1881deb9d8ffc884ba23843936615181f4220623 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -607,7 +607,7 @@ public abstract class Mob extends LivingEntity implements Targeting { - - this.setLeftHanded(nbt.getBoolean("LeftHanded")); - if (nbt.contains("DeathLootTable", 8)) { -- this.lootTable = new ResourceLocation(nbt.getString("DeathLootTable")); -+ this.lootTable = ResourceLocation.tryParse(nbt.getString("DeathLootTable")); // Paper - Validate ResourceLocation - this.lootTableSeed = nbt.getLong("DeathLootTableSeed"); - } - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index 618de60680de015bc68bf95a68eda98db7bab3c5..d14eab0d83d629a4522bf3f7d789d2853eb84f06 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -560,7 +560,7 @@ public abstract class AbstractArrow extends Projectile { - this.setCritArrow(nbt.getBoolean("crit")); - this.setPierceLevel(nbt.getByte("PierceLevel")); - if (nbt.contains("SoundEvent", 8)) { -- this.soundEvent = (SoundEvent) BuiltInRegistries.SOUND_EVENT.getOptional(new ResourceLocation(nbt.getString("SoundEvent"))).orElse(this.getDefaultHitGroundSoundEvent()); -+ this.soundEvent = (SoundEvent) BuiltInRegistries.SOUND_EVENT.getOptional(ResourceLocation.tryParse(nbt.getString("SoundEvent"))).orElse(this.getDefaultHitGroundSoundEvent()); // Paper - Validate resource location - } - - this.setShotFromCrossbow(nbt.getBoolean("ShotFromCrossbow")); -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java -index 7529751afa2932fd16bc4591189b0358268a7b14..e2e1c7a017e82dc7299e5cd1783818e4f0319c0b 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java -@@ -67,7 +67,7 @@ public interface ContainerEntity extends Container, MenuProvider { - default void readChestVehicleSaveData(CompoundTag nbt) { - this.clearItemStacks(); - if (nbt.contains("LootTable", 8)) { -- this.setLootTable(new ResourceLocation(nbt.getString("LootTable"))); -+ this.setLootTable(ResourceLocation.tryParse(nbt.getString("LootTable"))); // Paper - Validate ResourceLocation - this.setLootTableSeed(nbt.getLong("LootTableSeed")); - } - diff --git a/patches/server/0985-Rewrite-chunk-system.patch b/patches/server/0985-Rewrite-chunk-system.patch new file mode 100644 index 000000000000..a14343ab34cb --- /dev/null +++ b/patches/server/0985-Rewrite-chunk-system.patch @@ -0,0 +1,21682 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 11 Mar 2021 02:32:30 -0800 +Subject: [PATCH] Rewrite chunk system + +Rebased patches: + +New player chunk loader system + +Make ChunkStatus.EMPTY not rely on the main thread for completion + +In order to do this, we need to push the POI consistency checks +to a later status. Since FULL is the only other status that +uses the main thread, it can go there. + +The consistency checks are only really for when a desync occurs, +and so that delaying the check only matters when the chunk data +has desync'd. As long as the desync is sorted before the +chunk is full loaded (i.e before setBlock can occur on +a chunk), it should not matter. + +This change is primarily due to behavioural changes +in the chunk task queue brought by region threading - +which is to split the queue into separate regions. As such, +it is required that in order for the sync load to complete +that the region owning the chunk drain and execute the task +while ticking. However, that is not always possible in +region threading. Thus, removing the main thread reliance allows +the chunk to progress without requiring a tick thread. +Specifically, this allows far sync loads (outside of a specific +regions bounds) to occur without issue - namely with structure +searching. + +Increase parallelism for neighbour writing chunk statuses + +Namely, everything after FEATURES. By creating a dependency +chain indicating what chunks are in use, we can safely +schedule completely independent tasks in parallel. This +will allow the chunk system to scale beyond 10 threads +per world. + +Properly cancel chunk load tasks that were not scheduled + +Since the chunk load task was not scheduled, the entity/poi load +task fields will not be set, but the task complete counter +will not be adjusted. Thus, the chunk load task will not complete. + +To resolve this, detect when the entity/poi tasks were not scheduled +and decrement the task complete counter in such cases. + +Mark POI/Entity load tasks as completed before releasing scheduling lock + +It must be marked as completed during that lock hold since the +waiters field is set to null. Thus, any other thread attempting +a cancellation will fail to remove from waiters. Also, any +other thread attempting to cancel may set the completed field +to true which would cause accept() to fail as well. + +Completion was always designed to happen while holding the +scheduling lock to prevent these race conditions. The code +was originally set up to complete while not holding the +scheduling lock to avoid invoking callbacks while holding the +lock, however the access to the completion field was not +considered. + +Resolve this by marking the callback as completed during the +lock, but invoking the accept() function after releasing +the lock. This will prevent any cancellation attempts to be +blocked, and allow the current thread to complete the callback +without any issues. + +Cache whether region files do not exist + +The repeated I/O of creating the directory for the regionfile +or for checking if the file exists can be heavy in +when pushing chunk generation extremely hard - as each chunk gen +request may effectively go through to the I/O thread. + +Use coordinate-based locking to increase chunk system parallelism + +A significant overhead in Folia comes from the chunk system's +locks, the ticket lock and the scheduling lock. The public +test server, which had ~330 players, had signficant performance +problems with these locks: ~80% of the time spent ticking +was _waiting_ for the locks to free. Given that it used +around 15 cores total at peak, this is a complete and utter loss +of potential. + +To address this issue, I have replaced the ticket lock and scheduling +lock with two ReentrantAreaLocks. The ReentrantAreaLock takes a +shift, which is used internally to group positions into sections. +This grouping is neccessary, as the possible radius of area that +needs to be acquired for any given lock usage is up to 64. As such, +the shift is critical to reduce the number of areas required to lock +for any lock operation. Currently, it is set to a shift of 6, which +is identical to the ticket level propagation shift (and, it must be +at least the ticket level propagation shift AND the region shift). + +The chunk system locking changes required a complete rewrite of the +chunk system tick, chunk system unload, and chunk system ticket level +propagation - as all of the previous logic only works with a single +global lock. + +This does introduce two other section shifts: the lock shift, and the +ticket shift. The lock shift is simply what shift the area locks use, +and the ticket shift represents the size of the ticket sections. +Currently, these values are just set to the region shift for simplicity. +However, they are not arbitrary: the lock shift must be at least the size +of the ticket shift and must be at least the size of the region shift. +The ticket shift must also be >= the ceil(log2(max ticket level source)). + +The chunk system's ticket propagator is now global state, instead of +region state. This cleans up the logic for ticket levels significantly, +and removes usage of the region lock in this area, but it also means +that the addition of a ticket no longer creates a region. To alleviate +the side effects of this change, the global tick thread now processes +ticket level updates for each world every tick to guarantee eventual +ticket level processing. The chunk system also provides a hook to +process ticket level changes in a given _section_, so that the +region queue can guarantee that after adding its reference counter +that the region section is created/exists/wont be destroyed. + +The ticket propagator operates by updating the sources in a single ticket +section, and propagating the updates to its 1 radius neighbours. This +allows the ticket updates to occur in parallel or selectively (see above). +Currently, the process ticket level update function operates by +polling from a concurrent queue of sections to update and simply +invoking the single section update logic. This allows the function +to operate completely in parallel, provided the queue is ordered right. +Additionally, this limits the area used in the ticket/scheduling lock +when processing updates, which should massively increase parallelism compared +to before. + +The chunk system ticket addition for expirable ticket types has been modified +to no longer track exact tick deadlines, as this relies on what region the +ticket is in. Instead, the chunk system tracks a map of +lock section -> (chunk coordinate -> expire ticket count) and every ticket +has been changed to have a removeDelay count that is decremented each tick. +Each region searches its own sections to find tickets to try to expire. + +Chunk system unloading has been modified to track unloads by lock section. +The ordering is determined by which section a chunk resides in. +The unload process now removes from unload sections and processes +the full unload stages (1, 2, 3) before moving to the next section, if possible. +This allows the unload logic to only hold one lock section at a time for +each lock, which is a massive parallelism increase. + +In stress testing, these changes lowered the locking overhead to only 5% +from ~70%, which completely fix the original problem as described. + +== AT == +public net.minecraft.server.level.ChunkHolder pos +public net.minecraft.server.level.ChunkMap overworldDataStorage +public-f net.minecraft.world.level.chunk.storage.RegionFileStorage +public net.minecraft.server.level.ChunkMap getPoiManager()Lnet/minecraft/world/entity/ai/village/poi/PoiManager; + +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4fd9a0cd8f1e6ae1a97e963dc7731a80bc6fac5b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java +@@ -0,0 +1,395 @@ ++package ca.spottedleaf.concurrentutil.lock; ++ ++import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; ++import it.unimi.dsi.fastutil.HashCommon; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.locks.LockSupport; ++ ++public final class ReentrantAreaLock { ++ ++ public final int coordinateShift; ++ ++ // aggressive load factor to reduce contention ++ private final ConcurrentHashMap nodes = new ConcurrentHashMap<>(128, 0.2f); ++ ++ public ReentrantAreaLock(final int coordinateShift) { ++ this.coordinateShift = coordinateShift; ++ } ++ ++ public boolean isHeldByCurrentThread(final int x, final int z) { ++ final Thread currThread = Thread.currentThread(); ++ final int shift = this.coordinateShift; ++ final int sectionX = x >> shift; ++ final int sectionZ = z >> shift; ++ ++ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); ++ final Node node = this.nodes.get(coordinate); ++ ++ return node != null && node.thread == currThread; ++ } ++ ++ public boolean isHeldByCurrentThread(final int centerX, final int centerZ, final int radius) { ++ return this.isHeldByCurrentThread(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); ++ } ++ ++ public boolean isHeldByCurrentThread(final int fromX, final int fromZ, final int toX, final int toZ) { ++ if (fromX > toX || fromZ > toZ) { ++ throw new IllegalArgumentException(); ++ } ++ ++ final Thread currThread = Thread.currentThread(); ++ final int shift = this.coordinateShift; ++ final int fromSectionX = fromX >> shift; ++ final int fromSectionZ = fromZ >> shift; ++ final int toSectionX = toX >> shift; ++ final int toSectionZ = toZ >> shift; ++ ++ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { ++ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { ++ final Coordinate coordinate = new Coordinate(Coordinate.key(currX, currZ)); ++ ++ final Node node = this.nodes.get(coordinate); ++ ++ if (node == null || node.thread != currThread) { ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ public Node tryLock(final int x, final int z) { ++ return this.tryLock(x, z, x, z); ++ } ++ ++ public Node tryLock(final int centerX, final int centerZ, final int radius) { ++ return this.tryLock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); ++ } ++ ++ public Node tryLock(final int fromX, final int fromZ, final int toX, final int toZ) { ++ if (fromX > toX || fromZ > toZ) { ++ throw new IllegalArgumentException(); ++ } ++ ++ final Thread currThread = Thread.currentThread(); ++ final int shift = this.coordinateShift; ++ final int fromSectionX = fromX >> shift; ++ final int fromSectionZ = fromZ >> shift; ++ final int toSectionX = toX >> shift; ++ final int toSectionZ = toZ >> shift; ++ ++ final List areaAffected = new ArrayList<>(); ++ ++ final Node ret = new Node(this, areaAffected, currThread); ++ ++ boolean failed = false; ++ ++ // try to fast acquire area ++ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { ++ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { ++ final Coordinate coordinate = new Coordinate(Coordinate.key(currX, currZ)); ++ ++ final Node prev = this.nodes.putIfAbsent(coordinate, ret); ++ ++ if (prev == null) { ++ areaAffected.add(coordinate); ++ continue; ++ } ++ ++ if (prev.thread != currThread) { ++ failed = true; ++ break; ++ } ++ } ++ } ++ ++ if (!failed) { ++ return ret; ++ } ++ ++ // failed, undo logic ++ if (!areaAffected.isEmpty()) { ++ for (int i = 0, len = areaAffected.size(); i < len; ++i) { ++ final Coordinate key = areaAffected.get(i); ++ ++ if (this.nodes.remove(key) != ret) { ++ throw new IllegalStateException(); ++ } ++ } ++ ++ areaAffected.clear(); ++ ++ // since we inserted, we need to drain waiters ++ Thread unpark; ++ while ((unpark = ret.pollOrBlockAdds()) != null) { ++ LockSupport.unpark(unpark); ++ } ++ } ++ ++ return null; ++ } ++ ++ public Node lock(final int x, final int z) { ++ final Thread currThread = Thread.currentThread(); ++ final int shift = this.coordinateShift; ++ final int sectionX = x >> shift; ++ final int sectionZ = z >> shift; ++ ++ final List areaAffected = new ArrayList<>(1); ++ ++ final Node ret = new Node(this, areaAffected, currThread); ++ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); ++ ++ for (long failures = 0L;;) { ++ final Node park; ++ ++ // try to fast acquire area ++ { ++ final Node prev = this.nodes.putIfAbsent(coordinate, ret); ++ ++ if (prev == null) { ++ areaAffected.add(coordinate); ++ return ret; ++ } else if (prev.thread != currThread) { ++ park = prev; ++ } else { ++ // only one node we would want to acquire, and it's owned by this thread already ++ return ret; ++ } ++ } ++ ++ ++failures; ++ ++ if (failures > 128L && park.add(currThread)) { ++ LockSupport.park(); ++ } else { ++ // high contention, spin wait ++ if (failures < 128L) { ++ for (long i = 0; i < failures; ++i) { ++ Thread.onSpinWait(); ++ } ++ failures = failures << 1; ++ } else if (failures < 1_200L) { ++ LockSupport.parkNanos(1_000L); ++ failures = failures + 1L; ++ } else { // scale 0.1ms (100us) per failure ++ Thread.yield(); ++ LockSupport.parkNanos(100_000L * failures); ++ failures = failures + 1L; ++ } ++ } ++ } ++ } ++ ++ public Node lock(final int centerX, final int centerZ, final int radius) { ++ return this.lock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); ++ } ++ ++ public Node lock(final int fromX, final int fromZ, final int toX, final int toZ) { ++ if (fromX > toX || fromZ > toZ) { ++ throw new IllegalArgumentException(); ++ } ++ ++ final Thread currThread = Thread.currentThread(); ++ final int shift = this.coordinateShift; ++ final int fromSectionX = fromX >> shift; ++ final int fromSectionZ = fromZ >> shift; ++ final int toSectionX = toX >> shift; ++ final int toSectionZ = toZ >> shift; ++ ++ if (((fromSectionX ^ toSectionX) | (fromSectionZ ^ toSectionZ)) == 0) { ++ return this.lock(fromX, fromZ); ++ } ++ ++ final List areaAffected = new ArrayList<>(); ++ ++ final Node ret = new Node(this, areaAffected, currThread); ++ ++ for (long failures = 0L;;) { ++ Node park = null; ++ boolean addedToArea = false; ++ boolean alreadyOwned = false; ++ boolean allOwned = true; ++ ++ // try to fast acquire area ++ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { ++ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { ++ final Coordinate coordinate = new Coordinate(Coordinate.key(currX, currZ)); ++ ++ final Node prev = this.nodes.putIfAbsent(coordinate, ret); ++ ++ if (prev == null) { ++ addedToArea = true; ++ allOwned = false; ++ areaAffected.add(coordinate); ++ continue; ++ } ++ ++ if (prev.thread != currThread) { ++ park = prev; ++ alreadyOwned = true; ++ break; ++ } ++ } ++ } ++ ++ if (park == null) { ++ if (alreadyOwned && !allOwned) { ++ throw new IllegalStateException("Improper lock usage: Should never acquire intersecting areas"); ++ } ++ return ret; ++ } ++ ++ // failed, undo logic ++ if (addedToArea) { ++ for (int i = 0, len = areaAffected.size(); i < len; ++i) { ++ final Coordinate key = areaAffected.get(i); ++ ++ if (this.nodes.remove(key) != ret) { ++ throw new IllegalStateException(); ++ } ++ } ++ ++ areaAffected.clear(); ++ ++ // since we inserted, we need to drain waiters ++ Thread unpark; ++ while ((unpark = ret.pollOrBlockAdds()) != null) { ++ LockSupport.unpark(unpark); ++ } ++ } ++ ++ ++failures; ++ ++ if (failures > 128L && park.add(currThread)) { ++ LockSupport.park(park); ++ } else { ++ // high contention, spin wait ++ if (failures < 128L) { ++ for (long i = 0; i < failures; ++i) { ++ Thread.onSpinWait(); ++ } ++ failures = failures << 1; ++ } else if (failures < 1_200L) { ++ LockSupport.parkNanos(1_000L); ++ failures = failures + 1L; ++ } else { // scale 0.1ms (100us) per failure ++ Thread.yield(); ++ LockSupport.parkNanos(100_000L * failures); ++ failures = failures + 1L; ++ } ++ } ++ ++ if (addedToArea) { ++ // try again, so we need to allow adds so that other threads can properly block on us ++ ret.allowAdds(); ++ } ++ } ++ } ++ ++ public void unlock(final Node node) { ++ if (node.lock != this) { ++ throw new IllegalStateException("Unlock target lock mismatch"); ++ } ++ ++ final List areaAffected = node.areaAffected; ++ ++ if (areaAffected.isEmpty()) { ++ // here we are not in the node map, and so do not need to remove from the node map or unblock any waiters ++ return; ++ } ++ ++ // remove from node map; allowing other threads to lock ++ for (int i = 0, len = areaAffected.size(); i < len; ++i) { ++ final Coordinate coordinate = areaAffected.get(i); ++ if (this.nodes.remove(coordinate) != node) { ++ throw new IllegalStateException(); ++ } ++ } ++ ++ Thread unpark; ++ while ((unpark = node.pollOrBlockAdds()) != null) { ++ LockSupport.unpark(unpark); ++ } ++ } ++ ++ public static final class Node extends MultiThreadedQueue { ++ ++ private final ReentrantAreaLock lock; ++ private final List areaAffected; ++ private final Thread thread; ++ //private final Throwable WHO_CREATED_MY_ASS = new Throwable(); ++ ++ private Node(final ReentrantAreaLock lock, final List areaAffected, final Thread thread) { ++ this.lock = lock; ++ this.areaAffected = areaAffected; ++ this.thread = thread; ++ } ++ ++ @Override ++ public String toString() { ++ return "Node{" + ++ "areaAffected=" + this.areaAffected + ++ ", thread=" + this.thread + ++ '}'; ++ } ++ } ++ ++ private static final class Coordinate implements Comparable { ++ ++ public final long key; ++ ++ public Coordinate(final long key) { ++ this.key = key; ++ } ++ ++ public Coordinate(final int x, final int z) { ++ this.key = key(x, z); ++ } ++ ++ public static long key(final int x, final int z) { ++ return ((long)z << 32) | (x & 0xFFFFFFFFL); ++ } ++ ++ public static int x(final long key) { ++ return (int)key; ++ } ++ ++ public static int z(final long key) { ++ return (int)(key >>> 32); ++ } ++ ++ @Override ++ public int hashCode() { ++ return (int)HashCommon.mix(this.key); ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ ++ if (!(obj instanceof Coordinate other)) { ++ return false; ++ } ++ ++ return this.key == other.key; ++ } ++ ++ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain ++ // is too large. So we should implement compareTo to help. ++ @Override ++ public int compareTo(final Coordinate other) { ++ return Long.compare(this.key, other.key); ++ } ++ ++ @Override ++ public String toString() { ++ return "[" + x(this.key) + "," + z(this.key) + "]"; ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/lock/SyncReentrantAreaLock.java b/src/main/java/ca/spottedleaf/concurrentutil/lock/SyncReentrantAreaLock.java +new file mode 100644 +index 0000000000000000000000000000000000000000..64b5803d002b2968841a5ddee987f98b72964e87 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/concurrentutil/lock/SyncReentrantAreaLock.java +@@ -0,0 +1,217 @@ ++package ca.spottedleaf.concurrentutil.lock; ++ ++import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; ++import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import java.util.concurrent.locks.LockSupport; ++ ++// not concurrent, unlike ReentrantAreaLock ++// no incorrect lock usage detection (acquiring intersecting areas) ++// this class is nothing more than a performance reference for ReentrantAreaLock ++public final class SyncReentrantAreaLock { ++ ++ private final int coordinateShift; ++ ++ // aggressive load factor to reduce contention ++ private final Long2ReferenceOpenHashMap nodes = new Long2ReferenceOpenHashMap<>(128, 0.2f); ++ ++ public SyncReentrantAreaLock(final int coordinateShift) { ++ this.coordinateShift = coordinateShift; ++ } ++ ++ private static long key(final int x, final int z) { ++ return ((long)z << 32) | (x & 0xFFFFFFFFL); ++ } ++ ++ public Node lock(final int x, final int z) { ++ final Thread currThread = Thread.currentThread(); ++ final int shift = this.coordinateShift; ++ final int sectionX = x >> shift; ++ final int sectionZ = z >> shift; ++ ++ final LongArrayList areaAffected = new LongArrayList(); ++ ++ final Node ret = new Node(this, areaAffected, currThread); ++ ++ final long coordinate = key(sectionX, sectionZ); ++ ++ for (long failures = 0L;;) { ++ final Node park; ++ ++ synchronized (this) { ++ // try to fast acquire area ++ final Node prev = this.nodes.putIfAbsent(coordinate, ret); ++ ++ if (prev == null) { ++ areaAffected.add(coordinate); ++ return ret; ++ } else if (prev.thread != currThread) { ++ park = prev; ++ } else { ++ // only one node we would want to acquire, and it's owned by this thread already ++ return ret; ++ } ++ } ++ ++ ++failures; ++ ++ if (failures > 128L && park.add(currThread)) { ++ LockSupport.park(); ++ } else { ++ // high contention, spin wait ++ if (failures < 128L) { ++ for (long i = 0; i < failures; ++i) { ++ Thread.onSpinWait(); ++ } ++ failures = failures << 1; ++ } else if (failures < 1_200L) { ++ LockSupport.parkNanos(1_000L); ++ failures = failures + 1L; ++ } else { // scale 0.1ms (100us) per failure ++ Thread.yield(); ++ LockSupport.parkNanos(100_000L * failures); ++ failures = failures + 1L; ++ } ++ } ++ } ++ } ++ ++ public Node lock(final int centerX, final int centerZ, final int radius) { ++ return this.lock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); ++ } ++ ++ public Node lock(final int fromX, final int fromZ, final int toX, final int toZ) { ++ if (fromX > toX || fromZ > toZ) { ++ throw new IllegalArgumentException(); ++ } ++ ++ final Thread currThread = Thread.currentThread(); ++ final int shift = this.coordinateShift; ++ final int fromSectionX = fromX >> shift; ++ final int fromSectionZ = fromZ >> shift; ++ final int toSectionX = toX >> shift; ++ final int toSectionZ = toZ >> shift; ++ ++ final LongArrayList areaAffected = new LongArrayList(); ++ ++ final Node ret = new Node(this, areaAffected, currThread); ++ ++ for (long failures = 0L;;) { ++ Node park = null; ++ boolean addedToArea = false; ++ ++ synchronized (this) { ++ // try to fast acquire area ++ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { ++ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { ++ final long coordinate = key(currX, currZ); ++ ++ final Node prev = this.nodes.putIfAbsent(coordinate, ret); ++ ++ if (prev == null) { ++ addedToArea = true; ++ areaAffected.add(coordinate); ++ continue; ++ } ++ ++ if (prev.thread != currThread) { ++ park = prev; ++ break; ++ } ++ } ++ } ++ ++ if (park == null) { ++ return ret; ++ } ++ ++ // failed, undo logic ++ if (!areaAffected.isEmpty()) { ++ for (int i = 0, len = areaAffected.size(); i < len; ++i) { ++ final long key = areaAffected.getLong(i); ++ ++ if (!this.nodes.remove(key, ret)) { ++ throw new IllegalStateException(); ++ } ++ } ++ } ++ } ++ ++ if (addedToArea) { ++ areaAffected.clear(); ++ // since we inserted, we need to drain waiters ++ Thread unpark; ++ while ((unpark = ret.pollOrBlockAdds()) != null) { ++ LockSupport.unpark(unpark); ++ } ++ } ++ ++ ++failures; ++ ++ if (failures > 128L && park.add(currThread)) { ++ LockSupport.park(); ++ } else { ++ // high contention, spin wait ++ if (failures < 128L) { ++ for (long i = 0; i < failures; ++i) { ++ Thread.onSpinWait(); ++ } ++ failures = failures << 1; ++ } else if (failures < 1_200L) { ++ LockSupport.parkNanos(1_000L); ++ failures = failures + 1L; ++ } else { // scale 0.1ms (100us) per failure ++ Thread.yield(); ++ LockSupport.parkNanos(100_000L * failures); ++ failures = failures + 1L; ++ } ++ } ++ ++ if (addedToArea) { ++ // try again, so we need to allow adds so that other threads can properly block on us ++ ret.allowAdds(); ++ } ++ } ++ } ++ ++ public void unlock(final Node node) { ++ if (node.lock != this) { ++ throw new IllegalStateException("Unlock target lock mismatch"); ++ } ++ ++ final LongArrayList areaAffected = node.areaAffected; ++ ++ if (areaAffected.isEmpty()) { ++ // here we are not in the node map, and so do not need to remove from the node map or unblock any waiters ++ return; ++ } ++ ++ // remove from node map; allowing other threads to lock ++ synchronized (this) { ++ for (int i = 0, len = areaAffected.size(); i < len; ++i) { ++ final long coordinate = areaAffected.getLong(i); ++ if (!this.nodes.remove(coordinate, node)) { ++ throw new IllegalStateException(); ++ } ++ } ++ } ++ ++ Thread unpark; ++ while ((unpark = node.pollOrBlockAdds()) != null) { ++ LockSupport.unpark(unpark); ++ } ++ } ++ ++ public static final class Node extends MultiThreadedQueue { ++ ++ private final SyncReentrantAreaLock lock; ++ private final LongArrayList areaAffected; ++ private final Thread thread; ++ ++ private Node(final SyncReentrantAreaLock lock, final LongArrayList areaAffected, final Thread thread) { ++ this.lock = lock; ++ this.areaAffected = areaAffected; ++ this.thread = thread; ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java +index 499c069d64692872924963d3a7ac39664b20468d..ef8ea36b2acefb935afda01396d2699e2921f396 100644 +--- a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java ++++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java +@@ -41,14 +41,14 @@ public final class StarLightInterface { + protected final ArrayDeque cachedSkyPropagators; + protected final ArrayDeque cachedBlockPropagators; + +- protected final LightQueue lightQueue = new LightQueue(this); ++ public final io.papermc.paper.chunk.system.light.LightQueue lightQueue; // Paper - replace light queue + + protected final LayerLightEventListener skyReader; + protected final LayerLightEventListener blockReader; + protected final boolean isClientSide; + +- protected final int minSection; +- protected final int maxSection; ++ public final int minSection; // Paper - public ++ public final int maxSection; // Paper - public + protected final int minLightSection; + protected final int maxLightSection; + +@@ -182,6 +182,7 @@ public final class StarLightInterface { + StarLightInterface.this.sectionChange(pos, notReady); + } + }; ++ this.lightQueue = new io.papermc.paper.chunk.system.light.LightQueue(this); // Paper - replace light queue + } + + public boolean hasSkyLight() { +@@ -333,7 +334,7 @@ public final class StarLightInterface { + return this.lightAccess; + } + +- protected final SkyStarLightEngine getSkyLightEngine() { ++ public final SkyStarLightEngine getSkyLightEngine() { // Paper - public + if (this.cachedSkyPropagators == null) { + return null; + } +@@ -348,7 +349,7 @@ public final class StarLightInterface { + return ret; + } + +- protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) { ++ public final void releaseSkyLightEngine(final SkyStarLightEngine engine) { // Paper - public + if (this.cachedSkyPropagators == null) { + return; + } +@@ -357,7 +358,7 @@ public final class StarLightInterface { + } + } + +- protected final BlockStarLightEngine getBlockLightEngine() { ++ public final BlockStarLightEngine getBlockLightEngine() { // Paper - public + if (this.cachedBlockPropagators == null) { + return null; + } +@@ -372,7 +373,7 @@ public final class StarLightInterface { + return ret; + } + +- protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) { ++ public final void releaseBlockLightEngine(final BlockStarLightEngine engine) { // Paper - public + if (this.cachedBlockPropagators == null) { + return; + } +@@ -381,7 +382,7 @@ public final class StarLightInterface { + } + } + +- public LightQueue.ChunkTasks blockChange(final BlockPos pos) { ++ public io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks blockChange(final BlockPos pos) { // Paper - rewrite chunk system + if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world + return null; + } +@@ -389,7 +390,7 @@ public final class StarLightInterface { + return this.lightQueue.queueBlockChange(pos); + } + +- public LightQueue.ChunkTasks sectionChange(final SectionPos pos, final boolean newEmptyValue) { ++ public io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks sectionChange(final SectionPos pos, final boolean newEmptyValue) { // Paper - rewrite chunk system + if (this.world == null) { // empty world + return null; + } +@@ -519,57 +520,15 @@ public final class StarLightInterface { + } + + public void scheduleChunkLight(final ChunkPos pos, final Runnable run) { +- this.lightQueue.queueChunkLighting(pos, run); ++ throw new UnsupportedOperationException("No longer implemented, use the new lightQueue field to queue tasks"); // Paper - replace light queue + } + + public void removeChunkTasks(final ChunkPos pos) { +- this.lightQueue.removeChunk(pos); ++ throw new UnsupportedOperationException("No longer implemented, use the new lightQueue field to queue tasks"); // Paper - replace light queue + } + + public void propagateChanges() { +- if (this.lightQueue.isEmpty()) { +- return; +- } +- +- final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); +- final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); +- +- try { +- LightQueue.ChunkTasks task; +- while ((task = this.lightQueue.removeFirstTask()) != null) { +- if (task.lightTasks != null) { +- for (final Runnable run : task.lightTasks) { +- run.run(); +- } +- } +- +- final long coordinate = task.chunkCoordinate; +- final int chunkX = CoordinateUtils.getChunkX(coordinate); +- final int chunkZ = CoordinateUtils.getChunkZ(coordinate); +- +- final Set positions = task.changedPositions; +- final Boolean[] sectionChanges = task.changedSectionSet; +- +- if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) { +- skyEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); +- } +- if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) { +- blockEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); +- } +- +- if (skyEngine != null && task.queuedEdgeChecksSky != null) { +- skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksSky); +- } +- if (blockEngine != null && task.queuedEdgeChecksBlock != null) { +- blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksBlock); +- } +- +- task.onComplete.complete(null); +- } +- } finally { +- this.releaseSkyLightEngine(skyEngine); +- this.releaseBlockLightEngine(blockEngine); +- } ++ throw new UnsupportedOperationException("No longer implemented, task draining is now performed by the light thread"); // Paper - replace light queue + } + + public static final class LightQueue { +diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +index 2f0d9b953802dee821cfde82d22b0567cce8ee91..22687667ec69a954261e55e59261286ac1b8b8cd 100644 +--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java ++++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java +@@ -59,6 +59,16 @@ public class WorldTimingsHandler { + + public final Timing miscMobSpawning; + ++ public final Timing poiUnload; ++ public final Timing chunkUnload; ++ public final Timing poiSaveDataSerialization; ++ public final Timing chunkSave; ++ public final Timing chunkSaveDataSerialization; ++ public final Timing chunkSaveIOWait; ++ public final Timing chunkUnloadPrepareSave; ++ public final Timing chunkUnloadPOISerialization; ++ public final Timing chunkUnloadDataSave; ++ + public WorldTimingsHandler(Level server) { + String name = ((PrimaryLevelData) server.getLevelData()).getLevelName() + " - "; + +@@ -112,6 +122,16 @@ public class WorldTimingsHandler { + + + miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc"); ++ ++ poiUnload = Timings.ofSafe(name + "Chunk unload - POI"); ++ chunkUnload = Timings.ofSafe(name + "Chunk unload - Chunk"); ++ poiSaveDataSerialization = Timings.ofSafe(name + "Chunk save - POI Data serialization"); ++ chunkSave = Timings.ofSafe(name + "Chunk save - Chunk"); ++ chunkSaveDataSerialization = Timings.ofSafe(name + "Chunk save - Chunk Data serialization"); ++ chunkSaveIOWait = Timings.ofSafe(name + "Chunk save - Chunk IO Wait"); ++ chunkUnloadPrepareSave = Timings.ofSafe(name + "Chunk unload - Async Save Prepare"); ++ chunkUnloadPOISerialization = Timings.ofSafe(name + "Chunk unload - POI Data Serialization"); ++ chunkUnloadDataSave = Timings.ofSafe(name + "Chunk unload - Data Serialization"); + } + + public static Timing getTickList(ServerLevel worldserver, String timingsType) { +diff --git a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java +index 05bddc0697faa8d9d9955d89d76930c84ef7df0d..cbeaadaecf816070b3a37938c8e683180939afc4 100644 +--- a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java ++++ b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java +@@ -32,192 +32,41 @@ public final class ChunkSystem { + } + + public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) { +- level.chunkSource.mainThreadProcessor.execute(run); ++ level.chunkTaskScheduler.scheduleChunkTask(chunkX, chunkZ, run, priority); // Paper - rewrite chunk system + } + + public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen, + final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority, + final Consumer onComplete) { +- if (gen) { +- scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); +- return; +- } +- scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> { +- if (chunk == null) { +- onComplete.accept(null); +- } else { +- if (chunk.getStatus().isOrAfter(toStatus)) { +- scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); +- } else { +- onComplete.accept(null); +- } +- } +- }); ++ level.chunkTaskScheduler.scheduleChunkLoad(chunkX, chunkZ, gen, toStatus, addTicket, priority, onComplete); // Paper - rewrite chunk system + } + +- static final TicketType CHUNK_LOAD = TicketType.create("chunk_load", Long::compareTo); +- +- private static long chunkLoadCounter = 0L; ++ // Paper - rewrite chunk system + public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus, + final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) { +- if (!Bukkit.isPrimaryThread()) { +- scheduleChunkTask(level, chunkX, chunkZ, () -> { +- scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); +- }, priority); +- return; +- } +- +- final int minLevel = 33 + ChunkStatus.getDistance(toStatus); +- final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null; +- final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); +- +- if (addTicket) { +- level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); +- } +- level.chunkSource.runDistanceManagerUpdates(); +- +- final Consumer loadCallback = (final ChunkAccess chunk) -> { +- try { +- if (onComplete != null) { +- onComplete.accept(chunk); +- } +- } catch (final ThreadDeath death) { +- throw death; +- } catch (final Throwable thr) { +- LOGGER.error("Exception handling chunk load callback", thr); +- SneakyThrow.sneaky(thr); +- } finally { +- if (addTicket) { +- level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos); +- level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); +- } +- } +- }; +- +- final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); +- +- if (holder == null || holder.getTicketLevel() > minLevel) { +- loadCallback.accept(null); +- return; +- } +- +- final CompletableFuture> loadFuture = holder.getOrScheduleFuture(toStatus, level.chunkSource.chunkMap); +- +- if (loadFuture.isDone()) { +- loadCallback.accept(loadFuture.join().left().orElse(null)); +- return; +- } +- +- loadFuture.whenCompleteAsync((final Either either, final Throwable thr) -> { +- if (thr != null) { +- loadCallback.accept(null); +- return; +- } +- loadCallback.accept(either.left().orElse(null)); +- }, (final Runnable r) -> { +- scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); +- }); ++ level.chunkTaskScheduler.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); // Paper - rewrite chunk system + } + + public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ, + final FullChunkStatus toStatus, final boolean addTicket, + final PrioritisedExecutor.Priority priority, final Consumer onComplete) { +- // This method goes unused until the chunk system rewrite +- if (toStatus == FullChunkStatus.INACCESSIBLE) { +- throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status"); +- } +- +- if (!Bukkit.isPrimaryThread()) { +- scheduleChunkTask(level, chunkX, chunkZ, () -> { +- scheduleTickingState(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); +- }, priority); +- return; +- } +- +- final int minLevel = 33 - (toStatus.ordinal() - 1); +- final int radius = toStatus.ordinal() - 1; +- final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null; +- final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); +- +- if (addTicket) { +- level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); +- } +- level.chunkSource.runDistanceManagerUpdates(); +- +- final Consumer loadCallback = (final LevelChunk chunk) -> { +- try { +- if (onComplete != null) { +- onComplete.accept(chunk); +- } +- } catch (final ThreadDeath death) { +- throw death; +- } catch (final Throwable thr) { +- LOGGER.error("Exception handling chunk load callback", thr); +- SneakyThrow.sneaky(thr); +- } finally { +- if (addTicket) { +- level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos); +- level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); +- } +- } +- }; +- +- final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); +- +- if (holder == null || holder.getTicketLevel() > minLevel) { +- loadCallback.accept(null); +- return; +- } +- +- final CompletableFuture> tickingState; +- switch (toStatus) { +- case FULL: { +- tickingState = holder.getFullChunkFuture(); +- break; +- } +- case BLOCK_TICKING: { +- tickingState = holder.getTickingChunkFuture(); +- break; +- } +- case ENTITY_TICKING: { +- tickingState = holder.getEntityTickingChunkFuture(); +- break; +- } +- default: { +- throw new IllegalStateException("Cannot reach here"); +- } +- } +- +- if (tickingState.isDone()) { +- loadCallback.accept(tickingState.join().left().orElse(null)); +- return; +- } +- +- tickingState.whenCompleteAsync((final Either either, final Throwable thr) -> { +- if (thr != null) { +- loadCallback.accept(null); +- return; +- } +- loadCallback.accept(either.left().orElse(null)); +- }, (final Runnable r) -> { +- scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); +- }); ++ level.chunkTaskScheduler.scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); // Paper - rewrite chunk system + } + + public static List getVisibleChunkHolders(final ServerLevel level) { +- return new ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values()); ++ return level.chunkTaskScheduler.chunkHolderManager.getOldChunkHolders(); // Paper - rewrite chunk system + } + + public static List getUpdatingChunkHolders(final ServerLevel level) { +- return new ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values()); ++ return level.chunkTaskScheduler.chunkHolderManager.getOldChunkHolders(); // Paper - rewrite chunk system + } + + public static int getVisibleChunkHolderCount(final ServerLevel level) { +- return level.chunkSource.chunkMap.visibleChunkMap.size(); ++ return level.chunkTaskScheduler.chunkHolderManager.size(); // Paper - rewrite chunk system + } + + public static int getUpdatingChunkHolderCount(final ServerLevel level) { +- return level.chunkSource.chunkMap.updatingChunkMap.size(); ++ return level.chunkTaskScheduler.chunkHolderManager.size(); // Paper - rewrite chunk system + } + + public static boolean hasAnyChunkHolders(final ServerLevel level) { +@@ -244,26 +93,31 @@ public final class ChunkSystem { + + public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { + chunk.playerChunk = holder; ++ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.FULL; + } + + public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) { +- ++ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.INACCESSIBLE; + } + + public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) { + chunk.level.getChunkSource().tickingChunks.add(chunk); ++ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.BLOCK_TICKING; + } + + public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) { + chunk.level.getChunkSource().tickingChunks.remove(chunk); ++ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.FULL; + } + + public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { + chunk.level.getChunkSource().entityTickingChunks.add(chunk); ++ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING; + } + + public static void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { + chunk.level.getChunkSource().entityTickingChunks.remove(chunk); ++ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.BLOCK_TICKING; + } + + public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) { +@@ -271,23 +125,15 @@ public final class ChunkSystem { + } + + public static int getSendViewDistance(final ServerPlayer player) { +- return getLoadViewDistance(player); ++ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPISendViewDistance(player); + } + + public static int getLoadViewDistance(final ServerPlayer player) { +- final ServerLevel level = player.serverLevel(); +- if (level == null) { +- return Bukkit.getViewDistance(); +- } +- return level.chunkSource.chunkMap.getPlayerViewDistance(player); ++ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getLoadViewDistance(player); + } + + public static int getTickViewDistance(final ServerPlayer player) { +- final ServerLevel level = player.serverLevel(); +- if (level == null) { +- return Bukkit.getSimulationDistance(); +- } +- return level.chunkSource.chunkMap.distanceManager.simulationDistance; ++ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPITickViewDistance(player); + } + + private ChunkSystem() { +diff --git a/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1b090f1e79b996e52097afc49c1cec85936653e6 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java +@@ -0,0 +1,1208 @@ ++package io.papermc.paper.chunk.system; ++ ++import ca.spottedleaf.concurrentutil.collection.SRSWLinkedQueue; ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import io.papermc.paper.chunk.system.scheduling.ChunkHolderManager; ++import io.papermc.paper.configuration.GlobalConfiguration; ++import io.papermc.paper.util.CoordinateUtils; ++import io.papermc.paper.util.TickThread; ++import io.papermc.paper.util.player.SingleUserAreaMap; ++import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import it.unimi.dsi.fastutil.longs.LongComparator; ++import it.unimi.dsi.fastutil.longs.LongHeapPriorityQueue; ++import it.unimi.dsi.fastutil.longs.LongOpenHashSet; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; ++import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket; ++import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket; ++import net.minecraft.server.level.ChunkTrackingView; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.TicketType; ++import net.minecraft.server.network.PlayerChunkSender; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.GameRules; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.levelgen.BelowZeroRetrogen; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.entity.Player; ++import java.lang.invoke.VarHandle; ++import java.util.ArrayDeque; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.atomic.AtomicLong; ++ ++public class RegionizedPlayerChunkLoader { ++ ++ public static final TicketType REGION_PLAYER_TICKET = TicketType.create("region_player_ticket", Long::compareTo); ++ ++ public static final int MIN_VIEW_DISTANCE = 2; ++ public static final int MAX_VIEW_DISTANCE = 32; ++ ++ public static final int TICK_TICKET_LEVEL = 31; ++ public static final int GENERATED_TICKET_LEVEL = 33 + ChunkStatus.getDistance(ChunkStatus.FULL); ++ public static final int LOADED_TICKET_LEVEL = 33 + ChunkStatus.getDistance(ChunkStatus.EMPTY); ++ ++ public static final record ViewDistances( ++ int tickViewDistance, ++ int loadViewDistance, ++ int sendViewDistance ++ ) { ++ public ViewDistances setTickViewDistance(final int distance) { ++ return new ViewDistances(distance, this.loadViewDistance, this.sendViewDistance); ++ } ++ ++ public ViewDistances setLoadViewDistance(final int distance) { ++ return new ViewDistances(this.tickViewDistance, distance, this.sendViewDistance); ++ } ++ ++ ++ public ViewDistances setSendViewDistance(final int distance) { ++ return new ViewDistances(this.tickViewDistance, this.loadViewDistance, distance); ++ } ++ } ++ ++ public static int getAPITickViewDistance(final Player player) { ++ return getAPITickViewDistance(((CraftPlayer)player).getHandle()); ++ } ++ ++ public static int getAPITickViewDistance(final ServerPlayer player) { ++ final ServerLevel level = (ServerLevel)player.level(); ++ final PlayerChunkLoaderData data = player.chunkLoader; ++ if (data == null) { ++ return level.playerChunkLoader.getAPITickDistance(); ++ } ++ return data.lastTickDistance; ++ } ++ ++ public static int getAPIViewDistance(final Player player) { ++ return getAPIViewDistance(((CraftPlayer)player).getHandle()); ++ } ++ ++ public static int getAPIViewDistance(final ServerPlayer player) { ++ final ServerLevel level = (ServerLevel)player.level(); ++ final PlayerChunkLoaderData data = player.chunkLoader; ++ if (data == null) { ++ return level.playerChunkLoader.getAPIViewDistance(); ++ } ++ // view distance = load distance + 1 ++ return data.lastLoadDistance - 1; ++ } ++ ++ public static int getLoadViewDistance(final ServerPlayer player) { ++ final ServerLevel level = (ServerLevel)player.level(); ++ final PlayerChunkLoaderData data = player.chunkLoader; ++ if (data == null) { ++ return level.playerChunkLoader.getAPIViewDistance(); ++ } ++ // view distance = load distance + 1 ++ return data.lastLoadDistance - 1; ++ } ++ ++ public static int getAPISendViewDistance(final Player player) { ++ return getAPISendViewDistance(((CraftPlayer)player).getHandle()); ++ } ++ ++ public static int getAPISendViewDistance(final ServerPlayer player) { ++ final ServerLevel level = (ServerLevel)player.level(); ++ final PlayerChunkLoaderData data = player.chunkLoader; ++ if (data == null) { ++ return level.playerChunkLoader.getAPISendViewDistance(); ++ } ++ return data.lastSendDistance; ++ } ++ ++ private final ServerLevel world; ++ ++ public RegionizedPlayerChunkLoader(final ServerLevel world) { ++ this.world = world; ++ } ++ ++ public void addPlayer(final ServerPlayer player) { ++ TickThread.ensureTickThread(player, "Cannot add player to player chunk loader async"); ++ if (!player.isRealPlayer) { ++ return; ++ } ++ ++ if (player.chunkLoader != null) { ++ throw new IllegalStateException("Player is already added to player chunk loader"); ++ } ++ ++ final PlayerChunkLoaderData loader = new PlayerChunkLoaderData(this.world, player); ++ ++ player.chunkLoader = loader; ++ loader.add(); ++ } ++ ++ public void updatePlayer(final ServerPlayer player) { ++ final PlayerChunkLoaderData loader = player.chunkLoader; ++ if (loader != null) { ++ loader.update(); ++ } ++ } ++ ++ public void removePlayer(final ServerPlayer player) { ++ TickThread.ensureTickThread(player, "Cannot remove player from player chunk loader async"); ++ if (!player.isRealPlayer) { ++ return; ++ } ++ ++ final PlayerChunkLoaderData loader = player.chunkLoader; ++ ++ if (loader == null) { ++ return; ++ } ++ ++ loader.remove(); ++ player.chunkLoader = null; ++ } ++ ++ public void setSendDistance(final int distance) { ++ this.world.setSendViewDistance(distance); ++ } ++ ++ public void setLoadDistance(final int distance) { ++ this.world.setLoadViewDistance(distance); ++ } ++ ++ public void setTickDistance(final int distance) { ++ this.world.setTickViewDistance(distance); ++ } ++ ++ // Note: follow the player chunk loader so everything stays consistent... ++ public int getAPITickDistance() { ++ final ViewDistances distances = this.world.getViewDistances(); ++ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance); ++ return tickViewDistance; ++ } ++ ++ public int getAPIViewDistance() { ++ final ViewDistances distances = this.world.getViewDistances(); ++ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance); ++ final int loadDistance = PlayerChunkLoaderData.getLoadViewDistance(tickViewDistance, -1, distances.loadViewDistance); ++ ++ // loadDistance = api view distance + 1 ++ return loadDistance - 1; ++ } ++ ++ public int getAPISendViewDistance() { ++ final ViewDistances distances = this.world.getViewDistances(); ++ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance); ++ final int loadDistance = PlayerChunkLoaderData.getLoadViewDistance(tickViewDistance, -1, distances.loadViewDistance); ++ final int sendViewDistance = PlayerChunkLoaderData.getSendViewDistance( ++ loadDistance, -1, -1, distances.sendViewDistance ++ ); ++ ++ return sendViewDistance; ++ } ++ ++ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ, final boolean borderOnly) { ++ return borderOnly ? this.isChunkSentBorderOnly(player, chunkX, chunkZ) : this.isChunkSent(player, chunkX, chunkZ); ++ } ++ ++ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ) { ++ final PlayerChunkLoaderData loader = player.chunkLoader; ++ if (loader == null) { ++ return false; ++ } ++ ++ return loader.sentChunks.contains(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } ++ ++ public boolean isChunkSentBorderOnly(final ServerPlayer player, final int chunkX, final int chunkZ) { ++ final PlayerChunkLoaderData loader = player.chunkLoader; ++ if (loader == null) { ++ return false; ++ } ++ ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ if (!loader.sentChunks.contains(CoordinateUtils.getChunkKey(dx + chunkX, dz + chunkZ))) { ++ return true; ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ public void tick() { ++ TickThread.ensureTickThread("Cannot tick player chunk loader async"); ++ long currTime = System.nanoTime(); ++ for (final ServerPlayer player : new java.util.ArrayList<>(this.world.players())) { ++ final PlayerChunkLoaderData loader = player.chunkLoader; ++ if (loader == null || loader.world != this.world) { ++ // not our problem anymore ++ continue; ++ } ++ loader.update(); // can't invoke plugin logic ++ loader.updateQueues(currTime); ++ } ++ } ++ ++ private static long[] generateBFSOrder(final int radius) { ++ final LongArrayList chunks = new LongArrayList(); ++ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); ++ final LongOpenHashSet seen = new LongOpenHashSet(); ++ ++ seen.add(CoordinateUtils.getChunkKey(0, 0)); ++ queue.enqueue(CoordinateUtils.getChunkKey(0, 0)); ++ while (!queue.isEmpty()) { ++ final long chunk = queue.dequeueLong(); ++ final int chunkX = CoordinateUtils.getChunkX(chunk); ++ final int chunkZ = CoordinateUtils.getChunkZ(chunk); ++ ++ // important that the addition to the list is here, rather than when enqueueing neighbours ++ // ensures the order is actually kept ++ chunks.add(chunk); ++ ++ // -x ++ final long n1 = CoordinateUtils.getChunkKey(chunkX - 1, chunkZ); ++ // -z ++ final long n2 = CoordinateUtils.getChunkKey(chunkX, chunkZ - 1); ++ // +x ++ final long n3 = CoordinateUtils.getChunkKey(chunkX + 1, chunkZ); ++ // +z ++ final long n4 = CoordinateUtils.getChunkKey(chunkX, chunkZ + 1); ++ ++ final long[] list = new long[] {n1, n2, n3, n4}; ++ ++ for (final long neighbour : list) { ++ final int neighbourX = CoordinateUtils.getChunkX(neighbour); ++ final int neighbourZ = CoordinateUtils.getChunkZ(neighbour); ++ if (Math.max(Math.abs(neighbourX), Math.abs(neighbourZ)) > radius) { ++ // don't enqueue out of range ++ continue; ++ } ++ if (!seen.add(neighbour)) { ++ continue; ++ } ++ queue.enqueue(neighbour); ++ } ++ } ++ ++ // to increase generation parallelism, we want to space the chunks out so that they are not nearby when generating ++ // this also means we are minimising locality ++ // but, we need to maintain sorted order by manhatten distance ++ ++ // first, build a map of manhatten distance -> chunks ++ final java.util.List byDistance = new java.util.ArrayList<>(); ++ for (final it.unimi.dsi.fastutil.longs.LongIterator iterator = chunks.iterator(); iterator.hasNext();) { ++ final long chunkKey = iterator.nextLong(); ++ ++ final int chunkX = CoordinateUtils.getChunkX(chunkKey); ++ final int chunkZ = CoordinateUtils.getChunkZ(chunkKey); ++ ++ final int dist = Math.abs(chunkX) + Math.abs(chunkZ); ++ if (dist == byDistance.size()) { ++ final LongArrayList list = new LongArrayList(); ++ list.add(chunkKey); ++ byDistance.add(list); ++ continue; ++ } ++ ++ byDistance.get(dist).add(chunkKey); ++ } ++ ++ // per distance we transform the chunk list so that each element is maximally spaced out from each other ++ for (int i = 0, len = byDistance.size(); i < len; ++i) { ++ final LongArrayList notAdded = byDistance.get(i).clone(); ++ final LongArrayList added = new LongArrayList(); ++ ++ while (!notAdded.isEmpty()) { ++ if (added.isEmpty()) { ++ added.add(notAdded.removeLong(notAdded.size() - 1)); ++ continue; ++ } ++ ++ long maxChunk = -1L; ++ int maxDist = 0; ++ ++ // select the chunk from the not yet added set that has the largest minimum distance from ++ // the current set of added chunks ++ ++ for (final it.unimi.dsi.fastutil.longs.LongIterator iterator = notAdded.iterator(); iterator.hasNext();) { ++ final long chunkKey = iterator.nextLong(); ++ final int chunkX = CoordinateUtils.getChunkX(chunkKey); ++ final int chunkZ = CoordinateUtils.getChunkZ(chunkKey); ++ ++ int minDist = Integer.MAX_VALUE; ++ ++ for (final it.unimi.dsi.fastutil.longs.LongIterator iterator2 = added.iterator(); iterator2.hasNext();) { ++ final long addedKey = iterator2.nextLong(); ++ final int addedX = CoordinateUtils.getChunkX(addedKey); ++ final int addedZ = CoordinateUtils.getChunkZ(addedKey); ++ ++ // here we use square distance because chunk generation uses neighbours in a square radius ++ final int dist = Math.max(Math.abs(addedX - chunkX), Math.abs(addedZ - chunkZ)); ++ ++ if (dist < minDist) { ++ minDist = dist; ++ } ++ } ++ ++ if (minDist > maxDist) { ++ maxDist = minDist; ++ maxChunk = chunkKey; ++ } ++ } ++ ++ // move the selected chunk from the not added set to the added set ++ ++ if (!notAdded.rem(maxChunk)) { ++ throw new IllegalStateException(); ++ } ++ ++ added.add(maxChunk); ++ } ++ ++ byDistance.set(i, added); ++ } ++ ++ // now, rebuild the list so that it still maintains manhatten distance order ++ final LongArrayList ret = new LongArrayList(chunks.size()); ++ ++ for (final LongArrayList dist : byDistance) { ++ ret.addAll(dist); ++ } ++ ++ return ret.toLongArray(); ++ } ++ ++ public static final class PlayerChunkLoaderData { ++ ++ private static final AtomicLong ID_GENERATOR = new AtomicLong(); ++ private final long id = ID_GENERATOR.incrementAndGet(); ++ private final Long idBoxed = Long.valueOf(this.id); ++ ++ // expected that this list returns for a given radius, the set of chunks ordered ++ // by manhattan distance ++ private static final long[][] SEARCH_RADIUS_ITERATION_LIST = new long[65][]; ++ static { ++ for (int i = 0; i < SEARCH_RADIUS_ITERATION_LIST.length; ++i) { ++ // a BFS around -x, -z, +x, +z will give increasing manhatten distance ++ SEARCH_RADIUS_ITERATION_LIST[i] = generateBFSOrder(i); ++ } ++ } ++ ++ private static final long MAX_RATE = 10_000L; ++ ++ private final ServerPlayer player; ++ private final ServerLevel world; ++ ++ private int lastChunkX = Integer.MIN_VALUE; ++ private int lastChunkZ = Integer.MIN_VALUE; ++ ++ private int lastSendDistance = Integer.MIN_VALUE; ++ private int lastLoadDistance = Integer.MIN_VALUE; ++ private int lastTickDistance = Integer.MIN_VALUE; ++ ++ private int lastSentChunkCenterX = Integer.MIN_VALUE; ++ private int lastSentChunkCenterZ = Integer.MIN_VALUE; ++ ++ private int lastSentChunkRadius = Integer.MIN_VALUE; ++ private int lastSentSimulationDistance = Integer.MIN_VALUE; ++ ++ private boolean canGenerateChunks = true; ++ ++ private final ArrayDeque> delayedTicketOps = new ArrayDeque<>(); ++ private final LongOpenHashSet sentChunks = new LongOpenHashSet(); ++ ++ private static final byte CHUNK_TICKET_STAGE_NONE = 0; ++ private static final byte CHUNK_TICKET_STAGE_LOADING = 1; ++ private static final byte CHUNK_TICKET_STAGE_LOADED = 2; ++ private static final byte CHUNK_TICKET_STAGE_GENERATING = 3; ++ private static final byte CHUNK_TICKET_STAGE_GENERATED = 4; ++ private static final byte CHUNK_TICKET_STAGE_TICK = 5; ++ private static final int[] TICKET_STAGE_TO_LEVEL = new int[] { ++ ChunkHolderManager.MAX_TICKET_LEVEL + 1, ++ LOADED_TICKET_LEVEL, ++ LOADED_TICKET_LEVEL, ++ GENERATED_TICKET_LEVEL, ++ GENERATED_TICKET_LEVEL, ++ TICK_TICKET_LEVEL ++ }; ++ private final Long2ByteOpenHashMap chunkTicketStage = new Long2ByteOpenHashMap(); ++ { ++ this.chunkTicketStage.defaultReturnValue(CHUNK_TICKET_STAGE_NONE); ++ } ++ ++ // rate limiting ++ private final AllocatingRateLimiter chunkSendLimiter = new AllocatingRateLimiter(); ++ private final AllocatingRateLimiter chunkLoadTicketLimiter = new AllocatingRateLimiter(); ++ private final AllocatingRateLimiter chunkGenerateTicketLimiter = new AllocatingRateLimiter(); ++ ++ // queues ++ private final LongComparator CLOSEST_MANHATTAN_DIST = (final long c1, final long c2) -> { ++ final int c1x = CoordinateUtils.getChunkX(c1); ++ final int c1z = CoordinateUtils.getChunkZ(c1); ++ ++ final int c2x = CoordinateUtils.getChunkX(c2); ++ final int c2z = CoordinateUtils.getChunkZ(c2); ++ ++ final int centerX = PlayerChunkLoaderData.this.lastChunkX; ++ final int centerZ = PlayerChunkLoaderData.this.lastChunkZ; ++ ++ return Integer.compare( ++ Math.abs(c1x - centerX) + Math.abs(c1z - centerZ), ++ Math.abs(c2x - centerX) + Math.abs(c2z - centerZ) ++ ); ++ }; ++ private final LongHeapPriorityQueue sendQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); ++ private final LongHeapPriorityQueue tickingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); ++ private final LongHeapPriorityQueue generatingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); ++ private final LongHeapPriorityQueue genQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); ++ private final LongHeapPriorityQueue loadingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); ++ private final LongHeapPriorityQueue loadQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); ++ ++ private volatile boolean removed; ++ ++ public PlayerChunkLoaderData(final ServerLevel world, final ServerPlayer player) { ++ this.world = world; ++ this.player = player; ++ } ++ ++ private void flushDelayedTicketOps() { ++ if (this.delayedTicketOps.isEmpty()) { ++ return; ++ } ++ this.world.chunkTaskScheduler.chunkHolderManager.performTicketUpdates(this.delayedTicketOps); ++ this.delayedTicketOps.clear(); ++ } ++ ++ private void pushDelayedTicketOp(final ChunkHolderManager.TicketOperation op) { ++ this.delayedTicketOps.addLast(op); ++ } ++ ++ private void sendChunk(final int chunkX, final int chunkZ) { ++ if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { ++ PlayerChunkSender.sendChunk(this.player.connection, this.world, this.world.getChunkIfLoaded(chunkX, chunkZ)); ++ return; ++ } ++ throw new IllegalStateException(); ++ } ++ ++ private void sendUnloadChunk(final int chunkX, final int chunkZ) { ++ if (!this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { ++ return; ++ } ++ this.sendUnloadChunkRaw(chunkX, chunkZ); ++ } ++ ++ private void sendUnloadChunkRaw(final int chunkX, final int chunkZ) { ++ PlayerChunkSender.dropChunkStatic(this.player, new ChunkPos(chunkX, chunkZ)); ++ } ++ ++ private final SingleUserAreaMap broadcastMap = new SingleUserAreaMap<>(this) { ++ @Override ++ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { ++ // do nothing, we only care about remove ++ } ++ ++ @Override ++ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { ++ parameter.sendUnloadChunk(chunkX, chunkZ); ++ } ++ }; ++ private final SingleUserAreaMap loadTicketCleanup = new SingleUserAreaMap<>(this) { ++ @Override ++ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { ++ // do nothing, we only care about remove ++ } ++ ++ @Override ++ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { ++ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ final byte ticketStage = parameter.chunkTicketStage.remove(chunk); ++ final int level = TICKET_STAGE_TO_LEVEL[ticketStage]; ++ if (level > ChunkHolderManager.MAX_TICKET_LEVEL) { ++ return; ++ } ++ ++ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addAndRemove( ++ chunk, ++ TicketType.UNKNOWN, level, new ChunkPos(chunkX, chunkZ), ++ REGION_PLAYER_TICKET, level, parameter.idBoxed ++ )); ++ } ++ }; ++ private final SingleUserAreaMap tickMap = new SingleUserAreaMap<>(this) { ++ @Override ++ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { ++ // do nothing, we will detect ticking chunks when we try to load them ++ } ++ ++ @Override ++ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { ++ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ // note: by the time this is called, the tick cleanup should have ran - so, if the chunk is at ++ // the tick stage it was deemed in range for loading. Thus, we need to move it to generated ++ if (!parameter.chunkTicketStage.replace(chunk, CHUNK_TICKET_STAGE_TICK, CHUNK_TICKET_STAGE_GENERATED)) { ++ return; ++ } ++ ++ // Since we are possibly downgrading the ticket level, we add an unknown ticket so that ++ // the level is kept until tick(). ++ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addAndRemove( ++ chunk, ++ TicketType.UNKNOWN, TICK_TICKET_LEVEL, new ChunkPos(chunkX, chunkZ), ++ REGION_PLAYER_TICKET, TICK_TICKET_LEVEL, parameter.idBoxed ++ )); ++ // keep chunk at new generated level ++ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addOp( ++ chunk, ++ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, parameter.idBoxed ++ )); ++ } ++ }; ++ ++ private static boolean wantChunkLoaded(final int centerX, final int centerZ, final int chunkX, final int chunkZ, ++ final int sendRadius) { ++ // expect sendRadius to be = 1 + target viewable radius ++ return ChunkTrackingView.isWithinDistance(centerX, centerZ, sendRadius, chunkX, chunkZ, true); ++ } ++ ++ private static int getClientViewDistance(final ServerPlayer player) { ++ final Integer vd = player.requestedViewDistance(); ++ return vd == null ? -1 : Math.max(0, vd.intValue()); ++ } ++ ++ private static int getTickDistance(final int playerTickViewDistance, final int worldTickViewDistance) { ++ return playerTickViewDistance < 0 ? worldTickViewDistance : playerTickViewDistance; ++ } ++ ++ private static int getLoadViewDistance(final int tickViewDistance, final int playerLoadViewDistance, ++ final int worldLoadViewDistance) { ++ return Math.max(tickViewDistance + 1, playerLoadViewDistance < 0 ? worldLoadViewDistance : playerLoadViewDistance); ++ } ++ ++ private static int getSendViewDistance(final int loadViewDistance, final int clientViewDistance, ++ final int playerSendViewDistance, final int worldSendViewDistance) { ++ return Math.min( ++ loadViewDistance - 1, ++ playerSendViewDistance < 0 ? (!GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance || clientViewDistance < 0 ? (worldSendViewDistance < 0 ? (loadViewDistance - 1) : worldSendViewDistance) : clientViewDistance + 1) : playerSendViewDistance ++ ); ++ } ++ ++ private Packet updateClientChunkRadius(final int radius) { ++ this.lastSentChunkRadius = radius; ++ return new ClientboundSetChunkCacheRadiusPacket(radius); ++ } ++ ++ private Packet updateClientSimulationDistance(final int distance) { ++ this.lastSentSimulationDistance = distance; ++ return new ClientboundSetSimulationDistancePacket(distance); ++ } ++ ++ private Packet updateClientChunkCenter(final int chunkX, final int chunkZ) { ++ this.lastSentChunkCenterX = chunkX; ++ this.lastSentChunkCenterZ = chunkZ; ++ return new ClientboundSetChunkCacheCenterPacket(chunkX, chunkZ); ++ } ++ ++ private boolean canPlayerGenerateChunks() { ++ return !this.player.isSpectator() || this.world.getGameRules().getBoolean(GameRules.RULE_SPECTATORSGENERATECHUNKS); ++ } ++ ++ private double getMaxChunkLoadRate() { ++ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate; ++ ++ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); ++ } ++ ++ private double getMaxChunkGenRate() { ++ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate; ++ ++ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); ++ } ++ ++ private double getMaxChunkSendRate() { ++ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate; ++ ++ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); ++ } ++ ++ private long getMaxChunkLoads() { ++ final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L); ++ long configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads; ++ if (configLimit == 0L) { ++ // by default, only allow 1/5th of the chunks in the view distance to be concurrently active ++ configLimit = Math.max(5L, radiusChunks / 5L); ++ } else if (configLimit < 0L) { ++ configLimit = Integer.MAX_VALUE; ++ } // else: use the value configured ++ configLimit = configLimit - this.loadingQueue.size(); ++ ++ return configLimit; ++ } ++ ++ private long getMaxChunkGenerates() { ++ final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L); ++ long configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates; ++ if (configLimit == 0L) { ++ // by default, only allow 1/5th of the chunks in the view distance to be concurrently active ++ configLimit = Math.max(5L, radiusChunks / 5L); ++ } else if (configLimit < 0L) { ++ configLimit = Integer.MAX_VALUE; ++ } // else: use the value configured ++ configLimit = configLimit - this.generatingQueue.size(); ++ ++ return configLimit; ++ } ++ ++ private boolean wantChunkSent(final int chunkX, final int chunkZ) { ++ final int dx = this.lastChunkX - chunkX; ++ final int dz = this.lastChunkZ - chunkZ; ++ return (Math.max(Math.abs(dx), Math.abs(dz)) <= (this.lastSendDistance + 1)) && wantChunkLoaded( ++ this.lastChunkX, this.lastChunkZ, chunkX, chunkZ, this.lastSendDistance ++ ); ++ } ++ ++ private boolean wantChunkTicked(final int chunkX, final int chunkZ) { ++ final int dx = this.lastChunkX - chunkX; ++ final int dz = this.lastChunkZ - chunkZ; ++ return Math.max(Math.abs(dx), Math.abs(dz)) <= this.lastTickDistance; ++ } ++ ++ void updateQueues(final long time) { ++ TickThread.ensureTickThread(this.player, "Cannot tick player chunk loader async"); ++ if (this.removed) { ++ throw new IllegalStateException("Ticking removed player chunk loader"); ++ } ++ // update rate limits ++ final double loadRate = this.getMaxChunkLoadRate(); ++ final double genRate = this.getMaxChunkGenRate(); ++ final double sendRate = this.getMaxChunkSendRate(); ++ ++ this.chunkLoadTicketLimiter.tickAllocation(time, loadRate, loadRate); ++ this.chunkGenerateTicketLimiter.tickAllocation(time, genRate, genRate); ++ this.chunkSendLimiter.tickAllocation(time, sendRate, sendRate); ++ ++ // try to progress chunk loads ++ while (!this.loadingQueue.isEmpty()) { ++ final long pendingLoadChunk = this.loadingQueue.firstLong(); ++ final int pendingChunkX = CoordinateUtils.getChunkX(pendingLoadChunk); ++ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingLoadChunk); ++ final ChunkAccess pending = this.world.chunkSource.getChunkAtImmediately(pendingChunkX, pendingChunkZ); ++ if (pending == null) { ++ // nothing to do here ++ break; ++ } ++ // chunk has loaded, so we can take it out of the queue ++ this.loadingQueue.dequeueLong(); ++ ++ // try to move to generate queue ++ final byte prev = this.chunkTicketStage.put(pendingLoadChunk, CHUNK_TICKET_STAGE_LOADED); ++ if (prev != CHUNK_TICKET_STAGE_LOADING) { ++ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_LOADING + ", not " + prev); ++ } ++ ++ if (this.canGenerateChunks || this.isLoadedChunkGeneratable(pending)) { ++ this.genQueue.enqueue(pendingLoadChunk); ++ } // else: don't want to generate, so just leave it loaded ++ } ++ ++ // try to push more chunk loads ++ final long maxLoads = Math.max(0L, Math.min(MAX_RATE, Math.min(this.loadQueue.size(), this.getMaxChunkLoads()))); ++ final int maxLoadsThisTick = (int)this.chunkLoadTicketLimiter.takeAllocation(time, loadRate, maxLoads); ++ if (maxLoadsThisTick > 0) { ++ final LongArrayList chunks = new LongArrayList(maxLoadsThisTick); ++ for (int i = 0; i < maxLoadsThisTick; ++i) { ++ final long chunk = this.loadQueue.dequeueLong(); ++ final byte prev = this.chunkTicketStage.put(chunk, CHUNK_TICKET_STAGE_LOADING); ++ if (prev != CHUNK_TICKET_STAGE_NONE) { ++ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_NONE + ", not " + prev); ++ } ++ this.pushDelayedTicketOp( ++ ChunkHolderManager.TicketOperation.addOp( ++ chunk, ++ REGION_PLAYER_TICKET, LOADED_TICKET_LEVEL, this.idBoxed ++ ) ++ ); ++ chunks.add(chunk); ++ this.loadingQueue.enqueue(chunk); ++ } ++ ++ // here we need to flush tickets, as scheduleChunkLoad requires tickets to be propagated with addTicket = false ++ this.flushDelayedTicketOps(); ++ // we only need to call scheduleChunkLoad because the loaded ticket level is not enough to start the chunk ++ // load - only generate ticket levels start anything, but they start generation... ++ // propagate levels ++ // Note: this CAN call plugin logic, so it is VITAL that our bookkeeping logic is completely done by the time this is invoked ++ this.world.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(); ++ ++ if (this.removed) { ++ // process ticket updates may invoke plugin logic, which may remove this player ++ return; ++ } ++ ++ for (int i = 0; i < maxLoadsThisTick; ++i) { ++ final long queuedLoadChunk = chunks.getLong(i); ++ final int queuedChunkX = CoordinateUtils.getChunkX(queuedLoadChunk); ++ final int queuedChunkZ = CoordinateUtils.getChunkZ(queuedLoadChunk); ++ this.world.chunkTaskScheduler.scheduleChunkLoad( ++ queuedChunkX, queuedChunkZ, ChunkStatus.EMPTY, false, PrioritisedExecutor.Priority.NORMAL, null ++ ); ++ if (this.removed) { ++ return; ++ } ++ } ++ } ++ ++ // try to progress chunk generations ++ while (!this.generatingQueue.isEmpty()) { ++ final long pendingGenChunk = this.generatingQueue.firstLong(); ++ final int pendingChunkX = CoordinateUtils.getChunkX(pendingGenChunk); ++ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingGenChunk); ++ final LevelChunk pending = this.world.chunkSource.getChunkAtIfLoadedMainThreadNoCache(pendingChunkX, pendingChunkZ); ++ if (pending == null) { ++ // nothing to do here ++ break; ++ } ++ ++ // chunk has generated, so we can take it out of queue ++ this.generatingQueue.dequeueLong(); ++ ++ final byte prev = this.chunkTicketStage.put(pendingGenChunk, CHUNK_TICKET_STAGE_GENERATED); ++ if (prev != CHUNK_TICKET_STAGE_GENERATING) { ++ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_GENERATING + ", not " + prev); ++ } ++ ++ // try to move to send queue ++ if (this.wantChunkSent(pendingChunkX, pendingChunkZ)) { ++ this.sendQueue.enqueue(pendingGenChunk); ++ } ++ // try to move to tick queue ++ if (this.wantChunkTicked(pendingChunkX, pendingChunkZ)) { ++ this.tickingQueue.enqueue(pendingGenChunk); ++ } ++ } ++ ++ // try to push more chunk generations ++ final long maxGens = Math.max(0L, Math.min(MAX_RATE, Math.min(this.genQueue.size(), this.getMaxChunkGenerates()))); ++ final int maxGensThisTick = (int)this.chunkGenerateTicketLimiter.takeAllocation(time, genRate, maxGens); ++ int ratedGensThisTick = 0; ++ while (!this.genQueue.isEmpty()) { ++ final long chunkKey = this.genQueue.firstLong(); ++ final int chunkX = CoordinateUtils.getChunkX(chunkKey); ++ final int chunkZ = CoordinateUtils.getChunkZ(chunkKey); ++ final ChunkAccess chunk = this.world.chunkSource.getChunkAtImmediately(chunkX, chunkZ); ++ if (chunk.getStatus() != ChunkStatus.FULL) { ++ // only rate limit actual generations ++ if ((ratedGensThisTick + 1) > maxGensThisTick) { ++ break; ++ } ++ ++ratedGensThisTick; ++ } ++ ++ this.genQueue.dequeueLong(); ++ ++ final byte prev = this.chunkTicketStage.put(chunkKey, CHUNK_TICKET_STAGE_GENERATING); ++ if (prev != CHUNK_TICKET_STAGE_LOADED) { ++ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_LOADED + ", not " + prev); ++ } ++ this.pushDelayedTicketOp( ++ ChunkHolderManager.TicketOperation.addAndRemove( ++ chunkKey, ++ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, this.idBoxed, ++ REGION_PLAYER_TICKET, LOADED_TICKET_LEVEL, this.idBoxed ++ ) ++ ); ++ this.generatingQueue.enqueue(chunkKey); ++ } ++ ++ // try to pull ticking chunks ++ tick_check_outer: ++ while (!this.tickingQueue.isEmpty()) { ++ final long pendingTicking = this.tickingQueue.firstLong(); ++ final int pendingChunkX = CoordinateUtils.getChunkX(pendingTicking); ++ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingTicking); ++ ++ final int tickingReq = 2; ++ for (int dz = -tickingReq; dz <= tickingReq; ++dz) { ++ for (int dx = -tickingReq; dx <= tickingReq; ++dx) { ++ if ((dx | dz) == 0) { ++ continue; ++ } ++ final long neighbour = CoordinateUtils.getChunkKey(dx + pendingChunkX, dz + pendingChunkZ); ++ final byte stage = this.chunkTicketStage.get(neighbour); ++ if (stage != CHUNK_TICKET_STAGE_GENERATED && stage != CHUNK_TICKET_STAGE_TICK) { ++ break tick_check_outer; ++ } ++ } ++ } ++ // only gets here if all neighbours were marked as generated or ticking themselves ++ this.tickingQueue.dequeueLong(); ++ this.pushDelayedTicketOp( ++ ChunkHolderManager.TicketOperation.addAndRemove( ++ pendingTicking, ++ REGION_PLAYER_TICKET, TICK_TICKET_LEVEL, this.idBoxed, ++ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, this.idBoxed ++ ) ++ ); ++ // there is no queue to add after ticking ++ final byte prev = this.chunkTicketStage.put(pendingTicking, CHUNK_TICKET_STAGE_TICK); ++ if (prev != CHUNK_TICKET_STAGE_GENERATED) { ++ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_GENERATED + ", not " + prev); ++ } ++ } ++ ++ // try to pull sending chunks ++ final long maxSends = Math.max(0L, Math.min(MAX_RATE, Integer.MAX_VALUE)); // no logic to track concurrent sends ++ final int maxSendsThisTick = Math.min((int)this.chunkSendLimiter.takeAllocation(time, sendRate, maxSends), this.sendQueue.size()); ++ // we do not return sends that we took from the allocation back because we want to limit the max send rate, not target it ++ for (int i = 0; i < maxSendsThisTick; ++i) { ++ final long pendingSend = this.sendQueue.firstLong(); ++ final int pendingSendX = CoordinateUtils.getChunkX(pendingSend); ++ final int pendingSendZ = CoordinateUtils.getChunkZ(pendingSend); ++ final LevelChunk chunk = this.world.chunkSource.getChunkAtIfLoadedMainThreadNoCache(pendingSendX, pendingSendZ); ++ if (!chunk.areNeighboursLoaded(1) || !TickThread.isTickThreadFor(this.world, pendingSendX, pendingSendZ)) { ++ // nothing to do ++ // the target chunk may not be owned by this region, but this should be resolved in the future ++ break; ++ } ++ if (!chunk.isPostProcessingDone) { ++ // not yet post-processed, need to do this so that tile entities can properly be sent to clients ++ chunk.postProcessGeneration(); ++ // check if there was any recursive action ++ if (this.removed || this.sendQueue.isEmpty() || this.sendQueue.firstLong() != pendingSend) { ++ return; ++ } // else: good to dequeue and send, fall through ++ } ++ this.sendQueue.dequeueLong(); ++ ++ this.sendChunk(pendingSendX, pendingSendZ); ++ if (this.removed) { ++ // sendChunk may invoke plugin logic ++ return; ++ } ++ } ++ ++ this.flushDelayedTicketOps(); ++ // we assume propagate ticket levels happens after this call ++ } ++ ++ void add() { ++ TickThread.ensureTickThread(this.player, "Cannot add player asynchronously"); ++ if (this.removed) { ++ throw new IllegalStateException("Adding removed player chunk loader"); ++ } ++ final ViewDistances playerDistances = this.player.getViewDistances(); ++ final ViewDistances worldDistances = this.world.getViewDistances(); ++ final int chunkX = this.player.chunkPosition().x; ++ final int chunkZ = this.player.chunkPosition().z; ++ ++ final int tickViewDistance = getTickDistance(playerDistances.tickViewDistance, worldDistances.tickViewDistance); ++ // load view cannot be less-than tick view + 1 ++ final int loadViewDistance = getLoadViewDistance(tickViewDistance, playerDistances.loadViewDistance, worldDistances.loadViewDistance); ++ // send view cannot be greater-than load view ++ final int clientViewDistance = getClientViewDistance(this.player); ++ final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance); ++ ++ // send view distances ++ this.player.connection.send(this.updateClientChunkRadius(sendViewDistance)); ++ this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance)); ++ ++ // add to distance maps ++ this.broadcastMap.add(chunkX, chunkZ, sendViewDistance + 1); ++ this.loadTicketCleanup.add(chunkX, chunkZ, loadViewDistance + 1); ++ this.tickMap.add(chunkX, chunkZ, tickViewDistance); ++ ++ // update chunk center ++ this.player.connection.send(this.updateClientChunkCenter(chunkX, chunkZ)); ++ ++ // now we can update ++ this.update(); ++ } ++ ++ private boolean isLoadedChunkGeneratable(final int chunkX, final int chunkZ) { ++ return this.isLoadedChunkGeneratable(this.world.chunkSource.getChunkAtImmediately(chunkX, chunkZ)); ++ } ++ ++ private boolean isLoadedChunkGeneratable(final ChunkAccess chunkAccess) { ++ final BelowZeroRetrogen belowZeroRetrogen; ++ // see PortalForcer#findPortalAround ++ return chunkAccess != null && ( ++ chunkAccess.getStatus() == ChunkStatus.FULL || ++ ((belowZeroRetrogen = chunkAccess.getBelowZeroRetrogen()) != null && belowZeroRetrogen.targetStatus().isOrAfter(ChunkStatus.SPAWN)) ++ ); ++ } ++ ++ void update() { ++ TickThread.ensureTickThread(this.player, "Cannot update player asynchronously"); ++ if (this.removed) { ++ throw new IllegalStateException("Updating removed player chunk loader"); ++ } ++ final ViewDistances playerDistances = this.player.getViewDistances(); ++ final ViewDistances worldDistances = this.world.getViewDistances(); ++ ++ final int tickViewDistance = getTickDistance(playerDistances.tickViewDistance, worldDistances.tickViewDistance); ++ // load view cannot be less-than tick view + 1 ++ final int loadViewDistance = getLoadViewDistance(tickViewDistance, playerDistances.loadViewDistance, worldDistances.loadViewDistance); ++ // send view cannot be greater-than load view ++ final int clientViewDistance = getClientViewDistance(this.player); ++ final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance); ++ ++ final ChunkPos playerPos = this.player.chunkPosition(); ++ final boolean canGenerateChunks = this.canPlayerGenerateChunks(); ++ final int currentChunkX = playerPos.x; ++ final int currentChunkZ = playerPos.z; ++ ++ final int prevChunkX = this.lastChunkX; ++ final int prevChunkZ = this.lastChunkZ; ++ ++ if ( ++ // has view distance stayed the same? ++ sendViewDistance == this.lastSendDistance ++ && loadViewDistance == this.lastLoadDistance ++ && tickViewDistance == this.lastTickDistance ++ ++ // has our chunk stayed the same? ++ && prevChunkX == currentChunkX ++ && prevChunkZ == currentChunkZ ++ ++ // can we still generate chunks? ++ && this.canGenerateChunks == canGenerateChunks ++ ) { ++ // nothing we care about changed, so we're not re-calculating ++ return; ++ } ++ ++ // update distance maps ++ this.broadcastMap.update(currentChunkX, currentChunkZ, sendViewDistance + 1); ++ this.loadTicketCleanup.update(currentChunkX, currentChunkZ, loadViewDistance + 1); ++ this.tickMap.update(currentChunkX, currentChunkZ, tickViewDistance); ++ if (sendViewDistance > loadViewDistance || tickViewDistance > loadViewDistance) { ++ throw new IllegalStateException(); ++ } ++ ++ // update VDs for client ++ // this should be after the distance map updates, as they will send unload packets ++ if (this.lastSentChunkRadius != sendViewDistance) { ++ this.player.connection.send(this.updateClientChunkRadius(sendViewDistance)); ++ } ++ if (this.lastSentSimulationDistance != tickViewDistance) { ++ this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance)); ++ } ++ ++ this.sendQueue.clear(); ++ this.tickingQueue.clear(); ++ this.generatingQueue.clear(); ++ this.genQueue.clear(); ++ this.loadingQueue.clear(); ++ this.loadQueue.clear(); ++ ++ this.lastChunkX = currentChunkX; ++ this.lastChunkZ = currentChunkZ; ++ this.lastSendDistance = sendViewDistance; ++ this.lastLoadDistance = loadViewDistance; ++ this.lastTickDistance = tickViewDistance; ++ this.canGenerateChunks = canGenerateChunks; ++ ++ // +1 since we need to load chunks +1 around the load view distance... ++ final long[] toIterate = SEARCH_RADIUS_ITERATION_LIST[loadViewDistance + 1]; ++ // the iteration order is by increasing manhattan distance - so, we do NOT need to ++ // sort anything in the queue! ++ for (final long deltaChunk : toIterate) { ++ final int dx = CoordinateUtils.getChunkX(deltaChunk); ++ final int dz = CoordinateUtils.getChunkZ(deltaChunk); ++ final int chunkX = dx + currentChunkX; ++ final int chunkZ = dz + currentChunkZ; ++ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz)); ++ final int manhattanDistance = Math.abs(dx) + Math.abs(dz); ++ ++ // since chunk sending is not by radius alone, we need an extra check here to account for ++ // everything <= sendDistance ++ // Note: Vanilla may want to send chunks outside the send view distance, so we do need ++ // the dist <= view check ++ final boolean sendChunk = (squareDistance <= (sendViewDistance + 1)) ++ && wantChunkLoaded(currentChunkX, currentChunkZ, chunkX, chunkZ, sendViewDistance); ++ final boolean sentChunk = sendChunk ? this.sentChunks.contains(chunk) : this.sentChunks.remove(chunk); ++ ++ if (!sendChunk && sentChunk) { ++ // have sent the chunk, but don't want it anymore ++ // unload it now ++ this.sendUnloadChunkRaw(chunkX, chunkZ); ++ } ++ ++ final byte stage = this.chunkTicketStage.get(chunk); ++ switch (stage) { ++ case CHUNK_TICKET_STAGE_NONE: { ++ // we want the chunk to be at least loaded ++ this.loadQueue.enqueue(chunk); ++ break; ++ } ++ case CHUNK_TICKET_STAGE_LOADING: { ++ this.loadingQueue.enqueue(chunk); ++ break; ++ } ++ case CHUNK_TICKET_STAGE_LOADED: { ++ if (canGenerateChunks || this.isLoadedChunkGeneratable(chunkX, chunkZ)) { ++ this.genQueue.enqueue(chunk); ++ } ++ break; ++ } ++ case CHUNK_TICKET_STAGE_GENERATING: { ++ this.generatingQueue.enqueue(chunk); ++ break; ++ } ++ case CHUNK_TICKET_STAGE_GENERATED: { ++ if (sendChunk && !sentChunk) { ++ this.sendQueue.enqueue(chunk); ++ } ++ if (squareDistance <= tickViewDistance) { ++ this.tickingQueue.enqueue(chunk); ++ } ++ break; ++ } ++ case CHUNK_TICKET_STAGE_TICK: { ++ if (sendChunk && !sentChunk) { ++ this.sendQueue.enqueue(chunk); ++ } ++ break; ++ } ++ default: { ++ throw new IllegalStateException("Unknown stage: " + stage); ++ } ++ } ++ } ++ ++ // update the chunk center ++ // this must be done last so that the client does not ignore any of our unload chunk packets above ++ if (this.lastSentChunkCenterX != currentChunkX || this.lastSentChunkCenterZ != currentChunkZ) { ++ this.player.connection.send(this.updateClientChunkCenter(currentChunkX, currentChunkZ)); ++ } ++ ++ this.flushDelayedTicketOps(); ++ } ++ ++ void remove() { ++ TickThread.ensureTickThread(this.player, "Cannot add player asynchronously"); ++ if (this.removed) { ++ throw new IllegalStateException("Removing removed player chunk loader"); ++ } ++ this.removed = true; ++ // sends the chunk unload packets ++ this.broadcastMap.remove(); ++ // cleans up loading/generating tickets ++ this.loadTicketCleanup.remove(); ++ // cleans up ticking tickets ++ this.tickMap.remove(); ++ ++ // purge queues ++ this.sendQueue.clear(); ++ this.tickingQueue.clear(); ++ this.generatingQueue.clear(); ++ this.genQueue.clear(); ++ this.loadingQueue.clear(); ++ this.loadQueue.clear(); ++ ++ // flush ticket changes ++ this.flushDelayedTicketOps(); ++ ++ // now all tickets should be removed, which is all of our external state ++ } ++ } ++ ++ // TODO rebase into util patch ++ private static final class AllocatingRateLimiter { ++ ++ // max difference granularity in ns ++ private static final long MAX_GRANULARITY = TimeUnit.SECONDS.toNanos(1L); ++ ++ private double allocation; ++ private long lastAllocationUpdate; ++ private double takeCarry; ++ private long lastTakeUpdate; ++ ++ // rate in units/s, and time in ns ++ public void tickAllocation(final long time, final double rate, final double maxAllocation) { ++ final long diff = Math.min(MAX_GRANULARITY, time - this.lastAllocationUpdate); ++ this.lastAllocationUpdate = time; ++ ++ this.allocation = Math.min(maxAllocation - this.takeCarry, this.allocation + rate * (diff*1.0E-9D)); ++ } ++ ++ // rate in units/s, and time in ns ++ public long takeAllocation(final long time, final double rate, final long maxTake) { ++ if (maxTake < 1L) { ++ return 0L; ++ } ++ ++ double ret = this.takeCarry; ++ final long diff = Math.min(MAX_GRANULARITY, time - this.lastTakeUpdate); ++ this.lastTakeUpdate = time; ++ ++ // note: abs(takeCarry) <= 1.0 ++ final double take = Math.min(Math.min((double)maxTake - this.takeCarry, this.allocation), rate * (diff*1.0E-9)); ++ ++ ret += take; ++ this.allocation -= take; ++ ++ final long retInteger = (long)Math.floor(ret); ++ this.takeCarry = ret - (double)retInteger; ++ ++ return retInteger; ++ } ++ } ++ ++ static final class CountedSRSWLinkedQueue { ++ ++ private final SRSWLinkedQueue queue = new SRSWLinkedQueue<>(); ++ private volatile long countAdded; ++ private volatile long countRemoved; ++ ++ private static final VarHandle COUNT_ADDED_HANDLE = ConcurrentUtil.getVarHandle(CountedSRSWLinkedQueue.class, "countAdded", long.class); ++ private static final VarHandle COUNT_REMOVED_HANDLE = ConcurrentUtil.getVarHandle(CountedSRSWLinkedQueue.class, "countRemoved", long.class); ++ ++ private long getCountAddedPlain() { ++ return (long)COUNT_ADDED_HANDLE.get(this); ++ } ++ ++ private long getCountAddedAcquire() { ++ return (long)COUNT_ADDED_HANDLE.getAcquire(this); ++ } ++ ++ private void setCountAddedRelease(final long to) { ++ COUNT_ADDED_HANDLE.setRelease(this, to); ++ } ++ ++ private long getCountRemovedPlain() { ++ return (long)COUNT_REMOVED_HANDLE.get(this); ++ } ++ ++ private long getCountRemovedAcquire() { ++ return (long)COUNT_REMOVED_HANDLE.getAcquire(this); ++ } ++ ++ private void setCountRemovedRelease(final long to) { ++ COUNT_REMOVED_HANDLE.setRelease(this, to); ++ } ++ ++ public void add(final E element) { ++ this.setCountAddedRelease(this.getCountAddedPlain() + 1L); ++ this.queue.addLast(element); ++ } ++ ++ public E poll() { ++ final E ret = this.queue.poll(); ++ if (ret != null) { ++ this.setCountRemovedRelease(this.getCountRemovedPlain() + 1L); ++ } ++ ++ return ret; ++ } ++ ++ public long size() { ++ final long removed = this.getCountRemovedAcquire(); ++ final long added = this.getCountAddedAcquire(); ++ ++ return added - removed; ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..15ee41452992714108efe53b708b5a4e1da7c1ff +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java +@@ -0,0 +1,902 @@ ++package io.papermc.paper.chunk.system.entity; ++ ++import com.destroystokyo.paper.util.maplist.EntityList; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.util.CoordinateUtils; ++import io.papermc.paper.util.TickThread; ++import io.papermc.paper.util.WorldUtil; ++import io.papermc.paper.world.ChunkEntitySlices; ++import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; ++import net.minecraft.core.BlockPos; ++import io.papermc.paper.chunk.system.ChunkSystem; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.util.AbortableIterationConsumer; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.entity.EntityInLevelCallback; ++import net.minecraft.world.level.entity.EntityTypeTest; ++import net.minecraft.world.level.entity.LevelCallback; ++import net.minecraft.server.level.FullChunkStatus; ++import net.minecraft.world.level.entity.LevelEntityGetter; ++import net.minecraft.world.level.entity.Visibility; ++import net.minecraft.world.phys.AABB; ++import net.minecraft.world.phys.Vec3; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++import org.slf4j.Logger; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.List; ++import java.util.NoSuchElementException; ++import java.util.UUID; ++import java.util.concurrent.locks.StampedLock; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++ ++public final class EntityLookup implements LevelEntityGetter { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ protected static final int REGION_SHIFT = 5; ++ protected static final int REGION_MASK = (1 << REGION_SHIFT) - 1; ++ protected static final int REGION_SIZE = 1 << REGION_SHIFT; ++ ++ public final ServerLevel world; ++ ++ private final StampedLock stateLock = new StampedLock(); ++ protected final Long2ObjectOpenHashMap regions = new Long2ObjectOpenHashMap<>(128, 0.5f); ++ ++ private final int minSection; // inclusive ++ private final int maxSection; // inclusive ++ private final LevelCallback worldCallback; ++ ++ private final StampedLock entityByLock = new StampedLock(); ++ private final Int2ReferenceOpenHashMap entityById = new Int2ReferenceOpenHashMap<>(); ++ private final Object2ReferenceOpenHashMap entityByUUID = new Object2ReferenceOpenHashMap<>(); ++ private final EntityList accessibleEntities = new EntityList(); ++ ++ public EntityLookup(final ServerLevel world, final LevelCallback worldCallback) { ++ this.world = world; ++ this.minSection = WorldUtil.getMinSection(world); ++ this.maxSection = WorldUtil.getMaxSection(world); ++ this.worldCallback = worldCallback; ++ } ++ ++ private static Entity maskNonAccessible(final Entity entity) { ++ if (entity == null) { ++ return null; ++ } ++ final Visibility visibility = EntityLookup.getEntityStatus(entity); ++ return visibility.isAccessible() ? entity : null; ++ } ++ ++ @Nullable ++ @Override ++ public Entity get(final int id) { ++ final long attempt = this.entityByLock.tryOptimisticRead(); ++ if (attempt != 0L) { ++ try { ++ final Entity ret = this.entityById.get(id); ++ ++ if (this.entityByLock.validate(attempt)) { ++ return maskNonAccessible(ret); ++ } ++ } catch (final Error error) { ++ throw error; ++ } catch (final Throwable thr) { ++ // ignore ++ } ++ } ++ ++ this.entityByLock.readLock(); ++ try { ++ return maskNonAccessible(this.entityById.get(id)); ++ } finally { ++ this.entityByLock.tryUnlockRead(); ++ } ++ } ++ ++ @Nullable ++ @Override ++ public Entity get(final UUID id) { ++ final long attempt = this.entityByLock.tryOptimisticRead(); ++ if (attempt != 0L) { ++ try { ++ final Entity ret = this.entityByUUID.get(id); ++ ++ if (this.entityByLock.validate(attempt)) { ++ return maskNonAccessible(ret); ++ } ++ } catch (final Error error) { ++ throw error; ++ } catch (final Throwable thr) { ++ // ignore ++ } ++ } ++ ++ this.entityByLock.readLock(); ++ try { ++ return maskNonAccessible(this.entityByUUID.get(id)); ++ } finally { ++ this.entityByLock.tryUnlockRead(); ++ } ++ } ++ ++ public boolean hasEntity(final UUID uuid) { ++ return this.get(uuid) != null; ++ } ++ ++ public String getDebugInfo() { ++ return "count_id:" + this.entityById.size() + ",count_uuid:" + this.entityByUUID.size() + ",region_count:" + this.regions.size(); ++ } ++ ++ static final class ArrayIterable implements Iterable { ++ ++ private final T[] array; ++ private final int off; ++ private final int length; ++ ++ public ArrayIterable(final T[] array, final int off, final int length) { ++ this.array = array; ++ this.off = off; ++ this.length = length; ++ if (length > array.length) { ++ throw new IllegalArgumentException("Length must be no greater-than the array length"); ++ } ++ } ++ ++ @NotNull ++ @Override ++ public Iterator iterator() { ++ return new ArrayIterator<>(this.array, this.off, this.length); ++ } ++ ++ static final class ArrayIterator implements Iterator { ++ ++ private final T[] array; ++ private int off; ++ private final int length; ++ ++ public ArrayIterator(final T[] array, final int off, final int length) { ++ this.array = array; ++ this.off = off; ++ this.length = length; ++ } ++ ++ @Override ++ public boolean hasNext() { ++ return this.off < this.length; ++ } ++ ++ @Override ++ public T next() { ++ if (this.off >= this.length) { ++ throw new NoSuchElementException(); ++ } ++ return this.array[this.off++]; ++ } ++ ++ @Override ++ public void remove() { ++ throw new UnsupportedOperationException(); ++ } ++ } ++ } ++ ++ @Override ++ public Iterable getAll() { ++ return new ArrayIterable<>(this.accessibleEntities.getRawData(), 0, this.accessibleEntities.size()); ++ } ++ ++ public Entity[] getAllCopy() { ++ return Arrays.copyOf(this.accessibleEntities.getRawData(), this.accessibleEntities.size(), Entity[].class); ++ } ++ ++ @Override ++ public void get(final EntityTypeTest filter, final AbortableIterationConsumer action) { ++ final Int2ReferenceOpenHashMap entityCopy; ++ ++ this.entityByLock.readLock(); ++ try { ++ entityCopy = this.entityById.clone(); ++ } finally { ++ this.entityByLock.tryUnlockRead(); ++ } ++ for (final Entity entity : entityCopy.values()) { ++ final Visibility visibility = EntityLookup.getEntityStatus(entity); ++ if (!visibility.isAccessible()) { ++ continue; ++ } ++ final U casted = filter.tryCast(entity); ++ if (casted != null && action.accept(casted).shouldAbort()) { ++ break; ++ } ++ } ++ } ++ ++ @Override ++ public void get(final AABB box, final Consumer action) { ++ List entities = new ArrayList<>(); ++ this.getEntitiesWithoutDragonParts(null, box, entities, null); ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ action.accept(entities.get(i)); ++ } ++ } ++ ++ @Override ++ public void get(final EntityTypeTest filter, final AABB box, final AbortableIterationConsumer action) { ++ List entities = new ArrayList<>(); ++ this.getEntitiesWithoutDragonParts(null, box, entities, null); ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ final U casted = filter.tryCast(entities.get(i)); ++ if (casted != null && action.accept(casted).shouldAbort()) { ++ break; ++ } ++ } ++ } ++ ++ public void entityStatusChange(final Entity entity, final ChunkEntitySlices slices, final Visibility oldVisibility, final Visibility newVisibility, final boolean moved, ++ final boolean created, final boolean destroyed) { ++ TickThread.ensureTickThread(entity, "Entity status change must only happen on the main thread"); ++ ++ if (entity.updatingSectionStatus) { ++ // recursive status update ++ LOGGER.error("Cannot recursively update entity chunk status for entity " + entity, new Throwable()); ++ return; ++ } ++ ++ final boolean entityStatusUpdateBefore = slices == null ? false : slices.startPreventingStatusUpdates(); ++ ++ if (entityStatusUpdateBefore) { ++ LOGGER.error("Cannot update chunk status for entity " + entity + " since entity chunk (" + slices.chunkX + "," + slices.chunkZ + ") is receiving update", new Throwable()); ++ return; ++ } ++ ++ try { ++ final Boolean ticketBlockBefore = this.world.chunkTaskScheduler.chunkHolderManager.blockTicketUpdates(); ++ try { ++ entity.updatingSectionStatus = true; ++ try { ++ if (created) { ++ EntityLookup.this.worldCallback.onCreated(entity); ++ } ++ ++ if (oldVisibility == newVisibility) { ++ if (moved && newVisibility.isAccessible()) { ++ EntityLookup.this.worldCallback.onSectionChange(entity); ++ } ++ return; ++ } ++ ++ if (newVisibility.ordinal() > oldVisibility.ordinal()) { ++ // status upgrade ++ if (!oldVisibility.isAccessible() && newVisibility.isAccessible()) { ++ this.accessibleEntities.add(entity); ++ EntityLookup.this.worldCallback.onTrackingStart(entity); ++ } ++ ++ if (!oldVisibility.isTicking() && newVisibility.isTicking()) { ++ EntityLookup.this.worldCallback.onTickingStart(entity); ++ } ++ } else { ++ // status downgrade ++ if (oldVisibility.isTicking() && !newVisibility.isTicking()) { ++ EntityLookup.this.worldCallback.onTickingEnd(entity); ++ } ++ ++ if (oldVisibility.isAccessible() && !newVisibility.isAccessible()) { ++ this.accessibleEntities.remove(entity); ++ EntityLookup.this.worldCallback.onTrackingEnd(entity); ++ } ++ } ++ ++ if (moved && newVisibility.isAccessible()) { ++ EntityLookup.this.worldCallback.onSectionChange(entity); ++ } ++ ++ if (destroyed) { ++ EntityLookup.this.worldCallback.onDestroyed(entity); ++ } ++ } finally { ++ entity.updatingSectionStatus = false; ++ } ++ } finally { ++ this.world.chunkTaskScheduler.chunkHolderManager.unblockTicketUpdates(ticketBlockBefore); ++ } ++ } finally { ++ if (slices != null) { ++ slices.stopPreventingStatusUpdates(false); ++ } ++ } ++ } ++ ++ public void chunkStatusChange(final int x, final int z, final FullChunkStatus newStatus) { ++ this.getChunk(x, z).updateStatus(newStatus, this); ++ } ++ ++ public void addLegacyChunkEntities(final List entities, final ChunkPos forChunk) { ++ this.addEntityChunk(entities, forChunk, true); ++ } ++ ++ public void addEntityChunkEntities(final List entities, final ChunkPos forChunk) { ++ this.addEntityChunk(entities, forChunk, true); ++ } ++ ++ public void addWorldGenChunkEntities(final List entities, final ChunkPos forChunk) { ++ this.addEntityChunk(entities, forChunk, false); ++ } ++ ++ private void addRecursivelySafe(final Entity root, final boolean fromDisk) { ++ if (!this.addEntity(root, fromDisk)) { ++ // possible we are a passenger, and so should dismount from any valid entity in the world ++ root.stopRiding(true); ++ return; ++ } ++ for (final Entity passenger : root.getPassengers()) { ++ this.addRecursivelySafe(passenger, fromDisk); ++ } ++ } ++ ++ private void addEntityChunk(final List entities, final ChunkPos forChunk, final boolean fromDisk) { ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ final Entity entity = entities.get(i); ++ if (entity.isPassenger()) { ++ continue; ++ } ++ ++ if (!entity.chunkPosition().equals(forChunk)) { ++ LOGGER.warn("Root entity " + entity + " is outside of serialized chunk " + forChunk); ++ // can't set removed here, as we may not own the chunk position ++ // skip the entity ++ continue; ++ } ++ ++ final Vec3 rootPosition = entity.position(); ++ ++ // always adjust positions before adding passengers in case plugins access the entity, and so that ++ // they are added to the right entity chunk ++ for (final Entity passenger : entity.getIndirectPassengers()) { ++ if (!passenger.chunkPosition().equals(forChunk)) { ++ passenger.setPosRaw(rootPosition.x, rootPosition.y, rootPosition.z, true); ++ } ++ } ++ ++ this.addRecursivelySafe(entity, fromDisk); ++ } ++ } ++ ++ public boolean addNewEntity(final Entity entity) { ++ return this.addEntity(entity, false); ++ } ++ ++ public static Visibility getEntityStatus(final Entity entity) { ++ if (entity.isAlwaysTicking()) { ++ return Visibility.TICKING; ++ } ++ final FullChunkStatus entityStatus = entity.chunkStatus; ++ return Visibility.fromFullChunkStatus(entityStatus == null ? FullChunkStatus.INACCESSIBLE : entityStatus); ++ } ++ ++ private boolean addEntity(final Entity entity, final boolean fromDisk) { ++ final BlockPos pos = entity.blockPosition(); ++ final int sectionX = pos.getX() >> 4; ++ final int sectionY = Mth.clamp(pos.getY() >> 4, this.minSection, this.maxSection); ++ final int sectionZ = pos.getZ() >> 4; ++ TickThread.ensureTickThread(this.world, sectionX, sectionZ, "Cannot add entity off-main thread"); ++ ++ if (entity.isRemoved()) { ++ LOGGER.warn("Refusing to add removed entity: " + entity); ++ return false; ++ } ++ ++ if (entity.updatingSectionStatus) { ++ LOGGER.warn("Entity " + entity + " is currently prevented from being added/removed to world since it is processing section status updates", new Throwable()); ++ return false; ++ } ++ ++ if (fromDisk) { ++ ChunkSystem.onEntityPreAdd(this.world, entity); ++ if (entity.isRemoved()) { ++ // removed from checkDupeUUID call ++ return false; ++ } ++ } ++ ++ this.entityByLock.writeLock(); ++ try { ++ if (this.entityById.containsKey(entity.getId())) { ++ LOGGER.warn("Entity id already exists: " + entity.getId() + ", mapped to " + this.entityById.get(entity.getId()) + ", can't add " + entity); ++ return false; ++ } ++ if (this.entityByUUID.containsKey(entity.getUUID())) { ++ LOGGER.warn("Entity uuid already exists: " + entity.getUUID() + ", mapped to " + this.entityByUUID.get(entity.getUUID()) + ", can't add " + entity); ++ return false; ++ } ++ this.entityById.put(entity.getId(), entity); ++ this.entityByUUID.put(entity.getUUID(), entity); ++ } finally { ++ this.entityByLock.tryUnlockWrite(); ++ } ++ ++ entity.sectionX = sectionX; ++ entity.sectionY = sectionY; ++ entity.sectionZ = sectionZ; ++ final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ); ++ if (!slices.addEntity(entity, sectionY)) { ++ LOGGER.warn("Entity " + entity + " added to world '" + this.world.getWorld().getName() + "', but was already contained in entity chunk (" + sectionX + "," + sectionZ + ")"); ++ } ++ ++ entity.setLevelCallback(new EntityCallback(entity)); ++ ++ this.entityStatusChange(entity, slices, Visibility.HIDDEN, getEntityStatus(entity), false, !fromDisk, false); ++ ++ return true; ++ } ++ ++ public boolean canRemoveEntity(final Entity entity) { ++ if (entity.updatingSectionStatus) { ++ return false; ++ } ++ ++ final int sectionX = entity.sectionX; ++ final int sectionZ = entity.sectionZ; ++ final ChunkEntitySlices slices = this.getChunk(sectionX, sectionZ); ++ return slices == null || !slices.isPreventingStatusUpdates(); ++ } ++ ++ private void removeEntity(final Entity entity) { ++ final int sectionX = entity.sectionX; ++ final int sectionY = entity.sectionY; ++ final int sectionZ = entity.sectionZ; ++ TickThread.ensureTickThread(this.world, sectionX, sectionZ, "Cannot remove entity off-main"); ++ if (!entity.isRemoved()) { ++ throw new IllegalStateException("Only call Entity#setRemoved to remove an entity"); ++ } ++ final ChunkEntitySlices slices = this.getChunk(sectionX, sectionZ); ++ // all entities should be in a chunk ++ if (slices == null) { ++ LOGGER.warn("Cannot remove entity " + entity + " from null entity slices (" + sectionX + "," + sectionZ + ")"); ++ } else { ++ if (slices.isPreventingStatusUpdates()) { ++ throw new IllegalStateException("Attempting to remove entity " + entity + " from entity slices (" + sectionX + "," + sectionZ + ") that is receiving status updates"); ++ } ++ if (!slices.removeEntity(entity, sectionY)) { ++ LOGGER.warn("Failed to remove entity " + entity + " from entity slices (" + sectionX + "," + sectionZ + ")"); ++ } ++ } ++ entity.sectionX = entity.sectionY = entity.sectionZ = Integer.MIN_VALUE; ++ ++ this.entityByLock.writeLock(); ++ try { ++ if (!this.entityById.remove(entity.getId(), entity)) { ++ LOGGER.warn("Failed to remove entity " + entity + " by id, current entity mapped: " + this.entityById.get(entity.getId())); ++ } ++ if (!this.entityByUUID.remove(entity.getUUID(), entity)) { ++ LOGGER.warn("Failed to remove entity " + entity + " by uuid, current entity mapped: " + this.entityByUUID.get(entity.getUUID())); ++ } ++ } finally { ++ this.entityByLock.tryUnlockWrite(); ++ } ++ } ++ ++ private ChunkEntitySlices moveEntity(final Entity entity) { ++ // ensure we own the entity ++ TickThread.ensureTickThread(entity, "Cannot move entity off-main"); ++ ++ final BlockPos newPos = entity.blockPosition(); ++ final int newSectionX = newPos.getX() >> 4; ++ final int newSectionY = Mth.clamp(newPos.getY() >> 4, this.minSection, this.maxSection); ++ final int newSectionZ = newPos.getZ() >> 4; ++ ++ if (newSectionX == entity.sectionX && newSectionY == entity.sectionY && newSectionZ == entity.sectionZ) { ++ return null; ++ } ++ ++ // ensure the new section is owned by this tick thread ++ TickThread.ensureTickThread(this.world, newSectionX, newSectionZ, "Cannot move entity off-main"); ++ ++ // ensure the old section is owned by this tick thread ++ TickThread.ensureTickThread(this.world, entity.sectionX, entity.sectionZ, "Cannot move entity off-main"); ++ ++ final ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ); ++ final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ); ++ ++ if (!old.removeEntity(entity, entity.sectionY)) { ++ LOGGER.warn("Could not remove entity " + entity + " from its old chunk section (" + entity.sectionX + "," + entity.sectionY + "," + entity.sectionZ + ") since it was not contained in the section"); ++ } ++ ++ if (!slices.addEntity(entity, newSectionY)) { ++ LOGGER.warn("Could not add entity " + entity + " to its new chunk section (" + newSectionX + "," + newSectionY + "," + newSectionZ + ") as it is already contained in the section"); ++ } ++ ++ entity.sectionX = newSectionX; ++ entity.sectionY = newSectionY; ++ entity.sectionZ = newSectionZ; ++ ++ return slices; ++ } ++ ++ public void getEntitiesWithoutDragonParts(final Entity except, final AABB box, final List into, final Predicate predicate) { ++ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; ++ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; ++ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; ++ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; ++ ++ final int minRegionX = minChunkX >> REGION_SHIFT; ++ final int minRegionZ = minChunkZ >> REGION_SHIFT; ++ final int maxRegionX = maxChunkX >> REGION_SHIFT; ++ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; ++ ++ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { ++ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; ++ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; ++ ++ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { ++ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); ++ ++ if (region == null) { ++ continue; ++ } ++ ++ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; ++ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); ++ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { ++ continue; ++ } ++ ++ chunk.getEntitiesWithoutDragonParts(except, box, into, predicate); ++ } ++ } ++ } ++ } ++ } ++ ++ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { ++ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; ++ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; ++ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; ++ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; ++ ++ final int minRegionX = minChunkX >> REGION_SHIFT; ++ final int minRegionZ = minChunkZ >> REGION_SHIFT; ++ final int maxRegionX = maxChunkX >> REGION_SHIFT; ++ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; ++ ++ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { ++ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; ++ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; ++ ++ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { ++ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); ++ ++ if (region == null) { ++ continue; ++ } ++ ++ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; ++ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); ++ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { ++ continue; ++ } ++ ++ chunk.getEntities(except, box, into, predicate); ++ } ++ } ++ } ++ } ++ } ++ ++ public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { ++ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; ++ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; ++ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; ++ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; ++ ++ final int minRegionX = minChunkX >> REGION_SHIFT; ++ final int minRegionZ = minChunkZ >> REGION_SHIFT; ++ final int maxRegionX = maxChunkX >> REGION_SHIFT; ++ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; ++ ++ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { ++ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; ++ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; ++ ++ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { ++ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); ++ ++ if (region == null) { ++ continue; ++ } ++ ++ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; ++ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); ++ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { ++ continue; ++ } ++ ++ chunk.getHardCollidingEntities(except, box, into, predicate); ++ } ++ } ++ } ++ } ++ } ++ ++ public void getEntities(final EntityType type, final AABB box, final List into, ++ final Predicate predicate) { ++ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; ++ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; ++ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; ++ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; ++ ++ final int minRegionX = minChunkX >> REGION_SHIFT; ++ final int minRegionZ = minChunkZ >> REGION_SHIFT; ++ final int maxRegionX = maxChunkX >> REGION_SHIFT; ++ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; ++ ++ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { ++ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; ++ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; ++ ++ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { ++ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); ++ ++ if (region == null) { ++ continue; ++ } ++ ++ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; ++ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); ++ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { ++ continue; ++ } ++ ++ chunk.getEntities(type, box, (List)into, (Predicate)predicate); ++ } ++ } ++ } ++ } ++ } ++ ++ public void getEntities(final Class clazz, final Entity except, final AABB box, final List into, ++ final Predicate predicate) { ++ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; ++ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; ++ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; ++ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; ++ ++ final int minRegionX = minChunkX >> REGION_SHIFT; ++ final int minRegionZ = minChunkZ >> REGION_SHIFT; ++ final int maxRegionX = maxChunkX >> REGION_SHIFT; ++ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; ++ ++ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { ++ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; ++ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; ++ ++ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { ++ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); ++ ++ if (region == null) { ++ continue; ++ } ++ ++ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; ++ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); ++ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { ++ continue; ++ } ++ ++ chunk.getEntities(clazz, except, box, into, predicate); ++ } ++ } ++ } ++ } ++ } ++ ++ public void entitySectionLoad(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { ++ TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot load in entity section off-main"); ++ synchronized (this) { ++ final ChunkEntitySlices curr = this.getChunk(chunkX, chunkZ); ++ if (curr != null) { ++ this.removeChunk(chunkX, chunkZ); ++ ++ curr.mergeInto(slices); ++ ++ this.addChunk(chunkX, chunkZ, slices); ++ } else { ++ this.addChunk(chunkX, chunkZ, slices); ++ } ++ } ++ } ++ ++ public void entitySectionUnload(final int chunkX, final int chunkZ) { ++ TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot unload entity section off-main"); ++ this.removeChunk(chunkX, chunkZ); ++ } ++ ++ public ChunkEntitySlices getChunk(final int chunkX, final int chunkZ) { ++ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); ++ if (region == null) { ++ return null; ++ } ++ ++ return region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT)); ++ } ++ ++ public ChunkEntitySlices getOrCreateChunk(final int chunkX, final int chunkZ) { ++ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); ++ ChunkEntitySlices ret; ++ if (region == null || (ret = region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT))) == null) { ++ // loadInEntityChunk will call addChunk for us ++ return this.world.chunkTaskScheduler.chunkHolderManager.getOrCreateEntityChunk(chunkX, chunkZ, true); ++ } ++ ++ return ret; ++ } ++ ++ public ChunkSlicesRegion getRegion(final int regionX, final int regionZ) { ++ final long key = CoordinateUtils.getChunkKey(regionX, regionZ); ++ final long attempt = this.stateLock.tryOptimisticRead(); ++ if (attempt != 0L) { ++ try { ++ final ChunkSlicesRegion ret = this.regions.get(key); ++ ++ if (this.stateLock.validate(attempt)) { ++ return ret; ++ } ++ } catch (final Error error) { ++ throw error; ++ } catch (final Throwable thr) { ++ // ignore ++ } ++ } ++ ++ this.stateLock.readLock(); ++ try { ++ return this.regions.get(key); ++ } finally { ++ this.stateLock.tryUnlockRead(); ++ } ++ } ++ ++ private synchronized void removeChunk(final int chunkX, final int chunkZ) { ++ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); ++ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); ++ ++ final ChunkSlicesRegion region = this.regions.get(key); ++ final int remaining = region.remove(relIndex); ++ ++ if (remaining == 0) { ++ this.stateLock.writeLock(); ++ try { ++ this.regions.remove(key); ++ } finally { ++ this.stateLock.tryUnlockWrite(); ++ } ++ } ++ } ++ ++ public synchronized void addChunk(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { ++ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); ++ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); ++ ++ ChunkSlicesRegion region = this.regions.get(key); ++ if (region != null) { ++ region.add(relIndex, slices); ++ } else { ++ region = new ChunkSlicesRegion(); ++ region.add(relIndex, slices); ++ this.stateLock.writeLock(); ++ try { ++ this.regions.put(key, region); ++ } finally { ++ this.stateLock.tryUnlockWrite(); ++ } ++ } ++ } ++ ++ public static final class ChunkSlicesRegion { ++ ++ protected final ChunkEntitySlices[] slices = new ChunkEntitySlices[REGION_SIZE * REGION_SIZE]; ++ protected int sliceCount; ++ ++ public ChunkEntitySlices get(final int index) { ++ return this.slices[index]; ++ } ++ ++ public int remove(final int index) { ++ final ChunkEntitySlices slices = this.slices[index]; ++ if (slices == null) { ++ throw new IllegalStateException(); ++ } ++ ++ this.slices[index] = null; ++ ++ return --this.sliceCount; ++ } ++ ++ public void add(final int index, final ChunkEntitySlices slices) { ++ final ChunkEntitySlices curr = this.slices[index]; ++ if (curr != null) { ++ throw new IllegalStateException(); ++ } ++ ++ this.slices[index] = slices; ++ ++ ++this.sliceCount; ++ } ++ } ++ ++ private final class EntityCallback implements EntityInLevelCallback { ++ ++ public final Entity entity; ++ ++ public EntityCallback(final Entity entity) { ++ this.entity = entity; ++ } ++ ++ @Override ++ public void onMove() { ++ final Entity entity = this.entity; ++ final Visibility oldVisibility = getEntityStatus(entity); ++ final ChunkEntitySlices newSlices = EntityLookup.this.moveEntity(this.entity); ++ if (newSlices == null) { ++ // no new section, so didn't change sections ++ return; ++ } ++ final Visibility newVisibility = getEntityStatus(entity); ++ ++ EntityLookup.this.entityStatusChange(entity, newSlices, oldVisibility, newVisibility, true, false, false); ++ } ++ ++ @Override ++ public void onRemove(final Entity.RemovalReason reason) { ++ final Entity entity = this.entity; ++ TickThread.ensureTickThread(entity, "Cannot remove entity off-main"); // Paper - rewrite chunk system ++ final Visibility tickingState = EntityLookup.getEntityStatus(entity); ++ ++ EntityLookup.this.removeEntity(entity); ++ ++ EntityLookup.this.entityStatusChange(entity, null, tickingState, Visibility.HIDDEN, false, false, reason.shouldDestroy()); ++ ++ this.entity.setLevelCallback(NoOpCallback.INSTANCE); ++ } ++ } ++ ++ private static final class NoOpCallback implements EntityInLevelCallback { ++ ++ public static final NoOpCallback INSTANCE = new NoOpCallback(); ++ ++ @Override ++ public void onMove() {} ++ ++ @Override ++ public void onRemove(final Entity.RemovalReason reason) {} ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2934f0cf0ef09c84739312b00186c2ef0019a165 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java +@@ -0,0 +1,1343 @@ ++package io.papermc.paper.chunk.system.io; ++ ++import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; ++import ca.spottedleaf.concurrentutil.executor.Cancellable; ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedQueueExecutorThread; ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.util.CoordinateUtils; ++import io.papermc.paper.util.TickThread; ++import it.unimi.dsi.fastutil.HashCommon; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.storage.RegionFile; ++import net.minecraft.world.level.chunk.storage.RegionFileStorage; ++import org.slf4j.Logger; ++import java.io.IOException; ++import java.lang.invoke.VarHandle; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.CompletionException; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.BiConsumer; ++import java.util.function.BiFunction; ++import java.util.function.Consumer; ++import java.util.function.Function; ++ ++/** ++ * Prioritised RegionFile I/O executor, responsible for all RegionFile access. ++ *

    ++ * All functions provided are MT-Safe, however certain ordering constraints are recommended: ++ *

      ++ *
    • ++ * Chunk saves may not occur for unloaded chunks. ++ *
    • ++ *
    • ++ * Tasks must be scheduled on the chunk scheduler thread. ++ *
    • ++ *
    ++ * By following these constraints, no chunk data loss should occur with the exception of underlying I/O problems. ++ */ ++public final class RegionFileIOThread extends PrioritisedQueueExecutorThread { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ /** ++ * The kinds of region files controlled by the region file thread. Add more when needed, and ensure ++ * getControllerFor is updated. ++ */ ++ public static enum RegionFileType { ++ CHUNK_DATA, ++ POI_DATA, ++ ENTITY_DATA; ++ } ++ ++ protected static final RegionFileType[] CACHED_REGIONFILE_TYPES = RegionFileType.values(); ++ ++ private ChunkDataController getControllerFor(final ServerLevel world, final RegionFileType type) { ++ return switch (type) { ++ case CHUNK_DATA -> world.chunkDataControllerNew; ++ case POI_DATA -> world.poiDataControllerNew; ++ case ENTITY_DATA -> world.entityDataControllerNew; ++ default -> throw new IllegalStateException("Unknown controller type " + type); ++ }; ++ } ++ ++ /** ++ * Collects regionfile data for a certain chunk. ++ */ ++ public static final class RegionFileData { ++ ++ private final boolean[] hasResult = new boolean[CACHED_REGIONFILE_TYPES.length]; ++ private final CompoundTag[] data = new CompoundTag[CACHED_REGIONFILE_TYPES.length]; ++ private final Throwable[] throwables = new Throwable[CACHED_REGIONFILE_TYPES.length]; ++ ++ /** ++ * Sets the result associated with the specified regionfile type. Note that ++ * results can only be set once per regionfile type. ++ * ++ * @param type The regionfile type. ++ * @param data The result to set. ++ */ ++ public void setData(final RegionFileType type, final CompoundTag data) { ++ final int index = type.ordinal(); ++ ++ if (this.hasResult[index]) { ++ throw new IllegalArgumentException("Result already exists for type " + type); ++ } ++ this.hasResult[index] = true; ++ this.data[index] = data; ++ } ++ ++ /** ++ * Sets the result associated with the specified regionfile type. Note that ++ * results can only be set once per regionfile type. ++ * ++ * @param type The regionfile type. ++ * @param throwable The result to set. ++ */ ++ public void setThrowable(final RegionFileType type, final Throwable throwable) { ++ final int index = type.ordinal(); ++ ++ if (this.hasResult[index]) { ++ throw new IllegalArgumentException("Result already exists for type " + type); ++ } ++ this.hasResult[index] = true; ++ this.throwables[index] = throwable; ++ } ++ ++ /** ++ * Returns whether there is a result for the specified regionfile type. ++ * ++ * @param type Specified regionfile type. ++ * ++ * @return Whether a result exists for {@code type}. ++ */ ++ public boolean hasResult(final RegionFileType type) { ++ return this.hasResult[type.ordinal()]; ++ } ++ ++ /** ++ * Returns the data result for the regionfile type. ++ * ++ * @param type Specified regionfile type. ++ * ++ * @throws IllegalArgumentException If the result has not been set for {@code type}. ++ * @return The data result for the specified type. If the result is a {@code Throwable}, ++ * then returns {@code null}. ++ */ ++ public CompoundTag getData(final RegionFileType type) { ++ final int index = type.ordinal(); ++ ++ if (!this.hasResult[index]) { ++ throw new IllegalArgumentException("Result does not exist for type " + type); ++ } ++ ++ return this.data[index]; ++ } ++ ++ /** ++ * Returns the throwable result for the regionfile type. ++ * ++ * @param type Specified regionfile type. ++ * ++ * @throws IllegalArgumentException If the result has not been set for {@code type}. ++ * @return The throwable result for the specified type. If the result is an {@code CompoundTag}, ++ * then returns {@code null}. ++ */ ++ public Throwable getThrowable(final RegionFileType type) { ++ final int index = type.ordinal(); ++ ++ if (!this.hasResult[index]) { ++ throw new IllegalArgumentException("Result does not exist for type " + type); ++ } ++ ++ return this.throwables[index]; ++ } ++ } ++ ++ private static final Object INIT_LOCK = new Object(); ++ ++ static RegionFileIOThread[] threads; ++ ++ /* needs to be consistent given a set of parameters */ ++ static RegionFileIOThread selectThread(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { ++ if (threads == null) { ++ throw new IllegalStateException("Threads not initialised"); ++ } ++ ++ final int regionX = chunkX >> 5; ++ final int regionZ = chunkZ >> 5; ++ final int typeOffset = type.ordinal(); ++ ++ return threads[(System.identityHashCode(world) + regionX + regionZ + typeOffset) % threads.length]; ++ } ++ ++ /** ++ * Shuts down the I/O executor(s). Watis for all tasks to complete if specified. ++ * Tasks queued during this call might not be accepted, and tasks queued after will not be accepted. ++ * ++ * @param wait Whether to wait until all tasks have completed. ++ */ ++ public static void close(final boolean wait) { ++ for (int i = 0, len = threads.length; i < len; ++i) { ++ threads[i].close(false, true); ++ } ++ if (wait) { ++ RegionFileIOThread.flush(); ++ } ++ } ++ ++ public static long[] getExecutedTasks() { ++ final long[] ret = new long[threads.length]; ++ for (int i = 0, len = threads.length; i < len; ++i) { ++ ret[i] = threads[i].getTotalTasksExecuted(); ++ } ++ ++ return ret; ++ } ++ ++ public static long[] getTasksScheduled() { ++ final long[] ret = new long[threads.length]; ++ for (int i = 0, len = threads.length; i < len; ++i) { ++ ret[i] = threads[i].getTotalTasksScheduled(); ++ } ++ return ret; ++ } ++ ++ public static void flush() { ++ for (int i = 0, len = threads.length; i < len; ++i) { ++ threads[i].waitUntilAllExecuted(); ++ } ++ } ++ ++ public static void partialFlush(final int totalTasksRemaining) { ++ long failures = 1L; // start out at 0.25ms ++ ++ for (;;) { ++ final long[] executed = getExecutedTasks(); ++ final long[] scheduled = getTasksScheduled(); ++ ++ long sum = 0; ++ for (int i = 0; i < executed.length; ++i) { ++ sum += scheduled[i] - executed[i]; ++ } ++ ++ if (sum <= totalTasksRemaining) { ++ break; ++ } ++ ++ failures = ConcurrentUtil.linearLongBackoff(failures, 250_000L, 5_000_000L); // 500us, 5ms ++ } ++ } ++ ++ /** ++ * Inits the executor with the specified number of threads. ++ * ++ * @param threads Specified number of threads. ++ */ ++ public static void init(final int threads) { ++ synchronized (INIT_LOCK) { ++ if (RegionFileIOThread.threads != null) { ++ throw new IllegalStateException("Already initialised threads"); ++ } ++ ++ RegionFileIOThread.threads = new RegionFileIOThread[threads]; ++ ++ for (int i = 0; i < threads; ++i) { ++ RegionFileIOThread.threads[i] = new RegionFileIOThread(i); ++ RegionFileIOThread.threads[i].start(); ++ } ++ } ++ } ++ ++ private RegionFileIOThread(final int threadNumber) { ++ super(new PrioritisedThreadedTaskQueue(), (int)(1.0e6)); // 1.0ms spinwait time ++ this.setName("RegionFile I/O Thread #" + threadNumber); ++ this.setPriority(Thread.NORM_PRIORITY - 2); // we keep priority close to normal because threads can wait on us ++ this.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { ++ LOGGER.error("Uncaught exception thrown from I/O thread, report this! Thread: " + thread.getName(), thr); ++ }); ++ } ++ ++ /** ++ * Returns whether the current thread is a regionfile I/O executor. ++ * @return Whether the current thread is a regionfile I/O executor. ++ */ ++ public static boolean isRegionFileThread() { ++ return Thread.currentThread() instanceof RegionFileIOThread; ++ } ++ ++ /** ++ * Returns the priority associated with blocking I/O based on the current thread. The goal is to avoid ++ * dumb plugins from taking away priority from threads we consider crucial. ++ * @return The priroity to use with blocking I/O on the current thread. ++ */ ++ public static PrioritisedExecutor.Priority getIOBlockingPriorityForCurrentThread() { ++ if (TickThread.isTickThread()) { ++ return PrioritisedExecutor.Priority.BLOCKING; ++ } ++ return PrioritisedExecutor.Priority.HIGHEST; ++ } ++ ++ /** ++ * Returns the current {@code CompoundTag} pending for write for the specified chunk and regionfile type. ++ * Note that this does not copy the result, so do not modify the result returned. ++ * ++ * @param world Specified world. ++ * @param chunkX Specified chunk x. ++ * @param chunkZ Specified chunk z. ++ * @param type Specified regionfile type. ++ * ++ * @return The compound tag associated for the specified chunk. {@code null} if no write was pending, or if {@code null} is the write pending. ++ */ ++ public static CompoundTag getPendingWrite(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { ++ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); ++ return thread.getPendingWriteInternal(world, chunkX, chunkZ, type); ++ } ++ ++ CompoundTag getPendingWriteInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { ++ final ChunkDataController taskController = this.getControllerFor(world, type); ++ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); ++ ++ if (task == null) { ++ return null; ++ } ++ ++ final CompoundTag ret = task.inProgressWrite; ++ ++ return ret == ChunkDataTask.NOTHING_TO_WRITE ? null : ret; ++ } ++ ++ /** ++ * Returns the priority for the specified regionfile type for the specified chunk. ++ * @param world Specified world. ++ * @param chunkX Specified chunk x. ++ * @param chunkZ Specified chunk z. ++ * @param type Specified regionfile type. ++ * @return The priority for the chunk ++ */ ++ public static PrioritisedExecutor.Priority getPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { ++ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); ++ return thread.getPriorityInternal(world, chunkX, chunkZ, type); ++ } ++ ++ PrioritisedExecutor.Priority getPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { ++ final ChunkDataController taskController = this.getControllerFor(world, type); ++ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); ++ ++ if (task == null) { ++ return PrioritisedExecutor.Priority.COMPLETING; ++ } ++ ++ return task.prioritisedTask.getPriority(); ++ } ++ ++ /** ++ * Sets the priority for all regionfile types for the specified chunk. Note that great care should ++ * be taken using this method, as there can be multiple tasks tied to the same chunk that want different ++ * priorities. ++ * ++ * @param world Specified world. ++ * @param chunkX Specified chunk x. ++ * @param chunkZ Specified chunk z. ++ * @param priority New priority. ++ * ++ * @see #raisePriority(ServerLevel, int, int, Priority) ++ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) ++ * @see #lowerPriority(ServerLevel, int, int, Priority) ++ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) ++ */ ++ public static void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, ++ final PrioritisedExecutor.Priority priority) { ++ for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { ++ RegionFileIOThread.setPriority(world, chunkX, chunkZ, type, priority); ++ } ++ } ++ ++ /** ++ * Sets the priority for the specified regionfile type for the specified chunk. Note that great care should ++ * be taken using this method, as there can be multiple tasks tied to the same chunk that want different ++ * priorities. ++ * ++ * @param world Specified world. ++ * @param chunkX Specified chunk x. ++ * @param chunkZ Specified chunk z. ++ * @param type Specified regionfile type. ++ * @param priority New priority. ++ * ++ * @see #raisePriority(ServerLevel, int, int, Priority) ++ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) ++ * @see #lowerPriority(ServerLevel, int, int, Priority) ++ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) ++ */ ++ public static void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, ++ final PrioritisedExecutor.Priority priority) { ++ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); ++ thread.setPriorityInternal(world, chunkX, chunkZ, type, priority); ++ } ++ ++ void setPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, ++ final PrioritisedExecutor.Priority priority) { ++ final ChunkDataController taskController = this.getControllerFor(world, type); ++ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); ++ ++ if (task != null) { ++ task.prioritisedTask.setPriority(priority); ++ } ++ } ++ ++ /** ++ * Raises the priority for all regionfile types for the specified chunk. ++ * ++ * @param world Specified world. ++ * @param chunkX Specified chunk x. ++ * @param chunkZ Specified chunk z. ++ * @param priority New priority. ++ * ++ * @see #setPriority(ServerLevel, int, int, Priority) ++ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) ++ * @see #lowerPriority(ServerLevel, int, int, Priority) ++ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) ++ */ ++ public static void raisePriority(final ServerLevel world, final int chunkX, final int chunkZ, ++ final PrioritisedExecutor.Priority priority) { ++ for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { ++ RegionFileIOThread.raisePriority(world, chunkX, chunkZ, type, priority); ++ } ++ } ++ ++ /** ++ * Raises the priority for the specified regionfile type for the specified chunk. ++ * ++ * @param world Specified world. ++ * @param chunkX Specified chunk x. ++ * @param chunkZ Specified chunk z. ++ * @param type Specified regionfile type. ++ * @param priority New priority. ++ * ++ * @see #setPriority(ServerLevel, int, int, Priority) ++ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) ++ * @see #lowerPriority(ServerLevel, int, int, Priority) ++ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) ++ */ ++ public static void raisePriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, ++ final PrioritisedExecutor.Priority priority) { ++ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); ++ thread.raisePriorityInternal(world, chunkX, chunkZ, type, priority); ++ } ++ ++ void raisePriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, ++ final PrioritisedExecutor.Priority priority) { ++ final ChunkDataController taskController = this.getControllerFor(world, type); ++ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); ++ ++ if (task != null) { ++ task.prioritisedTask.raisePriority(priority); ++ } ++ } ++ ++ /** ++ * Lowers the priority for all regionfile types for the specified chunk. ++ * ++ * @param world Specified world. ++ * @param chunkX Specified chunk x. ++ * @param chunkZ Specified chunk z. ++ * @param priority New priority. ++ * ++ * @see #raisePriority(ServerLevel, int, int, Priority) ++ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) ++ * @see #setPriority(ServerLevel, int, int, Priority) ++ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) ++ */ ++ public static void lowerPriority(final ServerLevel world, final int chunkX, final int chunkZ, ++ final PrioritisedExecutor.Priority priority) { ++ for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { ++ RegionFileIOThread.lowerPriority(world, chunkX, chunkZ, type, priority); ++ } ++ } ++ ++ /** ++ * Lowers the priority for the specified regionfile type for the specified chunk. ++ * ++ * @param world Specified world. ++ * @param chunkX Specified chunk x. ++ * @param chunkZ Specified chunk z. ++ * @param type Specified regionfile type. ++ * @param priority New priority. ++ * ++ * @see #raisePriority(ServerLevel, int, int, Priority) ++ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) ++ * @see #setPriority(ServerLevel, int, int, Priority) ++ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) ++ */ ++ public static void lowerPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, ++ final PrioritisedExecutor.Priority priority) { ++ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); ++ thread.lowerPriorityInternal(world, chunkX, chunkZ, type, priority); ++ } ++ ++ void lowerPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, ++ final PrioritisedExecutor.Priority priority) { ++ final ChunkDataController taskController = this.getControllerFor(world, type); ++ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); ++ ++ if (task != null) { ++ task.prioritisedTask.lowerPriority(priority); ++ } ++ } ++ ++ /** ++ * Schedules the chunk data to be written asynchronously. ++ *

    ++ * Impl notes: ++ *

      ++ *
    • ++ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means ++ * saves must be scheduled before a chunk is unloaded. ++ *
    • ++ *
    • ++ * Writes may be called concurrently, although only the "later" write will go through. ++ *
    • ++ *
    ++ * ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param data Chunk's data ++ * @param type The regionfile type to write to. ++ * ++ * @throws IllegalStateException If the file io thread has shutdown. ++ */ ++ public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, ++ final RegionFileType type) { ++ RegionFileIOThread.scheduleSave(world, chunkX, chunkZ, data, type, PrioritisedExecutor.Priority.NORMAL); ++ } ++ ++ /** ++ * Schedules the chunk data to be written asynchronously. ++ *

    ++ * Impl notes: ++ *

      ++ *
    • ++ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means ++ * saves must be scheduled before a chunk is unloaded. ++ *
    • ++ *
    • ++ * Writes may be called concurrently, although only the "later" write will go through. ++ *
    • ++ *
    ++ * ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param data Chunk's data ++ * @param type The regionfile type to write to. ++ * @param priority The minimum priority to schedule at. ++ * ++ * @throws IllegalStateException If the file io thread has shutdown. ++ */ ++ public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, ++ final RegionFileType type, final PrioritisedExecutor.Priority priority) { ++ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); ++ thread.scheduleSaveInternal(world, chunkX, chunkZ, data, type, priority); ++ } ++ ++ void scheduleSaveInternal(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, ++ final RegionFileType type, final PrioritisedExecutor.Priority priority) { ++ final ChunkDataController taskController = this.getControllerFor(world, type); ++ ++ final boolean[] created = new boolean[1]; ++ final ChunkCoordinate key = new ChunkCoordinate(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ final ChunkDataTask task = taskController.tasks.compute(key, (final ChunkCoordinate keyInMap, final ChunkDataTask taskRunning) -> { ++ if (taskRunning == null || taskRunning.failedWrite) { ++ // no task is scheduled or the previous write failed - meaning we need to overwrite it ++ ++ // create task ++ final ChunkDataTask newTask = new ChunkDataTask(world, chunkX, chunkZ, taskController, RegionFileIOThread.this, priority); ++ newTask.inProgressWrite = data; ++ created[0] = true; ++ ++ return newTask; ++ } ++ ++ taskRunning.inProgressWrite = data; ++ ++ return taskRunning; ++ }); ++ ++ if (created[0]) { ++ task.prioritisedTask.queue(); ++ } else { ++ task.prioritisedTask.raisePriority(priority); ++ } ++ } ++ ++ /** ++ * Schedules a load to be executed asynchronously. This task will load all regionfile types, and then call ++ * {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean)} ++ * for single load. ++ *

    ++ * Impl notes: ++ *

      ++ *
    • ++ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may ++ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of ++ * data is undefined behaviour, and can cause deadlock. ++ *
    • ++ *
    ++ * ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param onComplete Consumer to execute once this task has completed ++ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost ++ * of this call. ++ * ++ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. ++ * ++ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) ++ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) ++ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) ++ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) ++ */ ++ public static Cancellable loadAllChunkData(final ServerLevel world, final int chunkX, final int chunkZ, ++ final Consumer onComplete, final boolean intendingToBlock) { ++ return RegionFileIOThread.loadAllChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, PrioritisedExecutor.Priority.NORMAL); ++ } ++ ++ /** ++ * Schedules a load to be executed asynchronously. This task will load all regionfile types, and then call ++ * {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority)} ++ * for single load. ++ *

    ++ * Impl notes: ++ *

      ++ *
    • ++ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may ++ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of ++ * data is undefined behaviour, and can cause deadlock. ++ *
    • ++ *
    ++ * ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param onComplete Consumer to execute once this task has completed ++ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost ++ * of this call. ++ * @param priority The minimum priority to load the data at. ++ * ++ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. ++ * ++ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) ++ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) ++ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) ++ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) ++ */ ++ public static Cancellable loadAllChunkData(final ServerLevel world, final int chunkX, final int chunkZ, ++ final Consumer onComplete, final boolean intendingToBlock, ++ final PrioritisedExecutor.Priority priority) { ++ return RegionFileIOThread.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, priority, CACHED_REGIONFILE_TYPES); ++ } ++ ++ /** ++ * Schedules a load to be executed asynchronously. This task will load data for the specified regionfile type(s), and ++ * then call {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean)} ++ * for single load. ++ *

    ++ * Impl notes: ++ *

      ++ *
    • ++ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may ++ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of ++ * data is undefined behaviour, and can cause deadlock. ++ *
    • ++ *
    ++ * ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param onComplete Consumer to execute once this task has completed ++ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost ++ * of this call. ++ * @param types The regionfile type(s) to load. ++ * ++ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. ++ * ++ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) ++ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) ++ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) ++ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) ++ */ ++ public static Cancellable loadChunkData(final ServerLevel world, final int chunkX, final int chunkZ, ++ final Consumer onComplete, final boolean intendingToBlock, ++ final RegionFileType... types) { ++ return RegionFileIOThread.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, PrioritisedExecutor.Priority.NORMAL, types); ++ } ++ ++ /** ++ * Schedules a load to be executed asynchronously. This task will load data for the specified regionfile type(s), and ++ * then call {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority)} ++ * for single load. ++ *

    ++ * Impl notes: ++ *

      ++ *
    • ++ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may ++ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of ++ * data is undefined behaviour, and can cause deadlock. ++ *
    • ++ *
    ++ * ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param onComplete Consumer to execute once this task has completed ++ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost ++ * of this call. ++ * @param types The regionfile type(s) to load. ++ * @param priority The minimum priority to load the data at. ++ * ++ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. ++ * ++ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) ++ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) ++ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) ++ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) ++ */ ++ public static Cancellable loadChunkData(final ServerLevel world, final int chunkX, final int chunkZ, ++ final Consumer onComplete, final boolean intendingToBlock, ++ final PrioritisedExecutor.Priority priority, final RegionFileType... types) { ++ if (types == null) { ++ throw new NullPointerException("Types cannot be null"); ++ } ++ if (types.length == 0) { ++ throw new IllegalArgumentException("Types cannot be empty"); ++ } ++ ++ final RegionFileData ret = new RegionFileData(); ++ ++ final Cancellable[] reads = new CancellableRead[types.length]; ++ final AtomicInteger completions = new AtomicInteger(); ++ final int expectedCompletions = types.length; ++ ++ for (int i = 0; i < expectedCompletions; ++i) { ++ final RegionFileType type = types[i]; ++ reads[i] = RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, ++ (final CompoundTag data, final Throwable throwable) -> { ++ if (throwable != null) { ++ ret.setThrowable(type, throwable); ++ } else { ++ ret.setData(type, data); ++ } ++ ++ if (completions.incrementAndGet() == expectedCompletions) { ++ onComplete.accept(ret); ++ } ++ }, intendingToBlock, priority); ++ } ++ ++ return new CancellableReads(reads); ++ } ++ ++ /** ++ * Schedules a load to be executed asynchronously. This task will load the specified regionfile type, and then call ++ * {@code onComplete}. ++ *

    ++ * Impl notes: ++ *

      ++ *
    • ++ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may ++ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of ++ * data is undefined behaviour, and can cause deadlock. ++ *
    • ++ *
    ++ * ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param onComplete Consumer to execute once this task has completed ++ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost ++ * of this call. ++ * ++ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. ++ * ++ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) ++ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) ++ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) ++ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) ++ */ ++ public static Cancellable loadDataAsync(final ServerLevel world, final int chunkX, final int chunkZ, ++ final RegionFileType type, final BiConsumer onComplete, ++ final boolean intendingToBlock) { ++ return RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, onComplete, intendingToBlock, PrioritisedExecutor.Priority.NORMAL); ++ } ++ ++ /** ++ * Schedules a load to be executed asynchronously. This task will load the specified regionfile type, and then call ++ * {@code onComplete}. ++ *

    ++ * Impl notes: ++ *

      ++ *
    • ++ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may ++ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of ++ * data is undefined behaviour, and can cause deadlock. ++ *
    • ++ *
    ++ * ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param onComplete Consumer to execute once this task has completed ++ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost ++ * of this call. ++ * @param priority Minimum priority to load the data at. ++ * ++ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. ++ * ++ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) ++ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) ++ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) ++ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) ++ */ ++ public static Cancellable loadDataAsync(final ServerLevel world, final int chunkX, final int chunkZ, ++ final RegionFileType type, final BiConsumer onComplete, ++ final boolean intendingToBlock, final PrioritisedExecutor.Priority priority) { ++ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); ++ return thread.loadDataAsyncInternal(world, chunkX, chunkZ, type, onComplete, intendingToBlock, priority); ++ } ++ ++ private static Boolean doesRegionFileExist(final int chunkX, final int chunkZ, final boolean intendingToBlock, ++ final ChunkDataController taskController) { ++ final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); ++ if (intendingToBlock) { ++ return taskController.computeForRegionFile(chunkX, chunkZ, true, (final RegionFile file) -> { ++ if (file == null) { // null if no regionfile exists ++ return Boolean.FALSE; ++ } ++ ++ return file.hasChunk(chunkPos) ? Boolean.TRUE : Boolean.FALSE; ++ }); ++ } else { ++ // first check if the region file for sure does not exist ++ if (taskController.doesRegionFileNotExist(chunkX, chunkZ)) { ++ return Boolean.FALSE; ++ } // else: it either exists or is not known, fall back to checking the loaded region file ++ ++ return taskController.computeForRegionFileIfLoaded(chunkX, chunkZ, (final RegionFile file) -> { ++ if (file == null) { // null if not loaded ++ // not sure at this point, let the I/O thread figure it out ++ return Boolean.TRUE; ++ } ++ ++ return file.hasChunk(chunkPos) ? Boolean.TRUE : Boolean.FALSE; ++ }); ++ } ++ } ++ ++ Cancellable loadDataAsyncInternal(final ServerLevel world, final int chunkX, final int chunkZ, ++ final RegionFileType type, final BiConsumer onComplete, ++ final boolean intendingToBlock, final PrioritisedExecutor.Priority priority) { ++ final ChunkDataController taskController = this.getControllerFor(world, type); ++ ++ final ImmediateCallbackCompletion callbackInfo = new ImmediateCallbackCompletion(); ++ ++ final ChunkCoordinate key = new ChunkCoordinate(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ final BiFunction compute = (final ChunkCoordinate keyInMap, final ChunkDataTask running) -> { ++ if (running == null) { ++ // not scheduled ++ ++ if (callbackInfo.regionFileCalculation == null) { ++ // caller will compute this outside of compute(), to avoid holding the bin lock ++ callbackInfo.needsRegionFileTest = true; ++ return null; ++ } ++ ++ if (callbackInfo.regionFileCalculation == Boolean.FALSE) { ++ // not on disk ++ callbackInfo.data = null; ++ callbackInfo.throwable = null; ++ callbackInfo.completeNow = true; ++ return null; ++ } ++ ++ // set up task ++ final ChunkDataTask newTask = new ChunkDataTask( ++ world, chunkX, chunkZ, taskController, RegionFileIOThread.this, priority ++ ); ++ newTask.inProgressRead = new RegionFileIOThread.InProgressRead(); ++ newTask.inProgressRead.waiters.add(onComplete); ++ ++ callbackInfo.tasksNeedsScheduling = true; ++ return newTask; ++ } ++ ++ final CompoundTag pendingWrite = running.inProgressWrite; ++ ++ if (pendingWrite == ChunkDataTask.NOTHING_TO_WRITE) { ++ // need to add to waiters here, because the regionfile thread will use compute() to lock and check for cancellations ++ if (!running.inProgressRead.addToWaiters(onComplete)) { ++ callbackInfo.data = running.inProgressRead.value; ++ callbackInfo.throwable = running.inProgressRead.throwable; ++ callbackInfo.completeNow = true; ++ } ++ return running; ++ } ++ // using the result sync here - don't bump priority ++ ++ // at this stage we have to use the in progress write's data to avoid an order issue ++ callbackInfo.data = pendingWrite; ++ callbackInfo.throwable = null; ++ callbackInfo.completeNow = true; ++ return running; ++ }; ++ ++ ChunkDataTask curr = taskController.tasks.get(key); ++ if (curr == null) { ++ callbackInfo.regionFileCalculation = doesRegionFileExist(chunkX, chunkZ, intendingToBlock, taskController); ++ } ++ ChunkDataTask ret = taskController.tasks.compute(key, compute); ++ if (callbackInfo.needsRegionFileTest) { ++ // curr isn't null but when we went into compute() it was ++ callbackInfo.regionFileCalculation = doesRegionFileExist(chunkX, chunkZ, intendingToBlock, taskController); ++ // now it should be fine ++ ret = taskController.tasks.compute(key, compute); ++ } ++ ++ // needs to be scheduled ++ if (callbackInfo.tasksNeedsScheduling) { ++ ret.prioritisedTask.queue(); ++ } else if (callbackInfo.completeNow) { ++ try { ++ onComplete.accept(callbackInfo.data, callbackInfo.throwable); ++ } catch (final ThreadDeath thr) { ++ throw thr; ++ } catch (final Throwable thr) { ++ LOGGER.error("Callback " + ConcurrentUtil.genericToString(onComplete) + " synchronously failed to handle chunk data for task " + ret.toString(), thr); ++ } ++ } else { ++ // we're waiting on a task we didn't schedule, so raise its priority to what we want ++ ret.prioritisedTask.raisePriority(priority); ++ } ++ ++ return new CancellableRead(onComplete, ret); ++ } ++ ++ /** ++ * Schedules a load task to be executed asynchronously, and blocks on that task. ++ * ++ * @param world Chunk's world ++ * @param chunkX Chunk's x coordinate ++ * @param chunkZ Chunk's z coordinate ++ * @param type Regionfile type ++ * @param priority Minimum priority to load the data at. ++ * ++ * @return The chunk data for the chunk. Note that a {@code null} result means the chunk or regionfile does not exist on disk. ++ * ++ * @throws IOException If the load fails for any reason ++ */ ++ public static CompoundTag loadData(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, ++ final PrioritisedExecutor.Priority priority) throws IOException { ++ final CompletableFuture ret = new CompletableFuture<>(); ++ ++ RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, (final CompoundTag compound, final Throwable thr) -> { ++ if (thr != null) { ++ ret.completeExceptionally(thr); ++ } else { ++ ret.complete(compound); ++ } ++ }, true, priority); ++ ++ try { ++ return ret.join(); ++ } catch (final CompletionException ex) { ++ throw new IOException(ex); ++ } ++ } ++ ++ private static final class ImmediateCallbackCompletion { ++ ++ public CompoundTag data; ++ public Throwable throwable; ++ public boolean completeNow; ++ public boolean tasksNeedsScheduling; ++ public boolean needsRegionFileTest; ++ public Boolean regionFileCalculation; ++ ++ } ++ ++ static final class CancellableRead implements Cancellable { ++ ++ private BiConsumer callback; ++ private RegionFileIOThread.ChunkDataTask task; ++ ++ CancellableRead(final BiConsumer callback, final RegionFileIOThread.ChunkDataTask task) { ++ this.callback = callback; ++ this.task = task; ++ } ++ ++ @Override ++ public boolean cancel() { ++ final BiConsumer callback = this.callback; ++ final RegionFileIOThread.ChunkDataTask task = this.task; ++ ++ if (callback == null || task == null) { ++ return false; ++ } ++ ++ this.callback = null; ++ this.task = null; ++ ++ final RegionFileIOThread.InProgressRead read = task.inProgressRead; ++ ++ // read can be null if no read was scheduled (i.e no regionfile existed or chunk in regionfile didn't) ++ return (read != null && read.waiters.remove(callback)); ++ } ++ } ++ ++ static final class CancellableReads implements Cancellable { ++ ++ private Cancellable[] reads; ++ ++ protected static final VarHandle READS_HANDLE = ConcurrentUtil.getVarHandle(CancellableReads.class, "reads", Cancellable[].class); ++ ++ CancellableReads(final Cancellable[] reads) { ++ this.reads = reads; ++ } ++ ++ @Override ++ public boolean cancel() { ++ final Cancellable[] reads = (Cancellable[])READS_HANDLE.getAndSet((CancellableReads)this, (Cancellable[])null); ++ ++ if (reads == null) { ++ return false; ++ } ++ ++ boolean ret = false; ++ ++ for (final Cancellable read : reads) { ++ ret |= read.cancel(); ++ } ++ ++ return ret; ++ } ++ } ++ ++ static final class InProgressRead { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ CompoundTag value; ++ Throwable throwable; ++ final MultiThreadedQueue> waiters = new MultiThreadedQueue<>(); ++ ++ // rets false if already completed (callback not invoked), true if callback was added ++ boolean addToWaiters(final BiConsumer callback) { ++ return this.waiters.add(callback); ++ } ++ ++ void complete(final RegionFileIOThread.ChunkDataTask task, final CompoundTag value, final Throwable throwable) { ++ this.value = value; ++ this.throwable = throwable; ++ ++ BiConsumer consumer; ++ while ((consumer = this.waiters.pollOrBlockAdds()) != null) { ++ try { ++ consumer.accept(value, throwable); ++ } catch (final ThreadDeath thr) { ++ throw thr; ++ } catch (final Throwable thr) { ++ LOGGER.error("Callback " + ConcurrentUtil.genericToString(consumer) + " failed to handle chunk data for task " + task.toString(), thr); ++ } ++ } ++ } ++ } ++ ++ /** ++ * Class exists to replace {@link Long} usages as keys inside non-fastutil hashtables. The hash for some Long {@code x} ++ * is defined as {@code (x >>> 32) ^ x}. Chunk keys as long values are defined as {@code ((chunkX & 0xFFFFFFFFL) | (chunkZ << 32))}, ++ * which means the hashcode as a Long value will be {@code chunkX ^ chunkZ}. Given that most chunks are created within a radius arounds players, ++ * this will lead to many hash collisions. So, this class uses a better hashing algorithm so that usage of ++ * non-fastutil collections is not degraded. ++ */ ++ public static final class ChunkCoordinate implements Comparable { ++ ++ public final long key; ++ ++ public ChunkCoordinate(final long key) { ++ this.key = key; ++ } ++ ++ @Override ++ public int hashCode() { ++ return (int)HashCommon.mix(this.key); ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ ++ if (!(obj instanceof ChunkCoordinate)) { ++ return false; ++ } ++ ++ final ChunkCoordinate other = (ChunkCoordinate)obj; ++ ++ return this.key == other.key; ++ } ++ ++ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain ++ // is too large. So we should implement compareTo to help. ++ @Override ++ public int compareTo(final RegionFileIOThread.ChunkCoordinate other) { ++ return Long.compare(this.key, other.key); ++ } ++ ++ @Override ++ public String toString() { ++ return new ChunkPos(this.key).toString(); ++ } ++ } ++ ++ public static abstract class ChunkDataController { ++ ++ // ConcurrentHashMap synchronizes per chain, so reduce the chance of task's hashes colliding. ++ protected final ConcurrentHashMap tasks = new ConcurrentHashMap<>(8192, 0.10f); ++ ++ public final RegionFileType type; ++ ++ public ChunkDataController(final RegionFileType type) { ++ this.type = type; ++ } ++ ++ public abstract RegionFileStorage getCache(); ++ ++ public abstract void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException; ++ ++ public abstract CompoundTag readData(final int chunkX, final int chunkZ) throws IOException; ++ ++ public boolean hasTasks() { ++ return !this.tasks.isEmpty(); ++ } ++ ++ public boolean doesRegionFileNotExist(final int chunkX, final int chunkZ) { ++ return this.getCache().doesRegionFileNotExistNoIO(new ChunkPos(chunkX, chunkZ)); ++ } ++ ++ public T computeForRegionFile(final int chunkX, final int chunkZ, final boolean existingOnly, final Function function) { ++ final RegionFileStorage cache = this.getCache(); ++ final RegionFile regionFile; ++ synchronized (cache) { ++ try { ++ regionFile = cache.getRegionFile(new ChunkPos(chunkX, chunkZ), existingOnly, true); ++ } catch (final IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ } ++ ++ try { ++ return function.apply(regionFile); ++ } finally { ++ if (regionFile != null) { ++ regionFile.fileLock.unlock(); ++ } ++ } ++ } ++ ++ public T computeForRegionFileIfLoaded(final int chunkX, final int chunkZ, final Function function) { ++ final RegionFileStorage cache = this.getCache(); ++ final RegionFile regionFile; ++ ++ synchronized (cache) { ++ regionFile = cache.getRegionFileIfLoaded(new ChunkPos(chunkX, chunkZ)); ++ if (regionFile != null) { ++ regionFile.fileLock.lock(); ++ } ++ } ++ ++ try { ++ return function.apply(regionFile); ++ } finally { ++ if (regionFile != null) { ++ regionFile.fileLock.unlock(); ++ } ++ } ++ } ++ } ++ ++ static final class ChunkDataTask implements Runnable { ++ ++ protected static final CompoundTag NOTHING_TO_WRITE = new CompoundTag(); ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ RegionFileIOThread.InProgressRead inProgressRead; ++ volatile CompoundTag inProgressWrite = NOTHING_TO_WRITE; // only needs to be acquire/release ++ ++ boolean failedWrite; ++ ++ final ServerLevel world; ++ final int chunkX; ++ final int chunkZ; ++ final RegionFileIOThread.ChunkDataController taskController; ++ ++ final PrioritisedExecutor.PrioritisedTask prioritisedTask; ++ ++ /* ++ * IO thread will perform reads before writes for a given chunk x and z ++ * ++ * How reads/writes are scheduled: ++ * ++ * If read is scheduled while scheduling write, take no special action and just schedule write ++ * If read is scheduled while scheduling read and no write is scheduled, chain the read task ++ * ++ * ++ * If write is scheduled while scheduling read, use the pending write data and ret immediately (so no read is scheduled) ++ * If write is scheduled while scheduling write (ignore read in progress), overwrite the write in progress data ++ * ++ * This allows the reads and writes to act as if they occur synchronously to the thread scheduling them, however ++ * it fails to properly propagate write failures thanks to writes overwriting each other ++ */ ++ ++ public ChunkDataTask(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileIOThread.ChunkDataController taskController, ++ final PrioritisedExecutor executor, final PrioritisedExecutor.Priority priority) { ++ this.world = world; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.taskController = taskController; ++ this.prioritisedTask = executor.createTask(this, priority); ++ } ++ ++ @Override ++ public String toString() { ++ return "Task for world: '" + this.world.getWorld().getName() + "' at (" + this.chunkX + "," + this.chunkZ + ++ ") type: " + this.taskController.type.name() + ", hash: " + this.hashCode(); ++ } ++ ++ @Override ++ public void run() { ++ final RegionFileIOThread.InProgressRead read = this.inProgressRead; ++ final ChunkCoordinate chunkKey = new ChunkCoordinate(CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ)); ++ ++ if (read != null) { ++ final boolean[] canRead = new boolean[] { true }; ++ ++ if (read.waiters.isEmpty()) { ++ // cancelled read? go to task controller to confirm ++ final ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final ChunkCoordinate keyInMap, final ChunkDataTask valueInMap) -> { ++ if (valueInMap == null) { ++ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); ++ } ++ if (valueInMap != ChunkDataTask.this) { ++ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); ++ } ++ ++ if (!read.waiters.isEmpty()) { // as per usual IntelliJ is unable to figure out that there are concurrent accesses. ++ return valueInMap; ++ } else { ++ canRead[0] = false; ++ } ++ ++ return valueInMap.inProgressWrite == NOTHING_TO_WRITE ? null : valueInMap; ++ }); ++ ++ if (inMap == null) { ++ // read is cancelled - and no write pending, so we're done ++ return; ++ } ++ // if there is a write in progress, we don't actually have to worry about waiters gaining new entries - ++ // the readers will just use the in progress write, so the value in canRead is good to use without ++ // further synchronisation. ++ } ++ ++ if (canRead[0]) { ++ CompoundTag compound = null; ++ Throwable throwable = null; ++ ++ try { ++ compound = this.taskController.readData(this.chunkX, this.chunkZ); ++ } catch (final ThreadDeath thr) { ++ throw thr; ++ } catch (final Throwable thr) { ++ throwable = thr; ++ LOGGER.error("Failed to read chunk data for task: " + this.toString(), thr); ++ } ++ read.complete(this, compound, throwable); ++ } ++ } ++ ++ CompoundTag write = this.inProgressWrite; ++ ++ if (write == NOTHING_TO_WRITE) { ++ final ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final ChunkCoordinate keyInMap, final ChunkDataTask valueInMap) -> { ++ if (valueInMap == null) { ++ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); ++ } ++ if (valueInMap != ChunkDataTask.this) { ++ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); ++ } ++ return valueInMap.inProgressWrite == NOTHING_TO_WRITE ? null : valueInMap; ++ }); ++ ++ if (inMap == null) { ++ return; // set the task value to null, indicating we're done ++ } // else: inProgressWrite changed, so now we have something to write ++ } ++ ++ for (;;) { ++ write = this.inProgressWrite; ++ final CompoundTag dataWritten = write; ++ ++ boolean failedWrite = false; ++ ++ try { ++ this.taskController.writeData(this.chunkX, this.chunkZ, write); ++ } catch (final ThreadDeath thr) { ++ throw thr; ++ } catch (final Throwable thr) { ++ if (thr instanceof RegionFileStorage.RegionFileSizeException) { ++ final int maxSize = RegionFile.MAX_CHUNK_SIZE / (1024 * 1024); ++ LOGGER.error("Chunk at (" + this.chunkX + "," + this.chunkZ + ") in '" + this.world.getWorld().getName() + "' exceeds max size of " + maxSize + "MiB, it has been deleted from disk."); ++ } else { ++ failedWrite = thr instanceof IOException; ++ LOGGER.error("Failed to write chunk data for task: " + this.toString(), thr); ++ } ++ } ++ ++ final boolean finalFailWrite = failedWrite; ++ final boolean[] done = new boolean[] { false }; ++ ++ this.taskController.tasks.compute(chunkKey, (final ChunkCoordinate keyInMap, final ChunkDataTask valueInMap) -> { ++ if (valueInMap == null) { ++ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); ++ } ++ if (valueInMap != ChunkDataTask.this) { ++ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); ++ } ++ if (valueInMap.inProgressWrite == dataWritten) { ++ valueInMap.failedWrite = finalFailWrite; ++ done[0] = true; ++ // keep the data in map if we failed the write so we can try to prevent data loss ++ return finalFailWrite ? valueInMap : null; ++ } ++ // different data than expected, means we need to retry write ++ return valueInMap; ++ }); ++ ++ if (done[0]) { ++ return; ++ } ++ ++ // fetch & write new data ++ continue; ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/light/LightQueue.java b/src/main/java/io/papermc/paper/chunk/system/light/LightQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..de28d6ee71990da74d9deb360fac8bde5adbc918 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/light/LightQueue.java +@@ -0,0 +1,283 @@ ++package io.papermc.paper.chunk.system.light; ++ ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.starlight.common.light.BlockStarLightEngine; ++import ca.spottedleaf.starlight.common.light.SkyStarLightEngine; ++import ca.spottedleaf.starlight.common.light.StarLightInterface; ++import io.papermc.paper.util.CoordinateUtils; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.shorts.ShortCollection; ++import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.SectionPos; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import java.util.ArrayList; ++import java.util.HashSet; ++import java.util.List; ++import java.util.Set; ++import java.util.concurrent.CompletableFuture; ++import java.util.function.BooleanSupplier; ++ ++public final class LightQueue { ++ ++ protected final Long2ObjectOpenHashMap chunkTasks = new Long2ObjectOpenHashMap<>(); ++ protected final StarLightInterface manager; ++ protected final ServerLevel world; ++ ++ public LightQueue(final StarLightInterface manager) { ++ this.manager = manager; ++ this.world = ((ServerLevel)manager.getWorld()); ++ } ++ ++ public void lowerPriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { ++ final ChunkTasks task; ++ synchronized (this) { ++ task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } ++ if (task != null) { ++ task.lowerPriority(priority); ++ } ++ } ++ ++ public void setPriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { ++ final ChunkTasks task; ++ synchronized (this) { ++ task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } ++ if (task != null) { ++ task.setPriority(priority); ++ } ++ } ++ ++ public void raisePriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { ++ final ChunkTasks task; ++ synchronized (this) { ++ task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } ++ if (task != null) { ++ task.raisePriority(priority); ++ } ++ } ++ ++ public PrioritisedExecutor.Priority getPriority(final int chunkX, final int chunkZ) { ++ final ChunkTasks task; ++ synchronized (this) { ++ task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } ++ if (task != null) { ++ return task.getPriority(); ++ } ++ ++ return PrioritisedExecutor.Priority.COMPLETING; ++ } ++ ++ public boolean isEmpty() { ++ synchronized (this) { ++ return this.chunkTasks.isEmpty(); ++ } ++ } ++ ++ public ChunkTasks queueBlockChange(final BlockPos pos) { ++ final ChunkTasks tasks; ++ synchronized (this) { ++ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { ++ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this); ++ }); ++ tasks.changedPositions.add(pos.immutable()); ++ } ++ ++ tasks.schedule(); ++ ++ return tasks; ++ } ++ ++ public ChunkTasks queueSectionChange(final SectionPos pos, final boolean newEmptyValue) { ++ final ChunkTasks tasks; ++ synchronized (this) { ++ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { ++ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this); ++ }); ++ ++ if (tasks.changedSectionSet == null) { ++ tasks.changedSectionSet = new Boolean[this.manager.maxSection - this.manager.minSection + 1]; ++ } ++ tasks.changedSectionSet[pos.getY() - this.manager.minSection] = Boolean.valueOf(newEmptyValue); ++ } ++ ++ tasks.schedule(); ++ ++ return tasks; ++ } ++ ++ public ChunkTasks queueChunkLightTask(final ChunkPos pos, final BooleanSupplier lightTask, final PrioritisedExecutor.Priority priority) { ++ final ChunkTasks tasks; ++ synchronized (this) { ++ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { ++ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this, priority); ++ }); ++ if (tasks.lightTasks == null) { ++ tasks.lightTasks = new ArrayList<>(); ++ } ++ tasks.lightTasks.add(lightTask); ++ } ++ ++ tasks.schedule(); ++ ++ return tasks; ++ } ++ ++ public ChunkTasks queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) { ++ final ChunkTasks tasks; ++ synchronized (this) { ++ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { ++ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this); ++ }); ++ ++ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksSky; ++ if (queuedEdges == null) { ++ queuedEdges = tasks.queuedEdgeChecksSky = new ShortOpenHashSet(); ++ } ++ queuedEdges.addAll(sections); ++ } ++ ++ tasks.schedule(); ++ ++ return tasks; ++ } ++ ++ public ChunkTasks queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) { ++ final ChunkTasks tasks; ++ ++ synchronized (this) { ++ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { ++ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this); ++ }); ++ ++ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksBlock; ++ if (queuedEdges == null) { ++ queuedEdges = tasks.queuedEdgeChecksBlock = new ShortOpenHashSet(); ++ } ++ queuedEdges.addAll(sections); ++ } ++ ++ tasks.schedule(); ++ ++ return tasks; ++ } ++ ++ public void removeChunk(final ChunkPos pos) { ++ final ChunkTasks tasks; ++ synchronized (this) { ++ tasks = this.chunkTasks.remove(CoordinateUtils.getChunkKey(pos)); ++ } ++ if (tasks != null && tasks.cancel()) { ++ tasks.onComplete.complete(null); ++ } ++ } ++ ++ public static final class ChunkTasks implements Runnable { ++ ++ public final CompletableFuture onComplete = new CompletableFuture<>(); ++ public boolean isTicketAdded; ++ public final long chunkCoordinate; ++ ++ private final StarLightInterface lightEngine; ++ private final LightQueue queue; ++ private final PrioritisedExecutor.PrioritisedTask task; ++ private final Set changedPositions = new HashSet<>(); ++ private Boolean[] changedSectionSet; ++ private ShortOpenHashSet queuedEdgeChecksSky; ++ private ShortOpenHashSet queuedEdgeChecksBlock; ++ private List lightTasks; ++ ++ public ChunkTasks(final long chunkCoordinate, final StarLightInterface lightEngine, final LightQueue queue) { ++ this(chunkCoordinate, lightEngine, queue, PrioritisedExecutor.Priority.NORMAL); ++ } ++ ++ public ChunkTasks(final long chunkCoordinate, final StarLightInterface lightEngine, final LightQueue queue, ++ final PrioritisedExecutor.Priority priority) { ++ this.chunkCoordinate = chunkCoordinate; ++ this.lightEngine = lightEngine; ++ this.queue = queue; ++ this.task = queue.world.chunkTaskScheduler.radiusAwareScheduler.createTask( ++ CoordinateUtils.getChunkX(chunkCoordinate), CoordinateUtils.getChunkZ(chunkCoordinate), ++ ChunkStatus.LIGHT.writeRadius, this, priority ++ ); ++ } ++ ++ public void schedule() { ++ this.task.queue(); ++ } ++ ++ public boolean cancel() { ++ return this.task.cancel(); ++ } ++ ++ public PrioritisedExecutor.Priority getPriority() { ++ return this.task.getPriority(); ++ } ++ ++ public void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ this.task.lowerPriority(priority); ++ } ++ ++ public void setPriority(final PrioritisedExecutor.Priority priority) { ++ this.task.setPriority(priority); ++ } ++ ++ public void raisePriority(final PrioritisedExecutor.Priority priority) { ++ this.task.raisePriority(priority); ++ } ++ ++ @Override ++ public void run() { ++ synchronized (this.queue) { ++ this.queue.chunkTasks.remove(this.chunkCoordinate); ++ } ++ ++ boolean litChunk = false; ++ if (this.lightTasks != null) { ++ for (final BooleanSupplier run : this.lightTasks) { ++ if (run.getAsBoolean()) { ++ litChunk = true; ++ break; ++ } ++ } ++ } ++ ++ final SkyStarLightEngine skyEngine = this.lightEngine.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.lightEngine.getBlockLightEngine(); ++ try { ++ final long coordinate = this.chunkCoordinate; ++ final int chunkX = CoordinateUtils.getChunkX(coordinate); ++ final int chunkZ = CoordinateUtils.getChunkZ(coordinate); ++ ++ final Set positions = this.changedPositions; ++ final Boolean[] sectionChanges = this.changedSectionSet; ++ ++ if (!litChunk) { ++ if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) { ++ skyEngine.blocksChangedInChunk(this.lightEngine.getLightAccess(), chunkX, chunkZ, positions, sectionChanges); ++ } ++ if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) { ++ blockEngine.blocksChangedInChunk(this.lightEngine.getLightAccess(), chunkX, chunkZ, positions, sectionChanges); ++ } ++ ++ if (skyEngine != null && this.queuedEdgeChecksSky != null) { ++ skyEngine.checkChunkEdges(this.lightEngine.getLightAccess(), chunkX, chunkZ, this.queuedEdgeChecksSky); ++ } ++ if (blockEngine != null && this.queuedEdgeChecksBlock != null) { ++ blockEngine.checkChunkEdges(this.lightEngine.getLightAccess(), chunkX, chunkZ, this.queuedEdgeChecksBlock); ++ } ++ } ++ ++ this.onComplete.complete(null); ++ } finally { ++ this.lightEngine.releaseSkyLightEngine(skyEngine); ++ this.lightEngine.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/poi/PoiChunk.java b/src/main/java/io/papermc/paper/chunk/system/poi/PoiChunk.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d72041aa814ff179e6e29a45dcd359a91d426d47 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/poi/PoiChunk.java +@@ -0,0 +1,213 @@ ++package io.papermc.paper.chunk.system.poi; ++ ++import com.mojang.logging.LogUtils; ++import com.mojang.serialization.Codec; ++import com.mojang.serialization.DataResult; ++import io.papermc.paper.util.CoordinateUtils; ++import io.papermc.paper.util.TickThread; ++import io.papermc.paper.util.WorldUtil; ++import net.minecraft.SharedConstants; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.Tag; ++import net.minecraft.resources.RegistryOps; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.ai.village.poi.PoiManager; ++import net.minecraft.world.entity.ai.village.poi.PoiSection; ++import org.slf4j.Logger; ++ ++import java.util.Optional; ++ ++public final class PoiChunk { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ public final ServerLevel world; ++ public final int chunkX; ++ public final int chunkZ; ++ public final int minSection; ++ public final int maxSection; ++ ++ protected final PoiSection[] sections; ++ ++ private boolean isDirty; ++ private boolean loaded; ++ ++ public PoiChunk(final ServerLevel world, final int chunkX, final int chunkZ, final int minSection, final int maxSection) { ++ this(world, chunkX, chunkZ, minSection, maxSection, new PoiSection[maxSection - minSection + 1]); ++ } ++ ++ public PoiChunk(final ServerLevel world, final int chunkX, final int chunkZ, final int minSection, final int maxSection, final PoiSection[] sections) { ++ this.world = world; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.minSection = minSection; ++ this.maxSection = maxSection; ++ this.sections = sections; ++ if (this.sections.length != (maxSection - minSection + 1)) { ++ throw new IllegalStateException("Incorrect length used, expected " + (maxSection - minSection + 1) + ", got " + this.sections.length); ++ } ++ } ++ ++ public void load() { ++ TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Loading in poi chunk off-main"); ++ if (this.loaded) { ++ return; ++ } ++ this.loaded = true; ++ this.world.chunkSource.getPoiManager().loadInPoiChunk(this); ++ } ++ ++ public boolean isLoaded() { ++ return this.loaded; ++ } ++ ++ public boolean isEmpty() { ++ for (final PoiSection section : this.sections) { ++ if (section != null && !section.isEmpty()) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ ++ public PoiSection getOrCreateSection(final int chunkY) { ++ if (chunkY >= this.minSection && chunkY <= this.maxSection) { ++ final int idx = chunkY - this.minSection; ++ final PoiSection ret = this.sections[idx]; ++ if (ret != null) { ++ return ret; ++ } ++ ++ final PoiManager poiManager = this.world.getPoiManager(); ++ final long key = CoordinateUtils.getChunkSectionKey(this.chunkX, chunkY, this.chunkZ); ++ ++ return this.sections[idx] = new PoiSection(() -> { ++ poiManager.setDirty(key); ++ }); ++ } ++ throw new IllegalArgumentException("chunkY is out of bounds, chunkY: " + chunkY + " outside [" + this.minSection + "," + this.maxSection + "]"); ++ } ++ ++ public PoiSection getSection(final int chunkY) { ++ if (chunkY >= this.minSection && chunkY <= this.maxSection) { ++ return this.sections[chunkY - this.minSection]; ++ } ++ return null; ++ } ++ ++ public Optional getSectionForVanilla(final int chunkY) { ++ if (chunkY >= this.minSection && chunkY <= this.maxSection) { ++ final PoiSection ret = this.sections[chunkY - this.minSection]; ++ return ret == null ? Optional.empty() : ret.noAllocateOptional; ++ } ++ return Optional.empty(); ++ } ++ ++ public boolean isDirty() { ++ return this.isDirty; ++ } ++ ++ public void setDirty(final boolean dirty) { ++ this.isDirty = dirty; ++ } ++ ++ // returns null if empty ++ public CompoundTag save() { ++ final RegistryOps registryOps = RegistryOps.create(NbtOps.INSTANCE, world.getPoiManager().registryAccess); ++ ++ final CompoundTag ret = new CompoundTag(); ++ final CompoundTag sections = new CompoundTag(); ++ ret.put("Sections", sections); ++ ++ ret.putInt("DataVersion", SharedConstants.getCurrentVersion().getDataVersion().getVersion()); ++ ++ final ServerLevel world = this.world; ++ final PoiManager poiManager = world.getPoiManager(); ++ final int chunkX = this.chunkX; ++ final int chunkZ = this.chunkZ; ++ ++ for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) { ++ final PoiSection chunk = this.sections[sectionY - this.minSection]; ++ if (chunk == null || chunk.isEmpty()) { ++ continue; ++ } ++ ++ final long key = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ); ++ // codecs are honestly such a fucking disaster. What the fuck is this trash? ++ final Codec codec = PoiSection.codec(() -> { ++ poiManager.setDirty(key); ++ }); ++ ++ final DataResult serializedResult = codec.encodeStart(registryOps, chunk); ++ final int finalSectionY = sectionY; ++ final Tag serialized = serializedResult.resultOrPartial((final String description) -> { ++ LOGGER.error("Failed to serialize poi chunk for world: " + world.getWorld().getName() + ", chunk: (" + chunkX + "," + finalSectionY + "," + chunkZ + "); description: " + description); ++ }).orElse(null); ++ if (serialized == null) { ++ // failed, should be logged from the resultOrPartial ++ continue; ++ } ++ ++ sections.put(Integer.toString(sectionY), serialized); ++ } ++ ++ return sections.isEmpty() ? null : ret; ++ } ++ ++ public static PoiChunk empty(final ServerLevel world, final int chunkX, final int chunkZ) { ++ final PoiChunk ret = new PoiChunk(world, chunkX, chunkZ, WorldUtil.getMinSection(world), WorldUtil.getMaxSection(world)); ++ ret.loaded = true; ++ return ret; ++ } ++ ++ public static PoiChunk parse(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data) { ++ final PoiChunk ret = empty(world, chunkX, chunkZ); ++ ++ final RegistryOps registryOps = RegistryOps.create(NbtOps.INSTANCE, world.getPoiManager().registryAccess); ++ ++ final CompoundTag sections = data.getCompound("Sections"); ++ ++ if (sections.isEmpty()) { ++ // nothing to parse ++ return ret; ++ } ++ ++ final PoiManager poiManager = world.getPoiManager(); ++ ++ boolean readAnything = false; ++ ++ for (int sectionY = ret.minSection; sectionY <= ret.maxSection; ++sectionY) { ++ final String key = Integer.toString(sectionY); ++ if (!sections.contains(key)) { ++ continue; ++ } ++ ++ final long coordinateKey = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ); ++ // codecs are honestly such a fucking disaster. What the fuck is this trash? ++ final Codec codec = PoiSection.codec(() -> { ++ poiManager.setDirty(coordinateKey); ++ }); ++ ++ final CompoundTag section = sections.getCompound(key); ++ final DataResult deserializeResult = codec.parse(registryOps, section); ++ final int finalSectionY = sectionY; ++ final PoiSection deserialized = deserializeResult.resultOrPartial((final String description) -> { ++ LOGGER.error("Failed to deserialize poi chunk for world: " + world.getWorld().getName() + ", chunk: (" + chunkX + "," + finalSectionY + "," + chunkZ + "); description: " + description); ++ }).orElse(null); ++ ++ if (deserialized == null || deserialized.isEmpty()) { ++ // completely empty, no point in storing this ++ continue; ++ } ++ ++ readAnything = true; ++ ret.sections[sectionY - ret.minSection] = deserialized; ++ } ++ ++ ret.loaded = !readAnything; // Set loaded to false if we read anything to ensure proper callbacks to PoiManager are made on #load ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkFullTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkFullTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..679ed4d53269e1113035b462cf74ab16a231e22e +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkFullTask.java +@@ -0,0 +1,135 @@ ++package io.papermc.paper.chunk.system.scheduling; ++ ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.chunk.system.poi.PoiChunk; ++import net.minecraft.server.level.ChunkMap; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.ImposterProtoChunk; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.ProtoChunk; ++import org.slf4j.Logger; ++import java.lang.invoke.VarHandle; ++ ++public final class ChunkFullTask extends ChunkProgressionTask implements Runnable { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ protected final NewChunkHolder chunkHolder; ++ protected final ChunkAccess fromChunk; ++ protected final PrioritisedExecutor.PrioritisedTask convertToFullTask; ++ ++ public ChunkFullTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, ++ final NewChunkHolder chunkHolder, final ChunkAccess fromChunk, final PrioritisedExecutor.Priority priority) { ++ super(scheduler, world, chunkX, chunkZ); ++ this.chunkHolder = chunkHolder; ++ this.fromChunk = fromChunk; ++ this.convertToFullTask = scheduler.createChunkTask(chunkX, chunkZ, this, priority); ++ } ++ ++ @Override ++ public ChunkStatus getTargetStatus() { ++ return ChunkStatus.FULL; ++ } ++ ++ @Override ++ public void run() { ++ // See Vanilla protoChunkToFullChunk for what this function should be doing ++ final LevelChunk chunk; ++ try { ++ // moved from the load from nbt stage into here ++ final PoiChunk poiChunk = this.chunkHolder.getPoiChunk(); ++ if (poiChunk == null) { ++ LOGGER.error("Expected poi chunk to be loaded with chunk for task " + this.toString()); ++ } else { ++ poiChunk.load(); ++ this.world.getPoiManager().checkConsistency(this.fromChunk); ++ } ++ ++ if (this.fromChunk instanceof ImposterProtoChunk wrappedFull) { ++ chunk = wrappedFull.getWrapped(); ++ } else { ++ final ServerLevel world = this.world; ++ final ProtoChunk protoChunk = (ProtoChunk)this.fromChunk; ++ chunk = new LevelChunk(this.world, protoChunk, (final LevelChunk unused) -> { ++ ChunkMap.postLoadProtoChunk(world, protoChunk.getEntities(), protoChunk.getPos()); // Paper - rewrite chunk system ++ }); ++ } ++ ++ chunk.setChunkHolder(this.scheduler.chunkHolderManager.getChunkHolder(this.chunkX, this.chunkZ)); // replaces setFullStatus ++ chunk.runPostLoad(); ++ // Unlike Vanilla, we load the entity chunk here, as we load the NBT in empty status (unlike Vanilla) ++ // This brings entity addition back in line with older versions of the game ++ // Since we load the NBT in the empty status, this will never block for I/O ++ this.world.chunkTaskScheduler.chunkHolderManager.getOrCreateEntityChunk(this.chunkX, this.chunkZ, false); ++ ++ // we don't need the entitiesInLevel trash, this system doesn't double run callbacks ++ chunk.setLoaded(true); ++ chunk.registerAllBlockEntitiesAfterLevelLoad(); ++ chunk.registerTickContainerInLevel(this.world); ++ } catch (final Throwable throwable) { ++ this.complete(null, throwable); ++ ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ return; ++ } ++ this.complete(chunk, null); ++ } ++ ++ protected volatile boolean scheduled; ++ protected static final VarHandle SCHEDULED_HANDLE = ConcurrentUtil.getVarHandle(ChunkFullTask.class, "scheduled", boolean.class); ++ ++ @Override ++ public boolean isScheduled() { ++ return this.scheduled; ++ } ++ ++ @Override ++ public void schedule() { ++ if ((boolean)SCHEDULED_HANDLE.getAndSet((ChunkFullTask)this, true)) { ++ throw new IllegalStateException("Cannot double call schedule()"); ++ } ++ this.convertToFullTask.queue(); ++ } ++ ++ @Override ++ public void cancel() { ++ if (this.convertToFullTask.cancel()) { ++ this.complete(null, null); ++ } ++ } ++ ++ @Override ++ public PrioritisedExecutor.Priority getPriority() { ++ return this.convertToFullTask.getPriority(); ++ } ++ ++ @Override ++ public void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ this.convertToFullTask.lowerPriority(priority); ++ } ++ ++ @Override ++ public void setPriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ this.convertToFullTask.setPriority(priority); ++ } ++ ++ @Override ++ public void raisePriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ this.convertToFullTask.raisePriority(priority); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5b446e6ac151f99f64f0c442d0b40b5e251bc4c4 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java +@@ -0,0 +1,1500 @@ ++package io.papermc.paper.chunk.system.scheduling; ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; ++import ca.spottedleaf.concurrentutil.map.SWMRLong2ObjectHashTable; ++import com.google.common.collect.ImmutableList; ++import com.google.gson.JsonArray; ++import com.google.gson.JsonObject; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader; ++import io.papermc.paper.chunk.system.io.RegionFileIOThread; ++import io.papermc.paper.chunk.system.poi.PoiChunk; ++import io.papermc.paper.threadedregions.TickRegions; ++import io.papermc.paper.util.CoordinateUtils; ++import io.papermc.paper.util.TickThread; ++import io.papermc.paper.world.ChunkEntitySlices; ++import it.unimi.dsi.fastutil.longs.Long2ByteLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ByteMap; ++import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2IntMap; ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; ++import net.minecraft.nbt.CompoundTag; ++import io.papermc.paper.chunk.system.ChunkSystem; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ChunkLevel; ++import net.minecraft.server.level.FullChunkStatus; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.Ticket; ++import net.minecraft.server.level.TicketType; ++import net.minecraft.util.SortedArraySet; ++import net.minecraft.util.Unit; ++import net.minecraft.world.level.ChunkPos; ++import org.bukkit.plugin.Plugin; ++import org.slf4j.Logger; ++import java.io.IOException; ++import java.text.DecimalFormat; ++import java.util.ArrayDeque; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Iterator; ++import java.util.List; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.concurrent.atomic.AtomicReference; ++import java.util.concurrent.locks.LockSupport; ++import java.util.function.Predicate; ++ ++public final class ChunkHolderManager { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ public static final int FULL_LOADED_TICKET_LEVEL = 33; ++ public static final int BLOCK_TICKING_TICKET_LEVEL = 32; ++ public static final int ENTITY_TICKING_TICKET_LEVEL = 31; ++ public static final int MAX_TICKET_LEVEL = ChunkLevel.MAX_LEVEL; // inclusive ++ ++ private static final long NO_TIMEOUT_MARKER = Long.MIN_VALUE; ++ private static final long PROBE_MARKER = Long.MIN_VALUE + 1; ++ public final ReentrantAreaLock ticketLockArea; ++ ++ private final ConcurrentHashMap>> tickets = new java.util.concurrent.ConcurrentHashMap<>(); ++ private final ConcurrentHashMap sectionToChunkToExpireCount = new java.util.concurrent.ConcurrentHashMap<>(); ++ final ChunkQueue unloadQueue; ++ ++ public boolean processTicketUpdates(final int posX, final int posZ) { ++ final int ticketShift = ThreadedTicketLevelPropagator.SECTION_SHIFT; ++ final int ticketMask = (1 << ticketShift) - 1; ++ final List scheduledTasks = new ArrayList<>(); ++ final List changedFullStatus = new ArrayList<>(); ++ final boolean ret; ++ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( ++ ((posX >> ticketShift) - 1) << ticketShift, ++ ((posZ >> ticketShift) - 1) << ticketShift, ++ (((posX >> ticketShift) + 1) << ticketShift) | ticketMask, ++ (((posZ >> ticketShift) + 1) << ticketShift) | ticketMask ++ ); ++ try { ++ ret = this.processTicketUpdatesNoLock(posX >> ticketShift, posZ >> ticketShift, scheduledTasks, changedFullStatus); ++ } finally { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ ++ this.addChangedStatuses(changedFullStatus); ++ ++ for (int i = 0, len = scheduledTasks.size(); i < len; ++i) { ++ scheduledTasks.get(i).schedule(); ++ } ++ ++ return ret; ++ } ++ ++ private boolean processTicketUpdatesNoLock(final int sectionX, final int sectionZ, final List scheduledTasks, ++ final List changedFullStatus) { ++ return this.ticketLevelPropagator.performUpdate( ++ sectionX, sectionZ, this.taskScheduler.schedulingLockArea, scheduledTasks, changedFullStatus ++ ); ++ } ++ ++ private final SWMRLong2ObjectHashTable chunkHolders = new SWMRLong2ObjectHashTable<>(16384, 0.25f); ++ // what a disaster of a name ++ // this is a map of removal tick to a map of chunks and the number of tickets a chunk has that are to expire that tick ++ private final Long2ObjectOpenHashMap removeTickToChunkExpireTicketCount = new Long2ObjectOpenHashMap<>(); ++ private final ServerLevel world; ++ private final ChunkTaskScheduler taskScheduler; ++ private long currentTick; ++ ++ private final ArrayDeque pendingFullLoadUpdate = new ArrayDeque<>(); ++ private final ObjectRBTreeSet autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> { ++ if (c1 == c2) { ++ return 0; ++ } ++ ++ final int saveTickCompare = Long.compare(c1.lastAutoSave, c2.lastAutoSave); ++ ++ if (saveTickCompare != 0) { ++ return saveTickCompare; ++ } ++ ++ final long coord1 = CoordinateUtils.getChunkKey(c1.chunkX, c1.chunkZ); ++ final long coord2 = CoordinateUtils.getChunkKey(c2.chunkX, c2.chunkZ); ++ ++ if (coord1 == coord2) { ++ throw new IllegalStateException("Duplicate chunkholder in auto save queue"); ++ } ++ ++ return Long.compare(coord1, coord2); ++ }); ++ ++ public ChunkHolderManager(final ServerLevel world, final ChunkTaskScheduler taskScheduler) { ++ this.world = world; ++ this.taskScheduler = taskScheduler; ++ this.ticketLockArea = new ReentrantAreaLock(taskScheduler.getChunkSystemLockShift()); ++ this.unloadQueue = new ChunkQueue(world.getRegionChunkShift()); ++ } ++ ++ private final AtomicLong statusUpgradeId = new AtomicLong(); ++ ++ long getNextStatusUpgradeId() { ++ return this.statusUpgradeId.incrementAndGet(); ++ } ++ ++ public List getOldChunkHolders() { ++ final List holders = this.getChunkHolders(); ++ final List ret = new ArrayList<>(holders.size()); ++ for (final NewChunkHolder holder : holders) { ++ ret.add(holder.vanillaChunkHolder); ++ } ++ return ret; ++ } ++ ++ public List getChunkHolders() { ++ final List ret = new ArrayList<>(this.chunkHolders.size()); ++ this.chunkHolders.forEachValue(ret::add); ++ return ret; ++ } ++ ++ public int size() { ++ return this.chunkHolders.size(); ++ } ++ ++ public void close(final boolean save, final boolean halt) { ++ TickThread.ensureTickThread("Closing world off-main"); ++ if (halt) { ++ LOGGER.info("Waiting 60s for chunk system to halt for world '" + this.world.getWorld().getName() + "'"); ++ if (!this.taskScheduler.halt(true, TimeUnit.SECONDS.toNanos(60L))) { ++ LOGGER.warn("Failed to halt world generation/loading tasks for world '" + this.world.getWorld().getName() + "'"); ++ } else { ++ LOGGER.info("Halted chunk system for world '" + this.world.getWorld().getName() + "'"); ++ } ++ } ++ ++ if (save) { ++ this.saveAllChunks(true, true, true); ++ } ++ ++ if (this.world.chunkDataControllerNew.hasTasks() || this.world.entityDataControllerNew.hasTasks() || this.world.poiDataControllerNew.hasTasks()) { ++ RegionFileIOThread.flush(); ++ } ++ ++ // kill regionfile cache ++ try { ++ this.world.chunkDataControllerNew.getCache().close(); ++ } catch (final IOException ex) { ++ LOGGER.error("Failed to close chunk regionfile cache for world '" + this.world.getWorld().getName() + "'", ex); ++ } ++ try { ++ this.world.entityDataControllerNew.getCache().close(); ++ } catch (final IOException ex) { ++ LOGGER.error("Failed to close entity regionfile cache for world '" + this.world.getWorld().getName() + "'", ex); ++ } ++ try { ++ this.world.poiDataControllerNew.getCache().close(); ++ } catch (final IOException ex) { ++ LOGGER.error("Failed to close poi regionfile cache for world '" + this.world.getWorld().getName() + "'", ex); ++ } ++ } ++ ++ void ensureInAutosave(final NewChunkHolder holder) { ++ if (!this.autoSaveQueue.contains(holder)) { ++ holder.lastAutoSave = MinecraftServer.currentTick; ++ this.autoSaveQueue.add(holder); ++ } ++ } ++ ++ public void autoSave() { ++ final List reschedule = new ArrayList<>(); ++ final long currentTick = MinecraftServer.currentTickLong; ++ final long maxSaveTime = currentTick - this.world.paperConfig().chunks.autoSaveInterval.value(); ++ for (int autoSaved = 0; autoSaved < this.world.paperConfig().chunks.maxAutoSaveChunksPerTick && !this.autoSaveQueue.isEmpty();) { ++ final NewChunkHolder holder = this.autoSaveQueue.first(); ++ ++ if (holder.lastAutoSave > maxSaveTime) { ++ break; ++ } ++ ++ this.autoSaveQueue.remove(holder); ++ ++ holder.lastAutoSave = currentTick; ++ if (holder.save(false, false) != null) { ++ ++autoSaved; ++ } ++ ++ if (holder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)) { ++ reschedule.add(holder); ++ } ++ } ++ ++ for (final NewChunkHolder holder : reschedule) { ++ if (holder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)) { ++ this.autoSaveQueue.add(holder); ++ } ++ } ++ } ++ ++ public void saveAllChunks(final boolean flush, final boolean shutdown, final boolean logProgress) { ++ final List holders = this.getChunkHolders(); ++ ++ if (logProgress) { ++ LOGGER.info("Saving all chunkholders for world '" + this.world.getWorld().getName() + "'"); ++ } ++ ++ final DecimalFormat format = new DecimalFormat("#0.00"); ++ ++ int saved = 0; ++ ++ long start = System.nanoTime(); ++ long lastLog = start; ++ boolean needsFlush = false; ++ final int flushInterval = 50; ++ ++ int savedChunk = 0; ++ int savedEntity = 0; ++ int savedPoi = 0; ++ ++ for (int i = 0, len = holders.size(); i < len; ++i) { ++ final NewChunkHolder holder = holders.get(i); ++ try { ++ final NewChunkHolder.SaveStat saveStat = holder.save(shutdown, false); ++ if (saveStat != null) { ++ ++saved; ++ needsFlush = flush; ++ if (saveStat.savedChunk()) { ++ ++savedChunk; ++ } ++ if (saveStat.savedEntityChunk()) { ++ ++savedEntity; ++ } ++ if (saveStat.savedPoiChunk()) { ++ ++savedPoi; ++ } ++ } ++ } catch (final ThreadDeath thr) { ++ throw thr; ++ } catch (final Throwable thr) { ++ LOGGER.error("Failed to save chunk (" + holder.chunkX + "," + holder.chunkZ + ") in world '" + this.world.getWorld().getName() + "'", thr); ++ } ++ if (needsFlush && (saved % flushInterval) == 0) { ++ needsFlush = false; ++ RegionFileIOThread.partialFlush(flushInterval / 2); ++ } ++ if (logProgress) { ++ final long currTime = System.nanoTime(); ++ if ((currTime - lastLog) > TimeUnit.SECONDS.toNanos(10L)) { ++ lastLog = currTime; ++ LOGGER.info("Saved " + saved + " chunks (" + format.format((double)(i+1)/(double)len * 100.0) + "%) in world '" + this.world.getWorld().getName() + "'"); ++ } ++ } ++ } ++ if (flush) { ++ RegionFileIOThread.flush(); ++ if (this.world.paperConfig().chunks.flushRegionsOnSave) { ++ try { ++ this.world.chunkSource.chunkMap.regionFileCache.flush(); ++ } catch (IOException ex) { ++ LOGGER.error("Exception when flushing regions in world {}", this.world.getWorld().getName(), ex); ++ } ++ } ++ } ++ if (logProgress) { ++ LOGGER.info("Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi + " poi chunks in world '" + this.world.getWorld().getName() + "' in " + format.format(1.0E-9 * (System.nanoTime() - start)) + "s"); ++ } ++ } ++ ++ protected final ThreadedTicketLevelPropagator ticketLevelPropagator = new ThreadedTicketLevelPropagator() { ++ @Override ++ protected void processLevelUpdates(final Long2ByteLinkedOpenHashMap updates) { ++ // first the necessary chunkholders must be created, so just update the ticket levels ++ for (final Iterator iterator = updates.long2ByteEntrySet().fastIterator(); iterator.hasNext();) { ++ final Long2ByteMap.Entry entry = iterator.next(); ++ final long key = entry.getLongKey(); ++ final int newLevel = convertBetweenTicketLevels((int)entry.getByteValue()); ++ ++ NewChunkHolder current = ChunkHolderManager.this.chunkHolders.get(key); ++ if (current == null && newLevel > MAX_TICKET_LEVEL) { ++ // not loaded and it shouldn't be loaded! ++ iterator.remove(); ++ continue; ++ } ++ ++ final int currentLevel = current == null ? MAX_TICKET_LEVEL + 1 : current.getCurrentTicketLevel(); ++ if (currentLevel == newLevel) { ++ // nothing to do ++ iterator.remove(); ++ continue; ++ } ++ ++ if (current == null) { ++ // must create ++ current = ChunkHolderManager.this.createChunkHolder(key); ++ synchronized (ChunkHolderManager.this.chunkHolders) { ++ ChunkHolderManager.this.chunkHolders.put(key, current); ++ } ++ current.updateTicketLevel(newLevel); ++ } else { ++ current.updateTicketLevel(newLevel); ++ } ++ } ++ } ++ ++ @Override ++ protected void processSchedulingUpdates(final Long2ByteLinkedOpenHashMap updates, final List scheduledTasks, ++ final List changedFullStatus) { ++ final List prev = CURRENT_TICKET_UPDATE_SCHEDULING.get(); ++ CURRENT_TICKET_UPDATE_SCHEDULING.set(scheduledTasks); ++ try { ++ for (final LongIterator iterator = updates.keySet().iterator(); iterator.hasNext();) { ++ final long key = iterator.nextLong(); ++ final NewChunkHolder current = ChunkHolderManager.this.chunkHolders.get(key); ++ ++ if (current == null) { ++ throw new IllegalStateException("Expected chunk holder to be created"); ++ } ++ ++ current.processTicketLevelUpdate(scheduledTasks, changedFullStatus); ++ } ++ } finally { ++ CURRENT_TICKET_UPDATE_SCHEDULING.set(prev); ++ } ++ } ++ }; ++ // function for converting between ticket levels and propagator levels and vice versa ++ // the problem is the ticket level propagator will propagate from a set source down to zero, whereas mojang expects ++ // levels to propagate from a set value up to a maximum value. so we need to convert the levels we put into the propagator ++ // and the levels we get out of the propagator ++ ++ public static int convertBetweenTicketLevels(final int level) { ++ return ChunkLevel.MAX_LEVEL - level + 1; ++ } ++ ++ public String getTicketDebugString(final long coordinate) { ++ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate)); ++ try { ++ final SortedArraySet> tickets = this.tickets.get(new RegionFileIOThread.ChunkCoordinate(coordinate)); ++ ++ return tickets != null ? tickets.first().toString() : "no_ticket"; ++ } finally { ++ if (ticketLock != null) { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ } ++ } ++ ++ public Long2ObjectOpenHashMap>> getTicketsCopy() { ++ final Long2ObjectOpenHashMap>> ret = new Long2ObjectOpenHashMap<>(); ++ final Long2ObjectOpenHashMap> sections = new Long2ObjectOpenHashMap(); ++ final int sectionShift = this.taskScheduler.getChunkSystemLockShift(); ++ for (final RegionFileIOThread.ChunkCoordinate coord : this.tickets.keySet()) { ++ sections.computeIfAbsent( ++ CoordinateUtils.getChunkKey( ++ CoordinateUtils.getChunkX(coord.key) >> sectionShift, ++ CoordinateUtils.getChunkZ(coord.key) >> sectionShift ++ ), ++ (final long keyInMap) -> { ++ return new ArrayList<>(); ++ } ++ ).add(coord); ++ } ++ ++ for (final Iterator>> iterator = sections.long2ObjectEntrySet().fastIterator(); ++ iterator.hasNext();) { ++ final Long2ObjectMap.Entry> entry = iterator.next(); ++ final long sectionKey = entry.getLongKey(); ++ final List coordinates = entry.getValue(); ++ ++ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( ++ CoordinateUtils.getChunkX(sectionKey) << sectionShift, ++ CoordinateUtils.getChunkZ(sectionKey) << sectionShift ++ ); ++ try { ++ for (final RegionFileIOThread.ChunkCoordinate coord : coordinates) { ++ final SortedArraySet> tickets = this.tickets.get(coord); ++ if (tickets == null) { ++ // removed before we acquired lock ++ continue; ++ } ++ ret.put(coord.key, new SortedArraySet<>(tickets)); ++ } ++ } finally { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ } ++ ++ return ret; ++ } ++ ++ public Collection getPluginChunkTickets(int x, int z) { ++ ImmutableList.Builder ret; ++ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(x, z); ++ try { ++ final long coordinate = CoordinateUtils.getChunkKey(x, z); ++ final SortedArraySet> tickets = this.tickets.get(new RegionFileIOThread.ChunkCoordinate(coordinate)); ++ ++ if (tickets == null) { ++ return Collections.emptyList(); ++ } ++ ++ ret = ImmutableList.builder(); ++ for (Ticket ticket : tickets) { ++ if (ticket.getType() == TicketType.PLUGIN_TICKET) { ++ ret.add((Plugin)ticket.key); ++ } ++ } ++ } finally { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ ++ return ret.build(); ++ } ++ ++ protected final void updateTicketLevel(final long coordinate, final int ticketLevel) { ++ if (ticketLevel > ChunkLevel.MAX_LEVEL) { ++ this.ticketLevelPropagator.removeSource(CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate)); ++ } else { ++ this.ticketLevelPropagator.setSource(CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate), convertBetweenTicketLevels(ticketLevel)); ++ } ++ } ++ ++ private static int getTicketLevelAt(SortedArraySet> tickets) { ++ return !tickets.isEmpty() ? tickets.first().getTicketLevel() : MAX_TICKET_LEVEL + 1; ++ } ++ ++ public boolean addTicketAtLevel(final TicketType type, final ChunkPos chunkPos, final int level, ++ final T identifier) { ++ return this.addTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkPos), level, identifier); ++ } ++ ++ public boolean addTicketAtLevel(final TicketType type, final int chunkX, final int chunkZ, final int level, ++ final T identifier) { ++ return this.addTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkX, chunkZ), level, identifier); ++ } ++ ++ private void addExpireCount(final int chunkX, final int chunkZ) { ++ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ ++ final int sectionShift = this.world.getRegionChunkShift(); ++ final RegionFileIOThread.ChunkCoordinate sectionKey = new RegionFileIOThread.ChunkCoordinate(CoordinateUtils.getChunkKey( ++ chunkX >> sectionShift, ++ chunkZ >> sectionShift ++ )); ++ ++ this.sectionToChunkToExpireCount.computeIfAbsent(sectionKey, (final RegionFileIOThread.ChunkCoordinate keyInMap) -> { ++ return new Long2IntOpenHashMap(); ++ }).addTo(chunkKey, 1); ++ } ++ ++ private void removeExpireCount(final int chunkX, final int chunkZ) { ++ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ ++ final int sectionShift = this.world.getRegionChunkShift(); ++ final RegionFileIOThread.ChunkCoordinate sectionKey = new RegionFileIOThread.ChunkCoordinate(CoordinateUtils.getChunkKey( ++ chunkX >> sectionShift, ++ chunkZ >> sectionShift ++ )); ++ ++ final Long2IntOpenHashMap removeCounts = this.sectionToChunkToExpireCount.get(sectionKey); ++ final int prevCount = removeCounts.addTo(chunkKey, -1); ++ ++ if (prevCount == 1) { ++ removeCounts.remove(chunkKey); ++ if (removeCounts.isEmpty()) { ++ this.sectionToChunkToExpireCount.remove(sectionKey); ++ } ++ } ++ } ++ ++ // supposed to return true if the ticket was added and did not replace another ++ // but, we always return false if the ticket cannot be added ++ public boolean addTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier) { ++ return this.addTicketAtLevel(type, chunk, level, identifier, true); ++ } ++ ++ boolean addTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier, final boolean lock) { ++ final long removeDelay = type.timeout <= 0 ? NO_TIMEOUT_MARKER : type.timeout; ++ if (level > MAX_TICKET_LEVEL) { ++ return false; ++ } ++ ++ final int chunkX = CoordinateUtils.getChunkX(chunk); ++ final int chunkZ = CoordinateUtils.getChunkZ(chunk); ++ final RegionFileIOThread.ChunkCoordinate chunkCoord = new RegionFileIOThread.ChunkCoordinate(chunk); ++ final Ticket ticket = new Ticket<>(type, level, identifier, removeDelay); ++ ++ final ReentrantAreaLock.Node ticketLock = lock ? this.ticketLockArea.lock(chunkX, chunkZ) : null; ++ try { ++ final SortedArraySet> ticketsAtChunk = this.tickets.computeIfAbsent(chunkCoord, (final RegionFileIOThread.ChunkCoordinate keyInMap) -> { ++ return SortedArraySet.create(4); ++ }); ++ ++ final int levelBefore = getTicketLevelAt(ticketsAtChunk); ++ final Ticket current = (Ticket)ticketsAtChunk.replace(ticket); ++ final int levelAfter = getTicketLevelAt(ticketsAtChunk); ++ ++ if (current != ticket) { ++ final long oldRemoveDelay = current.removeDelay; ++ if (removeDelay != oldRemoveDelay) { ++ if (oldRemoveDelay != NO_TIMEOUT_MARKER && removeDelay == NO_TIMEOUT_MARKER) { ++ this.removeExpireCount(chunkX, chunkZ); ++ } else if (oldRemoveDelay == NO_TIMEOUT_MARKER) { ++ // since old != new, we have that NO_TIMEOUT_MARKER != new ++ this.addExpireCount(chunkX, chunkZ); ++ } ++ } ++ } else { ++ if (removeDelay != NO_TIMEOUT_MARKER) { ++ this.addExpireCount(chunkX, chunkZ); ++ } ++ } ++ ++ if (levelBefore != levelAfter) { ++ this.updateTicketLevel(chunk, levelAfter); ++ } ++ ++ return current == ticket; ++ } finally { ++ if (ticketLock != null) { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ } ++ } ++ ++ public boolean removeTicketAtLevel(final TicketType type, final ChunkPos chunkPos, final int level, final T identifier) { ++ return this.removeTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkPos), level, identifier); ++ } ++ ++ public boolean removeTicketAtLevel(final TicketType type, final int chunkX, final int chunkZ, final int level, final T identifier) { ++ return this.removeTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkX, chunkZ), level, identifier); ++ } ++ ++ public boolean removeTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier) { ++ return this.removeTicketAtLevel(type, chunk, level, identifier, true); ++ } ++ ++ boolean removeTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier, final boolean lock) { ++ if (level > MAX_TICKET_LEVEL) { ++ return false; ++ } ++ ++ final int chunkX = CoordinateUtils.getChunkX(chunk); ++ final int chunkZ = CoordinateUtils.getChunkZ(chunk); ++ final RegionFileIOThread.ChunkCoordinate chunkCoord = new RegionFileIOThread.ChunkCoordinate(chunk); ++ final Ticket probe = new Ticket<>(type, level, identifier, PROBE_MARKER); ++ ++ final ReentrantAreaLock.Node ticketLock = lock ? this.ticketLockArea.lock(chunkX, chunkZ) : null; ++ try { ++ final SortedArraySet> ticketsAtChunk = this.tickets.get(chunkCoord); ++ if (ticketsAtChunk == null) { ++ return false; ++ } ++ ++ final int oldLevel = getTicketLevelAt(ticketsAtChunk); ++ final Ticket ticket = (Ticket)ticketsAtChunk.removeAndGet(probe); ++ ++ if (ticket == null) { ++ return false; ++ } ++ ++ final int newLevel = getTicketLevelAt(ticketsAtChunk); ++ // we should not change the ticket levels while the target region may be ticking ++ if (oldLevel != newLevel) { ++ // Delay unload chunk patch originally by Aikar, updated to 1.20 by jpenilla ++ // these days, the patch is mostly useful to keep chunks ticking when players teleport ++ // so that their pets can teleport with them as well. ++ final long delayTimeout = this.world.paperConfig().chunks.delayChunkUnloadsBy.ticks(); ++ final TicketType toAdd; ++ final long timeout; ++ if (type == RegionizedPlayerChunkLoader.REGION_PLAYER_TICKET && delayTimeout > 0) { ++ toAdd = TicketType.DELAY_UNLOAD; ++ timeout = delayTimeout; ++ } else { ++ toAdd = TicketType.UNKNOWN; ++ // always expect UNKNOWN to be > 1, but just in case ++ timeout = Math.max(1, toAdd.timeout); ++ } ++ final Ticket unknownTicket = new Ticket<>(toAdd, level, new ChunkPos(chunk), timeout); ++ if (ticketsAtChunk.add(unknownTicket)) { ++ this.addExpireCount(chunkX, chunkZ); ++ } else { ++ throw new IllegalStateException("Should have been able to add " + unknownTicket + " to " + ticketsAtChunk); ++ } ++ } ++ ++ final long removeDelay = ticket.removeDelay; ++ if (removeDelay != NO_TIMEOUT_MARKER) { ++ this.removeExpireCount(chunkX, chunkZ); ++ } ++ ++ return true; ++ } finally { ++ if (ticketLock != null) { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ } ++ } ++ ++ // atomic with respect to all add/remove/addandremove ticket calls for the given chunk ++ public void addAndRemoveTickets(final long chunk, final TicketType addType, final int addLevel, final T addIdentifier, ++ final TicketType removeType, final int removeLevel, final V removeIdentifier) { ++ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk)); ++ try { ++ this.addTicketAtLevel(addType, chunk, addLevel, addIdentifier, false); ++ this.removeTicketAtLevel(removeType, chunk, removeLevel, removeIdentifier, false); ++ } finally { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ } ++ ++ // atomic with respect to all add/remove/addandremove ticket calls for the given chunk ++ public boolean addIfRemovedTicket(final long chunk, final TicketType addType, final int addLevel, final T addIdentifier, ++ final TicketType removeType, final int removeLevel, final V removeIdentifier) { ++ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk)); ++ try { ++ if (this.removeTicketAtLevel(removeType, chunk, removeLevel, removeIdentifier, false)) { ++ this.addTicketAtLevel(addType, chunk, addLevel, addIdentifier, false); ++ return true; ++ } ++ return false; ++ } finally { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ } ++ ++ public void removeAllTicketsFor(final TicketType ticketType, final int ticketLevel, final T ticketIdentifier) { ++ if (ticketLevel > MAX_TICKET_LEVEL) { ++ return; ++ } ++ ++ final Long2ObjectOpenHashMap> sections = new Long2ObjectOpenHashMap(); ++ final int sectionShift = this.taskScheduler.getChunkSystemLockShift(); ++ for (final RegionFileIOThread.ChunkCoordinate coord : this.tickets.keySet()) { ++ sections.computeIfAbsent( ++ CoordinateUtils.getChunkKey( ++ CoordinateUtils.getChunkX(coord.key) >> sectionShift, ++ CoordinateUtils.getChunkZ(coord.key) >> sectionShift ++ ), ++ (final long keyInMap) -> { ++ return new ArrayList<>(); ++ } ++ ).add(coord); ++ } ++ ++ for (final Iterator>> iterator = sections.long2ObjectEntrySet().fastIterator(); ++ iterator.hasNext();) { ++ final Long2ObjectMap.Entry> entry = iterator.next(); ++ final long sectionKey = entry.getLongKey(); ++ final List coordinates = entry.getValue(); ++ ++ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( ++ CoordinateUtils.getChunkX(sectionKey) << sectionShift, ++ CoordinateUtils.getChunkZ(sectionKey) << sectionShift ++ ); ++ try { ++ for (final RegionFileIOThread.ChunkCoordinate coord : coordinates) { ++ this.removeTicketAtLevel(ticketType, coord.key, ticketLevel, ticketIdentifier, false); ++ } ++ } finally { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ } ++ } ++ ++ public void tick() { ++ final int sectionShift = this.world.getRegionChunkShift(); ++ ++ final Predicate> expireNow = (final Ticket ticket) -> { ++ if (ticket.removeDelay == NO_TIMEOUT_MARKER) { ++ return false; ++ } ++ return --ticket.removeDelay <= 0L; ++ }; ++ ++ for (final Iterator iterator = this.sectionToChunkToExpireCount.keySet().iterator(); iterator.hasNext();) { ++ final RegionFileIOThread.ChunkCoordinate section = iterator.next(); ++ final long sectionKey = section.key; ++ ++ if (!this.sectionToChunkToExpireCount.containsKey(section)) { ++ // removed concurrently ++ continue; ++ } ++ ++ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( ++ CoordinateUtils.getChunkX(sectionKey) << sectionShift, ++ CoordinateUtils.getChunkZ(sectionKey) << sectionShift ++ ); ++ ++ try { ++ final Long2IntOpenHashMap chunkToExpireCount = this.sectionToChunkToExpireCount.get(section); ++ if (chunkToExpireCount == null) { ++ // lost to some race ++ continue; ++ } ++ ++ for (final Iterator iterator1 = chunkToExpireCount.long2IntEntrySet().fastIterator(); iterator1.hasNext();) { ++ final Long2IntMap.Entry entry = iterator1.next(); ++ ++ final long chunkKey = entry.getLongKey(); ++ final int expireCount = entry.getIntValue(); ++ ++ final RegionFileIOThread.ChunkCoordinate chunk = new RegionFileIOThread.ChunkCoordinate(chunkKey); ++ ++ final SortedArraySet> tickets = this.tickets.get(chunk); ++ final int levelBefore = getTicketLevelAt(tickets); ++ ++ final int sizeBefore = tickets.size(); ++ tickets.removeIf(expireNow); ++ final int sizeAfter = tickets.size(); ++ final int levelAfter = getTicketLevelAt(tickets); ++ ++ if (tickets.isEmpty()) { ++ this.tickets.remove(chunk); ++ } ++ if (levelBefore != levelAfter) { ++ this.updateTicketLevel(chunkKey, levelAfter); ++ } ++ ++ final int newExpireCount = expireCount - (sizeBefore - sizeAfter); ++ ++ if (newExpireCount == expireCount) { ++ continue; ++ } ++ ++ if (newExpireCount != 0) { ++ entry.setValue(newExpireCount); ++ } else { ++ iterator1.remove(); ++ } ++ } ++ ++ if (chunkToExpireCount.isEmpty()) { ++ this.sectionToChunkToExpireCount.remove(section); ++ } ++ } finally { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ } ++ ++ this.processTicketUpdates(); ++ } ++ ++ public NewChunkHolder getChunkHolder(final int chunkX, final int chunkZ) { ++ return this.chunkHolders.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } ++ ++ public NewChunkHolder getChunkHolder(final long position) { ++ return this.chunkHolders.get(position); ++ } ++ ++ public void raisePriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { ++ final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); ++ if (chunkHolder != null) { ++ chunkHolder.raisePriority(priority); ++ } ++ } ++ ++ public void setPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { ++ final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); ++ if (chunkHolder != null) { ++ chunkHolder.setPriority(priority); ++ } ++ } ++ ++ public void lowerPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { ++ final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); ++ if (chunkHolder != null) { ++ chunkHolder.lowerPriority(priority); ++ } ++ } ++ ++ private NewChunkHolder createChunkHolder(final long position) { ++ final NewChunkHolder ret = new NewChunkHolder(this.world, CoordinateUtils.getChunkX(position), CoordinateUtils.getChunkZ(position), this.taskScheduler); ++ ++ ChunkSystem.onChunkHolderCreate(this.world, ret.vanillaChunkHolder); ++ ret.vanillaChunkHolder.onChunkAdd(); ++ ++ return ret; ++ } ++ ++ // because this function creates the chunk holder without a ticket, it is the caller's responsibility to ensure ++ // the chunk holder eventually unloads. this should only be used to avoid using processTicketUpdates to create chunkholders, ++ // as processTicketUpdates may call plugin logic; in every other case a ticket is appropriate ++ private NewChunkHolder getOrCreateChunkHolder(final int chunkX, final int chunkZ) { ++ return this.getOrCreateChunkHolder(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } ++ ++ private NewChunkHolder getOrCreateChunkHolder(final long position) { ++ final int chunkX = CoordinateUtils.getChunkX(position); ++ final int chunkZ = CoordinateUtils.getChunkZ(position); ++ ++ if (!this.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ)) { ++ throw new IllegalStateException("Must hold ticket level update lock!"); ++ } ++ if (!this.taskScheduler.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ)) { ++ throw new IllegalStateException("Must hold scheduler lock!!"); ++ } ++ ++ // we could just acquire these locks, but... ++ // must own the locks because the caller needs to ensure that no unload can occur AFTER this function returns ++ ++ NewChunkHolder current = this.chunkHolders.get(position); ++ if (current != null) { ++ return current; ++ } ++ ++ current = this.createChunkHolder(position); ++ synchronized (this.chunkHolders) { ++ this.chunkHolders.put(position, current); ++ } ++ ++ return current; ++ } ++ ++ private final AtomicLong entityLoadCounter = new AtomicLong(); ++ ++ public ChunkEntitySlices getOrCreateEntityChunk(final int chunkX, final int chunkZ, final boolean transientChunk) { ++ TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot create entity chunk off-main"); ++ ChunkEntitySlices ret; ++ ++ NewChunkHolder current = this.getChunkHolder(chunkX, chunkZ); ++ if (current != null && (ret = current.getEntityChunk()) != null && (transientChunk || !ret.isTransient())) { ++ return ret; ++ } ++ ++ final AtomicBoolean isCompleted = new AtomicBoolean(); ++ final Thread waiter = Thread.currentThread(); ++ final Long entityLoadId = Long.valueOf(this.entityLoadCounter.getAndIncrement()); ++ NewChunkHolder.GenericDataLoadTaskCallback loadTask = null; ++ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(chunkX, chunkZ); ++ try { ++ this.addTicketAtLevel(TicketType.ENTITY_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, entityLoadId); ++ final ReentrantAreaLock.Node schedulingLock = this.taskScheduler.schedulingLockArea.lock(chunkX, chunkZ); ++ try { ++ current = this.getOrCreateChunkHolder(chunkX, chunkZ); ++ if ((ret = current.getEntityChunk()) != null && (transientChunk || !ret.isTransient())) { ++ this.removeTicketAtLevel(TicketType.ENTITY_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, entityLoadId); ++ return ret; ++ } ++ ++ if (current.isEntityChunkNBTLoaded()) { ++ isCompleted.setPlain(true); ++ } else { ++ loadTask = current.getOrLoadEntityData((final GenericDataLoadTask.TaskResult result) -> { ++ if (!transientChunk) { ++ isCompleted.set(true); ++ LockSupport.unpark(waiter); ++ } ++ }); ++ final ChunkLoadTask.EntityDataLoadTask entityLoad = current.getEntityDataLoadTask(); ++ ++ if (entityLoad != null && !transientChunk) { ++ entityLoad.raisePriority(PrioritisedExecutor.Priority.BLOCKING); ++ } ++ } ++ } finally { ++ this.taskScheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ } finally { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ ++ if (loadTask != null) { ++ loadTask.schedule(); ++ } ++ ++ if (!transientChunk) { ++ // Note: no need to busy wait on the chunk queue, entity load will complete off-main ++ boolean interrupted = false; ++ while (!isCompleted.get()) { ++ interrupted |= Thread.interrupted(); ++ LockSupport.park(); ++ } ++ ++ if (interrupted) { ++ Thread.currentThread().interrupt(); ++ } ++ } ++ ++ // now that the entity data is loaded, we can load it into the world ++ ++ ret = current.loadInEntityChunk(transientChunk); ++ ++ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ this.addAndRemoveTickets(chunkKey, ++ TicketType.UNKNOWN, MAX_TICKET_LEVEL, new ChunkPos(chunkX, chunkZ), ++ TicketType.ENTITY_LOAD, MAX_TICKET_LEVEL, entityLoadId ++ ); ++ ++ return ret; ++ } ++ ++ public PoiChunk getPoiChunkIfLoaded(final int chunkX, final int chunkZ, final boolean checkLoadInCallback) { ++ final NewChunkHolder holder = this.getChunkHolder(chunkX, chunkZ); ++ if (holder != null) { ++ final PoiChunk ret = holder.getPoiChunk(); ++ return ret == null || (checkLoadInCallback && !ret.isLoaded()) ? null : ret; ++ } ++ return null; ++ } ++ ++ private final AtomicLong poiLoadCounter = new AtomicLong(); ++ ++ public PoiChunk loadPoiChunk(final int chunkX, final int chunkZ) { ++ TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot create poi chunk off-main"); ++ PoiChunk ret; ++ ++ NewChunkHolder current = this.getChunkHolder(chunkX, chunkZ); ++ if (current != null && (ret = current.getPoiChunk()) != null) { ++ if (!ret.isLoaded()) { ++ ret.load(); ++ } ++ return ret; ++ } ++ ++ final AtomicReference completed = new AtomicReference<>(); ++ final AtomicBoolean isCompleted = new AtomicBoolean(); ++ final Thread waiter = Thread.currentThread(); ++ final Long poiLoadId = Long.valueOf(this.poiLoadCounter.getAndIncrement()); ++ NewChunkHolder.GenericDataLoadTaskCallback loadTask = null; ++ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention ++ try { ++ // Folia - use area based lock to reduce contention ++ this.addTicketAtLevel(TicketType.POI_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, poiLoadId); ++ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.taskScheduler.schedulingLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention ++ try { ++ current = this.getOrCreateChunkHolder(chunkX, chunkZ); ++ if (current.isPoiChunkLoaded()) { ++ this.removeTicketAtLevel(TicketType.POI_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, poiLoadId); ++ return current.getPoiChunk(); ++ } ++ ++ loadTask = current.getOrLoadPoiData((final GenericDataLoadTask.TaskResult result) -> { ++ completed.setPlain(result.left()); ++ isCompleted.set(true); ++ LockSupport.unpark(waiter); ++ }); ++ final ChunkLoadTask.PoiDataLoadTask poiLoad = current.getPoiDataLoadTask(); ++ ++ if (poiLoad != null) { ++ poiLoad.raisePriority(PrioritisedExecutor.Priority.BLOCKING); ++ } ++ } finally { ++ this.taskScheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention ++ } ++ } finally { ++ this.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention ++ } ++ ++ if (loadTask != null) { ++ loadTask.schedule(); ++ } ++ ++ // Note: no need to busy wait on the chunk queue, poi load will complete off-main ++ ++ boolean interrupted = false; ++ while (!isCompleted.get()) { ++ interrupted |= Thread.interrupted(); ++ LockSupport.park(); ++ } ++ ++ if (interrupted) { ++ Thread.currentThread().interrupt(); ++ } ++ ++ ret = completed.getPlain(); ++ ++ ret.load(); ++ ++ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ this.addAndRemoveTickets(chunkKey, ++ TicketType.UNKNOWN, MAX_TICKET_LEVEL, new ChunkPos(chunkX, chunkZ), ++ TicketType.POI_LOAD, MAX_TICKET_LEVEL, poiLoadId ++ ); ++ ++ return ret; ++ } ++ ++ void addChangedStatuses(final List changedFullStatus) { ++ if (changedFullStatus.isEmpty()) { ++ return; ++ } ++ if (!TickThread.isTickThread()) { ++ this.taskScheduler.scheduleChunkTask(() -> { ++ final ArrayDeque pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate; ++ for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { ++ pendingFullLoadUpdate.add(changedFullStatus.get(i)); ++ } ++ ++ ChunkHolderManager.this.processPendingFullUpdate(); ++ }, PrioritisedExecutor.Priority.HIGHEST); ++ } else { ++ final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; ++ for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { ++ pendingFullLoadUpdate.add(changedFullStatus.get(i)); ++ } ++ } ++ } ++ ++ private void removeChunkHolder(final NewChunkHolder holder) { ++ holder.killed = true; ++ holder.vanillaChunkHolder.onChunkRemove(); ++ this.autoSaveQueue.remove(holder); ++ ChunkSystem.onChunkHolderDelete(this.world, holder.vanillaChunkHolder); ++ synchronized (this.chunkHolders) { ++ this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ)); ++ } ++ } ++ ++ // note: never call while inside the chunk system, this will absolutely break everything ++ public void processUnloads() { ++ TickThread.ensureTickThread("Cannot unload chunks off-main"); ++ ++ if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { ++ throw new IllegalStateException("Cannot unload chunks recursively"); ++ } ++ final int sectionShift = this.unloadQueue.coordinateShift; // sectionShift <= lock shift ++ final List unloadSectionsForRegion = this.unloadQueue.retrieveForAllRegions(); ++ int unloadCountTentative = 0; ++ for (final ChunkQueue.SectionToUnload sectionRef : unloadSectionsForRegion) { ++ final ChunkQueue.UnloadSection section ++ = this.unloadQueue.getSectionUnsynchronized(sectionRef.sectionX(), sectionRef.sectionZ()); ++ ++ if (section == null) { ++ // removed concurrently ++ continue; ++ } ++ ++ // technically reading the size field is unsafe, and it may be incorrect. ++ // We assume that the error here cumulatively goes away over many ticks. If it did not, then it is possible ++ // for chunks to never unload or not unload fast enough. ++ unloadCountTentative += section.chunks.size(); ++ } ++ ++ if (unloadCountTentative <= 0) { ++ // no work to do ++ return; ++ } ++ ++ // Note: The behaviour that we process ticket updates while holding the lock has been dropped here, as it is racey behavior. ++ // But, we do need to process updates here so that any add ticket that is synchronised before this call does not go missed. ++ this.processTicketUpdates(); ++ ++ final int toUnloadCount = Math.max(50, (int)(unloadCountTentative * 0.05)); ++ int processedCount = 0; ++ ++ for (final ChunkQueue.SectionToUnload sectionRef : unloadSectionsForRegion) { ++ final List stage1 = new ArrayList<>(); ++ final List stage2 = new ArrayList<>(); ++ ++ final int sectionLowerX = sectionRef.sectionX() << sectionShift; ++ final int sectionLowerZ = sectionRef.sectionZ() << sectionShift; ++ ++ // stage 1: set up for stage 2 while holding critical locks ++ ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(sectionLowerX, sectionLowerZ); ++ try { ++ final ReentrantAreaLock.Node scheduleLock = this.taskScheduler.schedulingLockArea.lock(sectionLowerX, sectionLowerZ); ++ try { ++ final ChunkQueue.UnloadSection section ++ = this.unloadQueue.getSectionUnsynchronized(sectionRef.sectionX(), sectionRef.sectionZ()); ++ ++ if (section == null) { ++ // removed concurrently ++ continue; ++ } ++ ++ // collect the holders to run stage 1 on ++ final int sectionCount = section.chunks.size(); ++ ++ if ((sectionCount + processedCount) <= toUnloadCount) { ++ // we can just drain the entire section ++ ++ for (final LongIterator iterator = section.chunks.iterator(); iterator.hasNext();) { ++ final NewChunkHolder holder = this.chunkHolders.get(iterator.nextLong()); ++ if (holder == null) { ++ throw new IllegalStateException(); ++ } ++ stage1.add(holder); ++ } ++ ++ // remove section ++ this.unloadQueue.removeSection(sectionRef.sectionX(), sectionRef.sectionZ()); ++ } else { ++ // processedCount + len = toUnloadCount ++ // we cannot drain the entire section ++ for (int i = 0, len = toUnloadCount - processedCount; i < len; ++i) { ++ final NewChunkHolder holder = this.chunkHolders.get(section.chunks.removeFirstLong()); ++ if (holder == null) { ++ throw new IllegalStateException(); ++ } ++ stage1.add(holder); ++ } ++ } ++ ++ // run stage 1 ++ for (int i = 0, len = stage1.size(); i < len; ++i) { ++ final NewChunkHolder chunkHolder = stage1.get(i); ++ if (chunkHolder.isSafeToUnload() != null) { ++ LOGGER.error("Chunkholder " + chunkHolder + " is not safe to unload but is inside the unload queue?"); ++ continue; ++ } ++ final NewChunkHolder.UnloadState state = chunkHolder.unloadStage1(); ++ if (state == null) { ++ // can unload immediately ++ this.removeChunkHolder(chunkHolder); ++ continue; ++ } ++ stage2.add(state); ++ } ++ } finally { ++ this.taskScheduler.schedulingLockArea.unlock(scheduleLock); ++ } ++ } finally { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ ++ // stage 2: invoke expensive unload logic, designed to run without locks thanks to stage 1 ++ final List stage3 = new ArrayList<>(stage2.size()); ++ ++ final Boolean before = this.blockTicketUpdates(); ++ try { ++ for (int i = 0, len = stage2.size(); i < len; ++i) { ++ final NewChunkHolder.UnloadState state = stage2.get(i); ++ final NewChunkHolder holder = state.holder(); ++ ++ holder.unloadStage2(state); ++ stage3.add(holder); ++ } ++ } finally { ++ this.unblockTicketUpdates(before); ++ } ++ ++ // stage 3: actually attempt to remove the chunk holders ++ ticketLock = this.ticketLockArea.lock(sectionLowerX, sectionLowerZ); ++ try { ++ final ReentrantAreaLock.Node scheduleLock = this.taskScheduler.schedulingLockArea.lock(sectionLowerX, sectionLowerZ); ++ try { ++ for (int i = 0, len = stage3.size(); i < len; ++i) { ++ final NewChunkHolder holder = stage3.get(i); ++ ++ if (holder.unloadStage3()) { ++ this.removeChunkHolder(holder); ++ } else { ++ // add cooldown so the next unload check is not immediately next tick ++ this.addTicketAtLevel(TicketType.UNLOAD_COOLDOWN, CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ), MAX_TICKET_LEVEL, Unit.INSTANCE, false); ++ } ++ } ++ } finally { ++ this.taskScheduler.schedulingLockArea.unlock(scheduleLock); ++ } ++ } finally { ++ this.ticketLockArea.unlock(ticketLock); ++ } ++ ++ processedCount += stage1.size(); ++ ++ if (processedCount >= toUnloadCount) { ++ break; ++ } ++ } ++ } ++ ++ public enum TicketOperationType { ++ ADD, REMOVE, ADD_IF_REMOVED, ADD_AND_REMOVE ++ } ++ ++ public static record TicketOperation ( ++ TicketOperationType op, long chunkCoord, ++ TicketType ticketType, int ticketLevel, T identifier, ++ TicketType ticketType2, int ticketLevel2, V identifier2 ++ ) { ++ ++ private TicketOperation(TicketOperationType op, long chunkCoord, ++ TicketType ticketType, int ticketLevel, T identifier) { ++ this(op, chunkCoord, ticketType, ticketLevel, identifier, null, 0, null); ++ } ++ ++ public static TicketOperation addOp(final ChunkPos chunk, final TicketType type, final int ticketLevel, final T identifier) { ++ return addOp(CoordinateUtils.getChunkKey(chunk), type, ticketLevel, identifier); ++ } ++ ++ public static TicketOperation addOp(final int chunkX, final int chunkZ, final TicketType type, final int ticketLevel, final T identifier) { ++ return addOp(CoordinateUtils.getChunkKey(chunkX, chunkZ), type, ticketLevel, identifier); ++ } ++ ++ public static TicketOperation addOp(final long chunk, final TicketType type, final int ticketLevel, final T identifier) { ++ return new TicketOperation<>(TicketOperationType.ADD, chunk, type, ticketLevel, identifier); ++ } ++ ++ public static TicketOperation removeOp(final ChunkPos chunk, final TicketType type, final int ticketLevel, final T identifier) { ++ return removeOp(CoordinateUtils.getChunkKey(chunk), type, ticketLevel, identifier); ++ } ++ ++ public static TicketOperation removeOp(final int chunkX, final int chunkZ, final TicketType type, final int ticketLevel, final T identifier) { ++ return removeOp(CoordinateUtils.getChunkKey(chunkX, chunkZ), type, ticketLevel, identifier); ++ } ++ ++ public static TicketOperation removeOp(final long chunk, final TicketType type, final int ticketLevel, final T identifier) { ++ return new TicketOperation<>(TicketOperationType.REMOVE, chunk, type, ticketLevel, identifier); ++ } ++ ++ public static TicketOperation addIfRemovedOp(final long chunk, ++ final TicketType addType, final int addLevel, final T addIdentifier, ++ final TicketType removeType, final int removeLevel, final V removeIdentifier) { ++ return new TicketOperation<>( ++ TicketOperationType.ADD_IF_REMOVED, chunk, addType, addLevel, addIdentifier, ++ removeType, removeLevel, removeIdentifier ++ ); ++ } ++ ++ public static TicketOperation addAndRemove(final long chunk, ++ final TicketType addType, final int addLevel, final T addIdentifier, ++ final TicketType removeType, final int removeLevel, final V removeIdentifier) { ++ return new TicketOperation<>( ++ TicketOperationType.ADD_AND_REMOVE, chunk, addType, addLevel, addIdentifier, ++ removeType, removeLevel, removeIdentifier ++ ); ++ } ++ } ++ ++ private boolean processTicketOp(TicketOperation operation) { ++ boolean ret = false; ++ switch (operation.op) { ++ case ADD: { ++ ret |= this.addTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier); ++ break; ++ } ++ case REMOVE: { ++ ret |= this.removeTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier); ++ break; ++ } ++ case ADD_IF_REMOVED: { ++ ret |= this.addIfRemovedTicket( ++ operation.chunkCoord, ++ operation.ticketType, operation.ticketLevel, operation.identifier, ++ operation.ticketType2, operation.ticketLevel2, operation.identifier2 ++ ); ++ break; ++ } ++ case ADD_AND_REMOVE: { ++ ret = true; ++ this.addAndRemoveTickets( ++ operation.chunkCoord, ++ operation.ticketType, operation.ticketLevel, operation.identifier, ++ operation.ticketType2, operation.ticketLevel2, operation.identifier2 ++ ); ++ break; ++ } ++ } ++ ++ return ret; ++ } ++ ++ public void performTicketUpdates(final Collection> operations) { ++ for (final TicketOperation operation : operations) { ++ this.processTicketOp(operation); ++ } ++ } ++ ++ private final ThreadLocal BLOCK_TICKET_UPDATES = ThreadLocal.withInitial(() -> { ++ return Boolean.FALSE; ++ }); ++ ++ public Boolean blockTicketUpdates() { ++ final Boolean ret = BLOCK_TICKET_UPDATES.get(); ++ BLOCK_TICKET_UPDATES.set(Boolean.TRUE); ++ return ret; ++ } ++ ++ public void unblockTicketUpdates(final Boolean before) { ++ BLOCK_TICKET_UPDATES.set(before); ++ } ++ ++ public boolean processTicketUpdates() { ++ return this.processTicketUpdates(true, true, null); ++ } ++ ++ private static final ThreadLocal> CURRENT_TICKET_UPDATE_SCHEDULING = new ThreadLocal<>(); ++ ++ static List getCurrentTicketUpdateScheduling() { ++ return CURRENT_TICKET_UPDATE_SCHEDULING.get(); ++ } ++ ++ private boolean processTicketUpdates(final boolean checkLocks, final boolean processFullUpdates, List scheduledTasks) { ++ TickThread.ensureTickThread("Cannot process ticket levels off-main"); ++ if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { ++ throw new IllegalStateException("Cannot update ticket level while unloading chunks or updating entity manager"); ++ } ++ ++ List changedFullStatus = null; ++ ++ final boolean isTickThread = TickThread.isTickThread(); ++ ++ boolean ret = false; ++ final boolean canProcessFullUpdates = processFullUpdates & isTickThread; ++ final boolean canProcessScheduling = scheduledTasks == null; ++ ++ if (this.ticketLevelPropagator.hasPendingUpdates()) { ++ if (scheduledTasks == null) { ++ scheduledTasks = new ArrayList<>(); ++ } ++ changedFullStatus = new ArrayList<>(); ++ ++ ret |= this.ticketLevelPropagator.performUpdates( ++ this.ticketLockArea, this.taskScheduler.schedulingLockArea, ++ scheduledTasks, changedFullStatus ++ ); ++ } ++ ++ if (changedFullStatus != null) { ++ this.addChangedStatuses(changedFullStatus); ++ } ++ ++ if (canProcessScheduling && scheduledTasks != null) { ++ for (int i = 0, len = scheduledTasks.size(); i < len; ++i) { ++ scheduledTasks.get(i).schedule(); ++ } ++ } ++ ++ if (canProcessFullUpdates) { ++ ret |= this.processPendingFullUpdate(); ++ } ++ ++ return ret; ++ } ++ ++ // only call on tick thread ++ protected final boolean processPendingFullUpdate() { ++ final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; ++ ++ boolean ret = false; ++ ++ List changedFullStatus = new ArrayList<>(); ++ ++ NewChunkHolder holder; ++ while ((holder = pendingFullLoadUpdate.poll()) != null) { ++ ret |= holder.handleFullStatusChange(changedFullStatus); ++ ++ if (!changedFullStatus.isEmpty()) { ++ for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { ++ pendingFullLoadUpdate.add(changedFullStatus.get(i)); ++ } ++ changedFullStatus.clear(); ++ } ++ } ++ ++ return ret; ++ } ++ ++ public JsonObject getDebugJsonForWatchdog() { ++ return this.getDebugJsonNoLock(); ++ } ++ ++ private JsonObject getDebugJsonNoLock() { ++ final JsonObject ret = new JsonObject(); ++ ret.addProperty("current_tick", Long.valueOf(this.currentTick)); ++ ++ final JsonArray unloadQueue = new JsonArray(); ++ ret.add("unload_queue", unloadQueue); ++ ret.addProperty("lock_shift", Integer.valueOf(this.taskScheduler.getChunkSystemLockShift())); ++ ret.addProperty("ticket_shift", Integer.valueOf(ThreadedTicketLevelPropagator.SECTION_SHIFT)); ++ ret.addProperty("region_shift", Integer.valueOf(this.world.getRegionChunkShift())); ++ for (final ChunkQueue.SectionToUnload section : this.unloadQueue.retrieveForAllRegions()) { ++ final JsonObject sectionJson = new JsonObject(); ++ unloadQueue.add(sectionJson); ++ sectionJson.addProperty("sectionX", section.sectionX()); ++ sectionJson.addProperty("sectionZ", section.sectionX()); ++ sectionJson.addProperty("order", section.order()); ++ ++ final JsonArray coordinates = new JsonArray(); ++ sectionJson.add("coordinates", coordinates); ++ ++ final ChunkQueue.UnloadSection actualSection = this.unloadQueue.getSectionUnsynchronized(section.sectionX(), section.sectionZ()); ++ for (final LongIterator iterator = actualSection.chunks.iterator(); iterator.hasNext();) { ++ final long coordinate = iterator.nextLong(); ++ ++ final JsonObject coordinateJson = new JsonObject(); ++ coordinates.add(coordinateJson); ++ ++ coordinateJson.addProperty("chunkX", Integer.valueOf(CoordinateUtils.getChunkX(coordinate))); ++ coordinateJson.addProperty("chunkZ", Integer.valueOf(CoordinateUtils.getChunkZ(coordinate))); ++ } ++ } ++ ++ final JsonArray holders = new JsonArray(); ++ ret.add("chunkholders", holders); ++ ++ for (final NewChunkHolder holder : this.getChunkHolders()) { ++ holders.add(holder.getDebugJson()); ++ } ++ ++ // TODO ++ /* ++ final JsonArray removeTickToChunkExpireTicketCount = new JsonArray(); ++ ret.add("remove_tick_to_chunk_expire_ticket_count", removeTickToChunkExpireTicketCount); ++ ++ for (final Long2ObjectMap.Entry tickEntry : this.removeTickToChunkExpireTicketCount.long2ObjectEntrySet()) { ++ final long tick = tickEntry.getLongKey(); ++ final Long2IntOpenHashMap coordinateToCount = tickEntry.getValue(); ++ ++ final JsonObject tickJson = new JsonObject(); ++ removeTickToChunkExpireTicketCount.add(tickJson); ++ ++ tickJson.addProperty("tick", Long.valueOf(tick)); ++ ++ final JsonArray tickEntries = new JsonArray(); ++ tickJson.add("entries", tickEntries); ++ ++ for (final Long2IntMap.Entry entry : coordinateToCount.long2IntEntrySet()) { ++ final long coordinate = entry.getLongKey(); ++ final int count = entry.getIntValue(); ++ ++ final JsonObject entryJson = new JsonObject(); ++ tickEntries.add(entryJson); ++ ++ entryJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); ++ entryJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); ++ entryJson.addProperty("count", Integer.valueOf(count)); ++ } ++ } ++ ++ final JsonArray allTicketsJson = new JsonArray(); ++ ret.add("tickets", allTicketsJson); ++ ++ for (final Long2ObjectMap.Entry>> coordinateTickets : this.tickets.long2ObjectEntrySet()) { ++ final long coordinate = coordinateTickets.getLongKey(); ++ final SortedArraySet> tickets = coordinateTickets.getValue(); ++ ++ final JsonObject coordinateJson = new JsonObject(); ++ allTicketsJson.add(coordinateJson); ++ ++ coordinateJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); ++ coordinateJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); ++ ++ final JsonArray ticketsSerialized = new JsonArray(); ++ coordinateJson.add("tickets", ticketsSerialized); ++ ++ for (final Ticket ticket : tickets) { ++ final JsonObject ticketSerialized = new JsonObject(); ++ ticketsSerialized.add(ticketSerialized); ++ ++ ticketSerialized.addProperty("type", ticket.getType().toString()); ++ ticketSerialized.addProperty("level", Integer.valueOf(ticket.getTicketLevel())); ++ ticketSerialized.addProperty("identifier", Objects.toString(ticket.key)); ++ ticketSerialized.addProperty("remove_tick", Long.valueOf(ticket.removalTick)); ++ } ++ } ++ */ ++ ++ return ret; ++ } ++ ++ public JsonObject getDebugJson() { ++ return this.getDebugJsonNoLock(); // Folia - use area based lock to reduce contention ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLightTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLightTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..53ddd7e9ac05e6a9eb809f329796e6d4f6bb2ab1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLightTask.java +@@ -0,0 +1,181 @@ ++package io.papermc.paper.chunk.system.scheduling; ++ ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.starlight.common.light.StarLightEngine; ++import ca.spottedleaf.starlight.common.light.StarLightInterface; ++import io.papermc.paper.chunk.system.light.LightQueue; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.ProtoChunk; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++import java.util.function.BooleanSupplier; ++ ++public final class ChunkLightTask extends ChunkProgressionTask { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ ++ protected final ChunkAccess fromChunk; ++ ++ private final LightTaskPriorityHolder priorityHolder; ++ ++ public ChunkLightTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, ++ final ChunkAccess chunk, final PrioritisedExecutor.Priority priority) { ++ super(scheduler, world, chunkX, chunkZ); ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ this.priorityHolder = new LightTaskPriorityHolder(priority, this); ++ this.fromChunk = chunk; ++ } ++ ++ @Override ++ public boolean isScheduled() { ++ return this.priorityHolder.isScheduled(); ++ } ++ ++ @Override ++ public ChunkStatus getTargetStatus() { ++ return ChunkStatus.LIGHT; ++ } ++ ++ @Override ++ public void schedule() { ++ this.priorityHolder.schedule(); ++ } ++ ++ @Override ++ public void cancel() { ++ this.priorityHolder.cancel(); ++ } ++ ++ @Override ++ public PrioritisedExecutor.Priority getPriority() { ++ return this.priorityHolder.getPriority(); ++ } ++ ++ @Override ++ public void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ this.priorityHolder.raisePriority(priority); ++ } ++ ++ @Override ++ public void setPriority(final PrioritisedExecutor.Priority priority) { ++ this.priorityHolder.setPriority(priority); ++ } ++ ++ @Override ++ public void raisePriority(final PrioritisedExecutor.Priority priority) { ++ this.priorityHolder.raisePriority(priority); ++ } ++ ++ private static final class LightTaskPriorityHolder extends PriorityHolder { ++ ++ protected final ChunkLightTask task; ++ ++ protected LightTaskPriorityHolder(final PrioritisedExecutor.Priority priority, final ChunkLightTask task) { ++ super(priority); ++ this.task = task; ++ } ++ ++ @Override ++ protected void cancelScheduled() { ++ final ChunkLightTask task = this.task; ++ task.complete(null, null); ++ } ++ ++ @Override ++ protected PrioritisedExecutor.Priority getScheduledPriority() { ++ final ChunkLightTask task = this.task; ++ return task.world.getChunkSource().getLightEngine().theLightEngine.lightQueue.getPriority(task.chunkX, task.chunkZ); ++ } ++ ++ @Override ++ protected void scheduleTask(final PrioritisedExecutor.Priority priority) { ++ final ChunkLightTask task = this.task; ++ final StarLightInterface starLightInterface = task.world.getChunkSource().getLightEngine().theLightEngine; ++ final LightQueue lightQueue = starLightInterface.lightQueue; ++ lightQueue.queueChunkLightTask(new ChunkPos(task.chunkX, task.chunkZ), new LightTask(starLightInterface, task), priority); ++ lightQueue.setPriority(task.chunkX, task.chunkZ, priority); ++ } ++ ++ @Override ++ protected void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority) { ++ final ChunkLightTask task = this.task; ++ final StarLightInterface starLightInterface = task.world.getChunkSource().getLightEngine().theLightEngine; ++ final LightQueue lightQueue = starLightInterface.lightQueue; ++ lightQueue.lowerPriority(task.chunkX, task.chunkZ, priority); ++ } ++ ++ @Override ++ protected void setPriorityScheduled(final PrioritisedExecutor.Priority priority) { ++ final ChunkLightTask task = this.task; ++ final StarLightInterface starLightInterface = task.world.getChunkSource().getLightEngine().theLightEngine; ++ final LightQueue lightQueue = starLightInterface.lightQueue; ++ lightQueue.setPriority(task.chunkX, task.chunkZ, priority); ++ } ++ ++ @Override ++ protected void raisePriorityScheduled(final PrioritisedExecutor.Priority priority) { ++ final ChunkLightTask task = this.task; ++ final StarLightInterface starLightInterface = task.world.getChunkSource().getLightEngine().theLightEngine; ++ final LightQueue lightQueue = starLightInterface.lightQueue; ++ lightQueue.raisePriority(task.chunkX, task.chunkZ, priority); ++ } ++ } ++ ++ private static final class LightTask implements BooleanSupplier { ++ ++ protected final StarLightInterface lightEngine; ++ protected final ChunkLightTask task; ++ ++ public LightTask(final StarLightInterface lightEngine, final ChunkLightTask task) { ++ this.lightEngine = lightEngine; ++ this.task = task; ++ } ++ ++ @Override ++ public boolean getAsBoolean() { ++ final ChunkLightTask task = this.task; ++ // executed on light thread ++ if (!task.priorityHolder.markExecuting()) { ++ // cancelled ++ return false; ++ } ++ ++ try { ++ final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(task.fromChunk); ++ ++ if (task.fromChunk.isLightCorrect() && task.fromChunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { ++ this.lightEngine.forceLoadInChunk(task.fromChunk, emptySections); ++ this.lightEngine.checkChunkEdges(task.chunkX, task.chunkZ); ++ } else { ++ task.fromChunk.setLightCorrect(false); ++ this.lightEngine.lightChunk(task.fromChunk, emptySections); ++ task.fromChunk.setLightCorrect(true); ++ } ++ // we need to advance status ++ if (task.fromChunk instanceof ProtoChunk chunk && chunk.getStatus() == ChunkStatus.LIGHT.getParent()) { ++ chunk.setStatus(ChunkStatus.LIGHT); ++ } ++ } catch (final Throwable thr) { ++ if (!(thr instanceof ThreadDeath)) { ++ LOGGER.fatal("Failed to light chunk " + task.fromChunk.getPos().toString() + " in world '" + this.lightEngine.getWorld().getWorld().getName() + "'", thr); ++ } ++ ++ task.complete(null, thr); ++ ++ if (thr instanceof ThreadDeath) { ++ throw (ThreadDeath)thr; ++ } ++ ++ return true; ++ } ++ ++ task.complete(task.fromChunk, null); ++ return true; ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f0b03a05387e38ca6933b1ea6fe59b537d5535b9 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java +@@ -0,0 +1,484 @@ ++package io.papermc.paper.chunk.system.scheduling; ++ ++import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.dataconverter.minecraft.MCDataConverter; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.chunk.system.io.RegionFileIOThread; ++import io.papermc.paper.chunk.system.poi.PoiChunk; ++import net.minecraft.SharedConstants; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.level.ChunkMap; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.ProtoChunk; ++import net.minecraft.world.level.chunk.UpgradeData; ++import net.minecraft.world.level.chunk.storage.ChunkSerializer; ++import net.minecraft.world.level.chunk.storage.EntityStorage; ++import net.minecraft.world.level.levelgen.blending.BlendingData; ++import org.slf4j.Logger; ++import java.lang.invoke.VarHandle; ++import java.util.Map; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.Consumer; ++ ++public final class ChunkLoadTask extends ChunkProgressionTask { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ private final NewChunkHolder chunkHolder; ++ private final ChunkDataLoadTask loadTask; ++ ++ private volatile boolean cancelled; ++ private NewChunkHolder.GenericDataLoadTaskCallback entityLoadTask; ++ private NewChunkHolder.GenericDataLoadTaskCallback poiLoadTask; ++ private GenericDataLoadTask.TaskResult loadResult; ++ private final AtomicInteger taskCountToComplete = new AtomicInteger(3); // one for poi, one for entity, and one for chunk data ++ ++ protected ChunkLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, ++ final NewChunkHolder chunkHolder, final PrioritisedExecutor.Priority priority) { ++ super(scheduler, world, chunkX, chunkZ); ++ this.chunkHolder = chunkHolder; ++ this.loadTask = new ChunkDataLoadTask(scheduler, world, chunkX, chunkZ, priority); ++ this.loadTask.addCallback((final GenericDataLoadTask.TaskResult result) -> { ++ ChunkLoadTask.this.loadResult = result; // must be before getAndDecrement ++ ChunkLoadTask.this.tryCompleteLoad(); ++ }); ++ } ++ ++ private void tryCompleteLoad() { ++ if (this.taskCountToComplete.decrementAndGet() == 0) { ++ final GenericDataLoadTask.TaskResult result = this.cancelled ? null : this.loadResult; // only after the getAndDecrement ++ ChunkLoadTask.this.complete(result == null ? null : result.left(), result == null ? null : result.right()); ++ } ++ } ++ ++ @Override ++ public ChunkStatus getTargetStatus() { ++ return ChunkStatus.EMPTY; ++ } ++ ++ private boolean scheduled; ++ ++ @Override ++ public boolean isScheduled() { ++ return this.scheduled; ++ } ++ ++ @Override ++ public void schedule() { ++ final NewChunkHolder.GenericDataLoadTaskCallback entityLoadTask; ++ final NewChunkHolder.GenericDataLoadTaskCallback poiLoadTask; ++ ++ final Consumer> scheduleLoadTask = (final GenericDataLoadTask.TaskResult result) -> { ++ ChunkLoadTask.this.tryCompleteLoad(); ++ }; ++ ++ // NOTE: it is IMPOSSIBLE for getOrLoadEntityData/getOrLoadPoiData to complete synchronously, because ++ // they must schedule a task to off main or to on main to complete ++ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); ++ try { ++ if (this.scheduled) { ++ throw new IllegalStateException("schedule() called twice"); ++ } ++ this.scheduled = true; ++ if (this.cancelled) { ++ return; ++ } ++ if (!this.chunkHolder.isEntityChunkNBTLoaded()) { ++ entityLoadTask = this.chunkHolder.getOrLoadEntityData((Consumer)scheduleLoadTask); ++ } else { ++ entityLoadTask = null; ++ this.taskCountToComplete.getAndDecrement(); // we know the chunk load is not done here, as it is not scheduled ++ } ++ ++ if (!this.chunkHolder.isPoiChunkLoaded()) { ++ poiLoadTask = this.chunkHolder.getOrLoadPoiData((Consumer)scheduleLoadTask); ++ } else { ++ poiLoadTask = null; ++ this.taskCountToComplete.getAndDecrement(); // we know the chunk load is not done here, as it is not scheduled ++ } ++ ++ this.entityLoadTask = entityLoadTask; ++ this.poiLoadTask = poiLoadTask; ++ } finally { ++ this.scheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ ++ if (entityLoadTask != null) { ++ entityLoadTask.schedule(); ++ } ++ ++ if (poiLoadTask != null) { ++ poiLoadTask.schedule(); ++ } ++ ++ this.loadTask.schedule(false); ++ } ++ ++ @Override ++ public void cancel() { ++ // must be before load task access, so we can synchronise with the writes to the fields ++ final boolean scheduled; ++ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); ++ try { ++ // must read field here, as it may be written later conucrrently - ++ // we need to know if we scheduled _before_ cancellation ++ scheduled = this.scheduled; ++ this.cancelled = true; ++ } finally { ++ this.scheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ ++ /* ++ Note: The entityLoadTask/poiLoadTask do not complete when cancelled, ++ so we need to manually try to complete in those cases ++ It is also important to note that we set the cancelled field first, just in case ++ the chunk load task attempts to complete with a non-null value ++ */ ++ ++ if (scheduled) { ++ // since we scheduled, we need to cancel the tasks ++ if (this.entityLoadTask != null) { ++ if (this.entityLoadTask.cancel()) { ++ this.tryCompleteLoad(); ++ } ++ } ++ if (this.poiLoadTask != null) { ++ if (this.poiLoadTask.cancel()) { ++ this.tryCompleteLoad(); ++ } ++ } ++ } else { ++ // since nothing was scheduled, we need to decrement the task count here ourselves ++ ++ // for entity load task ++ this.tryCompleteLoad(); ++ ++ // for poi load task ++ this.tryCompleteLoad(); ++ } ++ this.loadTask.cancel(); ++ } ++ ++ @Override ++ public PrioritisedExecutor.Priority getPriority() { ++ return this.loadTask.getPriority(); ++ } ++ ++ @Override ++ public void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); ++ if (entityLoad != null) { ++ entityLoad.lowerPriority(priority); ++ } ++ ++ final PoiDataLoadTask poiLoad = this.chunkHolder.getPoiDataLoadTask(); ++ ++ if (poiLoad != null) { ++ poiLoad.lowerPriority(priority); ++ } ++ ++ this.loadTask.lowerPriority(priority); ++ } ++ ++ @Override ++ public void setPriority(final PrioritisedExecutor.Priority priority) { ++ final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); ++ if (entityLoad != null) { ++ entityLoad.setPriority(priority); ++ } ++ ++ final PoiDataLoadTask poiLoad = this.chunkHolder.getPoiDataLoadTask(); ++ ++ if (poiLoad != null) { ++ poiLoad.setPriority(priority); ++ } ++ ++ this.loadTask.setPriority(priority); ++ } ++ ++ @Override ++ public void raisePriority(final PrioritisedExecutor.Priority priority) { ++ final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); ++ if (entityLoad != null) { ++ entityLoad.raisePriority(priority); ++ } ++ ++ final PoiDataLoadTask poiLoad = this.chunkHolder.getPoiDataLoadTask(); ++ ++ if (poiLoad != null) { ++ poiLoad.raisePriority(priority); ++ } ++ ++ this.loadTask.raisePriority(priority); ++ } ++ ++ protected static abstract class CallbackDataLoadTask extends GenericDataLoadTask { ++ ++ private TaskResult result; ++ private final MultiThreadedQueue>> waiters = new MultiThreadedQueue<>(); ++ ++ protected volatile boolean completed; ++ protected static final VarHandle COMPLETED_HANDLE = ConcurrentUtil.getVarHandle(CallbackDataLoadTask.class, "completed", boolean.class); ++ ++ protected CallbackDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, ++ final int chunkZ, final RegionFileIOThread.RegionFileType type, ++ final PrioritisedExecutor.Priority priority) { ++ super(scheduler, world, chunkX, chunkZ, type, priority); ++ } ++ ++ public void addCallback(final Consumer> consumer) { ++ if (!this.waiters.add(consumer)) { ++ try { ++ consumer.accept(this.result); ++ } catch (final Throwable throwable) { ++ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( ++ "Consumer", ChunkTaskScheduler.stringIfNull(consumer), ++ "Completed throwable", ChunkTaskScheduler.stringIfNull(this.result.right()) ++ ), throwable); ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ } ++ } ++ } ++ ++ @Override ++ protected void onComplete(final TaskResult result) { ++ if ((boolean)COMPLETED_HANDLE.getAndSet((CallbackDataLoadTask)this, (boolean)true)) { ++ throw new IllegalStateException("Already completed"); ++ } ++ this.result = result; ++ Consumer> consumer; ++ while ((consumer = this.waiters.pollOrBlockAdds()) != null) { ++ try { ++ consumer.accept(result); ++ } catch (final Throwable throwable) { ++ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( ++ "Consumer", ChunkTaskScheduler.stringIfNull(consumer), ++ "Completed throwable", ChunkTaskScheduler.stringIfNull(result.right()) ++ ), throwable); ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ return; ++ } ++ } ++ } ++ } ++ ++ public static final class ChunkDataLoadTask extends CallbackDataLoadTask { ++ protected ChunkDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, ++ final int chunkZ, final PrioritisedExecutor.Priority priority) { ++ super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.CHUNK_DATA, priority); ++ } ++ ++ @Override ++ protected boolean hasOffMain() { ++ return true; ++ } ++ ++ @Override ++ protected boolean hasOnMain() { ++ return false; ++ } ++ ++ @Override ++ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ return this.scheduler.loadExecutor.createTask(run, priority); ++ } ++ ++ @Override ++ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ protected TaskResult completeOnMainOffMain(final ChunkAccess data, final Throwable throwable) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ private ProtoChunk getEmptyChunk() { ++ return new ProtoChunk( ++ new ChunkPos(this.chunkX, this.chunkZ), UpgradeData.EMPTY, this.world, ++ this.world.registryAccess().registryOrThrow(Registries.BIOME), (BlendingData)null ++ ); ++ } ++ ++ @Override ++ protected TaskResult runOffMain(final CompoundTag data, final Throwable throwable) { ++ if (throwable != null) { ++ LOGGER.error("Failed to load chunk data for task: " + this.toString() + ", chunk data will be lost", throwable); ++ return new TaskResult<>(this.getEmptyChunk(), null); ++ } ++ ++ if (data == null) { ++ return new TaskResult<>(this.getEmptyChunk(), null); ++ } ++ ++ // need to convert data, and then deserialize it ++ ++ try { ++ final ChunkPos chunkPos = new ChunkPos(this.chunkX, this.chunkZ); ++ final ChunkMap chunkMap = this.world.getChunkSource().chunkMap; ++ // run converters ++ // note: upgradeChunkTag copies the data already ++ final CompoundTag converted = chunkMap.upgradeChunkTag( ++ this.world.getTypeKey(), chunkMap.overworldDataStorage, data, chunkMap.generator.getTypeNameForDataFixer(), ++ chunkPos, this.world ++ ); ++ // deserialize ++ final ChunkSerializer.InProgressChunkHolder chunkHolder = ChunkSerializer.readInProgressChunkHolder( ++ this.world, chunkMap.getPoiManager(), chunkPos, converted ++ ); ++ ++ return new TaskResult<>(chunkHolder.protoChunk, null); ++ } catch (final ThreadDeath death) { ++ throw death; ++ } catch (final Throwable thr2) { ++ LOGGER.error("Failed to parse chunk data for task: " + this.toString() + ", chunk data will be lost", thr2); ++ return new TaskResult<>(this.getEmptyChunk(), null); ++ } ++ } ++ ++ @Override ++ protected TaskResult runOnMain(final ChunkAccess data, final Throwable throwable) { ++ throw new UnsupportedOperationException(); ++ } ++ } ++ ++ public static final class PoiDataLoadTask extends CallbackDataLoadTask { ++ public PoiDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, ++ final int chunkZ, final PrioritisedExecutor.Priority priority) { ++ super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.POI_DATA, priority); ++ } ++ ++ @Override ++ protected boolean hasOffMain() { ++ return true; ++ } ++ ++ @Override ++ protected boolean hasOnMain() { ++ return false; ++ } ++ ++ @Override ++ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ return this.scheduler.loadExecutor.createTask(run, priority); ++ } ++ ++ @Override ++ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ protected TaskResult completeOnMainOffMain(final PoiChunk data, final Throwable throwable) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ protected TaskResult runOffMain(CompoundTag data, final Throwable throwable) { ++ if (throwable != null) { ++ LOGGER.error("Failed to load poi data for task: " + this.toString() + ", poi data will be lost", throwable); ++ return new TaskResult<>(PoiChunk.empty(this.world, this.chunkX, this.chunkZ), null); ++ } ++ ++ if (data == null || data.isEmpty()) { ++ // nothing to do ++ return new TaskResult<>(PoiChunk.empty(this.world, this.chunkX, this.chunkZ), null); ++ } ++ ++ try { ++ data = data.copy(); // coming from the I/O thread, so we need to copy ++ // run converters ++ final int dataVersion = !data.contains(SharedConstants.DATA_VERSION_TAG, net.minecraft.nbt.Tag.TAG_ANY_NUMERIC) ? 1945 : data.getInt(SharedConstants.DATA_VERSION_TAG); ++ final CompoundTag converted = MCDataConverter.convertTag( ++ MCTypeRegistry.POI_CHUNK, data, dataVersion, SharedConstants.getCurrentVersion().getDataVersion().getVersion() ++ ); ++ ++ // now we need to parse it ++ return new TaskResult<>(PoiChunk.parse(this.world, this.chunkX, this.chunkZ, converted), null); ++ } catch (final ThreadDeath death) { ++ throw death; ++ } catch (final Throwable thr2) { ++ LOGGER.error("Failed to run parse poi data for task: " + this.toString() + ", poi data will be lost", thr2); ++ return new TaskResult<>(PoiChunk.empty(this.world, this.chunkX, this.chunkZ), null); ++ } ++ } ++ ++ @Override ++ protected TaskResult runOnMain(final PoiChunk data, final Throwable throwable) { ++ throw new UnsupportedOperationException(); ++ } ++ } ++ ++ public static final class EntityDataLoadTask extends CallbackDataLoadTask { ++ ++ public EntityDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, ++ final int chunkZ, final PrioritisedExecutor.Priority priority) { ++ super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.ENTITY_DATA, priority); ++ } ++ ++ @Override ++ protected boolean hasOffMain() { ++ return true; ++ } ++ ++ @Override ++ protected boolean hasOnMain() { ++ return false; ++ } ++ ++ @Override ++ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ return this.scheduler.loadExecutor.createTask(run, priority); ++ } ++ ++ @Override ++ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ protected TaskResult completeOnMainOffMain(final CompoundTag data, final Throwable throwable) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ protected TaskResult runOffMain(final CompoundTag data, final Throwable throwable) { ++ if (throwable != null) { ++ LOGGER.error("Failed to load entity data for task: " + this.toString() + ", entity data will be lost", throwable); ++ return new TaskResult<>(null, null); ++ } ++ ++ if (data == null || data.isEmpty()) { ++ // nothing to do ++ return new TaskResult<>(null, null); ++ } ++ ++ try { ++ // note: data comes from the I/O thread, so we need to copy it ++ return new TaskResult<>(EntityStorage.upgradeChunkTag(data.copy()), null); ++ } catch (final ThreadDeath death) { ++ throw death; ++ } catch (final Throwable thr2) { ++ LOGGER.error("Failed to run converters for entity data for task: " + this.toString() + ", entity data will be lost", thr2); ++ return new TaskResult<>(null, thr2); ++ } ++ } ++ ++ @Override ++ protected TaskResult runOnMain(final CompoundTag data, final Throwable throwable) { ++ throw new UnsupportedOperationException(); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkProgressionTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkProgressionTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..322675a470eacbf0e5452f4009c643f2d0b4ce24 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkProgressionTask.java +@@ -0,0 +1,105 @@ ++package io.papermc.paper.chunk.system.scheduling; ++ ++import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import java.lang.invoke.VarHandle; ++import java.util.Map; ++import java.util.function.BiConsumer; ++ ++public abstract class ChunkProgressionTask { ++ ++ private final MultiThreadedQueue> waiters = new MultiThreadedQueue<>(); ++ private ChunkAccess completedChunk; ++ private Throwable completedThrowable; ++ ++ protected final ChunkTaskScheduler scheduler; ++ protected final ServerLevel world; ++ protected final int chunkX; ++ protected final int chunkZ; ++ ++ protected volatile boolean completed; ++ protected static final VarHandle COMPLETED_HANDLE = ConcurrentUtil.getVarHandle(ChunkProgressionTask.class, "completed", boolean.class); ++ ++ protected ChunkProgressionTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ) { ++ this.scheduler = scheduler; ++ this.world = world; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ } ++ ++ // Used only for debug json ++ public abstract boolean isScheduled(); ++ ++ // Note: It is the responsibility of the task to set the chunk's status once it has completed ++ public abstract ChunkStatus getTargetStatus(); ++ ++ /* Only executed once */ ++ /* Implementations must be prepared to handle cases where cancel() is called before schedule() */ ++ public abstract void schedule(); ++ ++ /* May be called multiple times */ ++ public abstract void cancel(); ++ ++ public abstract PrioritisedExecutor.Priority getPriority(); ++ ++ /* Schedule lock is always held for the priority update calls */ ++ ++ public abstract void lowerPriority(final PrioritisedExecutor.Priority priority); ++ ++ public abstract void setPriority(final PrioritisedExecutor.Priority priority); ++ ++ public abstract void raisePriority(final PrioritisedExecutor.Priority priority); ++ ++ public final void onComplete(final BiConsumer onComplete) { ++ if (!this.waiters.add(onComplete)) { ++ try { ++ onComplete.accept(this.completedChunk, this.completedThrowable); ++ } catch (final Throwable throwable) { ++ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( ++ "Consumer", ChunkTaskScheduler.stringIfNull(onComplete), ++ "Completed throwable", ChunkTaskScheduler.stringIfNull(this.completedThrowable) ++ ), throwable); ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ } ++ } ++ } ++ ++ protected final void complete(final ChunkAccess chunk, final Throwable throwable) { ++ try { ++ this.complete0(chunk, throwable); ++ } catch (final Throwable thr2) { ++ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( ++ "Completed throwable", ChunkTaskScheduler.stringIfNull(throwable) ++ ), thr2); ++ if (thr2 instanceof ThreadDeath) { ++ throw (ThreadDeath)thr2; ++ } ++ } ++ } ++ ++ private void complete0(final ChunkAccess chunk, final Throwable throwable) { ++ if ((boolean)COMPLETED_HANDLE.getAndSet((ChunkProgressionTask)this, (boolean)true)) { ++ throw new IllegalStateException("Already completed"); ++ } ++ this.completedChunk = chunk; ++ this.completedThrowable = throwable; ++ ++ BiConsumer consumer; ++ while ((consumer = this.waiters.pollOrBlockAdds()) != null) { ++ consumer.accept(chunk, throwable); ++ } ++ } ++ ++ @Override ++ public String toString() { ++ return "ChunkProgressionTask{class: " + this.getClass().getName() + ", for world: " + this.world.getWorld().getName() + ++ ", chunk: (" + this.chunkX + "," + this.chunkZ + "), hashcode: " + System.identityHashCode(this) + ", priority: " + this.getPriority() + ++ ", status: " + this.getTargetStatus().toString() + ", scheduled: " + this.isScheduled() + "}"; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkQueue.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4cc1b3ba6d093a9683dbd8b7fe76106ae391e019 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkQueue.java +@@ -0,0 +1,160 @@ ++package io.papermc.paper.chunk.system.scheduling; ++ ++import it.unimi.dsi.fastutil.HashCommon; ++import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.atomic.AtomicLong; ++ ++public final class ChunkQueue { ++ ++ public final int coordinateShift; ++ private final AtomicLong orderGenerator = new AtomicLong(); ++ private final ConcurrentHashMap unloadSections = new ConcurrentHashMap<>(); ++ ++ /* ++ * Note: write operations do not occur in parallel for any given section. ++ * Note: coordinateShift <= region shift in order for retrieveForCurrentRegion() to function correctly ++ */ ++ ++ public ChunkQueue(final int coordinateShift) { ++ this.coordinateShift = coordinateShift; ++ } ++ ++ public static record SectionToUnload(int sectionX, int sectionZ, Coordinate coord, long order, int count) {} ++ ++ public List retrieveForAllRegions() { ++ final List ret = new ArrayList<>(); ++ ++ for (final Map.Entry entry : this.unloadSections.entrySet()) { ++ final Coordinate coord = entry.getKey(); ++ final long key = coord.key; ++ final UnloadSection section = entry.getValue(); ++ final int sectionX = Coordinate.x(key); ++ final int sectionZ = Coordinate.z(key); ++ ++ ret.add(new SectionToUnload(sectionX, sectionZ, coord, section.order, section.chunks.size())); ++ } ++ ++ ret.sort((final SectionToUnload s1, final SectionToUnload s2) -> { ++ return Long.compare(s1.order, s2.order); ++ }); ++ ++ return ret; ++ } ++ ++ public UnloadSection getSectionUnsynchronized(final int sectionX, final int sectionZ) { ++ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); ++ return this.unloadSections.get(coordinate); ++ } ++ ++ public UnloadSection removeSection(final int sectionX, final int sectionZ) { ++ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); ++ return this.unloadSections.remove(coordinate); ++ } ++ ++ // write operation ++ public boolean addChunk(final int chunkX, final int chunkZ) { ++ final int shift = this.coordinateShift; ++ final int sectionX = chunkX >> shift; ++ final int sectionZ = chunkZ >> shift; ++ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); ++ final long chunkKey = Coordinate.key(chunkX, chunkZ); ++ ++ UnloadSection section = this.unloadSections.get(coordinate); ++ if (section == null) { ++ section = new UnloadSection(this.orderGenerator.getAndIncrement()); ++ // write operations do not occur in parallel for a given section ++ this.unloadSections.put(coordinate, section); ++ } ++ ++ return section.chunks.add(chunkKey); ++ } ++ ++ // write operation ++ public boolean removeChunk(final int chunkX, final int chunkZ) { ++ final int shift = this.coordinateShift; ++ final int sectionX = chunkX >> shift; ++ final int sectionZ = chunkZ >> shift; ++ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); ++ final long chunkKey = Coordinate.key(chunkX, chunkZ); ++ ++ final UnloadSection section = this.unloadSections.get(coordinate); ++ ++ if (section == null) { ++ return false; ++ } ++ ++ if (!section.chunks.remove(chunkKey)) { ++ return false; ++ } ++ ++ if (section.chunks.isEmpty()) { ++ this.unloadSections.remove(coordinate); ++ } ++ ++ return true; ++ } ++ ++ public static final class UnloadSection { ++ ++ public final long order; ++ public final LongLinkedOpenHashSet chunks = new LongLinkedOpenHashSet(); ++ ++ public UnloadSection(final long order) { ++ this.order = order; ++ } ++ } ++ ++ private static final class Coordinate implements Comparable { ++ ++ public final long key; ++ ++ public Coordinate(final long key) { ++ this.key = key; ++ } ++ ++ public Coordinate(final int x, final int z) { ++ this.key = key(x, z); ++ } ++ ++ public static long key(final int x, final int z) { ++ return ((long)z << 32) | (x & 0xFFFFFFFFL); ++ } ++ ++ public static int x(final long key) { ++ return (int)key; ++ } ++ ++ public static int z(final long key) { ++ return (int)(key >>> 32); ++ } ++ ++ @Override ++ public int hashCode() { ++ return (int)HashCommon.mix(this.key); ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ ++ if (!(obj instanceof Coordinate other)) { ++ return false; ++ } ++ ++ return this.key == other.key; ++ } ++ ++ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain ++ // is too large. So we should implement compareTo to help. ++ @Override ++ public int compareTo(final Coordinate other) { ++ return Long.compare(this.key, other.key); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..17ce14f2dcbf900890efbc2351782bc6f8867068 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java +@@ -0,0 +1,883 @@ ++package io.papermc.paper.chunk.system.scheduling; ++ ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool; ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue; ++import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.chunk.system.io.RegionFileIOThread; ++import io.papermc.paper.chunk.system.scheduling.queue.RadiusAwarePrioritisedExecutor; ++import io.papermc.paper.configuration.GlobalConfiguration; ++import io.papermc.paper.util.CoordinateUtils; ++import io.papermc.paper.util.TickThread; ++import java.util.function.BooleanSupplier; ++import net.minecraft.CrashReport; ++import net.minecraft.CrashReportCategory; ++import net.minecraft.ReportedException; ++import io.papermc.paper.util.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkMap; ++import net.minecraft.server.level.FullChunkStatus; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.TicketType; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.LevelChunk; ++import org.bukkit.Bukkit; ++import org.slf4j.Logger; ++import java.io.File; ++import java.util.ArrayDeque; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collections; ++import java.util.List; ++import java.util.Map; ++import java.util.Objects; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.function.Consumer; ++ ++public final class ChunkTaskScheduler { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ static int newChunkSystemIOThreads; ++ static int newChunkSystemWorkerThreads; ++ static int newChunkSystemGenParallelism; ++ static int newChunkSystemLoadParallelism; ++ ++ public static ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool workerThreads; ++ ++ private static boolean initialised = false; ++ ++ public static void init(final GlobalConfiguration.ChunkSystem config) { ++ if (initialised) { ++ return; ++ } ++ initialised = true; ++ newChunkSystemIOThreads = config.ioThreads; ++ newChunkSystemWorkerThreads = config.workerThreads; ++ if (newChunkSystemIOThreads < 0) { ++ newChunkSystemIOThreads = 1; ++ } else { ++ newChunkSystemIOThreads = Math.max(1, newChunkSystemIOThreads); ++ } ++ int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; ++ if (defaultWorkerThreads <= 4) { ++ defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; ++ } else { ++ defaultWorkerThreads = defaultWorkerThreads / 2; ++ } ++ defaultWorkerThreads = Integer.getInteger("Paper.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); ++ ++ if (newChunkSystemWorkerThreads < 0) { ++ newChunkSystemWorkerThreads = defaultWorkerThreads; ++ } else { ++ newChunkSystemWorkerThreads = Math.max(1, newChunkSystemWorkerThreads); ++ } ++ ++ String newChunkSystemGenParallelism = config.genParallelism; ++ if (newChunkSystemGenParallelism.equalsIgnoreCase("default")) { ++ newChunkSystemGenParallelism = "true"; ++ } ++ boolean useParallelGen; ++ if (newChunkSystemGenParallelism.equalsIgnoreCase("on") || newChunkSystemGenParallelism.equalsIgnoreCase("enabled") ++ || newChunkSystemGenParallelism.equalsIgnoreCase("true")) { ++ useParallelGen = true; ++ } else if (newChunkSystemGenParallelism.equalsIgnoreCase("off") || newChunkSystemGenParallelism.equalsIgnoreCase("disabled") ++ || newChunkSystemGenParallelism.equalsIgnoreCase("false")) { ++ useParallelGen = false; ++ } else { ++ throw new IllegalStateException("Invalid option for gen-parallelism: must be one of [on, off, enabled, disabled, true, false, default]"); ++ } ++ ++ ChunkTaskScheduler.newChunkSystemGenParallelism = useParallelGen ? newChunkSystemWorkerThreads : 1; ++ ChunkTaskScheduler.newChunkSystemLoadParallelism = newChunkSystemWorkerThreads; ++ ++ RegionFileIOThread.init(newChunkSystemIOThreads); ++ workerThreads = new ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool( ++ "Paper Chunk System Worker Pool", newChunkSystemWorkerThreads, ++ (final Thread thread, final Integer id) -> { ++ thread.setPriority(Thread.NORM_PRIORITY - 2); ++ thread.setName("Tuinity Chunk System Worker #" + id.intValue()); ++ thread.setUncaughtExceptionHandler(io.papermc.paper.chunk.system.scheduling.NewChunkHolder.CHUNKSYSTEM_UNCAUGHT_EXCEPTION_HANDLER); ++ }, (long)(20.0e6)); // 20ms ++ ++ LOGGER.info("Chunk system is using " + newChunkSystemIOThreads + " I/O threads, " + newChunkSystemWorkerThreads + " worker threads, and gen parallelism of " + ChunkTaskScheduler.newChunkSystemGenParallelism + " threads"); ++ } ++ ++ public final ServerLevel world; ++ public final PrioritisedThreadPool workers; ++ public final RadiusAwarePrioritisedExecutor radiusAwareScheduler; ++ public final PrioritisedThreadPool.PrioritisedPoolExecutor parallelGenExecutor; ++ private final PrioritisedThreadPool.PrioritisedPoolExecutor radiusAwareGenExecutor; ++ public final PrioritisedThreadPool.PrioritisedPoolExecutor loadExecutor; ++ ++ private final PrioritisedThreadedTaskQueue mainThreadExecutor = new PrioritisedThreadedTaskQueue(); ++ ++ public final ChunkHolderManager chunkHolderManager; ++ ++ static { ++ ChunkStatus.EMPTY.writeRadius = 0; ++ ChunkStatus.STRUCTURE_STARTS.writeRadius = 0; ++ ChunkStatus.STRUCTURE_REFERENCES.writeRadius = 0; ++ ChunkStatus.BIOMES.writeRadius = 0; ++ ChunkStatus.NOISE.writeRadius = 0; ++ ChunkStatus.SURFACE.writeRadius = 0; ++ ChunkStatus.CARVERS.writeRadius = 0; ++ ChunkStatus.FEATURES.writeRadius = 1; ++ ChunkStatus.INITIALIZE_LIGHT.writeRadius = 0; ++ ChunkStatus.LIGHT.writeRadius = 2; ++ ChunkStatus.SPAWN.writeRadius = 0; ++ ChunkStatus.FULL.writeRadius = 0; ++ ++ /* ++ It's important that the neighbour read radius is taken into account. If _any_ later status is using some chunk as ++ a neighbour, it must be also safe if that neighbour is being generated. i.e for any status later than FEATURES, ++ for a status to be parallel safe it must not read the block data from its neighbours. ++ */ ++ final List parallelCapableStatus = Arrays.asList( ++ // No-op executor. ++ ChunkStatus.EMPTY, ++ ++ // This is parallel capable, as CB has fixed the concurrency issue with stronghold generations. ++ // Does not touch neighbour chunks. ++ ChunkStatus.STRUCTURE_STARTS, ++ ++ // Surprisingly this is parallel capable. It is simply reading the already-created structure starts ++ // into the structure references for the chunk. So while it reads from it neighbours, its neighbours ++ // will not change, even if executed in parallel. ++ ChunkStatus.STRUCTURE_REFERENCES, ++ ++ // Safe. Mojang runs it in parallel as well. ++ ChunkStatus.BIOMES, ++ ++ // Safe. Mojang runs it in parallel as well. ++ ChunkStatus.NOISE, ++ ++ // Parallel safe. Only touches the target chunk. Biome retrieval is now noise based, which is ++ // completely thread-safe. ++ ChunkStatus.SURFACE, ++ ++ // No global state is modified in the carvers. It only touches the specified chunk. So it is parallel safe. ++ ChunkStatus.CARVERS, ++ ++ // FEATURES is not parallel safe. It writes to neighbours. ++ ++ // no-op executor ++ ChunkStatus.INITIALIZE_LIGHT ++ ++ // LIGHT is not parallel safe. It also doesn't run on the generation executor, so no point. ++ ++ // Only writes to the specified chunk. State is not read by later statuses. Parallel safe. ++ // Note: it may look unsafe because it writes to a worldgenregion, but the region size is always 0 - ++ // see the task margin. ++ // However, if the neighbouring FEATURES chunk is unloaded, but then fails to load in again (for whatever ++ // reason), then it would write to this chunk - and since this status reads blocks from itself, it's not ++ // safe to execute this in parallel. ++ // SPAWN ++ ++ // FULL is executed on main. ++ ); ++ ++ for (final ChunkStatus status : parallelCapableStatus) { ++ status.isParallelCapable = true; ++ } ++ } ++ ++ private static final int[] ACCESS_RADIUS_TABLE = new int[ChunkStatus.getStatusList().size()]; ++ private static final int[] MAX_ACCESS_RADIUS_TABLE = new int[ACCESS_RADIUS_TABLE.length]; ++ static { ++ Arrays.fill(ACCESS_RADIUS_TABLE, -1); ++ } ++ ++ private static int getAccessRadius0(final ChunkStatus genStatus) { ++ if (genStatus == ChunkStatus.EMPTY) { ++ return 0; ++ } ++ ++ final int radius = Math.max(genStatus.loadRange, genStatus.getRange()); ++ int maxRange = radius; ++ ++ for (int dist = 1; dist <= radius; ++dist) { ++ final ChunkStatus requiredNeighbourStatus = ChunkMap.getDependencyStatus(genStatus, radius); ++ final int rad = ACCESS_RADIUS_TABLE[requiredNeighbourStatus.getIndex()]; ++ if (rad == -1) { ++ throw new IllegalStateException(); ++ } ++ ++ maxRange = Math.max(maxRange, dist + rad); ++ } ++ ++ return maxRange; ++ } ++ ++ private static int maxAccessRadius; ++ ++ static { ++ final List statuses = ChunkStatus.getStatusList(); ++ for (int i = 0, len = statuses.size(); i < len; ++i) { ++ ACCESS_RADIUS_TABLE[i] = getAccessRadius0(statuses.get(i)); ++ } ++ int max = 0; ++ for (int i = 0, len = statuses.size(); i < len; ++i) { ++ MAX_ACCESS_RADIUS_TABLE[i] = max = Math.max(ACCESS_RADIUS_TABLE[i], max); ++ } ++ maxAccessRadius = max; ++ } ++ ++ public static int getMaxAccessRadius() { ++ return maxAccessRadius; ++ } ++ ++ public static int getAccessRadius(final ChunkStatus genStatus) { ++ return ACCESS_RADIUS_TABLE[genStatus.getIndex()]; ++ } ++ ++ public static int getAccessRadius(final FullChunkStatus status) { ++ return (status.ordinal() - 1) + getAccessRadius(ChunkStatus.FULL); ++ } ++ ++ final ReentrantAreaLock schedulingLockArea; ++ private final int lockShift; ++ ++ public final int getChunkSystemLockShift() { ++ return this.lockShift; ++ } ++ // Folia end - use area based lock to reduce contention ++ ++ public ChunkTaskScheduler(final ServerLevel world, final PrioritisedThreadPool workers) { ++ this.world = world; ++ this.workers = workers; ++ // must be >= region shift (in paper, doesn't exist) and must be >= ticket propagator section shift ++ // it must be >= region shift since the regioniser assumes ticket updates do not occur in parallel for the region sections ++ // it must be >= ticket propagator section shift so that the ticket propagator can assume that owning a position implies owning ++ // the entire section ++ // we just take the max, as we want the smallest shift that satisfies these properties ++ this.lockShift = Math.max(world.getRegionChunkShift(), ThreadedTicketLevelPropagator.SECTION_SHIFT); ++ this.schedulingLockArea = new ReentrantAreaLock(this.getChunkSystemLockShift()); ++ ++ final String worldName = world.getWorld().getName(); ++ this.parallelGenExecutor = workers.createExecutor("Chunk parallel generation executor for world '" + worldName + "'", Math.max(1, newChunkSystemGenParallelism)); ++ this.radiusAwareGenExecutor = ++ newChunkSystemGenParallelism <= 1 ? this.parallelGenExecutor : workers.createExecutor("Chunk radius aware generator for world '" + worldName + "'", newChunkSystemGenParallelism); ++ this.loadExecutor = workers.createExecutor("Chunk load executor for world '" + worldName + "'", newChunkSystemLoadParallelism); ++ this.radiusAwareScheduler = new RadiusAwarePrioritisedExecutor(this.radiusAwareGenExecutor, Math.max(1, newChunkSystemGenParallelism)); ++ this.chunkHolderManager = new ChunkHolderManager(world, this); ++ } ++ ++ private final AtomicBoolean failedChunkSystem = new AtomicBoolean(); ++ ++ public static Object stringIfNull(final Object obj) { ++ return obj == null ? "null" : obj; ++ } ++ ++ public void unrecoverableChunkSystemFailure(final int chunkX, final int chunkZ, final Map objectsOfInterest, final Throwable thr) { ++ final NewChunkHolder holder = this.chunkHolderManager.getChunkHolder(chunkX, chunkZ); ++ LOGGER.error("Chunk system error at chunk (" + chunkX + "," + chunkZ + "), holder: " + holder + ", exception:", new Throwable(thr)); ++ ++ if (this.failedChunkSystem.getAndSet(true)) { ++ return; ++ } ++ ++ final ReportedException reportedException = thr instanceof ReportedException ? (ReportedException)thr : new ReportedException(new CrashReport("Chunk system error", thr)); ++ ++ CrashReportCategory crashReportCategory = reportedException.getReport().addCategory("Chunk system details"); ++ crashReportCategory.setDetail("Chunk coordinate", new ChunkPos(chunkX, chunkZ).toString()); ++ crashReportCategory.setDetail("ChunkHolder", Objects.toString(holder)); ++ crashReportCategory.setDetail("unrecoverableChunkSystemFailure caller thread", Thread.currentThread().getName()); ++ ++ crashReportCategory = reportedException.getReport().addCategory("Chunk System Objects of Interest"); ++ for (final Map.Entry entry : objectsOfInterest.entrySet()) { ++ if (entry.getValue() instanceof Throwable thrObject) { ++ crashReportCategory.setDetailError(Objects.toString(entry.getKey()), thrObject); ++ } else { ++ crashReportCategory.setDetail(Objects.toString(entry.getKey()), Objects.toString(entry.getValue())); ++ } ++ } ++ ++ final Runnable crash = () -> { ++ throw new RuntimeException("Chunk system crash propagated from unrecoverableChunkSystemFailure", reportedException); ++ }; ++ ++ // this may not be good enough, specifically thanks to stupid ass plugins swallowing exceptions ++ this.scheduleChunkTask(chunkX, chunkZ, crash, PrioritisedExecutor.Priority.BLOCKING); ++ // so, make the main thread pick it up ++ MinecraftServer.chunkSystemCrash = new RuntimeException("Chunk system crash propagated from unrecoverableChunkSystemFailure", reportedException); ++ } ++ ++ public boolean executeMainThreadTask() { ++ TickThread.ensureTickThread("Cannot execute main thread task off-main"); ++ return this.mainThreadExecutor.executeTask(); ++ } ++ ++ public void raisePriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { ++ this.chunkHolderManager.raisePriority(x, z, priority); ++ } ++ ++ public void setPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { ++ this.chunkHolderManager.setPriority(x, z, priority); ++ } ++ ++ public void lowerPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { ++ this.chunkHolderManager.lowerPriority(x, z, priority); ++ } ++ ++ private final AtomicLong chunkLoadCounter = new AtomicLong(); ++ ++ public void scheduleTickingState(final int chunkX, final int chunkZ, final FullChunkStatus toStatus, ++ final boolean addTicket, final PrioritisedExecutor.Priority priority, ++ final Consumer onComplete) { ++ if (!TickThread.isTickThread()) { ++ this.scheduleChunkTask(chunkX, chunkZ, () -> { ++ ChunkTaskScheduler.this.scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); ++ }, priority); ++ return; ++ } ++ final int accessRadius = getAccessRadius(toStatus); ++ if (this.chunkHolderManager.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { ++ throw new IllegalStateException("Cannot schedule chunk load during ticket level update"); ++ } ++ if (this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { ++ throw new IllegalStateException("Cannot schedule chunk loading recursively"); ++ } ++ ++ if (toStatus == FullChunkStatus.INACCESSIBLE) { ++ throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status"); ++ } ++ ++ final int minLevel = 33 - (toStatus.ordinal() - 1); ++ final Long chunkReference = addTicket ? Long.valueOf(this.chunkLoadCounter.getAndIncrement()) : null; ++ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ ++ if (addTicket) { ++ this.chunkHolderManager.addTicketAtLevel(TicketType.CHUNK_LOAD, chunkKey, minLevel, chunkReference); ++ this.chunkHolderManager.processTicketUpdates(); ++ } ++ ++ final Consumer loadCallback = (final LevelChunk chunk) -> { ++ try { ++ if (onComplete != null) { ++ onComplete.accept(chunk); ++ } ++ } finally { ++ if (addTicket) { ++ ChunkTaskScheduler.this.chunkHolderManager.addAndRemoveTickets(chunkKey, ++ TicketType.UNKNOWN, minLevel, new ChunkPos(chunkKey), ++ TicketType.CHUNK_LOAD, minLevel, chunkReference ++ ); ++ } ++ } ++ }; ++ ++ final boolean scheduled; ++ final LevelChunk chunk; ++ final ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius); ++ try { ++ final ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius); ++ try { ++ final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey); ++ if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) { ++ scheduled = false; ++ chunk = null; ++ } else { ++ final FullChunkStatus currStatus = chunkHolder.getChunkStatus(); ++ if (currStatus.isOrAfter(toStatus)) { ++ scheduled = false; ++ chunk = (LevelChunk)chunkHolder.getCurrentChunk(); ++ } else { ++ scheduled = true; ++ chunk = null; ++ ++ final int radius = toStatus.ordinal() - 1; // 0 -> BORDER, 1 -> TICKING, 2 -> ENTITY_TICKING ++ for (int dz = -radius; dz <= radius; ++dz) { ++ for (int dx = -radius; dx <= radius; ++dx) { ++ final NewChunkHolder neighbour = ++ (dx | dz) == 0 ? chunkHolder : this.chunkHolderManager.getChunkHolder(dx + chunkX, dz + chunkZ); ++ if (neighbour != null) { ++ neighbour.raisePriority(priority); ++ } ++ } ++ } ++ ++ // ticket level should schedule for us ++ chunkHolder.addFullStatusConsumer(toStatus, loadCallback); ++ } ++ } ++ } finally { ++ this.schedulingLockArea.unlock(schedulingLock); ++ } ++ } finally { ++ this.chunkHolderManager.ticketLockArea.unlock(ticketLock); ++ } ++ ++ if (!scheduled) { ++ // couldn't schedule ++ try { ++ loadCallback.accept(chunk); ++ } catch (final ThreadDeath thr) { ++ throw thr; ++ } catch (final Throwable thr) { ++ LOGGER.error("Failed to process chunk full status callback", thr); ++ } ++ } ++ } ++ ++ public void scheduleChunkLoad(final int chunkX, final int chunkZ, final boolean gen, final ChunkStatus toStatus, final boolean addTicket, ++ final PrioritisedExecutor.Priority priority, final Consumer onComplete) { ++ if (gen) { ++ this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); ++ return; ++ } ++ this.scheduleChunkLoad(chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> { ++ if (chunk == null) { ++ onComplete.accept(null); ++ } else { ++ if (chunk.getStatus().isOrAfter(toStatus)) { ++ this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); ++ } else { ++ onComplete.accept(null); ++ } ++ } ++ }); ++ } ++ ++ // only appropriate to use with ServerLevel#syncLoadNonFull ++ public boolean beginChunkLoadForNonFullSync(final int chunkX, final int chunkZ, final ChunkStatus toStatus, ++ final PrioritisedExecutor.Priority priority) { ++ final int accessRadius = getAccessRadius(toStatus); ++ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ final int minLevel = 33 + ChunkStatus.getDistance(toStatus); ++ final List tasks = new ArrayList<>(); ++ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention ++ try { ++ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention ++ try { ++ final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey); ++ if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) { ++ return false; ++ } else { ++ final ChunkStatus genStatus = chunkHolder.getCurrentGenStatus(); ++ if (genStatus != null && genStatus.isOrAfter(toStatus)) { ++ return true; ++ } else { ++ chunkHolder.raisePriority(priority); ++ ++ if (!chunkHolder.upgradeGenTarget(toStatus)) { ++ this.schedule(chunkX, chunkZ, toStatus, chunkHolder, tasks); ++ } ++ } ++ } ++ } finally { ++ this.schedulingLockArea.unlock(schedulingLock); ++ } ++ } finally { ++ this.chunkHolderManager.ticketLockArea.unlock(ticketLock); ++ } ++ ++ for (int i = 0, len = tasks.size(); i < len; ++i) { ++ tasks.get(i).schedule(); ++ } ++ ++ return true; ++ } ++ ++ public void scheduleChunkLoad(final int chunkX, final int chunkZ, final ChunkStatus toStatus, final boolean addTicket, ++ final PrioritisedExecutor.Priority priority, final Consumer onComplete) { ++ if (!TickThread.isTickThread()) { ++ this.scheduleChunkTask(chunkX, chunkZ, () -> { ++ ChunkTaskScheduler.this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); ++ }, priority); ++ return; ++ } ++ final int accessRadius = getAccessRadius(toStatus); ++ if (this.chunkHolderManager.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { ++ throw new IllegalStateException("Cannot schedule chunk load during ticket level update"); ++ } ++ if (this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { ++ throw new IllegalStateException("Cannot schedule chunk loading recursively"); ++ } ++ ++ if (toStatus == ChunkStatus.FULL) { ++ this.scheduleTickingState(chunkX, chunkZ, FullChunkStatus.FULL, addTicket, priority, (Consumer)onComplete); ++ return; ++ } ++ ++ final int minLevel = 33 + ChunkStatus.getDistance(toStatus); ++ final Long chunkReference = addTicket ? Long.valueOf(this.chunkLoadCounter.getAndIncrement()) : null; ++ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ ++ if (addTicket) { ++ this.chunkHolderManager.addTicketAtLevel(TicketType.CHUNK_LOAD, chunkKey, minLevel, chunkReference); ++ this.chunkHolderManager.processTicketUpdates(); ++ } ++ ++ final Consumer loadCallback = (final ChunkAccess chunk) -> { ++ try { ++ if (onComplete != null) { ++ onComplete.accept(chunk); ++ } ++ } finally { ++ if (addTicket) { ++ ChunkTaskScheduler.this.chunkHolderManager.addAndRemoveTickets(chunkKey, ++ TicketType.UNKNOWN, minLevel, new ChunkPos(chunkKey), ++ TicketType.CHUNK_LOAD, minLevel, chunkReference ++ ); ++ } ++ } ++ }; ++ ++ final List tasks = new ArrayList<>(); ++ ++ final boolean scheduled; ++ final ChunkAccess chunk; ++ final ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius); ++ try { ++ final ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius); ++ try { ++ final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey); ++ if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) { ++ scheduled = false; ++ chunk = null; ++ } else { ++ final ChunkStatus genStatus = chunkHolder.getCurrentGenStatus(); ++ if (genStatus != null && genStatus.isOrAfter(toStatus)) { ++ scheduled = false; ++ chunk = chunkHolder.getCurrentChunk(); ++ } else { ++ scheduled = true; ++ chunk = null; ++ chunkHolder.raisePriority(priority); ++ ++ if (!chunkHolder.upgradeGenTarget(toStatus)) { ++ this.schedule(chunkX, chunkZ, toStatus, chunkHolder, tasks); ++ } ++ chunkHolder.addStatusConsumer(toStatus, loadCallback); ++ } ++ } ++ } finally { ++ this.schedulingLockArea.unlock(schedulingLock); ++ } ++ } finally { ++ this.chunkHolderManager.ticketLockArea.unlock(ticketLock); ++ } ++ ++ for (int i = 0, len = tasks.size(); i < len; ++i) { ++ tasks.get(i).schedule(); ++ } ++ ++ if (!scheduled) { ++ // couldn't schedule ++ try { ++ loadCallback.accept(chunk); ++ } catch (final ThreadDeath thr) { ++ throw thr; ++ } catch (final Throwable thr) { ++ LOGGER.error("Failed to process chunk status callback", thr); ++ } ++ } ++ } ++ ++ private ChunkProgressionTask createTask(final int chunkX, final int chunkZ, final ChunkAccess chunk, ++ final NewChunkHolder chunkHolder, final List neighbours, ++ final ChunkStatus toStatus, final PrioritisedExecutor.Priority initialPriority) { ++ if (toStatus == ChunkStatus.EMPTY) { ++ return new ChunkLoadTask(this, this.world, chunkX, chunkZ, chunkHolder, initialPriority); ++ } ++ if (toStatus == ChunkStatus.LIGHT) { ++ return new ChunkLightTask(this, this.world, chunkX, chunkZ, chunk, initialPriority); ++ } ++ if (toStatus == ChunkStatus.FULL) { ++ return new ChunkFullTask(this, this.world, chunkX, chunkZ, chunkHolder, chunk, initialPriority); ++ } ++ ++ return new ChunkUpgradeGenericStatusTask(this, this.world, chunkX, chunkZ, chunk, neighbours, toStatus, initialPriority); ++ } ++ ++ ChunkProgressionTask schedule(final int chunkX, final int chunkZ, final ChunkStatus targetStatus, final NewChunkHolder chunkHolder, ++ final List allTasks) { ++ return this.schedule(chunkX, chunkZ, targetStatus, chunkHolder, allTasks, chunkHolder.getEffectivePriority()); ++ } ++ ++ // rets new task scheduled for the _specified_ chunk ++ // note: this must hold the scheduling lock ++ // minPriority is only used to pass the priority through to neighbours, as priority calculation has not yet been done ++ // schedule will ignore the generation target, so it should be checked by the caller to ensure the target is not regressed! ++ private ChunkProgressionTask schedule(final int chunkX, final int chunkZ, final ChunkStatus targetStatus, ++ final NewChunkHolder chunkHolder, final List allTasks, ++ final PrioritisedExecutor.Priority minPriority) { ++ if (!this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, getAccessRadius(targetStatus))) { ++ throw new IllegalStateException("Not holding scheduling lock"); ++ } ++ ++ if (chunkHolder.hasGenerationTask()) { ++ chunkHolder.upgradeGenTarget(targetStatus); ++ return null; ++ } ++ ++ final PrioritisedExecutor.Priority requestedPriority = PrioritisedExecutor.Priority.max(minPriority, chunkHolder.getEffectivePriority()); ++ final ChunkStatus currentGenStatus = chunkHolder.getCurrentGenStatus(); ++ final ChunkAccess chunk = chunkHolder.getCurrentChunk(); ++ ++ if (currentGenStatus == null) { ++ // not yet loaded ++ final ChunkProgressionTask task = this.createTask( ++ chunkX, chunkZ, chunk, chunkHolder, Collections.emptyList(), ChunkStatus.EMPTY, requestedPriority ++ ); ++ ++ allTasks.add(task); ++ ++ final List chunkHolderNeighbours = new ArrayList<>(1); ++ chunkHolderNeighbours.add(chunkHolder); ++ ++ chunkHolder.setGenerationTarget(targetStatus); ++ chunkHolder.setGenerationTask(task, ChunkStatus.EMPTY, chunkHolderNeighbours); ++ ++ return task; ++ } ++ ++ if (currentGenStatus.isOrAfter(targetStatus)) { ++ // nothing to do ++ return null; ++ } ++ ++ // we know for sure now that we want to schedule _something_, so set the target ++ chunkHolder.setGenerationTarget(targetStatus); ++ ++ final ChunkStatus chunkRealStatus = chunk.getStatus(); ++ final ChunkStatus toStatus = currentGenStatus.getNextStatus(); ++ ++ // if this chunk has already generated up to or past the specified status, then we don't ++ // need the neighbours AT ALL. ++ final int neighbourReadRadius = chunkRealStatus.isOrAfter(toStatus) ? toStatus.loadRange : toStatus.getRange(); ++ ++ boolean unGeneratedNeighbours = false; ++ ++ // copied from MCUtil.getSpiralOutChunks ++ for (int r = 1; r <= neighbourReadRadius; r++) { ++ int x = -r; ++ int z = r; ++ ++ // Iterates the edge of half of the box; then negates for other half. ++ while (x <= r && z > -r) { ++ final int radius = Math.max(Math.abs(x), Math.abs(z)); ++ final ChunkStatus requiredNeighbourStatus = ChunkMap.getDependencyStatus(toStatus, radius); ++ ++ unGeneratedNeighbours |= this.checkNeighbour( ++ chunkX + x, chunkZ + z, requiredNeighbourStatus, chunkHolder, allTasks, requestedPriority ++ ); ++ unGeneratedNeighbours |= this.checkNeighbour( ++ chunkX - x, chunkZ - z, requiredNeighbourStatus, chunkHolder, allTasks, requestedPriority ++ ); ++ ++ if (x < r) { ++ x++; ++ } else { ++ z--; ++ } ++ } ++ } ++ ++ if (unGeneratedNeighbours) { ++ // can't schedule, but neighbour completion will schedule for us when they're ALL done ++ ++ // propagate our priority to neighbours ++ chunkHolder.recalculateNeighbourPriorities(); ++ return null; ++ } ++ ++ // need to gather neighbours ++ ++ final List neighbours; ++ final List chunkHolderNeighbours; ++ if (neighbourReadRadius <= 0) { ++ neighbours = new ArrayList<>(1); ++ chunkHolderNeighbours = new ArrayList<>(1); ++ neighbours.add(chunk); ++ chunkHolderNeighbours.add(chunkHolder); ++ } else { ++ // the iteration order is _very_ important, as all generation statuses expect a certain order such that: ++ // chunkAtRelative = neighbours.get(relX + relZ * (2 * radius + 1)) ++ neighbours = new ArrayList<>((2 * neighbourReadRadius + 1) * (2 * neighbourReadRadius + 1)); ++ chunkHolderNeighbours = new ArrayList<>((2 * neighbourReadRadius + 1) * (2 * neighbourReadRadius + 1)); ++ for (int dz = -neighbourReadRadius; dz <= neighbourReadRadius; ++dz) { ++ for (int dx = -neighbourReadRadius; dx <= neighbourReadRadius; ++dx) { ++ final NewChunkHolder holder = (dx | dz) == 0 ? chunkHolder : this.chunkHolderManager.getChunkHolder(dx + chunkX, dz + chunkZ); ++ neighbours.add(holder.getChunkForNeighbourAccess()); ++ chunkHolderNeighbours.add(holder); ++ } ++ } ++ } ++ ++ final ChunkProgressionTask task = this.createTask(chunkX, chunkZ, chunk, chunkHolder, neighbours, toStatus, chunkHolder.getEffectivePriority()); ++ allTasks.add(task); ++ ++ chunkHolder.setGenerationTask(task, toStatus, chunkHolderNeighbours); ++ ++ return task; ++ } ++ ++ // rets true if the neighbour is not at the required status, false otherwise ++ private boolean checkNeighbour(final int chunkX, final int chunkZ, final ChunkStatus requiredStatus, final NewChunkHolder center, ++ final List tasks, final PrioritisedExecutor.Priority minPriority) { ++ final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkX, chunkZ); ++ ++ if (chunkHolder == null) { ++ throw new IllegalStateException("Missing chunkholder when required"); ++ } ++ ++ final ChunkStatus holderStatus = chunkHolder.getCurrentGenStatus(); ++ if (holderStatus != null && holderStatus.isOrAfter(requiredStatus)) { ++ return false; ++ } ++ ++ if (chunkHolder.hasFailedGeneration()) { ++ return true; ++ } ++ ++ center.addGenerationBlockingNeighbour(chunkHolder); ++ chunkHolder.addWaitingNeighbour(center, requiredStatus); ++ ++ if (chunkHolder.upgradeGenTarget(requiredStatus)) { ++ return true; ++ } ++ ++ // not at status required, so we need to schedule its generation ++ this.schedule( ++ chunkX, chunkZ, requiredStatus, chunkHolder, tasks, minPriority ++ ); ++ ++ return true; ++ } ++ ++ /** ++ * @deprecated Chunk tasks must be tied to coordinates in the future ++ */ ++ @Deprecated ++ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run) { ++ return this.scheduleChunkTask(run, PrioritisedExecutor.Priority.NORMAL); ++ } ++ ++ /** ++ * @deprecated Chunk tasks must be tied to coordinates in the future ++ */ ++ @Deprecated ++ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ return this.mainThreadExecutor.queueRunnable(run, priority); ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run) { ++ return this.createChunkTask(chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL); ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run, ++ final PrioritisedExecutor.Priority priority) { ++ return this.mainThreadExecutor.createTask(run, priority); ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run) { ++ return this.mainThreadExecutor.queueRunnable(run); ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run, ++ final PrioritisedExecutor.Priority priority) { ++ return this.mainThreadExecutor.queueRunnable(run, priority); ++ } ++ ++ public void executeTasksUntil(final BooleanSupplier exit) { ++ if (Bukkit.isPrimaryThread()) { ++ this.mainThreadExecutor.executeConditionally(exit); ++ } else { ++ long counter = 1L; ++ while (!exit.getAsBoolean()) { ++ counter = ConcurrentUtil.linearLongBackoff(counter, 100_000L, 5_000_000L); // 100us, 5ms ++ } ++ } ++ } ++ ++ public boolean halt(final boolean sync, final long maxWaitNS) { ++ this.radiusAwareGenExecutor.halt(); ++ this.parallelGenExecutor.halt(); ++ this.loadExecutor.halt(); ++ final long time = System.nanoTime(); ++ if (sync) { ++ for (long failures = 9L;; failures = ConcurrentUtil.linearLongBackoff(failures, 500_000L, 50_000_000L)) { ++ if ( ++ !this.radiusAwareGenExecutor.isActive() && ++ !this.parallelGenExecutor.isActive() && ++ !this.loadExecutor.isActive() ++ ) { ++ return true; ++ } ++ if ((System.nanoTime() - time) >= maxWaitNS) { ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ public static final ArrayDeque WAITING_CHUNKS = new ArrayDeque<>(); // stack ++ ++ public static final class ChunkInfo { ++ ++ public final int chunkX; ++ public final int chunkZ; ++ public final ServerLevel world; ++ ++ public ChunkInfo(final int chunkX, final int chunkZ, final ServerLevel world) { ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.world = world; ++ } ++ ++ @Override ++ public String toString() { ++ return "[( " + this.chunkX + "," + this.chunkZ + ") in '" + this.world.getWorld().getName() + "']"; ++ } ++ } ++ ++ public static void pushChunkWait(final ServerLevel world, final int chunkX, final int chunkZ) { ++ synchronized (WAITING_CHUNKS) { ++ WAITING_CHUNKS.push(new ChunkInfo(chunkX, chunkZ, world)); ++ } ++ } ++ ++ public static void popChunkWait() { ++ synchronized (WAITING_CHUNKS) { ++ WAITING_CHUNKS.pop(); ++ } ++ } ++ ++ public static ChunkInfo[] getChunkInfos() { ++ synchronized (WAITING_CHUNKS) { ++ return WAITING_CHUNKS.toArray(new ChunkInfo[0]); ++ } ++ } ++ ++ public static void dumpAllChunkLoadInfo(final boolean longPrint) { ++ final ChunkInfo[] chunkInfos = getChunkInfos(); ++ if (chunkInfos.length > 0) { ++ LOGGER.error("Chunk wait task info below: "); ++ for (final ChunkInfo chunkInfo : chunkInfos) { ++ final NewChunkHolder holder = chunkInfo.world.chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkInfo.chunkX, chunkInfo.chunkZ); ++ LOGGER.error("Chunk wait: " + chunkInfo); ++ LOGGER.error("Chunk holder: " + holder); ++ } ++ ++ if (longPrint) { ++ final File file = new File(new File(new File("."), "debug"), "chunks-watchdog.txt"); ++ LOGGER.error("Writing chunk information dump to " + file); ++ try { ++ MCUtil.dumpChunks(file, true); ++ LOGGER.error("Successfully written chunk information!"); ++ } catch (final Throwable thr) { ++ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkUpgradeGenericStatusTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkUpgradeGenericStatusTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e929d55fdb6fad6587af058dff6cadb6bc2a156b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkUpgradeGenericStatusTask.java +@@ -0,0 +1,212 @@ ++package io.papermc.paper.chunk.system.scheduling; ++ ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import com.mojang.datafixers.util.Either; ++import com.mojang.logging.LogUtils; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ChunkMap; ++import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.ProtoChunk; ++import org.slf4j.Logger; ++import java.lang.invoke.VarHandle; ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.CompletableFuture; ++ ++public final class ChunkUpgradeGenericStatusTask extends ChunkProgressionTask implements Runnable { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ protected final ChunkAccess fromChunk; ++ protected final ChunkStatus fromStatus; ++ protected final ChunkStatus toStatus; ++ protected final List neighbours; ++ ++ protected final PrioritisedExecutor.PrioritisedTask generateTask; ++ ++ public ChunkUpgradeGenericStatusTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, ++ final int chunkZ, final ChunkAccess chunk, final List neighbours, ++ final ChunkStatus toStatus, final PrioritisedExecutor.Priority priority) { ++ super(scheduler, world, chunkX, chunkZ); ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ this.fromChunk = chunk; ++ this.fromStatus = chunk.getStatus(); ++ this.toStatus = toStatus; ++ this.neighbours = neighbours; ++ if (this.toStatus.isParallelCapable) { ++ this.generateTask = this.scheduler.parallelGenExecutor.createTask(this, priority); ++ } else { ++ this.generateTask = this.scheduler.radiusAwareScheduler.createTask(chunkX, chunkZ, this.toStatus.writeRadius, this, priority); ++ } ++ } ++ ++ @Override ++ public ChunkStatus getTargetStatus() { ++ return this.toStatus; ++ } ++ ++ private boolean isEmptyTask() { ++ // must use fromStatus here to avoid any race condition with run() overwriting the status ++ final boolean generation = !this.fromStatus.isOrAfter(this.toStatus); ++ return (generation && this.toStatus.isEmptyGenStatus()) || (!generation && this.toStatus.isEmptyLoadStatus()); ++ } ++ ++ @Override ++ public void run() { ++ final ChunkAccess chunk = this.fromChunk; ++ ++ final ServerChunkCache serverChunkCache = this.world.chunkSource; ++ final ChunkMap chunkMap = serverChunkCache.chunkMap; ++ ++ final CompletableFuture> completeFuture; ++ ++ final boolean generation; ++ boolean completing = false; ++ ++ // note: should optimise the case where the chunk does not need to execute the status, because ++ // schedule() calls this synchronously if it will run through that path ++ ++ try { ++ generation = !chunk.getStatus().isOrAfter(this.toStatus); ++ if (generation) { ++ if (this.toStatus.isEmptyGenStatus()) { ++ if (chunk instanceof ProtoChunk) { ++ ((ProtoChunk)chunk).setStatus(this.toStatus); ++ } ++ completing = true; ++ this.complete(chunk, null); ++ return; ++ } ++ completeFuture = this.toStatus.generate(Runnable::run, this.world, chunkMap.generator, chunkMap.structureTemplateManager, ++ serverChunkCache.getLightEngine(), null, this.neighbours) ++ .whenComplete((final Either either, final Throwable throwable) -> { ++ final ChunkAccess newChunk = (either == null) ? null : either.left().orElse(null); ++ if (newChunk instanceof ProtoChunk) { ++ ((ProtoChunk)newChunk).setStatus(ChunkUpgradeGenericStatusTask.this.toStatus); ++ } ++ } ++ ); ++ } else { ++ if (this.toStatus.isEmptyLoadStatus()) { ++ completing = true; ++ this.complete(chunk, null); ++ return; ++ } ++ completeFuture = this.toStatus.load(this.world, chunkMap.structureTemplateManager, serverChunkCache.getLightEngine(), null, chunk); ++ } ++ } catch (final Throwable throwable) { ++ if (!completing) { ++ this.complete(null, throwable); ++ ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ return; ++ } ++ ++ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( ++ "Target status", ChunkTaskScheduler.stringIfNull(this.toStatus), ++ "From status", ChunkTaskScheduler.stringIfNull(this.fromStatus), ++ "Generation task", this ++ ), throwable); ++ ++ if (!(throwable instanceof ThreadDeath)) { ++ LOGGER.error("Failed to complete status for chunk: status:" + this.toStatus + ", chunk: (" + this.chunkX + "," + this.chunkZ + "), world: " + this.world.getWorld().getName(), throwable); ++ } else { ++ // ensure the chunk system can respond, then die ++ throw (ThreadDeath)throwable; ++ } ++ return; ++ } ++ ++ if (!completeFuture.isDone() && !this.toStatus.warnedAboutNoImmediateComplete.getAndSet(true)) { ++ LOGGER.warn("Future status not complete after scheduling: " + this.toStatus.toString() + ", generate: " + generation); ++ } ++ ++ final Either either; ++ final ChunkAccess newChunk; ++ ++ try { ++ either = completeFuture.join(); ++ newChunk = (either == null) ? null : either.left().orElse(null); ++ } catch (final Throwable throwable) { ++ this.complete(null, throwable); ++ // ensure the chunk system can respond, then die ++ if (throwable instanceof ThreadDeath) { ++ throw (ThreadDeath)throwable; ++ } ++ return; ++ } ++ ++ if (newChunk == null) { ++ this.complete(null, new IllegalStateException("Chunk for status: " + ChunkUpgradeGenericStatusTask.this.toStatus.toString() + ", generation: " + generation + " should not be null! Either: " + either).fillInStackTrace()); ++ return; ++ } ++ ++ this.complete(newChunk, null); ++ } ++ ++ protected volatile boolean scheduled; ++ protected static final VarHandle SCHEDULED_HANDLE = ConcurrentUtil.getVarHandle(ChunkUpgradeGenericStatusTask.class, "scheduled", boolean.class); ++ ++ @Override ++ public boolean isScheduled() { ++ return this.scheduled; ++ } ++ ++ @Override ++ public void schedule() { ++ if ((boolean)SCHEDULED_HANDLE.getAndSet((ChunkUpgradeGenericStatusTask)this, true)) { ++ throw new IllegalStateException("Cannot double call schedule()"); ++ } ++ if (this.isEmptyTask()) { ++ if (this.generateTask.cancel()) { ++ this.run(); ++ } ++ } else { ++ this.generateTask.queue(); ++ } ++ } ++ ++ @Override ++ public void cancel() { ++ if (this.generateTask.cancel()) { ++ this.complete(null, null); ++ } ++ } ++ ++ @Override ++ public PrioritisedExecutor.Priority getPriority() { ++ return this.generateTask.getPriority(); ++ } ++ ++ @Override ++ public void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ this.generateTask.lowerPriority(priority); ++ } ++ ++ @Override ++ public void setPriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ this.generateTask.setPriority(priority); ++ } ++ ++ @Override ++ public void raisePriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ this.generateTask.raisePriority(priority); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/GenericDataLoadTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/GenericDataLoadTask.java +new file mode 100644 +index 0000000000000000000000000000000000000000..396d72c00e47cf1669ae20dc839c1c961b1f262a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/GenericDataLoadTask.java +@@ -0,0 +1,746 @@ ++package io.papermc.paper.chunk.system.scheduling; ++ ++import ca.spottedleaf.concurrentutil.completable.Completable; ++import ca.spottedleaf.concurrentutil.executor.Cancellable; ++import ca.spottedleaf.concurrentutil.executor.standard.DelayedPrioritisedTask; ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.chunk.system.io.RegionFileIOThread; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.level.ServerLevel; ++import org.slf4j.Logger; ++import java.lang.invoke.VarHandle; ++import java.util.Map; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.function.BiConsumer; ++ ++public abstract class GenericDataLoadTask { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ protected static final CompoundTag CANCELLED_DATA = new CompoundTag(); ++ ++ // reference count is the upper 32 bits ++ protected final AtomicLong stageAndReferenceCount = new AtomicLong(STAGE_NOT_STARTED); ++ ++ protected static final long STAGE_MASK = 0xFFFFFFFFL; ++ protected static final long STAGE_CANCELLED = 0xFFFFFFFFL; ++ protected static final long STAGE_NOT_STARTED = 0L; ++ protected static final long STAGE_LOADING = 1L; ++ protected static final long STAGE_PROCESSING = 2L; ++ protected static final long STAGE_COMPLETED = 3L; ++ ++ // for loading data off disk ++ protected final LoadDataFromDiskTask loadDataFromDiskTask; ++ // processing off-main ++ protected final PrioritisedExecutor.PrioritisedTask processOffMain; ++ // processing on-main ++ protected final PrioritisedExecutor.PrioritisedTask processOnMain; ++ ++ protected final ChunkTaskScheduler scheduler; ++ protected final ServerLevel world; ++ protected final int chunkX; ++ protected final int chunkZ; ++ protected final RegionFileIOThread.RegionFileType type; ++ ++ public GenericDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, ++ final int chunkZ, final RegionFileIOThread.RegionFileType type, ++ final PrioritisedExecutor.Priority priority) { ++ this.scheduler = scheduler; ++ this.world = world; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.type = type; ++ ++ final ProcessOnMainTask mainTask; ++ if (this.hasOnMain()) { ++ mainTask = new ProcessOnMainTask(); ++ this.processOnMain = this.createOnMain(mainTask, priority); ++ } else { ++ mainTask = null; ++ this.processOnMain = null; ++ } ++ ++ final ProcessOffMainTask offMainTask; ++ if (this.hasOffMain()) { ++ offMainTask = new ProcessOffMainTask(mainTask); ++ this.processOffMain = this.createOffMain(offMainTask, priority); ++ } else { ++ offMainTask = null; ++ this.processOffMain = null; ++ } ++ ++ if (this.processOffMain == null && this.processOnMain == null) { ++ throw new IllegalStateException("Illegal class implementation: " + this.getClass().getName() + ", should be able to schedule at least one task!"); ++ } ++ ++ this.loadDataFromDiskTask = new LoadDataFromDiskTask(world, chunkX, chunkZ, type, new DataLoadCallback(offMainTask, mainTask), priority); ++ } ++ ++ public static final record TaskResult(L left, R right) {} ++ ++ protected abstract boolean hasOffMain(); ++ ++ protected abstract boolean hasOnMain(); ++ ++ protected abstract PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority); ++ ++ protected abstract PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority); ++ ++ protected abstract TaskResult runOffMain(final CompoundTag data, final Throwable throwable); ++ ++ protected abstract TaskResult runOnMain(final OnMain data, final Throwable throwable); ++ ++ protected abstract void onComplete(final TaskResult result); ++ ++ protected abstract TaskResult completeOnMainOffMain(final OnMain data, final Throwable throwable); ++ ++ @Override ++ public String toString() { ++ return "GenericDataLoadTask{class: " + this.getClass().getName() + ", world: " + this.world.getWorld().getName() + ++ ", chunk: (" + this.chunkX + "," + this.chunkZ + "), hashcode: " + System.identityHashCode(this) + ", priority: " + this.getPriority() + ++ ", type: " + this.type.toString() + "}"; ++ } ++ ++ public PrioritisedExecutor.Priority getPriority() { ++ if (this.processOnMain != null) { ++ return this.processOnMain.getPriority(); ++ } else { ++ return this.processOffMain.getPriority(); ++ } ++ } ++ ++ public void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ // can't lower I/O tasks, we don't know what they affect ++ if (this.processOffMain != null) { ++ this.processOffMain.lowerPriority(priority); ++ } ++ if (this.processOnMain != null) { ++ this.processOnMain.lowerPriority(priority); ++ } ++ } ++ ++ public void setPriority(final PrioritisedExecutor.Priority priority) { ++ // can't lower I/O tasks, we don't know what they affect ++ this.loadDataFromDiskTask.raisePriority(priority); ++ if (this.processOffMain != null) { ++ this.processOffMain.setPriority(priority); ++ } ++ if (this.processOnMain != null) { ++ this.processOnMain.setPriority(priority); ++ } ++ } ++ ++ public void raisePriority(final PrioritisedExecutor.Priority priority) { ++ // can't lower I/O tasks, we don't know what they affect ++ this.loadDataFromDiskTask.raisePriority(priority); ++ if (this.processOffMain != null) { ++ this.processOffMain.raisePriority(priority); ++ } ++ if (this.processOnMain != null) { ++ this.processOnMain.raisePriority(priority); ++ } ++ } ++ ++ // returns whether scheduleNow() needs to be called ++ public boolean schedule(final boolean delay) { ++ if (this.stageAndReferenceCount.get() != STAGE_NOT_STARTED || ++ !this.stageAndReferenceCount.compareAndSet(STAGE_NOT_STARTED, (1L << 32) | STAGE_LOADING)) { ++ // try and increment reference count ++ int failures = 0; ++ for (long curr = this.stageAndReferenceCount.get();;) { ++ if ((curr & STAGE_MASK) == STAGE_CANCELLED || (curr & STAGE_MASK) == STAGE_COMPLETED) { ++ // cancelled or completed, nothing to do here ++ return false; ++ } ++ ++ if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, curr + (1L << 32)))) { ++ // successful ++ return false; ++ } ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ if (!delay) { ++ this.scheduleNow(); ++ return false; ++ } ++ return true; ++ } ++ ++ public void scheduleNow() { ++ this.loadDataFromDiskTask.schedule(); // will schedule the rest ++ } ++ ++ // assumes the current stage cannot be completed ++ // returns false if cancelled, returns true if can proceed ++ private boolean advanceStage(final long expect, final long to) { ++ int failures = 0; ++ for (long curr = this.stageAndReferenceCount.get();;) { ++ if ((curr & STAGE_MASK) != expect) { ++ // must be cancelled ++ return false; ++ } ++ ++ final long newVal = (curr & ~STAGE_MASK) | to; ++ if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, newVal))) { ++ return true; ++ } ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ public boolean cancel() { ++ int failures = 0; ++ for (long curr = this.stageAndReferenceCount.get();;) { ++ if ((curr & STAGE_MASK) == STAGE_COMPLETED || (curr & STAGE_MASK) == STAGE_CANCELLED) { ++ return false; ++ } ++ ++ if ((curr & STAGE_MASK) == STAGE_NOT_STARTED || (curr & ~STAGE_MASK) == (1L << 32)) { ++ // no other references, so we can cancel ++ final long newVal = STAGE_CANCELLED; ++ if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, newVal))) { ++ this.loadDataFromDiskTask.cancel(); ++ if (this.processOffMain != null) { ++ this.processOffMain.cancel(); ++ } ++ if (this.processOnMain != null) { ++ this.processOnMain.cancel(); ++ } ++ this.onComplete(null); ++ return true; ++ } ++ } else { ++ if ((curr & ~STAGE_MASK) == (0L << 32)) { ++ throw new IllegalStateException("Reference count cannot be zero here"); ++ } ++ // just decrease the reference count ++ final long newVal = curr - (1L << 32); ++ if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, newVal))) { ++ return false; ++ } ++ } ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ protected final class DataLoadCallback implements BiConsumer { ++ ++ protected final ProcessOffMainTask offMainTask; ++ protected final ProcessOnMainTask onMainTask; ++ ++ public DataLoadCallback(final ProcessOffMainTask offMainTask, final ProcessOnMainTask onMainTask) { ++ this.offMainTask = offMainTask; ++ this.onMainTask = onMainTask; ++ } ++ ++ @Override ++ public void accept(final CompoundTag compoundTag, final Throwable throwable) { ++ if (GenericDataLoadTask.this.stageAndReferenceCount.get() == STAGE_CANCELLED) { ++ // don't try to schedule further ++ return; ++ } ++ ++ try { ++ if (compoundTag == CANCELLED_DATA) { ++ // cancelled, except this isn't possible ++ LOGGER.error("Data callback says cancelled, but stage does not?"); ++ return; ++ } ++ ++ // get off of the regionfile callback ASAP, no clue what locks are held right now... ++ if (GenericDataLoadTask.this.processOffMain != null) { ++ this.offMainTask.data = compoundTag; ++ this.offMainTask.throwable = throwable; ++ GenericDataLoadTask.this.processOffMain.queue(); ++ return; ++ } else { ++ // no off-main task, so go straight to main ++ this.onMainTask.data = (OnMain)compoundTag; ++ this.onMainTask.throwable = throwable; ++ GenericDataLoadTask.this.processOnMain.queue(); ++ } ++ } catch (final ThreadDeath death) { ++ throw death; ++ } catch (final Throwable thr2) { ++ LOGGER.error("Failed I/O callback for task: " + GenericDataLoadTask.this.toString(), thr2); ++ GenericDataLoadTask.this.scheduler.unrecoverableChunkSystemFailure( ++ GenericDataLoadTask.this.chunkX, GenericDataLoadTask.this.chunkZ, Map.of( ++ "Callback throwable", ChunkTaskScheduler.stringIfNull(throwable) ++ ), thr2); ++ } ++ } ++ } ++ ++ protected final class ProcessOffMainTask implements Runnable { ++ ++ protected CompoundTag data; ++ protected Throwable throwable; ++ protected final ProcessOnMainTask schedule; ++ ++ public ProcessOffMainTask(final ProcessOnMainTask schedule) { ++ this.schedule = schedule; ++ } ++ ++ @Override ++ public void run() { ++ if (!GenericDataLoadTask.this.advanceStage(STAGE_LOADING, this.schedule == null ? STAGE_COMPLETED : STAGE_PROCESSING)) { ++ // cancelled ++ return; ++ } ++ final TaskResult newData = GenericDataLoadTask.this.runOffMain(this.data, this.throwable); ++ ++ if (GenericDataLoadTask.this.stageAndReferenceCount.get() == STAGE_CANCELLED) { ++ // don't try to schedule further ++ return; ++ } ++ ++ if (this.schedule != null) { ++ final TaskResult syncComplete = GenericDataLoadTask.this.completeOnMainOffMain(newData.left, newData.right); ++ ++ if (syncComplete != null) { ++ if (GenericDataLoadTask.this.advanceStage(STAGE_PROCESSING, STAGE_COMPLETED)) { ++ GenericDataLoadTask.this.onComplete(syncComplete); ++ } // else: cancelled ++ return; ++ } ++ ++ this.schedule.data = newData.left; ++ this.schedule.throwable = newData.right; ++ ++ GenericDataLoadTask.this.processOnMain.queue(); ++ } else { ++ GenericDataLoadTask.this.onComplete((TaskResult)newData); ++ } ++ } ++ } ++ ++ protected final class ProcessOnMainTask implements Runnable { ++ ++ protected OnMain data; ++ protected Throwable throwable; ++ ++ @Override ++ public void run() { ++ if (!GenericDataLoadTask.this.advanceStage(STAGE_PROCESSING, STAGE_COMPLETED)) { ++ // cancelled ++ return; ++ } ++ final TaskResult result = GenericDataLoadTask.this.runOnMain(this.data, this.throwable); ++ ++ GenericDataLoadTask.this.onComplete(result); ++ } ++ } ++ ++ public static final class LoadDataFromDiskTask { ++ ++ protected volatile int priority; ++ protected static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(LoadDataFromDiskTask.class, "priority", int.class); ++ ++ protected static final int PRIORITY_EXECUTED = Integer.MIN_VALUE >>> 0; ++ protected static final int PRIORITY_LOAD_SCHEDULED = Integer.MIN_VALUE >>> 1; ++ protected static final int PRIORITY_UNLOAD_SCHEDULED = Integer.MIN_VALUE >>> 2; ++ ++ protected static final int PRIORITY_FLAGS = ~Character.MAX_VALUE; ++ ++ protected final int getPriorityVolatile() { ++ return (int)PRIORITY_HANDLE.getVolatile((LoadDataFromDiskTask)this); ++ } ++ ++ protected final int compareAndExchangePriorityVolatile(final int expect, final int update) { ++ return (int)PRIORITY_HANDLE.compareAndExchange((LoadDataFromDiskTask)this, (int)expect, (int)update); ++ } ++ ++ protected final int getAndOrPriorityVolatile(final int val) { ++ return (int)PRIORITY_HANDLE.getAndBitwiseOr((LoadDataFromDiskTask)this, (int)val); ++ } ++ ++ protected final void setPriorityPlain(final int val) { ++ PRIORITY_HANDLE.set((LoadDataFromDiskTask)this, (int)val); ++ } ++ ++ private final ServerLevel world; ++ private final int chunkX; ++ private final int chunkZ; ++ ++ private final RegionFileIOThread.RegionFileType type; ++ private Cancellable dataLoadTask; ++ private Cancellable dataUnloadCancellable; ++ private DelayedPrioritisedTask dataUnloadTask; ++ ++ private final BiConsumer onComplete; ++ ++ // onComplete should be caller sensitive, it may complete synchronously with schedule() - which does ++ // hold a priority lock. ++ public LoadDataFromDiskTask(final ServerLevel world, final int chunkX, final int chunkZ, ++ final RegionFileIOThread.RegionFileType type, ++ final BiConsumer onComplete, ++ final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ this.world = world; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.type = type; ++ this.onComplete = onComplete; ++ this.setPriorityPlain(priority.priority); ++ } ++ ++ private void complete(final CompoundTag data, final Throwable throwable) { ++ try { ++ this.onComplete.accept(data, throwable); ++ } catch (final Throwable thr2) { ++ this.world.chunkTaskScheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( ++ "Completed throwable", ChunkTaskScheduler.stringIfNull(throwable), ++ "Regionfile type", ChunkTaskScheduler.stringIfNull(this.type) ++ ), thr2); ++ if (thr2 instanceof ThreadDeath) { ++ throw (ThreadDeath)thr2; ++ } ++ } ++ } ++ ++ protected boolean markExecuting() { ++ return (this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) == 0; ++ } ++ ++ protected boolean isMarkedExecuted() { ++ return (this.getPriorityVolatile() & PRIORITY_EXECUTED) != 0; ++ } ++ ++ public void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ ++ int failures = 0; ++ for (int curr = this.getPriorityVolatile();;) { ++ if ((curr & PRIORITY_EXECUTED) != 0) { ++ // cancelled or executed ++ return; ++ } ++ ++ if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { ++ RegionFileIOThread.lowerPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); ++ return; ++ } ++ ++ if ((curr & PRIORITY_UNLOAD_SCHEDULED) != 0) { ++ if (this.dataUnloadTask != null) { ++ this.dataUnloadTask.lowerPriority(priority); ++ } ++ // no return - we need to propagate priority ++ } ++ ++ if (!priority.isHigherPriority(curr & ~PRIORITY_FLAGS)) { ++ return; ++ } ++ ++ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority | (curr & PRIORITY_FLAGS)))) { ++ return; ++ } ++ ++ // failed, retry ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ public void setPriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ ++ int failures = 0; ++ for (int curr = this.getPriorityVolatile();;) { ++ if ((curr & PRIORITY_EXECUTED) != 0) { ++ // cancelled or executed ++ return; ++ } ++ ++ if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { ++ RegionFileIOThread.setPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); ++ return; ++ } ++ ++ if ((curr & PRIORITY_UNLOAD_SCHEDULED) != 0) { ++ if (this.dataUnloadTask != null) { ++ this.dataUnloadTask.setPriority(priority); ++ } ++ // no return - we need to propagate priority ++ } ++ ++ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority | (curr & PRIORITY_FLAGS)))) { ++ return; ++ } ++ ++ // failed, retry ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ public void raisePriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ ++ int failures = 0; ++ for (int curr = this.getPriorityVolatile();;) { ++ if ((curr & PRIORITY_EXECUTED) != 0) { ++ // cancelled or executed ++ return; ++ } ++ ++ if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { ++ RegionFileIOThread.raisePriority(this.world, this.chunkX, this.chunkZ, this.type, priority); ++ return; ++ } ++ ++ if ((curr & PRIORITY_UNLOAD_SCHEDULED) != 0) { ++ if (this.dataUnloadTask != null) { ++ this.dataUnloadTask.raisePriority(priority); ++ } ++ // no return - we need to propagate priority ++ } ++ ++ if (!priority.isLowerPriority(curr & ~PRIORITY_FLAGS)) { ++ return; ++ } ++ ++ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority | (curr & PRIORITY_FLAGS)))) { ++ return; ++ } ++ ++ // failed, retry ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ public void cancel() { ++ if ((this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) != 0) { ++ // cancelled or executed already ++ return; ++ } ++ ++ // OK if we miss the field read, the task cannot complete if the cancelled bit is set and ++ // the write to dataLoadTask will check for the cancelled bit ++ if (this.dataUnloadCancellable != null) { ++ this.dataUnloadCancellable.cancel(); ++ } ++ ++ if (this.dataLoadTask != null) { ++ this.dataLoadTask.cancel(); ++ } ++ ++ this.complete(CANCELLED_DATA, null); ++ } ++ ++ private final AtomicBoolean scheduled = new AtomicBoolean(); ++ ++ public void schedule() { ++ if (this.scheduled.getAndSet(true)) { ++ throw new IllegalStateException("schedule() called twice"); ++ } ++ int priority = this.getPriorityVolatile(); ++ ++ if ((priority & PRIORITY_EXECUTED) != 0) { ++ // cancelled ++ return; ++ } ++ ++ final BiConsumer consumer = (final CompoundTag data, final Throwable thr) -> { ++ // because cancelScheduled() cannot actually stop this task from executing in every case, we need ++ // to mark complete here to ensure we do not double complete ++ if (LoadDataFromDiskTask.this.markExecuting()) { ++ LoadDataFromDiskTask.this.complete(data, thr); ++ } // else: cancelled ++ }; ++ ++ final PrioritisedExecutor.Priority initialPriority = PrioritisedExecutor.Priority.getPriority(priority); ++ boolean scheduledUnload = false; ++ ++ final NewChunkHolder holder = this.world.chunkTaskScheduler.chunkHolderManager.getChunkHolder(this.chunkX, this.chunkZ); ++ if (holder != null) { ++ final BiConsumer unloadConsumer = (final CompoundTag data, final Throwable thr) -> { ++ if (data != null) { ++ consumer.accept(data, null); ++ } else { ++ // need to schedule task ++ LoadDataFromDiskTask.this.schedule(false, consumer, PrioritisedExecutor.Priority.getPriority(LoadDataFromDiskTask.this.getPriorityVolatile() & ~PRIORITY_FLAGS)); ++ } ++ }; ++ Cancellable unloadCancellable = null; ++ CompoundTag syncComplete = null; ++ final NewChunkHolder.UnloadTask unloadTask = holder.getUnloadTask(this.type); // can be null if no task exists ++ final Completable unloadCompletable = unloadTask == null ? null : unloadTask.completable(); ++ if (unloadCompletable != null) { ++ unloadCancellable = unloadCompletable.addAsynchronousWaiter(unloadConsumer); ++ if (unloadCancellable == null) { ++ syncComplete = unloadCompletable.getResult(); ++ } ++ } ++ ++ if (syncComplete != null) { ++ consumer.accept(syncComplete, null); ++ return; ++ } ++ ++ if (unloadCancellable != null) { ++ scheduledUnload = true; ++ this.dataUnloadCancellable = unloadCancellable; ++ this.dataUnloadTask = unloadTask.task(); ++ } ++ } ++ ++ this.schedule(scheduledUnload, consumer, initialPriority); ++ } ++ ++ private void schedule(final boolean scheduledUnload, final BiConsumer consumer, final PrioritisedExecutor.Priority initialPriority) { ++ int priority = this.getPriorityVolatile(); ++ ++ if ((priority & PRIORITY_EXECUTED) != 0) { ++ // cancelled ++ return; ++ } ++ ++ if (!scheduledUnload) { ++ this.dataLoadTask = RegionFileIOThread.loadDataAsync( ++ this.world, this.chunkX, this.chunkZ, this.type, consumer, ++ initialPriority.isHigherPriority(PrioritisedExecutor.Priority.NORMAL), initialPriority ++ ); ++ } ++ ++ int failures = 0; ++ for (;;) { ++ if (priority == (priority = this.compareAndExchangePriorityVolatile(priority, priority | (scheduledUnload ? PRIORITY_UNLOAD_SCHEDULED : PRIORITY_LOAD_SCHEDULED)))) { ++ return; ++ } ++ ++ if ((priority & PRIORITY_EXECUTED) != 0) { ++ // cancelled or executed ++ if (this.dataUnloadCancellable != null) { ++ this.dataUnloadCancellable.cancel(); ++ } ++ ++ if (this.dataLoadTask != null) { ++ this.dataLoadTask.cancel(); ++ } ++ return; ++ } ++ ++ if (scheduledUnload) { ++ if (this.dataUnloadTask != null) { ++ this.dataUnloadTask.setPriority(PrioritisedExecutor.Priority.getPriority(priority & ~PRIORITY_FLAGS)); ++ } ++ } else { ++ RegionFileIOThread.setPriority(this.world, this.chunkX, this.chunkZ, this.type, PrioritisedExecutor.Priority.getPriority(priority & ~PRIORITY_FLAGS)); ++ } ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ /* ++ private static final class LoadDataPriorityHolder extends PriorityHolder { ++ ++ protected final LoadDataFromDiskTask task; ++ ++ protected LoadDataPriorityHolder(final PrioritisedExecutor.Priority priority, final LoadDataFromDiskTask task) { ++ super(priority); ++ this.task = task; ++ } ++ ++ @Override ++ protected void cancelScheduled() { ++ final Cancellable dataLoadTask = this.task.dataLoadTask; ++ if (dataLoadTask != null) { ++ // OK if we miss the field read, the task cannot complete if the cancelled bit is set and ++ // the write to dataLoadTask will check for the cancelled bit ++ this.task.dataLoadTask.cancel(); ++ } ++ this.task.complete(CANCELLED_DATA, null); ++ } ++ ++ @Override ++ protected PrioritisedExecutor.Priority getScheduledPriority() { ++ final LoadDataFromDiskTask task = this.task; ++ return RegionFileIOThread.getPriority(task.world, task.chunkX, task.chunkZ, task.type); ++ } ++ ++ @Override ++ protected void scheduleTask(final PrioritisedExecutor.Priority priority) { ++ final LoadDataFromDiskTask task = this.task; ++ final BiConsumer consumer = (final CompoundTag data, final Throwable thr) -> { ++ // because cancelScheduled() cannot actually stop this task from executing in every case, we need ++ // to mark complete here to ensure we do not double complete ++ if (LoadDataPriorityHolder.this.markExecuting()) { ++ LoadDataPriorityHolder.this.task.complete(data, thr); ++ } // else: cancelled ++ }; ++ task.dataLoadTask = RegionFileIOThread.loadDataAsync( ++ task.world, task.chunkX, task.chunkZ, task.type, consumer, ++ priority.isHigherPriority(PrioritisedExecutor.Priority.NORMAL), priority ++ ); ++ if (this.isMarkedExecuted()) { ++ // if we are marked as completed, it could be: ++ // 1. we were cancelled ++ // 2. the consumer was completed ++ // in the 2nd case, cancel() does nothing ++ // in the 1st case, we ensure cancel() is called as it is possible for the cancelling thread ++ // to miss the field write here ++ task.dataLoadTask.cancel(); ++ } ++ } ++ ++ @Override ++ protected void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority) { ++ final LoadDataFromDiskTask task = this.task; ++ RegionFileIOThread.lowerPriority(task.world, task.chunkX, task.chunkZ, task.type, priority); ++ } ++ ++ @Override ++ protected void setPriorityScheduled(final PrioritisedExecutor.Priority priority) { ++ final LoadDataFromDiskTask task = this.task; ++ RegionFileIOThread.setPriority(task.world, task.chunkX, task.chunkZ, task.type, priority); ++ } ++ ++ @Override ++ protected void raisePriorityScheduled(final PrioritisedExecutor.Priority priority) { ++ final LoadDataFromDiskTask task = this.task; ++ RegionFileIOThread.raisePriority(task.world, task.chunkX, task.chunkZ, task.type, priority); ++ } ++ } ++ */ ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b66a7d4aab887309579154815a0d4abf9de506b0 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java +@@ -0,0 +1,2106 @@ ++package io.papermc.paper.chunk.system.scheduling; ++ ++import ca.spottedleaf.concurrentutil.completable.Completable; ++import ca.spottedleaf.concurrentutil.executor.Cancellable; ++import ca.spottedleaf.concurrentutil.executor.standard.DelayedPrioritisedTask; ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import com.google.gson.JsonArray; ++import com.google.gson.JsonElement; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonPrimitive; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.chunk.system.io.RegionFileIOThread; ++import io.papermc.paper.chunk.system.poi.PoiChunk; ++import io.papermc.paper.util.CoordinateUtils; ++import io.papermc.paper.util.TickThread; ++import io.papermc.paper.util.WorldUtil; ++import io.papermc.paper.world.ChunkEntitySlices; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ChunkLevel; ++import net.minecraft.server.level.FullChunkStatus; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.TicketType; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.ImposterProtoChunk; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.storage.ChunkSerializer; ++import net.minecraft.world.level.chunk.storage.EntityStorage; ++import org.slf4j.Logger; ++import java.lang.invoke.VarHandle; ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Objects; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.function.Consumer; ++ ++public final class NewChunkHolder { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ ++ public static final Thread.UncaughtExceptionHandler CHUNKSYSTEM_UNCAUGHT_EXCEPTION_HANDLER = new Thread.UncaughtExceptionHandler() { ++ @Override ++ public void uncaughtException(final Thread thread, final Throwable throwable) { ++ if (!(throwable instanceof ThreadDeath)) { ++ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); ++ } ++ } ++ }; ++ ++ public final ServerLevel world; ++ public final int chunkX; ++ public final int chunkZ; ++ ++ public final ChunkTaskScheduler scheduler; ++ ++ // load/unload state ++ ++ // chunk data state ++ ++ private ChunkEntitySlices entityChunk; ++ // entity chunk that is loaded, but not yet deserialized ++ private CompoundTag pendingEntityChunk; ++ ++ ChunkEntitySlices loadInEntityChunk(final boolean transientChunk) { ++ TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Cannot sync load entity data off-main"); ++ final CompoundTag entityChunk; ++ final ChunkEntitySlices ret; ++ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); ++ try { ++ if (this.entityChunk != null && (transientChunk || !this.entityChunk.isTransient())) { ++ return this.entityChunk; ++ } ++ final CompoundTag pendingEntityChunk = this.pendingEntityChunk; ++ if (!transientChunk && pendingEntityChunk == null) { ++ throw new IllegalStateException("Must load entity data from disk before loading in the entity chunk!"); ++ } ++ ++ if (this.entityChunk == null) { ++ ret = this.entityChunk = new ChunkEntitySlices( ++ this.world, this.chunkX, this.chunkZ, this.getChunkStatus(), ++ WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) ++ ); ++ ++ ret.setTransient(transientChunk); ++ ++ this.world.getEntityLookup().entitySectionLoad(this.chunkX, this.chunkZ, ret); ++ } else { ++ // transientChunk = false here ++ ret = this.entityChunk; ++ this.entityChunk.setTransient(false); ++ } ++ ++ if (!transientChunk) { ++ this.pendingEntityChunk = null; ++ entityChunk = pendingEntityChunk == EMPTY_ENTITY_CHUNK ? null : pendingEntityChunk; ++ } else { ++ entityChunk = null; ++ } ++ } finally { ++ this.scheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ ++ if (!transientChunk) { ++ if (entityChunk != null) { ++ final List entities = EntityStorage.readEntities(this.world, entityChunk); ++ ++ this.world.getEntityLookup().addEntityChunkEntities(entities, new ChunkPos(this.chunkX, this.chunkZ)); ++ } ++ } ++ ++ return ret; ++ } ++ ++ // needed to distinguish whether the entity chunk has been read from disk but is empty or whether it has _not_ ++ // been read from disk ++ private static final CompoundTag EMPTY_ENTITY_CHUNK = new CompoundTag(); ++ ++ private ChunkLoadTask.EntityDataLoadTask entityDataLoadTask; ++ // note: if entityDataLoadTask is cancelled, but on its completion entityDataLoadTaskWaiters.size() != 0, ++ // then the task is rescheduled ++ private List entityDataLoadTaskWaiters; ++ ++ public ChunkLoadTask.EntityDataLoadTask getEntityDataLoadTask() { ++ return this.entityDataLoadTask; ++ } ++ ++ // must hold schedule lock for the two below functions ++ ++ // returns only if the data has been loaded from disk, DOES NOT relate to whether it has been deserialized ++ // or added into the world (or even into entityChunk) ++ public boolean isEntityChunkNBTLoaded() { ++ return (this.entityChunk != null && !this.entityChunk.isTransient()) || this.pendingEntityChunk != null; ++ } ++ ++ private void completeEntityLoad(final GenericDataLoadTask.TaskResult result) { ++ final List completeWaiters; ++ ChunkLoadTask.EntityDataLoadTask entityDataLoadTask = null; ++ boolean scheduleEntityTask = false; ++ ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); ++ try { ++ final List waiters = this.entityDataLoadTaskWaiters; ++ this.entityDataLoadTask = null; ++ if (result != null) { ++ this.entityDataLoadTaskWaiters = null; ++ this.pendingEntityChunk = result.left() == null ? EMPTY_ENTITY_CHUNK : result.left(); ++ if (result.right() != null) { ++ LOGGER.error("Unhandled entity data load exception, data data will be lost: ", result.right()); ++ } ++ ++ for (final GenericDataLoadTaskCallback callback : waiters) { ++ callback.markCompleted(); ++ } ++ ++ completeWaiters = waiters; ++ } else { ++ // cancelled ++ completeWaiters = null; ++ ++ // need to re-schedule? ++ if (waiters.isEmpty()) { ++ this.entityDataLoadTaskWaiters = null; ++ // no tasks to schedule _for_ ++ } else { ++ entityDataLoadTask = this.entityDataLoadTask = new ChunkLoadTask.EntityDataLoadTask( ++ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority() ++ ); ++ entityDataLoadTask.addCallback(this::completeEntityLoad); ++ // need one schedule() per waiter ++ for (final GenericDataLoadTaskCallback callback : waiters) { ++ scheduleEntityTask |= entityDataLoadTask.schedule(true); ++ } ++ } ++ } ++ } finally { ++ this.scheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ ++ if (scheduleEntityTask) { ++ entityDataLoadTask.scheduleNow(); ++ } ++ ++ // avoid holding the scheduling lock while completing ++ if (completeWaiters != null) { ++ for (final GenericDataLoadTaskCallback callback : completeWaiters) { ++ callback.acceptCompleted(result); ++ } ++ } ++ ++ schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); ++ try { ++ this.checkUnload(); ++ } finally { ++ this.scheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ } ++ ++ // note: it is guaranteed that the consumer cannot be called for the entirety that the schedule lock is held ++ // however, when the consumer is invoked, it will hold the schedule lock ++ public GenericDataLoadTaskCallback getOrLoadEntityData(final Consumer> consumer) { ++ if (this.isEntityChunkNBTLoaded()) { ++ throw new IllegalStateException("Cannot load entity data, it is already loaded"); ++ } ++ // why not just acquire the lock? because the caller NEEDS to call isEntityChunkNBTLoaded before this! ++ if (!this.scheduler.schedulingLockArea.isHeldByCurrentThread(this.chunkX, this.chunkZ)) { ++ throw new IllegalStateException("Must hold scheduling lock"); ++ } ++ ++ final GenericDataLoadTaskCallback ret = new EntityDataLoadTaskCallback((Consumer)consumer, this); ++ ++ if (this.entityDataLoadTask == null) { ++ this.entityDataLoadTask = new ChunkLoadTask.EntityDataLoadTask( ++ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority() ++ ); ++ this.entityDataLoadTask.addCallback(this::completeEntityLoad); ++ this.entityDataLoadTaskWaiters = new ArrayList<>(); ++ } ++ this.entityDataLoadTaskWaiters.add(ret); ++ if (this.entityDataLoadTask.schedule(true)) { ++ ret.schedule = this.entityDataLoadTask; ++ } ++ this.checkUnload(); ++ ++ return ret; ++ } ++ ++ private static final class EntityDataLoadTaskCallback extends GenericDataLoadTaskCallback { ++ ++ public EntityDataLoadTaskCallback(final Consumer> consumer, final NewChunkHolder chunkHolder) { ++ super(consumer, chunkHolder); ++ } ++ ++ @Override ++ void internalCancel() { ++ this.chunkHolder.entityDataLoadTaskWaiters.remove(this); ++ this.chunkHolder.entityDataLoadTask.cancel(); ++ } ++ } ++ ++ private PoiChunk poiChunk; ++ ++ private ChunkLoadTask.PoiDataLoadTask poiDataLoadTask; ++ // note: if entityDataLoadTask is cancelled, but on its completion entityDataLoadTaskWaiters.size() != 0, ++ // then the task is rescheduled ++ private List poiDataLoadTaskWaiters; ++ ++ public ChunkLoadTask.PoiDataLoadTask getPoiDataLoadTask() { ++ return this.poiDataLoadTask; ++ } ++ ++ // must hold schedule lock for the two below functions ++ ++ public boolean isPoiChunkLoaded() { ++ return this.poiChunk != null; ++ } ++ ++ private void completePoiLoad(final GenericDataLoadTask.TaskResult result) { ++ final List completeWaiters; ++ ChunkLoadTask.PoiDataLoadTask poiDataLoadTask = null; ++ boolean schedulePoiTask = false; ++ ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); ++ try { ++ final List waiters = this.poiDataLoadTaskWaiters; ++ this.poiDataLoadTask = null; ++ if (result != null) { ++ this.poiDataLoadTaskWaiters = null; ++ this.poiChunk = result.left(); ++ if (result.right() != null) { ++ LOGGER.error("Unhandled poi load exception, poi data will be lost: ", result.right()); ++ } ++ ++ for (final GenericDataLoadTaskCallback callback : waiters) { ++ callback.markCompleted(); ++ } ++ ++ completeWaiters = waiters; ++ } else { ++ // cancelled ++ completeWaiters = null; ++ ++ // need to re-schedule? ++ if (waiters.isEmpty()) { ++ this.poiDataLoadTaskWaiters = null; ++ // no tasks to schedule _for_ ++ } else { ++ poiDataLoadTask = this.poiDataLoadTask = new ChunkLoadTask.PoiDataLoadTask( ++ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority() ++ ); ++ poiDataLoadTask.addCallback(this::completePoiLoad); ++ // need one schedule() per waiter ++ for (final GenericDataLoadTaskCallback callback : waiters) { ++ schedulePoiTask |= poiDataLoadTask.schedule(true); ++ } ++ } ++ } ++ } finally { ++ this.scheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ ++ if (schedulePoiTask) { ++ poiDataLoadTask.scheduleNow(); ++ } ++ ++ // avoid holding the scheduling lock while completing ++ if (completeWaiters != null) { ++ for (final GenericDataLoadTaskCallback callback : completeWaiters) { ++ callback.acceptCompleted(result); ++ } ++ } ++ schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); ++ try { ++ this.checkUnload(); ++ } finally { ++ this.scheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ } ++ ++ // note: it is guaranteed that the consumer cannot be called for the entirety that the schedule lock is held ++ // however, when the consumer is invoked, it will hold the schedule lock ++ public GenericDataLoadTaskCallback getOrLoadPoiData(final Consumer> consumer) { ++ if (this.isPoiChunkLoaded()) { ++ throw new IllegalStateException("Cannot load poi data, it is already loaded"); ++ } ++ // why not just acquire the lock? because the caller NEEDS to call isPoiChunkLoaded before this! ++ if (!this.scheduler.schedulingLockArea.isHeldByCurrentThread(this.chunkX, this.chunkZ)) { ++ throw new IllegalStateException("Must hold scheduling lock"); ++ } ++ ++ final GenericDataLoadTaskCallback ret = new PoiDataLoadTaskCallback((Consumer)consumer, this); ++ ++ if (this.poiDataLoadTask == null) { ++ this.poiDataLoadTask = new ChunkLoadTask.PoiDataLoadTask( ++ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority() ++ ); ++ this.poiDataLoadTask.addCallback(this::completePoiLoad); ++ this.poiDataLoadTaskWaiters = new ArrayList<>(); ++ } ++ this.poiDataLoadTaskWaiters.add(ret); ++ if (this.poiDataLoadTask.schedule(true)) { ++ ret.schedule = this.poiDataLoadTask; ++ } ++ this.checkUnload(); ++ ++ return ret; ++ } ++ ++ private static final class PoiDataLoadTaskCallback extends GenericDataLoadTaskCallback { ++ ++ public PoiDataLoadTaskCallback(final Consumer> consumer, final NewChunkHolder chunkHolder) { ++ super(consumer, chunkHolder); ++ } ++ ++ @Override ++ void internalCancel() { ++ this.chunkHolder.poiDataLoadTaskWaiters.remove(this); ++ this.chunkHolder.poiDataLoadTask.cancel(); ++ } ++ } ++ ++ public static abstract class GenericDataLoadTaskCallback implements Cancellable { ++ ++ protected final Consumer> consumer; ++ protected final NewChunkHolder chunkHolder; ++ protected boolean completed; ++ protected GenericDataLoadTask schedule; ++ protected final AtomicBoolean scheduled = new AtomicBoolean(); ++ ++ public GenericDataLoadTaskCallback(final Consumer> consumer, ++ final NewChunkHolder chunkHolder) { ++ this.consumer = consumer; ++ this.chunkHolder = chunkHolder; ++ } ++ ++ public void schedule() { ++ if (this.scheduled.getAndSet(true)) { ++ throw new IllegalStateException("Double calling schedule()"); ++ } ++ if (this.schedule != null) { ++ this.schedule.scheduleNow(); ++ this.schedule = null; ++ } ++ } ++ ++ boolean isCompleted() { ++ return this.completed; ++ } ++ ++ // must hold scheduling lock ++ private boolean setCompleted() { ++ if (this.completed) { ++ return false; ++ } ++ return this.completed = true; ++ } ++ ++ // must hold scheduling lock ++ void markCompleted() { ++ if (this.completed) { ++ throw new IllegalStateException("May not be completed here"); ++ } ++ this.completed = true; ++ } ++ ++ void acceptCompleted(final GenericDataLoadTask.TaskResult result) { ++ if (result != null) { ++ if (this.completed) { ++ this.consumer.accept(result); ++ } else { ++ throw new IllegalStateException("Cannot be uncompleted at this point"); ++ } ++ } else { ++ throw new NullPointerException("Result cannot be null (cancelled)"); ++ } ++ } ++ ++ // holds scheduling lock ++ abstract void internalCancel(); ++ ++ @Override ++ public boolean cancel() { ++ final NewChunkHolder holder = this.chunkHolder; // Folia - use area based lock to reduce contention ++ final ReentrantAreaLock.Node schedulingLock = holder.scheduler.schedulingLockArea.lock(holder.chunkX, holder.chunkZ); ++ try { ++ if (!this.completed) { ++ this.completed = true; ++ this.internalCancel(); ++ return true; ++ } ++ return false; ++ } finally { ++ holder.scheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ } ++ } ++ ++ private ChunkAccess currentChunk; ++ ++ // generation status state ++ ++ /** ++ * Current status the chunk has been brought up to by the chunk system. null indicates no work at all ++ */ ++ private ChunkStatus currentGenStatus; ++ ++ // This allows unsynchronised access to the chunk and last gen status ++ private volatile ChunkCompletion lastChunkCompletion; ++ ++ public ChunkCompletion getLastChunkCompletion() { ++ return this.lastChunkCompletion; ++ } ++ ++ public static final record ChunkCompletion(ChunkAccess chunk, ChunkStatus genStatus) {}; ++ ++ /** ++ * The target final chunk status the chunk system will bring the chunk to. ++ */ ++ private ChunkStatus requestedGenStatus; ++ ++ private ChunkProgressionTask generationTask; ++ private ChunkStatus generationTaskStatus; ++ ++ /** ++ * contains the neighbours that this chunk generation is blocking on ++ */ ++ protected final ReferenceLinkedOpenHashSet neighboursBlockingGenTask = new ReferenceLinkedOpenHashSet<>(4); ++ ++ /** ++ * map of ChunkHolder -> Required Status for this chunk ++ */ ++ protected final Reference2ObjectLinkedOpenHashMap neighboursWaitingForUs = new Reference2ObjectLinkedOpenHashMap<>(); ++ ++ public void addGenerationBlockingNeighbour(final NewChunkHolder neighbour) { ++ this.neighboursBlockingGenTask.add(neighbour); ++ } ++ ++ public void addWaitingNeighbour(final NewChunkHolder neighbour, final ChunkStatus requiredStatus) { ++ final boolean wasEmpty = this.neighboursWaitingForUs.isEmpty(); ++ this.neighboursWaitingForUs.put(neighbour, requiredStatus); ++ if (wasEmpty) { ++ this.checkUnload(); ++ } ++ } ++ ++ // priority state ++ ++ // the target priority for this chunk to generate at ++ // TODO this will screw over scheduling at lower priorities to neighbours, fix ++ private PrioritisedExecutor.Priority priority = PrioritisedExecutor.Priority.NORMAL; ++ private boolean priorityLocked; ++ ++ // the priority neighbouring chunks have requested this chunk generate at ++ private PrioritisedExecutor.Priority neighbourRequestedPriority = PrioritisedExecutor.Priority.IDLE; ++ ++ public PrioritisedExecutor.Priority getEffectivePriority() { ++ return PrioritisedExecutor.Priority.max(this.priority, this.neighbourRequestedPriority); ++ } ++ ++ protected void recalculateNeighbourRequestedPriority() { ++ if (this.neighboursWaitingForUs.isEmpty()) { ++ this.neighbourRequestedPriority = PrioritisedExecutor.Priority.IDLE; ++ return; ++ } ++ ++ PrioritisedExecutor.Priority max = PrioritisedExecutor.Priority.IDLE; ++ ++ for (final NewChunkHolder holder : this.neighboursWaitingForUs.keySet()) { ++ final PrioritisedExecutor.Priority neighbourPriority = holder.getEffectivePriority(); ++ if (neighbourPriority.isHigherPriority(max)) { ++ max = neighbourPriority; ++ } ++ } ++ ++ final PrioritisedExecutor.Priority current = this.getEffectivePriority(); ++ this.neighbourRequestedPriority = max; ++ final PrioritisedExecutor.Priority next = this.getEffectivePriority(); ++ ++ if (current == next) { ++ return; ++ } ++ ++ // our effective priority has changed, so change our task ++ if (this.generationTask != null) { ++ this.generationTask.setPriority(next); ++ } ++ ++ // now propagate this to our neighbours ++ this.recalculateNeighbourPriorities(); ++ } ++ ++ public void recalculateNeighbourPriorities() { ++ for (final NewChunkHolder holder : this.neighboursBlockingGenTask) { ++ holder.recalculateNeighbourRequestedPriority(); ++ } ++ } ++ ++ // must hold scheduling lock ++ public void raisePriority(final PrioritisedExecutor.Priority priority) { ++ if (this.priority != null && this.priority.isHigherOrEqualPriority(priority)) { ++ return; ++ } ++ this.setPriority(priority); ++ } ++ ++ private void lockPriority() { ++ this.priority = PrioritisedExecutor.Priority.NORMAL; ++ this.priorityLocked = true; ++ } ++ ++ // must hold scheduling lock ++ public void setPriority(final PrioritisedExecutor.Priority priority) { ++ if (this.priorityLocked) { ++ return; ++ } ++ final PrioritisedExecutor.Priority old = this.getEffectivePriority(); ++ this.priority = priority; ++ final PrioritisedExecutor.Priority newPriority = this.getEffectivePriority(); ++ ++ if (old != newPriority) { ++ if (this.generationTask != null) { ++ this.generationTask.setPriority(newPriority); ++ } ++ } ++ ++ this.recalculateNeighbourPriorities(); ++ } ++ ++ // must hold scheduling lock ++ public void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ if (this.priority != null && this.priority.isLowerOrEqualPriority(priority)) { ++ return; ++ } ++ this.setPriority(priority); ++ } ++ ++ // error handling state ++ private ChunkStatus failedGenStatus; ++ private Throwable genTaskException; ++ private Thread genTaskFailedThread; ++ ++ private boolean failedLightUpdate; ++ ++ public void failedLightUpdate() { ++ this.failedLightUpdate = true; ++ } ++ ++ public boolean hasFailedGeneration() { ++ return this.genTaskException != null; ++ } ++ ++ // ticket level state ++ private int oldTicketLevel = ChunkLevel.MAX_LEVEL + 1; ++ private int currentTicketLevel = ChunkLevel.MAX_LEVEL + 1; ++ ++ public int getTicketLevel() { ++ return this.currentTicketLevel; ++ } ++ ++ public final ChunkHolder vanillaChunkHolder; ++ ++ public NewChunkHolder(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkTaskScheduler scheduler) { ++ this.world = world; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.scheduler = scheduler; ++ this.vanillaChunkHolder = new ChunkHolder(new ChunkPos(chunkX, chunkZ), world, world.getLightEngine(), world.chunkSource.chunkMap, this); ++ } ++ ++ protected ImposterProtoChunk wrappedChunkForNeighbour; ++ ++ // holds scheduling lock ++ public ChunkAccess getChunkForNeighbourAccess() { ++ // Vanilla overrides the status futures with an imposter chunk to prevent writes to full chunks ++ // But we don't store per-status futures, so we need this hack ++ if (this.wrappedChunkForNeighbour != null) { ++ return this.wrappedChunkForNeighbour; ++ } ++ final ChunkAccess ret = this.currentChunk; ++ return ret instanceof LevelChunk fullChunk ? this.wrappedChunkForNeighbour = new ImposterProtoChunk(fullChunk, false) : ret; ++ } ++ ++ public ChunkAccess getCurrentChunk() { ++ return this.currentChunk; ++ } ++ ++ int getCurrentTicketLevel() { ++ return this.currentTicketLevel; ++ } ++ ++ void updateTicketLevel(final int toLevel) { ++ this.currentTicketLevel = toLevel; ++ } ++ ++ private int totalNeighboursUsingThisChunk = 0; ++ ++ // holds schedule lock ++ public void addNeighbourUsingChunk() { ++ final int now = ++this.totalNeighboursUsingThisChunk; ++ ++ if (now == 1) { ++ this.checkUnload(); ++ } ++ } ++ ++ // holds schedule lock ++ public void removeNeighbourUsingChunk() { ++ final int now = --this.totalNeighboursUsingThisChunk; ++ ++ if (now == 0) { ++ this.checkUnload(); ++ } ++ ++ if (now < 0) { ++ throw new IllegalStateException("Neighbours using this chunk cannot be negative"); ++ } ++ } ++ ++ // must hold scheduling lock ++ // returns string reason for why chunk should remain loaded, null otherwise ++ public final String isSafeToUnload() { ++ // is ticket level below threshold? ++ if (this.oldTicketLevel <= ChunkHolderManager.MAX_TICKET_LEVEL) { ++ return "ticket_level"; ++ } ++ ++ // are we being used by another chunk for generation? ++ if (this.totalNeighboursUsingThisChunk != 0) { ++ return "neighbours_generating"; ++ } ++ ++ // are we going to be used by another chunk for generation? ++ if (!this.neighboursWaitingForUs.isEmpty()) { ++ return "neighbours_waiting"; ++ } ++ ++ // chunk must be marked inaccessible (i.e unloaded to plugins) ++ if (this.getChunkStatus() != FullChunkStatus.INACCESSIBLE) { ++ return "fullchunkstatus"; ++ } ++ ++ // are we currently generating anything, or have requested generation? ++ if (this.generationTask != null) { ++ return "generating"; ++ } ++ if (this.requestedGenStatus != null) { ++ return "requested_generation"; ++ } ++ ++ // entity data requested? ++ if (this.entityDataLoadTask != null) { ++ return "entity_data_requested"; ++ } ++ ++ // poi data requested? ++ if (this.poiDataLoadTask != null) { ++ return "poi_data_requested"; ++ } ++ ++ // are we pending serialization? ++ if (this.entityDataUnload != null) { ++ return "entity_serialization"; ++ } ++ if (this.poiDataUnload != null) { ++ return "poi_serialization"; ++ } ++ if (this.chunkDataUnload != null) { ++ return "chunk_serialization"; ++ } ++ ++ // Note: light tasks do not need a check, as they add a ticket. ++ ++ // nothing is using this chunk, so it should be unloaded ++ return null; ++ } ++ ++ /** Unloaded from chunk map */ ++ boolean killed; ++ ++ // must hold scheduling lock ++ private void checkUnload() { ++ if (this.killed) { ++ return; ++ } ++ if (this.isSafeToUnload() == null) { ++ // ensure in unload queue ++ this.scheduler.chunkHolderManager.unloadQueue.addChunk(this.chunkX, this.chunkZ); ++ } else { ++ // ensure not in unload queue ++ this.scheduler.chunkHolderManager.unloadQueue.removeChunk(this.chunkX, this.chunkZ); ++ } ++ } ++ ++ static final record UnloadState(NewChunkHolder holder, ChunkAccess chunk, ChunkEntitySlices entityChunk, PoiChunk poiChunk) {}; ++ ++ // note: these are completed with null to indicate that no write occurred ++ // they are also completed with null to indicate a null write occurred ++ private UnloadTask chunkDataUnload; ++ private UnloadTask entityDataUnload; ++ private UnloadTask poiDataUnload; ++ ++ public static final record UnloadTask(Completable completable, DelayedPrioritisedTask task) {} ++ ++ public UnloadTask getUnloadTask(final RegionFileIOThread.RegionFileType type) { ++ switch (type) { ++ case CHUNK_DATA: ++ return this.chunkDataUnload; ++ case ENTITY_DATA: ++ return this.entityDataUnload; ++ case POI_DATA: ++ return this.poiDataUnload; ++ default: ++ throw new IllegalStateException("Unknown regionfile type " + type); ++ } ++ } ++ ++ private UnloadState unloadState; ++ ++ // holds schedule lock ++ UnloadState unloadStage1() { ++ // because we hold the scheduling lock, we cannot actually unload anything ++ // so we need to null this chunk's state ++ ChunkAccess chunk = this.currentChunk; ++ ChunkEntitySlices entityChunk = this.entityChunk; ++ PoiChunk poiChunk = this.poiChunk; ++ // chunk state ++ this.currentChunk = null; ++ this.currentGenStatus = null; ++ this.wrappedChunkForNeighbour = null; ++ this.lastChunkCompletion = null; ++ // entity chunk state ++ this.entityChunk = null; ++ this.pendingEntityChunk = null; ++ ++ // poi chunk state ++ this.poiChunk = null; ++ ++ // priority state ++ this.priorityLocked = false; ++ ++ if (chunk != null) { ++ this.chunkDataUnload = new UnloadTask(new Completable<>(), new DelayedPrioritisedTask(PrioritisedExecutor.Priority.NORMAL)); ++ } ++ if (poiChunk != null) { ++ this.poiDataUnload = new UnloadTask(new Completable<>(), null); ++ } ++ if (entityChunk != null) { ++ this.entityDataUnload = new UnloadTask(new Completable<>(), null); ++ } ++ ++ return this.unloadState = (chunk != null || entityChunk != null || poiChunk != null) ? new UnloadState(this, chunk, entityChunk, poiChunk) : null; ++ } ++ ++ // data is null if failed or does not need to be saved ++ void completeAsyncChunkDataSave(final CompoundTag data) { ++ if (data != null) { ++ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, data, RegionFileIOThread.RegionFileType.CHUNK_DATA); ++ } ++ this.chunkDataUnload.completable().complete(data); ++ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); ++ try { ++ // can only write to these fields while holding the schedule lock ++ this.chunkDataUnload = null; ++ this.checkUnload(); ++ } finally { ++ this.scheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ } ++ ++ void unloadStage2(final UnloadState state) { ++ this.unloadState = null; ++ final ChunkAccess chunk = state.chunk(); ++ final ChunkEntitySlices entityChunk = state.entityChunk(); ++ final PoiChunk poiChunk = state.poiChunk(); ++ ++ final boolean shouldLevelChunkNotSave = (chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave); ++ ++ // unload chunk data ++ if (chunk != null) { ++ if (chunk instanceof LevelChunk levelChunk) { ++ levelChunk.setLoaded(false); ++ } ++ ++ if (!shouldLevelChunkNotSave) { ++ this.saveChunk(chunk, true); ++ } else { ++ this.completeAsyncChunkDataSave(null); ++ } ++ ++ if (chunk instanceof LevelChunk levelChunk) { ++ this.world.unload(levelChunk); ++ } ++ } ++ ++ // unload entity data ++ if (entityChunk != null) { ++ this.saveEntities(entityChunk, true); ++ // yes this is a hack to pass the compound tag through... ++ final CompoundTag lastEntityUnload = this.lastEntityUnload; ++ this.lastEntityUnload = null; ++ ++ if (entityChunk.unload()) { ++ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); ++ try { ++ entityChunk.setTransient(true); ++ this.entityChunk = entityChunk; ++ } finally { ++ this.scheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ } else { ++ this.world.getEntityLookup().entitySectionUnload(this.chunkX, this.chunkZ); ++ } ++ // we need to delay the callback until after determining transience, otherwise a potential loader could ++ // set entityChunk before we do ++ this.entityDataUnload.completable().complete(lastEntityUnload); ++ } ++ ++ // unload poi data ++ if (poiChunk != null) { ++ if (poiChunk.isDirty() && !shouldLevelChunkNotSave) { ++ this.savePOI(poiChunk, true); ++ } else { ++ this.poiDataUnload.completable().complete(null); ++ } ++ ++ if (poiChunk.isLoaded()) { ++ this.world.getPoiManager().onUnload(CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ)); ++ } ++ } ++ } ++ ++ boolean unloadStage3() { ++ // can only write to these while holding the schedule lock, and we instantly complete them in stage2 ++ this.poiDataUnload = null; ++ this.entityDataUnload = null; ++ ++ // we need to check if anything has been loaded in the meantime (or if we have transient entities) ++ if (this.entityChunk != null || this.poiChunk != null || this.currentChunk != null) { ++ return false; ++ } ++ ++ return this.isSafeToUnload() == null; ++ } ++ ++ private void cancelGenTask() { ++ if (this.generationTask != null) { ++ this.generationTask.cancel(); ++ } else { ++ // otherwise, we are blocking on neighbours, so remove them ++ if (!this.neighboursBlockingGenTask.isEmpty()) { ++ for (final NewChunkHolder neighbour : this.neighboursBlockingGenTask) { ++ if (neighbour.neighboursWaitingForUs.remove(this) == null) { ++ throw new IllegalStateException("Corrupt state"); ++ } ++ if (neighbour.neighboursWaitingForUs.isEmpty()) { ++ neighbour.checkUnload(); ++ } ++ } ++ this.neighboursBlockingGenTask.clear(); ++ this.checkUnload(); ++ } ++ } ++ } ++ ++ // holds: ticket level update lock ++ // holds: schedule lock ++ public void processTicketLevelUpdate(final List scheduledTasks, final List changedLoadStatus) { ++ final int oldLevel = this.oldTicketLevel; ++ final int newLevel = this.currentTicketLevel; ++ ++ if (oldLevel == newLevel) { ++ return; ++ } ++ ++ this.oldTicketLevel = newLevel; ++ ++ final FullChunkStatus oldState = ChunkLevel.fullStatus(oldLevel); ++ final FullChunkStatus newState = ChunkLevel.fullStatus(newLevel); ++ final boolean oldUnloaded = oldLevel > ChunkHolderManager.MAX_TICKET_LEVEL; ++ final boolean newUnloaded = newLevel > ChunkHolderManager.MAX_TICKET_LEVEL; ++ ++ final ChunkStatus maxGenerationStatusOld = ChunkLevel.generationStatus(oldLevel); ++ final ChunkStatus maxGenerationStatusNew = ChunkLevel.generationStatus(newLevel); ++ ++ // check for cancellations from downgrading ticket level ++ if (this.requestedGenStatus != null && !newState.isOrAfter(FullChunkStatus.FULL) && newLevel > oldLevel) { ++ // note: cancel() may invoke onChunkGenComplete synchronously here ++ if (newUnloaded) { ++ // need to cancel all tasks ++ // note: requested status must be set to null here before cancellation, to indicate to the ++ // completion logic that we do not want rescheduling to occur ++ this.requestedGenStatus = null; ++ this.cancelGenTask(); ++ } else { ++ final ChunkStatus toCancel = maxGenerationStatusNew.getNextStatus(); ++ final ChunkStatus currentRequestedStatus = this.requestedGenStatus; ++ ++ if (currentRequestedStatus.isOrAfter(toCancel)) { ++ // we do have to cancel something here ++ // clamp requested status to the maximum ++ if (this.currentGenStatus != null && this.currentGenStatus.isOrAfter(maxGenerationStatusNew)) { ++ // already generated to status, so we must cancel ++ this.requestedGenStatus = null; ++ this.cancelGenTask(); ++ } else { ++ // not generated to status, so we may have to cancel ++ // note: gen task is always 1 status above current gen status if not null ++ this.requestedGenStatus = maxGenerationStatusNew; ++ if (this.generationTaskStatus != null && this.generationTaskStatus.isOrAfter(toCancel)) { ++ // TOOD is this even possible? i don't think so ++ throw new IllegalStateException("?????"); ++ } ++ } ++ } ++ } ++ } ++ ++ if (newState != oldState) { ++ if (newState.isOrAfter(oldState)) { ++ // status upgrade ++ if (!oldState.isOrAfter(FullChunkStatus.FULL) && newState.isOrAfter(FullChunkStatus.FULL)) { ++ // may need to schedule full load ++ if (this.currentGenStatus != ChunkStatus.FULL) { ++ if (this.requestedGenStatus != null) { ++ this.requestedGenStatus = ChunkStatus.FULL; ++ } else { ++ this.scheduler.schedule( ++ this.chunkX, this.chunkZ, ChunkStatus.FULL, this, scheduledTasks ++ ); ++ } ++ } else { ++ // now we are fully loaded ++ this.queueBorderFullStatus(true, changedLoadStatus); ++ } ++ } ++ } else { ++ // status downgrade ++ if (!newState.isOrAfter(FullChunkStatus.ENTITY_TICKING) && oldState.isOrAfter(FullChunkStatus.ENTITY_TICKING)) { ++ this.completeFullStatusConsumers(FullChunkStatus.ENTITY_TICKING, null); ++ } ++ ++ if (!newState.isOrAfter(FullChunkStatus.BLOCK_TICKING) && oldState.isOrAfter(FullChunkStatus.BLOCK_TICKING)) { ++ this.completeFullStatusConsumers(FullChunkStatus.BLOCK_TICKING, null); ++ } ++ ++ if (!newState.isOrAfter(FullChunkStatus.FULL) && oldState.isOrAfter(FullChunkStatus.FULL)) { ++ this.completeFullStatusConsumers(FullChunkStatus.FULL, null); ++ } ++ } ++ } ++ ++ if (oldState != newState) { ++ if (this.onTicketUpdate(oldState, newState)) { ++ changedLoadStatus.add(this); ++ } ++ } ++ ++ if (oldUnloaded != newUnloaded) { ++ this.checkUnload(); ++ } ++ } ++ ++ /* ++ For full chunks, vanilla just loads chunks around it up to FEATURES, 1 radius ++ ++ For ticking chunks, it updates the persistent entity manager (soon to be completely nuked by EntitySliceManager, which ++ will also need to be updated but with far less implications) ++ It also shoves the scheduled block ticks into the tick scheduler ++ ++ For entity ticking chunks, updates the entity manager (see above) ++ */ ++ ++ static final int NEIGHBOUR_RADIUS = 2; ++ private long fullNeighbourChunksLoadedBitset; ++ ++ private static int getFullNeighbourIndex(final int relativeX, final int relativeZ) { ++ // index = (relativeX + NEIGHBOUR_CACHE_RADIUS) + (relativeZ + NEIGHBOUR_CACHE_RADIUS) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1) ++ // optimised variant of the above by moving some of the ops to compile time ++ return relativeX + (relativeZ * (NEIGHBOUR_RADIUS * 2 + 1)) + (NEIGHBOUR_RADIUS + NEIGHBOUR_RADIUS * ((NEIGHBOUR_RADIUS * 2 + 1))); ++ } ++ public final boolean isNeighbourFullLoaded(final int relativeX, final int relativeZ) { ++ return (this.fullNeighbourChunksLoadedBitset & (1L << getFullNeighbourIndex(relativeX, relativeZ))) != 0; ++ } ++ ++ // returns true if this chunk changed full status ++ public final boolean setNeighbourFullLoaded(final int relativeX, final int relativeZ) { ++ final long before = this.fullNeighbourChunksLoadedBitset; ++ final int index = getFullNeighbourIndex(relativeX, relativeZ); ++ this.fullNeighbourChunksLoadedBitset |= (1L << index); ++ return this.onNeighbourChange(before, this.fullNeighbourChunksLoadedBitset); ++ } ++ ++ // returns true if this chunk changed full status ++ public final boolean setNeighbourFullUnloaded(final int relativeX, final int relativeZ) { ++ final long before = this.fullNeighbourChunksLoadedBitset; ++ final int index = getFullNeighbourIndex(relativeX, relativeZ); ++ this.fullNeighbourChunksLoadedBitset &= ~(1L << index); ++ return this.onNeighbourChange(before, this.fullNeighbourChunksLoadedBitset); ++ } ++ ++ public static boolean areNeighboursFullLoaded(final long bitset, final int radius) { ++ // index = relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1))) ++ switch (radius) { ++ case 0: { ++ return (bitset & (1L << getFullNeighbourIndex(0, 0))) != 0L; ++ } ++ case 1: { ++ long mask = 0L; ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ mask |= (1L << getFullNeighbourIndex(dx, dz)); ++ } ++ } ++ return (bitset & mask) == mask; ++ } ++ case 2: { ++ long mask = 0L; ++ for (int dx = -2; dx <= 2; ++dx) { ++ for (int dz = -2; dz <= 2; ++dz) { ++ mask |= (1L << getFullNeighbourIndex(dx, dz)); ++ } ++ } ++ return (bitset & mask) == mask; ++ } ++ ++ default: { ++ throw new IllegalArgumentException("Radius not recognized: " + radius); ++ } ++ } ++ } ++ ++ // upper 16 bits are pending status, lower 16 bits are current status ++ private volatile long chunkStatus; ++ private static final long PENDING_STATUS_MASK = Long.MIN_VALUE >> 31; ++ private static final FullChunkStatus[] CHUNK_STATUS_BY_ID = FullChunkStatus.values(); ++ private static final VarHandle CHUNK_STATUS_HANDLE = ConcurrentUtil.getVarHandle(NewChunkHolder.class, "chunkStatus", long.class); ++ ++ public static FullChunkStatus getCurrentChunkStatus(final long encoded) { ++ return CHUNK_STATUS_BY_ID[(int)encoded]; ++ } ++ ++ public static FullChunkStatus getPendingChunkStatus(final long encoded) { ++ return CHUNK_STATUS_BY_ID[(int)(encoded >>> 32)]; ++ } ++ ++ public FullChunkStatus getChunkStatus() { ++ return getCurrentChunkStatus(((long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this))); ++ } ++ ++ public boolean isEntityTickingReady() { ++ return this.getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING); ++ } ++ ++ public boolean isTickingReady() { ++ return this.getChunkStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); ++ } ++ ++ public boolean isFullChunkReady() { ++ return this.getChunkStatus().isOrAfter(FullChunkStatus.FULL); ++ } ++ ++ private static FullChunkStatus getStatusForBitset(final long bitset) { ++ if (areNeighboursFullLoaded(bitset, 2)) { ++ return FullChunkStatus.ENTITY_TICKING; ++ } else if (areNeighboursFullLoaded(bitset, 1)) { ++ return FullChunkStatus.BLOCK_TICKING; ++ } else if (areNeighboursFullLoaded(bitset, 0)) { ++ return FullChunkStatus.FULL; ++ } else { ++ return FullChunkStatus.INACCESSIBLE; ++ } ++ } ++ ++ // note: only while updating ticket level, so holds ticket update lock + scheduling lock ++ protected final boolean onTicketUpdate(final FullChunkStatus oldState, final FullChunkStatus newState) { ++ if (oldState == newState) { ++ return false; ++ } ++ ++ // preserve border request after full status complete, as it does not set anything in the bitset ++ FullChunkStatus byNeighbours = getStatusForBitset(this.fullNeighbourChunksLoadedBitset); ++ if (byNeighbours == FullChunkStatus.INACCESSIBLE && newState.isOrAfter(FullChunkStatus.FULL) && this.currentGenStatus == ChunkStatus.FULL) { ++ byNeighbours = FullChunkStatus.FULL; ++ } ++ ++ final FullChunkStatus toSet; ++ ++ if (newState.isOrAfter(byNeighbours)) { ++ // must clamp to neighbours level, even though we have the ticket level ++ toSet = byNeighbours; ++ } else { ++ // must clamp to ticket level, even though we have the neighbours ++ toSet = newState; ++ } ++ ++ long curr = (long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this); ++ ++ if (curr == ((long)toSet.ordinal() | ((long)toSet.ordinal() << 32))) { ++ // nothing to do ++ return false; ++ } ++ ++ int failures = 0; ++ for (;;) { ++ final long update = (curr & ~PENDING_STATUS_MASK) | ((long)toSet.ordinal() << 32); ++ if (curr == (curr = (long)CHUNK_STATUS_HANDLE.compareAndExchange((NewChunkHolder)this, curr, update))) { ++ return true; ++ } ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ protected final boolean onNeighbourChange(final long bitsetBefore, final long bitsetAfter) { ++ FullChunkStatus oldState = getStatusForBitset(bitsetBefore); ++ FullChunkStatus newState = getStatusForBitset(bitsetAfter); ++ final FullChunkStatus currStateTicketLevel = ChunkLevel.fullStatus(this.oldTicketLevel); ++ if (oldState.isOrAfter(currStateTicketLevel)) { ++ oldState = currStateTicketLevel; ++ } ++ if (newState.isOrAfter(currStateTicketLevel)) { ++ newState = currStateTicketLevel; ++ } ++ // preserve border request after full status complete, as it does not set anything in the bitset ++ if (newState == FullChunkStatus.INACCESSIBLE && currStateTicketLevel.isOrAfter(FullChunkStatus.FULL) && this.currentGenStatus == ChunkStatus.FULL) { ++ newState = FullChunkStatus.FULL; ++ } ++ ++ if (oldState == newState) { ++ return false; ++ } ++ ++ int failures = 0; ++ for (long curr = (long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this);;) { ++ final long update = (curr & ~PENDING_STATUS_MASK) | ((long)newState.ordinal() << 32); ++ if (curr == (curr = (long)CHUNK_STATUS_HANDLE.compareAndExchange((NewChunkHolder)this, curr, update))) { ++ return true; ++ } ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ private boolean queueBorderFullStatus(final boolean loaded, final List changedFullStatus) { ++ final FullChunkStatus toStatus = loaded ? FullChunkStatus.FULL : FullChunkStatus.INACCESSIBLE; ++ ++ int failures = 0; ++ for (long curr = (long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this);;) { ++ final FullChunkStatus currPending = getPendingChunkStatus(curr); ++ if (loaded && currPending != FullChunkStatus.INACCESSIBLE) { ++ throw new IllegalStateException("Expected " + FullChunkStatus.INACCESSIBLE + " for pending, but got " + currPending); ++ } ++ ++ final long update = (curr & ~PENDING_STATUS_MASK) | ((long)toStatus.ordinal() << 32); ++ if (curr == (curr = (long)CHUNK_STATUS_HANDLE.compareAndExchange((NewChunkHolder)this, curr, update))) { ++ if ((int)(update) != (int)(update >>> 32)) { ++ changedFullStatus.add(this); ++ return true; ++ } ++ return false; ++ } ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ // only call on main thread, must hold ticket level and scheduling lock ++ private void onFullChunkLoadChange(final boolean loaded, final List changedFullStatus) { ++ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ, NEIGHBOUR_RADIUS); ++ try { ++ for (int dz = -NEIGHBOUR_RADIUS; dz <= NEIGHBOUR_RADIUS; ++dz) { ++ for (int dx = -NEIGHBOUR_RADIUS; dx <= NEIGHBOUR_RADIUS; ++dx) { ++ final NewChunkHolder holder = (dx | dz) == 0 ? this : this.scheduler.chunkHolderManager.getChunkHolder(dx + this.chunkX, dz + this.chunkZ); ++ if (loaded) { ++ if (holder.setNeighbourFullLoaded(-dx, -dz)) { ++ changedFullStatus.add(holder); ++ } ++ } else { ++ if (holder != null && holder.setNeighbourFullUnloaded(-dx, -dz)) { ++ changedFullStatus.add(holder); ++ } ++ } ++ } ++ } ++ } finally { ++ this.scheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ } ++ ++ private FullChunkStatus updateCurrentState(final FullChunkStatus to) { ++ int failures = 0; ++ for (long curr = (long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this);;) { ++ final long update = (curr & PENDING_STATUS_MASK) | (long)to.ordinal(); ++ if (curr == (curr = (long)CHUNK_STATUS_HANDLE.compareAndExchange((NewChunkHolder)this, curr, update))) { ++ return getPendingChunkStatus(curr); ++ } ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ private void changeEntityChunkStatus(final FullChunkStatus toStatus) { ++ this.world.getEntityLookup().chunkStatusChange(this.chunkX, this.chunkZ, toStatus); ++ } ++ ++ private boolean processingFullStatus = false; ++ ++ // only to be called on the main thread, no locks need to be held ++ public boolean handleFullStatusChange(final List changedFullStatus) { ++ TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Cannot update full status thread off-main"); ++ ++ boolean ret = false; ++ ++ if (this.processingFullStatus) { ++ // we cannot process updates recursively ++ return ret; ++ } ++ ++ // note: use opaque reads for chunk status read since we need it to be atomic ++ ++ // test if anything changed ++ long statusCheck = (long)CHUNK_STATUS_HANDLE.getOpaque((NewChunkHolder)this); ++ if ((int)statusCheck == (int)(statusCheck >>> 32)) { ++ // nothing changed ++ return ret; ++ } ++ ++ final ChunkTaskScheduler scheduler = this.scheduler; ++ final ChunkHolderManager holderManager = scheduler.chunkHolderManager; ++ final int ticketKeep; ++ final Long ticketId = Long.valueOf(holderManager.getNextStatusUpgradeId()); ++ final ReentrantAreaLock.Node ticketLock = holderManager.ticketLockArea.lock(this.chunkX, this.chunkZ); ++ try { ++ ticketKeep = this.currentTicketLevel; ++ statusCheck = (long)CHUNK_STATUS_HANDLE.getOpaque((NewChunkHolder)this); ++ // handle race condition where ticket level and target status is updated concurrently ++ if ((int)statusCheck == (int)(statusCheck >>> 32)) { ++ // nothing changed ++ return ret; ++ } ++ holderManager.addTicketAtLevel(TicketType.STATUS_UPGRADE, CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ), ticketKeep, ticketId, false); ++ } finally { ++ holderManager.ticketLockArea.unlock(ticketLock); ++ } ++ ++ this.processingFullStatus = true; ++ try { ++ for (;;) { ++ final long currStateEncoded = (long)CHUNK_STATUS_HANDLE.getOpaque((NewChunkHolder)this); ++ final FullChunkStatus currState = getCurrentChunkStatus(currStateEncoded); ++ FullChunkStatus nextState = getPendingChunkStatus(currStateEncoded); ++ if (currState == nextState) { ++ if (nextState == FullChunkStatus.INACCESSIBLE) { ++ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); ++ try { ++ this.checkUnload(); ++ } finally { ++ this.scheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ } ++ break; ++ } ++ ++ // chunks cannot downgrade state while status is pending a change ++ final LevelChunk chunk = (LevelChunk)this.currentChunk; ++ ++ // Note: we assume that only load/unload contain plugin logic ++ // plugin logic is anything stupid enough to possibly change the chunk status while it is already ++ // being changed (i.e during load it is possible it will try to set to full ticking) ++ // in order to allow this change, we also need this plugin logic to be contained strictly after all ++ // of the chunk system load callbacks are invoked ++ if (nextState.isOrAfter(currState)) { ++ // state upgrade ++ if (!currState.isOrAfter(FullChunkStatus.FULL) && nextState.isOrAfter(FullChunkStatus.FULL)) { ++ nextState = this.updateCurrentState(FullChunkStatus.FULL); ++ holderManager.ensureInAutosave(this); ++ chunk.pushChunkIntoLoadedMap(); ++ this.changeEntityChunkStatus(FullChunkStatus.FULL); ++ chunk.onChunkLoad(this); ++ this.onFullChunkLoadChange(true, changedFullStatus); ++ this.completeFullStatusConsumers(FullChunkStatus.FULL, chunk); ++ } ++ ++ if (!currState.isOrAfter(FullChunkStatus.BLOCK_TICKING) && nextState.isOrAfter(FullChunkStatus.BLOCK_TICKING)) { ++ nextState = this.updateCurrentState(FullChunkStatus.BLOCK_TICKING); ++ this.changeEntityChunkStatus(FullChunkStatus.BLOCK_TICKING); ++ chunk.onChunkTicking(this); ++ this.completeFullStatusConsumers(FullChunkStatus.BLOCK_TICKING, chunk); ++ } ++ ++ if (!currState.isOrAfter(FullChunkStatus.ENTITY_TICKING) && nextState.isOrAfter(FullChunkStatus.ENTITY_TICKING)) { ++ nextState = this.updateCurrentState(FullChunkStatus.ENTITY_TICKING); ++ this.changeEntityChunkStatus(FullChunkStatus.ENTITY_TICKING); ++ chunk.onChunkEntityTicking(this); ++ this.completeFullStatusConsumers(FullChunkStatus.ENTITY_TICKING, chunk); ++ } ++ } else { ++ if (currState.isOrAfter(FullChunkStatus.ENTITY_TICKING) && !nextState.isOrAfter(FullChunkStatus.ENTITY_TICKING)) { ++ this.changeEntityChunkStatus(FullChunkStatus.BLOCK_TICKING); ++ chunk.onChunkNotEntityTicking(this); ++ nextState = this.updateCurrentState(FullChunkStatus.BLOCK_TICKING); ++ } ++ ++ if (currState.isOrAfter(FullChunkStatus.BLOCK_TICKING) && !nextState.isOrAfter(FullChunkStatus.BLOCK_TICKING)) { ++ this.changeEntityChunkStatus(FullChunkStatus.FULL); ++ chunk.onChunkNotTicking(this); ++ nextState = this.updateCurrentState(FullChunkStatus.FULL); ++ } ++ ++ if (currState.isOrAfter(FullChunkStatus.FULL) && !nextState.isOrAfter(FullChunkStatus.FULL)) { ++ this.onFullChunkLoadChange(false, changedFullStatus); ++ this.changeEntityChunkStatus(FullChunkStatus.INACCESSIBLE); ++ chunk.onChunkUnload(this); ++ nextState = this.updateCurrentState(FullChunkStatus.INACCESSIBLE); ++ } ++ } ++ ++ ret = true; ++ } ++ } finally { ++ this.processingFullStatus = false; ++ holderManager.removeTicketAtLevel(TicketType.STATUS_UPGRADE, this.chunkX, this.chunkZ, ticketKeep, ticketId); ++ } ++ ++ return ret; ++ } ++ ++ // note: must hold scheduling lock ++ // rets true if the current requested gen status is not null (effectively, whether further scheduling is not needed) ++ boolean upgradeGenTarget(final ChunkStatus toStatus) { ++ if (toStatus == null) { ++ throw new NullPointerException("toStatus cannot be null"); ++ } ++ if (this.requestedGenStatus == null && this.generationTask == null) { ++ return false; ++ } ++ if (this.requestedGenStatus == null || !this.requestedGenStatus.isOrAfter(toStatus)) { ++ this.requestedGenStatus = toStatus; ++ } ++ return true; ++ } ++ ++ public void setGenerationTarget(final ChunkStatus toStatus) { ++ this.requestedGenStatus = toStatus; ++ } ++ ++ public boolean hasGenerationTask() { ++ return this.generationTask != null; ++ } ++ ++ public ChunkStatus getCurrentGenStatus() { ++ return this.currentGenStatus; ++ } ++ ++ public ChunkStatus getRequestedGenStatus() { ++ return this.requestedGenStatus; ++ } ++ ++ private final Reference2ObjectOpenHashMap>> statusWaiters = new Reference2ObjectOpenHashMap<>(); ++ ++ void addStatusConsumer(final ChunkStatus status, final Consumer consumer) { ++ this.statusWaiters.computeIfAbsent(status, (final ChunkStatus keyInMap) -> { ++ return new ArrayList<>(4); ++ }).add(consumer); ++ } ++ ++ private void completeStatusConsumers(ChunkStatus status, final ChunkAccess chunk) { ++ // need to tell future statuses to complete if cancelled ++ do { ++ this.completeStatusConsumers0(status, chunk); ++ } while (chunk == null && status != (status = status.getNextStatus())); ++ } ++ ++ private void completeStatusConsumers0(final ChunkStatus status, final ChunkAccess chunk) { ++ final List> consumers; ++ consumers = this.statusWaiters.remove(status); ++ ++ if (consumers == null) { ++ return; ++ } ++ ++ // must be scheduled to main, we do not trust the callback to not do anything stupid ++ this.scheduler.scheduleChunkTask(this.chunkX, this.chunkZ, () -> { ++ for (final Consumer consumer : consumers) { ++ try { ++ consumer.accept(chunk); ++ } catch (final ThreadDeath thr) { ++ throw thr; ++ } catch (final Throwable thr) { ++ LOGGER.error("Failed to process chunk status callback", thr); ++ } ++ } ++ }, PrioritisedExecutor.Priority.HIGHEST); ++ } ++ ++ private final Reference2ObjectOpenHashMap>> fullStatusWaiters = new Reference2ObjectOpenHashMap<>(); ++ ++ void addFullStatusConsumer(final FullChunkStatus status, final Consumer consumer) { ++ this.fullStatusWaiters.computeIfAbsent(status, (final FullChunkStatus keyInMap) -> { ++ return new ArrayList<>(4); ++ }).add(consumer); ++ } ++ ++ private void completeFullStatusConsumers(FullChunkStatus status, final LevelChunk chunk) { ++ // need to tell future statuses to complete if cancelled ++ final FullChunkStatus max = CHUNK_STATUS_BY_ID[CHUNK_STATUS_BY_ID.length - 1]; ++ ++ for (;;) { ++ this.completeFullStatusConsumers0(status, chunk); ++ if (chunk != null || status == max) { ++ break; ++ } ++ status = CHUNK_STATUS_BY_ID[status.ordinal() + 1]; ++ } ++ } ++ ++ private void completeFullStatusConsumers0(final FullChunkStatus status, final LevelChunk chunk) { ++ final List> consumers; ++ consumers = this.fullStatusWaiters.remove(status); ++ ++ if (consumers == null) { ++ return; ++ } ++ ++ // must be scheduled to main, we do not trust the callback to not do anything stupid ++ this.scheduler.scheduleChunkTask(this.chunkX, this.chunkZ, () -> { ++ for (final Consumer consumer : consumers) { ++ try { ++ consumer.accept(chunk); ++ } catch (final ThreadDeath thr) { ++ throw thr; ++ } catch (final Throwable thr) { ++ LOGGER.error("Failed to process chunk status callback", thr); ++ } ++ } ++ }, PrioritisedExecutor.Priority.HIGHEST); ++ } ++ ++ // note: must hold scheduling lock ++ private void onChunkGenComplete(final ChunkAccess newChunk, final ChunkStatus newStatus, ++ final List scheduleList, final List changedLoadStatus) { ++ if (!this.neighboursBlockingGenTask.isEmpty()) { ++ throw new IllegalStateException("Cannot have neighbours blocking this gen task"); ++ } ++ if (newChunk != null || (this.requestedGenStatus == null || !this.requestedGenStatus.isOrAfter(newStatus))) { ++ this.completeStatusConsumers(newStatus, newChunk); ++ } ++ // done now, clear state (must be done before scheduling new tasks) ++ this.generationTask = null; ++ this.generationTaskStatus = null; ++ if (newChunk == null) { ++ // task was cancelled ++ // should be careful as this could be called while holding the schedule lock and/or inside the ++ // ticket level update ++ // while a task may be cancelled, it is possible for it to be later re-scheduled ++ // however, because generationTask is only set to null on _completion_, the scheduler leaves ++ // the rescheduling logic to us here ++ final ChunkStatus requestedGenStatus = this.requestedGenStatus; ++ this.requestedGenStatus = null; ++ if (requestedGenStatus != null) { ++ // it looks like it has been requested, so we must reschedule ++ if (!this.neighboursWaitingForUs.isEmpty()) { ++ for (final Iterator> iterator = this.neighboursWaitingForUs.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ final Reference2ObjectMap.Entry entry = iterator.next(); ++ ++ final NewChunkHolder chunkHolder = entry.getKey(); ++ final ChunkStatus toStatus = entry.getValue(); ++ ++ if (!requestedGenStatus.isOrAfter(toStatus)) { ++ // if we were cancelled, we are responsible for removing the waiter ++ if (!chunkHolder.neighboursBlockingGenTask.remove(this)) { ++ throw new IllegalStateException("Corrupt state"); ++ } ++ if (chunkHolder.neighboursBlockingGenTask.isEmpty()) { ++ chunkHolder.checkUnload(); ++ } ++ iterator.remove(); ++ continue; ++ } ++ } ++ } ++ ++ // note: only after generationTask -> null, generationTaskStatus -> null, and requestedGenStatus -> null ++ this.scheduler.schedule( ++ this.chunkX, this.chunkZ, requestedGenStatus, this, scheduleList ++ ); ++ ++ // return, can't do anything further ++ return; ++ } ++ ++ if (!this.neighboursWaitingForUs.isEmpty()) { ++ for (final NewChunkHolder chunkHolder : this.neighboursWaitingForUs.keySet()) { ++ if (!chunkHolder.neighboursBlockingGenTask.remove(this)) { ++ throw new IllegalStateException("Corrupt state"); ++ } ++ if (chunkHolder.neighboursBlockingGenTask.isEmpty()) { ++ chunkHolder.checkUnload(); ++ } ++ } ++ this.neighboursWaitingForUs.clear(); ++ } ++ // reset priority, we have nothing left to generate to ++ this.setPriority(PrioritisedExecutor.Priority.NORMAL); ++ this.checkUnload(); ++ return; ++ } ++ ++ this.currentChunk = newChunk; ++ this.currentGenStatus = newStatus; ++ this.lastChunkCompletion = new ChunkCompletion(newChunk, newStatus); ++ ++ final ChunkStatus requestedGenStatus = this.requestedGenStatus; ++ ++ List needsScheduling = null; ++ boolean recalculatePriority = false; ++ for (final Iterator> iterator ++ = this.neighboursWaitingForUs.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ final Reference2ObjectMap.Entry entry = iterator.next(); ++ final NewChunkHolder neighbour = entry.getKey(); ++ final ChunkStatus requiredStatus = entry.getValue(); ++ ++ if (!newStatus.isOrAfter(requiredStatus)) { ++ if (requestedGenStatus == null || !requestedGenStatus.isOrAfter(requiredStatus)) { ++ // if we're cancelled, still need to clear this map ++ if (!neighbour.neighboursBlockingGenTask.remove(this)) { ++ throw new IllegalStateException("Neighbour is not waiting for us?"); ++ } ++ if (neighbour.neighboursBlockingGenTask.isEmpty()) { ++ neighbour.checkUnload(); ++ } ++ ++ iterator.remove(); ++ } ++ continue; ++ } ++ ++ // doesn't matter what isCancelled is here, we need to schedule if we can ++ ++ recalculatePriority = true; ++ if (!neighbour.neighboursBlockingGenTask.remove(this)) { ++ throw new IllegalStateException("Neighbour is not waiting for us?"); ++ } ++ ++ if (neighbour.neighboursBlockingGenTask.isEmpty()) { ++ if (neighbour.requestedGenStatus != null) { ++ if (needsScheduling == null) { ++ needsScheduling = new ArrayList<>(); ++ } ++ needsScheduling.add(neighbour); ++ } else { ++ neighbour.checkUnload(); ++ } ++ } ++ ++ // remove last; access to entry will throw if removed ++ iterator.remove(); ++ } ++ ++ if (newStatus == ChunkStatus.FULL) { ++ this.lockPriority(); ++ // must use oldTicketLevel, we hold the schedule lock but not the ticket level lock ++ // however, schedule lock needs to be held for ticket level callback, so we're fine here ++ if (ChunkLevel.fullStatus(this.oldTicketLevel).isOrAfter(FullChunkStatus.FULL)) { ++ this.queueBorderFullStatus(true, changedLoadStatus); ++ } ++ } ++ ++ if (recalculatePriority) { ++ this.recalculateNeighbourRequestedPriority(); ++ } ++ ++ if (requestedGenStatus != null && !newStatus.isOrAfter(requestedGenStatus)) { ++ this.scheduleNeighbours(needsScheduling, scheduleList); ++ ++ // we need to schedule more tasks now ++ this.scheduler.schedule( ++ this.chunkX, this.chunkZ, requestedGenStatus, this, scheduleList ++ ); ++ } else { ++ // we're done now ++ if (requestedGenStatus != null) { ++ this.requestedGenStatus = null; ++ } ++ // reached final stage, so stop scheduling now ++ this.setPriority(PrioritisedExecutor.Priority.NORMAL); ++ this.checkUnload(); ++ ++ this.scheduleNeighbours(needsScheduling, scheduleList); ++ } ++ } ++ ++ private void scheduleNeighbours(final List needsScheduling, final List scheduleList) { ++ if (needsScheduling != null) { ++ for (int i = 0, len = needsScheduling.size(); i < len; ++i) { ++ final NewChunkHolder neighbour = needsScheduling.get(i); ++ ++ this.scheduler.schedule( ++ neighbour.chunkX, neighbour.chunkZ, neighbour.requestedGenStatus, neighbour, scheduleList ++ ); ++ } ++ } ++ } ++ ++ public void setGenerationTask(final ChunkProgressionTask generationTask, final ChunkStatus taskStatus, ++ final List neighbours) { ++ if (this.generationTask != null || (this.currentGenStatus != null && this.currentGenStatus.isOrAfter(taskStatus))) { ++ throw new IllegalStateException("Currently generating or provided task is trying to generate to a level we are already at!"); ++ } ++ if (this.requestedGenStatus == null || !this.requestedGenStatus.isOrAfter(taskStatus)) { ++ throw new IllegalStateException("Cannot schedule generation task when not requested"); ++ } ++ this.generationTask = generationTask; ++ this.generationTaskStatus = taskStatus; ++ ++ for (int i = 0, len = neighbours.size(); i < len; ++i) { ++ neighbours.get(i).addNeighbourUsingChunk(); ++ } ++ ++ this.checkUnload(); ++ ++ generationTask.onComplete((final ChunkAccess access, final Throwable thr) -> { ++ if (generationTask != this.generationTask) { ++ throw new IllegalStateException( ++ "Cannot complete generation task '" + generationTask + "' because we are waiting on '" + this.generationTask + "' instead!" ++ ); ++ } ++ if (thr != null) { ++ if (this.genTaskException != null) { ++ // first one is probably the TRUE problem ++ return; ++ } ++ // don't set generation task to null, so that scheduling will not attempt to create another task and it ++ // will automatically block any further scheduling usage of this chunk as it will wait forever for a failed ++ // task to complete ++ this.genTaskException = thr; ++ this.failedGenStatus = taskStatus; ++ this.genTaskFailedThread = Thread.currentThread(); ++ ++ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( ++ "Generation task", ChunkTaskScheduler.stringIfNull(generationTask), ++ "Task to status", ChunkTaskScheduler.stringIfNull(taskStatus) ++ ), thr); ++ return; ++ } ++ ++ final boolean scheduleTasks; ++ List tasks = ChunkHolderManager.getCurrentTicketUpdateScheduling(); ++ if (tasks == null) { ++ scheduleTasks = true; ++ tasks = new ArrayList<>(); ++ } else { ++ scheduleTasks = false; ++ // we are currently updating ticket levels, so we already hold the schedule lock ++ // this means we have to leave the ticket level update to handle the scheduling ++ } ++ final List changedLoadStatus = new ArrayList<>(); ++ // theoretically, we could schedule a chunk at the max radius which performs another max radius access. So we need to double the radius. ++ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ, 2 * ChunkTaskScheduler.getMaxAccessRadius()); ++ try { ++ for (int i = 0, len = neighbours.size(); i < len; ++i) { ++ neighbours.get(i).removeNeighbourUsingChunk(); ++ } ++ this.onChunkGenComplete(access, taskStatus, tasks, changedLoadStatus); ++ } finally { ++ this.scheduler.schedulingLockArea.unlock(schedulingLock); ++ } ++ this.scheduler.chunkHolderManager.addChangedStatuses(changedLoadStatus); ++ ++ if (scheduleTasks) { ++ // can't hold the lock while scheduling, so we have to build the tasks and then schedule after ++ for (int i = 0, len = tasks.size(); i < len; ++i) { ++ tasks.get(i).schedule(); ++ } ++ } ++ }); ++ } ++ ++ public PoiChunk getPoiChunk() { ++ return this.poiChunk; ++ } ++ ++ public ChunkEntitySlices getEntityChunk() { ++ return this.entityChunk; ++ } ++ ++ public long lastAutoSave; ++ ++ public static final record SaveStat(boolean savedChunk, boolean savedEntityChunk, boolean savedPoiChunk) {} ++ ++ public SaveStat save(final boolean shutdown, final boolean unloading) { ++ TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Cannot save data off-main"); ++ ++ ChunkAccess chunk = this.getCurrentChunk(); ++ PoiChunk poi = this.getPoiChunk(); ++ ChunkEntitySlices entities = this.getEntityChunk(); ++ boolean executedUnloadTask = false; ++ ++ if (shutdown) { ++ // make sure that the async unloads complete ++ if (this.unloadState != null) { ++ // must have errored during unload ++ chunk = this.unloadState.chunk(); ++ poi = this.unloadState.poiChunk(); ++ entities = this.unloadState.entityChunk(); ++ } ++ final UnloadTask chunkUnloadTask = this.chunkDataUnload; ++ final DelayedPrioritisedTask chunkDataUnloadTask = chunkUnloadTask == null ? null : chunkUnloadTask.task(); ++ if (chunkDataUnloadTask != null) { ++ final PrioritisedExecutor.PrioritisedTask unloadTask = chunkDataUnloadTask.getTask(); ++ if (unloadTask != null) { ++ executedUnloadTask = unloadTask.execute(); ++ } ++ } ++ } ++ ++ boolean canSaveChunk = !(chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave) && ++ (chunk != null && ((shutdown || chunk instanceof LevelChunk) && chunk.isUnsaved())); ++ boolean canSavePOI = !(chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave) && (poi != null && poi.isDirty()); ++ boolean canSaveEntities = entities != null; ++ ++ try (co.aikar.timings.Timing ignored = this.world.timings.chunkSave.startTiming()) { // Paper ++ if (canSaveChunk) { ++ canSaveChunk = this.saveChunk(chunk, unloading); ++ } ++ if (canSavePOI) { ++ canSavePOI = this.savePOI(poi, unloading); ++ } ++ if (canSaveEntities) { ++ // on shutdown, we need to force transient entity chunks to save ++ canSaveEntities = this.saveEntities(entities, unloading || shutdown); ++ if (unloading || shutdown) { ++ this.lastEntityUnload = null; ++ } ++ } ++ } ++ ++ return executedUnloadTask | canSaveChunk | canSaveEntities | canSavePOI ? new SaveStat(executedUnloadTask || canSaveChunk, canSaveEntities, canSavePOI): null; ++ } ++ ++ static final class AsyncChunkSerializeTask implements Runnable { ++ ++ private final ServerLevel world; ++ private final ChunkAccess chunk; ++ private final ChunkSerializer.AsyncSaveData asyncSaveData; ++ private final NewChunkHolder toComplete; ++ ++ public AsyncChunkSerializeTask(final ServerLevel world, final ChunkAccess chunk, final ChunkSerializer.AsyncSaveData asyncSaveData, ++ final NewChunkHolder toComplete) { ++ this.world = world; ++ this.chunk = chunk; ++ this.asyncSaveData = asyncSaveData; ++ this.toComplete = toComplete; ++ } ++ ++ @Override ++ public void run() { ++ final CompoundTag toSerialize; ++ try { ++ toSerialize = ChunkSerializer.saveChunk(this.world, this.chunk, this.asyncSaveData); ++ } catch (final ThreadDeath death) { ++ throw death; ++ } catch (final Throwable throwable) { ++ LOGGER.error("Failed to asynchronously save chunk " + this.chunk.getPos() + " for world '" + this.world.getWorld().getName() + "', falling back to synchronous save", throwable); ++ this.world.chunkTaskScheduler.scheduleChunkTask(this.chunk.locX, this.chunk.locZ, () -> { ++ final CompoundTag synchronousSave; ++ try { ++ synchronousSave = ChunkSerializer.saveChunk(AsyncChunkSerializeTask.this.world, AsyncChunkSerializeTask.this.chunk, AsyncChunkSerializeTask.this.asyncSaveData); ++ } catch (final ThreadDeath death) { ++ throw death; ++ } catch (final Throwable throwable2) { ++ LOGGER.error("Failed to synchronously save chunk " + AsyncChunkSerializeTask.this.chunk.getPos() + " for world '" + AsyncChunkSerializeTask.this.world.getWorld().getName() + "', chunk data will be lost", throwable2); ++ AsyncChunkSerializeTask.this.toComplete.completeAsyncChunkDataSave(null); ++ return; ++ } ++ ++ AsyncChunkSerializeTask.this.toComplete.completeAsyncChunkDataSave(synchronousSave); ++ LOGGER.info("Successfully serialized chunk " + AsyncChunkSerializeTask.this.chunk.getPos() + " for world '" + AsyncChunkSerializeTask.this.world.getWorld().getName() + "' synchronously"); ++ ++ }, PrioritisedExecutor.Priority.HIGHEST); ++ return; ++ } ++ this.toComplete.completeAsyncChunkDataSave(toSerialize); ++ } ++ ++ @Override ++ public String toString() { ++ return "AsyncChunkSerializeTask{" + ++ "chunk={pos=" + this.chunk.getPos() + ",world=\"" + this.world.getWorld().getName() + "\"}" + ++ "}"; ++ } ++ } ++ ++ private boolean saveChunk(final ChunkAccess chunk, final boolean unloading) { ++ if (!chunk.isUnsaved()) { ++ if (unloading) { ++ this.completeAsyncChunkDataSave(null); ++ } ++ return false; ++ } ++ boolean completing = false; ++ try { ++ if (unloading) { ++ try { ++ final ChunkSerializer.AsyncSaveData asyncSaveData = ChunkSerializer.getAsyncSaveData(this.world, chunk); ++ ++ final PrioritisedExecutor.PrioritisedTask task = this.scheduler.loadExecutor.createTask(new AsyncChunkSerializeTask(this.world, chunk, asyncSaveData, this)); ++ ++ this.chunkDataUnload.task().setTask(task); ++ ++ task.queue(); ++ ++ chunk.setUnsaved(false); ++ ++ return true; ++ } catch (final ThreadDeath death) { ++ throw death; ++ } catch (final Throwable thr) { ++ LOGGER.error("Failed to prepare async chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "', falling back to synchronous save", thr); ++ // fall through to synchronous save ++ } ++ } ++ ++ final CompoundTag save = ChunkSerializer.saveChunk(this.world, chunk, null); ++ ++ if (unloading) { ++ completing = true; ++ this.completeAsyncChunkDataSave(save); ++ LOGGER.info("Successfully serialized chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "' synchronously"); ++ } else { ++ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.CHUNK_DATA); ++ } ++ chunk.setUnsaved(false); ++ } catch (final ThreadDeath death) { ++ throw death; ++ } catch (final Throwable thr) { ++ LOGGER.error("Failed to save chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "'"); ++ if (unloading && !completing) { ++ this.completeAsyncChunkDataSave(null); ++ } ++ } ++ ++ return true; ++ } ++ ++ private boolean lastEntitySaveNull; ++ private CompoundTag lastEntityUnload; ++ private boolean saveEntities(final ChunkEntitySlices entities, final boolean unloading) { ++ try { ++ CompoundTag mergeFrom = null; ++ if (entities.isTransient()) { ++ if (!unloading) { ++ // if we're a transient chunk, we cannot save until unloading because otherwise a double save will ++ // result in double adding the entities ++ return false; ++ } ++ try { ++ mergeFrom = RegionFileIOThread.loadData(this.world, this.chunkX, this.chunkZ, RegionFileIOThread.RegionFileType.ENTITY_DATA, PrioritisedExecutor.Priority.BLOCKING); ++ } catch (final Exception ex) { ++ LOGGER.error("Cannot merge transient entities for chunk (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "', data on disk will be replaced", ex); ++ } ++ } ++ ++ final CompoundTag save = entities.save(); ++ if (mergeFrom != null) { ++ if (save == null) { ++ // don't override the data on disk with nothing ++ return false; ++ } else { ++ EntityStorage.copyEntities(mergeFrom, save); ++ } ++ } ++ if (save == null && this.lastEntitySaveNull) { ++ return false; ++ } ++ ++ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.ENTITY_DATA); ++ this.lastEntitySaveNull = save == null; ++ if (unloading) { ++ this.lastEntityUnload = save; ++ } ++ } catch (final ThreadDeath death) { ++ throw death; ++ } catch (final Throwable thr) { ++ LOGGER.error("Failed to save entity data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "'"); ++ } ++ ++ return true; ++ } ++ ++ private boolean lastPoiSaveNull; ++ private boolean savePOI(final PoiChunk poi, final boolean unloading) { ++ try { ++ final CompoundTag save = poi.save(); ++ poi.setDirty(false); ++ if (save == null && this.lastPoiSaveNull) { ++ if (unloading) { ++ this.poiDataUnload.completable().complete(null); ++ } ++ return false; ++ } ++ ++ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.POI_DATA); ++ this.lastPoiSaveNull = save == null; ++ if (unloading) { ++ this.poiDataUnload.completable().complete(save); ++ } ++ } catch (final ThreadDeath death) { ++ throw death; ++ } catch (final Throwable thr) { ++ LOGGER.error("Failed to save poi data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "'"); ++ } ++ ++ return true; ++ } ++ ++ @Override ++ public String toString() { ++ final ChunkCompletion lastCompletion = this.lastChunkCompletion; ++ final ChunkEntitySlices entityChunk = this.entityChunk; ++ final long chunkStatus = this.chunkStatus; ++ final int fullChunkStatus = (int)chunkStatus; ++ final int pendingChunkStatus = (int)(chunkStatus >>> 32); ++ final FullChunkStatus currentFullStatus = fullChunkStatus < 0 || fullChunkStatus >= CHUNK_STATUS_BY_ID.length ? null : CHUNK_STATUS_BY_ID[fullChunkStatus]; ++ final FullChunkStatus pendingFullStatus = pendingChunkStatus < 0 || pendingChunkStatus >= CHUNK_STATUS_BY_ID.length ? null : CHUNK_STATUS_BY_ID[pendingChunkStatus]; ++ return "NewChunkHolder{" + ++ "world=" + this.world.getWorld().getName() + ++ ", chunkX=" + this.chunkX + ++ ", chunkZ=" + this.chunkZ + ++ ", entityChunkFromDisk=" + (entityChunk != null && !entityChunk.isTransient()) + ++ ", lastChunkCompletion={chunk_class=" + (lastCompletion == null || lastCompletion.chunk() == null ? "null" : lastCompletion.chunk().getClass().getName()) + ",status=" + (lastCompletion == null ? "null" : lastCompletion.genStatus()) + "}" + ++ ", currentGenStatus=" + this.currentGenStatus + ++ ", requestedGenStatus=" + this.requestedGenStatus + ++ ", generationTask=" + this.generationTask + ++ ", generationTaskStatus=" + this.generationTaskStatus + ++ ", priority=" + this.priority + ++ ", priorityLocked=" + this.priorityLocked + ++ ", neighbourRequestedPriority=" + this.neighbourRequestedPriority + ++ ", effective_priority=" + this.getEffectivePriority() + ++ ", oldTicketLevel=" + this.oldTicketLevel + ++ ", currentTicketLevel=" + this.currentTicketLevel + ++ ", totalNeighboursUsingThisChunk=" + this.totalNeighboursUsingThisChunk + ++ ", fullNeighbourChunksLoadedBitset=" + this.fullNeighbourChunksLoadedBitset + ++ ", chunkStatusRaw=" + chunkStatus + ++ ", currentChunkStatus=" + currentFullStatus + ++ ", pendingChunkStatus=" + pendingFullStatus + ++ ", is_unload_safe=" + this.isSafeToUnload() + ++ ", killed=" + this.killed + ++ '}'; ++ } ++ ++ private static JsonElement serializeCompletable(final Completable completable) { ++ if (completable == null) { ++ return new JsonPrimitive("null"); ++ } ++ ++ final JsonObject ret = new JsonObject(); ++ final boolean isCompleted = completable.isCompleted(); ++ ret.addProperty("completed", Boolean.valueOf(isCompleted)); ++ ++ if (isCompleted) { ++ ret.addProperty("completed_exceptionally", Boolean.valueOf(completable.getThrowable() != null)); ++ } ++ ++ return ret; ++ } ++ ++ // holds ticket and scheduling lock ++ public JsonObject getDebugJson() { ++ final JsonObject ret = new JsonObject(); ++ ++ final ChunkCompletion lastCompletion = this.lastChunkCompletion; ++ final ChunkEntitySlices slices = this.entityChunk; ++ final PoiChunk poiChunk = this.poiChunk; ++ ++ ret.addProperty("chunkX", Integer.valueOf(this.chunkX)); ++ ret.addProperty("chunkZ", Integer.valueOf(this.chunkZ)); ++ ret.addProperty("entity_chunk", slices == null ? "null" : "transient=" + slices.isTransient()); ++ ret.addProperty("poi_chunk", "null=" + (poiChunk == null)); ++ ret.addProperty("completed_chunk_class", lastCompletion == null ? "null" : lastCompletion.chunk().getClass().getName()); ++ ret.addProperty("completed_gen_status", lastCompletion == null ? "null" : lastCompletion.genStatus().toString()); ++ ret.addProperty("priority", Objects.toString(this.priority)); ++ ret.addProperty("neighbour_requested_priority", Objects.toString(this.neighbourRequestedPriority)); ++ ret.addProperty("generation_task", Objects.toString(this.generationTask)); ++ ret.addProperty("is_safe_unload", Objects.toString(this.isSafeToUnload())); ++ ret.addProperty("old_ticket_level", Integer.valueOf(this.oldTicketLevel)); ++ ret.addProperty("current_ticket_level", Integer.valueOf(this.currentTicketLevel)); ++ ret.addProperty("neighbours_using_chunk", Integer.valueOf(this.totalNeighboursUsingThisChunk)); ++ ++ final JsonObject neighbourWaitState = new JsonObject(); ++ ret.add("neighbour_state", neighbourWaitState); ++ ++ final JsonArray blockingGenNeighbours = new JsonArray(); ++ neighbourWaitState.add("blocking_gen_task", blockingGenNeighbours); ++ for (final NewChunkHolder blockingGenNeighbour : this.neighboursBlockingGenTask) { ++ final JsonObject neighbour = new JsonObject(); ++ blockingGenNeighbours.add(neighbour); ++ ++ neighbour.addProperty("chunkX", Integer.valueOf(blockingGenNeighbour.chunkX)); ++ neighbour.addProperty("chunkZ", Integer.valueOf(blockingGenNeighbour.chunkZ)); ++ } ++ ++ final JsonArray neighboursWaitingForUs = new JsonArray(); ++ neighbourWaitState.add("neighbours_waiting_on_us", neighboursWaitingForUs); ++ for (final Reference2ObjectMap.Entry entry : this.neighboursWaitingForUs.reference2ObjectEntrySet()) { ++ final NewChunkHolder holder = entry.getKey(); ++ final ChunkStatus status = entry.getValue(); ++ ++ final JsonObject neighbour = new JsonObject(); ++ neighboursWaitingForUs.add(neighbour); ++ ++ ++ neighbour.addProperty("chunkX", Integer.valueOf(holder.chunkX)); ++ neighbour.addProperty("chunkZ", Integer.valueOf(holder.chunkZ)); ++ neighbour.addProperty("waiting_for", Objects.toString(status)); ++ } ++ ++ ret.addProperty("fullchunkstatus", Objects.toString(this.getChunkStatus())); ++ ret.addProperty("fullchunkstatus_raw", Long.valueOf(this.chunkStatus)); ++ ret.addProperty("generation_task", Objects.toString(this.generationTask)); ++ ret.addProperty("requested_generation", Objects.toString(this.requestedGenStatus)); ++ ret.addProperty("has_entity_load_task", Boolean.valueOf(this.entityDataLoadTask != null)); ++ ret.addProperty("has_poi_load_task", Boolean.valueOf(this.poiDataLoadTask != null)); ++ ++ final UnloadTask entityDataUnload = this.entityDataUnload; ++ final UnloadTask poiDataUnload = this.poiDataUnload; ++ final UnloadTask chunkDataUnload = this.chunkDataUnload; ++ ++ ret.add("entity_unload_completable", serializeCompletable(entityDataUnload == null ? null : entityDataUnload.completable())); ++ ret.add("poi_unload_completable", serializeCompletable(poiDataUnload == null ? null : poiDataUnload.completable())); ++ ret.add("chunk_unload_completable", serializeCompletable(chunkDataUnload == null ? null : chunkDataUnload.completable())); ++ ++ final DelayedPrioritisedTask unloadTask = chunkDataUnload == null ? null : chunkDataUnload.task(); ++ if (unloadTask == null) { ++ ret.addProperty("unload_task_priority", "null"); ++ ret.addProperty("unload_task_priority_raw", "null"); ++ } else { ++ ret.addProperty("unload_task_priority", Objects.toString(unloadTask.getPriority())); ++ ret.addProperty("unload_task_priority_raw", Integer.valueOf(unloadTask.getPriorityInternal())); ++ } ++ ++ ret.addProperty("killed", Boolean.valueOf(this.killed)); ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/PriorityHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/PriorityHolder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b4c56bf12dc8dd17452210ece4fd67411cc6b2fd +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/PriorityHolder.java +@@ -0,0 +1,215 @@ ++package io.papermc.paper.chunk.system.scheduling; ++ ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import java.lang.invoke.VarHandle; ++ ++public abstract class PriorityHolder { ++ ++ protected volatile int priority; ++ protected static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(PriorityHolder.class, "priority", int.class); ++ ++ protected static final int PRIORITY_SCHEDULED = Integer.MIN_VALUE >>> 0; ++ protected static final int PRIORITY_EXECUTED = Integer.MIN_VALUE >>> 1; ++ ++ protected final int getPriorityVolatile() { ++ return (int)PRIORITY_HANDLE.getVolatile((PriorityHolder)this); ++ } ++ ++ protected final int compareAndExchangePriorityVolatile(final int expect, final int update) { ++ return (int)PRIORITY_HANDLE.compareAndExchange((PriorityHolder)this, (int)expect, (int)update); ++ } ++ ++ protected final int getAndOrPriorityVolatile(final int val) { ++ return (int)PRIORITY_HANDLE.getAndBitwiseOr((PriorityHolder)this, (int)val); ++ } ++ ++ protected final void setPriorityPlain(final int val) { ++ PRIORITY_HANDLE.set((PriorityHolder)this, (int)val); ++ } ++ ++ protected PriorityHolder(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ this.setPriorityPlain(priority.priority); ++ } ++ ++ // used only for debug json ++ public boolean isScheduled() { ++ return (this.getPriorityVolatile() & PRIORITY_SCHEDULED) != 0; ++ } ++ ++ // returns false if cancelled ++ protected boolean markExecuting() { ++ return (this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) == 0; ++ } ++ ++ protected boolean isMarkedExecuted() { ++ return (this.getPriorityVolatile() & PRIORITY_EXECUTED) != 0; ++ } ++ ++ public void cancel() { ++ if ((this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) != 0) { ++ // cancelled already ++ return; ++ } ++ this.cancelScheduled(); ++ } ++ ++ public void schedule() { ++ int priority = this.getPriorityVolatile(); ++ ++ if ((priority & PRIORITY_SCHEDULED) != 0) { ++ throw new IllegalStateException("schedule() called twice"); ++ } ++ ++ if ((priority & PRIORITY_EXECUTED) != 0) { ++ // cancelled ++ return; ++ } ++ ++ this.scheduleTask(PrioritisedExecutor.Priority.getPriority(priority)); ++ ++ int failures = 0; ++ for (;;) { ++ if (priority == (priority = this.compareAndExchangePriorityVolatile(priority, priority | PRIORITY_SCHEDULED))) { ++ return; ++ } ++ ++ if ((priority & PRIORITY_SCHEDULED) != 0) { ++ throw new IllegalStateException("schedule() called twice"); ++ } ++ ++ if ((priority & PRIORITY_EXECUTED) != 0) { ++ // cancelled or executed ++ return; ++ } ++ ++ this.setPriorityScheduled(PrioritisedExecutor.Priority.getPriority(priority)); ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ public final PrioritisedExecutor.Priority getPriority() { ++ final int ret = this.getPriorityVolatile(); ++ if ((ret & PRIORITY_EXECUTED) != 0) { ++ return PrioritisedExecutor.Priority.COMPLETING; ++ } ++ if ((ret & PRIORITY_SCHEDULED) != 0) { ++ return this.getScheduledPriority(); ++ } ++ return PrioritisedExecutor.Priority.getPriority(ret); ++ } ++ ++ public final void lowerPriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ ++ int failures = 0; ++ for (int curr = this.getPriorityVolatile();;) { ++ if ((curr & PRIORITY_EXECUTED) != 0) { ++ return; ++ } ++ ++ if ((curr & PRIORITY_SCHEDULED) != 0) { ++ this.lowerPriorityScheduled(priority); ++ return; ++ } ++ ++ if (!priority.isLowerPriority(curr)) { ++ return; ++ } ++ ++ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { ++ return; ++ } ++ ++ // failed, retry ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ public final void setPriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ ++ int failures = 0; ++ for (int curr = this.getPriorityVolatile();;) { ++ if ((curr & PRIORITY_EXECUTED) != 0) { ++ return; ++ } ++ ++ if ((curr & PRIORITY_SCHEDULED) != 0) { ++ this.setPriorityScheduled(priority); ++ return; ++ } ++ ++ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { ++ return; ++ } ++ ++ // failed, retry ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ public final void raisePriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ ++ int failures = 0; ++ for (int curr = this.getPriorityVolatile();;) { ++ if ((curr & PRIORITY_EXECUTED) != 0) { ++ return; ++ } ++ ++ if ((curr & PRIORITY_SCHEDULED) != 0) { ++ this.raisePriorityScheduled(priority); ++ return; ++ } ++ ++ if (!priority.isHigherPriority(curr)) { ++ return; ++ } ++ ++ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { ++ return; ++ } ++ ++ // failed, retry ++ ++ ++failures; ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ } ++ } ++ ++ protected abstract void cancelScheduled(); ++ ++ protected abstract PrioritisedExecutor.Priority getScheduledPriority(); ++ ++ protected abstract void scheduleTask(final PrioritisedExecutor.Priority priority); ++ ++ protected abstract void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority); ++ ++ protected abstract void setPriorityScheduled(final PrioritisedExecutor.Priority priority); ++ ++ protected abstract void raisePriorityScheduled(final PrioritisedExecutor.Priority priority); ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ThreadedTicketLevelPropagator.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ThreadedTicketLevelPropagator.java +new file mode 100644 +index 0000000000000000000000000000000000000000..287240ed3b440f2f5733c368416e4276f626405d +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ThreadedTicketLevelPropagator.java +@@ -0,0 +1,1477 @@ ++package io.papermc.paper.chunk.system.scheduling; ++ ++import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; ++import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import it.unimi.dsi.fastutil.HashCommon; ++import it.unimi.dsi.fastutil.longs.Long2ByteLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.shorts.Short2ByteLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.shorts.Short2ByteMap; ++import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; ++import java.lang.invoke.VarHandle; ++import java.util.ArrayDeque; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.List; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.locks.LockSupport; ++ ++public abstract class ThreadedTicketLevelPropagator { ++ ++ // sections are 64 in length ++ public static final int SECTION_SHIFT = 6; ++ public static final int SECTION_SIZE = 1 << SECTION_SHIFT; ++ private static final int LEVEL_BITS = SECTION_SHIFT; ++ private static final int LEVEL_COUNT = 1 << LEVEL_BITS; ++ private static final int MIN_SOURCE_LEVEL = 1; ++ // we limit the max source to 62 because the depropagation code _must_ attempt to depropagate ++ // a 1 level to 0; and if a source was 63 then it may cross more than 2 sections in depropagation ++ private static final int MAX_SOURCE_LEVEL = 62; ++ ++ private final UpdateQueue updateQueue; ++ private final ConcurrentHashMap sections = new ConcurrentHashMap<>(); ++ ++ public ThreadedTicketLevelPropagator() { ++ this.updateQueue = new UpdateQueue(); ++ } ++ ++ // must hold ticket lock for: ++ // (posX & ~(SECTION_SIZE - 1), posZ & ~(SECTION_SIZE - 1)) to (posX | (SECTION_SIZE - 1), posZ | (SECTION_SIZE - 1)) ++ public void setSource(final int posX, final int posZ, final int to) { ++ if (to < 1 || to > MAX_SOURCE_LEVEL) { ++ throw new IllegalArgumentException("Source: " + to); ++ } ++ ++ final int sectionX = posX >> SECTION_SHIFT; ++ final int sectionZ = posZ >> SECTION_SHIFT; ++ ++ final Coordinate coordinate = new Coordinate(sectionX, sectionZ); ++ Section section = this.sections.get(coordinate); ++ if (section == null) { ++ if (null != this.sections.putIfAbsent(coordinate, section = new Section(sectionX, sectionZ))) { ++ throw new IllegalStateException("Race condition while creating new section"); ++ } ++ } ++ ++ final int localIdx = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); ++ final short sLocalIdx = (short)localIdx; ++ ++ final short sourceAndLevel = section.levels[localIdx]; ++ final int currentSource = (sourceAndLevel >>> 8) & 0xFF; ++ ++ if (currentSource == to) { ++ // nothing to do ++ // make sure to kill the current update, if any ++ section.queuedSources.replace(sLocalIdx, (byte)to); ++ return; ++ } ++ ++ if (section.queuedSources.put(sLocalIdx, (byte)to) == Section.NO_QUEUED_UPDATE && section.queuedSources.size() == 1) { ++ this.queueSectionUpdate(section); ++ } ++ } ++ ++ // must hold ticket lock for: ++ // (posX & ~(SECTION_SIZE - 1), posZ & ~(SECTION_SIZE - 1)) to (posX | (SECTION_SIZE - 1), posZ | (SECTION_SIZE - 1)) ++ public void removeSource(final int posX, final int posZ) { ++ final int sectionX = posX >> SECTION_SHIFT; ++ final int sectionZ = posZ >> SECTION_SHIFT; ++ ++ final Coordinate coordinate = new Coordinate(sectionX, sectionZ); ++ final Section section = this.sections.get(coordinate); ++ ++ if (section == null) { ++ return; ++ } ++ ++ final int localIdx = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); ++ final short sLocalIdx = (short)localIdx; ++ ++ final int currentSource = (section.levels[localIdx] >>> 8) & 0xFF; ++ ++ if (currentSource == 0) { ++ // we use replace here so that we do not possibly multi-queue a section for an update ++ section.queuedSources.replace(sLocalIdx, (byte)0); ++ return; ++ } ++ ++ if (section.queuedSources.put(sLocalIdx, (byte)0) == Section.NO_QUEUED_UPDATE && section.queuedSources.size() == 1) { ++ this.queueSectionUpdate(section); ++ } ++ } ++ ++ private void queueSectionUpdate(final Section section) { ++ this.updateQueue.append(new UpdateQueue.UpdateQueueNode(section, null)); ++ } ++ ++ public boolean hasPendingUpdates() { ++ return !this.updateQueue.isEmpty(); ++ } ++ ++ // holds ticket lock for every chunk section represented by any position in the key set ++ // updates is modifiable and passed to processSchedulingUpdates after this call ++ protected abstract void processLevelUpdates(final Long2ByteLinkedOpenHashMap updates); ++ ++ // holds ticket lock for every chunk section represented by any position in the key set ++ // holds scheduling lock in max access radius for every position held by the ticket lock ++ // updates is cleared after this call ++ protected abstract void processSchedulingUpdates(final Long2ByteLinkedOpenHashMap updates, final List scheduledTasks, ++ final List changedFullStatus); ++ ++ // must hold ticket lock for every position in the sections in one radius around sectionX,sectionZ ++ public boolean performUpdate(final int sectionX, final int sectionZ, final ReentrantAreaLock schedulingLock, ++ final List scheduledTasks, final List changedFullStatus) { ++ if (!this.hasPendingUpdates()) { ++ return false; ++ } ++ ++ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); ++ final Section section = this.sections.get(coordinate); ++ ++ if (section == null || section.queuedSources.isEmpty()) { ++ // no section or no updates ++ return false; ++ } ++ ++ final Propagator propagator = Propagator.acquirePropagator(); ++ final boolean ret = this.performUpdate(section, null, propagator, ++ null, schedulingLock, scheduledTasks, changedFullStatus ++ ); ++ Propagator.returnPropagator(propagator); ++ return ret; ++ } ++ ++ private boolean performUpdate(final Section section, final UpdateQueue.UpdateQueueNode node, final Propagator propagator, ++ final ReentrantAreaLock ticketLock, final ReentrantAreaLock schedulingLock, ++ final List scheduledTasks, final List changedFullStatus) { ++ final int sectionX = section.sectionX; ++ final int sectionZ = section.sectionZ; ++ ++ final int rad1MinX = (sectionX - 1) << SECTION_SHIFT; ++ final int rad1MinZ = (sectionZ - 1) << SECTION_SHIFT; ++ final int rad1MaxX = ((sectionX + 1) << SECTION_SHIFT) | (SECTION_SIZE - 1); ++ final int rad1MaxZ = ((sectionZ + 1) << SECTION_SHIFT) | (SECTION_SIZE - 1); ++ ++ // set up encode offset first as we need to queue level changes _before_ ++ propagator.setupEncodeOffset(sectionX, sectionZ); ++ ++ final int coordinateOffset = propagator.coordinateOffset; ++ ++ final ReentrantAreaLock.Node ticketNode = ticketLock == null ? null : ticketLock.lock(rad1MinX, rad1MinZ, rad1MaxX, rad1MaxZ); ++ final boolean ret; ++ try { ++ // first, check if this update was stolen ++ if (section != this.sections.get(new Coordinate(sectionX, sectionZ))) { ++ // occurs when a stolen update deletes this section ++ // it is possible that another update is scheduled, but that one will have the correct section ++ if (node != null) { ++ this.updateQueue.remove(node); ++ } ++ return false; ++ } ++ ++ final int oldSourceSize = section.sources.size(); ++ ++ // process pending sources ++ for (final Iterator iterator = section.queuedSources.short2ByteEntrySet().fastIterator(); iterator.hasNext();) { ++ final Short2ByteMap.Entry entry = iterator.next(); ++ final int pos = (int)entry.getShortKey(); ++ final int posX = (pos & (SECTION_SIZE - 1)) | (sectionX << SECTION_SHIFT); ++ final int posZ = ((pos >> SECTION_SHIFT) & (SECTION_SIZE - 1)) | (sectionZ << SECTION_SHIFT); ++ final int newSource = (int)entry.getByteValue(); ++ ++ final short currentEncoded = section.levels[pos]; ++ final int currLevel = currentEncoded & 0xFF; ++ final int prevSource = (currentEncoded >>> 8) & 0xFF; ++ ++ if (prevSource == newSource) { ++ // nothing changed ++ continue; ++ } ++ ++ if ((prevSource < currLevel && newSource <= currLevel) || newSource == currLevel) { ++ // just update the source, don't need to propagate change ++ section.levels[pos] = (short)(currLevel | (newSource << 8)); ++ // level is unchanged, don't add to changed positions ++ } else { ++ // set current level and current source to new source ++ section.levels[pos] = (short)(newSource | (newSource << 8)); ++ // must add to updated positions in case this is final ++ propagator.updatedPositions.put(Coordinate.key(posX, posZ), (byte)newSource); ++ if (newSource != 0) { ++ // queue increase with new source level ++ propagator.appendToIncreaseQueue( ++ ((long)(posX + (posZ << Propagator.COORDINATE_BITS) + coordinateOffset) & ((1L << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) - 1)) | ++ ((newSource & (LEVEL_COUNT - 1L)) << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) | ++ (Propagator.ALL_DIRECTIONS_BITSET << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS + LEVEL_BITS)) ++ ); ++ } ++ // queue decrease with previous level ++ if (newSource < currLevel) { ++ propagator.appendToDecreaseQueue( ++ ((long)(posX + (posZ << Propagator.COORDINATE_BITS) + coordinateOffset) & ((1L << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) - 1)) | ++ ((currLevel & (LEVEL_COUNT - 1L)) << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) | ++ (Propagator.ALL_DIRECTIONS_BITSET << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS + LEVEL_BITS)) ++ ); ++ } ++ } ++ ++ if (newSource == 0) { ++ // prevSource != newSource, so we are removing this source ++ section.sources.remove((short)pos); ++ } else if (prevSource == 0) { ++ // prevSource != newSource, so we are adding this source ++ section.sources.add((short)pos); ++ } ++ } ++ ++ section.queuedSources.clear(); ++ ++ final int newSourceSize = section.sources.size(); ++ ++ if (oldSourceSize == 0 && newSourceSize != 0) { ++ // need to make sure the sections in 1 radius are initialised ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ if ((dx | dz) == 0) { ++ continue; ++ } ++ final int offX = dx + sectionX; ++ final int offZ = dz + sectionZ; ++ final Coordinate coordinate = new Coordinate(offX, offZ); ++ final Section neighbour = this.sections.computeIfAbsent(coordinate, (final Coordinate keyInMap) -> { ++ return new Section(Coordinate.x(keyInMap.key), Coordinate.z(keyInMap.key)); ++ }); ++ ++ // increase ref count ++ ++neighbour.oneRadNeighboursWithSources; ++ if (neighbour.oneRadNeighboursWithSources <= 0 || neighbour.oneRadNeighboursWithSources > 8) { ++ throw new IllegalStateException(Integer.toString(neighbour.oneRadNeighboursWithSources)); ++ } ++ } ++ } ++ } ++ ++ if (propagator.hasUpdates()) { ++ propagator.setupCaches(this, sectionX, sectionZ, 1); ++ propagator.performDecrease(); ++ // don't need try-finally, as any exception will cause the propagator to not be returned ++ propagator.destroyCaches(); ++ } ++ ++ if (newSourceSize == 0) { ++ final boolean decrementRef = oldSourceSize != 0; ++ // check for section de-init ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ final int offX = dx + sectionX; ++ final int offZ = dz + sectionZ; ++ final Coordinate coordinate = new Coordinate(offX, offZ); ++ final Section neighbour = this.sections.get(coordinate); ++ ++ if (neighbour == null) { ++ if (oldSourceSize == 0 && (dx | dz) != 0) { ++ // since we don't have sources, this section is allowed to null ++ continue; ++ } ++ throw new IllegalStateException("??"); ++ } ++ ++ if (decrementRef && (dx | dz) != 0) { ++ // decrease ref count, but only for neighbours ++ --neighbour.oneRadNeighboursWithSources; ++ } ++ ++ // we need to check the current section for de-init as well ++ if (neighbour.oneRadNeighboursWithSources == 0) { ++ if (neighbour.queuedSources.isEmpty() && neighbour.sources.isEmpty()) { ++ // need to de-init ++ this.sections.remove(coordinate); ++ } // else: neighbour is queued for an update, and it will de-init itself ++ } else if (neighbour.oneRadNeighboursWithSources < 0 || neighbour.oneRadNeighboursWithSources > 8) { ++ throw new IllegalStateException(Integer.toString(neighbour.oneRadNeighboursWithSources)); ++ } ++ } ++ } ++ } ++ ++ ++ ret = !propagator.updatedPositions.isEmpty(); ++ ++ if (ret) { ++ this.processLevelUpdates(propagator.updatedPositions); ++ ++ if (!propagator.updatedPositions.isEmpty()) { ++ // now we can actually update the ticket levels in the chunk holders ++ final int maxScheduleRadius = 2 * ChunkTaskScheduler.getMaxAccessRadius(); ++ ++ // allow the chunkholders to process ticket level updates without needing to acquire the schedule lock every time ++ final ReentrantAreaLock.Node schedulingNode = schedulingLock.lock( ++ rad1MinX - maxScheduleRadius, rad1MinZ - maxScheduleRadius, ++ rad1MaxX + maxScheduleRadius, rad1MaxZ + maxScheduleRadius ++ ); ++ try { ++ this.processSchedulingUpdates(propagator.updatedPositions, scheduledTasks, changedFullStatus); ++ } finally { ++ schedulingLock.unlock(schedulingNode); ++ } ++ } ++ ++ propagator.updatedPositions.clear(); ++ } ++ } finally { ++ if (ticketLock != null) { ++ ticketLock.unlock(ticketNode); ++ } ++ } ++ ++ // finished ++ if (node != null) { ++ this.updateQueue.remove(node); ++ } ++ ++ return ret; ++ } ++ ++ public boolean performUpdates(final ReentrantAreaLock ticketLock, final ReentrantAreaLock schedulingLock, ++ final List scheduledTasks, final List changedFullStatus) { ++ if (this.updateQueue.isEmpty()) { ++ return false; ++ } ++ ++ final long maxOrder = this.updateQueue.getLastOrder(); ++ ++ boolean updated = false; ++ Propagator propagator = null; ++ ++ for (;;) { ++ final UpdateQueue.UpdateQueueNode toUpdate = this.updateQueue.acquireNextToUpdate(maxOrder); ++ if (toUpdate == null) { ++ this.updateQueue.awaitFirst(maxOrder); ++ ++ if (!this.updateQueue.hasRemainingUpdates(maxOrder)) { ++ if (propagator != null) { ++ Propagator.returnPropagator(propagator); ++ } ++ return updated; ++ } ++ ++ continue; ++ } ++ ++ if (propagator == null) { ++ propagator = Propagator.acquirePropagator(); ++ } ++ ++ updated |= this.performUpdate(toUpdate.section, toUpdate, propagator, ticketLock, schedulingLock, scheduledTasks, changedFullStatus); ++ } ++ } ++ ++ private static final class UpdateQueue { ++ ++ private volatile UpdateQueueNode head; ++ private volatile UpdateQueueNode tail; ++ private volatile UpdateQueueNode lastUpdating; ++ ++ protected static final VarHandle HEAD_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueue.class, "head", UpdateQueueNode.class); ++ protected static final VarHandle TAIL_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueue.class, "tail", UpdateQueueNode.class); ++ protected static final VarHandle LAST_UPDATING = ConcurrentUtil.getVarHandle(UpdateQueue.class, "lastUpdating", UpdateQueueNode.class); ++ ++ /* head */ ++ ++ protected final void setHeadPlain(final UpdateQueueNode newHead) { ++ HEAD_HANDLE.set(this, newHead); ++ } ++ ++ protected final void setHeadOpaque(final UpdateQueueNode newHead) { ++ HEAD_HANDLE.setOpaque(this, newHead); ++ } ++ ++ protected final UpdateQueueNode getHeadPlain() { ++ return (UpdateQueueNode)HEAD_HANDLE.get(this); ++ } ++ ++ protected final UpdateQueueNode getHeadOpaque() { ++ return (UpdateQueueNode)HEAD_HANDLE.getOpaque(this); ++ } ++ ++ protected final UpdateQueueNode getHeadAcquire() { ++ return (UpdateQueueNode)HEAD_HANDLE.getAcquire(this); ++ } ++ ++ /* tail */ ++ ++ protected final void setTailPlain(final UpdateQueueNode newTail) { ++ TAIL_HANDLE.set(this, newTail); ++ } ++ ++ protected final void setTailOpaque(final UpdateQueueNode newTail) { ++ TAIL_HANDLE.setOpaque(this, newTail); ++ } ++ ++ protected final UpdateQueueNode getTailPlain() { ++ return (UpdateQueueNode)TAIL_HANDLE.get(this); ++ } ++ ++ protected final UpdateQueueNode getTailOpaque() { ++ return (UpdateQueueNode)TAIL_HANDLE.getOpaque(this); ++ } ++ ++ /* lastUpdating */ ++ ++ protected final UpdateQueueNode getLastUpdatingVolatile() { ++ return (UpdateQueueNode)LAST_UPDATING.getVolatile(this); ++ } ++ ++ protected final UpdateQueueNode compareAndExchangeLastUpdatingVolatile(final UpdateQueueNode expect, final UpdateQueueNode update) { ++ return (UpdateQueueNode)LAST_UPDATING.compareAndExchange(this, expect, update); ++ } ++ ++ public UpdateQueue() { ++ final UpdateQueueNode dummy = new UpdateQueueNode(null, null); ++ dummy.order = -1L; ++ dummy.preventAdds(); ++ ++ this.setHeadPlain(dummy); ++ this.setTailPlain(dummy); ++ } ++ ++ public boolean isEmpty() { ++ return this.peek() == null; ++ } ++ ++ public boolean hasRemainingUpdates(final long maxUpdate) { ++ final UpdateQueueNode node = this.peek(); ++ return node != null && node.order <= maxUpdate; ++ } ++ ++ public long getLastOrder() { ++ for (UpdateQueueNode tail = this.getTailOpaque(), curr = tail;;) { ++ final UpdateQueueNode next = curr.getNextVolatile(); ++ if (next == null) { ++ // try to update stale tail ++ if (this.getTailOpaque() == tail && curr != tail) { ++ this.setTailOpaque(curr); ++ } ++ return curr.order; ++ } ++ curr = next; ++ } ++ } ++ ++ public UpdateQueueNode acquireNextToUpdate(final long maxOrder) { ++ int failures = 0; ++ for (UpdateQueueNode prev = this.getLastUpdatingVolatile();;) { ++ UpdateQueueNode next = prev == null ? this.peek() : prev.next; ++ ++ if (next == null || next.order > maxOrder) { ++ return null; ++ } ++ ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ ++ if (prev == (prev = this.compareAndExchangeLastUpdatingVolatile(prev, next))) { ++ return next; ++ } ++ ++ ++failures; ++ } ++ } ++ ++ public void awaitFirst(final long maxOrder) { ++ final UpdateQueueNode earliest = this.peek(); ++ if (earliest == null || earliest.order > maxOrder) { ++ return; ++ } ++ ++ final Thread currThread = Thread.currentThread(); ++ // we do not use add-blocking because we use the nullability of the section to block ++ // remove() does not begin to poll from the wait queue until the section is null'd, ++ // and so provided we check the nullability before parking there is no ordering of these operations ++ // such that remove() finishes polling from the wait queue while section is not null ++ earliest.add(currThread); ++ ++ // wait until completed ++ while (earliest.getSectionVolatile() != null) { ++ LockSupport.park(); ++ } ++ } ++ ++ public UpdateQueueNode peek() { ++ for (UpdateQueueNode head = this.getHeadOpaque(), curr = head;;) { ++ final UpdateQueueNode next = curr.getNextVolatile(); ++ final Section element = curr.getSectionVolatile(); /* Likely in sync */ ++ ++ if (element != null) { ++ if (this.getHeadOpaque() == head && curr != head) { ++ this.setHeadOpaque(curr); ++ } ++ return curr; ++ } ++ ++ if (next == null) { ++ if (this.getHeadOpaque() == head && curr != head) { ++ this.setHeadOpaque(curr); ++ } ++ return null; ++ } ++ curr = next; ++ } ++ } ++ ++ public void remove(final UpdateQueueNode node) { ++ // mark as removed ++ node.setSectionVolatile(null); ++ ++ // use peek to advance head ++ this.peek(); ++ ++ // unpark any waiters / block the wait queue ++ Thread unpark; ++ while ((unpark = node.poll()) != null) { ++ LockSupport.unpark(unpark); ++ } ++ } ++ ++ public void append(final UpdateQueueNode node) { ++ int failures = 0; ++ ++ for (UpdateQueueNode currTail = this.getTailOpaque(), curr = currTail;;) { ++ /* It has been experimentally shown that placing the read before the backoff results in significantly greater performance */ ++ /* It is likely due to a cache miss caused by another write to the next field */ ++ final UpdateQueueNode next = curr.getNextVolatile(); ++ ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } ++ ++ if (next == null) { ++ node.order = curr.order + 1L; ++ final UpdateQueueNode compared = curr.compareExchangeNextVolatile(null, node); ++ ++ if (compared == null) { ++ /* Added */ ++ /* Avoid CASing on tail more than we need to */ ++ /* CAS to avoid setting an out-of-date tail */ ++ if (this.getTailOpaque() == currTail) { ++ this.setTailOpaque(node); ++ } ++ return; ++ } ++ ++ ++failures; ++ curr = compared; ++ continue; ++ } ++ ++ if (curr == currTail) { ++ /* Tail is likely not up-to-date */ ++ curr = next; ++ } else { ++ /* Try to update to tail */ ++ if (currTail == (currTail = this.getTailOpaque())) { ++ curr = next; ++ } else { ++ curr = currTail; ++ } ++ } ++ } ++ } ++ ++ // each node also represents a set of waiters, represented by the MTQ ++ // if the queue is add-blocked, then the update is complete ++ private static final class UpdateQueueNode extends MultiThreadedQueue { ++ private long order; ++ private Section section; ++ private volatile UpdateQueueNode next; ++ ++ protected static final VarHandle SECTION_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueueNode.class, "section", Section.class); ++ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueueNode.class, "next", UpdateQueueNode.class); ++ ++ public UpdateQueueNode(final Section section, final UpdateQueueNode next) { ++ SECTION_HANDLE.set(this, section); ++ NEXT_HANDLE.set(this, next); ++ } ++ ++ /* section */ ++ ++ protected final Section getSectionPlain() { ++ return (Section)SECTION_HANDLE.get(this); ++ } ++ ++ protected final Section getSectionVolatile() { ++ return (Section)SECTION_HANDLE.getVolatile(this); ++ } ++ ++ protected final void setSectionPlain(final Section update) { ++ SECTION_HANDLE.set(this, update); ++ } ++ ++ protected final void setSectionOpaque(final Section update) { ++ SECTION_HANDLE.setOpaque(this, update); ++ } ++ ++ protected final void setSectionVolatile(final Section update) { ++ SECTION_HANDLE.setVolatile(this, update); ++ } ++ ++ protected final Section getAndSetSectionVolatile(final Section update) { ++ return (Section)SECTION_HANDLE.getAndSet(this, update); ++ } ++ ++ protected final Section compareExchangeSectionVolatile(final Section expect, final Section update) { ++ return (Section)SECTION_HANDLE.compareAndExchange(this, expect, update); ++ } ++ ++ /* next */ ++ ++ protected final UpdateQueueNode getNextPlain() { ++ return (UpdateQueueNode)NEXT_HANDLE.get(this); ++ } ++ ++ protected final UpdateQueueNode getNextOpaque() { ++ return (UpdateQueueNode)NEXT_HANDLE.getOpaque(this); ++ } ++ ++ protected final UpdateQueueNode getNextAcquire() { ++ return (UpdateQueueNode)NEXT_HANDLE.getAcquire(this); ++ } ++ ++ protected final UpdateQueueNode getNextVolatile() { ++ return (UpdateQueueNode)NEXT_HANDLE.getVolatile(this); ++ } ++ ++ protected final void setNextPlain(final UpdateQueueNode next) { ++ NEXT_HANDLE.set(this, next); ++ } ++ ++ protected final void setNextVolatile(final UpdateQueueNode next) { ++ NEXT_HANDLE.setVolatile(this, next); ++ } ++ ++ protected final UpdateQueueNode compareExchangeNextVolatile(final UpdateQueueNode expect, final UpdateQueueNode set) { ++ return (UpdateQueueNode)NEXT_HANDLE.compareAndExchange(this, expect, set); ++ } ++ } ++ } ++ ++ private static final class Section { ++ ++ // upper 8 bits: sources, lower 8 bits: level ++ // if we REALLY wanted to get crazy, we could make the increase propagator use MethodHandles#byteArrayViewVarHandle ++ // to read and write the lower 8 bits of this array directly rather than reading, updating the bits, then writing back. ++ private final short[] levels = new short[SECTION_SIZE * SECTION_SIZE]; ++ // set of local positions that represent sources ++ private final ShortOpenHashSet sources = new ShortOpenHashSet(); ++ // map of local index to new source level ++ // the source level _cannot_ be updated in the backing storage immediately since the update ++ private static final byte NO_QUEUED_UPDATE = (byte)-1; ++ private final Short2ByteLinkedOpenHashMap queuedSources = new Short2ByteLinkedOpenHashMap(); ++ { ++ this.queuedSources.defaultReturnValue(NO_QUEUED_UPDATE); ++ } ++ private int oneRadNeighboursWithSources = 0; ++ ++ public final int sectionX; ++ public final int sectionZ; ++ ++ public Section(final int sectionX, final int sectionZ) { ++ this.sectionX = sectionX; ++ this.sectionZ = sectionZ; ++ } ++ ++ public boolean isZero() { ++ for (final short val : this.levels) { ++ if (val != 0) { ++ return false; ++ } ++ } ++ return true; ++ } ++ ++ @Override ++ public String toString() { ++ final StringBuilder ret = new StringBuilder(); ++ ++ for (int x = 0; x < SECTION_SIZE; ++x) { ++ ret.append("levels x=").append(x).append("\n"); ++ for (int z = 0; z < SECTION_SIZE; ++z) { ++ final short v = this.levels[x | (z << SECTION_SHIFT)]; ++ ret.append(v & 0xFF).append("."); ++ } ++ ret.append("\n"); ++ ret.append("sources x=").append(x).append("\n"); ++ for (int z = 0; z < SECTION_SIZE; ++z) { ++ final short v = this.levels[x | (z << SECTION_SHIFT)]; ++ ret.append((v >>> 8) & 0xFF).append("."); ++ } ++ ret.append("\n\n"); ++ } ++ ++ return ret.toString(); ++ } ++ } ++ ++ ++ private static final class Propagator { ++ ++ private static final ArrayDeque CACHED_PROPAGATORS = new ArrayDeque<>(); ++ private static final int MAX_PROPAGATORS = Runtime.getRuntime().availableProcessors() * 2; ++ ++ private static Propagator acquirePropagator() { ++ synchronized (CACHED_PROPAGATORS) { ++ final Propagator ret = CACHED_PROPAGATORS.pollFirst(); ++ if (ret != null) { ++ return ret; ++ } ++ } ++ return new Propagator(); ++ } ++ ++ private static void returnPropagator(final Propagator propagator) { ++ synchronized (CACHED_PROPAGATORS) { ++ if (CACHED_PROPAGATORS.size() < MAX_PROPAGATORS) { ++ CACHED_PROPAGATORS.add(propagator); ++ } ++ } ++ } ++ ++ private static final int SECTION_RADIUS = 2; ++ private static final int SECTION_CACHE_WIDTH = 2 * SECTION_RADIUS + 1; ++ // minimum number of bits to represent [0, SECTION_SIZE * SECTION_CACHE_WIDTH) ++ private static final int COORDINATE_BITS = 9; ++ private static final int COORDINATE_SIZE = 1 << COORDINATE_BITS; ++ static { ++ if ((SECTION_SIZE * SECTION_CACHE_WIDTH) > (1 << COORDINATE_BITS)) { ++ throw new IllegalStateException("Adjust COORDINATE_BITS"); ++ } ++ } ++ // index = x + (z * SECTION_CACHE_WIDTH) ++ // (this requires x >= 0 and z >= 0) ++ private final Section[] sections = new Section[SECTION_CACHE_WIDTH * SECTION_CACHE_WIDTH]; ++ ++ private int encodeOffsetX; ++ private int encodeOffsetZ; ++ ++ private int coordinateOffset; ++ ++ private int encodeSectionOffsetX; ++ private int encodeSectionOffsetZ; ++ ++ private int sectionIndexOffset; ++ ++ public final boolean hasUpdates() { ++ return this.decreaseQueueInitialLength != 0 || this.increaseQueueInitialLength != 0; ++ } ++ ++ protected final void setupEncodeOffset(final int centerSectionX, final int centerSectionZ) { ++ final int maxCoordinate = (SECTION_RADIUS * SECTION_SIZE - 1); ++ // must have that encoded >= 0 ++ // coordinates can range from [-maxCoordinate + centerSection*SECTION_SIZE, maxCoordinate + centerSection*SECTION_SIZE] ++ // we want a range of [0, maxCoordinate*2] ++ // so, 0 = -maxCoordinate + centerSection*SECTION_SIZE + offset ++ this.encodeOffsetX = maxCoordinate - (centerSectionX << SECTION_SHIFT); ++ this.encodeOffsetZ = maxCoordinate - (centerSectionZ << SECTION_SHIFT); ++ ++ // encoded coordinates range from [0, SECTION_SIZE * SECTION_CACHE_WIDTH) ++ // coordinate index = (x + encodeOffsetX) + ((z + encodeOffsetZ) << COORDINATE_BITS) ++ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << COORDINATE_BITS); ++ ++ // need encoded values to be >= 0 ++ // so, 0 = (-SECTION_RADIUS + centerSectionX) + encodeOffset ++ this.encodeSectionOffsetX = SECTION_RADIUS - centerSectionX; ++ this.encodeSectionOffsetZ = SECTION_RADIUS - centerSectionZ; ++ ++ // section index = (secX + encodeSectionOffsetX) + ((secZ + encodeSectionOffsetZ) * SECTION_CACHE_WIDTH) ++ this.sectionIndexOffset = this.encodeSectionOffsetX + (this.encodeSectionOffsetZ * SECTION_CACHE_WIDTH); ++ } ++ ++ // must hold ticket lock for (centerSectionX,centerSectionZ) in radius rad ++ // must call setupEncodeOffset ++ protected final void setupCaches(final ThreadedTicketLevelPropagator propagator, ++ final int centerSectionX, final int centerSectionZ, ++ final int rad) { ++ for (int dz = -rad; dz <= rad; ++dz) { ++ for (int dx = -rad; dx <= rad; ++dx) { ++ final int sectionX = centerSectionX + dx; ++ final int sectionZ = centerSectionZ + dz; ++ final Coordinate coordinate = new Coordinate(sectionX, sectionZ); ++ final Section section = propagator.sections.get(coordinate); ++ ++ if (section == null) { ++ throw new IllegalStateException("Section at " + coordinate + " should not be null"); ++ } ++ ++ this.setSectionInCache(sectionX, sectionZ, section); ++ } ++ } ++ } ++ ++ protected final void setSectionInCache(final int sectionX, final int sectionZ, final Section section) { ++ this.sections[sectionX + SECTION_CACHE_WIDTH*sectionZ + this.sectionIndexOffset] = section; ++ } ++ ++ protected final Section getSection(final int sectionX, final int sectionZ) { ++ return this.sections[sectionX + SECTION_CACHE_WIDTH*sectionZ + this.sectionIndexOffset]; ++ } ++ ++ protected final int getLevel(final int posX, final int posZ) { ++ final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH*(posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; ++ if (section != null) { ++ return (int)section.levels[(posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT)] & 0xFF; ++ } ++ ++ return 0; ++ } ++ ++ protected final void setLevel(final int posX, final int posZ, final int to) { ++ final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH*(posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; ++ if (section != null) { ++ final int index = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); ++ final short level = section.levels[index]; ++ section.levels[index] = (short)((level & ~0xFF) | (to & 0xFF)); ++ this.updatedPositions.put(Coordinate.key(posX, posZ), (byte)to); ++ } ++ } ++ ++ protected final void destroyCaches() { ++ Arrays.fill(this.sections, null); ++ } ++ ++ // contains: ++ // lower (COORDINATE_BITS(9) + COORDINATE_BITS(9) = 18) bits encoded position: (x | (z << COORDINATE_BITS)) ++ // next LEVEL_BITS (6) bits: propagated level [0, 63] ++ // propagation directions bitset (16 bits): ++ protected static final long ALL_DIRECTIONS_BITSET = ( ++ // z = -1 ++ (1L << ((1 - 1) | ((1 - 1) << 2))) | ++ (1L << ((1 + 0) | ((1 - 1) << 2))) | ++ (1L << ((1 + 1) | ((1 - 1) << 2))) | ++ ++ // z = 0 ++ (1L << ((1 - 1) | ((1 + 0) << 2))) | ++ //(1L << ((1 + 0) | ((1 + 0) << 2))) | // exclude (0,0) ++ (1L << ((1 + 1) | ((1 + 0) << 2))) | ++ ++ // z = 1 ++ (1L << ((1 - 1) | ((1 + 1) << 2))) | ++ (1L << ((1 + 0) | ((1 + 1) << 2))) | ++ (1L << ((1 + 1) | ((1 + 1) << 2))) ++ ); ++ ++ private void ex(int bitset) { ++ for (int i = 0, len = Integer.bitCount(bitset); i < len; ++i) { ++ final int set = Integer.numberOfTrailingZeros(bitset); ++ final int tailingBit = (-bitset) & bitset; ++ // XOR to remove the trailing bit ++ bitset ^= tailingBit; ++ ++ // the encoded value set is (x_val) | (z_val << 2), totaling 4 bits ++ // thus, the bitset is 16 bits wide where each one represents a direction to propagate and the ++ // index of the set bit is the encoded value ++ // the encoded coordinate has 3 valid states: ++ // 0b00 (0) -> -1 ++ // 0b01 (1) -> 0 ++ // 0b10 (2) -> 1 ++ // the decode operation then is val - 1, and the encode operation is val + 1 ++ final int xOff = (set & 3) - 1; ++ final int zOff = ((set >>> 2) & 3) - 1; ++ System.out.println("Encoded: (" + xOff + "," + zOff + ")"); ++ } ++ } ++ ++ private void ch(long bs, int shift) { ++ int bitset = (int)(bs >>> shift); ++ for (int i = 0, len = Integer.bitCount(bitset); i < len; ++i) { ++ final int set = Integer.numberOfTrailingZeros(bitset); ++ final int tailingBit = (-bitset) & bitset; ++ // XOR to remove the trailing bit ++ bitset ^= tailingBit; ++ ++ // the encoded value set is (x_val) | (z_val << 2), totaling 4 bits ++ // thus, the bitset is 16 bits wide where each one represents a direction to propagate and the ++ // index of the set bit is the encoded value ++ // the encoded coordinate has 3 valid states: ++ // 0b00 (0) -> -1 ++ // 0b01 (1) -> 0 ++ // 0b10 (2) -> 1 ++ // the decode operation then is val - 1, and the encode operation is val + 1 ++ final int xOff = (set & 3) - 1; ++ final int zOff = ((set >>> 2) & 3) - 1; ++ if (Math.abs(xOff) > 1 || Math.abs(zOff) > 1 || (xOff | zOff) == 0) { ++ throw new IllegalStateException(); ++ } ++ } ++ } ++ ++ // whether the increase propagator needs to write the propagated level to the position, used to avoid cascading ++ // updates for sources ++ protected static final long FLAG_WRITE_LEVEL = Long.MIN_VALUE >>> 1; ++ // whether the propagation needs to check if its current level is equal to the expected level ++ // used only in increase propagation ++ protected static final long FLAG_RECHECK_LEVEL = Long.MIN_VALUE >>> 0; ++ ++ protected long[] increaseQueue = new long[SECTION_SIZE * SECTION_SIZE * 2]; ++ protected int increaseQueueInitialLength; ++ protected long[] decreaseQueue = new long[SECTION_SIZE * SECTION_SIZE * 2]; ++ protected int decreaseQueueInitialLength; ++ ++ protected final Long2ByteLinkedOpenHashMap updatedPositions = new Long2ByteLinkedOpenHashMap(); ++ ++ protected final long[] resizeIncreaseQueue() { ++ return this.increaseQueue = Arrays.copyOf(this.increaseQueue, this.increaseQueue.length * 2); ++ } ++ ++ protected final long[] resizeDecreaseQueue() { ++ return this.decreaseQueue = Arrays.copyOf(this.decreaseQueue, this.decreaseQueue.length * 2); ++ } ++ ++ protected final void appendToIncreaseQueue(final long value) { ++ final int idx = this.increaseQueueInitialLength++; ++ long[] queue = this.increaseQueue; ++ if (idx >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ queue[idx] = value; ++ return; ++ } else { ++ queue[idx] = value; ++ return; ++ } ++ } ++ ++ protected final void appendToDecreaseQueue(final long value) { ++ final int idx = this.decreaseQueueInitialLength++; ++ long[] queue = this.decreaseQueue; ++ if (idx >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ queue[idx] = value; ++ return; ++ } else { ++ queue[idx] = value; ++ return; ++ } ++ } ++ ++ protected final void performIncrease() { ++ long[] queue = this.increaseQueue; ++ int queueReadIndex = 0; ++ int queueLength = this.increaseQueueInitialLength; ++ this.increaseQueueInitialLength = 0; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ final int encodeOffset = this.coordinateOffset; ++ final int sectionOffset = this.sectionIndexOffset; ++ ++ final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; ++ ++ while (queueReadIndex < queueLength) { ++ final long queueValue = queue[queueReadIndex++]; ++ ++ final int posX = ((int)queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; ++ final int propagatedLevel = ((int)queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); ++ // note: the above code requires coordinate bits * 2 < 32 ++ // bitset is 16 bits ++ int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); ++ ++ if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { ++ if (this.getLevel(posX, posZ) != propagatedLevel) { ++ // not at the level we expect, so something changed. ++ continue; ++ } ++ } else if ((queueValue & FLAG_WRITE_LEVEL) != 0L) { ++ // these are used to restore sources after a propagation decrease ++ this.setLevel(posX, posZ, propagatedLevel); ++ } ++ ++ // this bitset represents the values that we have not propagated to ++ // this bitset lets us determine what directions the neighbours we set should propagate to, in most cases ++ // significantly reducing the total number of ops ++ // since we propagate in a 1 radius, we need a 2 radius bitset to hold all possible values we would possibly need ++ // but if we use only 5x5 bits, then we need to use div/mod to retrieve coordinates from the bitset, so instead ++ // we use an 8x8 bitset and luckily that can be fit into only one long value (64 bits) ++ // to make things easy, we use positions [0, 4] in the bitset, with current position being 2 ++ // index = x | (z << 3) ++ ++ // to start, we eliminate everything 1 radius from the current position as the previous propagator ++ // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius ++ // but the rest not propagated are already handled ++ long currentPropagation = ~( ++ // z = -1 ++ (1L << ((2 - 1) | ((2 - 1) << 3))) | ++ (1L << ((2 + 0) | ((2 - 1) << 3))) | ++ (1L << ((2 + 1) | ((2 - 1) << 3))) | ++ ++ // z = 0 ++ (1L << ((2 - 1) | ((2 + 0) << 3))) | ++ (1L << ((2 + 0) | ((2 + 0) << 3))) | ++ (1L << ((2 + 1) | ((2 + 0) << 3))) | ++ ++ // z = 1 ++ (1L << ((2 - 1) | ((2 + 1) << 3))) | ++ (1L << ((2 + 0) | ((2 + 1) << 3))) | ++ (1L << ((2 + 1) | ((2 + 1) << 3))) ++ ); ++ ++ final int toPropagate = propagatedLevel - 1; ++ ++ // we could use while (propagateDirectionBitset != 0), but it's not a predictable branch. By counting ++ // the bits, the cpu loop predictor should perfectly predict the loop. ++ for (int l = 0, len = Integer.bitCount(propagateDirectionBitset); l < len; ++l) { ++ final int set = Integer.numberOfTrailingZeros(propagateDirectionBitset); ++ final int tailingBit = (-propagateDirectionBitset) & propagateDirectionBitset; ++ propagateDirectionBitset ^= tailingBit; ++ ++ // pDecode is from [0, 2], and 1 must be subtracted to fully decode the offset ++ // it has been split to save some cycles via parallelism ++ final int pDecodeX = (set & 3); ++ final int pDecodeZ = ((set >>> 2) & 3); ++ ++ // re-ordered -1 on the position decode into pos - 1 to occur in parallel with determining pDecodeX ++ final int offX = (posX - 1) + pDecodeX; ++ final int offZ = (posZ - 1) + pDecodeZ; ++ ++ final int sectionIndex = (offX >> SECTION_SHIFT) + ((offZ >> SECTION_SHIFT) * SECTION_CACHE_WIDTH) + sectionOffset; ++ final int localIndex = (offX & (SECTION_SIZE - 1)) | ((offZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); ++ ++ // to retrieve a set of bits from a long value: (n_bitmask << (nstartidx)) & bitset ++ // bitset idx = x | (z << 3) ++ ++ // read three bits, so we need 7L ++ // note that generally: off - pos = (pos - 1) + pDecode - pos = pDecode - 1 ++ // nstartidx1 = x rel -1 for z rel -1 ++ // = (offX - posX - 1 + 2) | ((offZ - posZ - 1 + 2) << 3) ++ // = (pDecodeX - 1 - 1 + 2) | ((pDecodeZ - 1 - 1 + 2) << 3) ++ // = pDecodeX | (pDecodeZ << 3) = start ++ final int start = pDecodeX | (pDecodeZ << 3); ++ final long bitsetLine1 = currentPropagation & (7L << (start)); ++ ++ // nstartidx2 = x rel -1 for z rel 0 = line after line1, so we can just add 8 (row length of bitset) ++ final long bitsetLine2 = currentPropagation & (7L << (start + 8)); ++ ++ // nstartidx2 = x rel -1 for z rel 0 = line after line2, so we can just add 8 (row length of bitset) ++ final long bitsetLine3 = currentPropagation & (7L << (start + (8 + 8))); ++ ++ // remove ("take") lines from bitset ++ currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); ++ ++ // now try to propagate ++ final Section section = this.sections[sectionIndex]; ++ ++ // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag ++ final short currentStoredLevel = section.levels[localIndex]; ++ final int currentLevel = currentStoredLevel & 0xFF; ++ ++ if (currentLevel >= toPropagate) { ++ continue; // already at the level we want ++ } ++ ++ // update level ++ section.levels[localIndex] = (short)((currentStoredLevel & ~0xFF) | (toPropagate & 0xFF)); ++ updatedPositions.putAndMoveToLast(Coordinate.key(offX, offZ), (byte)toPropagate); ++ ++ // queue next ++ if (toPropagate > 1) { ++ // now combine into one bitset to pass to child ++ // the child bitset is 4x4, so we just shift each line by 4 ++ // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value ++ final long childPropagation = ++ ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 ++ ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 ++ ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 ++ ++ // don't queue update if toPropagate cannot propagate anything to neighbours ++ // (for increase, propagating 0 to neighbours is useless) ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | ++ ((toPropagate & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | ++ childPropagation; //(ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); ++ continue; ++ } ++ continue; ++ } ++ } ++ } ++ ++ protected final void performDecrease() { ++ long[] queue = this.decreaseQueue; ++ long[] increaseQueue = this.increaseQueue; ++ int queueReadIndex = 0; ++ int queueLength = this.decreaseQueueInitialLength; ++ this.decreaseQueueInitialLength = 0; ++ int increaseQueueLength = this.increaseQueueInitialLength; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ final int encodeOffset = this.coordinateOffset; ++ final int sectionOffset = this.sectionIndexOffset; ++ ++ final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; ++ ++ while (queueReadIndex < queueLength) { ++ final long queueValue = queue[queueReadIndex++]; ++ ++ final int posX = ((int)queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; ++ final int propagatedLevel = ((int)queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); ++ // note: the above code requires coordinate bits * 2 < 32 ++ // bitset is 16 bits ++ int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); ++ ++ // this bitset represents the values that we have not propagated to ++ // this bitset lets us determine what directions the neighbours we set should propagate to, in most cases ++ // significantly reducing the total number of ops ++ // since we propagate in a 1 radius, we need a 2 radius bitset to hold all possible values we would possibly need ++ // but if we use only 5x5 bits, then we need to use div/mod to retrieve coordinates from the bitset, so instead ++ // we use an 8x8 bitset and luckily that can be fit into only one long value (64 bits) ++ // to make things easy, we use positions [0, 4] in the bitset, with current position being 2 ++ // index = x | (z << 3) ++ ++ // to start, we eliminate everything 1 radius from the current position as the previous propagator ++ // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius ++ // but the rest not propagated are already handled ++ long currentPropagation = ~( ++ // z = -1 ++ (1L << ((2 - 1) | ((2 - 1) << 3))) | ++ (1L << ((2 + 0) | ((2 - 1) << 3))) | ++ (1L << ((2 + 1) | ((2 - 1) << 3))) | ++ ++ // z = 0 ++ (1L << ((2 - 1) | ((2 + 0) << 3))) | ++ (1L << ((2 + 0) | ((2 + 0) << 3))) | ++ (1L << ((2 + 1) | ((2 + 0) << 3))) | ++ ++ // z = 1 ++ (1L << ((2 - 1) | ((2 + 1) << 3))) | ++ (1L << ((2 + 0) | ((2 + 1) << 3))) | ++ (1L << ((2 + 1) | ((2 + 1) << 3))) ++ ); ++ ++ final int toPropagate = propagatedLevel - 1; ++ ++ // we could use while (propagateDirectionBitset != 0), but it's not a predictable branch. By counting ++ // the bits, the cpu loop predictor should perfectly predict the loop. ++ for (int l = 0, len = Integer.bitCount(propagateDirectionBitset); l < len; ++l) { ++ final int set = Integer.numberOfTrailingZeros(propagateDirectionBitset); ++ final int tailingBit = (-propagateDirectionBitset) & propagateDirectionBitset; ++ propagateDirectionBitset ^= tailingBit; ++ ++ ++ // pDecode is from [0, 2], and 1 must be subtracted to fully decode the offset ++ // it has been split to save some cycles via parallelism ++ final int pDecodeX = (set & 3); ++ final int pDecodeZ = ((set >>> 2) & 3); ++ ++ // re-ordered -1 on the position decode into pos - 1 to occur in parallel with determining pDecodeX ++ final int offX = (posX - 1) + pDecodeX; ++ final int offZ = (posZ - 1) + pDecodeZ; ++ ++ final int sectionIndex = (offX >> SECTION_SHIFT) + ((offZ >> SECTION_SHIFT) * SECTION_CACHE_WIDTH) + sectionOffset; ++ final int localIndex = (offX & (SECTION_SIZE - 1)) | ((offZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); ++ ++ // to retrieve a set of bits from a long value: (n_bitmask << (nstartidx)) & bitset ++ // bitset idx = x | (z << 3) ++ ++ // read three bits, so we need 7L ++ // note that generally: off - pos = (pos - 1) + pDecode - pos = pDecode - 1 ++ // nstartidx1 = x rel -1 for z rel -1 ++ // = (offX - posX - 1 + 2) | ((offZ - posZ - 1 + 2) << 3) ++ // = (pDecodeX - 1 - 1 + 2) | ((pDecodeZ - 1 - 1 + 2) << 3) ++ // = pDecodeX | (pDecodeZ << 3) = start ++ final int start = pDecodeX | (pDecodeZ << 3); ++ final long bitsetLine1 = currentPropagation & (7L << (start)); ++ ++ // nstartidx2 = x rel -1 for z rel 0 = line after line1, so we can just add 8 (row length of bitset) ++ final long bitsetLine2 = currentPropagation & (7L << (start + 8)); ++ ++ // nstartidx2 = x rel -1 for z rel 0 = line after line2, so we can just add 8 (row length of bitset) ++ final long bitsetLine3 = currentPropagation & (7L << (start + (8 + 8))); ++ ++ // now try to propagate ++ final Section section = this.sections[sectionIndex]; ++ ++ // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag ++ final short currentStoredLevel = section.levels[localIndex]; ++ final int currentLevel = currentStoredLevel & 0xFF; ++ final int sourceLevel = (currentStoredLevel >>> 8) & 0xFF; ++ ++ if (currentLevel == 0) { ++ continue; // already at the level we want ++ } ++ ++ if (currentLevel > toPropagate) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | ++ ((currentLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | ++ (FLAG_RECHECK_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); ++ continue; ++ } ++ ++ // remove ("take") lines from bitset ++ // can't do this during decrease, TODO WHY? ++ //currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); ++ ++ // update level ++ section.levels[localIndex] = (short)((currentStoredLevel & ~0xFF)); ++ updatedPositions.putAndMoveToLast(Coordinate.key(offX, offZ), (byte)0); ++ ++ if (sourceLevel != 0) { ++ // re-propagate source ++ // note: do not set recheck level, or else the propagation will fail ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | ++ ((sourceLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | ++ (FLAG_WRITE_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); ++ } ++ ++ // queue next ++ // note: targetLevel > 0 here, since toPropagate >= currentLevel and currentLevel > 0 ++ // now combine into one bitset to pass to child ++ // the child bitset is 4x4, so we just shift each line by 4 ++ // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value ++ final long childPropagation = ++ ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 ++ ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 ++ ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 ++ ++ // don't queue update if toPropagate cannot propagate anything to neighbours ++ // (for increase, propagating 0 to neighbours is useless) ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | ++ ((toPropagate & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | ++ (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); //childPropagation; ++ continue; ++ } ++ } ++ ++ // propagate sources we clobbered ++ this.increaseQueueInitialLength = increaseQueueLength; ++ this.performIncrease(); ++ } ++ } ++ ++ private static final class Coordinate implements Comparable { ++ ++ public final long key; ++ ++ public Coordinate(final long key) { ++ this.key = key; ++ } ++ ++ public Coordinate(final int x, final int z) { ++ this.key = key(x, z); ++ } ++ ++ public static long key(final int x, final int z) { ++ return ((long)z << 32) | (x & 0xFFFFFFFFL); ++ } ++ ++ public static int x(final long key) { ++ return (int)key; ++ } ++ ++ public static int z(final long key) { ++ return (int)(key >>> 32); ++ } ++ ++ @Override ++ public int hashCode() { ++ return (int)HashCommon.mix(this.key); ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ ++ if (!(obj instanceof Coordinate other)) { ++ return false; ++ } ++ ++ return this.key == other.key; ++ } ++ ++ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain ++ // is too large. So we should implement compareTo to help. ++ @Override ++ public int compareTo(final Coordinate other) { ++ return Long.compare(this.key, other.key); ++ } ++ ++ @Override ++ public String toString() { ++ return "[" + x(this.key) + "," + z(this.key) + "]"; ++ } ++ } ++ ++ /* ++ private static final java.util.Random random = new java.util.Random(4L); ++ private static final List> walkers = ++ new java.util.ArrayList<>(); ++ static final int PLAYERS = 0; ++ static final int RAD_BLOCKS = 10000; ++ static final int RAD = RAD_BLOCKS >> 4; ++ static final int RAD_BIG_BLOCKS = 100_000; ++ static final int RAD_BIG = RAD_BIG_BLOCKS >> 4; ++ static final int VD = 4; ++ static final int BIG_PLAYERS = 50; ++ static final double WALK_CHANCE = 0.10; ++ static final double TP_CHANCE = 0.01; ++ static final int TP_BACK_PLAYERS = 200; ++ static final double TP_BACK_CHANCE = 0.25; ++ static final double TP_STEAL_CHANCE = 0.25; ++ private static final List> tpBack = ++ new java.util.ArrayList<>(); ++ ++ public static void main(final String[] args) { ++ final ReentrantAreaLock ticketLock = new ReentrantAreaLock(SECTION_SHIFT); ++ final ReentrantAreaLock schedulingLock = new ReentrantAreaLock(SECTION_SHIFT); ++ final Long2ByteLinkedOpenHashMap levelMap = new Long2ByteLinkedOpenHashMap(); ++ final Long2ByteLinkedOpenHashMap refMap = new Long2ByteLinkedOpenHashMap(); ++ final io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D ref = new io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D((final long coordinate, final byte oldLevel, final byte newLevel) -> { ++ if (newLevel == 0) { ++ refMap.remove(coordinate); ++ } else { ++ refMap.put(coordinate, newLevel); ++ } ++ }); ++ final ThreadedTicketLevelPropagator propagator = new ThreadedTicketLevelPropagator() { ++ @Override ++ protected void processLevelUpdates(Long2ByteLinkedOpenHashMap updates) { ++ for (final long key : updates.keySet()) { ++ final byte val = updates.get(key); ++ if (val == 0) { ++ levelMap.remove(key); ++ } else { ++ levelMap.put(key, val); ++ } ++ } ++ } ++ ++ @Override ++ protected void processSchedulingUpdates(Long2ByteLinkedOpenHashMap updates, List scheduledTasks, List changedFullStatus) {} ++ }; ++ ++ for (;;) { ++ if (walkers.isEmpty() && tpBack.isEmpty()) { ++ for (int i = 0; i < PLAYERS; ++i) { ++ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD; ++ int posX = random.nextInt(-rad, rad + 1); ++ int posZ = random.nextInt(-rad, rad + 1); ++ ++ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<>(null) { ++ @Override ++ protected void addCallback(Void parameter, int chunkX, int chunkZ) { ++ int src = 45 - 31 + 1; ++ ref.setSource(chunkX, chunkZ, src); ++ propagator.setSource(chunkX, chunkZ, src); ++ } ++ ++ @Override ++ protected void removeCallback(Void parameter, int chunkX, int chunkZ) { ++ ref.removeSource(chunkX, chunkZ); ++ propagator.removeSource(chunkX, chunkZ); ++ } ++ }; ++ ++ map.add(posX, posZ, VD); ++ ++ walkers.add(map); ++ } ++ for (int i = 0; i < TP_BACK_PLAYERS; ++i) { ++ int rad = RAD_BIG; ++ int posX = random.nextInt(-rad, rad + 1); ++ int posZ = random.nextInt(-rad, rad + 1); ++ ++ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<>(null) { ++ @Override ++ protected void addCallback(Void parameter, int chunkX, int chunkZ) { ++ int src = 45 - 31 + 1; ++ ref.setSource(chunkX, chunkZ, src); ++ propagator.setSource(chunkX, chunkZ, src); ++ } ++ ++ @Override ++ protected void removeCallback(Void parameter, int chunkX, int chunkZ) { ++ ref.removeSource(chunkX, chunkZ); ++ propagator.removeSource(chunkX, chunkZ); ++ } ++ }; ++ ++ map.add(posX, posZ, random.nextInt(1, 63)); ++ ++ tpBack.add(map); ++ } ++ } else { ++ for (int i = 0; i < PLAYERS; ++i) { ++ if (random.nextDouble() > WALK_CHANCE) { ++ continue; ++ } ++ ++ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = walkers.get(i); ++ ++ int updateX = random.nextInt(-1, 2); ++ int updateZ = random.nextInt(-1, 2); ++ ++ map.update(map.lastChunkX + updateX, map.lastChunkZ + updateZ, VD); ++ } ++ ++ for (int i = 0; i < PLAYERS; ++i) { ++ if (random.nextDouble() > TP_CHANCE) { ++ continue; ++ } ++ ++ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD; ++ int posX = random.nextInt(-rad, rad + 1); ++ int posZ = random.nextInt(-rad, rad + 1); ++ ++ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = walkers.get(i); ++ ++ map.update(posX, posZ, VD); ++ } ++ ++ for (int i = 0; i < TP_BACK_PLAYERS; ++i) { ++ if (random.nextDouble() > TP_BACK_CHANCE) { ++ continue; ++ } ++ ++ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = tpBack.get(i); ++ ++ map.update(-map.lastChunkX, -map.lastChunkZ, random.nextInt(1, 63)); ++ ++ if (random.nextDouble() > TP_STEAL_CHANCE) { ++ propagator.performUpdate( ++ map.lastChunkX >> SECTION_SHIFT, map.lastChunkZ >> SECTION_SHIFT, schedulingLock, null, null ++ ); ++ propagator.performUpdate( ++ (-map.lastChunkX >> SECTION_SHIFT), (-map.lastChunkZ >> SECTION_SHIFT), schedulingLock, null, null ++ ); ++ } ++ } ++ } ++ ++ ref.propagateUpdates(); ++ propagator.performUpdates(ticketLock, schedulingLock, null, null); ++ ++ if (!refMap.equals(levelMap)) { ++ throw new IllegalStateException("Error!"); ++ } ++ } ++ } ++ */ ++} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/queue/RadiusAwarePrioritisedExecutor.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/queue/RadiusAwarePrioritisedExecutor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f7b0e2564ac4bd2db1d2b2bdc230c9f52f8a21b7 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/queue/RadiusAwarePrioritisedExecutor.java +@@ -0,0 +1,667 @@ ++package io.papermc.paper.chunk.system.scheduling.queue; ++ ++import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; ++import io.papermc.paper.util.CoordinateUtils; ++import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; ++import java.util.ArrayList; ++import java.util.Comparator; ++import java.util.List; ++import java.util.PriorityQueue; ++ ++public class RadiusAwarePrioritisedExecutor { ++ ++ private static final Comparator DEPENDENCY_NODE_COMPARATOR = (final DependencyNode t1, final DependencyNode t2) -> { ++ return Long.compare(t1.id, t2.id); ++ }; ++ ++ private final DependencyTree[] queues = new DependencyTree[PrioritisedExecutor.Priority.TOTAL_SCHEDULABLE_PRIORITIES]; ++ private static final int NO_TASKS_QUEUED = -1; ++ private int selectedQueue = NO_TASKS_QUEUED; ++ private boolean canQueueTasks = true; ++ ++ public RadiusAwarePrioritisedExecutor(final PrioritisedExecutor executor, final int maxToSchedule) { ++ for (int i = 0; i < this.queues.length; ++i) { ++ this.queues[i] = new DependencyTree(this, executor, maxToSchedule, i); ++ } ++ } ++ ++ private boolean canQueueTasks() { ++ return this.canQueueTasks; ++ } ++ ++ private List treeFinished() { ++ this.canQueueTasks = true; ++ for (int priority = 0; priority < this.queues.length; ++priority) { ++ final DependencyTree queue = this.queues[priority]; ++ if (queue.hasWaitingTasks()) { ++ final List ret = queue.tryPushTasks(); ++ ++ if (ret == null || ret.isEmpty()) { ++ // this happens when the tasks in the wait queue were purged ++ // in this case, the queue was actually empty, we just had to purge it ++ // if we set the selected queue without scheduling any tasks, the queue will never be unselected ++ // as that requires a scheduled task completing... ++ continue; ++ } ++ ++ this.selectedQueue = priority; ++ return ret; ++ } ++ } ++ ++ this.selectedQueue = NO_TASKS_QUEUED; ++ ++ return null; ++ } ++ ++ private List queue(final Task task, final PrioritisedExecutor.Priority priority) { ++ final int priorityId = priority.priority; ++ final DependencyTree queue = this.queues[priorityId]; ++ ++ final DependencyNode node = new DependencyNode(task, queue); ++ ++ if (task.dependencyNode != null) { ++ throw new IllegalStateException(); ++ } ++ task.dependencyNode = node; ++ ++ queue.pushNode(node); ++ ++ if (this.selectedQueue == NO_TASKS_QUEUED) { ++ this.canQueueTasks = true; ++ this.selectedQueue = priorityId; ++ return queue.tryPushTasks(); ++ } ++ ++ if (!this.canQueueTasks) { ++ return null; ++ } ++ ++ if (PrioritisedExecutor.Priority.isHigherPriority(priorityId, this.selectedQueue)) { ++ // prevent the lower priority tree from queueing more tasks ++ this.canQueueTasks = false; ++ return null; ++ } ++ ++ // priorityId != selectedQueue: lower priority, don't care - treeFinished will pick it up ++ return priorityId == this.selectedQueue ? queue.tryPushTasks() : null; ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, ++ final Runnable run, final PrioritisedExecutor.Priority priority) { ++ if (radius < 0) { ++ throw new IllegalArgumentException("Radius must be > 0: " + radius); ++ } ++ return new Task(this, chunkX, chunkZ, radius, run, priority); ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, ++ final Runnable run) { ++ return this.createTask(chunkX, chunkZ, radius, run, PrioritisedExecutor.Priority.NORMAL); ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius, ++ final Runnable run, final PrioritisedExecutor.Priority priority) { ++ final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run, priority); ++ ++ ret.queue(); ++ ++ return ret; ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius, ++ final Runnable run) { ++ final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run); ++ ++ ret.queue(); ++ ++ return ret; ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ return new Task(this, 0, 0, -1, run, priority); ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run) { ++ return this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL); ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) { ++ final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, priority); ++ ++ ret.queue(); ++ ++ return ret; ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run) { ++ final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL); ++ ++ ret.queue(); ++ ++ return ret; ++ } ++ ++ // all accesses must be synchronised by the radius aware object ++ private static final class DependencyTree { ++ ++ private final RadiusAwarePrioritisedExecutor scheduler; ++ private final PrioritisedExecutor executor; ++ private final int maxToSchedule; ++ private final int treeIndex; ++ ++ private int currentlyExecuting; ++ private long idGenerator; ++ ++ private final PriorityQueue awaiting = new PriorityQueue<>(DEPENDENCY_NODE_COMPARATOR); ++ ++ private final PriorityQueue infiniteRadius = new PriorityQueue<>(DEPENDENCY_NODE_COMPARATOR); ++ private boolean isInfiniteRadiusScheduled; ++ ++ private final Long2ReferenceOpenHashMap nodeByPosition = new Long2ReferenceOpenHashMap<>(); ++ ++ public DependencyTree(final RadiusAwarePrioritisedExecutor scheduler, final PrioritisedExecutor executor, ++ final int maxToSchedule, final int treeIndex) { ++ this.scheduler = scheduler; ++ this.executor = executor; ++ this.maxToSchedule = maxToSchedule; ++ this.treeIndex = treeIndex; ++ } ++ ++ public boolean hasWaitingTasks() { ++ return !this.awaiting.isEmpty() || !this.infiniteRadius.isEmpty(); ++ } ++ ++ private long nextId() { ++ return this.idGenerator++; ++ } ++ ++ private boolean isExecutingAnyTasks() { ++ return this.currentlyExecuting != 0; ++ } ++ ++ private void pushNode(final DependencyNode node) { ++ if (!node.task.isFiniteRadius()) { ++ this.infiniteRadius.add(node); ++ return; ++ } ++ ++ // set up dependency for node ++ final Task task = node.task; ++ ++ final int centerX = task.chunkX; ++ final int centerZ = task.chunkZ; ++ final int radius = task.radius; ++ ++ final int minX = centerX - radius; ++ final int maxX = centerX + radius; ++ ++ final int minZ = centerZ - radius; ++ final int maxZ = centerZ + radius; ++ ++ ReferenceOpenHashSet parents = null; ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ final DependencyNode dependency = this.nodeByPosition.put(CoordinateUtils.getChunkKey(currX, currZ), node); ++ if (dependency != null) { ++ if (parents == null) { ++ parents = new ReferenceOpenHashSet<>(); ++ } ++ if (parents.add(dependency)) { ++ // added a dependency, so we need to add as a child to the dependency ++ if (dependency.children == null) { ++ dependency.children = new ArrayList<>(); ++ } ++ dependency.children.add(node); ++ } ++ } ++ } ++ } ++ ++ if (parents == null) { ++ // no dependencies, add straight to awaiting ++ this.awaiting.add(node); ++ } else { ++ node.parents = parents.size(); ++ // we will be added to awaiting once we have no parents ++ } ++ } ++ ++ // called only when a node is returned after being executed ++ private List returnNode(final DependencyNode node) { ++ final Task task = node.task; ++ ++ // now that the task is completed, we can push its children to the awaiting queue ++ this.pushChildren(node); ++ ++ if (task.isFiniteRadius()) { ++ // remove from dependency map ++ this.removeNodeFromMap(node); ++ } else { ++ // mark as no longer executing infinite radius ++ if (!this.isInfiniteRadiusScheduled) { ++ throw new IllegalStateException(); ++ } ++ this.isInfiniteRadiusScheduled = false; ++ } ++ ++ // decrement executing count, we are done executing this task ++ --this.currentlyExecuting; ++ ++ if (this.currentlyExecuting == 0) { ++ return this.scheduler.treeFinished(); ++ } ++ ++ return this.scheduler.canQueueTasks() ? this.tryPushTasks() : null; ++ } ++ ++ private List tryPushTasks() { ++ // tasks are not queued, but only created here - we do hold the lock for the map ++ List ret = null; ++ PrioritisedExecutor.PrioritisedTask pushedTask; ++ while ((pushedTask = this.tryPushTask()) != null) { ++ if (ret == null) { ++ ret = new ArrayList<>(); ++ } ++ ret.add(pushedTask); ++ } ++ ++ return ret; ++ } ++ ++ private void removeNodeFromMap(final DependencyNode node) { ++ final Task task = node.task; ++ ++ final int centerX = task.chunkX; ++ final int centerZ = task.chunkZ; ++ final int radius = task.radius; ++ ++ final int minX = centerX - radius; ++ final int maxX = centerX + radius; ++ ++ final int minZ = centerZ - radius; ++ final int maxZ = centerZ + radius; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ this.nodeByPosition.remove(CoordinateUtils.getChunkKey(currX, currZ), node); ++ } ++ } ++ } ++ ++ private void pushChildren(final DependencyNode node) { ++ // add all the children that we can into awaiting ++ final List children = node.children; ++ if (children != null) { ++ for (int i = 0, len = children.size(); i < len; ++i) { ++ final DependencyNode child = children.get(i); ++ int newParents = --child.parents; ++ if (newParents == 0) { ++ // no more dependents, we can push to awaiting ++ // even if the child is purged, we need to push it so that its children will be pushed ++ this.awaiting.add(child); ++ } else if (newParents < 0) { ++ throw new IllegalStateException(); ++ } ++ } ++ } ++ } ++ ++ private DependencyNode pollAwaiting() { ++ final DependencyNode ret = this.awaiting.poll(); ++ if (ret == null) { ++ return ret; ++ } ++ ++ if (ret.parents != 0) { ++ throw new IllegalStateException(); ++ } ++ ++ if (ret.purged) { ++ // need to manually remove from state here ++ this.pushChildren(ret); ++ this.removeNodeFromMap(ret); ++ } // else: delay children push until the task has finished ++ ++ return ret; ++ } ++ ++ private DependencyNode pollInfinite() { ++ return this.infiniteRadius.poll(); ++ } ++ ++ public PrioritisedExecutor.PrioritisedTask tryPushTask() { ++ if (this.currentlyExecuting >= this.maxToSchedule || this.isInfiniteRadiusScheduled) { ++ return null; ++ } ++ ++ DependencyNode firstInfinite; ++ while ((firstInfinite = this.infiniteRadius.peek()) != null && firstInfinite.purged) { ++ this.pollInfinite(); ++ } ++ ++ DependencyNode firstAwaiting; ++ while ((firstAwaiting = this.awaiting.peek()) != null && firstAwaiting.purged) { ++ this.pollAwaiting(); ++ } ++ ++ if (firstInfinite == null && firstAwaiting == null) { ++ return null; ++ } ++ ++ // firstAwaiting compared to firstInfinite ++ final int compare; ++ ++ if (firstAwaiting == null) { ++ // we choose first infinite, or infinite < awaiting ++ compare = 1; ++ } else if (firstInfinite == null) { ++ // we choose first awaiting, or awaiting < infinite ++ compare = -1; ++ } else { ++ compare = DEPENDENCY_NODE_COMPARATOR.compare(firstAwaiting, firstInfinite); ++ } ++ ++ if (compare >= 0) { ++ if (this.currentlyExecuting != 0) { ++ // don't queue infinite task while other tasks are executing in parallel ++ return null; ++ } ++ ++this.currentlyExecuting; ++ this.pollInfinite(); ++ this.isInfiniteRadiusScheduled = true; ++ return firstInfinite.task.pushTask(this.executor); ++ } else { ++ ++this.currentlyExecuting; ++ this.pollAwaiting(); ++ return firstAwaiting.task.pushTask(this.executor); ++ } ++ } ++ } ++ ++ private static final class DependencyNode { ++ ++ private final Task task; ++ private final DependencyTree tree; ++ ++ // dependency tree fields ++ // (must hold lock on the scheduler to use) ++ // null is the same as empty, we just use it so that we don't allocate the set unless we need to ++ private List children; ++ // 0 indicates that this task is considered "awaiting" ++ private int parents; ++ // false -> scheduled and not cancelled ++ // true -> scheduled but cancelled ++ private boolean purged; ++ private final long id; ++ ++ public DependencyNode(final Task task, final DependencyTree tree) { ++ this.task = task; ++ this.id = tree.nextId(); ++ this.tree = tree; ++ } ++ } ++ ++ private static final class Task implements PrioritisedExecutor.PrioritisedTask, Runnable { ++ ++ // task specific fields ++ private final RadiusAwarePrioritisedExecutor scheduler; ++ private final int chunkX; ++ private final int chunkZ; ++ private final int radius; ++ private Runnable run; ++ private PrioritisedExecutor.Priority priority; ++ ++ private DependencyNode dependencyNode; ++ private PrioritisedExecutor.PrioritisedTask queuedTask; ++ ++ private Task(final RadiusAwarePrioritisedExecutor scheduler, final int chunkX, final int chunkZ, final int radius, ++ final Runnable run, final PrioritisedExecutor.Priority priority) { ++ this.scheduler = scheduler; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.radius = radius; ++ this.run = run; ++ this.priority = priority; ++ } ++ ++ private boolean isFiniteRadius() { ++ return this.radius >= 0; ++ } ++ ++ private PrioritisedExecutor.PrioritisedTask pushTask(final PrioritisedExecutor executor) { ++ return this.queuedTask = executor.createTask(this, this.priority); ++ } ++ ++ private void executeTask() { ++ final Runnable run = this.run; ++ this.run = null; ++ run.run(); ++ } ++ ++ private static void scheduleTasks(final List toSchedule) { ++ if (toSchedule != null) { ++ for (int i = 0, len = toSchedule.size(); i < len; ++i) { ++ toSchedule.get(i).queue(); ++ } ++ } ++ } ++ ++ private void returnNode() { ++ final List toSchedule; ++ synchronized (this.scheduler) { ++ final DependencyNode node = this.dependencyNode; ++ this.dependencyNode = null; ++ toSchedule = node.tree.returnNode(node); ++ } ++ ++ scheduleTasks(toSchedule); ++ } ++ ++ @Override ++ public void run() { ++ final Runnable run = this.run; ++ this.run = null; ++ try { ++ run.run(); ++ } finally { ++ this.returnNode(); ++ } ++ } ++ ++ @Override ++ public boolean queue() { ++ final List toSchedule; ++ synchronized (this.scheduler) { ++ if (this.queuedTask != null || this.dependencyNode != null || this.priority == PrioritisedExecutor.Priority.COMPLETING) { ++ return false; ++ } ++ ++ toSchedule = this.scheduler.queue(this, this.priority); ++ } ++ ++ scheduleTasks(toSchedule); ++ return true; ++ } ++ ++ @Override ++ public boolean cancel() { ++ final PrioritisedExecutor.PrioritisedTask task; ++ synchronized (this.scheduler) { ++ if ((task = this.queuedTask) == null) { ++ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { ++ return false; ++ } ++ ++ this.priority = PrioritisedExecutor.Priority.COMPLETING; ++ if (this.dependencyNode != null) { ++ this.dependencyNode.purged = true; ++ this.dependencyNode = null; ++ } ++ ++ return true; ++ } ++ } ++ ++ if (task.cancel()) { ++ // must manually return the node ++ this.run = null; ++ this.returnNode(); ++ return true; ++ } ++ return false; ++ } ++ ++ @Override ++ public boolean execute() { ++ final PrioritisedExecutor.PrioritisedTask task; ++ synchronized (this.scheduler) { ++ if ((task = this.queuedTask) == null) { ++ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { ++ return false; ++ } ++ ++ this.priority = PrioritisedExecutor.Priority.COMPLETING; ++ if (this.dependencyNode != null) { ++ this.dependencyNode.purged = true; ++ this.dependencyNode = null; ++ } ++ // fall through to execution logic ++ } ++ } ++ ++ if (task != null) { ++ // will run the return node logic automatically ++ return task.execute(); ++ } else { ++ // don't run node removal/insertion logic, we aren't actually removed from the dependency tree ++ this.executeTask(); ++ return true; ++ } ++ } ++ ++ @Override ++ public PrioritisedExecutor.Priority getPriority() { ++ final PrioritisedExecutor.PrioritisedTask task; ++ synchronized (this.scheduler) { ++ if ((task = this.queuedTask) == null) { ++ return this.priority; ++ } ++ } ++ ++ return task.getPriority(); ++ } ++ ++ @Override ++ public boolean setPriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ ++ final PrioritisedExecutor.PrioritisedTask task; ++ List toSchedule = null; ++ synchronized (this.scheduler) { ++ if ((task = this.queuedTask) == null) { ++ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { ++ return false; ++ } ++ ++ if (this.priority == priority) { ++ return true; ++ } ++ ++ this.priority = priority; ++ if (this.dependencyNode != null) { ++ // need to re-insert node ++ this.dependencyNode.purged = true; ++ this.dependencyNode = null; ++ toSchedule = this.scheduler.queue(this, priority); ++ } ++ } ++ } ++ ++ if (task != null) { ++ return task.setPriority(priority); ++ } ++ ++ scheduleTasks(toSchedule); ++ ++ return true; ++ } ++ ++ @Override ++ public boolean raisePriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ ++ final PrioritisedExecutor.PrioritisedTask task; ++ List toSchedule = null; ++ synchronized (this.scheduler) { ++ if ((task = this.queuedTask) == null) { ++ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { ++ return false; ++ } ++ ++ if (this.priority.isHigherOrEqualPriority(priority)) { ++ return true; ++ } ++ ++ this.priority = priority; ++ if (this.dependencyNode != null) { ++ // need to re-insert node ++ this.dependencyNode.purged = true; ++ this.dependencyNode = null; ++ toSchedule = this.scheduler.queue(this, priority); ++ } ++ } ++ } ++ ++ if (task != null) { ++ return task.raisePriority(priority); ++ } ++ ++ scheduleTasks(toSchedule); ++ ++ return true; ++ } ++ ++ @Override ++ public boolean lowerPriority(final PrioritisedExecutor.Priority priority) { ++ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { ++ throw new IllegalArgumentException("Invalid priority " + priority); ++ } ++ ++ final PrioritisedExecutor.PrioritisedTask task; ++ List toSchedule = null; ++ synchronized (this.scheduler) { ++ if ((task = this.queuedTask) == null) { ++ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { ++ return false; ++ } ++ ++ if (this.priority.isLowerOrEqualPriority(priority)) { ++ return true; ++ } ++ ++ this.priority = priority; ++ if (this.dependencyNode != null) { ++ // need to re-insert node ++ this.dependencyNode.purged = true; ++ this.dependencyNode = null; ++ toSchedule = this.scheduler.queue(this, priority); ++ } ++ } ++ } ++ ++ if (task != null) { ++ return task.lowerPriority(priority); ++ } ++ ++ scheduleTasks(toSchedule); ++ ++ return true; ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index e47fb2aa5e885162cae5cbfc9f33ff7864bf538e..b68b37274f22c2a89d723aec4d1c6be813eef73c 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -43,6 +43,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); + commands.put(Set.of("dumplisteners"), new DumpListenersCommand()); + commands.put(Set.of("fixlight"), new FixLightCommand()); ++ commands.put(Set.of("debug", "chunkinfo", "holderinfo"), new ChunkDebugCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..962d3cae6340fc11607b59355e291629618f289c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java +@@ -0,0 +1,265 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.io.File; ++import java.time.LocalDateTime; ++import java.time.format.DateTimeFormatter; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.Locale; ++import io.papermc.paper.util.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.FullChunkStatus; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ImposterProtoChunk; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.ProtoChunk; ++import org.bukkit.Bukkit; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.BLUE; ++import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class ChunkDebugCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ switch (subCommand) { ++ case "debug" -> this.doDebug(sender, args); ++ case "chunkinfo" -> this.doChunkInfo(sender, args); ++ case "holderinfo" -> this.doHolderInfo(sender, args); ++ } ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ switch (subCommand) { ++ case "debug" -> { ++ if (args.length == 1) { ++ return CommandUtil.getListMatchingLast(sender, args, "help", "chunks"); ++ } ++ } ++ case "holderinfo" -> { ++ List worldNames = new ArrayList<>(); ++ worldNames.add("*"); ++ for (org.bukkit.World world : Bukkit.getWorlds()) { ++ worldNames.add(world.getName()); ++ } ++ if (args.length == 1) { ++ return CommandUtil.getListMatchingLast(sender, args, worldNames); ++ } ++ } ++ case "chunkinfo" -> { ++ List worldNames = new ArrayList<>(); ++ worldNames.add("*"); ++ for (org.bukkit.World world : Bukkit.getWorlds()) { ++ worldNames.add(world.getName()); ++ } ++ if (args.length == 1) { ++ return CommandUtil.getListMatchingLast(sender, args, worldNames); ++ } ++ } ++ } ++ return Collections.emptyList(); ++ } ++ ++ private void doChunkInfo(final CommandSender sender, final String[] args) { ++ List worlds; ++ if (args.length < 1 || args[0].equals("*")) { ++ worlds = Bukkit.getWorlds(); ++ } else { ++ worlds = new ArrayList<>(args.length); ++ for (final String arg : args) { ++ org.bukkit.@Nullable World world = Bukkit.getWorld(arg); ++ if (world == null) { ++ sender.sendMessage(text("World '" + arg + "' is invalid", RED)); ++ return; ++ } ++ worlds.add(world); ++ } ++ } ++ ++ int accumulatedTotal = 0; ++ int accumulatedInactive = 0; ++ int accumulatedBorder = 0; ++ int accumulatedTicking = 0; ++ int accumulatedEntityTicking = 0; ++ ++ for (final org.bukkit.World bukkitWorld : worlds) { ++ final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); ++ ++ int total = 0; ++ int inactive = 0; ++ int full = 0; ++ int blockTicking = 0; ++ int entityTicking = 0; ++ ++ for (final ChunkHolder chunk : io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(world)) { ++ if (chunk.getFullChunkNowUnchecked() == null) { ++ continue; ++ } ++ ++ ++total; ++ ++ FullChunkStatus state = chunk.getFullStatus(); ++ ++ switch (state) { ++ case INACCESSIBLE -> ++inactive; ++ case FULL -> ++full; ++ case BLOCK_TICKING -> ++blockTicking; ++ case ENTITY_TICKING -> ++entityTicking; ++ } ++ } ++ ++ accumulatedTotal += total; ++ accumulatedInactive += inactive; ++ accumulatedBorder += full; ++ accumulatedTicking += blockTicking; ++ accumulatedEntityTicking += entityTicking; ++ ++ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.getName(), GREEN), text(":"))); ++ sender.sendMessage(text().color(DARK_AQUA).append( ++ text("Total: ", BLUE), text(total), ++ text(" Inactive: ", BLUE), text(inactive), ++ text(" Full: ", BLUE), text(full), ++ text(" Block Ticking: ", BLUE), text(blockTicking), ++ text(" Entity Ticking: ", BLUE), text(entityTicking) ++ )); ++ } ++ if (worlds.size() > 1) { ++ sender.sendMessage(text().append(text("Chunks in ", BLUE), text("all listed worlds", GREEN), text(":", DARK_AQUA))); ++ sender.sendMessage(text().color(DARK_AQUA).append( ++ text("Total: ", BLUE), text(accumulatedTotal), ++ text(" Inactive: ", BLUE), text(accumulatedInactive), ++ text(" Full: ", BLUE), text(accumulatedBorder), ++ text(" Block Ticking: ", BLUE), text(accumulatedTicking), ++ text(" Entity Ticking: ", BLUE), text(accumulatedEntityTicking) ++ )); ++ } ++ } ++ ++ private void doHolderInfo(final CommandSender sender, final String[] args) { ++ List worlds; ++ if (args.length < 1 || args[0].equals("*")) { ++ worlds = Bukkit.getWorlds(); ++ } else { ++ worlds = new ArrayList<>(args.length); ++ for (final String arg : args) { ++ org.bukkit.@Nullable World world = Bukkit.getWorld(arg); ++ if (world == null) { ++ sender.sendMessage(text("World '" + arg + "' is invalid", RED)); ++ return; ++ } ++ worlds.add(world); ++ } ++ } ++ ++ int accumulatedTotal = 0; ++ int accumulatedCanUnload = 0; ++ int accumulatedNull = 0; ++ int accumulatedReadOnly = 0; ++ int accumulatedProtoChunk = 0; ++ int accumulatedFullChunk = 0; ++ ++ for (final org.bukkit.World bukkitWorld : worlds) { ++ final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); ++ ++ int total = 0; ++ int canUnload = 0; ++ int nullChunks = 0; ++ int readOnly = 0; ++ int protoChunk = 0; ++ int fullChunk = 0; ++ ++ for (final ChunkHolder chunk : world.chunkTaskScheduler.chunkHolderManager.getOldChunkHolders()) { // Paper - change updating chunks map ++ final ChunkAccess lastChunk = chunk.getAvailableChunkNow(); ++ ++ ++total; ++ ++ if (lastChunk == null) { ++ ++nullChunks; ++ } else if (lastChunk instanceof ImposterProtoChunk) { ++ ++readOnly; ++ } else if (lastChunk instanceof ProtoChunk) { ++ ++protoChunk; ++ } else if (lastChunk instanceof LevelChunk) { ++ ++fullChunk; ++ } ++ ++ if (chunk.newChunkHolder.isSafeToUnload() == null) { ++ ++canUnload; ++ } ++ } ++ ++ accumulatedTotal += total; ++ accumulatedCanUnload += canUnload; ++ accumulatedNull += nullChunks; ++ accumulatedReadOnly += readOnly; ++ accumulatedProtoChunk += protoChunk; ++ accumulatedFullChunk += fullChunk; ++ ++ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.getName(), GREEN), text(":"))); ++ sender.sendMessage(text().color(DARK_AQUA).append( ++ text("Total: ", BLUE), text(total), ++ text(" Unloadable: ", BLUE), text(canUnload), ++ text(" Null: ", BLUE), text(nullChunks), ++ text(" ReadOnly: ", BLUE), text(readOnly), ++ text(" Proto: ", BLUE), text(protoChunk), ++ text(" Full: ", BLUE), text(fullChunk) ++ )); ++ } ++ if (worlds.size() > 1) { ++ sender.sendMessage(text().append(text("Chunks in ", BLUE), text("all listed worlds", GREEN), text(":", DARK_AQUA))); ++ sender.sendMessage(text().color(DARK_AQUA).append( ++ text("Total: ", BLUE), text(accumulatedTotal), ++ text(" Unloadable: ", BLUE), text(accumulatedCanUnload), ++ text(" Null: ", BLUE), text(accumulatedNull), ++ text(" ReadOnly: ", BLUE), text(accumulatedReadOnly), ++ text(" Proto: ", BLUE), text(accumulatedProtoChunk), ++ text(" Full: ", BLUE), text(accumulatedFullChunk) ++ )); ++ } ++ } ++ ++ private void doDebug(final CommandSender sender, final String[] args) { ++ if (args.length < 1) { ++ sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); ++ return; ++ } ++ ++ final String debugType = args[0].toLowerCase(Locale.ENGLISH); ++ switch (debugType) { ++ case "chunks" -> { ++ if (args.length >= 2 && args[1].toLowerCase(Locale.ENGLISH).equals("help")) { ++ sender.sendMessage(text("Use /paper debug chunks [world] to dump loaded chunk information to a file", RED)); ++ break; ++ } ++ File file = new File(new File(new File("."), "debug"), ++ "chunks-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); ++ sender.sendMessage(text("Writing chunk information dump to " + file, GREEN)); ++ try { ++ MCUtil.dumpChunks(file, false); ++ sender.sendMessage(text("Successfully written chunk information!", GREEN)); ++ } catch (Throwable thr) { ++ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); ++ sender.sendMessage(text("Failed to dump chunk information, see console", RED)); ++ } ++ } ++ // "help" & default ++ default -> sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); ++ } ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +index ffd52f6871161cd1f2d23040ed4493434a29b834..a6f58b3457b7477015c5c6d969e7d83017dd3fa1 100644 +--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +@@ -28,6 +28,45 @@ public class GlobalConfiguration extends ConfigurationPart { + public static GlobalConfiguration get() { + return instance; + } ++ ++ public ChunkLoadingBasic chunkLoadingBasic; ++ ++ public class ChunkLoadingBasic extends ConfigurationPart { ++ @Comment("The maximum rate in chunks per second that the server will send to any individual player. Set to -1 to disable this limit.") ++ public double playerMaxChunkSendRate = 75.0; ++ ++ @Comment( ++ "The maximum rate at which chunks will load for any individual player. " + ++ "Note that this setting also affects chunk generations, since a chunk load is always first issued to test if a" + ++ "chunk is already generated. Set to -1 to disable this limit." ++ ) ++ public double playerMaxChunkLoadRate = 100.0; ++ ++ @Comment("The maximum rate at which chunks will generate for any individual player. Set to -1 to disable this limit.") ++ public double playerMaxChunkGenerateRate = -1.0; ++ } ++ ++ public ChunkLoadingAdvanced chunkLoadingAdvanced; ++ ++ public class ChunkLoadingAdvanced extends ConfigurationPart { ++ @Comment( ++ "Set to true if the server will match the chunk send radius that clients have configured" + ++ "in their view distance settings if the client is less-than the server's send distance." ++ ) ++ public boolean autoConfigSendDistance = true; ++ ++ @Comment( ++ "Specifies the maximum amount of concurrent chunk loads that an individual player can have." + ++ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." ++ ) ++ public int playerMaxConcurrentChunkLoads = 0; ++ ++ @Comment( ++ "Specifies the maximum amount of concurrent chunk generations that an individual player can have." + ++ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." ++ ) ++ public int playerMaxConcurrentChunkGenerates = 0; ++ } + static void set(GlobalConfiguration instance) { + GlobalConfiguration.instance = instance; + } +@@ -129,21 +168,6 @@ public class GlobalConfiguration extends ConfigurationPart { + public int incomingPacketThreshold = 300; + } + +- public ChunkLoading chunkLoading; +- +- public class ChunkLoading extends ConfigurationPart { +- public int minLoadRadius = 2; +- public int maxConcurrentSends = 2; +- public boolean autoconfigSendDistance = true; +- public double targetPlayerChunkSendRate = 100.0; +- public double globalMaxChunkSendRate = -1.0; +- public boolean enableFrustumPriority = false; +- public double globalMaxChunkLoadRate = -1.0; +- public double playerMaxConcurrentLoads = 20.0; +- public double globalMaxConcurrentLoads = 500.0; +- public double playerMaxChunkLoadRate = -1.0; +- } +- + public UnsupportedSettings unsupportedSettings; + + public class UnsupportedSettings extends ConfigurationPart { +@@ -198,7 +222,7 @@ public class GlobalConfiguration extends ConfigurationPart { + + @PostProcess + private void postProcess() { +- //io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.init(this); ++ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.init(this); + } + } + +diff --git a/src/main/java/io/papermc/paper/threadedregions/TickRegions.java b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d5d39e9c1f326e91010237b0db80d527ac52f4d6 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java +@@ -0,0 +1,9 @@ ++package io.papermc.paper.threadedregions; ++ ++// placeholder class for Folia ++public class TickRegions { ++ ++ public static int getRegionChunkShift() { ++ return 4; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/util/IntervalledCounter.java b/src/main/java/io/papermc/paper/util/IntervalledCounter.java +index cea9c098ade00ee87b8efc8164ab72f5279758f0..197224e31175252d8438a8df585bbb65f2288d7f 100644 +--- a/src/main/java/io/papermc/paper/util/IntervalledCounter.java ++++ b/src/main/java/io/papermc/paper/util/IntervalledCounter.java +@@ -2,6 +2,8 @@ package io.papermc.paper.util; + + public final class IntervalledCounter { + ++ private static final int INITIAL_SIZE = 8; ++ + protected long[] times; + protected long[] counts; + protected final long interval; +@@ -11,8 +13,8 @@ public final class IntervalledCounter { + protected int tail; // exclusive + + public IntervalledCounter(final long interval) { +- this.times = new long[8]; +- this.counts = new long[8]; ++ this.times = new long[INITIAL_SIZE]; ++ this.counts = new long[INITIAL_SIZE]; + this.interval = interval; + } + +@@ -67,13 +69,13 @@ public final class IntervalledCounter { + this.tail = nextTail; + } + +- public void updateAndAdd(final int count) { ++ public void updateAndAdd(final long count) { + final long currTime = System.nanoTime(); + this.updateCurrentTime(currTime); + this.addTime(currTime, count); + } + +- public void updateAndAdd(final int count, final long currTime) { ++ public void updateAndAdd(final long count, final long currTime) { + this.updateCurrentTime(currTime); + this.addTime(currTime, count); + } +@@ -93,9 +95,13 @@ public final class IntervalledCounter { + this.tail = size; + + if (tail >= head) { ++ // sequentially ordered from [head, tail) + System.arraycopy(oldElements, head, newElements, 0, size); + System.arraycopy(oldCounts, head, newCounts, 0, size); + } else { ++ // ordered from [head, length) ++ // then followed by [0, tail) ++ + System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head); + System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail); + +@@ -106,10 +112,18 @@ public final class IntervalledCounter { + + // returns in units per second + public double getRate() { +- return this.size() / (this.interval * 1.0e-9); ++ return (double)this.sum / ((double)this.interval * 1.0E-9); ++ } ++ ++ public long getInterval() { ++ return this.interval; + } + +- public long size() { ++ public long getSum() { + return this.sum; + } ++ ++ public int totalDataPoints() { ++ return this.tail >= this.head ? (this.tail - this.head) : (this.tail + (this.counts.length - this.head)); ++ } + } +diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java +index 0d3ccf3ae219a3b24d17be03de8fd4906cb7235d..850f75172e9efa72cabb8e5bd124b96a0b1a945f 100644 +--- a/src/main/java/io/papermc/paper/util/MCUtil.java ++++ b/src/main/java/io/papermc/paper/util/MCUtil.java +@@ -6,17 +6,30 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; + import io.papermc.paper.math.BlockPosition; + import io.papermc.paper.math.FinePosition; + import io.papermc.paper.math.Position; ++import com.google.gson.JsonArray; ++import com.google.gson.JsonObject; ++import com.google.gson.internal.Streams; ++import com.google.gson.stream.JsonWriter; ++import com.mojang.datafixers.util.Either; + import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; + import java.lang.ref.Cleaner; ++import it.unimi.dsi.fastutil.objects.ReferenceArrayList; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.core.Vec3i; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ChunkMap; ++import net.minecraft.server.level.DistanceManager; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.Ticket; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.ClipContext; + import net.minecraft.world.level.Level; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.phys.Vec3; + import org.apache.commons.lang.exception.ExceptionUtils; + import com.mojang.authlib.GameProfile; +@@ -28,8 +41,11 @@ import org.spigotmc.AsyncCatcher; + + import javax.annotation.Nonnull; + import javax.annotation.Nullable; ++import java.io.*; ++import java.nio.charset.StandardCharsets; + import java.util.List; + import java.util.Queue; ++import java.util.Set; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.ExecutionException; + import java.util.concurrent.LinkedBlockingQueue; +@@ -529,6 +545,100 @@ public final class MCUtil { + } + } + ++ public static ChunkStatus getChunkStatus(ChunkHolder chunk) { ++ return chunk.getChunkHolderStatus(); ++ } ++ ++ public static void dumpChunks(File file, boolean watchdog) throws IOException { ++ file.getParentFile().mkdirs(); ++ file.createNewFile(); ++ ReferenceArrayList worlds = new ReferenceArrayList<>(org.bukkit.Bukkit.getWorlds()); ++ ReferenceArrayList loadedWorlds = new ReferenceArrayList<>(worlds); ++ JsonObject data = new JsonObject(); ++ ++ data.addProperty("server-version", org.bukkit.Bukkit.getVersion()); ++ data.addProperty("data-version", 1); ++ ++ { ++ JsonArray players = new JsonArray(); ++ data.add("all-players", players); ++ List playerList = MinecraftServer.getServer().getPlayerList().players; ++ for (ServerPlayer player : playerList) { ++ JsonObject playerData = new JsonObject(); ++ players.add(playerData); ++ ++ Level playerWorld = player.level(); ++ org.bukkit.World craftWorld = playerWorld.getWorld(); ++ Entity.RemovalReason removalReason = player.getRemovalReason(); ++ ++ playerData.addProperty("name", player.getScoreboardName()); ++ playerData.addProperty("x", player.getX()); ++ playerData.addProperty("y", player.getY()); ++ playerData.addProperty("z", player.getZ()); ++ playerData.addProperty("world", playerWorld == null ? "null world" : craftWorld.getName()); ++ playerData.addProperty("removalReason", removalReason == null ? "null" : removalReason.name()); ++ ++ if (!worlds.contains(craftWorld)) { ++ worlds.add(craftWorld); ++ } ++ } ++ } ++ ++ JsonArray chunkWaitInformation = new JsonArray(); ++ data.add("chunk-wait-infos", chunkWaitInformation); ++ ++ for (io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.ChunkInfo chunkInfo : io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.getChunkInfos()) { ++ chunkWaitInformation.add(chunkInfo.toString()); ++ } ++ ++ JsonArray worldsData = new JsonArray(); ++ ++ for (org.bukkit.World bukkitWorld : worlds) { ++ JsonObject worldData = new JsonObject(); ++ ++ ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle(); ++ List players = world.players(); ++ ++ worldData.addProperty("is-loaded", loadedWorlds.contains(bukkitWorld)); ++ worldData.addProperty("name", world.getWorld().getName()); ++ worldData.addProperty("view-distance", world.getWorld().getViewDistance()); // Paper - replace chunk loader system ++ worldData.addProperty("tick-view-distance", world.getWorld().getSimulationDistance()); // Paper - replace chunk loader system ++ worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory); ++ worldData.addProperty("keep-spawn-loaded-range", world.paperConfig().spawn.keepSpawnLoadedRange * 16); ++ ++ JsonArray playersData = new JsonArray(); ++ ++ for (ServerPlayer player : players) { ++ JsonObject playerData = new JsonObject(); ++ ++ playerData.addProperty("name", player.getScoreboardName()); ++ playerData.addProperty("x", player.getX()); ++ playerData.addProperty("y", player.getY()); ++ playerData.addProperty("z", player.getZ()); ++ ++ playersData.add(playerData); ++ } ++ ++ worldData.add("players", playersData); ++ worldData.add("chunk-data", watchdog ? world.chunkTaskScheduler.chunkHolderManager.getDebugJsonForWatchdog() : world.chunkTaskScheduler.chunkHolderManager.getDebugJson()); ++ worldsData.add(worldData); ++ } ++ ++ data.add("worlds", worldsData); ++ ++ StringWriter stringWriter = new StringWriter(); ++ JsonWriter jsonWriter = new JsonWriter(stringWriter); ++ jsonWriter.setIndent(" "); ++ jsonWriter.setLenient(false); ++ Streams.write(data, jsonWriter); ++ ++ String fileData = stringWriter.toString(); ++ ++ try (PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8)) { ++ out.print(fileData); ++ } ++ } ++ + public static int getTicketLevelFor(net.minecraft.world.level.chunk.ChunkStatus status) { + return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status); + } +diff --git a/src/main/java/io/papermc/paper/util/TickThread.java b/src/main/java/io/papermc/paper/util/TickThread.java +index 73e83d56a340f0c7dcb8ff737d621003e72c6de4..bdaf062f9b66ceab303a0807eca301342886a8ea 100644 +--- a/src/main/java/io/papermc/paper/util/TickThread.java ++++ b/src/main/java/io/papermc/paper/util/TickThread.java +@@ -1,12 +1,20 @@ + package io.papermc.paper.util; + ++import net.minecraft.core.BlockPos; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.network.ServerGamePacketListenerImpl; ++import net.minecraft.util.Mth; + import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.phys.AABB; ++import net.minecraft.world.phys.Vec3; + import org.bukkit.Bukkit; + import java.util.concurrent.atomic.AtomicInteger; + +-public final class TickThread extends Thread { ++public class TickThread extends Thread { + + public static final boolean STRICT_THREAD_CHECKS = Boolean.getBoolean("paper.strict-thread-checks"); + +@@ -16,6 +24,10 @@ public final class TickThread extends Thread { + } + } + ++ /** ++ * @deprecated ++ */ ++ @Deprecated + public static void softEnsureTickThread(final String reason) { + if (!STRICT_THREAD_CHECKS) { + return; +@@ -23,6 +35,10 @@ public final class TickThread extends Thread { + ensureTickThread(reason); + } + ++ /** ++ * @deprecated ++ */ ++ @Deprecated + public static void ensureTickThread(final String reason) { + if (!isTickThread()) { + MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); +@@ -30,6 +46,20 @@ public final class TickThread extends Thread { + } + } + ++ public static void ensureTickThread(final ServerLevel world, final BlockPos pos, final String reason) { ++ if (!isTickThreadFor(world, pos)) { ++ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); ++ throw new IllegalStateException(reason); ++ } ++ } ++ ++ public static void ensureTickThread(final ServerLevel world, final ChunkPos pos, final String reason) { ++ if (!isTickThreadFor(world, pos)) { ++ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); ++ throw new IllegalStateException(reason); ++ } ++ } ++ + public static void ensureTickThread(final ServerLevel world, final int chunkX, final int chunkZ, final String reason) { + if (!isTickThreadFor(world, chunkX, chunkZ)) { + MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); +@@ -44,6 +74,20 @@ public final class TickThread extends Thread { + } + } + ++ public static void ensureTickThread(final ServerLevel world, final AABB aabb, final String reason) { ++ if (!isTickThreadFor(world, aabb)) { ++ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); ++ throw new IllegalStateException(reason); ++ } ++ } ++ ++ public static void ensureTickThread(final ServerLevel world, final double blockX, final double blockZ, final String reason) { ++ if (!isTickThreadFor(world, blockX, blockZ)) { ++ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); ++ throw new IllegalStateException(reason); ++ } ++ } ++ + public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ + + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); +@@ -66,13 +110,45 @@ public final class TickThread extends Thread { + } + + public static boolean isTickThread() { +- return Bukkit.isPrimaryThread(); ++ return Thread.currentThread() instanceof TickThread; ++ } ++ ++ public static boolean isShutdownThread() { ++ return false; ++ } ++ ++ public static boolean isTickThreadFor(final ServerLevel world, final BlockPos pos) { ++ return isTickThread(); ++ } ++ ++ public static boolean isTickThreadFor(final ServerLevel world, final ChunkPos pos) { ++ return isTickThread(); ++ } ++ ++ public static boolean isTickThreadFor(final ServerLevel world, final Vec3 pos) { ++ return isTickThread(); + } + + public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ) { + return isTickThread(); + } + ++ public static boolean isTickThreadFor(final ServerLevel world, final AABB aabb) { ++ return isTickThread(); ++ } ++ ++ public static boolean isTickThreadFor(final ServerLevel world, final double blockX, final double blockZ) { ++ return isTickThread(); ++ } ++ ++ public static boolean isTickThreadFor(final ServerLevel world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { ++ return isTickThread(); ++ } ++ ++ public static boolean isTickThreadFor(final ServerLevel world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { ++ return isTickThread(); ++ } ++ + public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ, final int radius) { + return isTickThread(); + } +diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7e8dc9e8f381abfdcce2746edc93122d623622d1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +@@ -0,0 +1,606 @@ ++package io.papermc.paper.world; ++ ++import com.destroystokyo.paper.util.maplist.EntityList; ++import io.papermc.paper.chunk.system.entity.EntityLookup; ++import io.papermc.paper.util.TickThread; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.FullChunkStatus; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.entity.boss.EnderDragonPart; ++import net.minecraft.world.entity.boss.enderdragon.EnderDragon; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.storage.EntityStorage; ++import net.minecraft.world.level.entity.Visibility; ++import net.minecraft.world.phys.AABB; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.List; ++import java.util.function.Predicate; ++ ++public final class ChunkEntitySlices { ++ ++ protected final int minSection; ++ protected final int maxSection; ++ public final int chunkX; ++ public final int chunkZ; ++ protected final ServerLevel world; ++ ++ protected final EntityCollectionBySection allEntities; ++ protected final EntityCollectionBySection hardCollidingEntities; ++ protected final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByClass; ++ protected final EntityList entities = new EntityList(); ++ ++ public FullChunkStatus status; ++ ++ protected boolean isTransient; ++ ++ public boolean isTransient() { ++ return this.isTransient; ++ } ++ ++ public void setTransient(final boolean value) { ++ this.isTransient = value; ++ } ++ ++ // TODO implement container search optimisations ++ ++ public ChunkEntitySlices(final ServerLevel world, final int chunkX, final int chunkZ, final FullChunkStatus status, ++ final int minSection, final int maxSection) { // inclusive, inclusive ++ this.minSection = minSection; ++ this.maxSection = maxSection; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.world = world; ++ ++ this.allEntities = new EntityCollectionBySection(this); ++ this.hardCollidingEntities = new EntityCollectionBySection(this); ++ this.entitiesByClass = new Reference2ObjectOpenHashMap<>(); ++ ++ this.status = status; ++ } ++ ++ // Paper start - optimise CraftChunk#getEntities ++ public org.bukkit.entity.Entity[] getChunkEntities() { ++ List ret = new java.util.ArrayList<>(); ++ final Entity[] entities = this.entities.getRawData(); ++ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { ++ final Entity entity = entities[i]; ++ if (entity == null) { ++ continue; ++ } ++ final org.bukkit.entity.Entity bukkit = entity.getBukkitEntity(); ++ if (bukkit != null && bukkit.isValid()) { ++ ret.add(bukkit); ++ } ++ } ++ ++ return ret.toArray(new org.bukkit.entity.Entity[0]); ++ } ++ ++ public CompoundTag save() { ++ final int len = this.entities.size(); ++ if (len == 0) { ++ return null; ++ } ++ ++ final Entity[] rawData = this.entities.getRawData(); ++ final List collectedEntities = new ArrayList<>(len); ++ for (int i = 0; i < len; ++i) { ++ final Entity entity = rawData[i]; ++ if (entity.shouldBeSaved()) { ++ collectedEntities.add(entity); ++ } ++ } ++ ++ if (collectedEntities.isEmpty()) { ++ return null; ++ } ++ ++ return EntityStorage.saveEntityChunk(collectedEntities, new ChunkPos(this.chunkX, this.chunkZ), this.world); ++ } ++ ++ // returns true if this chunk has transient entities remaining ++ public boolean unload() { ++ final int len = this.entities.size(); ++ final Entity[] collectedEntities = Arrays.copyOf(this.entities.getRawData(), len); ++ ++ for (int i = 0; i < len; ++i) { ++ final Entity entity = collectedEntities[i]; ++ if (entity.isRemoved()) { ++ // removed by us below ++ continue; ++ } ++ if (entity.shouldBeSaved()) { ++ entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK); ++ if (entity.isVehicle()) { ++ // we cannot assume that these entities are contained within this chunk, because entities can ++ // desync - so we need to remove them all ++ for (final Entity passenger : entity.getIndirectPassengers()) { ++ passenger.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK); ++ } ++ } ++ } ++ } ++ ++ return this.entities.size() != 0; ++ } ++ ++ private List getAllEntities() { ++ final int len = this.entities.size(); ++ if (len == 0) { ++ return new ArrayList<>(); ++ } ++ ++ final Entity[] rawData = this.entities.getRawData(); ++ final List collectedEntities = new ArrayList<>(len); ++ for (int i = 0; i < len; ++i) { ++ collectedEntities.add(rawData[i]); ++ } ++ ++ return collectedEntities; ++ } ++ ++ public void callEntitiesLoadEvent() { ++ CraftEventFactory.callEntitiesLoadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); ++ } ++ ++ public void callEntitiesUnloadEvent() { ++ CraftEventFactory.callEntitiesUnloadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); ++ } ++ // Paper end - optimise CraftChunk#getEntities ++ ++ public boolean isEmpty() { ++ return this.entities.size() == 0; ++ } ++ ++ public void mergeInto(final ChunkEntitySlices slices) { ++ final Entity[] entities = this.entities.getRawData(); ++ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { ++ final Entity entity = entities[i]; ++ slices.addEntity(entity, entity.sectionY); ++ } ++ } ++ ++ private boolean preventStatusUpdates; ++ public boolean startPreventingStatusUpdates() { ++ final boolean ret = this.preventStatusUpdates; ++ this.preventStatusUpdates = true; ++ return ret; ++ } ++ ++ public boolean isPreventingStatusUpdates() { ++ return this.preventStatusUpdates; ++ } ++ ++ public void stopPreventingStatusUpdates(final boolean prev) { ++ this.preventStatusUpdates = prev; ++ } ++ ++ public void updateStatus(final FullChunkStatus status, final EntityLookup lookup) { ++ this.status = status; ++ ++ final Entity[] entities = this.entities.getRawData(); ++ ++ for (int i = 0, size = this.entities.size(); i < size; ++i) { ++ final Entity entity = entities[i]; ++ ++ final Visibility oldVisibility = EntityLookup.getEntityStatus(entity); ++ entity.chunkStatus = status; ++ final Visibility newVisibility = EntityLookup.getEntityStatus(entity); ++ ++ lookup.entityStatusChange(entity, this, oldVisibility, newVisibility, false, false, false); ++ } ++ } ++ ++ public boolean addEntity(final Entity entity, final int chunkSection) { ++ if (!this.entities.add(entity)) { ++ return false; ++ } ++ entity.chunkStatus = this.status; ++ final int sectionIndex = chunkSection - this.minSection; ++ ++ this.allEntities.addEntity(entity, sectionIndex); ++ ++ if (entity.hardCollides()) { ++ this.hardCollidingEntities.addEntity(entity, sectionIndex); ++ } ++ ++ for (final Iterator, EntityCollectionBySection>> iterator = ++ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); ++ ++ if (entry.getKey().isInstance(entity)) { ++ entry.getValue().addEntity(entity, sectionIndex); ++ } ++ } ++ ++ return true; ++ } ++ ++ public boolean removeEntity(final Entity entity, final int chunkSection) { ++ if (!this.entities.remove(entity)) { ++ return false; ++ } ++ entity.chunkStatus = null; ++ final int sectionIndex = chunkSection - this.minSection; ++ ++ this.allEntities.removeEntity(entity, sectionIndex); ++ ++ if (entity.hardCollides()) { ++ this.hardCollidingEntities.removeEntity(entity, sectionIndex); ++ } ++ ++ for (final Iterator, EntityCollectionBySection>> iterator = ++ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); ++ ++ if (entry.getKey().isInstance(entity)) { ++ entry.getValue().removeEntity(entity, sectionIndex); ++ } ++ } ++ ++ return true; ++ } ++ ++ public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { ++ this.hardCollidingEntities.getEntities(except, box, into, predicate); ++ } ++ ++ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { ++ this.allEntities.getEntitiesWithEnderDragonParts(except, box, into, predicate); ++ } ++ ++ public void getEntitiesWithoutDragonParts(final Entity except, final AABB box, final List into, final Predicate predicate) { ++ this.allEntities.getEntities(except, box, into, predicate); ++ } ++ ++ public void getEntities(final EntityType type, final AABB box, final List into, ++ final Predicate predicate) { ++ this.allEntities.getEntities(type, box, (List)into, (Predicate)predicate); ++ } ++ ++ protected EntityCollectionBySection initClass(final Class clazz) { ++ final EntityCollectionBySection ret = new EntityCollectionBySection(this); ++ ++ for (int sectionIndex = 0; sectionIndex < this.allEntities.entitiesBySection.length; ++sectionIndex) { ++ final BasicEntityList sectionEntities = this.allEntities.entitiesBySection[sectionIndex]; ++ if (sectionEntities == null) { ++ continue; ++ } ++ ++ final Entity[] storage = sectionEntities.storage; ++ ++ for (int i = 0, len = Math.min(storage.length, sectionEntities.size()); i < len; ++i) { ++ final Entity entity = storage[i]; ++ ++ if (clazz.isInstance(entity)) { ++ ret.addEntity(entity, sectionIndex); ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ public void getEntities(final Class clazz, final Entity except, final AABB box, final List into, ++ final Predicate predicate) { ++ EntityCollectionBySection collection = this.entitiesByClass.get(clazz); ++ if (collection != null) { ++ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); ++ } else { ++ this.entitiesByClass.putIfAbsent(clazz, collection = this.initClass(clazz)); ++ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); ++ } ++ } ++ ++ protected static final class BasicEntityList { ++ ++ protected static final Entity[] EMPTY = new Entity[0]; ++ protected static final int DEFAULT_CAPACITY = 4; ++ ++ protected E[] storage; ++ protected int size; ++ ++ public BasicEntityList() { ++ this(0); ++ } ++ ++ public BasicEntityList(final int cap) { ++ this.storage = (E[])(cap <= 0 ? EMPTY : new Entity[cap]); ++ } ++ ++ public boolean isEmpty() { ++ return this.size == 0; ++ } ++ ++ public int size() { ++ return this.size; ++ } ++ ++ private void resize() { ++ if (this.storage == EMPTY) { ++ this.storage = (E[])new Entity[DEFAULT_CAPACITY]; ++ } else { ++ this.storage = Arrays.copyOf(this.storage, this.storage.length * 2); ++ } ++ } ++ ++ public void add(final E entity) { ++ final int idx = this.size++; ++ if (idx >= this.storage.length) { ++ this.resize(); ++ this.storage[idx] = entity; ++ } else { ++ this.storage[idx] = entity; ++ } ++ } ++ ++ public int indexOf(final E entity) { ++ final E[] storage = this.storage; ++ ++ for (int i = 0, len = Math.min(this.storage.length, this.size); i < len; ++i) { ++ if (storage[i] == entity) { ++ return i; ++ } ++ } ++ ++ return -1; ++ } ++ ++ public boolean remove(final E entity) { ++ final int idx = this.indexOf(entity); ++ if (idx == -1) { ++ return false; ++ } ++ ++ final int size = --this.size; ++ final E[] storage = this.storage; ++ if (idx != size) { ++ System.arraycopy(storage, idx + 1, storage, idx, size - idx); ++ } ++ ++ storage[size] = null; ++ ++ return true; ++ } ++ ++ public boolean has(final E entity) { ++ return this.indexOf(entity) != -1; ++ } ++ } ++ ++ protected static final class EntityCollectionBySection { ++ ++ protected final ChunkEntitySlices manager; ++ protected final long[] nonEmptyBitset; ++ protected final BasicEntityList[] entitiesBySection; ++ protected int count; ++ ++ public EntityCollectionBySection(final ChunkEntitySlices manager) { ++ this.manager = manager; ++ ++ final int sectionCount = manager.maxSection - manager.minSection + 1; ++ ++ this.nonEmptyBitset = new long[(sectionCount + (Long.SIZE - 1)) >>> 6]; // (sectionCount + (Long.SIZE - 1)) / Long.SIZE ++ this.entitiesBySection = new BasicEntityList[sectionCount]; ++ } ++ ++ public void addEntity(final Entity entity, final int sectionIndex) { ++ BasicEntityList list = this.entitiesBySection[sectionIndex]; ++ ++ if (list != null && list.has(entity)) { ++ return; ++ } ++ ++ if (list == null) { ++ this.entitiesBySection[sectionIndex] = list = new BasicEntityList<>(); ++ this.nonEmptyBitset[sectionIndex >>> 6] |= (1L << (sectionIndex & (Long.SIZE - 1))); ++ } ++ ++ list.add(entity); ++ ++this.count; ++ } ++ ++ public void removeEntity(final Entity entity, final int sectionIndex) { ++ final BasicEntityList list = this.entitiesBySection[sectionIndex]; ++ ++ if (list == null || !list.remove(entity)) { ++ return; ++ } ++ ++ --this.count; ++ ++ if (list.isEmpty()) { ++ this.entitiesBySection[sectionIndex] = null; ++ this.nonEmptyBitset[sectionIndex >>> 6] ^= (1L << (sectionIndex & (Long.SIZE - 1))); ++ } ++ } ++ ++ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { ++ if (this.count == 0) { ++ return; ++ } ++ ++ final int minSection = this.manager.minSection; ++ final int maxSection = this.manager.maxSection; ++ ++ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); ++ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); ++ ++ final BasicEntityList[] entitiesBySection = this.entitiesBySection; ++ ++ for (int section = min; section <= max; ++section) { ++ final BasicEntityList list = entitiesBySection[section - minSection]; ++ ++ if (list == null) { ++ continue; ++ } ++ ++ final Entity[] storage = list.storage; ++ ++ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { ++ final Entity entity = storage[i]; ++ ++ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { ++ continue; ++ } ++ ++ if (predicate != null && !predicate.test(entity)) { ++ continue; ++ } ++ ++ into.add(entity); ++ } ++ } ++ } ++ ++ public void getEntitiesWithEnderDragonParts(final Entity except, final AABB box, final List into, ++ final Predicate predicate) { ++ if (this.count == 0) { ++ return; ++ } ++ ++ final int minSection = this.manager.minSection; ++ final int maxSection = this.manager.maxSection; ++ ++ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); ++ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); ++ ++ final BasicEntityList[] entitiesBySection = this.entitiesBySection; ++ ++ for (int section = min; section <= max; ++section) { ++ final BasicEntityList list = entitiesBySection[section - minSection]; ++ ++ if (list == null) { ++ continue; ++ } ++ ++ final Entity[] storage = list.storage; ++ ++ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { ++ final Entity entity = storage[i]; ++ ++ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { ++ continue; ++ } ++ ++ if (predicate == null || predicate.test(entity)) { ++ into.add(entity); ++ } // else: continue to test the ender dragon parts ++ ++ if (entity instanceof EnderDragon) { ++ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { ++ if (part == except || !part.getBoundingBox().intersects(box)) { ++ continue; ++ } ++ ++ if (predicate != null && !predicate.test(part)) { ++ continue; ++ } ++ ++ into.add(part); ++ } ++ } ++ } ++ } ++ } ++ ++ public void getEntitiesWithEnderDragonParts(final Entity except, final Class clazz, final AABB box, final List into, ++ final Predicate predicate) { ++ if (this.count == 0) { ++ return; ++ } ++ ++ final int minSection = this.manager.minSection; ++ final int maxSection = this.manager.maxSection; ++ ++ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); ++ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); ++ ++ final BasicEntityList[] entitiesBySection = this.entitiesBySection; ++ ++ for (int section = min; section <= max; ++section) { ++ final BasicEntityList list = entitiesBySection[section - minSection]; ++ ++ if (list == null) { ++ continue; ++ } ++ ++ final Entity[] storage = list.storage; ++ ++ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { ++ final Entity entity = storage[i]; ++ ++ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { ++ continue; ++ } ++ ++ if (predicate == null || predicate.test(entity)) { ++ into.add(entity); ++ } // else: continue to test the ender dragon parts ++ ++ if (entity instanceof EnderDragon) { ++ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { ++ if (part == except || !part.getBoundingBox().intersects(box) || !clazz.isInstance(part)) { ++ continue; ++ } ++ ++ if (predicate != null && !predicate.test(part)) { ++ continue; ++ } ++ ++ into.add(part); ++ } ++ } ++ } ++ } ++ } ++ ++ public void getEntities(final EntityType type, final AABB box, final List into, ++ final Predicate predicate) { ++ if (this.count == 0) { ++ return; ++ } ++ ++ final int minSection = this.manager.minSection; ++ final int maxSection = this.manager.maxSection; ++ ++ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); ++ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); ++ ++ final BasicEntityList[] entitiesBySection = this.entitiesBySection; ++ ++ for (int section = min; section <= max; ++section) { ++ final BasicEntityList list = entitiesBySection[section - minSection]; ++ ++ if (list == null) { ++ continue; ++ } ++ ++ final Entity[] storage = list.storage; ++ ++ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { ++ final Entity entity = storage[i]; ++ ++ if (entity == null || (type != null && entity.getType() != type) || !entity.getBoundingBox().intersects(box)) { ++ continue; ++ } ++ ++ if (predicate != null && !predicate.test((T)entity)) { ++ continue; ++ } ++ ++ into.add((T)entity); ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index deb2d8c22a1c5724d0ac8571f4ea54711988dc4b..72d013d06705b08ed696e3d3b6d631d65800c2c9 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -327,6 +327,7 @@ public class Main { + + convertable_conversionsession.saveDataTag(iregistrycustom_dimension, savedata); + */ ++ Class.forName(net.minecraft.world.entity.npc.VillagerTrades.class.getName()); // Paper - load this sync so it won't fail later async + final DedicatedServer dedicatedserver = (DedicatedServer) MinecraftServer.spin((thread) -> { + DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, worldLoader.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::new); + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 48da5bdabcf38afbbd1509eca56d5c761622409f..48e3b0f065b370f780f8a6145fba9f3f7976badb 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -311,7 +311,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { + AtomicReference atomicreference = new AtomicReference(); +- Thread thread = new Thread(() -> { ++ Thread thread = new io.papermc.paper.util.TickThread(() -> { // Paper - rewrite chunk system + ((MinecraftServer) atomicreference.get()).runServer(); + }, "Server thread"); + +@@ -643,7 +643,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { +- return worldserver1.getChunkSource().chunkMap.hasWork(); +- })) { +- this.nextTickTimeNanos = Util.getNanos() + TimeUtil.NANOSECONDS_PER_MILLISECOND; +- iterator = this.getAllLevels().iterator(); +- +- while (iterator.hasNext()) { +- worldserver = (ServerLevel) iterator.next(); +- worldserver.getChunkSource().removeTicketsOnClosing(); +- worldserver.getChunkSource().tick(() -> { +- return true; +- }, false); +- } +- +- this.waitUntilNextTick(); +- } +- +- this.saveAllChunks(false, true, false); +- iterator = this.getAllLevels().iterator(); +- +- while (iterator.hasNext()) { +- worldserver = (ServerLevel) iterator.next(); +- if (worldserver != null) { +- try { +- worldserver.close(); +- } catch (IOException ioexception) { +- MinecraftServer.LOGGER.error("Exception closing the level", ioexception); +- } +- } +- } ++ this.saveAllChunks(false, true, false, true); // Paper - rewrite chunk system - move closing into here + + this.isSaving = false; + this.resources.close(); +@@ -1014,6 +995,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { +- for (final Entity entity : level.getEntities().getAll()) { ++ for (final Entity entity : level.getEntityLookup().getAllCopy()) { // Paper - rewrite chunk system + if (entity.isRemoved()) { + continue; + } +@@ -2520,7 +2515,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { ++ LOGGER.info("Async debug chunks executing"); ++ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(false); ++ CommandSender sender = MinecraftServer.getServer().console; ++ java.io.File file = new java.io.File(new java.io.File(new java.io.File("."), "debug"), ++ "chunks-" + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(java.time.LocalDateTime.now()) + ".txt"); ++ sender.sendMessage(net.kyori.adventure.text.Component.text("Writing chunk information dump to " + file, net.kyori.adventure.text.format.NamedTextColor.GREEN)); ++ try { ++ io.papermc.paper.util.MCUtil.dumpChunks(file, true); ++ sender.sendMessage(net.kyori.adventure.text.Component.text("Successfully written chunk information!", net.kyori.adventure.text.format.NamedTextColor.GREEN)); ++ } catch (Throwable thr) { ++ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); ++ sender.sendMessage(net.kyori.adventure.text.Component.text("Failed to dump chunk information, see console", net.kyori.adventure.text.format.NamedTextColor.RED)); ++ } ++ }; ++ Thread t = new Thread(run); ++ t.setName("Async debug thread #" + ASYNC_DEBUG_CHUNKS_COUNT.getAndIncrement()); ++ t.setDaemon(true); ++ t.start(); ++ return; ++ } ++ // Paper end - rewrite chunk system + this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); // Paper - Perf: use proper queue + } + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 807a6bb1026dac2c4cd0a50afe06fd62ce23558b..2b998bdbe49bf8211b755e0eb7c1bf13ac280eab 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -48,17 +48,12 @@ public class ChunkHolder { + private static final Either NOT_DONE_YET = Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED); + private static final CompletableFuture> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK); + private static final List CHUNK_STATUSES = ChunkStatus.getStatusList(); +- private final AtomicReferenceArray>> futures; ++ // Paper - rewrite chunk system + private final LevelHeightAccessor levelHeightAccessor; +- private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage +- private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage +- private volatile CompletableFuture> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage +- public CompletableFuture chunkToSave; // Paper - public ++ // Paper - rewrite chunk system + @Nullable + private final DebugBuffer chunkToSaveHistory; +- public int oldTicketLevel; +- private int ticketLevel; +- private int queueLevel; ++ // Paper - rewrite chunk system + public final ChunkPos pos; + private boolean hasChangedSections; + private final ShortSet[] changedBlocksPerSection; +@@ -67,11 +62,20 @@ public class ChunkHolder { + private final LevelLightEngine lightEngine; + private final ChunkHolder.LevelChangeListener onLevelChange; + public final ChunkHolder.PlayerProvider playerProvider; +- private boolean wasAccessibleSinceLastSave; +- private CompletableFuture pendingFullStateConfirmation; +- private CompletableFuture sendSync; ++ // Paper - rewrite chunk system + + private final ChunkMap chunkMap; // Paper ++ // Paper start - no-tick view distance ++ public final LevelChunk getSendingChunk() { ++ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used ++ // in Chunk's neighbour callback ++ LevelChunk ret = this.chunkMap.level.getChunkSource().getChunkAtIfLoadedImmediately(this.pos.x, this.pos.z); ++ if (ret != null && ret.areNeighboursLoaded(1)) { ++ return ret; ++ } ++ return null; ++ } ++ // Paper end - no-tick view distance + + // Paper start + public void onChunkAdd() { +@@ -83,158 +87,143 @@ public class ChunkHolder { + } + // Paper end + +- public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { +- this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); +- this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; +- this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; +- this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; +- this.chunkToSave = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error ++ public final io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder; // Paper - rewrite chunk system ++ ++ // Paper start - replace player chunk loader ++ private final com.destroystokyo.paper.util.maplist.ReferenceList playersSentChunkTo = new com.destroystokyo.paper.util.maplist.ReferenceList<>(); ++ ++ public void addPlayer(ServerPlayer player) { ++ if (!this.playersSentChunkTo.add(player)) { ++ throw new IllegalStateException("Already sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player); ++ } ++ } ++ ++ public void removePlayer(ServerPlayer player) { ++ if (!this.playersSentChunkTo.remove(player)) { ++ throw new IllegalStateException("Have not sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player); ++ } ++ } ++ ++ public boolean hasChunkBeenSent() { ++ return this.playersSentChunkTo.size() != 0; ++ } ++ ++ public boolean hasBeenSent(ServerPlayer to) { ++ return this.playersSentChunkTo.contains(to); ++ } ++ // Paper end - replace player chunk loader ++ public ChunkHolder(ChunkPos pos, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.PlayerProvider playersWatchingChunkProvider, io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder) { // Paper - rewrite chunk system ++ this.newChunkHolder = newChunkHolder; // Paper - rewrite chunk system + this.chunkToSaveHistory = null; + this.blockChangedLightSectionFilter = new BitSet(); + this.skyChangedLightSectionFilter = new BitSet(); +- this.pendingFullStateConfirmation = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error +- this.sendSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error ++ // Paper - rewrite chunk system + this.pos = pos; + this.levelHeightAccessor = world; + this.lightEngine = lightingProvider; +- this.onLevelChange = levelUpdateListener; ++ this.onLevelChange = null; // Paper - rewrite chunk system + this.playerProvider = playersWatchingChunkProvider; +- this.oldTicketLevel = ChunkLevel.MAX_LEVEL + 1; +- this.ticketLevel = this.oldTicketLevel; +- this.queueLevel = this.oldTicketLevel; +- this.setTicketLevel(level); ++ // Paper - rewrite chunk system + this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()]; + this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper + } + + // Paper start + public @Nullable ChunkAccess getAvailableChunkNow() { +- // TODO can we just getStatusFuture(EMPTY)? +- for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) { +- CompletableFuture> future = this.getFutureIfPresentUnchecked(curr); +- Either either = future.getNow(null); +- if (either == null || either.left().isEmpty()) { +- continue; +- } +- return either.left().get(); +- } +- return null; ++ return this.newChunkHolder.getCurrentChunk(); // Paper - rewrite chunk system + } + // Paper end + // CraftBukkit start + public LevelChunk getFullChunkNow() { +- // Note: We use the oldTicketLevel for isLoaded checks. +- if (!ChunkLevel.fullStatus(this.oldTicketLevel).isOrAfter(FullChunkStatus.FULL)) return null; +- return this.getFullChunkNowUnchecked(); ++ // Paper start - rewrite chunk system ++ ChunkAccess chunk = this.getAvailableChunkNow(); ++ if (!this.isFullChunkReady() || !(chunk instanceof LevelChunk)) return null; // instanceof to avoid a race condition on off-main threads ++ return (LevelChunk)chunk; ++ // Paper end - rewrite chunk system + } + + public LevelChunk getFullChunkNowUnchecked() { +- CompletableFuture> statusFuture = this.getFutureIfPresentUnchecked(ChunkStatus.FULL); +- Either either = (Either) statusFuture.getNow(null); +- return (either == null) ? null : (LevelChunk) either.left().orElse(null); ++ // Paper start - rewrite chunk system ++ ChunkAccess chunk = this.getAvailableChunkNow(); ++ return chunk instanceof LevelChunk ? (LevelChunk)chunk : null; ++ // Paper end - rewrite chunk system + } + // CraftBukkit end + + public CompletableFuture> getFutureIfPresentUnchecked(ChunkStatus leastStatus) { +- CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(leastStatus.getIndex()); +- +- return completablefuture == null ? ChunkHolder.UNLOADED_CHUNK_FUTURE : completablefuture; ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + public CompletableFuture> getFutureIfPresent(ChunkStatus leastStatus) { +- return ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(leastStatus) ? this.getFutureIfPresentUnchecked(leastStatus) : ChunkHolder.UNLOADED_CHUNK_FUTURE; ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + public final CompletableFuture> getTickingChunkFuture() { // Paper - final for inline +- return this.tickingChunkFuture; ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + public final CompletableFuture> getEntityTickingChunkFuture() { // Paper - final for inline +- return this.entityTickingChunkFuture; ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + public final CompletableFuture> getFullChunkFuture() { // Paper - final for inline +- return this.fullChunkFuture; ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + @Nullable + public final LevelChunk getTickingChunk() { // Paper - final for inline +- CompletableFuture> completablefuture = this.getTickingChunkFuture(); +- Either either = (Either) completablefuture.getNow(null); // CraftBukkit - decompile error +- +- return either == null ? null : (LevelChunk) either.left().orElse(null); // CraftBukkit - decompile error ++ // Paper start - rewrite chunk system ++ if (!this.isTickingReady()) { ++ return null; ++ } ++ return (LevelChunk)this.getAvailableChunkNow(); ++ // Paper end - rewrite chunk system + } + + public CompletableFuture getChunkSendSyncFuture() { +- return this.sendSync; ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + @Nullable + public LevelChunk getChunkToSend() { +- return !this.sendSync.isDone() ? null : this.getTickingChunk(); ++ return this.getSendingChunk(); // Paper - rewrite chunk system + } + + @Nullable + public final LevelChunk getFullChunk() { // Paper - final for inline +- CompletableFuture> completablefuture = this.getFullChunkFuture(); +- Either either = (Either) completablefuture.getNow(null); // CraftBukkit - decompile error +- +- return either == null ? null : (LevelChunk) either.left().orElse(null); // CraftBukkit - decompile error ++ // Paper start - rewrite chunk system ++ if (!this.isFullChunkReady()) { ++ return null; ++ } ++ return (LevelChunk)this.getAvailableChunkNow(); ++ // Paper end - rewrite chunk system + } + + @Nullable + public ChunkStatus getLastAvailableStatus() { +- for (int i = ChunkHolder.CHUNK_STATUSES.size() - 1; i >= 0; --i) { +- ChunkStatus chunkstatus = (ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i); +- CompletableFuture> completablefuture = this.getFutureIfPresentUnchecked(chunkstatus); +- +- if (((Either) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).left().isPresent()) { +- return chunkstatus; +- } +- } +- +- return null; ++ return this.newChunkHolder.getCurrentGenStatus(); // Paper - rewrite chunk system + } + + // Paper start + public ChunkStatus getChunkHolderStatus() { +- for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) { +- CompletableFuture> future = this.getFutureIfPresentUnchecked(curr); +- Either either = future.getNow(null); +- if (either == null || !either.left().isPresent()) { +- continue; +- } +- return curr; +- } +- +- return null; ++ return this.newChunkHolder.getCurrentGenStatus(); // Paper - rewrite chunk system + } + // Paper end + + @Nullable + public ChunkAccess getLastAvailable() { +- for (int i = ChunkHolder.CHUNK_STATUSES.size() - 1; i >= 0; --i) { +- ChunkStatus chunkstatus = (ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i); +- CompletableFuture> completablefuture = this.getFutureIfPresentUnchecked(chunkstatus); +- +- if (!completablefuture.isCompletedExceptionally()) { +- Optional optional = ((Either) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).left(); +- +- if (optional.isPresent()) { +- return (ChunkAccess) optional.get(); +- } +- } +- } +- +- return null; ++ return this.newChunkHolder.getCurrentChunk(); // Paper - rewrite chunk system + } + +- public final CompletableFuture getChunkToSave() { // Paper - final for inline +- return this.chunkToSave; +- } ++ // Paper - rewrite chunk system + + public void blockChanged(BlockPos pos) { +- LevelChunk chunk = this.getTickingChunk(); ++ // Paper start - replace player chunk loader ++ if (this.playersSentChunkTo.size() == 0) { ++ return; ++ } ++ // Paper end - replace player chunk loader ++ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance + + if (chunk != null) { + int i = this.levelHeightAccessor.getSectionIndex(pos.getY()); +@@ -250,16 +239,17 @@ public class ChunkHolder { + } + + public void sectionLightChanged(LightLayer lightType, int y) { +- Either either = (Either) this.getFutureIfPresent(ChunkStatus.INITIALIZE_LIGHT).getNow(null); // CraftBukkit - decompile error ++ // Paper start - no-tick view distance + +- if (either != null) { +- ChunkAccess ichunkaccess = (ChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error ++ if (true) { ++ ChunkAccess ichunkaccess = this.getAvailableChunkNow(); + + if (ichunkaccess != null) { + ichunkaccess.setUnsaved(true); +- LevelChunk chunk = this.getTickingChunk(); ++ LevelChunk chunk = this.getSendingChunk(); ++ // Paper end - no-tick view distance + +- if (chunk != null) { ++ if (this.playersSentChunkTo.size() != 0 && chunk != null) { // Paper - replace player chunk loader + int j = this.lightEngine.getMinLightSection(); + int k = this.lightEngine.getMaxLightSection(); + +@@ -284,7 +274,7 @@ public class ChunkHolder { + List list; + + if (!this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) { +- list = this.playerProvider.getPlayers(this.pos, true); ++ list = this.getPlayers(true); // Paper - rewrite chunk system + if (!list.isEmpty()) { + ClientboundLightUpdatePacket packetplayoutlightupdate = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, this.skyChangedLightSectionFilter, this.blockChangedLightSectionFilter); + +@@ -296,7 +286,7 @@ public class ChunkHolder { + } + + if (this.hasChangedSections) { +- list = this.playerProvider.getPlayers(this.pos, false); ++ list = this.getPlayers(false); // Paper - rewrite chunk system + + for (int i = 0; i < this.changedBlocksPerSection.length; ++i) { + ShortSet shortset = this.changedBlocksPerSection[i]; +@@ -354,78 +344,35 @@ public class ChunkHolder { + + } + +- private void broadcast(List players, Packet packet) { +- players.forEach((entityplayer) -> { +- entityplayer.connection.send(packet); +- }); +- } +- +- public CompletableFuture> getOrScheduleFuture(ChunkStatus targetStatus, ChunkMap chunkStorage) { +- int i = targetStatus.getIndex(); +- CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(i); +- +- if (completablefuture != null) { +- Either either = (Either) completablefuture.getNow(ChunkHolder.NOT_DONE_YET); +- +- if (either == null) { +- String s = "value in future for status: " + targetStatus + " was incorrectly set to null at chunk: " + this.pos; +- +- throw chunkStorage.debugFuturesAndCreateReportedException(new IllegalStateException("null value previously set for chunk status"), s); +- } +- +- if (either == ChunkHolder.NOT_DONE_YET || either.right().isEmpty()) { +- return completablefuture; ++ // Paper start - rewrite chunk system ++ public List getPlayers(boolean onlyOnWatchDistanceEdge){ ++ List ret = new java.util.ArrayList<>(); ++ for (int i = 0, len = this.playersSentChunkTo.size(); i < len; ++i) { ++ ServerPlayer player = this.playersSentChunkTo.getUnchecked(i); ++ if (onlyOnWatchDistanceEdge && !this.chunkMap.level.playerChunkLoader.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) { ++ continue; + } ++ ret.add(player); + } + +- if (ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(targetStatus)) { +- CompletableFuture> completablefuture1 = chunkStorage.schedule(this, targetStatus); +- +- this.updateChunkToSave(completablefuture1, "schedule " + targetStatus); +- this.futures.set(i, completablefuture1); +- return completablefuture1; +- } else { +- return completablefuture == null ? ChunkHolder.UNLOADED_CHUNK_FUTURE : completablefuture; +- } ++ return ret; + } + +- protected void addSaveDependency(String thenDesc, CompletableFuture then) { +- if (this.chunkToSaveHistory != null) { +- this.chunkToSaveHistory.push(new ChunkHolder.ChunkSaveDebug(Thread.currentThread(), then, thenDesc)); +- } +- +- this.chunkToSave = this.chunkToSave.thenCombine(then, (ichunkaccess, object) -> { +- return ichunkaccess; +- }); ++ public void broadcast(Packet packet, boolean onlyOnWatchDistanceEdge) { ++ this.broadcast(this.getPlayers(onlyOnWatchDistanceEdge), packet); + } ++ // Paper end - rewrite chunk system + +- private void updateChunkToSave(CompletableFuture> then, String thenDesc) { +- if (this.chunkToSaveHistory != null) { +- this.chunkToSaveHistory.push(new ChunkHolder.ChunkSaveDebug(Thread.currentThread(), then, thenDesc)); +- } +- +- this.chunkToSave = this.chunkToSave.thenCombine(then, (ichunkaccess, either) -> { +- return (ChunkAccess) either.map((ichunkaccess1) -> { +- return ichunkaccess1; +- }, (playerchunk_failure) -> { +- return ichunkaccess; +- }); ++ private void broadcast(List players, Packet packet) { ++ players.forEach((entityplayer) -> { ++ entityplayer.connection.send(packet); + }); + } + +- public void addSendDependency(CompletableFuture postProcessingFuture) { +- if (this.sendSync.isDone()) { +- this.sendSync = postProcessingFuture; +- } else { +- this.sendSync = this.sendSync.thenCombine(postProcessingFuture, (object, object1) -> { +- return null; +- }); +- } +- +- } ++ // Paper - rewrite chunk system + + public FullChunkStatus getFullStatus() { +- return ChunkLevel.fullStatus(this.ticketLevel); ++ return this.newChunkHolder.getChunkStatus(); // Paper - rewrite chunk system) { + } + + public final ChunkPos getPos() { // Paper - final for inline +@@ -433,240 +380,17 @@ public class ChunkHolder { + } + + public final int getTicketLevel() { // Paper - final for inline +- return this.ticketLevel; +- } +- +- public int getQueueLevel() { +- return this.queueLevel; +- } +- +- private void setQueueLevel(int level) { +- this.queueLevel = level; +- } +- +- public void setTicketLevel(int level) { +- this.ticketLevel = level; +- } +- +- private void scheduleFullChunkPromotion(ChunkMap playerchunkmap, CompletableFuture> completablefuture, Executor executor, FullChunkStatus fullchunkstatus) { +- this.pendingFullStateConfirmation.cancel(false); +- CompletableFuture completablefuture1 = new CompletableFuture(); +- +- completablefuture1.thenRunAsync(() -> { +- playerchunkmap.onFullChunkStatusChange(this.pos, fullchunkstatus); +- }, executor); +- this.pendingFullStateConfirmation = completablefuture1; +- completablefuture.thenAccept((either) -> { +- either.ifLeft((chunk) -> { +- completablefuture1.complete(null); // CraftBukkit - decompile error +- }); +- }); +- } +- +- private void demoteFullChunk(ChunkMap playerchunkmap, FullChunkStatus fullchunkstatus) { +- this.pendingFullStateConfirmation.cancel(false); +- playerchunkmap.onFullChunkStatusChange(this.pos, fullchunkstatus); +- } +- +- protected void updateFutures(ChunkMap chunkStorage, Executor executor) { +- ChunkStatus chunkstatus = ChunkLevel.generationStatus(this.oldTicketLevel); +- ChunkStatus chunkstatus1 = ChunkLevel.generationStatus(this.ticketLevel); +- boolean flag = ChunkLevel.isLoaded(this.oldTicketLevel); +- boolean flag1 = ChunkLevel.isLoaded(this.ticketLevel); +- FullChunkStatus fullchunkstatus = ChunkLevel.fullStatus(this.oldTicketLevel); +- FullChunkStatus fullchunkstatus1 = ChunkLevel.fullStatus(this.ticketLevel); +- // CraftBukkit start +- // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. +- if (fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && !fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) { +- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { +- LevelChunk chunk = (LevelChunk)either.left().orElse(null); +- if (chunk != null) { +- chunkStorage.callbackExecutor.execute(() -> { +- // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick +- // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag. +- // These actions may however happen deferred, so we manually set the needsSaving flag already here. +- chunk.setUnsaved(true); +- chunk.unloadCallback(); +- }); +- } +- }).exceptionally((throwable) -> { +- // ensure exceptions are printed, by default this is not the case +- MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable); +- return null; +- }); +- +- // Run callback right away if the future was already done +- chunkStorage.callbackExecutor.run(); +- } +- // CraftBukkit end +- +- if (flag) { +- Either either = Either.right(new ChunkHolder.ChunkLoadingFailure() { +- public String toString() { +- return "Unloaded ticket level " + ChunkHolder.this.pos; +- } +- }); +- +- for (int i = flag1 ? chunkstatus1.getIndex() + 1 : 0; i <= chunkstatus.getIndex(); ++i) { +- CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(i); +- +- if (completablefuture == null) { +- this.futures.set(i, CompletableFuture.completedFuture(either)); +- } +- } +- } +- +- boolean flag2 = fullchunkstatus.isOrAfter(FullChunkStatus.FULL); +- boolean flag3 = fullchunkstatus1.isOrAfter(FullChunkStatus.FULL); +- +- this.wasAccessibleSinceLastSave |= flag3; +- if (!flag2 && flag3) { +- int expectCreateCount = ++this.fullChunkCreateCount; // Paper +- this.fullChunkFuture = chunkStorage.prepareAccessibleChunk(this); +- this.scheduleFullChunkPromotion(chunkStorage, this.fullChunkFuture, executor, FullChunkStatus.FULL); +- // Paper start - cache ticking ready status +- this.fullChunkFuture.thenAccept(either -> { +- final Optional left = either.left(); +- if (left.isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) { +- LevelChunk fullChunk = either.left().get(); +- ChunkHolder.this.isFullChunkReady = true; +- io.papermc.paper.chunk.system.ChunkSystem.onChunkBorder(fullChunk, this); +- } +- }); +- this.updateChunkToSave(this.fullChunkFuture, "full"); +- } +- +- if (flag2 && !flag3) { +- // Paper start +- if (this.isFullChunkReady) { +- io.papermc.paper.chunk.system.ChunkSystem.onChunkNotBorder(this.fullChunkFuture.join().left().get(), this); // Paper +- } +- // Paper end +- this.fullChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); +- this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; +- ++this.fullChunkCreateCount; // Paper - cache ticking ready status +- this.isFullChunkReady = false; // Paper - cache ticking ready status +- } +- +- boolean flag4 = fullchunkstatus.isOrAfter(FullChunkStatus.BLOCK_TICKING); +- boolean flag5 = fullchunkstatus1.isOrAfter(FullChunkStatus.BLOCK_TICKING); +- +- if (!flag4 && flag5) { +- this.tickingChunkFuture = chunkStorage.prepareTickingChunk(this); +- this.scheduleFullChunkPromotion(chunkStorage, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING); +- // Paper start - cache ticking ready status +- this.tickingChunkFuture.thenAccept(either -> { +- either.ifLeft(chunk -> { +- // note: Here is a very good place to add callbacks to logic waiting on this. +- ChunkHolder.this.isTickingReady = true; +- io.papermc.paper.chunk.system.ChunkSystem.onChunkTicking(chunk, this); +- }); +- }); +- // Paper end +- this.updateChunkToSave(this.tickingChunkFuture, "ticking"); +- } +- +- if (flag4 && !flag5) { +- // Paper start +- if (this.isTickingReady) { +- io.papermc.paper.chunk.system.ChunkSystem.onChunkNotTicking(this.tickingChunkFuture.join().left().get(), this); // Paper +- } +- // Paper end +- this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage +- this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; +- } +- +- boolean flag6 = fullchunkstatus.isOrAfter(FullChunkStatus.ENTITY_TICKING); +- boolean flag7 = fullchunkstatus1.isOrAfter(FullChunkStatus.ENTITY_TICKING); +- +- if (!flag6 && flag7) { +- if (this.entityTickingChunkFuture != ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE) { +- throw (IllegalStateException) Util.pauseInIde(new IllegalStateException()); +- } +- +- this.entityTickingChunkFuture = chunkStorage.prepareEntityTickingChunk(this); +- this.scheduleFullChunkPromotion(chunkStorage, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING); +- // Paper start - cache ticking ready status +- this.entityTickingChunkFuture.thenAccept(either -> { +- either.ifLeft(chunk -> { +- ChunkHolder.this.isEntityTickingReady = true; +- io.papermc.paper.chunk.system.ChunkSystem.onChunkEntityTicking(chunk, this); +- }); +- }); +- // Paper end +- this.updateChunkToSave(this.entityTickingChunkFuture, "entity ticking"); +- } +- +- if (flag6 && !flag7) { +- // Paper start +- if (this.isEntityTickingReady) { +- io.papermc.paper.chunk.system.ChunkSystem.onChunkNotEntityTicking(this.entityTickingChunkFuture.join().left().get(), this); +- } +- // Paper end +- this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage +- this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; +- } +- +- if (!fullchunkstatus1.isOrAfter(fullchunkstatus)) { +- this.demoteFullChunk(chunkStorage, fullchunkstatus1); +- } +- +- this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel); +- this.oldTicketLevel = this.ticketLevel; +- // CraftBukkit start +- // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. +- if (!fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) { +- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { +- LevelChunk chunk = (LevelChunk)either.left().orElse(null); +- if (chunk != null) { +- chunkStorage.callbackExecutor.execute(() -> { +- chunk.loadCallback(); +- }); +- } +- }).exceptionally((throwable) -> { +- // ensure exceptions are printed, by default this is not the case +- MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable); +- return null; +- }); +- +- // Run callback right away if the future was already done +- chunkStorage.callbackExecutor.run(); +- } +- // CraftBukkit end +- } +- +- public boolean wasAccessibleSinceLastSave() { +- return this.wasAccessibleSinceLastSave; ++ return this.newChunkHolder.getTicketLevel(); // Paper - rewrite chunk system + } + +- public void refreshAccessibility() { +- this.wasAccessibleSinceLastSave = ChunkLevel.fullStatus(this.ticketLevel).isOrAfter(FullChunkStatus.FULL); +- } ++ // Paper - rewrite chunk system + + public void replaceProtoChunk(ImposterProtoChunk chunk) { +- for (int i = 0; i < this.futures.length(); ++i) { +- CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(i); +- +- if (completablefuture != null) { +- Optional optional = ((Either) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).left(); +- +- if (!optional.isEmpty() && optional.get() instanceof ProtoChunk) { +- this.futures.set(i, CompletableFuture.completedFuture(Either.left(chunk))); +- } +- } +- } +- +- this.updateChunkToSave(CompletableFuture.completedFuture(Either.left(chunk.getWrapped())), "replaceProto"); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + public List>>> getAllFutures() { +- List>>> list = new ArrayList(); +- +- for (int i = 0; i < ChunkHolder.CHUNK_STATUSES.size(); ++i) { +- list.add(Pair.of((ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i), (CompletableFuture) this.futures.get(i))); +- } +- +- return list; ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + @FunctionalInterface +@@ -704,15 +428,15 @@ public class ChunkHolder { + + // Paper start + public final boolean isEntityTickingReady() { +- return this.isEntityTickingReady; ++ return this.newChunkHolder.isEntityTickingReady(); // Paper - rewrite chunk system + } + + public final boolean isTickingReady() { +- return this.isTickingReady; ++ return this.newChunkHolder.isTickingReady(); // Paper - rewrite chunk system + } + + public final boolean isFullChunkReady() { +- return this.isFullChunkReady; ++ return this.newChunkHolder.isFullChunkReady(); // Paper - rewrite chunk system + } + // Paper end + } +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 3ae47b86b80f9156e71d1da83e492153f360d1b5..5c1accb75655eadd4858ee24cdcdf9b200fbbcb2 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -119,10 +119,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public static final int MIN_VIEW_DISTANCE = 2; + public static final int MAX_VIEW_DISTANCE = 32; + public static final int FORCED_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING); +- public final Long2ObjectLinkedOpenHashMap updatingChunkMap = new Long2ObjectLinkedOpenHashMap(); +- public volatile Long2ObjectLinkedOpenHashMap visibleChunkMap; +- private final Long2ObjectLinkedOpenHashMap pendingUnloads; +- private final LongSet entitiesInLevel; ++ // Paper - rewrite chunk system + public final ServerLevel level; + private final ThreadedLevelLightEngine lightEngine; + public final BlockableEventLoop mainThreadExecutor; // Paper - public +@@ -131,16 +128,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + private final ChunkGeneratorStructureState chunkGeneratorState; + public final Supplier overworldDataStorage; + private final PoiManager poiManager; +- public final LongSet toDrop; ++ // Paper - rewrite chunk system + private boolean modified; +- private final ChunkTaskPriorityQueueSorter queueSorter; +- private final ProcessorHandle> worldgenMailbox; +- private final ProcessorHandle> mainThreadMailbox; ++ // Paper - rewrite chunk system + public final ChunkProgressListener progressListener; + private final ChunkStatusUpdateListener chunkStatusListener; + public final ChunkMap.ChunkDistanceManager distanceManager; + private final AtomicInteger tickingGenerated; +- private final StructureTemplateManager structureTemplateManager; ++ public final StructureTemplateManager structureTemplateManager; // Paper - rewrite chunk system + private final String storageName; + private final PlayerMap playerMap; + public final Int2ObjectMap entityMap; +@@ -149,27 +144,6 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + private final Queue unloadQueue; + public int serverViewDistance; + +- // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() +- public final CallbackExecutor callbackExecutor = new CallbackExecutor(); +- public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { +- +- private final java.util.Queue queue = new java.util.ArrayDeque<>(); +- +- @Override +- public void execute(Runnable runnable) { +- this.queue.add(runnable); +- } +- +- @Override +- public void run() { +- Runnable task; +- while ((task = this.queue.poll()) != null) { +- task.run(); +- } +- } +- }; +- // CraftBukkit end +- + // Paper start - distance maps + private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); + +@@ -178,6 +152,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated + this.nearbyPlayers.addPlayer(player); ++ this.level.playerChunkLoader.addPlayer(player); // Paper - replace chunk loader + } + + void removePlayerFromDistanceMaps(ServerPlayer player) { +@@ -185,6 +160,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated + this.nearbyPlayers.removePlayer(player); ++ this.level.playerChunkLoader.removePlayer(player); // Paper - replace chunk loader + } + + void updateMaps(ServerPlayer player) { +@@ -192,6 +168,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated + this.nearbyPlayers.tickPlayer(player); ++ this.level.playerChunkLoader.updatePlayer(player); // Paper - replace chunk loader + } + // Paper end + // Paper start +@@ -221,17 +198,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) { +- return this.pendingUnloads.get(io.papermc.paper.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ return null; // Paper - rewrite chunk system + } + public final io.papermc.paper.util.player.NearbyPlayers nearbyPlayers; + // Paper end + + public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { + super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); +- this.visibleChunkMap = this.updatingChunkMap.clone(); +- this.pendingUnloads = new Long2ObjectLinkedOpenHashMap(); +- this.entitiesInLevel = new LongOpenHashSet(); +- this.toDrop = new LongOpenHashSet(); ++ // Paper - rewrite chunk system + this.tickingGenerated = new AtomicInteger(); + this.playerMap = new PlayerMap(); + this.entityMap = new Int2ObjectOpenHashMap(); +@@ -262,19 +236,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j, world.spigotConfig); // Spigot + this.mainThreadExecutor = mainThreadExecutor; +- ProcessorMailbox threadedmailbox = ProcessorMailbox.create(executor, "worldgen"); ++ // Paper - rewrite chunk system + + Objects.requireNonNull(mainThreadExecutor); +- ProcessorHandle mailbox = ProcessorHandle.of("main", mainThreadExecutor::tell); ++ // Paper - rewrite chunk system + + this.progressListener = worldGenerationProgressListener; + this.chunkStatusListener = chunkStatusChangeListener; +- ProcessorMailbox threadedmailbox1 = ProcessorMailbox.create(executor, "light"); ++ // Paper - rewrite chunk system + +- this.queueSorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE); +- this.worldgenMailbox = this.queueSorter.getProcessor(threadedmailbox, false); +- this.mainThreadMailbox = this.queueSorter.getProcessor(mailbox, false); +- this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), threadedmailbox1, this.queueSorter.getProcessor(threadedmailbox1, false)); ++ // Paper - rewrite chunk system ++ this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), null, null); // Paper - rewrite chunk system + this.distanceManager = new ChunkMap.ChunkDistanceManager(executor, mainThreadExecutor); + this.overworldDataStorage = persistentStateManagerFactory; + this.poiManager = new PoiManager(path.resolve("poi"), dataFixer, dsync, iregistrycustom, world); +@@ -330,23 +302,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + boolean isChunkTracked(ServerPlayer player, int chunkX, int chunkZ) { +- return player.getChunkTrackingView().contains(chunkX, chunkZ) && !player.connection.chunkSender.isPending(ChunkPos.asLong(chunkX, chunkZ)); ++ // Paper start - rewrite player chunk loader ++ return this.level.playerChunkLoader.isChunkSent(player, chunkX, chunkZ); ++ // Paper end - rewrite player chunk loader + } + + private boolean isChunkOnTrackedBorder(ServerPlayer player, int chunkX, int chunkZ) { +- if (!this.isChunkTracked(player, chunkX, chunkZ)) { +- return false; +- } else { +- for (int k = -1; k <= 1; ++k) { +- for (int l = -1; l <= 1; ++l) { +- if ((k != 0 || l != 0) && !this.isChunkTracked(player, chunkX + k, chunkZ + l)) { +- return true; +- } +- } +- } +- +- return false; +- } ++ // Paper start - rewrite player chunk loader ++ return this.level.playerChunkLoader.isChunkSent(player, chunkX, chunkZ, true); ++ // Paper end - rewrite player chunk loader + } + + protected ThreadedLevelLightEngine getLightEngine() { +@@ -355,20 +319,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + @Nullable + protected ChunkHolder getUpdatingChunkIfPresent(long pos) { +- return (ChunkHolder) this.updatingChunkMap.get(pos); ++ // Paper start - rewrite chunk system ++ io.papermc.paper.chunk.system.scheduling.NewChunkHolder holder = this.level.chunkTaskScheduler.chunkHolderManager.getChunkHolder(pos); ++ return holder == null ? null : holder.vanillaChunkHolder; ++ // Paper end - rewrite chunk system + } + + @Nullable + public ChunkHolder getVisibleChunkIfPresent(long pos) { +- return (ChunkHolder) this.visibleChunkMap.get(pos); ++ // Paper start - rewrite chunk system ++ io.papermc.paper.chunk.system.scheduling.NewChunkHolder holder = this.level.chunkTaskScheduler.chunkHolderManager.getChunkHolder(pos); ++ return holder == null ? null : holder.vanillaChunkHolder; ++ // Paper end - rewrite chunk system + } + + protected IntSupplier getChunkQueueLevel(long pos) { +- return () -> { +- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); +- +- return playerchunk == null ? ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1 : Math.min(playerchunk.getQueueLevel(), ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1); +- }; ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + public String getChunkDebugData(ChunkPos chunkPos) { +@@ -397,84 +363,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + private CompletableFuture, ChunkHolder.ChunkLoadingFailure>> getChunkRangeFuture(ChunkHolder centerChunk, int margin, IntFunction distanceToStatus) { +- if (margin == 0) { +- ChunkStatus chunkstatus = (ChunkStatus) distanceToStatus.apply(0); +- +- return centerChunk.getOrScheduleFuture(chunkstatus, this).thenApply((either) -> { +- return either.mapLeft(List::of); +- }); +- } else { +- List>> list = new ArrayList(); +- List list1 = new ArrayList(); +- ChunkPos chunkcoordintpair = centerChunk.getPos(); +- int j = chunkcoordintpair.x; +- int k = chunkcoordintpair.z; +- +- for (int l = -margin; l <= margin; ++l) { +- for (int i1 = -margin; i1 <= margin; ++i1) { +- int j1 = Math.max(Math.abs(i1), Math.abs(l)); +- final ChunkPos chunkcoordintpair1 = new ChunkPos(j + i1, k + l); +- long k1 = chunkcoordintpair1.toLong(); +- ChunkHolder playerchunk1 = this.getUpdatingChunkIfPresent(k1); +- +- if (playerchunk1 == null) { +- return CompletableFuture.completedFuture(Either.right(new ChunkHolder.ChunkLoadingFailure() { +- public String toString() { +- return "Unloaded " + chunkcoordintpair1; +- } +- })); +- } +- +- ChunkStatus chunkstatus1 = (ChunkStatus) distanceToStatus.apply(j1); +- CompletableFuture> completablefuture = playerchunk1.getOrScheduleFuture(chunkstatus1, this); +- +- list1.add(playerchunk1); +- list.add(completablefuture); +- } +- } +- +- CompletableFuture>> completablefuture1 = Util.sequence(list); +- CompletableFuture, ChunkHolder.ChunkLoadingFailure>> completablefuture2 = completablefuture1.thenApply((list2) -> { +- List list3 = Lists.newArrayList(); +- // CraftBukkit start - decompile error +- int cnt = 0; +- +- for (Iterator iterator = list2.iterator(); iterator.hasNext(); ++cnt) { +- final int l1 = cnt; +- // CraftBukkit end +- final Either either = (Either) iterator.next(); +- +- if (either == null) { +- throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a"); +- } +- +- Optional optional = either.left(); +- +- if (optional.isEmpty()) { +- return Either.right(new ChunkHolder.ChunkLoadingFailure() { +- public String toString() { +- ChunkPos chunkcoordintpair2 = new ChunkPos(j + l1 % (margin * 2 + 1), k + l1 / (margin * 2 + 1)); +- +- return "Unloaded " + chunkcoordintpair2 + " " + either.right().get(); +- } +- }); +- } +- +- list3.add((ChunkAccess) optional.get()); +- } +- +- return Either.left(list3); +- }); +- Iterator iterator = list1.iterator(); +- +- while (iterator.hasNext()) { +- ChunkHolder playerchunk2 = (ChunkHolder) iterator.next(); +- +- playerchunk2.addSaveDependency("getChunkRangeFuture " + chunkcoordintpair + " " + margin, completablefuture2); +- } +- +- return completablefuture2; +- } ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) { +@@ -504,263 +393,72 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public CompletableFuture> prepareEntityTickingChunk(ChunkHolder chunk) { +- return this.getChunkRangeFuture(chunk, 2, (i) -> { +- return ChunkStatus.FULL; +- }).thenApplyAsync((either) -> { +- return either.mapLeft((list) -> { +- return (LevelChunk) list.get(list.size() / 2); +- }); +- }, this.mainThreadExecutor); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + @Nullable + ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k) { +- if (!ChunkLevel.isLoaded(k) && !ChunkLevel.isLoaded(level)) { +- return holder; +- } else { +- if (holder != null) { +- holder.setTicketLevel(level); +- } +- +- if (holder != null) { +- if (!ChunkLevel.isLoaded(level)) { +- this.toDrop.add(pos); +- } else { +- this.toDrop.remove(pos); +- } +- } +- +- if (ChunkLevel.isLoaded(level) && holder == null) { +- holder = (ChunkHolder) this.pendingUnloads.remove(pos); +- if (holder != null) { +- holder.setTicketLevel(level); +- } else { +- holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this); +- // Paper start +- io.papermc.paper.chunk.system.ChunkSystem.onChunkHolderCreate(this.level, holder); +- // Paper end +- } +- +- // Paper start +- holder.onChunkAdd(); +- // Paper end +- this.updatingChunkMap.put(pos, holder); +- this.modified = true; +- } +- +- return holder; +- } ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + @Override + public void close() throws IOException { +- try { +- this.queueSorter.close(); +- this.poiManager.close(); +- } finally { +- super.close(); +- } ++ throw new UnsupportedOperationException("Use ServerChunkCache#close"); // Paper - rewrite chunk system ++ } + ++ // Paper start - rewrite chunk system ++ protected void saveIncrementally() { ++ this.level.chunkTaskScheduler.chunkHolderManager.autoSave(); // Paper - rewrite chunk system + } ++ // Paper end - - rewrite chunk system + + protected void saveAllChunks(boolean flush) { +- if (flush) { +- List list = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); // Paper +- MutableBoolean mutableboolean = new MutableBoolean(); +- +- do { +- mutableboolean.setFalse(); +- list.stream().map((playerchunk) -> { +- CompletableFuture completablefuture; +- +- do { +- completablefuture = playerchunk.getChunkToSave(); +- BlockableEventLoop iasynctaskhandler = this.mainThreadExecutor; +- +- Objects.requireNonNull(completablefuture); +- iasynctaskhandler.managedBlock(completablefuture::isDone); +- } while (completablefuture != playerchunk.getChunkToSave()); +- +- return (ChunkAccess) completablefuture.join(); +- }).filter((ichunkaccess) -> { +- return ichunkaccess instanceof ImposterProtoChunk || ichunkaccess instanceof LevelChunk; +- }).filter(this::save).forEach((ichunkaccess) -> { +- mutableboolean.setTrue(); +- }); +- } while (mutableboolean.isTrue()); +- +- this.processUnloads(() -> { +- return true; +- }); +- this.flushWorker(); +- } else { +- io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).forEach(this::saveChunkIfNeeded); +- } +- ++ this.level.chunkTaskScheduler.chunkHolderManager.saveAllChunks(flush, false, false); // Paper - rewrite chunk system + } + + protected void tick(BooleanSupplier shouldKeepTicking) { + ProfilerFiller gameprofilerfiller = this.level.getProfiler(); + ++ try (Timing ignored = this.level.timings.poiUnload.startTiming()) { // Paper + gameprofilerfiller.push("poi"); + this.poiManager.tick(shouldKeepTicking); ++ } // Paper + gameprofilerfiller.popPush("chunk_unload"); + if (!this.level.noSave()) { ++ try (Timing ignored = this.level.timings.chunkUnload.startTiming()) { // Paper + this.processUnloads(shouldKeepTicking); ++ } // Paper + } + + gameprofilerfiller.pop(); + } + + public boolean hasWork() { +- return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || io.papermc.paper.chunk.system.ChunkSystem.hasAnyChunkHolders(this.level) || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets(); // Paper ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + private void processUnloads(BooleanSupplier shouldKeepTicking) { +- LongIterator longiterator = this.toDrop.iterator(); +- +- for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) { +- long j = longiterator.nextLong(); +- ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j); +- +- if (playerchunk != null) { +- playerchunk.onChunkRemove(); // Paper +- this.pendingUnloads.put(j, playerchunk); +- this.modified = true; +- ++i; +- this.scheduleUnload(j, playerchunk); +- } +- } +- +- int k = Math.max(0, this.unloadQueue.size() - 2000); +- +- Runnable runnable; +- +- while ((shouldKeepTicking.getAsBoolean() || k > 0) && (runnable = (Runnable) this.unloadQueue.poll()) != null) { +- --k; +- runnable.run(); +- } +- +- int l = 0; +- Iterator objectiterator = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper +- +- while (l < 20 && shouldKeepTicking.getAsBoolean() && objectiterator.hasNext()) { +- if (this.saveChunkIfNeeded((ChunkHolder) objectiterator.next())) { +- ++l; +- } +- } ++ this.level.chunkTaskScheduler.chunkHolderManager.processUnloads(); // Paper - rewrite chunk system + + } + + private void scheduleUnload(long pos, ChunkHolder holder) { +- CompletableFuture completablefuture = holder.getChunkToSave(); +- Consumer consumer = (ichunkaccess) -> { // CraftBukkit - decompile error +- CompletableFuture completablefuture1 = holder.getChunkToSave(); +- +- if (completablefuture1 != completablefuture) { +- this.scheduleUnload(pos, holder); +- } else { +- // Paper start +- boolean removed; +- if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) { +- io.papermc.paper.chunk.system.ChunkSystem.onChunkHolderDelete(this.level, holder); +- // Paper end +- if (ichunkaccess instanceof LevelChunk) { +- ((LevelChunk) ichunkaccess).setLoaded(false); +- } +- +- this.save(ichunkaccess); +- if (this.entitiesInLevel.remove(pos) && ichunkaccess instanceof LevelChunk) { +- LevelChunk chunk = (LevelChunk) ichunkaccess; +- +- this.level.unload(chunk); +- } +- +- this.lightEngine.updateChunkStatus(ichunkaccess.getPos()); +- this.lightEngine.tryScheduleUpdate(); +- this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null); +- this.chunkSaveCooldowns.remove(ichunkaccess.getPos().toLong()); +- } else if (removed) { // Paper start +- io.papermc.paper.chunk.system.ChunkSystem.onChunkHolderDelete(this.level, holder); +- } // Paper end +- +- } +- }; +- Queue queue = this.unloadQueue; +- +- Objects.requireNonNull(this.unloadQueue); +- completablefuture.thenAcceptAsync(consumer, queue::add).whenComplete((ovoid, throwable) -> { +- if (throwable != null) { +- ChunkMap.LOGGER.error("Failed to save chunk {}", holder.getPos(), throwable); +- } +- +- }); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + protected boolean promoteChunkMap() { +- if (!this.modified) { +- return false; +- } else { +- this.visibleChunkMap = this.updatingChunkMap.clone(); +- this.modified = false; +- return true; +- } ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + public CompletableFuture> schedule(ChunkHolder holder, ChunkStatus requiredStatus) { +- ChunkPos chunkcoordintpair = holder.getPos(); +- +- if (requiredStatus == ChunkStatus.EMPTY) { +- return this.scheduleChunkLoad(chunkcoordintpair); +- } else { +- if (requiredStatus == ChunkStatus.LIGHT) { +- this.distanceManager.addTicket(TicketType.LIGHT, chunkcoordintpair, ChunkLevel.byStatus(ChunkStatus.LIGHT), chunkcoordintpair); +- } +- +- if (!requiredStatus.hasLoadDependencies()) { +- Optional optional = ((Either) holder.getOrScheduleFuture(requiredStatus.getParent(), this).getNow(ChunkHolder.UNLOADED_CHUNK)).left(); +- +- if (optional.isPresent() && ((ChunkAccess) optional.get()).getStatus().isOrAfter(requiredStatus)) { +- CompletableFuture> completablefuture = requiredStatus.load(this.level, this.structureTemplateManager, this.lightEngine, (ichunkaccess) -> { +- return this.protoChunkToFullChunk(holder); +- }, (ChunkAccess) optional.get()); +- +- this.progressListener.onStatusChange(chunkcoordintpair, requiredStatus); +- return completablefuture; +- } +- } +- +- return this.scheduleChunkGeneration(holder, requiredStatus); +- } ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + private CompletableFuture> scheduleChunkLoad(ChunkPos pos) { +- return this.readChunk(pos).thenApply((optional) -> { +- return optional.filter((nbttagcompound) -> { +- boolean flag = ChunkMap.isChunkDataValid(nbttagcompound); +- +- if (!flag) { +- ChunkMap.LOGGER.error("Chunk file at {} is missing level data, skipping", pos); +- } +- +- return flag; +- }); +- }).thenApplyAsync((optional) -> { +- this.level.getProfiler().incrementCounter("chunkLoad"); +- if (optional.isPresent()) { +- ProtoChunk protochunk = ChunkSerializer.read(this.level, this.poiManager, pos, (CompoundTag) optional.get()); +- +- this.markPosition(pos, protochunk.getStatus().getChunkType()); +- return Either.left(protochunk); // CraftBukkit - decompile error +- } else { +- return Either.left(this.createEmptyChunk(pos)); // CraftBukkit - decompile error +- } +- }, this.mainThreadExecutor).exceptionallyAsync((throwable) -> { +- return this.handleChunkLoadFailure(throwable, pos); +- }, this.mainThreadExecutor); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + +- private static boolean isChunkDataValid(CompoundTag nbt) { ++ public static boolean isChunkDataValid(CompoundTag nbt) { // Paper - async chunk loading + return nbt.contains("Status", 8); + } + +@@ -796,54 +494,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + private CompletableFuture> scheduleChunkGeneration(ChunkHolder holder, ChunkStatus requiredStatus) { +- ChunkPos chunkcoordintpair = holder.getPos(); +- CompletableFuture, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkRangeFuture(holder, requiredStatus.getRange(), (i) -> { +- return this.getDependencyStatus(requiredStatus, i); +- }); +- +- this.level.getProfiler().incrementCounter(() -> { +- return "chunkGenerate " + requiredStatus; +- }); +- Executor executor = (runnable) -> { +- this.worldgenMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); +- }; +- +- return completablefuture.thenComposeAsync((either) -> { +- return (CompletionStage) either.map((list) -> { +- try { +- ChunkAccess ichunkaccess = (ChunkAccess) list.get(list.size() / 2); +- CompletableFuture completablefuture1; +- +- if (ichunkaccess.getStatus().isOrAfter(requiredStatus)) { +- completablefuture1 = requiredStatus.load(this.level, this.structureTemplateManager, this.lightEngine, (ichunkaccess1) -> { +- return this.protoChunkToFullChunk(holder); +- }, ichunkaccess); +- } else { +- completablefuture1 = requiredStatus.generate(executor, this.level, this.generator, this.structureTemplateManager, this.lightEngine, (ichunkaccess1) -> { +- return this.protoChunkToFullChunk(holder); +- }, list); +- } +- +- this.progressListener.onStatusChange(chunkcoordintpair, requiredStatus); +- return completablefuture1; +- } catch (Exception exception) { +- exception.getStackTrace(); +- CrashReport crashreport = CrashReport.forThrowable(exception, "Exception generating new chunk"); +- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk to be generated"); +- +- crashreportsystemdetails.setDetail("Location", (Object) String.format(Locale.ROOT, "%d,%d", chunkcoordintpair.x, chunkcoordintpair.z)); +- crashreportsystemdetails.setDetail("Position hash", (Object) ChunkPos.asLong(chunkcoordintpair.x, chunkcoordintpair.z)); +- crashreportsystemdetails.setDetail("Generator", (Object) this.generator); +- this.mainThreadExecutor.execute(() -> { +- throw new ReportedException(crashreport); +- }); +- throw new ReportedException(crashreport); +- } +- }, (playerchunk_failure) -> { +- this.releaseLightTicket(chunkcoordintpair); +- return CompletableFuture.completedFuture(Either.right(playerchunk_failure)); +- }); +- }, executor); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + protected void releaseLightTicket(ChunkPos pos) { +@@ -854,7 +505,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + })); + } + +- private ChunkStatus getDependencyStatus(ChunkStatus centerChunkTargetStatus, int distance) { ++ public static ChunkStatus getDependencyStatus(ChunkStatus centerChunkTargetStatus, int distance) { // Paper -> public, static + ChunkStatus chunkstatus1; + + if (distance == 0) { +@@ -866,7 +517,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return chunkstatus1; + } + +- private static void postLoadProtoChunk(ServerLevel world, List nbt) { ++ public static void postLoadProtoChunk(ServerLevel world, List nbt, ChunkPos position) { // Paper - public and add chunk position parameter + if (!nbt.isEmpty()) { + // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities + world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(nbt, world).filter((entity) -> { +@@ -882,53 +533,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + checkDupeUUID(world, entity); // Paper - duplicate uuid resolving + return !needsRemoval; +- })); ++ }), position); // Paper - rewrite chunk system + // CraftBukkit end + } + + } + + private CompletableFuture> protoChunkToFullChunk(ChunkHolder chunkHolder) { +- CompletableFuture> completablefuture = chunkHolder.getFutureIfPresentUnchecked(ChunkStatus.FULL.getParent()); +- +- return completablefuture.thenApplyAsync((either) -> { +- ChunkStatus chunkstatus = ChunkLevel.generationStatus(chunkHolder.getTicketLevel()); +- +- return !chunkstatus.isOrAfter(ChunkStatus.FULL) ? ChunkHolder.UNLOADED_CHUNK : either.mapLeft((ichunkaccess) -> { +- try (Timing ignored = level.timings.chunkPostLoad.startTimingIfSync()) { // Paper +- ChunkPos chunkcoordintpair = chunkHolder.getPos(); +- ProtoChunk protochunk = (ProtoChunk) ichunkaccess; +- LevelChunk chunk; +- +- if (protochunk instanceof ImposterProtoChunk) { +- chunk = ((ImposterProtoChunk) protochunk).getWrapped(); +- } else { +- chunk = new LevelChunk(this.level, protochunk, (chunk1) -> { +- ChunkMap.postLoadProtoChunk(this.level, protochunk.getEntities()); +- }); +- chunkHolder.replaceProtoChunk(new ImposterProtoChunk(chunk, false)); +- } +- +- chunk.setFullStatus(() -> { +- return ChunkLevel.fullStatus(chunkHolder.getTicketLevel()); +- }); +- chunk.runPostLoad(); +- if (this.entitiesInLevel.add(chunkcoordintpair.toLong())) { +- chunk.setLoaded(true); +- chunk.registerAllBlockEntitiesAfterLevelLoad(); +- chunk.registerTickContainerInLevel(this.level); +- } +- +- return chunk; +- } // Paper +- }); +- }, (runnable) -> { +- ProcessorHandle mailbox = this.mainThreadMailbox; +- long i = chunkHolder.getPos().toLong(); +- +- Objects.requireNonNull(chunkHolder); +- mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, chunkHolder::getTicketLevel)); +- }); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + // Paper start - duplicate uuid resolving +@@ -971,61 +583,16 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + // Paper end - duplicate uuid resolving + public CompletableFuture> prepareTickingChunk(ChunkHolder holder) { +- CompletableFuture, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkRangeFuture(holder, 1, (i) -> { +- return ChunkStatus.FULL; +- }); +- CompletableFuture> completablefuture1 = completablefuture.thenApplyAsync((either) -> { +- return either.mapLeft((list) -> { +- return (LevelChunk) list.get(list.size() / 2); +- }); +- }, (runnable) -> { +- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); +- }).thenApplyAsync((either) -> { +- return either.ifLeft((chunk) -> { +- chunk.postProcessGeneration(); +- this.level.startTickingChunk(chunk); +- CompletableFuture completablefuture2 = holder.getChunkSendSyncFuture(); +- +- if (completablefuture2.isDone()) { +- this.onChunkReadyToSend(chunk); +- } else { +- completablefuture2.thenAcceptAsync((object) -> { +- this.onChunkReadyToSend(chunk); +- }, this.mainThreadExecutor); +- } +- +- }); +- }, this.mainThreadExecutor); +- +- completablefuture1.handle((either, throwable) -> { +- this.tickingGenerated.getAndIncrement(); +- return null; +- }); +- return completablefuture1; ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + private void onChunkReadyToSend(LevelChunk chunk) { +- ChunkPos chunkcoordintpair = chunk.getPos(); +- Iterator iterator = this.playerMap.getAllPlayers().iterator(); +- +- while (iterator.hasNext()) { +- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); +- +- if (entityplayer.getChunkTrackingView().contains(chunkcoordintpair)) { +- ChunkMap.markChunkPendingToSend(entityplayer, chunk); +- } +- } ++ throw new UnsupportedOperationException(); // Paper - rewrite player chunk loader + + } + + public CompletableFuture> prepareAccessibleChunk(ChunkHolder holder) { +- return this.getChunkRangeFuture(holder, 1, ChunkStatus::getStatusAroundFullChunk).thenApplyAsync((either) -> { +- return either.mapLeft((list) -> { +- return (LevelChunk) list.get(list.size() / 2); +- }); +- }, (runnable) -> { +- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); +- }); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + public int getTickingGenerated() { +@@ -1033,130 +600,52 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + private boolean saveChunkIfNeeded(ChunkHolder chunkHolder) { +- if (!chunkHolder.wasAccessibleSinceLastSave()) { +- return false; +- } else { +- ChunkAccess ichunkaccess = (ChunkAccess) chunkHolder.getChunkToSave().getNow(null); // CraftBukkit - decompile error +- +- if (!(ichunkaccess instanceof ImposterProtoChunk) && !(ichunkaccess instanceof LevelChunk)) { +- return false; +- } else { +- long i = ichunkaccess.getPos().toLong(); +- long j = this.chunkSaveCooldowns.getOrDefault(i, -1L); +- long k = System.currentTimeMillis(); +- +- if (k < j) { +- return false; +- } else { +- boolean flag = this.save(ichunkaccess); +- +- chunkHolder.refreshAccessibility(); +- if (flag) { +- this.chunkSaveCooldowns.put(i, k + 10000L); +- } +- +- return flag; +- } +- } +- } ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + public boolean save(ChunkAccess chunk) { +- this.poiManager.flush(chunk.getPos()); +- if (!chunk.isUnsaved()) { +- return false; +- } else { +- chunk.setUnsaved(false); +- ChunkPos chunkcoordintpair = chunk.getPos(); +- +- try { +- ChunkStatus chunkstatus = chunk.getStatus(); +- +- if (chunkstatus.getChunkType() != ChunkStatus.ChunkType.LEVELCHUNK) { +- if (this.isExistingChunkFull(chunkcoordintpair)) { +- return false; +- } +- +- if (chunkstatus == ChunkStatus.EMPTY && chunk.getAllStarts().values().stream().noneMatch(StructureStart::isValid)) { +- return false; +- } +- } +- +- this.level.getProfiler().incrementCounter("chunkSave"); +- CompoundTag nbttagcompound = ChunkSerializer.write(this.level, chunk); +- +- this.write(chunkcoordintpair, nbttagcompound); +- this.markPosition(chunkcoordintpair, chunkstatus.getChunkType()); +- return true; +- } catch (Exception exception) { +- ChunkMap.LOGGER.error("Failed to save chunk {},{}", new Object[]{chunkcoordintpair.x, chunkcoordintpair.z, exception}); +- return false; +- } +- } ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + private boolean isExistingChunkFull(ChunkPos pos) { +- byte b0 = this.chunkTypeCache.get(pos.toLong()); +- +- if (b0 != 0) { +- return b0 == 1; +- } else { +- CompoundTag nbttagcompound; +- +- try { +- nbttagcompound = (CompoundTag) ((Optional) this.readChunk(pos).join()).orElse((Object) null); +- if (nbttagcompound == null) { +- this.markPositionReplaceable(pos); +- return false; +- } +- } catch (Exception exception) { +- ChunkMap.LOGGER.error("Failed to read chunk {}", pos, exception); +- this.markPositionReplaceable(pos); +- return false; +- } ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system ++ } + +- ChunkStatus.ChunkType chunkstatus_type = ChunkSerializer.getChunkTypeFromTag(nbttagcompound); ++ // Paper start - replace player loader system ++ public void setTickViewDistance(int distance) { ++ this.level.playerChunkLoader.setTickDistance(distance); ++ } + +- return this.markPosition(pos, chunkstatus_type) == 1; +- } ++ public void setSendViewDistance(int distance) { ++ this.level.playerChunkLoader.setSendDistance(distance); + } ++ // Paper end - replace player loader system + + public void setServerViewDistance(int watchDistance) { // Paper - public + int j = Mth.clamp(watchDistance, 2, 32); + + if (j != this.serverViewDistance) { + this.serverViewDistance = j; +- this.distanceManager.updatePlayerTickets(this.serverViewDistance); +- Iterator iterator = this.playerMap.getAllPlayers().iterator(); +- +- while (iterator.hasNext()) { +- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); +- +- this.updateChunkTracking(entityplayer); +- } ++ this.level.playerChunkLoader.setLoadDistance(this.serverViewDistance + 1); // Paper - replace player loader system + } + + } + + public int getPlayerViewDistance(ServerPlayer player) { // Paper - public +- return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance); ++ return io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player); // Paper - per player view distance + } + + private void markChunkPendingToSend(ServerPlayer player, ChunkPos pos) { +- LevelChunk chunk = this.getChunkToSend(pos.toLong()); +- +- if (chunk != null) { +- ChunkMap.markChunkPendingToSend(player, chunk); +- } ++ throw new UnsupportedOperationException(); // Paper - per player view distance + + } + + private static void markChunkPendingToSend(ServerPlayer player, LevelChunk chunk) { +- player.connection.chunkSender.markChunkPendingToSend(chunk); ++ throw new UnsupportedOperationException(); // Paper - rewrite player chunk loader + } + + private static void dropChunk(ServerPlayer player, ChunkPos pos) { +- player.connection.chunkSender.dropChunk(player, pos); ++ // Paper - rewrite player chunk loader + } + + @Nullable +@@ -1179,30 +668,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + void dumpChunks(Writer writer) throws IOException { +- CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer); +- TickingTracker tickingtracker = this.distanceManager.tickingTracker(); +- Iterator objectbidirectionaliterator = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper +- +- while (objectbidirectionaliterator.hasNext()) { +- ChunkHolder playerchunk = objectbidirectionaliterator.next(); // Paper +- long i = playerchunk.pos.toLong(); // Paper +- ChunkPos chunkcoordintpair = new ChunkPos(i); +- // Paper +- Optional optional = Optional.ofNullable(playerchunk.getLastAvailable()); +- Optional optional1 = optional.flatMap((ichunkaccess) -> { +- return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty(); +- }); +- +- // CraftBukkit - decompile error +- csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> { +- return chunk.getBlockEntities().size(); +- }).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> { +- return chunk.getBlockTicks().count(); +- }).orElse(0), optional1.map((chunk) -> { +- return chunk.getFluidTicks().count(); +- }).orElse(0)); +- } +- ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + private static String printFuture(CompletableFuture> future) { +@@ -1221,6 +687,35 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + } + ++ // Paper start - Asynchronous chunk io ++ @Nullable ++ @Override ++ public CompoundTag readSync(ChunkPos chunkcoordintpair) throws IOException { ++ // Paper start - rewrite chunk system ++ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { ++ return io.papermc.paper.chunk.system.io.RegionFileIOThread.loadData( ++ this.level, chunkcoordintpair.x, chunkcoordintpair.z, io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.CHUNK_DATA, ++ io.papermc.paper.chunk.system.io.RegionFileIOThread.getIOBlockingPriorityForCurrentThread() ++ ); ++ } ++ // Paper end - rewrite chunk system ++ return super.readSync(chunkcoordintpair); ++ } ++ ++ @Override ++ public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws IOException { ++ // Paper start - rewrite chunk system ++ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { ++ io.papermc.paper.chunk.system.io.RegionFileIOThread.scheduleSave( ++ this.level, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, ++ io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.CHUNK_DATA); ++ return; ++ } ++ // Paper end - rewrite chunk system ++ super.write(chunkcoordintpair, nbttagcompound); ++ } ++ // Paper end ++ + private CompletableFuture> readChunk(ChunkPos chunkPos) { + return this.read(chunkPos).thenApplyAsync((optional) -> { + return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit +@@ -1321,8 +816,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.distanceManager.addPlayer(SectionPos.of((EntityAccess) player), player); + } + +- player.setChunkTrackingView(ChunkTrackingView.EMPTY); +- this.updateChunkTracking(player); ++ // Paper - handled by player chunk loader + this.addPlayerToDistanceMaps(player); // Paper - distance maps + } else { + SectionPos sectionposition = player.getLastSectionPos(); +@@ -1333,7 +827,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + this.removePlayerFromDistanceMaps(player); // Paper - distance maps +- this.applyChunkTrackingView(player, ChunkTrackingView.EMPTY); ++ // Paper - handled by player chunk loader + } + + } +@@ -1381,73 +875,30 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.playerMap.unIgnorePlayer(player); + } + +- this.updateChunkTracking(player); ++ // Paper - replaced by PlayerChunkLoader + } + + this.updateMaps(player); // Paper - distance maps + } + + private void updateChunkTracking(ServerPlayer player) { +- ChunkPos chunkcoordintpair = player.chunkPosition(); +- int i = this.getPlayerViewDistance(player); +- ChunkTrackingView chunktrackingview = player.getChunkTrackingView(); +- +- if (chunktrackingview instanceof ChunkTrackingView.Positioned) { +- ChunkTrackingView.Positioned chunktrackingview_a = (ChunkTrackingView.Positioned) chunktrackingview; +- +- if (chunktrackingview_a.center().equals(chunkcoordintpair) && chunktrackingview_a.viewDistance() == i) { +- return; +- } +- } +- +- this.applyChunkTrackingView(player, ChunkTrackingView.of(chunkcoordintpair, i)); ++ throw new UnsupportedOperationException(); // Paper - replaced by PlayerChunkLoader + } + + private void applyChunkTrackingView(ServerPlayer player, ChunkTrackingView chunkFilter) { +- if (player.level() == this.level) { +- ChunkTrackingView chunktrackingview1 = player.getChunkTrackingView(); +- +- if (chunkFilter instanceof ChunkTrackingView.Positioned) { +- label15: +- { +- ChunkTrackingView.Positioned chunktrackingview_a = (ChunkTrackingView.Positioned) chunkFilter; +- +- if (chunktrackingview1 instanceof ChunkTrackingView.Positioned) { +- ChunkTrackingView.Positioned chunktrackingview_a1 = (ChunkTrackingView.Positioned) chunktrackingview1; +- +- if (chunktrackingview_a1.center().equals(chunktrackingview_a.center())) { +- break label15; +- } +- } +- +- player.connection.send(new ClientboundSetChunkCacheCenterPacket(chunktrackingview_a.center().x, chunktrackingview_a.center().z)); +- } +- } +- +- ChunkTrackingView.difference(chunktrackingview1, chunkFilter, (chunkcoordintpair) -> { +- this.markChunkPendingToSend(player, chunkcoordintpair); +- }, (chunkcoordintpair) -> { +- ChunkMap.dropChunk(player, chunkcoordintpair); +- }); +- player.setChunkTrackingView(chunkFilter); +- } ++ throw new UnsupportedOperationException(); // Paper - replaced by PlayerChunkLoader + } + + @Override + public List getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) { +- Set set = this.playerMap.getAllPlayers(); +- Builder builder = ImmutableList.builder(); +- Iterator iterator = set.iterator(); +- +- while (iterator.hasNext()) { +- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); +- +- if (onlyOnWatchDistanceEdge && this.isChunkOnTrackedBorder(entityplayer, chunkPos.x, chunkPos.z) || !onlyOnWatchDistanceEdge && this.isChunkTracked(entityplayer, chunkPos.x, chunkPos.z)) { +- builder.add(entityplayer); +- } ++ // Paper start - per player view distance ++ ChunkHolder holder = this.getVisibleChunkIfPresent(chunkPos.toLong()); ++ if (holder == null) { ++ return new java.util.ArrayList<>(); ++ } else { ++ return holder.getPlayers(onlyOnWatchDistanceEdge); + } +- +- return builder.build(); ++ // Paper end - per player view distance + } + + public void addEntity(Entity entity) { +@@ -1520,13 +971,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + protected void tick() { +- Iterator iterator = this.playerMap.getAllPlayers().iterator(); +- +- while (iterator.hasNext()) { +- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); +- +- this.updateChunkTracking(entityplayer); +- } ++ // Paper - replaced by PlayerChunkLoader + + List list = Lists.newArrayList(); + List list1 = this.level.players(); +@@ -1635,16 +1080,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public void waitForLightBeforeSending(ChunkPos centerPos, int radius) { +- int j = radius + 1; +- +- ChunkPos.rangeClosed(centerPos, j).forEach((chunkcoordintpair1) -> { +- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(chunkcoordintpair1.toLong()); +- +- if (playerchunk != null) { +- playerchunk.addSendDependency(this.lightEngine.waitForPendingTasks(chunkcoordintpair1.x, chunkcoordintpair1.z)); +- } +- +- }); ++ // Paper - rewrite player chunk loader + } + + public class ChunkDistanceManager extends DistanceManager { // Paper - public +@@ -1655,7 +1091,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + @Override + protected boolean isChunkToRemove(long pos) { +- return ChunkMap.this.toDrop.contains(pos); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + @Nullable +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 8e8e3896040241bba8fd15f4d6d046567847f741..c80a625f7289e3bb33c6851d2072957e153ca1fb 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -39,67 +39,29 @@ import org.slf4j.Logger; + + public abstract class DistanceManager { + ++ // Paper start - rewrite chunk system ++ public io.papermc.paper.chunk.system.scheduling.ChunkHolderManager getChunkHolderManager() { ++ return this.chunkMap.level.chunkTaskScheduler.chunkHolderManager; ++ } ++ // Paper end - rewrite chunk system ++ + static final Logger LOGGER = LogUtils.getLogger(); + static final int PLAYER_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING); + private static final int INITIAL_TICKET_LIST_CAPACITY = 4; + final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); +- public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); +- private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); ++ // Paper - rewrite chunk system + private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); +- private final TickingTracker tickingTicketsTracker = new TickingTracker(); +- private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(32); +- final Set chunksToUpdateFutures = Sets.newHashSet(); +- final ChunkTaskPriorityQueueSorter ticketThrottler; +- final ProcessorHandle> ticketThrottlerInput; +- final ProcessorHandle ticketThrottlerReleaser; +- final LongSet ticketsToRelease = new LongOpenHashSet(); +- final Executor mainThreadExecutor; +- private long ticketTickCounter; +- public int simulationDistance = 10; ++ // Paper - rewrite chunk system + private final ChunkMap chunkMap; // Paper + + protected DistanceManager(Executor workerExecutor, Executor mainThreadExecutor, ChunkMap chunkMap) { +- Objects.requireNonNull(mainThreadExecutor); +- ProcessorHandle mailbox = ProcessorHandle.of("player ticket throttler", mainThreadExecutor::execute); +- ChunkTaskPriorityQueueSorter chunktaskqueuesorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(mailbox), workerExecutor, 4); +- +- this.ticketThrottler = chunktaskqueuesorter; +- this.ticketThrottlerInput = chunktaskqueuesorter.getProcessor(mailbox, true); +- this.ticketThrottlerReleaser = chunktaskqueuesorter.getReleaseProcessor(mailbox); +- this.mainThreadExecutor = mainThreadExecutor; ++ // Paper - rewrite chunk system + this.chunkMap = chunkMap; // Paper + } + + protected void purgeStaleTickets() { +- ++this.ticketTickCounter; +- ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); +- +- while (objectiterator.hasNext()) { +- Entry>> entry = (Entry) objectiterator.next(); +- Iterator> iterator = ((SortedArraySet) entry.getValue()).iterator(); +- boolean flag = false; +- +- while (iterator.hasNext()) { +- Ticket ticket = (Ticket) iterator.next(); +- +- if (ticket.timedOut(this.ticketTickCounter)) { +- iterator.remove(); +- flag = true; +- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); +- } +- } +- +- if (flag) { +- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); +- } +- +- if (((SortedArraySet) entry.getValue()).isEmpty()) { +- objectiterator.remove(); +- } +- } +- ++ this.getChunkHolderManager().tick(); // Paper - rewrite chunk system + } +- + private static int getTicketLevelAt(SortedArraySet> tickets) { + return !tickets.isEmpty() ? ((Ticket) tickets.first()).getTicketLevel() : ChunkLevel.MAX_LEVEL + 1; + } +@@ -113,108 +75,25 @@ public abstract class DistanceManager { + protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k); + + public boolean runAllUpdates(ChunkMap chunkStorage) { +- this.naturalSpawnChunkCounter.runAllUpdates(); +- this.tickingTicketsTracker.runAllUpdates(); +- this.playerTicketManager.runAllUpdates(); +- int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); +- boolean flag = i != 0; +- +- if (flag) { +- ; +- } +- +- if (!this.chunksToUpdateFutures.isEmpty()) { +- // CraftBukkit start +- // Iterate pending chunk updates with protection against concurrent modification exceptions +- java.util.Iterator iter = this.chunksToUpdateFutures.iterator(); +- int expectedSize = this.chunksToUpdateFutures.size(); +- do { +- ChunkHolder playerchunk = iter.next(); +- iter.remove(); +- expectedSize--; +- +- playerchunk.updateFutures(chunkStorage, this.mainThreadExecutor); +- +- // Reset iterator if set was modified using add() +- if (this.chunksToUpdateFutures.size() != expectedSize) { +- expectedSize = this.chunksToUpdateFutures.size(); +- iter = this.chunksToUpdateFutures.iterator(); +- } +- } while (iter.hasNext()); +- // CraftBukkit end +- +- return true; +- } else { +- if (!this.ticketsToRelease.isEmpty()) { +- LongIterator longiterator = this.ticketsToRelease.iterator(); +- +- while (longiterator.hasNext()) { +- long j = longiterator.nextLong(); +- +- if (this.getTickets(j).stream().anyMatch((ticket) -> { +- return ticket.getType() == TicketType.PLAYER; +- })) { +- ChunkHolder playerchunk = chunkStorage.getUpdatingChunkIfPresent(j); +- +- if (playerchunk == null) { +- throw new IllegalStateException(); +- } +- +- CompletableFuture> completablefuture = playerchunk.getEntityTickingChunkFuture(); +- +- completablefuture.thenAccept((either) -> { +- this.mainThreadExecutor.execute(() -> { +- this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { +- }, j, false)); +- }); +- }); +- } +- } +- +- this.ticketsToRelease.clear(); +- } +- +- return flag; +- } ++ return this.getChunkHolderManager().processTicketUpdates(); // Paper - rewrite chunk system + } + + boolean addTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean +- SortedArraySet> arraysetsorted = this.getTickets(i); +- int j = DistanceManager.getTicketLevelAt(arraysetsorted); +- Ticket ticket1 = (Ticket) arraysetsorted.addOrGet(ticket); +- +- ticket1.setCreatedTick(this.ticketTickCounter); +- if (ticket.getTicketLevel() < j) { +- this.ticketTracker.update(i, ticket.getTicketLevel(), true); +- } +- +- return ticket == ticket1; // CraftBukkit ++ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::addTicket"); // Paper ++ return this.getChunkHolderManager().addTicketAtLevel((TicketType)ticket.getType(), i, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system + } + + boolean removeTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean +- SortedArraySet> arraysetsorted = this.getTickets(i); +- +- boolean removed = false; // CraftBukkit +- if (arraysetsorted.remove(ticket)) { +- removed = true; // CraftBukkit +- } +- +- if (arraysetsorted.isEmpty()) { +- this.tickets.remove(i); +- } +- +- this.ticketTracker.update(i, DistanceManager.getTicketLevelAt(arraysetsorted), false); +- return removed; // CraftBukkit ++ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::removeTicket"); // Paper ++ return this.getChunkHolderManager().removeTicketAtLevel((TicketType)ticket.getType(), i, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system + } + + public void addTicket(TicketType type, ChunkPos pos, int level, T argument) { +- this.addTicket(pos.toLong(), new Ticket<>(type, level, argument)); ++ this.getChunkHolderManager().addTicketAtLevel(type, pos, level, argument); // Paper - rewrite chunk system + } + + public void removeTicket(TicketType type, ChunkPos pos, int level, T argument) { +- Ticket ticket = new Ticket<>(type, level, argument); +- +- this.removeTicket(pos.toLong(), ticket); ++ this.getChunkHolderManager().removeTicketAtLevel(type, pos, level, argument); // Paper - rewrite chunk system + } + + public void addRegionTicket(TicketType type, ChunkPos pos, int radius, T argument) { +@@ -223,13 +102,7 @@ public abstract class DistanceManager { + } + + public boolean addRegionTicketAtDistance(TicketType tickettype, ChunkPos chunkcoordintpair, int i, T t0) { +- // CraftBukkit end +- Ticket ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); +- long j = chunkcoordintpair.toLong(); +- +- boolean added = this.addTicket(j, ticket); // CraftBukkit +- this.tickingTicketsTracker.addTicket(j, ticket); +- return added; // CraftBukkit ++ return this.getChunkHolderManager().addTicketAtLevel(tickettype, chunkcoordintpair, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); // Paper - rewrite chunk system + } + + public void removeRegionTicket(TicketType type, ChunkPos pos, int radius, T argument) { +@@ -238,31 +111,21 @@ public abstract class DistanceManager { + } + + public boolean removeRegionTicketAtDistance(TicketType tickettype, ChunkPos chunkcoordintpair, int i, T t0) { +- // CraftBukkit end +- Ticket ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); +- long j = chunkcoordintpair.toLong(); +- +- boolean removed = this.removeTicket(j, ticket); // CraftBukkit +- this.tickingTicketsTracker.removeTicket(j, ticket); +- return removed; // CraftBukkit ++ return this.getChunkHolderManager().removeTicketAtLevel(tickettype, chunkcoordintpair, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); // Paper - rewrite chunk system + } + +- private SortedArraySet> getTickets(long position) { +- return (SortedArraySet) this.tickets.computeIfAbsent(position, (j) -> { +- return SortedArraySet.create(4); +- }); +- } ++ // Paper - rewrite chunk system + + protected void updateChunkForced(ChunkPos pos, boolean forced) { +- Ticket ticket = new Ticket<>(TicketType.FORCED, ChunkMap.FORCED_TICKET_LEVEL, pos); ++ Ticket ticket = new Ticket<>(TicketType.FORCED, ChunkMap.FORCED_TICKET_LEVEL, pos, 0L); // Paper - rewrite chunk system + long i = pos.toLong(); + + if (forced) { + this.addTicket(i, ticket); +- this.tickingTicketsTracker.addTicket(i, ticket); ++ //this.tickingTicketsTracker.addTicket(i, ticket); // Paper - no longer used + } else { + this.removeTicket(i, ticket); +- this.tickingTicketsTracker.removeTicket(i, ticket); ++ //this.tickingTicketsTracker.removeTicket(i, ticket); // Paper - no longer used + } + + } +@@ -271,12 +134,10 @@ public abstract class DistanceManager { + ChunkPos chunkcoordintpair = pos.chunk(); + long i = chunkcoordintpair.toLong(); + +- ((ObjectSet) this.playersPerChunk.computeIfAbsent(i, (j) -> { +- return new ObjectOpenHashSet(); +- })).add(player); ++ // Paper - no longer used + this.naturalSpawnChunkCounter.update(i, 0, true); +- this.playerTicketManager.update(i, 0, true); +- this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); ++ //this.playerTicketManager.update(i, 0, true); // Paper - no longer used ++ //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used + } + + public void removePlayer(SectionPos pos, ServerPlayer player) { +@@ -289,40 +150,44 @@ public abstract class DistanceManager { + if (objectset == null || objectset.isEmpty()) { // Paper + this.playersPerChunk.remove(i); + this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); +- this.playerTicketManager.update(i, Integer.MAX_VALUE, false); +- this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); ++ //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used ++ //this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used + } + + } + +- private int getPlayerTicketLevel() { +- return Math.max(0, ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING) - this.simulationDistance); +- } ++ // Paper - rewrite chunk system + + public boolean inEntityTickingRange(long chunkPos) { +- return ChunkLevel.isEntityTicking(this.tickingTicketsTracker.getLevel(chunkPos)); ++ // Paper start - replace player chunk loader system ++ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos); ++ return holder != null && holder.isEntityTickingReady(); ++ // Paper end - replace player chunk loader system + } + + public boolean inBlockTickingRange(long chunkPos) { +- return ChunkLevel.isBlockTicking(this.tickingTicketsTracker.getLevel(chunkPos)); ++ // Paper start - replace player chunk loader system ++ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos); ++ return holder != null && holder.isTickingReady(); ++ // Paper end - replace player chunk loader system + } + + protected String getTicketDebugString(long pos) { +- SortedArraySet> arraysetsorted = (SortedArraySet) this.tickets.get(pos); +- +- return arraysetsorted != null && !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.first()).toString() : "no_ticket"; ++ return this.getChunkHolderManager().getTicketDebugString(pos); // Paper - rewrite chunk system + } + + protected void updatePlayerTickets(int viewDistance) { +- this.playerTicketManager.updateViewDistance(viewDistance); ++ this.chunkMap.setServerViewDistance(viewDistance); // Paper - route to player chunk manager + } + +- public void updateSimulationDistance(int simulationDistance) { +- if (simulationDistance != this.simulationDistance) { +- this.simulationDistance = simulationDistance; +- this.tickingTicketsTracker.replacePlayerTicketsLevel(this.getPlayerTicketLevel()); +- } ++ // Paper start ++ public int getSimulationDistance() { ++ return this.chunkMap.level.playerChunkLoader.getAPITickDistance(); ++ } ++ // Paper end + ++ public void updateSimulationDistance(int simulationDistance) { ++ this.chunkMap.level.playerChunkLoader.setTickDistance(simulationDistance); // Paper - route to player chunk manager + } + + public int getNaturalSpawnChunkCount() { +@@ -336,103 +201,28 @@ public abstract class DistanceManager { + } + + public String getDebugStatus() { +- return this.ticketThrottler.getDebugStatus(); ++ return "No DistanceManager stats available"; // Paper - rewrite chunk system + } + +- private void dumpTickets(String path) { +- try { +- FileOutputStream fileoutputstream = new FileOutputStream(new File(path)); ++ // Paper - rewrite chunk system + +- try { +- ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().iterator(); +- +- while (objectiterator.hasNext()) { +- Entry>> entry = (Entry) objectiterator.next(); +- ChunkPos chunkcoordintpair = new ChunkPos(entry.getLongKey()); +- Iterator iterator = ((SortedArraySet) entry.getValue()).iterator(); +- +- while (iterator.hasNext()) { +- Ticket ticket = (Ticket) iterator.next(); +- +- fileoutputstream.write((chunkcoordintpair.x + "\t" + chunkcoordintpair.z + "\t" + ticket.getType() + "\t" + ticket.getTicketLevel() + "\t\n").getBytes(StandardCharsets.UTF_8)); +- } +- } +- } catch (Throwable throwable) { +- try { +- fileoutputstream.close(); +- } catch (Throwable throwable1) { +- throwable.addSuppressed(throwable1); +- } +- +- throw throwable; +- } +- +- fileoutputstream.close(); +- } catch (IOException ioexception) { +- DistanceManager.LOGGER.error("Failed to dump tickets to {}", path, ioexception); +- } +- +- } +- +- @VisibleForTesting +- TickingTracker tickingTracker() { +- return this.tickingTicketsTracker; +- } ++ // Paper - replace player chunk loader + + public void removeTicketsOnClosing() { +- ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve +- ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); +- +- while (objectiterator.hasNext()) { +- Entry>> entry = (Entry) objectiterator.next(); +- Iterator> iterator = ((SortedArraySet) entry.getValue()).iterator(); +- boolean flag = false; +- +- while (iterator.hasNext()) { +- Ticket ticket = (Ticket) iterator.next(); +- +- if (!immutableset.contains(ticket.getType())) { +- iterator.remove(); +- flag = true; +- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); +- } +- } +- +- if (flag) { +- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); +- } +- +- if (((SortedArraySet) entry.getValue()).isEmpty()) { +- objectiterator.remove(); +- } +- } +- ++ // Paper - rewrite chunk system - this stupid hack ain't needed anymore + } + + public boolean hasTickets() { +- return !this.tickets.isEmpty(); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + // CraftBukkit start + public void removeAllTicketsFor(TicketType ticketType, int ticketLevel, T ticketIdentifier) { +- Ticket target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier); +- +- for (java.util.Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) { +- Entry>> entry = iterator.next(); +- SortedArraySet> tickets = entry.getValue(); +- if (tickets.remove(target)) { +- // copied from removeTicket +- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false); +- +- // can't use entry after it's removed +- if (tickets.isEmpty()) { +- iterator.remove(); +- } +- } +- } ++ this.getChunkHolderManager().removeAllTicketsFor(ticketType, ticketLevel, ticketIdentifier); // Paper - rewrite chunk system + } + // CraftBukkit end + ++ /* Paper - rewrite chunk system + private class ChunkTicketTracker extends ChunkTracker { + + private static final int MAX_LEVEL = ChunkLevel.MAX_LEVEL + 1; +@@ -479,6 +269,7 @@ public abstract class DistanceManager { + return this.runUpdates(distance); + } + } ++ */ // Paper - rewrite chunk system + + private class FixedPlayerDistanceChunkTracker extends ChunkTracker { + +@@ -558,6 +349,7 @@ public abstract class DistanceManager { + } + } + ++ /* Paper - rewrite chunk system + private class PlayerTicketTracker extends DistanceManager.FixedPlayerDistanceChunkTracker { + + private int viewDistance = 0; +@@ -653,4 +445,5 @@ public abstract class DistanceManager { + return distance <= this.viewDistance; + } + } ++ */ // Paper - rewrite chunk system + } +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 8a118a7b2878d3c99dadfa97e2ae58fda2b3f93b..9bb4223fbb665211df11dc89fcd13cb7a92cd5dd 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -73,7 +73,7 @@ public class ServerChunkCache extends ChunkSource { + public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet entityTickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true); + final com.destroystokyo.paper.util.concurrent.WeakSeqLock loadedChunkMapSeqLock = new com.destroystokyo.paper.util.concurrent.WeakSeqLock(); + final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap loadedChunkMap = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(8192, 0.5f); +- long chunkFutureAwaitCounter; ++ final java.util.concurrent.atomic.AtomicLong chunkFutureAwaitCounter = new java.util.concurrent.atomic.AtomicLong(); // Paper - chunk system rewrite + private final LevelChunk[] lastLoadedChunks = new LevelChunk[4 * 4]; + // Paper end + +@@ -197,7 +197,7 @@ public class ServerChunkCache extends ChunkSource { + public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) { + long k = ChunkPos.asLong(x, z); + +- if (Thread.currentThread() == this.mainThread) { ++ if (io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system + return this.getChunkAtIfLoadedMainThread(x, z); + } + +@@ -249,7 +249,8 @@ public class ServerChunkCache extends ChunkSource { + @Nullable + @Override + public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) { +- if (Thread.currentThread() != this.mainThread) { ++ final int x1 = x; final int z1 = z; // Paper - conflict on variable change ++ if (!io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system + return (ChunkAccess) CompletableFuture.supplyAsync(() -> { + return this.getChunk(x, z, leastStatus, create); + }, this.mainThreadProcessor).join(); +@@ -267,24 +268,19 @@ public class ServerChunkCache extends ChunkSource { + + ChunkAccess ichunkaccess; + +- for (int l = 0; l < 4; ++l) { +- if (k == this.lastChunkPos[l] && leastStatus == this.lastChunkStatus[l]) { +- ichunkaccess = this.lastChunk[l]; +- if (ichunkaccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime +- return ichunkaccess; +- } +- } +- } ++ // Paper - rewrite chunk system - there are no correct callbacks to remove items from cache in the new chunk system + + gameprofilerfiller.incrementCounter("getChunkCacheMiss"); +- CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create); ++ CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create, true); // Paper + ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor; + + Objects.requireNonNull(completablefuture); + if (!completablefuture.isDone()) { // Paper ++ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.pushChunkWait(this.level, x1, z1); // Paper - rewrite chunk system + com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads + this.level.timings.syncChunkLoad.startTiming(); // Paper + chunkproviderserver_b.managedBlock(completablefuture::isDone); ++ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.popChunkWait(); // Paper - rewrite chunk system + this.level.timings.syncChunkLoad.stopTiming(); // Paper + } // Paper + ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { +@@ -304,7 +300,7 @@ public class ServerChunkCache extends ChunkSource { + @Nullable + @Override + public LevelChunk getChunkNow(int chunkX, int chunkZ) { +- if (Thread.currentThread() != this.mainThread) { ++ if (!io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system + return null; + } else { + return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunks +@@ -318,7 +314,7 @@ public class ServerChunkCache extends ChunkSource { + } + + public CompletableFuture> getChunkFuture(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { +- boolean flag1 = Thread.currentThread() == this.mainThread; ++ boolean flag1 = io.papermc.paper.util.TickThread.isTickThread(); // Paper - rewrite chunk system + CompletableFuture completablefuture; + + if (flag1) { +@@ -339,47 +335,52 @@ public class ServerChunkCache extends ChunkSource { + } + + private CompletableFuture> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { +- ChunkPos chunkcoordintpair = new ChunkPos(chunkX, chunkZ); +- long k = chunkcoordintpair.toLong(); +- int l = ChunkLevel.byStatus(leastStatus); +- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k); ++ // Paper start - add isUrgent - old sig left in place for dirty nms plugins ++ return getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create, false); ++ } ++ private CompletableFuture> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create, boolean isUrgent) { ++ // Paper start - rewrite chunk system ++ io.papermc.paper.util.TickThread.ensureTickThread(this.level, chunkX, chunkZ, "Scheduling chunk load off-main"); ++ int minLevel = ChunkLevel.byStatus(leastStatus); ++ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.level.chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkX, chunkZ); + +- // CraftBukkit start - don't add new ticket for currently unloading chunk +- boolean currentlyUnloading = false; +- if (playerchunk != null) { +- FullChunkStatus oldChunkState = ChunkLevel.fullStatus(playerchunk.oldTicketLevel); +- FullChunkStatus currentChunkState = ChunkLevel.fullStatus(playerchunk.getTicketLevel()); +- currentlyUnloading = (oldChunkState.isOrAfter(FullChunkStatus.FULL) && !currentChunkState.isOrAfter(FullChunkStatus.FULL)); ++ boolean needsFullScheduling = leastStatus == ChunkStatus.FULL && (chunkHolder == null || !chunkHolder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)); ++ ++ if ((chunkHolder == null || chunkHolder.getTicketLevel() > minLevel || needsFullScheduling) && !create) { ++ return ChunkHolder.UNLOADED_CHUNK_FUTURE; + } +- if (create && !currentlyUnloading) { +- // CraftBukkit end +- this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); +- if (this.chunkAbsent(playerchunk, l)) { +- ProfilerFiller gameprofilerfiller = this.level.getProfiler(); +- +- gameprofilerfiller.push("chunkLoad"); +- this.runDistanceManagerUpdates(); +- playerchunk = this.getVisibleChunkIfPresent(k); +- gameprofilerfiller.pop(); +- if (this.chunkAbsent(playerchunk, l)) { +- throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added")); ++ ++ io.papermc.paper.chunk.system.scheduling.NewChunkHolder.ChunkCompletion chunkCompletion = chunkHolder == null ? null : chunkHolder.getLastChunkCompletion(); ++ if (needsFullScheduling || chunkCompletion == null || !chunkCompletion.genStatus().isOrAfter(leastStatus)) { ++ // schedule ++ CompletableFuture> ret = new CompletableFuture<>(); ++ Consumer complete = (ChunkAccess chunk) -> { ++ if (chunk == null) { ++ ret.complete(Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED)); ++ } else { ++ ret.complete(Either.left(chunk)); + } +- } +- } ++ }; + +- return this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(leastStatus, this.chunkMap); +- } ++ this.level.chunkTaskScheduler.scheduleChunkLoad( ++ chunkX, chunkZ, leastStatus, true, ++ isUrgent ? ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.BLOCKING : ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, ++ complete ++ ); + +- private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) { +- return holder == null || holder.oldTicketLevel > maxLevel; // CraftBukkit using oldTicketLevel for isLoaded checks ++ return ret; ++ } else { ++ // can return now ++ return CompletableFuture.completedFuture(Either.left(chunkCompletion.chunk())); ++ } ++ // Paper end - rewrite chunk system + } + ++ // Paper - rewrite chunk system ++ + @Override + public boolean hasChunk(int x, int z) { +- ChunkHolder playerchunk = this.getVisibleChunkIfPresent((new ChunkPos(x, z)).toLong()); +- int k = ChunkLevel.byStatus(ChunkStatus.FULL); +- +- return !this.chunkAbsent(playerchunk, k); ++ return this.getChunkAtIfLoadedImmediately(x, z) != null; // Paper - rewrite chunk system + } + + @Nullable +@@ -391,22 +392,13 @@ public class ServerChunkCache extends ChunkSource { + if (playerchunk == null) { + return null; + } else { +- int l = ServerChunkCache.CHUNK_STATUSES.size() - 1; +- +- while (true) { +- ChunkStatus chunkstatus = (ChunkStatus) ServerChunkCache.CHUNK_STATUSES.get(l); +- Optional optional = ((Either) playerchunk.getFutureIfPresentUnchecked(chunkstatus).getNow(ChunkHolder.UNLOADED_CHUNK)).left(); +- +- if (optional.isPresent()) { +- return (LightChunk) optional.get(); +- } +- +- if (chunkstatus == ChunkStatus.INITIALIZE_LIGHT.getParent()) { +- return null; +- } +- +- --l; ++ // Paper start - rewrite chunk system ++ ChunkStatus status = playerchunk.getChunkHolderStatus(); ++ if (status != null && !status.isOrAfter(ChunkStatus.LIGHT.getParent())) { ++ return null; + } ++ return playerchunk.getAvailableChunkNow(); ++ // Paper end - rewrite chunk system + } + } + +@@ -420,15 +412,7 @@ public class ServerChunkCache extends ChunkSource { + } + + public boolean runDistanceManagerUpdates() { // Paper - public +- boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); +- boolean flag1 = this.chunkMap.promoteChunkMap(); +- +- if (!flag && !flag1) { +- return false; +- } else { +- this.clearCache(); +- return true; +- } ++ return this.level.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(); // Paper - rewrite chunk system + } + + // Paper start +@@ -438,17 +422,10 @@ public class ServerChunkCache extends ChunkSource { + // Paper end + + public boolean isPositionTicking(long pos) { +- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); +- +- if (playerchunk == null) { +- return false; +- } else if (!this.level.shouldTickBlocksAt(pos)) { +- return false; +- } else { +- Either either = (Either) playerchunk.getTickingChunkFuture().getNow(null); // CraftBukkit - decompile error +- +- return either != null && either.left().isPresent(); +- } ++ // Paper start - replace player chunk loader system ++ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(pos); ++ return holder != null && holder.isTickingReady(); ++ // Paper end - replace player chunk loader system + } + + public void save(boolean flush) { +@@ -464,17 +441,13 @@ public class ServerChunkCache extends ChunkSource { + this.close(true); + } + +- public void close(boolean save) throws IOException { +- if (save) { +- this.save(true); +- } +- // CraftBukkit end +- this.lightEngine.close(); +- this.chunkMap.close(); ++ public void close(boolean save) { // Paper - rewrite chunk system ++ this.level.chunkTaskScheduler.chunkHolderManager.close(save, true); // Paper - rewrite chunk system + } + + // CraftBukkit start - modelled on below + public void purgeUnload() { ++ if (true) return; // Paper - tickets will be removed later, this behavior isn't really well accounted for by the chunk system + this.level.getProfiler().push("purge"); + this.distanceManager.purgeStaleTickets(); + this.runDistanceManagerUpdates(); +@@ -495,6 +468,7 @@ public class ServerChunkCache extends ChunkSource { + this.level.getProfiler().popPush("chunks"); + if (tickChunks) { + this.level.timings.chunks.startTiming(); // Paper - timings ++ this.chunkMap.level.playerChunkLoader.tick(); // Paper - replace player chunk loader - this is mostly required to account for view distance changes + this.tickChunks(); + this.level.timings.chunks.stopTiming(); // Paper - timings + this.chunkMap.tick(); +@@ -597,7 +571,12 @@ public class ServerChunkCache extends ChunkSource { + ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); + + if (playerchunk != null) { +- ((Either) playerchunk.getFullChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left().ifPresent(chunkConsumer); ++ // Paper start - rewrite chunk system ++ LevelChunk chunk = playerchunk.getFullChunk(); ++ if (chunk != null) { ++ chunkConsumer.accept(chunk); ++ } ++ // Paper end - rewrite chunk system + } + + } +@@ -763,17 +742,10 @@ public class ServerChunkCache extends ChunkSource { + @Override + // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task + public boolean pollTask() { +- try { + if (ServerChunkCache.this.runDistanceManagerUpdates()) { + return true; +- } else { +- ServerChunkCache.this.lightEngine.tryScheduleUpdate(); +- return super.pollTask(); + } +- } finally { +- ServerChunkCache.this.chunkMap.callbackExecutor.run(); +- } +- // CraftBukkit end ++ return super.pollTask() | ServerChunkCache.this.level.chunkTaskScheduler.executeMainThreadTask(); // Paper - rewrite chunk system + } + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index b5d6a7eaa24d9968e159d77a4295be00332a5457..dff2dfbe9cc04894d42181c6691e27ad061beb40 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -195,7 +195,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + private final MinecraftServer server; + public final PrimaryLevelData serverLevelData; // CraftBukkit - type + final EntityTickList entityTickList; +- public final PersistentEntitySectionManager entityManager; ++ //public final PersistentEntitySectionManager entityManager; // Paper - rewrite chunk system + private final GameEventDispatcher gameEventDispatcher; + public boolean noSave; + private final SleepStatus sleepStatus; +@@ -263,50 +263,65 @@ public class ServerLevel extends Level implements WorldGenLevel { + return true; + } + +- public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, +- java.util.function.Consumer> onLoad) { +- if (Thread.currentThread() != this.thread) { +- this.getChunkSource().mainThreadProcessor.execute(() -> { +- this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad); +- }); +- return; +- } ++ public final void loadChunksAsync(BlockPos pos, int radiusBlocks, ++ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, ++ java.util.function.Consumer> onLoad) { ++ loadChunksAsync( ++ (pos.getX() - radiusBlocks) >> 4, ++ (pos.getX() + radiusBlocks) >> 4, ++ (pos.getZ() - radiusBlocks) >> 4, ++ (pos.getZ() + radiusBlocks) >> 4, ++ priority, onLoad ++ ); ++ } ++ ++ public final void loadChunksAsync(BlockPos pos, int radiusBlocks, ++ net.minecraft.world.level.chunk.ChunkStatus chunkStatus, ++ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, ++ java.util.function.Consumer> onLoad) { ++ loadChunksAsync( ++ (pos.getX() - radiusBlocks) >> 4, ++ (pos.getX() + radiusBlocks) >> 4, ++ (pos.getZ() - radiusBlocks) >> 4, ++ (pos.getZ() + radiusBlocks) >> 4, ++ chunkStatus, priority, onLoad ++ ); ++ } ++ ++ public final void loadChunksAsync(int minChunkX, int maxChunkX, int minChunkZ, int maxChunkZ, ++ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, ++ java.util.function.Consumer> onLoad) { ++ this.loadChunksAsync(minChunkX, maxChunkX, minChunkZ, maxChunkZ, net.minecraft.world.level.chunk.ChunkStatus.FULL, priority, onLoad); ++ } ++ ++ public final void loadChunksAsync(int minChunkX, int maxChunkX, int minChunkZ, int maxChunkZ, ++ net.minecraft.world.level.chunk.ChunkStatus chunkStatus, ++ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, ++ java.util.function.Consumer> onLoad) { + List ret = new java.util.ArrayList<>(); +- it.unimi.dsi.fastutil.ints.IntArrayList ticketLevels = new it.unimi.dsi.fastutil.ints.IntArrayList(); +- +- int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; +- int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; +- +- int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; +- int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; +- +- int minChunkX = minBlockX >> 4; +- int maxChunkX = maxBlockX >> 4; +- +- int minChunkZ = minBlockZ >> 4; +- int maxChunkZ = maxBlockZ >> 4; + + ServerChunkCache chunkProvider = this.getChunkSource(); + + int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); +- int[] loadedChunks = new int[1]; ++ java.util.concurrent.atomic.AtomicInteger loadedChunks = new java.util.concurrent.atomic.AtomicInteger(); + +- Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++); ++ Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter.getAndIncrement()); ++ ++ int ticketLevel = 33 + net.minecraft.world.level.chunk.ChunkStatus.getDistance(chunkStatus); + + java.util.function.Consumer consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> { + if (chunk != null) { +- int ticketLevel = Math.max(33, chunkProvider.chunkMap.getUpdatingChunkIfPresent(chunk.getPos().toLong()).getTicketLevel()); ++ synchronized (ret) { // Folia - region threading - make callback thread-safe TODO rebase + ret.add(chunk); +- ticketLevels.add(ticketLevel); ++ } // Folia - region threading - make callback thread-safe TODO rebase + chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier); + } +- if (++loadedChunks[0] == requiredChunks) { ++ if (loadedChunks.incrementAndGet() == requiredChunks) { + try { + onLoad.accept(java.util.Collections.unmodifiableList(ret)); + } finally { + for (int i = 0, len = ret.size(); i < len; ++i) { + ChunkPos chunkPos = ret.get(i).getPos(); +- int ticketLevel = ticketLevels.getInt(i); + + chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); + chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier); +@@ -318,12 +333,228 @@ public class ServerLevel extends Level implements WorldGenLevel { + for (int cx = minChunkX; cx <= maxChunkX; ++cx) { + for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { + io.papermc.paper.chunk.system.ChunkSystem.scheduleChunkLoad( +- this, cx, cz, net.minecraft.world.level.chunk.ChunkStatus.FULL, true, priority, consumer ++ this, cx, cz, chunkStatus, true, priority, consumer + ); + } + } + } +- // Paper end ++ ++ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, ++ java.util.function.Consumer> onLoad) { ++ ++ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; ++ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; ++ ++ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; ++ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; ++ ++ int minChunkX = minBlockX >> 4; ++ int maxChunkX = maxBlockX >> 4; ++ ++ int minChunkZ = minBlockZ >> 4; ++ int maxChunkZ = maxBlockZ >> 4; ++ ++ this.loadChunksAsync(minChunkX, maxChunkX, minChunkZ, maxChunkZ, priority, onLoad); ++ } ++ ++ // Paper start - rewrite chunk system ++ public final io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler chunkTaskScheduler; ++ public final io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController chunkDataControllerNew ++ = new io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController(io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.CHUNK_DATA) { ++ ++ @Override ++ public net.minecraft.world.level.chunk.storage.RegionFileStorage getCache() { ++ return ServerLevel.this.getChunkSource().chunkMap.regionFileCache; ++ } ++ ++ @Override ++ public void writeData(int chunkX, int chunkZ, net.minecraft.nbt.CompoundTag compound) throws IOException { ++ ServerLevel.this.getChunkSource().chunkMap.write(new ChunkPos(chunkX, chunkZ), compound); ++ } ++ ++ @Override ++ public net.minecraft.nbt.CompoundTag readData(int chunkX, int chunkZ) throws IOException { ++ return ServerLevel.this.getChunkSource().chunkMap.readSync(new ChunkPos(chunkX, chunkZ)); ++ } ++ }; ++ public final io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController poiDataControllerNew ++ = new io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController(io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.POI_DATA) { ++ ++ @Override ++ public net.minecraft.world.level.chunk.storage.RegionFileStorage getCache() { ++ return ServerLevel.this.getChunkSource().chunkMap.getPoiManager(); ++ } ++ ++ @Override ++ public void writeData(int chunkX, int chunkZ, net.minecraft.nbt.CompoundTag compound) throws IOException { ++ ServerLevel.this.getChunkSource().chunkMap.getPoiManager().write(new ChunkPos(chunkX, chunkZ), compound); ++ } ++ ++ @Override ++ public net.minecraft.nbt.CompoundTag readData(int chunkX, int chunkZ) throws IOException { ++ return ServerLevel.this.getChunkSource().chunkMap.getPoiManager().read(new ChunkPos(chunkX, chunkZ)); ++ } ++ }; ++ public final io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController entityDataControllerNew ++ = new io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController(io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.ENTITY_DATA) { ++ ++ @Override ++ public net.minecraft.world.level.chunk.storage.RegionFileStorage getCache() { ++ return ServerLevel.this.entityStorage; ++ } ++ ++ @Override ++ public void writeData(int chunkX, int chunkZ, net.minecraft.nbt.CompoundTag compound) throws IOException { ++ ServerLevel.this.writeEntityChunk(chunkX, chunkZ, compound); ++ } ++ ++ @Override ++ public net.minecraft.nbt.CompoundTag readData(int chunkX, int chunkZ) throws IOException { ++ return ServerLevel.this.readEntityChunk(chunkX, chunkZ); ++ } ++ }; ++ private final EntityRegionFileStorage entityStorage; ++ ++ private static final class EntityRegionFileStorage extends net.minecraft.world.level.chunk.storage.RegionFileStorage { ++ ++ public EntityRegionFileStorage(Path directory, boolean dsync) { ++ super(directory, dsync); ++ } ++ ++ protected void write(ChunkPos pos, net.minecraft.nbt.CompoundTag nbt) throws IOException { ++ ChunkPos nbtPos = nbt == null ? null : EntityStorage.readChunkPos(nbt); ++ if (nbtPos != null && !pos.equals(nbtPos)) { ++ throw new IllegalArgumentException( ++ "Entity chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + pos.toString() ++ + " but compound says coordinate is " + nbtPos + " for world: " + this ++ ); ++ } ++ super.write(pos, nbt); ++ } ++ } ++ ++ private void writeEntityChunk(int chunkX, int chunkZ, net.minecraft.nbt.CompoundTag compound) throws IOException { ++ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { ++ io.papermc.paper.chunk.system.io.RegionFileIOThread.scheduleSave( ++ this, chunkX, chunkZ, compound, ++ io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.ENTITY_DATA); ++ return; ++ } ++ this.entityStorage.write(new ChunkPos(chunkX, chunkZ), compound); ++ } ++ ++ private net.minecraft.nbt.CompoundTag readEntityChunk(int chunkX, int chunkZ) throws IOException { ++ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { ++ return io.papermc.paper.chunk.system.io.RegionFileIOThread.loadData( ++ this, chunkX, chunkZ, io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.ENTITY_DATA, ++ io.papermc.paper.chunk.system.io.RegionFileIOThread.getIOBlockingPriorityForCurrentThread() ++ ); ++ } ++ return this.entityStorage.read(new ChunkPos(chunkX, chunkZ)); ++ } ++ ++ private final io.papermc.paper.chunk.system.entity.EntityLookup entityLookup; ++ public final io.papermc.paper.chunk.system.entity.EntityLookup getEntityLookup() { ++ return this.entityLookup; ++ } ++ ++ private final java.util.concurrent.atomic.AtomicLong nonFullSyncLoadIdGenerator = new java.util.concurrent.atomic.AtomicLong(); ++ ++ private ChunkAccess getIfAboveStatus(int chunkX, int chunkZ, net.minecraft.world.level.chunk.ChunkStatus status) { ++ io.papermc.paper.chunk.system.scheduling.NewChunkHolder loaded = ++ this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkX, chunkZ); ++ io.papermc.paper.chunk.system.scheduling.NewChunkHolder.ChunkCompletion loadedCompletion; ++ if (loaded != null && (loadedCompletion = loaded.getLastChunkCompletion()) != null && loadedCompletion.genStatus().isOrAfter(status)) { ++ return loadedCompletion.chunk(); ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public ChunkAccess syncLoadNonFull(int chunkX, int chunkZ, net.minecraft.world.level.chunk.ChunkStatus status) { ++ if (status == null || status.isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.FULL)) { ++ throw new IllegalArgumentException("Status: " + status); ++ } ++ ChunkAccess loaded = this.getIfAboveStatus(chunkX, chunkZ, status); ++ if (loaded != null) { ++ return loaded; ++ } ++ ++ Long ticketId = Long.valueOf(this.nonFullSyncLoadIdGenerator.getAndIncrement()); ++ int ticketLevel = 33 + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status); ++ this.chunkTaskScheduler.chunkHolderManager.addTicketAtLevel( ++ TicketType.NON_FULL_SYNC_LOAD, chunkX, chunkZ, ticketLevel, ticketId ++ ); ++ this.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(); ++ ++ this.chunkTaskScheduler.beginChunkLoadForNonFullSync(chunkX, chunkZ, status, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.BLOCKING); ++ ++ // we could do a simple spinwait here, since we do not need to process tasks while performing this load ++ // but we process tasks only because it's a better use of the time spent ++ this.chunkSource.mainThreadProcessor.managedBlock(() -> { ++ return ServerLevel.this.getIfAboveStatus(chunkX, chunkZ, status) != null; ++ }); ++ ++ loaded = ServerLevel.this.getIfAboveStatus(chunkX, chunkZ, status); ++ if (loaded == null) { ++ throw new IllegalStateException("Expected chunk to be loaded for status " + status); ++ } ++ ++ this.chunkTaskScheduler.chunkHolderManager.removeTicketAtLevel( ++ TicketType.NON_FULL_SYNC_LOAD, chunkX, chunkZ, ticketLevel, ticketId ++ ); ++ ++ return loaded; ++ } ++ ++ public final int getRegionChunkShift() { ++ // placeholder for folia ++ return io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift(); ++ } ++ // Paper end - rewrite chunk system ++ ++ public final io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader playerChunkLoader = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader(this); ++ private final java.util.concurrent.atomic.AtomicReference viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1)); ++ ++ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances getViewDistances() { ++ return this.viewDistances.get(); ++ } ++ ++ private void updateViewDistance(final java.util.function.Function update) { ++ for (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances curr = this.viewDistances.get();;) { ++ if (this.viewDistances.compareAndSet(curr, update.apply(curr))) { ++ return; ++ } ++ } ++ } ++ ++ public void setTickViewDistance(final int distance) { ++ if ((distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE)) { ++ throw new IllegalArgumentException("Tick view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE) + ", got: " + distance); ++ } ++ this.updateViewDistance((input) -> { ++ return input.setTickViewDistance(distance); ++ }); ++ } ++ ++ public void setLoadViewDistance(final int distance) { ++ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { ++ throw new IllegalArgumentException("Load view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); ++ } ++ this.updateViewDistance((input) -> { ++ return input.setLoadViewDistance(distance); ++ }); ++ } ++ ++ public void setSendViewDistance(final int distance) { ++ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { ++ throw new IllegalArgumentException("Send view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); ++ } ++ this.updateViewDistance((input) -> { ++ return input.setSendViewDistance(distance); ++ }); ++ } + + // Paper start - optimise getPlayerByUUID + @Nullable +@@ -376,16 +607,16 @@ public class ServerLevel extends Level implements WorldGenLevel { + // CraftBukkit end + boolean flag2 = minecraftserver.forceSynchronousWrites(); + DataFixer datafixer = minecraftserver.getFixerUpper(); +- EntityPersistentStorage entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver); ++ this.entityStorage = new EntityRegionFileStorage(convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), flag2); // Paper - rewrite chunk system //EntityPersistentStorage entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver); + +- this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage); ++ // this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage, this.entitySliceManager); // Paper // Paper - rewrite chunk system + StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager(); + int j = this.spigotConfig.viewDistance; // Spigot + int k = this.spigotConfig.simulationDistance; // Spigot +- PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; ++ //PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; // Paper - rewrite chunk system + +- Objects.requireNonNull(this.entityManager); +- this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, persistententitysectionmanager::updateChunkStatus, () -> { ++ //Objects.requireNonNull(this.entityManager); // Paper - rewrite chunk system ++ this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, null, () -> { // Paper - rewrite chunk system + return minecraftserver.overworld().getDataStorage(); + }); + this.chunkSource.getGeneratorState().ensureStructuresGenerated(); +@@ -414,6 +645,9 @@ public class ServerLevel extends Level implements WorldGenLevel { + return (RandomSequences) this.getDataStorage().computeIfAbsent(RandomSequences.factory(l), "random_sequences"); + }); + this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit ++ ++ this.chunkTaskScheduler = new io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler(this, io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.workerThreads); // Paper - rewrite chunk system ++ this.entityLookup = new io.papermc.paper.chunk.system.entity.EntityLookup(this, new EntityCallbacks()); // Paper - rewrite chunk system + } + + // Paper start +@@ -546,7 +780,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + gameprofilerfiller.push("checkDespawn"); + entity.checkDespawn(); + gameprofilerfiller.pop(); +- if (this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { ++ if (true || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { // Paper - now always true if in the ticking list + Entity entity1 = entity.getVehicle(); + + if (entity1 != null) { +@@ -571,13 +805,16 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + + gameprofilerfiller.push("entityManagement"); +- this.entityManager.tick(); ++ //this.entityManager.tick(); // Paper - rewrite chunk system + gameprofilerfiller.pop(); + } + + @Override + public boolean shouldTickBlocksAt(long chunkPos) { +- return this.chunkSource.chunkMap.getDistanceManager().inBlockTickingRange(chunkPos); ++ // Paper start - replace player chunk loader system ++ ChunkHolder holder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos); ++ return holder != null && holder.isTickingReady(); ++ // Paper end - replace player chunk loader system + } + + protected void tickTime() { +@@ -1054,6 +1291,11 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + + public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled) { ++ // Paper start - rewrite chunk system - add close param ++ this.save(progressListener, flush, savingDisabled, false); ++ } ++ public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled, boolean close) { ++ // Paper end - rewrite chunk system - add close param + ServerChunkCache chunkproviderserver = this.getChunkSource(); + + if (!savingDisabled) { +@@ -1069,16 +1311,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + + timings.worldSaveChunks.startTiming(); // Paper +- chunkproviderserver.save(flush); ++ if (!close) chunkproviderserver.save(flush); // Paper - rewrite chunk system ++ if (close) chunkproviderserver.close(true); // Paper - rewrite chunk system + timings.worldSaveChunks.stopTiming(); // Paper + }// Paper +- if (flush) { +- this.entityManager.saveAll(); +- } else { +- this.entityManager.autoSave(); +- } ++ // Paper - rewrite chunk system - entity saving moved into ChunkHolder + +- } ++ } else if (close) { chunkproviderserver.close(false); } // Paper - rewrite chunk system + + // CraftBukkit start - moved from MinecraftServer.saveChunks + ServerLevel worldserver1 = this; +@@ -1214,7 +1453,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + this.removePlayerImmediately((ServerPlayer) entity, Entity.RemovalReason.DISCARDED); + } + +- this.entityManager.addNewEntity(player); ++ this.entityLookup.addNewEntity(player); // Paper - rewite chunk system + } + + // CraftBukkit start +@@ -1245,7 +1484,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + // CraftBukkit end + +- return this.entityManager.addNewEntity(entity); ++ return this.entityLookup.addNewEntity(entity); // Paper - rewrite chunk system + } + } + +@@ -1257,10 +1496,10 @@ public class ServerLevel extends Level implements WorldGenLevel { + public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { + // CraftBukkit end + Stream stream = entity.getSelfAndPassengers().map(Entity::getUUID); // CraftBukkit - decompile error +- PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; ++ //PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; // Paper - rewrite chunk system + +- Objects.requireNonNull(this.entityManager); +- if (stream.anyMatch(persistententitysectionmanager::isLoaded)) { ++ //Objects.requireNonNull(this.entityManager); // Paper - rewrite chunk system ++ if (stream.anyMatch(this.entityLookup::hasEntity)) { // Paper - rewrite chunk system + return false; + } else { + this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit +@@ -1894,7 +2133,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + } + +- bufferedwriter.write(String.format(Locale.ROOT, "entities: %s\n", this.entityManager.gatherStats())); ++ bufferedwriter.write(String.format(Locale.ROOT, "entities: %s\n", this.entityLookup.getDebugInfo())); // Paper - rewrite chunk system + bufferedwriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size())); + bufferedwriter.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count())); + bufferedwriter.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count())); +@@ -1943,7 +2182,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + BufferedWriter bufferedwriter2 = Files.newBufferedWriter(path1); + + try { +- playerchunkmap.dumpChunks(bufferedwriter2); ++ //playerchunkmap.dumpChunks(bufferedwriter2); // Paper - rewrite chunk system + } catch (Throwable throwable4) { + if (bufferedwriter2 != null) { + try { +@@ -1964,7 +2203,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + BufferedWriter bufferedwriter3 = Files.newBufferedWriter(path2); + + try { +- this.entityManager.dumpSections(bufferedwriter3); ++ //this.entityManager.dumpSections(bufferedwriter3); // Paper - rewrite chunk system + } catch (Throwable throwable6) { + if (bufferedwriter3 != null) { + try { +@@ -2106,7 +2345,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + + @VisibleForTesting + public String getWatchdogStats() { +- return String.format(Locale.ROOT, "players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s", this.players.size(), this.entityManager.gatherStats(), ServerLevel.getTypeCount(this.entityManager.getEntityGetter().getAll(), (entity) -> { ++ return String.format(Locale.ROOT, "players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s", this.players.size(), this.entityLookup.getDebugInfo(), ServerLevel.getTypeCount(this.entityLookup.getAll(), (entity) -> { // Paper - rewrite chunk system + return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); + }), this.blockEntityTickers.size(), ServerLevel.getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType), this.getBlockTicks().count(), this.getFluidTicks().count(), this.gatherChunkSourceStats()); + } +@@ -2166,15 +2405,15 @@ public class ServerLevel extends Level implements WorldGenLevel { + @Override + public LevelEntityGetter getEntities() { + org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot +- return this.entityManager.getEntityGetter(); ++ return this.entityLookup; // Paper - rewrite chunk system + } + +- public void addLegacyChunkEntities(Stream entities) { +- this.entityManager.addLegacyChunkEntities(entities); ++ public void addLegacyChunkEntities(Stream entities, ChunkPos forChunk) { // Paper - rewrite chunk system ++ this.entityLookup.addLegacyChunkEntities(entities.toList(), forChunk); // Paper - rewrite chunk system + } + +- public void addWorldGenChunkEntities(Stream entities) { +- this.entityManager.addWorldGenChunkEntities(entities); ++ public void addWorldGenChunkEntities(Stream entities, ChunkPos forChunk) { // Paper - rewrite chunk system ++ this.entityLookup.addWorldGenChunkEntities(entities.toList(), forChunk); // Paper - rewrite chunk system + } + + public void startTickingChunk(LevelChunk chunk) { +@@ -2190,34 +2429,49 @@ public class ServerLevel extends Level implements WorldGenLevel { + @Override + public void close() throws IOException { + super.close(); +- this.entityManager.close(); ++ //this.entityManager.close(); // Paper - rewrite chunk system + } + + @Override + public String gatherChunkSourceStats() { + String s = this.chunkSource.gatherStats(); + +- return "Chunks[S] W: " + s + " E: " + this.entityManager.gatherStats(); ++ return "Chunks[S] W: " + s + " E: " + this.entityLookup.getDebugInfo(); // Paper - rewrite chunk system + } + + public boolean areEntitiesLoaded(long chunkPos) { +- return this.entityManager.areEntitiesLoaded(chunkPos); ++ // Paper start - rewrite chunk system ++ return this.getChunkIfLoadedImmediately(ChunkPos.getX(chunkPos), ChunkPos.getZ(chunkPos)) != null; ++ // Paper end - rewrite chunk system + } + + private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { +- return this.areEntitiesLoaded(chunkPos) && this.chunkSource.isPositionTicking(chunkPos); ++ // Paper start - optimize is ticking ready type functions ++ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkPos); ++ // isTicking implies the chunk is loaded, and the chunk is loaded now implies the entities are loaded ++ return chunkHolder != null && chunkHolder.isTickingReady(); ++ // Paper end + } + + public boolean isPositionEntityTicking(BlockPos pos) { +- return this.entityManager.canPositionTick(pos) && this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(ChunkPos.asLong(pos)); ++ // Paper start - rewrite chunk system ++ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(io.papermc.paper.util.CoordinateUtils.getChunkKey(pos)); ++ return chunkHolder != null && chunkHolder.isEntityTickingReady(); ++ // Paper end - rewrite chunk system + } + + public boolean isNaturalSpawningAllowed(BlockPos pos) { +- return this.entityManager.canPositionTick(pos); ++ // Paper start - rewrite chunk system ++ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(io.papermc.paper.util.CoordinateUtils.getChunkKey(pos)); ++ return chunkHolder != null && chunkHolder.isEntityTickingReady(); ++ // Paper end - rewrite chunk system + } + + public boolean isNaturalSpawningAllowed(ChunkPos pos) { +- return this.entityManager.canPositionTick(pos); ++ // Paper start - rewrite chunk system ++ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(io.papermc.paper.util.CoordinateUtils.getChunkKey(pos)); ++ return chunkHolder != null && chunkHolder.isEntityTickingReady(); ++ // Paper end - rewrite chunk system + } + + @Override +@@ -2238,7 +2492,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + CrashReportCategory crashreportsystemdetails = super.fillReportDetails(report); + + crashreportsystemdetails.setDetail("Loaded entity count", () -> { +- return String.valueOf(this.entityManager.count()); ++ return String.valueOf(this.entityLookup.getAllCopy().length); // Paper + }); + return crashreportsystemdetails; + } +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index ae5a2136a0e266d4c35190f5d33552994c842786..5657f1ecbadda96a79978f918393c0c9a58dca83 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -276,6 +276,50 @@ public class ServerPlayer extends Player { + public @Nullable String clientBrandName = null; // Paper - Brand support + public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event + ++ // Paper start - replace player chunk loader ++ private final java.util.concurrent.atomic.AtomicReference viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1)); ++ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; ++ ++ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances getViewDistances() { ++ return this.viewDistances.get(); ++ } ++ ++ private void updateViewDistance(final java.util.function.Function update) { ++ for (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances curr = this.viewDistances.get();;) { ++ if (this.viewDistances.compareAndSet(curr, update.apply(curr))) { ++ return; ++ } ++ } ++ } ++ ++ public void setTickViewDistance(final int distance) { ++ if ((distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE)) { ++ throw new IllegalArgumentException("Tick view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE) + ", got: " + distance); ++ } ++ this.updateViewDistance((input) -> { ++ return input.setTickViewDistance(distance); ++ }); ++ } ++ ++ public void setLoadViewDistance(final int distance) { ++ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { ++ throw new IllegalArgumentException("Load view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); ++ } ++ this.updateViewDistance((input) -> { ++ return input.setLoadViewDistance(distance); ++ }); ++ } ++ ++ public void setSendViewDistance(final int distance) { ++ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { ++ throw new IllegalArgumentException("Send view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); ++ } ++ this.updateViewDistance((input) -> { ++ return input.setSendViewDistance(distance); ++ }); ++ } ++ // Paper end - replace player chunk loader ++ + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { + super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); + this.chatVisibility = ChatVisiblity.FULL; +diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +index 97662f8c8c125cb964d46b9095509a0da9796dba..f382d138959b34bfc3a114bc9d96e056cccbfc89 100644 +--- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java ++++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +@@ -37,15 +37,12 @@ import net.minecraft.world.level.chunk.ChunkStatus; + public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable { + public static final int DEFAULT_BATCH_SIZE = 1000; + private static final Logger LOGGER = LogUtils.getLogger(); +- private final ProcessorMailbox taskMailbox; +- private final ObjectList> lightTasks = new ObjectArrayList<>(); ++ // Paper - rewrite chunk system + private final ChunkMap chunkMap; +- private final ProcessorHandle> sorterMailbox; +- private final int taskPerBatch = 1000; +- private final AtomicBoolean scheduled = new AtomicBoolean(); ++ // Paper - rewrite chunk system + + // Paper start - replace light engine impl +- protected final ca.spottedleaf.starlight.common.light.StarLightInterface theLightEngine; ++ public final ca.spottedleaf.starlight.common.light.StarLightInterface theLightEngine; + public final boolean hasBlockLight; + public final boolean hasSkyLight; + // Paper end - replace light engine impl +@@ -53,8 +50,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox processor, ProcessorHandle> executor) { + super(chunkProvider, false, false); // Paper - destroy vanilla light engine state + this.chunkMap = chunkStorage; +- this.sorterMailbox = executor; +- this.taskMailbox = processor; ++ // Paper - rewrite chunk system + // Paper start - replace light engine impl + this.hasBlockLight = true; + this.hasSkyLight = hasBlockLight; // Nice variable name. +@@ -98,7 +94,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + ++totalChunks; + } + +- this.taskMailbox.tell(() -> { ++ this.chunkMap.level.chunkTaskScheduler.radiusAwareScheduler.queueInfiniteRadiusTask(() -> { // Paper - rewrite chunk system + this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> { + chunkLightCallback.accept(chunkPos); + ((java.util.concurrent.Executor)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().mainThreadProcessor).execute(() -> { +@@ -115,7 +111,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); + + private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, +- final Supplier runnable) { ++ final Supplier runnable) { // Paper - rewrite chunk system + final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld(); + + final ChunkAccess center = this.theLightEngine.getAnyChunkNow(chunkX, chunkZ); +@@ -142,7 +138,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); + +- final ca.spottedleaf.starlight.common.light.StarLightInterface.LightQueue.ChunkTasks updateFuture = runnable.get(); ++ final io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks updateFuture = runnable.get(); // Paper - rewrite chunk system + + if (updateFuture == null) { + // not scheduled +@@ -282,17 +278,11 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + } + + private void addTask(int x, int z, ThreadedLevelLightEngine.TaskType stage, Runnable task) { +- this.addTask(x, z, this.chunkMap.getChunkQueueLevel(ChunkPos.asLong(x, z)), stage, task); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + private void addTask(int x, int z, IntSupplier completedLevelSupplier, ThreadedLevelLightEngine.TaskType stage, Runnable task) { +- this.sorterMailbox.tell(ChunkTaskPriorityQueueSorter.message(() -> { +- this.lightTasks.add(Pair.of(stage, task)); +- if (this.lightTasks.size() >= 1000) { +- this.runUpdate(); +- } +- +- }, ChunkPos.asLong(x, z), completedLevelSupplier)); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + @Override +@@ -334,90 +324,15 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + } + + public CompletableFuture lightChunk(ChunkAccess chunk, boolean excludeBlocks) { +- // Paper start - replace light engine impl +- if (true) { +- boolean lit = excludeBlocks; +- final ChunkPos chunkPos = chunk.getPos(); +- +- return CompletableFuture.supplyAsync(() -> { +- final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk); +- if (!lit) { +- chunk.setLightCorrect(false); +- this.theLightEngine.lightChunk(chunk, emptySections); +- chunk.setLightCorrect(true); +- } else { +- this.theLightEngine.forceLoadInChunk(chunk, emptySections); +- // can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have +- // them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should +- // catch what we miss here. +- this.theLightEngine.checkChunkEdges(chunkPos.x, chunkPos.z); +- } +- +- this.chunkMap.releaseLightTicket(chunkPos); +- return chunk; +- }, (runnable) -> { +- this.theLightEngine.scheduleChunkLight(chunkPos, runnable); +- this.tryScheduleUpdate(); +- }).whenComplete((final ChunkAccess c, final Throwable throwable) -> { +- if (throwable != null) { +- LOGGER.error("Failed to light chunk " + chunkPos, throwable); +- } +- }); +- } +- // Paper end - replace light engine impl +- ChunkPos chunkPos = chunk.getPos(); +- chunk.setLightCorrect(false); +- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { +- if (!excludeBlocks) { +- super.propagateLightSources(chunkPos); +- } +- +- }, () -> { +- return "lightChunk " + chunkPos + " " + excludeBlocks; +- })); +- return CompletableFuture.supplyAsync(() -> { +- chunk.setLightCorrect(true); +- this.chunkMap.releaseLightTicket(chunkPos); +- return chunk; +- }, (task) -> { +- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, task); +- }); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + public void tryScheduleUpdate() { +- if (this.hasLightWork() && this.scheduled.compareAndSet(false, true)) { // Paper // Paper - rewrite light engine +- this.taskMailbox.tell(() -> { +- this.runUpdate(); +- this.scheduled.set(false); +- }); +- } +- ++ // Paper - rewrite chunk system + } + + private void runUpdate() { +- int i = Math.min(this.lightTasks.size(), 1000); +- ObjectListIterator> objectListIterator = this.lightTasks.iterator(); +- +- int j; +- for(j = 0; objectListIterator.hasNext() && j < i; ++j) { +- Pair pair = objectListIterator.next(); +- if (pair.getFirst() == ThreadedLevelLightEngine.TaskType.PRE_UPDATE) { +- pair.getSecond().run(); +- } +- } +- +- objectListIterator.back(j); +- this.theLightEngine.propagateChanges(); // Paper - rewrite light engine +- +- for(int var5 = 0; objectListIterator.hasNext() && var5 < i; ++var5) { +- Pair pair2 = objectListIterator.next(); +- if (pair2.getFirst() == ThreadedLevelLightEngine.TaskType.POST_UPDATE) { +- pair2.getSecond().run(); +- } +- +- objectListIterator.remove(); +- } +- ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + public CompletableFuture waitForPendingTasks(int x, int z) { +diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java +index b346fa94b23d81da7da073f71dd12e672e0f079c..0edb97617f0c0da8dda901a26891b33c324715c7 100644 +--- a/src/main/java/net/minecraft/server/level/Ticket.java ++++ b/src/main/java/net/minecraft/server/level/Ticket.java +@@ -6,9 +6,12 @@ public final class Ticket implements Comparable> { + private final TicketType type; + private final int ticketLevel; + public final T key; +- private long createdTick; ++ // Paper start - rewrite chunk system ++ public long removeDelay; + +- protected Ticket(TicketType type, int level, T argument) { ++ public Ticket(TicketType type, int level, T argument, long removeDelay) { ++ this.removeDelay = removeDelay; ++ // Paper end - rewrite chunk system + this.type = type; + this.ticketLevel = level; + this.key = argument; +@@ -44,7 +47,7 @@ public final class Ticket implements Comparable> { + + @Override + public String toString() { +- return "Ticket[" + this.type + " " + this.ticketLevel + " (" + this.key + ")] at " + this.createdTick; ++ return "Ticket[" + this.type + " " + this.ticketLevel + " (" + this.key + ")] to die in " + this.removeDelay; // Paper - rewrite chunk system + } + + public TicketType getType() { +@@ -56,11 +59,10 @@ public final class Ticket implements Comparable> { + } + + protected void setCreatedTick(long tickCreated) { +- this.createdTick = tickCreated; ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + + protected boolean timedOut(long currentTick) { +- long l = this.type.timeout(); +- return l != 0L && currentTick - this.createdTick > l; ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + } +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index 6051e5f272838ef23276a90e21c2fc821ca155d1..658e63ebde81dc14c8ab5850fb246dc0aab25dea 100644 +--- a/src/main/java/net/minecraft/server/level/TicketType.java ++++ b/src/main/java/net/minecraft/server/level/TicketType.java +@@ -8,6 +8,7 @@ import net.minecraft.world.level.ChunkPos; + + public class TicketType { + public static final TicketType FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper ++ public static final TicketType ASYNC_LOAD = create("async_load", Long::compareTo); // Paper + + private final String name; + private final Comparator comparator; +@@ -27,6 +28,15 @@ public class TicketType { + public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit + public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit + public static final TicketType CHUNK_RELIGHT = create("light_update", Long::compareTo); // Paper - ensure chunks stay loaded for lighting ++ // Paper start - rewrite chunk system ++ public static final TicketType CHUNK_LOAD = create("chunk_load", Long::compareTo); ++ public static final TicketType STATUS_UPGRADE = create("status_upgrade", Long::compareTo); ++ public static final TicketType ENTITY_LOAD = create("entity_load", Long::compareTo); ++ public static final TicketType POI_LOAD = create("poi_load", Long::compareTo); ++ public static final TicketType UNLOAD_COOLDOWN = create("unload_cooldown", (u1, u2) -> 0, 5 * 20); ++ public static final TicketType NON_FULL_SYNC_LOAD = create("non_full_sync_load", Long::compareTo); ++ public static final TicketType DELAY_UNLOAD = create("delay_unload", Comparator.comparingLong(ChunkPos::toLong), 1); ++ // Paper end - rewrite chunk system + + public static TicketType create(String name, Comparator argumentComparator) { + return new TicketType<>(name, argumentComparator, 0L); +diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +index c3e7bd8865cc8990fc59f1ff0dfc1697cbb5ca49..5ece375eaf6bcc61864997a389bb5e24625e4505 100644 +--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java ++++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +@@ -535,4 +535,21 @@ public class WorldGenRegion implements WorldGenLevel { + public long nextSubTickCount() { + return this.subTickCount.getAndIncrement(); + } ++ ++ // Paper start ++ // No-op, this class doesn't provide entity access ++ @Override ++ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public void getEntities(Entity except, AABB box, Predicate predicate, List into) {} ++ ++ @Override ++ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) {} ++ ++ @Override ++ public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java +index 13209267c26f46492a92e820889a9be0bd2287a0..f3b96a921e7d085b51da62fa5493384a7ded1f9d 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java ++++ b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java +@@ -44,17 +44,23 @@ public class PlayerChunkSender { + + public void dropChunk(ServerPlayer player, ChunkPos pos) { + if (!this.pendingChunks.remove(pos.toLong()) && player.isAlive()) { ++ // Paper start - rewrite player chunk loader ++ dropChunkStatic(player, pos); ++ } ++ } ++ public static void dropChunkStatic(ServerPlayer player, ChunkPos pos) { ++ player.serverLevel().chunkSource.chunkMap.getVisibleChunkIfPresent(pos.toLong()).removePlayer(player); + player.connection.send(new ClientboundForgetLevelChunkPacket(pos)); + // Paper start - PlayerChunkUnloadEvent + if (io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0) { + new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(player.getBukkitEntity().getWorld().getChunkAt(pos.longKey), player.getBukkitEntity()).callEvent(); + } + // Paper end - PlayerChunkUnloadEvent +- } +- + } ++ // Paper end - rewrite player chunk loader + + public void sendNextChunks(ServerPlayer player) { ++ if (true) return; // Paper - rewrite player chunk loader + if (this.unacknowledgedBatches < this.maxUnacknowledgedBatches) { + float f = Math.max(1.0F, this.desiredChunksPerTick); + this.batchQuota = Math.min(this.batchQuota + this.desiredChunksPerTick, f); +@@ -80,7 +86,8 @@ public class PlayerChunkSender { + } + } + +- private static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) { ++ public static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) { // Paper - rewrite chunk loader - public ++ handler.player.serverLevel().chunkSource.chunkMap.getVisibleChunkIfPresent(chunk.getPos().toLong()).addPlayer(handler.player); + handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), (BitSet)null, (BitSet)null)); + // Paper start - PlayerChunkLoadEvent + if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { +@@ -110,6 +117,7 @@ public class PlayerChunkSender { + } + + public void onChunkBatchReceivedByClient(float desiredBatchSize) { ++ if (true) return; // Paper - rewrite player chunk loader + --this.unacknowledgedBatches; + this.desiredChunksPerTick = Double.isNaN((double)desiredBatchSize) ? 0.01F : Mth.clamp(desiredBatchSize, 0.01F, 64.0F); + if (this.unacknowledgedBatches == 0) { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 16ef96f0b2f68556b89c9d732d0e1a407f083fdc..d2e65c105c38c71a6b1739b95547772511a36345 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -293,7 +293,7 @@ public abstract class PlayerList { + boolean flag2 = gamerules.getBoolean(GameRules.RULE_LIMITED_CRAFTING); + + // Spigot - view distance +- playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, worldserver1.spigotConfig.simulationDistance, flag1, !flag, flag2, player.createCommonSpawnInfo(worldserver1))); ++ 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())); +@@ -943,8 +943,8 @@ public abstract class PlayerList { + LevelData worlddata = worldserver2.getLevelData(); + + entityplayer1.connection.send(new ClientboundRespawnPacket(entityplayer1.createCommonSpawnInfo(worldserver2), (byte) i)); +- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot +- entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.spigotConfig.simulationDistance)); // Spigot ++ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getWorld().getSendViewDistance())); // Spigot // Paper - replace old player chunk management ++ entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.getWorld().getSimulationDistance())); // Spigot // Paper - replace old player chunk management + entityplayer1.connection.teleport(CraftLocation.toBukkit(entityplayer1.position(), worldserver2.getWorld(), entityplayer1.getYRot(), entityplayer1.getXRot())); // CraftBukkit + entityplayer1.connection.send(new ClientboundSetDefaultSpawnPositionPacket(worldserver1.getSharedSpawnPos(), worldserver1.getSharedSpawnAngle())); + entityplayer1.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); +@@ -1496,7 +1496,7 @@ public abstract class PlayerList { + + public void setViewDistance(int viewDistance) { + this.viewDistance = viewDistance; +- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); ++ //this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); // Paper - move into setViewDistance + Iterator iterator = this.server.getAllLevels().iterator(); + + while (iterator.hasNext()) { +@@ -1511,7 +1511,7 @@ public abstract class PlayerList { + + public void setSimulationDistance(int simulationDistance) { + this.simulationDistance = simulationDistance; +- this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); ++ //this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); // Paper - handled by playerchunkloader + Iterator iterator = this.server.getAllLevels().iterator(); + + while (iterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java +index ca788f0dcec4a117b410fe8348969e056b138b1e..a6ac76707da39cf86113003b1f326433fdc86c86 100644 +--- a/src/main/java/net/minecraft/util/SortedArraySet.java ++++ b/src/main/java/net/minecraft/util/SortedArraySet.java +@@ -14,6 +14,14 @@ public class SortedArraySet extends AbstractSet { + T[] contents; + int size; + ++ // Paper start - rewrite chunk system ++ public SortedArraySet(final SortedArraySet other) { ++ this.comparator = other.comparator; ++ this.size = other.size; ++ this.contents = Arrays.copyOf(other.contents, this.size); ++ } ++ // Paper end - rewrite chunk system ++ + private SortedArraySet(int initialCapacity, Comparator comparator) { + this.comparator = comparator; + if (initialCapacity < 0) { +@@ -22,6 +30,41 @@ public class SortedArraySet extends AbstractSet { + this.contents = (T[])castRawArray(new Object[initialCapacity]); + } + } ++ // Paper start - optimise removeIf ++ @Override ++ public boolean removeIf(java.util.function.Predicate filter) { ++ // prev. impl used an iterator, which could be n^2 and creates garbage ++ int i = 0, len = this.size; ++ T[] backingArray = this.contents; ++ ++ for (;;) { ++ if (i >= len) { ++ return false; ++ } ++ if (!filter.test(backingArray[i])) { ++ ++i; ++ continue; ++ } ++ break; ++ } ++ ++ // we only want to write back to backingArray if we really need to ++ ++ int lastIndex = i; // this is where new elements are shifted to ++ ++ for (; i < len; ++i) { ++ T curr = backingArray[i]; ++ if (!filter.test(curr)) { // if test throws we're screwed ++ backingArray[lastIndex++] = curr; ++ } ++ } ++ ++ // cleanup end ++ Arrays.fill(backingArray, lastIndex, len, null); ++ this.size = lastIndex; ++ return true; ++ } ++ // Paper end - optimise removeIf + + public static > SortedArraySet create() { + return create(10); +@@ -110,6 +153,31 @@ public class SortedArraySet extends AbstractSet { + } + } + ++ // Paper start - rewrite chunk system ++ public T replace(T object) { ++ int i = this.findIndex(object); ++ if (i >= 0) { ++ T old = this.contents[i]; ++ this.contents[i] = object; ++ return old; ++ } else { ++ this.addInternal(object, getInsertionPosition(i)); ++ return object; ++ } ++ } ++ ++ public T removeAndGet(T object) { ++ int i = this.findIndex(object); ++ if (i >= 0) { ++ final T ret = this.contents[i]; ++ this.removeInternal(i); ++ return ret; ++ } else { ++ return null; ++ } ++ } ++ // Paper end - rewrite chunk system ++ + @Override + public boolean remove(Object object) { + int i = this.findIndex((T)object); +diff --git a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java +index 640db9f71608310a64e09f1e3e677c01e6ccd98a..f2a7cb6ebed7a4b4019a09af2a025f624f6fe9c9 100644 +--- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java ++++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java +@@ -186,7 +186,11 @@ public class WorldUpgrader { + } + + WorldUpgrader.LOGGER.error("Error upgrading chunk {}", chunkcoordintpair, throwable); ++ // Paper start ++ } catch (IOException e) { ++ WorldUpgrader.LOGGER.error("Error upgrading chunk {}", chunkcoordintpair, e); + } ++ // Paper end + + if (flag1) { + ++this.converted; +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 8c42792ea5cb0fe5d1da7467875efda9be9040e1..bfc3242952fd425ab4f7cd42b21228baa6f75c87 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -479,6 +479,58 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + // Paper end + ++ // Paper start ++ /** ++ * Overriding this field will cause memory leaks. ++ */ ++ private final boolean hardCollides; ++ ++ private static final java.util.Map, Boolean> cachedOverrides = java.util.Collections.synchronizedMap(new java.util.WeakHashMap<>()); ++ { ++ /* // Goodbye, broken on reobf... ++ Boolean hardCollides = cachedOverrides.get(this.getClass()); ++ if (hardCollides == null) { ++ try { ++ java.lang.reflect.Method getHardCollisionBoxEntityMethod = Entity.class.getMethod("canCollideWith", Entity.class); ++ java.lang.reflect.Method hasHardCollisionBoxMethod = Entity.class.getMethod("canBeCollidedWith"); ++ if (!this.getClass().getMethod(hasHardCollisionBoxMethod.getName(), hasHardCollisionBoxMethod.getParameterTypes()).equals(hasHardCollisionBoxMethod) ++ || !this.getClass().getMethod(getHardCollisionBoxEntityMethod.getName(), getHardCollisionBoxEntityMethod.getParameterTypes()).equals(getHardCollisionBoxEntityMethod)) { ++ hardCollides = Boolean.TRUE; ++ } else { ++ hardCollides = Boolean.FALSE; ++ } ++ cachedOverrides.put(this.getClass(), hardCollides); ++ } ++ catch (ThreadDeath thr) { throw thr; } ++ catch (Throwable thr) { ++ // shouldn't happen, just explode ++ throw new RuntimeException(thr); ++ } ++ } */ ++ this.hardCollides = this instanceof Boat ++ || this instanceof net.minecraft.world.entity.monster.Shulker ++ || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart ++ || this.shouldHardCollide(); ++ } ++ ++ // plugins can override ++ protected boolean shouldHardCollide() { ++ return false; ++ } ++ ++ public final boolean hardCollides() { ++ return this.hardCollides; ++ } ++ ++ public net.minecraft.server.level.FullChunkStatus chunkStatus; ++ ++ public int sectionX = Integer.MIN_VALUE; ++ public int sectionY = Integer.MIN_VALUE; ++ public int sectionZ = Integer.MIN_VALUE; ++ ++ public boolean updatingSectionStatus = false; ++ // Paper end ++ + public Entity(EntityType type, Level world) { + this.id = Entity.ENTITY_COUNTER.incrementAndGet(); + this.passengers = ImmutableList.of(); +@@ -2564,11 +2616,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + return InteractionResult.PASS; + } + +- public boolean canCollideWith(Entity other) { ++ public boolean canCollideWith(Entity other) { // Paper - diff on change, hard colliding entities override this - TODO CHECK ON UPDATE - AbstractMinecart/Boat override + return other.canBeCollidedWith() && !this.isPassengerOfSameVehicle(other); + } + +- public boolean canBeCollidedWith() { ++ public boolean canBeCollidedWith() { // Paper - diff on change, hard colliding entities override this TODO CHECK ON UPDATE - Boat/Shulker override + return false; + } + +@@ -3993,6 +4045,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + }).count(); + } + ++ // Paper start - rewrite chunk system ++ public boolean hasAnyPlayerPassengers() { ++ // copied from below ++ if (this.passengers.isEmpty()) { return false; } ++ return this.getIndirectPassengersStream().anyMatch((entity) -> entity instanceof Player); ++ } ++ // Paper end - rewrite chunk system + public boolean hasExactlyOnePlayerPassenger() { + if (this.passengers.isEmpty()) { return false; } // Paper - Optimize indirect passenger iteration + return this.countPlayerPassengers() == 1; +@@ -4345,6 +4404,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + return; + } + // Paper end - Block invalid positions and bounding box ++ // Paper start - rewrite chunk system ++ if (this.updatingSectionStatus) { ++ LOGGER.error("Refusing to update position for entity {} to position {} since it is processing a section status update", this, new Vec3(x, y, z), new Throwable()); ++ return; ++ } ++ // Paper end - rewrite chunk system + // Paper start - Fix MC-4 + if (this instanceof ItemEntity) { + if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.fixEntityPositionDesync) { +@@ -4468,6 +4533,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + + @Override + public final void setRemoved(Entity.RemovalReason reason) { ++ // Paper start - rewrite chunk system ++ io.papermc.paper.util.TickThread.ensureTickThread(this, "Cannot remove entity off-main"); ++ if (!((ServerLevel)this.level).getEntityLookup().canRemoveEntity(this)) { ++ LOGGER.warn("Entity " + this + " is currently prevented from being removed from the world since it is processing section status updates", new Throwable()); ++ return; ++ } ++ // Paper end - rewrite chunk system + final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers + if (this.removalReason == null) { + this.removalReason = reason; +@@ -4477,7 +4549,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + this.stopRiding(); + } + +- this.getPassengers().forEach(Entity::stopRiding); ++ if (reason != RemovalReason.UNLOADED_TO_CHUNK) this.getPassengers().forEach(Entity::stopRiding); // Paper - chunk system - don't adjust passenger state when unloading, it's just not safe (and messes with our logic in entity chunk unload) + this.levelCallback.onRemove(reason); + // Paper start - Folia schedulers + if (!(this instanceof ServerPlayer) && reason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) { +@@ -4508,7 +4580,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + + @Override + public boolean shouldBeSaved() { +- return this.removalReason != null && !this.removalReason.shouldSave() ? false : (this.isPassenger() ? false : !this.isVehicle() || !this.hasExactlyOnePlayerPassenger()); ++ return this.removalReason != null && !this.removalReason.shouldSave() ? false : (this.isPassenger() ? false : !this.isVehicle() || !this.hasAnyPlayerPassengers()); // Paper - rewrite chunk system - it should check if the entity has ANY player passengers + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +index b18b896c624d5cadc02b1db9d011d82124d61d54..6f2c7baea0d1ac7813c7b85e1f5558573745762c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +@@ -38,12 +38,28 @@ import net.minecraft.world.level.chunk.storage.SectionStorage; + public class PoiManager extends SectionStorage { + public static final int MAX_VILLAGE_DISTANCE = 6; + public static final int VILLAGE_SECTION_SIZE = 1; +- private final PoiManager.DistanceTracker distanceTracker; +- private final LongSet loadedChunks = new LongOpenHashSet(); ++ // Paper start - rewrite chunk system ++ // the vanilla tracker needs to be replaced because it does not support level removes ++ public final net.minecraft.server.level.ServerLevel world; ++ private final io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D villageDistanceTracker = new io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D(); ++ static final int POI_DATA_SOURCE = 7; ++ public static int convertBetweenLevels(final int level) { ++ return POI_DATA_SOURCE - level; ++ } ++ ++ protected void updateDistanceTracking(long section) { ++ if (this.isVillageCenter(section)) { ++ this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE); ++ } else { ++ this.villageDistanceTracker.removeSource(section); ++ } ++ } ++ // Paper end - rewrite chunk system ++ + + public PoiManager(Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) { + super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world); +- this.distanceTracker = new PoiManager.DistanceTracker(); ++ this.world = (net.minecraft.server.level.ServerLevel)world; // Paper - rewrite chunk system + } + + public void add(BlockPos pos, Holder type) { +@@ -180,8 +196,8 @@ public class PoiManager extends SectionStorage { + } + + public int sectionsToVillage(SectionPos pos) { +- this.distanceTracker.runAllUpdates(); +- return this.distanceTracker.getLevel(pos.asLong()); ++ this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking util ++ return convertBetweenLevels(this.villageDistanceTracker.getLevel(io.papermc.paper.util.CoordinateUtils.getChunkSectionKey(pos))); // Paper - replace distance tracking util + } + + boolean isVillageCenter(long pos) { +@@ -195,21 +211,118 @@ public class PoiManager extends SectionStorage { + + @Override + public void tick(BooleanSupplier shouldKeepTicking) { +- super.tick(shouldKeepTicking); +- this.distanceTracker.runAllUpdates(); ++ this.villageDistanceTracker.propagateUpdates(); // Paper - rewrite chunk system + } + + @Override +- protected void setDirty(long pos) { +- super.setDirty(pos); +- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false); ++ public void setDirty(long pos) { ++ // Paper start - rewrite chunk system ++ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(pos); ++ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(pos); ++ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager manager = this.world.chunkTaskScheduler.chunkHolderManager; ++ io.papermc.paper.chunk.system.poi.PoiChunk chunk = manager.getPoiChunkIfLoaded(chunkX, chunkZ, false); ++ if (chunk != null) { ++ chunk.setDirty(true); ++ } ++ this.updateDistanceTracking(pos); ++ // Paper end - rewrite chunk system + } + + @Override + protected void onSectionLoad(long pos) { +- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false); ++ this.updateDistanceTracking(pos); // Paper - move to new distance tracking util ++ } ++ ++ // Paper start - rewrite chunk system ++ @Override ++ public Optional get(long pos) { ++ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(pos); ++ int chunkY = io.papermc.paper.util.CoordinateUtils.getChunkSectionY(pos); ++ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(pos); ++ ++ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main"); ++ ++ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager manager = this.world.chunkTaskScheduler.chunkHolderManager; ++ io.papermc.paper.chunk.system.poi.PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true); ++ ++ return ret == null ? Optional.empty() : ret.getSectionForVanilla(chunkY); ++ } ++ ++ @Override ++ public Optional getOrLoad(long pos) { ++ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(pos); ++ int chunkY = io.papermc.paper.util.CoordinateUtils.getChunkSectionY(pos); ++ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(pos); ++ ++ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main"); ++ ++ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager manager = this.world.chunkTaskScheduler.chunkHolderManager; ++ ++ if (chunkY >= io.papermc.paper.util.WorldUtil.getMinSection(this.world) && ++ chunkY <= io.papermc.paper.util.WorldUtil.getMaxSection(this.world)) { ++ io.papermc.paper.chunk.system.poi.PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true); ++ if (ret != null) { ++ return ret.getSectionForVanilla(chunkY); ++ } else { ++ return manager.loadPoiChunk(chunkX, chunkZ).getSectionForVanilla(chunkY); ++ } ++ } ++ // retain vanilla behavior: do not load section if out of bounds! ++ return Optional.empty(); ++ } ++ ++ @Override ++ protected PoiSection getOrCreate(long pos) { ++ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(pos); ++ int chunkY = io.papermc.paper.util.CoordinateUtils.getChunkSectionY(pos); ++ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(pos); ++ ++ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main"); ++ ++ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager manager = this.world.chunkTaskScheduler.chunkHolderManager; ++ ++ io.papermc.paper.chunk.system.poi.PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true); ++ if (ret != null) { ++ return ret.getOrCreateSection(chunkY); ++ } else { ++ return manager.loadPoiChunk(chunkX, chunkZ).getOrCreateSection(chunkY); ++ } ++ } ++ ++ public void onUnload(long coordinate) { // Paper - rewrite chunk system ++ int chunkX = io.papermc.paper.util.MCUtil.getCoordinateX(coordinate); ++ int chunkZ = io.papermc.paper.util.MCUtil.getCoordinateZ(coordinate); ++ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Unloading poi chunk off-main"); ++ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) { ++ long sectionPos = SectionPos.asLong(chunkX, section, chunkZ); ++ this.updateDistanceTracking(sectionPos); ++ } ++ } ++ ++ public void loadInPoiChunk(io.papermc.paper.chunk.system.poi.PoiChunk poiChunk) { ++ int chunkX = poiChunk.chunkX; ++ int chunkZ = poiChunk.chunkZ; ++ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Loading poi chunk off-main"); ++ for (int sectionY = this.levelHeightAccessor.getMinSection(); sectionY < this.levelHeightAccessor.getMaxSection(); ++sectionY) { ++ PoiSection section = poiChunk.getSection(sectionY); ++ if (section != null && !section.isEmpty()) { ++ this.onSectionLoad(SectionPos.asLong(chunkX, sectionY, chunkZ)); ++ } ++ } + } + ++ public void checkConsistency(net.minecraft.world.level.chunk.ChunkAccess chunk) { ++ int chunkX = chunk.getPos().x; ++ int chunkZ = chunk.getPos().z; ++ int minY = io.papermc.paper.util.WorldUtil.getMinSection(chunk); ++ int maxY = io.papermc.paper.util.WorldUtil.getMaxSection(chunk); ++ LevelChunkSection[] sections = chunk.getSections(); ++ for (int section = minY; section <= maxY; ++section) { ++ this.checkConsistencyWithBlocks(SectionPos.of(chunkX, section, chunkZ), sections[section - minY]); ++ } ++ } ++ // Paper end - rewrite chunk system ++ + public void checkConsistencyWithBlocks(SectionPos sectionPos, LevelChunkSection chunkSection) { + Util.ifElse(this.getOrLoad(sectionPos.asLong()), (poiSet) -> { + poiSet.refresh((populator) -> { +@@ -248,7 +361,7 @@ public class PoiManager extends SectionStorage { + }).map((pair) -> { + return pair.getFirst().chunk(); + }).filter((chunkPos) -> { +- return this.loadedChunks.add(chunkPos.toLong()); ++ return true; // Paper - rewrite chunk system + }).forEach((chunkPos) -> { + world.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.EMPTY); + }); +@@ -264,7 +377,7 @@ public class PoiManager extends SectionStorage { + + @Override + protected int getLevelFromSource(long id) { +- return PoiManager.this.isVillageCenter(id) ? 0 : 7; ++ return PoiManager.this.isVillageCenter(id) ? 0 : 7; // Paper - rewrite chunk system - diff on change, this specifies the source level to use for distance tracking + } + + @Override +@@ -287,6 +400,35 @@ public class PoiManager extends SectionStorage { + } + } + ++ // Paper start - Asynchronous chunk io ++ @javax.annotation.Nullable ++ @Override ++ public net.minecraft.nbt.CompoundTag read(ChunkPos chunkcoordintpair) throws java.io.IOException { ++ // Paper start - rewrite chunk system ++ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { ++ return io.papermc.paper.chunk.system.io.RegionFileIOThread.loadData( ++ this.world, chunkcoordintpair.x, chunkcoordintpair.z, io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.POI_DATA, ++ io.papermc.paper.chunk.system.io.RegionFileIOThread.getIOBlockingPriorityForCurrentThread() ++ ); ++ } ++ // Paper end - rewrite chunk system ++ return super.read(chunkcoordintpair); ++ } ++ ++ @Override ++ public void write(ChunkPos chunkcoordintpair, net.minecraft.nbt.CompoundTag nbttagcompound) throws java.io.IOException { ++ // Paper start - rewrite chunk system ++ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { ++ io.papermc.paper.chunk.system.io.RegionFileIOThread.scheduleSave( ++ this.world, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, ++ io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.POI_DATA); ++ return; ++ } ++ // Paper end - rewrite chunk system ++ super.write(chunkcoordintpair, nbttagcompound); ++ } ++ // Paper end ++ + public static enum Occupancy { + HAS_SPACE(PoiRecord::hasSpace), + IS_OCCUPIED(PoiRecord::isOccupied), +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java +index 795a02941d7cecb58ec45b5e79c8d510ff21163a..3fc17817906876e83f040f908b8b1ba6cfa37b8b 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java +@@ -29,6 +29,7 @@ public class PoiSection { + private final Map, Set> byType = Maps.newHashMap(); + private final Runnable setDirty; + private boolean isValid; ++ public final Optional noAllocateOptional = Optional.of(this); // Paper - rewrite chunk system + + public static Codec codec(Runnable updateListener) { + return RecordCodecBuilder.create((instance) -> { +@@ -46,6 +47,12 @@ public class PoiSection { + this(updateListener, true, ImmutableList.of()); + } + ++ // Paper start - isEmpty ++ public boolean isEmpty() { ++ return this.isValid && this.records.isEmpty() && this.byType.isEmpty(); ++ } ++ // Paper end ++ + private PoiSection(Runnable updateListener, boolean valid, List pois) { + this.setDirty = updateListener; + this.isValid = valid; +diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java +index 57d4d2014f33a2f069d6c5aaa8e87e36b63a7177..cc888bbcd6a50124fa553bc4a8ffd1e8885d3856 100644 +--- a/src/main/java/net/minecraft/world/level/EntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +@@ -18,6 +18,18 @@ import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; + + public interface EntityGetter { ++ ++ // Paper start ++ List getHardCollidingEntities(Entity except, AABB box, Predicate predicate); ++ ++ void getEntities(Entity except, AABB box, Predicate predicate, List into); ++ ++ void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into); ++ ++ void getEntitiesByClass(Class clazz, Entity except, final AABB box, List into, ++ Predicate predicate); ++ // Paper end ++ + List getEntities(@Nullable Entity except, AABB box, Predicate predicate); + + List getEntities(EntityTypeTest filter, AABB box, Predicate predicate); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index d5290c97babfa9415bd52deb14610821f0fa2575..c9afe29bae7f339184f4f46d8f9828f5762d0a9c 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -545,6 +545,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement + this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i); ++ // Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance ++ // if copied from above ++ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0)) { // Paper - replace old player chunk management ++ ((ServerLevel)this).getChunkSource().blockChanged(blockposition); ++ // Paper end - per player view distance + } + + if ((i & 1) != 0) { +@@ -936,7 +941,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + // Paper end - Perf: Optimize capturedTileEntities lookup + // CraftBukkit end +- return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); ++ return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && !io.papermc.paper.util.TickThread.isTickThread() ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); // Paper - rewrite chunk system + } + + public void setBlockEntity(BlockEntity blockEntity) { +@@ -1027,26 +1032,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public List getEntities(@Nullable Entity except, AABB box, Predicate predicate) { + this.getProfiler().incrementCounter("getEntities"); + List list = Lists.newArrayList(); +- +- this.getEntities().get(box, (entity1) -> { +- if (entity1 != except && predicate.test(entity1)) { +- list.add(entity1); +- } +- +- if (entity1 instanceof EnderDragon) { +- EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity1).getSubEntities(); +- int i = aentitycomplexpart.length; +- +- for (int j = 0; j < i; ++j) { +- EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; +- +- if (entity1 != except && predicate.test(entitycomplexpart)) { +- list.add(entitycomplexpart); +- } +- } +- } +- +- }); ++ ((ServerLevel)this).getEntityLookup().getEntities(except, box, list, predicate); // Paper - optimise this call + return list; + } + +@@ -1064,34 +1050,23 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public void getEntities(EntityTypeTest filter, AABB box, Predicate predicate, List result, int limit) { + this.getProfiler().incrementCounter("getEntities"); +- this.getEntities().get(filter, box, (entity) -> { +- if (predicate.test(entity)) { +- result.add(entity); +- if (result.size() >= limit) { +- return AbortableIterationConsumer.Continuation.ABORT; +- } +- } +- +- if (entity instanceof EnderDragon) { +- EnderDragon entityenderdragon = (EnderDragon) entity; +- EnderDragonPart[] aentitycomplexpart = entityenderdragon.getSubEntities(); +- int j = aentitycomplexpart.length; +- +- for (int k = 0; k < j; ++k) { +- EnderDragonPart entitycomplexpart = aentitycomplexpart[k]; +- T t0 = filter.tryCast(entitycomplexpart); // CraftBukkit - decompile error +- +- if (t0 != null && predicate.test(t0)) { +- result.add(t0); +- if (result.size() >= limit) { +- return AbortableIterationConsumer.Continuation.ABORT; +- } +- } +- } ++ // Paper start - optimise this call ++ //TODO use limit ++ if (filter instanceof net.minecraft.world.entity.EntityType entityTypeTest) { ++ ((ServerLevel) this).getEntityLookup().getEntities(entityTypeTest, box, result, predicate); ++ } else { ++ Predicate test = (obj) -> { ++ return filter.tryCast(obj) != null; ++ }; ++ predicate = predicate == null ? test : test.and((Predicate) predicate); ++ Class base; ++ if (filter == null || (base = filter.getBaseClass()) == null || base == Entity.class) { ++ ((ServerLevel) this).getEntityLookup().getEntities((Entity) null, box, (List) result, (Predicate)predicate); ++ } else { ++ ((ServerLevel) this).getEntityLookup().getEntities(base, null, box, (List) result, (Predicate)predicate); // Paper - optimise this call + } +- +- return AbortableIterationConsumer.Continuation.CONTINUE; +- }); ++ } ++ // Paper end - optimise this call + } + + @Nullable +@@ -1383,4 +1358,45 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + } + // Paper end - notify observers even if grow failed ++ // Paper start ++ //protected final io.papermc.paper.world.EntitySliceManager entitySliceManager; // Paper - rewrite chunk system ++ ++ 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) { ++ return new org.bukkit.entity.Entity[0]; ++ } ++ return slices.getChunkEntities(); ++ } ++ ++ @Override ++ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { ++ List ret = new java.util.ArrayList<>(); ++ ((ServerLevel)this).getEntityLookup().getHardCollidingEntities(except, box, ret, predicate); ++ return ret; ++ } ++ ++ @Override ++ public void getEntities(Entity except, AABB box, Predicate predicate, List into) { ++ ((ServerLevel)this).getEntityLookup().getEntities(except, box, into, predicate); ++ } ++ ++ @Override ++ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) { ++ ((ServerLevel)this).getEntityLookup().getHardCollidingEntities(except, box, into, predicate); ++ } ++ ++ @Override ++ public void getEntitiesByClass(Class clazz, Entity except, final AABB box, List into, ++ Predicate predicate) { ++ ((ServerLevel)this).getEntityLookup().getEntities((Class)clazz, except, box, (List)into, (Predicate)predicate); ++ } ++ ++ @Override ++ public List getEntitiesOfClass(Class entityClass, AABB box, Predicate predicate) { ++ List ret = new java.util.ArrayList<>(); ++ ((ServerLevel)this).getEntityLookup().getEntities(entityClass, null, box, ret, predicate); ++ return ret; ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/LevelReader.java b/src/main/java/net/minecraft/world/level/LevelReader.java +index cc0d20e9f851268fe8403ac516f426ec1d008150..12eaafdbd324fa36b3f46c3b644bc8117a4123ad 100644 +--- a/src/main/java/net/minecraft/world/level/LevelReader.java ++++ b/src/main/java/net/minecraft/world/level/LevelReader.java +@@ -26,6 +26,15 @@ public interface LevelReader extends BlockAndTintGetter, CollisionGetter, Signal + @Nullable + ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create); + ++ // Paper start - rewrite chunk system ++ default ChunkAccess syncLoadNonFull(int chunkX, int chunkZ, ChunkStatus status) { ++ if (status == null || status.isOrAfter(ChunkStatus.FULL)) { ++ throw new IllegalArgumentException("Status: " + status.toString()); ++ } ++ return this.getChunk(chunkX, chunkZ, status, true); ++ } ++ // Paper end - rewrite chunk system ++ + @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z); // Paper - ifLoaded api (we need this since current impl blocks if the chunk is loading) + @Nullable default ChunkAccess getChunkIfLoadedImmediately(BlockPos pos) { return this.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4);} + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +index b26a4eb4951e87f891b59028d98b8ffba8e103a8..b8b78494449c0cd638f9706a803dc54e184d981f 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -114,7 +114,7 @@ public abstract class ChunkGenerator { + return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName("init_biomes", () -> { + chunk.fillBiomesFromNoise(this.biomeSource, noiseConfig.sampler()); + return chunk; +- }), Util.backgroundExecutor()); ++ }), executor); // Paper - run with supplied executor + } + + public abstract void applyCarvers(WorldGenRegion chunkRegion, long seed, RandomState noiseConfig, BiomeManager biomeAccess, StructureManager structureAccessor, ChunkAccess chunk, GenerationStep.Carving carverStep); +@@ -309,7 +309,7 @@ public abstract class ChunkGenerator { + return Pair.of(placement.getLocatePos(pos), holder); + } + +- ChunkAccess ichunkaccess = world.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_STARTS); ++ ChunkAccess ichunkaccess = world.syncLoadNonFull(pos.x, pos.z, ChunkStatus.STRUCTURE_STARTS); // Paper - rewrite chunk system + + structurestart = structureAccessor.getStartForStructure(SectionPos.bottomOf(ichunkaccess), (Structure) holder.value(), ichunkaccess); + } while (structurestart == null); +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +index 846ae3fd184a1d63b743aa25e045604576697c96..a907b79fd8291a0e92db138f37239d17424188a1 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java +@@ -30,6 +30,30 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp + + public class ChunkStatus { + ++ // Paper start - rewrite chunk system ++ public boolean isParallelCapable; // Paper ++ public int writeRadius = -1; ++ public int loadRange = 0; ++ ++ protected static final java.util.List statuses = new java.util.ArrayList<>(); ++ ++ private ChunkStatus nextStatus; ++ ++ public final ChunkStatus getNextStatus() { ++ return this.nextStatus; ++ } ++ ++ public final boolean isEmptyLoadStatus() { ++ return this.loadingTask == PASSTHROUGH_LOAD_TASK; ++ } ++ ++ public final boolean isEmptyGenStatus() { ++ return this == ChunkStatus.EMPTY; ++ } ++ ++ public final java.util.concurrent.atomic.AtomicBoolean warnedAboutNoImmediateComplete = new java.util.concurrent.atomic.AtomicBoolean(); ++ // Paper end - rewrite chunk system ++ + public static final int MAX_STRUCTURE_DISTANCE = 8; + private static final EnumSet PRE_FEATURES = EnumSet.of(Heightmap.Types.OCEAN_FLOOR_WG, Heightmap.Types.WORLD_SURFACE_WG); + public static final EnumSet POST_FEATURES = EnumSet.of(Heightmap.Types.OCEAN_FLOOR, Heightmap.Types.WORLD_SURFACE, Heightmap.Types.MOTION_BLOCKING, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES); +@@ -151,13 +175,13 @@ public class ChunkStatus { + ((ProtoChunk) chunk).setLightEngine(lightingProvider); + boolean flag = ChunkStatus.isLighted(chunk); + +- return lightingProvider.initializeLight(chunk, flag).thenApply(Either::left); ++ return CompletableFuture.completedFuture(Either.left(chunk)); // Paper - rewrite chunk system + } + + private static CompletableFuture> lightChunk(ThreadedLevelLightEngine lightingProvider, ChunkAccess chunk) { + boolean flag = ChunkStatus.isLighted(chunk); + +- return lightingProvider.lightChunk(chunk, flag).thenApply(Either::left); ++ return CompletableFuture.completedFuture(Either.left(chunk)); // Paper - rewrite chunk system + } + + private static ChunkStatus registerSimple(String id, @Nullable ChunkStatus previous, int taskMargin, EnumSet heightMapTypes, ChunkStatus.ChunkType chunkType, ChunkStatus.SimpleGenerationTask task) { +@@ -211,6 +235,13 @@ public class ChunkStatus { + this.chunkType = chunkType; + this.heightmapsAfter = heightMapTypes; + this.index = previous == null ? 0 : previous.getIndex() + 1; ++ // Paper start ++ this.nextStatus = this; ++ if (statuses.size() > 0) { ++ statuses.get(statuses.size() - 1).nextStatus = this; ++ } ++ statuses.add(this); ++ // Paper end + } + + public int getIndex() { +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 73e682bb3ef3b2e450ec8c594b5365c7a340615e..6a5756bd333d9b221e7770842e5114d295cb7f1d 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -84,6 +84,7 @@ public class LevelChunk extends ChunkAccess { + private final Int2ObjectMap gameEventListenerRegistrySections; + private final LevelChunkTicks blockTicks; + private final LevelChunkTicks fluidTicks; ++ public volatile FullChunkStatus chunkStatus = FullChunkStatus.INACCESSIBLE; // Paper - rewrite chunk system + + public LevelChunk(Level world, ChunkPos pos) { + this(world, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, (LevelChunkSection[]) null, (LevelChunk.PostLoadProcessor) null, (BlendingData) null); +@@ -690,9 +691,26 @@ public class LevelChunk extends ChunkAccess { + + } + +- // CraftBukkit start +- public void loadCallback() { +- // Paper start - neighbour cache ++ // Paper start - new load callbacks ++ private io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder; ++ public io.papermc.paper.chunk.system.scheduling.NewChunkHolder getChunkHolder() { ++ return this.chunkHolder; ++ } ++ ++ public void setChunkHolder(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { ++ if (chunkHolder == null) { ++ throw new NullPointerException("Chunkholder cannot be null"); ++ } ++ if (this.chunkHolder != null) { ++ throw new IllegalStateException("Already have chunkholder: " + this.chunkHolder + ", cannot replace with " + chunkHolder); ++ } ++ this.chunkHolder = chunkHolder; ++ this.playerChunk = chunkHolder.vanillaChunkHolder; ++ } ++ ++ /* Note: We skip the light neighbour chunk loading done for the vanilla full chunk */ ++ /* Starlight does not need these chunks for lighting purposes because of edge checks */ ++ public void pushChunkIntoLoadedMap() { + int chunkX = this.chunkPos.x; + int chunkZ = this.chunkPos.z; + net.minecraft.server.level.ServerChunkCache chunkProvider = this.level.getChunkSource(); +@@ -707,10 +725,55 @@ public class LevelChunk extends ChunkAccess { + } + } + this.setNeighbourLoaded(0, 0, this); ++ this.level.getChunkSource().addLoadedChunk(this); ++ } ++ ++ public void onChunkLoad(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { ++ // figure out how this should interface with: ++ // the entity chunk load event // -> moved to the FULL status ++ // the chunk load event // -> stays here ++ // any entity add to world events // -> in FULL status ++ this.loadCallback(); ++ io.papermc.paper.chunk.system.ChunkSystem.onChunkBorder(this, chunkHolder.vanillaChunkHolder); ++ } ++ ++ public void onChunkUnload(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { ++ // figure out how this should interface with: ++ // the entity chunk load event // -> moved to chunk unload to disk (not written yet) ++ // the chunk load event // -> stays here ++ // any entity add to world events // -> goes into the unload logic, it will completely explode ++ // etc later ++ this.unloadCallback(); ++ io.papermc.paper.chunk.system.ChunkSystem.onChunkNotBorder(this, chunkHolder.vanillaChunkHolder); ++ } ++ ++ public void onChunkTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { ++ this.postProcessGeneration(); ++ this.level.startTickingChunk(this); ++ io.papermc.paper.chunk.system.ChunkSystem.onChunkTicking(this, chunkHolder.vanillaChunkHolder); ++ } ++ ++ public void onChunkNotTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { ++ io.papermc.paper.chunk.system.ChunkSystem.onChunkNotTicking(this, chunkHolder.vanillaChunkHolder); ++ } ++ ++ public void onChunkEntityTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { ++ io.papermc.paper.chunk.system.ChunkSystem.onChunkEntityTicking(this, chunkHolder.vanillaChunkHolder); ++ } ++ ++ public void onChunkNotEntityTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { ++ io.papermc.paper.chunk.system.ChunkSystem.onChunkNotEntityTicking(this, chunkHolder.vanillaChunkHolder); ++ } ++ // Paper end - new load callbacks ++ ++ // CraftBukkit start ++ public void loadCallback() { ++ if (this.loadedTicketLevel) { LOGGER.error("Double calling chunk load!", new Throwable()); } // Paper ++ // Paper - rewrite chunk system - move into separate callback + this.loadedTicketLevel = true; +- // Paper end - neighbour cache ++ // Paper - rewrite chunk system - move into separate callback + org.bukkit.Server server = this.level.getCraftServer(); +- this.level.getChunkSource().addLoadedChunk(this); // Paper ++ // Paper - rewrite chunk system - move into separate callback + if (server != null) { + /* + * If it's a new world, the first few chunks are generated inside +@@ -719,6 +782,7 @@ public class LevelChunk extends ChunkAccess { + */ + org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); + server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration)); ++ this.chunkHolder.getEntityChunk().callEntitiesLoadEvent(); // Paper - rewrite chunk system + + if (this.needsDecoration) { + try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper +@@ -747,9 +811,11 @@ public class LevelChunk extends ChunkAccess { + } + + public void unloadCallback() { ++ if (!this.loadedTicketLevel) { LOGGER.error("Double calling chunk unload!", new Throwable()); } // Paper + org.bukkit.Server server = this.level.getCraftServer(); ++ this.chunkHolder.getEntityChunk().callEntitiesUnloadEvent(); // Paper - rewrite chunk system + org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); +- org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, this.isUnsaved()); ++ org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, true); // Paper - rewrite chunk system - force save to true so that mustNotSave is correctly set below + server.getPluginManager().callEvent(unloadEvent); + // note: saving can be prevented, but not forced if no saving is actually required + this.mustNotSave = !unloadEvent.isSaveChunk(); +@@ -771,9 +837,26 @@ public class LevelChunk extends ChunkAccess { + // Paper end + } + ++ // Paper start - add dirty system to tick lists ++ @Override ++ public void setUnsaved(boolean needsSaving) { ++ if (!needsSaving) { ++ this.blockTicks.clearDirty(); ++ this.fluidTicks.clearDirty(); ++ } ++ super.setUnsaved(needsSaving); ++ } ++ // Paper end - add dirty system to tick lists ++ + @Override + public boolean isUnsaved() { +- return super.isUnsaved() && !this.mustNotSave; ++ // Paper start - add dirty system to tick lists ++ long gameTime = this.level.getLevelData().getGameTime(); ++ if (this.blockTicks.isDirty(gameTime) || this.fluidTicks.isDirty(gameTime)) { ++ return true; ++ } ++ // Paper end - add dirty system to tick lists ++ return super.isUnsaved(); // Paper - rewrite chunk system - do NOT clobber the dirty flag + } + // CraftBukkit end + +@@ -842,7 +925,9 @@ public class LevelChunk extends ChunkAccess { + return this.blockEntities; + } + ++ public boolean isPostProcessingDone; // Paper - replace chunk loader system + public void postProcessGeneration() { ++ try { // Paper - replace chunk loader system + ChunkPos chunkcoordintpair = this.getPos(); + + for (int i = 0; i < this.postProcessing.length; ++i) { +@@ -863,6 +948,7 @@ public class LevelChunk extends ChunkAccess { + BlockState iblockdata1 = Block.updateFromNeighbourShapes(iblockdata, this.level, blockposition); + + this.level.setBlock(blockposition, iblockdata1, 20); ++ if (iblockdata1 != iblockdata) this.level.chunkSource.blockChanged(blockposition); // Paper - replace player chunk loader - notify since we send before processing full updates + } + } + +@@ -880,6 +966,10 @@ public class LevelChunk extends ChunkAccess { + + this.pendingBlockEntities.clear(); + this.upgradeData.upgrade(this); ++ } finally { // Paper start - replace chunk loader system ++ this.isPostProcessingDone = true; ++ } ++ // Paper end - replace chunk loader system + } + + @Nullable +@@ -929,7 +1019,7 @@ public class LevelChunk extends ChunkAccess { + } + + public FullChunkStatus getFullStatus() { +- return this.fullStatus == null ? FullChunkStatus.FULL : (FullChunkStatus) this.fullStatus.get(); ++ return this.chunkHolder == null ? FullChunkStatus.INACCESSIBLE : this.chunkHolder.getChunkStatus(); // Paper - rewrite chunk system + } + + public void setFullStatus(Supplier levelTypeProvider) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index c6115477cc94bf47a5f459418a232412b7c358ba..e67ebc8517a1afb0c7fe23f19a781942dded3241 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -110,6 +110,17 @@ public class ChunkSerializer { + } + } + // Paper end - guard against serializing mismatching coordinates ++ // Paper start - rewrite chunk system ++ public static final class InProgressChunkHolder { ++ ++ public final ProtoChunk protoChunk; ++ ++ public CompoundTag poiData; ++ ++ public InProgressChunkHolder(final ProtoChunk protoChunk) { ++ this.protoChunk = protoChunk; ++ } ++ } + public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) { + // Paper start - Do not let the server load chunks from newer versions + if (nbt.contains("DataVersion", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { +@@ -120,6 +131,12 @@ public class ChunkSerializer { + } + } + // Paper end - Do not let the server load chunks from newer versions ++ InProgressChunkHolder holder = readInProgressChunkHolder(world, poiStorage, chunkPos, nbt); ++ return holder.protoChunk; ++ } ++ ++ public static InProgressChunkHolder readInProgressChunkHolder(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) { ++ // Paper end - rewrite chunk system + ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate + + if (!Objects.equals(chunkPos, chunkcoordintpair1)) { +@@ -185,7 +202,7 @@ public class ChunkSerializer { + achunksection[k] = chunksection; + SectionPos sectionposition = SectionPos.of(chunkPos, b0); + +- poiStorage.checkConsistencyWithBlocks(sectionposition, chunksection); ++ // Paper - rewrite chunk system - moved to final load stage + } + + boolean flag3 = nbttagcompound1.contains("BlockLight", 7); +@@ -331,7 +348,7 @@ public class ChunkSerializer { + } + + if (chunkstatus_type == ChunkStatus.ChunkType.LEVELCHUNK) { +- return new ImposterProtoChunk((LevelChunk) object1, false); ++ return new InProgressChunkHolder(new ImposterProtoChunk((LevelChunk) object1, false)); // Paper - Async chunk loading + } else { + ProtoChunk protochunk1 = (ProtoChunk) object1; + +@@ -366,9 +383,41 @@ public class ChunkSerializer { + protochunk1.setCarvingMask(worldgenstage_features, new CarvingMask(nbttagcompound5.getLongArray(s1), ((ChunkAccess) object1).getMinBuildHeight())); + } + +- return protochunk1; ++ return new InProgressChunkHolder(protochunk1); // Paper - Async chunk loading ++ } ++ } ++ ++ // Paper start - async chunk save for unload ++ public record AsyncSaveData( ++ Tag blockTickList, // non-null if we had to go to the server's tick list ++ Tag fluidTickList, // non-null if we had to go to the server's tick list ++ ListTag blockEntities, ++ long worldTime ++ ) {} ++ ++ // must be called sync ++ public static AsyncSaveData getAsyncSaveData(ServerLevel world, ChunkAccess chunk) { ++ org.spigotmc.AsyncCatcher.catchOp("preparation of chunk data for async save"); ++ ++ final CompoundTag tickLists = new CompoundTag(); ++ ChunkSerializer.saveTicks(world, tickLists, chunk.getTicksForSerialization()); ++ ++ ListTag blockEntitiesSerialized = new ListTag(); ++ for (final BlockPos blockPos : chunk.getBlockEntitiesPos()) { ++ final CompoundTag blockEntityNbt = chunk.getBlockEntityNbtForSaving(blockPos); ++ if (blockEntityNbt != null) { ++ blockEntitiesSerialized.add(blockEntityNbt); ++ } + } ++ ++ return new AsyncSaveData( ++ tickLists.get(BLOCK_TICKS_TAG), ++ tickLists.get(FLUID_TICKS_TAG), ++ blockEntitiesSerialized, ++ world.getGameTime() ++ ); + } ++ // Paper end + + private static void logErrors(ChunkPos chunkPos, int y, String message) { + ChunkSerializer.LOGGER.error("Recoverable errors when loading section [" + chunkPos.x + ", " + y + ", " + chunkPos.z + "]: " + message); +@@ -385,6 +434,11 @@ public class ChunkSerializer { + // CraftBukkit end + + public static CompoundTag write(ServerLevel world, ChunkAccess chunk) { ++ // Paper start ++ return saveChunk(world, chunk, null); ++ } ++ public static CompoundTag saveChunk(ServerLevel world, ChunkAccess chunk, @org.checkerframework.checker.nullness.qual.Nullable AsyncSaveData asyncsavedata) { ++ // Paper end + // Paper start - rewrite light impl + final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world); + final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world); +@@ -397,7 +451,7 @@ public class ChunkSerializer { + nbttagcompound.putInt("xPos", chunkcoordintpair.x); + nbttagcompound.putInt("yPos", chunk.getMinSection()); + nbttagcompound.putInt("zPos", chunkcoordintpair.z); +- nbttagcompound.putLong("LastUpdate", world.getGameTime()); ++ nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading + nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime()); + nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString()); + BlendingData blendingdata = chunk.getBlendingData(); +@@ -497,8 +551,17 @@ public class ChunkSerializer { + nbttagcompound.putBoolean("isLightOn", false); // Paper - set to false but still store, this allows us to detect --eraseCache (as eraseCache _removes_) + } + +- ListTag nbttaglist1 = new ListTag(); +- Iterator iterator = chunk.getBlockEntitiesPos().iterator(); ++ // Paper start ++ ListTag nbttaglist1; ++ Iterator iterator; ++ if (asyncsavedata != null) { ++ nbttaglist1 = asyncsavedata.blockEntities; ++ iterator = java.util.Collections.emptyIterator(); ++ } else { ++ nbttaglist1 = new ListTag(); ++ iterator = chunk.getBlockEntitiesPos().iterator(); ++ } ++ // Paper end + + CompoundTag nbttagcompound2; + +@@ -534,7 +597,14 @@ public class ChunkSerializer { + nbttagcompound.put("CarvingMasks", nbttagcompound2); + } + ++ // Paper start ++ if (asyncsavedata != null) { ++ nbttagcompound.put(BLOCK_TICKS_TAG, asyncsavedata.blockTickList); ++ nbttagcompound.put(FLUID_TICKS_TAG, asyncsavedata.fluidTickList); ++ } else { + ChunkSerializer.saveTicks(world, nbttagcompound, chunk.getTicksForSerialization()); ++ } ++ // Paper end + nbttagcompound.put("PostProcessing", ChunkSerializer.packOffsets(chunk.getPostProcessing())); + CompoundTag nbttagcompound3 = new CompoundTag(); + Iterator iterator1 = chunk.getHeightmaps().iterator(); +@@ -590,7 +660,7 @@ public class ChunkSerializer { + + return nbttaglist == null && nbttaglist1 == null ? null : (chunk) -> { + if (nbttaglist != null) { +- world.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(nbttaglist, world)); ++ world.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(nbttaglist, world), chunk.getPos()); // Paper - rewrite chunk system + } + + if (nbttaglist1 != null) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index eebaf98bc0fa4696af59b2a79563beb73501a554..645a1773237f2002c233ec1f3ff6f0ca46ca4024 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -28,18 +28,21 @@ import net.minecraft.world.level.storage.DimensionDataStorage; + public class ChunkStorage implements AutoCloseable { + + public static final int LAST_MONOLYTH_STRUCTURE_DATA_VERSION = 1493; +- private final IOWorker worker; ++ // Paper start - rewrite chunk system; async chunk IO ++ private final Object persistentDataLock = new Object(); ++ public final RegionFileStorage regionFileCache; ++ // Paper end - rewrite chunk system + protected final DataFixer fixerUpper; + @Nullable + private volatile LegacyStructureDataHandler legacyStructureHandler; + + public ChunkStorage(Path directory, DataFixer dataFixer, boolean dsync) { + this.fixerUpper = dataFixer; +- this.worker = new IOWorker(directory, dsync, "chunk"); ++ this.regionFileCache = new RegionFileStorage(directory, dsync); // Paper - rewrite chunk system; async chunk IO + } + + public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) { +- return this.worker.isOldChunkAround(chunkPos, checkRadius); ++ return true; // Paper - rewrite chunk system; (for now, unoptimised behavior) TODO implement later? the chunk status that blender uses SHOULD already have this radius loaded, no need to go back later for it... + } + + // CraftBukkit start +@@ -47,8 +50,9 @@ public class ChunkStorage implements AutoCloseable { + if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full" + ChunkPos pos = new ChunkPos(x, z); + if (cps != null) { +- com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); +- if (cps.hasChunk(x, z)) { ++ // Paper start - rewrite chunk system; async chunk IO ++ if (cps.getChunkAtIfCachedImmediately(x, z) != null) { // isLoaded is a ticket level check, not a chunk loaded check! ++ // Paper end - rewrite chunk system + return true; + } + } +@@ -76,6 +80,7 @@ public class ChunkStorage implements AutoCloseable { + + public CompoundTag upgradeChunkTag(ResourceKey resourcekey, Supplier supplier, CompoundTag nbttagcompound, Optional>> optional, ChunkPos pos, @Nullable LevelAccessor generatoraccess) { + // CraftBukkit end ++ nbttagcompound = nbttagcompound.copy(); // Paper - defensive copy, another thread might modify this + int i = ChunkStorage.getVersion(nbttagcompound); + + // CraftBukkit start +@@ -93,9 +98,11 @@ public class ChunkStorage implements AutoCloseable { + if (i < 1493) { + ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, i, 1493); // Paper - replace chunk converter + if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) { ++ synchronized (this.persistentDataLock) { // Paper - Async chunk loading + LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier); + + nbttagcompound = persistentstructurelegacy.updateFromLegacy(nbttagcompound); ++ } // Paper - Async chunk loading + } + } + +@@ -128,7 +135,7 @@ public class ChunkStorage implements AutoCloseable { + LegacyStructureDataHandler persistentstructurelegacy = this.legacyStructureHandler; + + if (persistentstructurelegacy == null) { +- synchronized (this) { ++ synchronized (this.persistentDataLock) { // Paper - async chunk loading + persistentstructurelegacy = this.legacyStructureHandler; + if (persistentstructurelegacy == null) { + this.legacyStructureHandler = persistentstructurelegacy = LegacyStructureDataHandler.getLegacyStructureHandler(worldKey, (DimensionDataStorage) stateManagerGetter.get()); +@@ -154,10 +161,20 @@ public class ChunkStorage implements AutoCloseable { + } + + public CompletableFuture> read(ChunkPos chunkPos) { +- return this.worker.loadAsync(chunkPos); ++ // Paper start - async chunk io ++ try { ++ return CompletableFuture.completedFuture(Optional.ofNullable(this.readSync(chunkPos))); ++ } catch (Throwable thr) { ++ return CompletableFuture.failedFuture(thr); ++ } ++ } ++ @Nullable ++ public CompoundTag readSync(ChunkPos chunkPos) throws IOException { ++ return this.regionFileCache.read(chunkPos); + } ++ // Paper end - async chunk io + +- public void write(ChunkPos chunkPos, CompoundTag nbt) { ++ public void write(ChunkPos chunkPos, CompoundTag nbt) throws IOException { // Paper - rewrite chunk system; async chunk io + // Paper start - guard against serializing mismatching coordinates + if (nbt != null && !chunkPos.equals(ChunkSerializer.getChunkCoordinate(nbt))) { + final String world = (this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap) this).level.getWorld().getName() : null; +@@ -165,22 +182,33 @@ public class ChunkStorage implements AutoCloseable { + + " but compound says coordinate is " + ChunkSerializer.getChunkCoordinate(nbt) + (world == null ? " for an unknown world" : (" for world: " + world))); + } + // Paper end - guard against serializing mismatching coordinates +- this.worker.store(chunkPos, nbt); ++ this.regionFileCache.write(chunkPos, nbt); // Paper - rewrite chunk system; async chunk io + if (this.legacyStructureHandler != null) { ++ synchronized (this.persistentDataLock) { // Paper - rewrite chunk system; async chunk io + this.legacyStructureHandler.removeIndex(chunkPos.toLong()); ++ } // Paper - rewrite chunk system; async chunk io + } + + } + + public void flushWorker() { +- this.worker.synchronize(true).join(); ++ io.papermc.paper.chunk.system.io.RegionFileIOThread.flush(); // Paper - rewrite chunk system + } + + public void close() throws IOException { +- this.worker.close(); ++ this.regionFileCache.close(); // Paper - nuke IO worker + } + + public ChunkScanAccess chunkScanner() { +- return this.worker; ++ // Paper start - nuke IO worker ++ return ((chunkPos, streamTagVisitor) -> { ++ try { ++ this.regionFileCache.scanChunk(chunkPos, streamTagVisitor); ++ return java.util.concurrent.CompletableFuture.completedFuture(null); ++ } catch (IOException e) { ++ throw new RuntimeException(e); ++ } ++ }); ++ // Paper end + } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java +index 98b3909b536f11eda9c481ffd74066ad0cdb0ebc..0ec0be22f7292d57c40da6f1f4575bdebf8dbd09 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java +@@ -30,43 +30,31 @@ public class EntityStorage implements EntityPersistentStorage { + private static final String ENTITIES_TAG = "Entities"; + private static final String POSITION_TAG = "Position"; + public final ServerLevel level; +- private final IOWorker worker; ++ // Paper - rewrite chunk system + private final LongSet emptyChunks = new LongOpenHashSet(); +- public final ProcessorMailbox entityDeserializerQueue; ++ // Paper - rewrite chunk system + protected final DataFixer fixerUpper; + + public EntityStorage(ServerLevel world, Path path, DataFixer dataFixer, boolean dsync, Executor executor) { + this.level = world; + this.fixerUpper = dataFixer; +- this.entityDeserializerQueue = ProcessorMailbox.create(executor, "entity-deserializer"); +- this.worker = new IOWorker(path, dsync, "entities"); ++ // Paper - rewrite chunk system + } + + @Override + public CompletableFuture> loadEntities(ChunkPos pos) { +- return this.emptyChunks.contains(pos.toLong()) ? CompletableFuture.completedFuture(emptyChunk(pos)) : this.worker.loadAsync(pos).thenApplyAsync((nbt) -> { +- if (nbt.isEmpty()) { +- this.emptyChunks.add(pos.toLong()); +- return emptyChunk(pos); +- } else { +- try { +- ChunkPos chunkPos2 = readChunkPos(nbt.get()); +- if (!Objects.equals(pos, chunkPos2)) { +- LOGGER.error("Chunk file at {} is in the wrong location. (Expected {}, got {})", pos, pos, chunkPos2); +- } +- } catch (Exception var6) { +- LOGGER.warn("Failed to parse chunk {} position info", pos, var6); +- } +- +- CompoundTag compoundTag = this.upgradeChunkTag(nbt.get()); +- ListTag listTag = compoundTag.getList("Entities", 10); +- List list = EntityType.loadEntitiesRecursive(listTag, this.level).collect(ImmutableList.toImmutableList()); +- return new ChunkEntities<>(pos, list); +- } +- }, this.entityDeserializerQueue::tell); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - copy out read logic into readEntities ++ } ++ ++ // Paper start - rewrite chunk system ++ public static List readEntities(ServerLevel level, CompoundTag compoundTag) { ++ ListTag listTag = compoundTag.getList("Entities", 10); ++ List list = EntityType.loadEntitiesRecursive(listTag, level).collect(ImmutableList.toImmutableList()); ++ return list; + } ++ // Paper end - rewrite chunk system + +- private static ChunkPos readChunkPos(CompoundTag chunkNbt) { ++ public static ChunkPos readChunkPos(CompoundTag chunkNbt) { // Paper - public + int[] is = chunkNbt.getIntArray("Position"); + return new ChunkPos(is[0], is[1]); + } +@@ -81,45 +69,75 @@ public class EntityStorage implements EntityPersistentStorage { + + @Override + public void storeEntities(ChunkEntities dataList) { ++ // Paper start - rewrite chunk system ++ if (true) { ++ throw new UnsupportedOperationException(); ++ } ++ // Paper end - rewrite chunk system + ChunkPos chunkPos = dataList.getPos(); + if (dataList.isEmpty()) { + if (this.emptyChunks.add(chunkPos.toLong())) { +- this.worker.store(chunkPos, (CompoundTag)null); ++ // Paper - rewrite chunk system + } + + } else { +- ListTag listTag = new ListTag(); +- dataList.getEntities().forEach((entity) -> { +- CompoundTag compoundTag = new CompoundTag(); +- if (entity.save(compoundTag)) { +- listTag.add(compoundTag); +- } +- +- }); +- CompoundTag compoundTag = NbtUtils.addCurrentDataVersion(new CompoundTag()); +- compoundTag.put("Entities", listTag); +- writeChunkPos(compoundTag, chunkPos); +- this.worker.store(chunkPos, compoundTag).exceptionally((ex) -> { +- LOGGER.error("Failed to store chunk {}", chunkPos, ex); +- return null; +- }); ++ // Paper - move into saveEntityChunk0 + this.emptyChunks.remove(chunkPos.toLong()); + } + } + ++ // Paper start - rewrite chunk system ++ public static void copyEntities(final CompoundTag from, final CompoundTag into) { ++ if (from == null) { ++ return; ++ } ++ final ListTag entitiesFrom = from.getList("Entities", net.minecraft.nbt.Tag.TAG_COMPOUND); ++ if (entitiesFrom == null || entitiesFrom.isEmpty()) { ++ return; ++ } ++ ++ final ListTag entitiesInto = into.getList("Entities", net.minecraft.nbt.Tag.TAG_COMPOUND); ++ into.put("Entities", entitiesInto); // this is in case into doesn't have any entities ++ entitiesInto.addAll(0, entitiesFrom.copy()); // need to copy, this is coming from the save thread ++ } ++ ++ public static CompoundTag saveEntityChunk(List entities, ChunkPos chunkPos, ServerLevel level) { ++ return saveEntityChunk0(entities, chunkPos, level, false); ++ } ++ private static CompoundTag saveEntityChunk0(List entities, ChunkPos chunkPos, ServerLevel level, boolean force) { ++ if (!force && entities.isEmpty()) { ++ return null; ++ } ++ ++ ListTag listTag = new ListTag(); ++ entities.forEach((entity) -> { // diff here: use entities parameter ++ CompoundTag compoundTag = new CompoundTag(); ++ if (entity.save(compoundTag)) { ++ listTag.add(compoundTag); ++ } ++ ++ }); ++ CompoundTag compoundTag = NbtUtils.addCurrentDataVersion(new CompoundTag()); ++ compoundTag.put("Entities", listTag); ++ writeChunkPos(compoundTag, chunkPos); ++ // Paper - remove worker usage ++ ++ return !force && listTag.isEmpty() ? null : compoundTag; ++ } ++ // Paper end - rewrite chunk system ++ + @Override + public void flush(boolean sync) { +- this.worker.synchronize(sync).join(); +- this.entityDeserializerQueue.runAll(); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + +- private CompoundTag upgradeChunkTag(CompoundTag chunkNbt) { ++ public static CompoundTag upgradeChunkTag(CompoundTag chunkNbt) { // Paper - public and static + int i = NbtUtils.getDataVersion(chunkNbt, -1); + return ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY_CHUNK, chunkNbt, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - route to new converter system + } + + @Override + public void close() throws IOException { +- this.worker.close(); ++ throw new UnsupportedOperationException(); // Paper - rewrite chunk system + } + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +index 8d20e265872e1f8200de186a69a29f498ceb8588..bc8038da65f834249c61a262fc1a5abb7cc91a63 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -44,6 +44,7 @@ public class RegionFile implements AutoCloseable { + private final IntBuffer timestamps; + @VisibleForTesting + protected final RegionBitmap usedSectors; ++ public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // Paper + + public RegionFile(Path file, Path directory, boolean dsync) throws IOException { + this(file, directory, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format +@@ -228,7 +229,7 @@ public class RegionFile implements AutoCloseable { + return (byteCount + 4096 - 1) / 4096; + } + +- public boolean doesChunkExist(ChunkPos pos) { ++ public synchronized boolean doesChunkExist(ChunkPos pos) { // Paper - synchronized + int i = this.getOffset(pos); + + if (i == 0) { +@@ -395,6 +396,11 @@ public class RegionFile implements AutoCloseable { + } + + public void close() throws IOException { ++ // Paper start - Prevent regionfiles from being closed during use ++ this.fileLock.lock(); ++ synchronized (this) { ++ try { ++ // Paper end + try { + this.padToFullSector(); + } finally { +@@ -404,6 +410,10 @@ public class RegionFile implements AutoCloseable { + this.file.close(); + } + } ++ } finally { // Paper start - Prevent regionfiles from being closed during use ++ this.fileLock.unlock(); ++ } ++ } // Paper end + + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index fa086a19f038b929f356292b2f657929765f7b6f..f1ecc3832da094400ed9d45bfc60af10da682b4a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -25,30 +25,98 @@ public class RegionFileStorage implements AutoCloseable { + private final Path folder; + private final boolean sync; + +- RegionFileStorage(Path directory, boolean dsync) { ++ // Paper start - cache regionfile does not exist state ++ static final int MAX_NON_EXISTING_CACHE = 1024 * 64; ++ private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(); ++ private synchronized boolean doesRegionFilePossiblyExist(long position) { ++ if (this.nonExistingRegionFiles.contains(position)) { ++ this.nonExistingRegionFiles.addAndMoveToFirst(position); ++ return false; ++ } ++ return true; ++ } ++ ++ private synchronized void createRegionFile(long position) { ++ this.nonExistingRegionFiles.remove(position); ++ } ++ ++ private synchronized void markNonExisting(long position) { ++ if (this.nonExistingRegionFiles.addAndMoveToFirst(position)) { ++ while (this.nonExistingRegionFiles.size() >= MAX_NON_EXISTING_CACHE) { ++ this.nonExistingRegionFiles.removeLastLong(); ++ } ++ } ++ } ++ ++ public synchronized boolean doesRegionFileNotExistNoIO(ChunkPos pos) { ++ long key = ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ()); ++ return !this.doesRegionFilePossiblyExist(key); ++ } ++ // Paper end - cache regionfile does not exist state ++ ++ protected RegionFileStorage(Path directory, boolean dsync) { // Paper - protected constructor + this.folder = directory; + this.sync = dsync; + } + +- private RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit +- long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); ++ // Paper start ++ public synchronized RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) { ++ return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ())); ++ } ++ ++ public synchronized boolean chunkExists(ChunkPos pos) throws IOException { ++ RegionFile regionfile = getRegionFile(pos, true); ++ ++ return regionfile != null ? regionfile.hasChunk(pos) : false; ++ } ++ ++ public synchronized RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit ++ return this.getRegionFile(chunkcoordintpair, existingOnly, false); ++ } ++ public synchronized RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly, boolean lock) throws IOException { ++ // Paper end ++ long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); final long regionPos = i; // Paper - OBFHELPER + RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i); + + if (regionfile != null) { ++ // Paper start ++ if (lock) { ++ // must be in this synchronized block ++ regionfile.fileLock.lock(); ++ } ++ // Paper end + return regionfile; + } else { ++ // Paper start - cache regionfile does not exist state ++ if (existingOnly && !this.doesRegionFilePossiblyExist(regionPos)) { ++ return null; ++ } ++ // Paper end - cache regionfile does not exist state + if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - Sanitise RegionFileCache and make configurable + ((RegionFile) this.regionCache.removeLast()).close(); + } + +- FileUtil.createDirectoriesSafe(this.folder); ++ // Paper - only create directory if not existing only - moved down + Path path = this.folder; + int j = chunkcoordintpair.getRegionX(); + Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); +- if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit ++ if (existingOnly && !java.nio.file.Files.exists(path1)) { // Paper start - cache regionfile does not exist state ++ this.markNonExisting(regionPos); ++ return null; // CraftBukkit ++ } else { ++ this.createRegionFile(regionPos); ++ } ++ // Paper end - cache regionfile does not exist state ++ FileUtil.createDirectoriesSafe(this.folder); // Paper - only create directory if not existing only - moved from above + RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync); + + this.regionCache.putAndMoveToFirst(i, regionfile1); ++ // Paper start ++ if (lock) { ++ // must be in this synchronized block ++ regionfile1.fileLock.lock(); ++ } ++ // Paper end + return regionfile1; + } + } +@@ -56,11 +124,12 @@ public class RegionFileStorage implements AutoCloseable { + @Nullable + public CompoundTag read(ChunkPos pos) throws IOException { + // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing +- RegionFile regionfile = this.getRegionFile(pos, true); ++ RegionFile regionfile = this.getRegionFile(pos, true, true); // Paper + if (regionfile == null) { + return null; + } + // CraftBukkit end ++ try { // Paper + DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos); + + CompoundTag nbttagcompound; +@@ -97,6 +166,9 @@ public class RegionFileStorage implements AutoCloseable { + } + + return nbttagcompound; ++ } finally { // Paper start ++ regionfile.fileLock.unlock(); ++ } // Paper end + } + + public void scanChunk(ChunkPos chunkPos, StreamTagVisitor scanner) throws IOException { +@@ -131,7 +203,13 @@ public class RegionFileStorage implements AutoCloseable { + } + + protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException { +- RegionFile regionfile = this.getRegionFile(pos, false); // CraftBukkit ++ // Paper start - rewrite chunk system ++ RegionFile regionfile = this.getRegionFile(pos, nbt == null, true); // CraftBukkit ++ if (nbt == null && regionfile == null) { ++ return; ++ } ++ try { // Try finally to unlock the region file ++ // Paper end - rewrite chunk system + // Paper start - Chunk save reattempt + int attempts = 0; + Exception lastException = null; +@@ -177,9 +255,14 @@ public class RegionFileStorage implements AutoCloseable { + net.minecraft.server.MinecraftServer.LOGGER.error("Failed to save chunk {}", pos, lastException); + } + // Paper end - Chunk save reattempt ++ // Paper start - rewrite chunk system ++ } finally { ++ regionfile.fileLock.unlock(); ++ } ++ // Paper end - rewrite chunk system + } + +- public void close() throws IOException { ++ public synchronized void close() throws IOException { // Paper -> synchronized + ExceptionCollector exceptionsuppressor = new ExceptionCollector<>(); + ObjectIterator objectiterator = this.regionCache.values().iterator(); + +@@ -196,7 +279,7 @@ public class RegionFileStorage implements AutoCloseable { + exceptionsuppressor.throwIfPresent(); + } + +- public void flush() throws IOException { ++ public synchronized void flush() throws IOException { // Paper - synchronize + ObjectIterator objectiterator = this.regionCache.values().iterator(); + + while (objectiterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java +index 56f0e217276b01aed2f20a71f6849826285fc15b..54db563d80bbabd87a2be6f5ead92b482ac07b10 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java +@@ -34,27 +34,28 @@ import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.LevelHeightAccessor; + import org.slf4j.Logger; + +-public class SectionStorage implements AutoCloseable { ++public class SectionStorage extends RegionFileStorage implements AutoCloseable { // Paper - nuke IOWorker + private static final Logger LOGGER = LogUtils.getLogger(); + private static final String SECTIONS_TAG = "Sections"; +- private final IOWorker worker; ++ // Paper - remove mojang I/O thread + private final Long2ObjectMap> storage = new Long2ObjectOpenHashMap<>(); + private final LongLinkedOpenHashSet dirty = new LongLinkedOpenHashSet(); + private final Function> codec; + private final Function factory; + private final DataFixer fixerUpper; + private final DataFixTypes type; +- private final RegistryAccess registryAccess; ++ public final RegistryAccess registryAccess; // Paper - rewrite chunk system + protected final LevelHeightAccessor levelHeightAccessor; + + public SectionStorage(Path path, Function> codecFactory, Function factory, DataFixer dataFixer, DataFixTypes dataFixTypes, boolean dsync, RegistryAccess dynamicRegistryManager, LevelHeightAccessor world) { ++ super(path, dsync); // Paper - remove mojang I/O thread + this.codec = codecFactory; + this.factory = factory; + this.fixerUpper = dataFixer; + this.type = dataFixTypes; + this.registryAccess = dynamicRegistryManager; + this.levelHeightAccessor = world; +- this.worker = new IOWorker(path, dsync, path.getFileName().toString()); ++ // Paper - remove mojang I/O thread + } + + protected void tick(BooleanSupplier shouldKeepTicking) { +@@ -116,23 +117,21 @@ public class SectionStorage implements AutoCloseable { + } + + private void readColumn(ChunkPos pos) { +- Optional optional = this.tryRead(pos).join(); +- RegistryOps registryOps = RegistryOps.create(NbtOps.INSTANCE, this.registryAccess); +- this.readColumn(pos, registryOps, optional.orElse((CompoundTag)null)); ++ throw new IllegalStateException("Only chunk system can load in state, offending class:" + this.getClass().getName()); // Paper - rewrite chunk system + } + + private CompletableFuture> tryRead(ChunkPos pos) { +- return this.worker.loadAsync(pos).exceptionally((throwable) -> { +- if (throwable instanceof IOException iOException) { +- LOGGER.error("Error reading chunk {} data from disk", pos, iOException); +- return Optional.empty(); +- } else { +- throw new CompletionException(throwable); +- } +- }); ++ // Paper start - rewrite chunk system ++ try { ++ return CompletableFuture.completedFuture(Optional.ofNullable(this.read(pos))); ++ } catch (Throwable thr) { ++ return CompletableFuture.failedFuture(thr); ++ } ++ // Paper end - rewrite chunk system + } + + private void readColumn(ChunkPos pos, DynamicOps ops, @Nullable T data) { ++ if (true) throw new IllegalStateException("Only chunk system can load in state, offending class:" + this.getClass().getName()); // Paper - rewrite chunk system + if (data == null) { + for(int i = this.levelHeightAccessor.getMinSection(); i < this.levelHeightAccessor.getMaxSection(); ++i) { + this.storage.put(getKey(pos, i), Optional.empty()); +@@ -177,7 +176,7 @@ public class SectionStorage implements AutoCloseable { + Dynamic dynamic = this.writeColumn(pos, registryOps); + Tag tag = dynamic.getValue(); + if (tag instanceof CompoundTag) { +- this.worker.store(pos, (CompoundTag)tag); ++ try { this.write(pos, (CompoundTag)tag); } catch (IOException ioexception) { SectionStorage.LOGGER.error("Error writing data to disk", ioexception); } // Paper - nuke IOWorker + } else { + LOGGER.error("Expected compound tag, got {}", (Object)tag); + } +@@ -222,7 +221,7 @@ public class SectionStorage implements AutoCloseable { + } + + private static int getVersion(Dynamic dynamic) { +- return dynamic.get("DataVersion").asInt(1945); ++ return dynamic.get("DataVersion").asInt(1945); // Paper - diff on change, constant used in ChunkLoadTask + } + + public void flush(ChunkPos pos) { +@@ -240,6 +239,9 @@ public class SectionStorage implements AutoCloseable { + + @Override + public void close() throws IOException { +- this.worker.close(); ++ //this.worker.close(); // Paper - nuke I/O worker - don't call the worker ++ super.close(); // Paper - nuke I/O worker - call super.close method which is responsible for closing used files. + } ++ ++ // Paper - rewrite chunk system + } +diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java +index 2830d32bba3dc85847e3a5d9b4d98f822e34b606..4cdfc433df67afcd455422e9baf56f167dd712ae 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java +@@ -8,54 +8,42 @@ import javax.annotation.Nullable; + import net.minecraft.world.entity.Entity; + + public class EntityTickList { +- private Int2ObjectMap active = new Int2ObjectLinkedOpenHashMap<>(); +- private Int2ObjectMap passive = new Int2ObjectLinkedOpenHashMap<>(); +- @Nullable +- private Int2ObjectMap iterated; ++ private final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking? + + private void ensureActiveIsNotIterated() { +- if (this.iterated == this.active) { +- this.passive.clear(); +- +- for(Int2ObjectMap.Entry entry : Int2ObjectMaps.fastIterable(this.active)) { +- this.passive.put(entry.getIntKey(), entry.getValue()); +- } +- +- Int2ObjectMap int2ObjectMap = this.active; +- this.active = this.passive; +- this.passive = int2ObjectMap; +- } ++ // Paper - replace with better logic, do not delay removals + + } + + public void add(Entity entity) { ++ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper + this.ensureActiveIsNotIterated(); +- this.active.put(entity.getId(), entity); ++ this.entities.add(entity); // Paper - replace with better logic, do not delay removals/additions + } + + public void remove(Entity entity) { ++ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist removal"); // Paper + this.ensureActiveIsNotIterated(); +- this.active.remove(entity.getId()); ++ this.entities.remove(entity); // Paper - replace with better logic, do not delay removals/additions + } + + public boolean contains(Entity entity) { +- return this.active.containsKey(entity.getId()); ++ return this.entities.contains(entity); // Paper - replace with better logic, do not delay removals/additions + } + + public void forEach(Consumer action) { +- if (this.iterated != null) { +- throw new UnsupportedOperationException("Only one concurrent iteration supported"); +- } else { +- this.iterated = this.active; +- +- try { +- for(Entity entity : this.active.values()) { +- action.accept(entity); +- } +- } finally { +- this.iterated = null; ++ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist iteration"); // Paper ++ // Paper start - replace with better logic, do not delay removals/additions ++ // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... ++ // (by dfl iterator() is configured to not iterate over new entries) ++ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.entities.iterator(); ++ try { ++ while (iterator.hasNext()) { ++ action.accept(iterator.next()); + } +- ++ } finally { ++ iterator.finishedIterating(); + } ++ // Paper end - replace with better logic, do not delay removals/additions + } + } +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 54308f1decc3982f30bf8b7a8a9d8865bfdbb9fd..902156477bdfc9917105f1229f760c26e5af302a 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +@@ -87,7 +87,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName("init_biomes", () -> { + this.doCreateBiomes(blender, noiseConfig, structureAccessor, chunk); + return chunk; +- }), Util.backgroundExecutor()); ++ }), executor); // Paper - run with supplied executor + } + + private void doCreateBiomes(Blender blender, RandomState noiseConfig, StructureManager structureAccessor, ChunkAccess chunk) { +@@ -286,7 +286,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + + return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName("wgen_fill_noise", () -> { + return this.doFill(blender, structureAccessor, noiseConfig, chunk, j, k); +- }), Util.backgroundExecutor()).whenCompleteAsync((ichunkaccess1, throwable) -> { ++ }), executor).whenCompleteAsync((ichunkaccess1, throwable) -> { // Paper - run with supplied executor + Iterator iterator = set.iterator(); + + while (iterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java +index 1ca00340aaa201dd34e5c350d23ef53e126a0ca6..16356d7f388561300e794a52f3f263b8e7d9b880 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java +@@ -50,8 +50,101 @@ public class StructureCheck { + private final BiomeSource biomeSource; + private final long seed; + private final DataFixer fixerUpper; +- private final Long2ObjectMap> loadedChunks = new Long2ObjectOpenHashMap<>(); +- private final Map featureChecks = new HashMap<>(); ++ // Paper start - rewrite chunk system - synchronise this class ++ // additionally, make sure to purge entries from the maps so it does not leak memory ++ private static final int CHUNK_TOTAL_LIMIT = 50 * (2 * 100 + 1) * (2 * 100 + 1); // cache 50 structure lookups ++ private static final int PER_FEATURE_CHECK_LIMIT = 50 * (2 * 100 + 1) * (2 * 100 + 1); // cache 50 structure lookups ++ ++ private final SynchronisedLong2ObjectMap> loadedChunksSafe = new SynchronisedLong2ObjectMap<>(CHUNK_TOTAL_LIMIT); ++ private final java.util.concurrent.ConcurrentHashMap featureChecksSafe = new java.util.concurrent.ConcurrentHashMap<>(); ++ ++ private static final class SynchronisedLong2ObjectMap { ++ private final it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap map = new it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<>(); ++ private final int limit; ++ ++ public SynchronisedLong2ObjectMap(final int limit) { ++ this.limit = limit; ++ } ++ ++ // must hold lock on map ++ private void purgeEntries() { ++ while (this.map.size() > this.limit) { ++ this.map.removeLast(); ++ } ++ } ++ ++ public V get(final long key) { ++ synchronized (this.map) { ++ return this.map.getAndMoveToFirst(key); ++ } ++ } ++ ++ public V put(final long key, final V value) { ++ synchronized (this.map) { ++ final V ret = this.map.putAndMoveToFirst(key, value); ++ this.purgeEntries(); ++ return ret; ++ } ++ } ++ ++ public V compute(final long key, final java.util.function.BiFunction remappingFunction) { ++ synchronized (this.map) { ++ // first, compute the value - if one is added, it will be at the last entry ++ this.map.compute(key, remappingFunction); ++ // move the entry to first, just in case it was added at last ++ final V ret = this.map.getAndMoveToFirst(key); ++ // now purge the last entries ++ this.purgeEntries(); ++ ++ return ret; ++ } ++ } ++ } ++ ++ private static final class SynchronisedLong2BooleanMap { ++ private final it.unimi.dsi.fastutil.longs.Long2BooleanLinkedOpenHashMap map = new it.unimi.dsi.fastutil.longs.Long2BooleanLinkedOpenHashMap(); ++ private final int limit; ++ ++ public SynchronisedLong2BooleanMap(final int limit) { ++ this.limit = limit; ++ } ++ ++ // must hold lock on map ++ private void purgeEntries() { ++ while (this.map.size() > this.limit) { ++ this.map.removeLastBoolean(); ++ } ++ } ++ ++ public boolean remove(final long key) { ++ synchronized (this.map) { ++ return this.map.remove(key); ++ } ++ } ++ ++ // note: ++ public boolean getOrCompute(final long key, final it.unimi.dsi.fastutil.longs.Long2BooleanFunction ifAbsent) { ++ synchronized (this.map) { ++ if (this.map.containsKey(key)) { ++ return this.map.getAndMoveToFirst(key); ++ } ++ } ++ ++ final boolean put = ifAbsent.get(key); ++ ++ synchronized (this.map) { ++ if (this.map.containsKey(key)) { ++ return this.map.getAndMoveToFirst(key); ++ } ++ this.map.putAndMoveToFirst(key, put); ++ ++ this.purgeEntries(); ++ ++ return put; ++ } ++ } ++ } ++ // Paper end - rewrite chunk system - synchronise this class + + public StructureCheck(ChunkScanAccess chunkIoWorker, RegistryAccess registryManager, StructureTemplateManager structureTemplateManager, ResourceKey worldKey, ChunkGenerator chunkGenerator, RandomState noiseConfig, LevelHeightAccessor world, BiomeSource biomeSource, long seed, DataFixer dataFixer) { // Paper - fix missing CB diff + this.storageAccess = chunkIoWorker; +@@ -70,7 +163,7 @@ public class StructureCheck { + + public StructureCheckResult checkStart(ChunkPos pos, Structure type, boolean skipReferencedStructures) { + long l = pos.toLong(); +- Object2IntMap object2IntMap = this.loadedChunks.get(l); ++ Object2IntMap object2IntMap = this.loadedChunksSafe.get(l); // Paper - rewrite chunk system - synchronise this class + if (object2IntMap != null) { + return this.checkStructureInfo(object2IntMap, type, skipReferencedStructures); + } else { +@@ -78,9 +171,9 @@ public class StructureCheck { + if (structureCheckResult != null) { + return structureCheckResult; + } else { +- boolean bl = this.featureChecks.computeIfAbsent(type, (structure2) -> { +- return new Long2BooleanOpenHashMap(); +- }).computeIfAbsent(l, (chunkPos) -> { ++ boolean bl = this.featureChecksSafe.computeIfAbsent(type, (structure2) -> { // Paper - rewrite chunk system - synchronise this class ++ return new SynchronisedLong2BooleanMap(PER_FEATURE_CHECK_LIMIT); // Paper - rewrite chunk system - synchronise this class ++ }).getOrCompute(l, (chunkPos) -> { // Paper - rewrite chunk system - synchronise this class + return this.canCreateStructure(pos, type); + }); + return !bl ? StructureCheckResult.START_NOT_PRESENT : StructureCheckResult.CHUNK_LOAD_NEEDED; +@@ -193,17 +286,26 @@ public class StructureCheck { + } + + private void storeFullResults(long pos, Object2IntMap referencesByStructure) { +- this.loadedChunks.put(pos, deduplicateEmptyMap(referencesByStructure)); +- this.featureChecks.values().forEach((generationPossibilityByChunkPos) -> { +- generationPossibilityByChunkPos.remove(pos); +- }); ++ // Paper start - rewrite chunk system - synchronise this class ++ this.loadedChunksSafe.put(pos, deduplicateEmptyMap(referencesByStructure)); ++ // once we insert into loadedChunks, we don't really need to be very careful about removing everything ++ // from this map, as everything that checks this map uses loadedChunks first ++ // so, one way or another it's a race condition that doesn't matter ++ for (SynchronisedLong2BooleanMap value : this.featureChecksSafe.values()) { ++ value.remove(pos); ++ } ++ // Paper end - rewrite chunk system - synchronise this class + } + + public void incrementReference(ChunkPos pos, Structure structure) { +- this.loadedChunks.compute(pos.toLong(), (posx, referencesByStructure) -> { +- if (referencesByStructure == null || referencesByStructure.isEmpty()) { ++ this.loadedChunksSafe.compute(pos.toLong(), (posx, referencesByStructure) -> { // Paper start - rewrite chunk system - synchronise this class ++ // make this COW so that we do not mutate state that may be currently in use ++ if (referencesByStructure == null) { + referencesByStructure = new Object2IntOpenHashMap<>(); ++ } else { ++ referencesByStructure = referencesByStructure instanceof Object2IntOpenHashMap fastClone ? fastClone.clone() : new Object2IntOpenHashMap<>(referencesByStructure); + } ++ // Paper end - rewrite chunk system - synchronise this class + + referencesByStructure.computeInt(structure, (feature, references) -> { + return references == null ? 1 : references + 1; +diff --git a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java +index 9f6c2e5b5d9e8d714a47c770e255d06c0ef7c190..ac807277a6b26d140ea9873d17c7aa4fb5fe37b2 100644 +--- a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java ++++ b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java +@@ -25,6 +25,19 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + @Nullable + private BiConsumer, ScheduledTick> onTickAdded; + ++ // Paper start - add dirty flag ++ private boolean dirty; ++ private long lastSaved = Long.MIN_VALUE; ++ ++ public boolean isDirty(final long tick) { ++ return this.dirty || (!this.tickQueue.isEmpty() && tick != this.lastSaved); ++ } ++ ++ public void clearDirty() { ++ this.dirty = false; ++ } ++ // Paper end - add dirty flag ++ + public LevelChunkTicks() { + } + +@@ -50,6 +63,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + public ScheduledTick poll() { + ScheduledTick scheduledTick = this.tickQueue.poll(); + if (scheduledTick != null) { ++ this.dirty = true; // Paper - add dirty flag + this.ticksPerPosition.remove(scheduledTick); + } + +@@ -59,6 +73,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + @Override + public void schedule(ScheduledTick orderedTick) { + if (this.ticksPerPosition.add(orderedTick)) { ++ this.dirty = true; // Paper - add dirty flag + this.scheduleUnchecked(orderedTick); + } + +@@ -83,7 +98,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + while(iterator.hasNext()) { + ScheduledTick scheduledTick = iterator.next(); + if (predicate.test(scheduledTick)) { +- iterator.remove(); ++ iterator.remove(); this.dirty = true; // Paper - add dirty flag + this.ticksPerPosition.remove(scheduledTick); + } + } +@@ -101,6 +116,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + + @Override + public ListTag save(long l, Function function) { ++ this.lastSaved = l; // Paper - add dirty system to level ticks + ListTag listTag = new ListTag(); + if (this.pendingTicks != null) { + for(SavedTick savedTick : this.pendingTicks) { +@@ -117,6 +133,11 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + + public void unpack(long time) { + if (this.pendingTicks != null) { ++ // Paper start - add dirty system to level chunk ticks ++ if (this.tickQueue.isEmpty()) { ++ this.lastSaved = time; ++ } ++ // Paper end - add dirty system to level chunk ticks + int i = -this.pendingTicks.size(); + + for(SavedTick savedTick : this.pendingTicks) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index 260ca4a9c170567b27488466f802c91212997f91..e47b00912fc76e9639f9c51d96e6d39da3c963e3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -115,7 +115,7 @@ public class CraftChunk implements Chunk { + + @Override + public boolean isEntitiesLoaded() { +- return this.getCraftWorld().getHandle().entityManager.areEntitiesLoaded(ChunkPos.asLong(this.x, this.z)); ++ return this.getCraftWorld().getHandle().areEntitiesLoaded(io.papermc.paper.util.CoordinateUtils.getChunkKey(this.x, this.z)); // Paper - rewrite chunk system + } + + @Override +@@ -124,51 +124,7 @@ public class CraftChunk implements Chunk { + this.getWorld().getChunkAt(this.x, this.z); // Transient load for this tick + } + +- PersistentEntitySectionManager entityManager = this.getCraftWorld().getHandle().entityManager; +- long pair = ChunkPos.asLong(this.x, this.z); +- +- if (entityManager.areEntitiesLoaded(pair)) { +- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream() +- .map(net.minecraft.world.entity.Entity::getBukkitEntity) +- .filter(Objects::nonNull).toArray(Entity[]::new); +- } +- +- entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading +- +- // SPIGOT-6772: Use entity mailbox and re-schedule entities if they get unloaded +- ProcessorMailbox mailbox = ((EntityStorage) entityManager.permanentStorage).entityDeserializerQueue; +- BooleanSupplier supplier = () -> { +- // only execute inbox if our entities are not present +- if (entityManager.areEntitiesLoaded(pair)) { +- return true; +- } +- +- if (!entityManager.isPending(pair)) { +- // Our entities got unloaded, this should normally not happen. +- entityManager.ensureChunkQueuedForLoad(pair); // Re-start entity loading +- } +- +- // tick loading inbox, which loads the created entities to the world +- // (if present) +- entityManager.tick(); +- // check if our entities are loaded +- return entityManager.areEntitiesLoaded(pair); +- }; +- +- // now we wait until the entities are loaded, +- // the converting from NBT to entity object is done on the main Thread which is why we wait +- while (!supplier.getAsBoolean()) { +- if (mailbox.size() != 0) { +- mailbox.run(); +- } else { +- Thread.yield(); +- LockSupport.parkNanos("waiting for entity loading", 100000L); +- } +- } +- +- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream() +- .map(net.minecraft.world.entity.Entity::getBukkitEntity) +- .filter(Objects::nonNull).toArray(Entity[]::new); ++ return this.getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - rewrite chunk system + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 92369eb350fd795a4e99731d7ceda4f8b890791e..a76f966d72b749f1706363d33caa351b54e9fa14 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1396,7 +1396,6 @@ public final class CraftServer implements Server { + + internal.keepSpawnInMemory = creator.keepSpawnLoaded().toBooleanOrElse(internal.getWorld().getKeepSpawnInMemory()); // Paper + this.getServer().prepareLevels(internal.getChunkSource().chunkMap.progressListener, internal); +- internal.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API + + this.pluginManager.callEvent(new WorldLoadEvent(internal.getWorld())); + return internal.getWorld(); +@@ -1441,7 +1440,7 @@ public final class CraftServer implements Server { + } + + handle.getChunkSource().close(save); +- handle.entityManager.close(save); // SPIGOT-6722: close entityManager ++ // handle.entityManager.close(save); // SPIGOT-6722: close entityManager // Paper - rewrite chunk system + handle.convertable.close(); + } catch (Exception ex) { + this.getLogger().log(Level.SEVERE, null, ex); +@@ -2456,7 +2455,7 @@ public final class CraftServer implements Server { + + @Override + public boolean isPrimaryThread() { +- return Thread.currentThread().equals(this.console.serverThread) || this.console.hasStopped() || !org.spigotmc.AsyncCatcher.enabled; // All bets are off if we have shut down (e.g. due to watchdog) ++ return io.papermc.paper.util.TickThread.isTickThread(); // Paper - rewrite chunk system + } + + // Paper start - Adventure +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 6379f26776e2e267b84fe8f8392b53d7d89eb5ad..726aa0461bbf380989a5b51dbfdfdda259b8632b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -507,10 +507,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { + ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); + if (playerChunk == null) return false; + +- playerChunk.getTickingChunkFuture().thenAccept(either -> { +- either.left().ifPresent(chunk -> { ++ // Paper start - rewrite player chunk loader ++ net.minecraft.world.level.chunk.LevelChunk chunk = playerChunk.getSendingChunk(); ++ if (chunk == null) { ++ return false; ++ } ++ // Paper end - rewrite player chunk loader + List playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false); +- if (playersInRange.isEmpty()) return; ++ if (playersInRange.isEmpty()) return true; // Paper - rewrite player chunk loader + + ClientboundLevelChunkWithLightPacket refreshPacket = new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null); + for (ServerPlayer player : playersInRange) { +@@ -518,8 +522,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + player.connection.send(refreshPacket); + } +- }); +- }); ++ // Paper - rewrite player chunk loader + + return true; + } +@@ -598,20 +601,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public Collection getPluginChunkTickets(int x, int z) { + DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager; +- SortedArraySet> tickets = chunkDistanceManager.tickets.get(ChunkPos.asLong(x, z)); +- +- if (tickets == null) { +- return Collections.emptyList(); +- } +- +- ImmutableList.Builder ret = ImmutableList.builder(); +- for (Ticket ticket : tickets) { +- if (ticket.getType() == TicketType.PLUGIN_TICKET) { +- ret.add((Plugin) ticket.key); +- } +- } +- +- return ret.build(); ++ return chunkDistanceManager.getChunkHolderManager().getPluginChunkTickets(x, z); // Paper - rewrite chunk system + } + + @Override +@@ -619,7 +609,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + Map> ret = new HashMap<>(); + DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager; + +- for (Long2ObjectMap.Entry>> chunkTickets : chunkDistanceManager.tickets.long2ObjectEntrySet()) { ++ for (Long2ObjectMap.Entry>> chunkTickets : chunkDistanceManager.getChunkHolderManager().getTicketsCopy().long2ObjectEntrySet()) { // Paper - rewrite chunk system + long chunkKey = chunkTickets.getLongKey(); + SortedArraySet> tickets = chunkTickets.getValue(); + +@@ -1288,12 +1278,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public int getViewDistance() { +- return this.world.getChunkSource().chunkMap.serverViewDistance; ++ return this.getHandle().playerChunkLoader.getAPIViewDistance(); // Paper - replace player chunk loader + } + + @Override + public int getSimulationDistance() { +- return this.world.getChunkSource().chunkMap.getDistanceManager().simulationDistance; ++ return this.getHandle().playerChunkLoader.getAPITickDistance(); // Paper - replace player chunk loader + } + + public BlockMetadataStore getBlockMetadata() { +@@ -2459,17 +2449,20 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setSimulationDistance(final int simulationDistance) { +- throw new UnsupportedOperationException("Not implemented yet"); ++ if (simulationDistance < 2 || simulationDistance > 32) { ++ throw new IllegalArgumentException("Simulation distance " + simulationDistance + " is out of range of [2, 32]"); ++ } ++ this.getHandle().chunkSource.chunkMap.setTickViewDistance(simulationDistance); + } + + @Override + public int getSendViewDistance() { +- return this.getViewDistance(); ++ return this.getHandle().playerChunkLoader.getAPISendViewDistance(); // Paper - replace player chunk loader + } + + @Override + public void setSendViewDistance(final int viewDistance) { +- throw new UnsupportedOperationException("Not implemented yet"); ++ this.getHandle().chunkSource.chunkMap.setSendViewDistance(viewDistance); // Paper - replace player chunk loader + } + + // Paper start - implement pointers +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 205dfed388db7d022e4fd8b3f89485735d3320a8..206520f6f20b2e48b1eefdd4edb26510b88e4c92 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -3400,31 +3400,31 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public int getViewDistance() { +- return io.papermc.paper.chunk.system.ChunkSystem.getLoadViewDistance(this.getHandle()); ++ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPIViewDistance(this); + } + + @Override + public void setViewDistance(final int viewDistance) { +- throw new UnsupportedOperationException("Not implemented yet"); ++ this.getHandle().setLoadViewDistance(viewDistance < 0 ? viewDistance : viewDistance + 1); + } + + @Override + public int getSimulationDistance() { +- return io.papermc.paper.chunk.system.ChunkSystem.getTickViewDistance(this.getHandle()); ++ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPITickViewDistance(this); + } + + @Override + public void setSimulationDistance(final int simulationDistance) { +- throw new UnsupportedOperationException("Not implemented yet"); ++ this.getHandle().setTickViewDistance(simulationDistance); + } + + @Override + public int getSendViewDistance() { +- return io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(this.getHandle()); ++ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPISendViewDistance(this); + } + + @Override + public void setSendViewDistance(final int viewDistance) { +- throw new UnsupportedOperationException("Not implemented yet"); ++ this.getHandle().setSendViewDistance(viewDistance); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java +index f6d003ea707f43287e52f8ffad24be35eeefec69..c6e5d3b7ef3886d0ffa9302d1270c048eaaeb671 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java +@@ -264,7 +264,7 @@ public class CustomChunkGenerator extends InternalChunkGenerator { + return ichunkaccess1; + }; + +- return future == null ? CompletableFuture.supplyAsync(() -> function.apply(chunk), net.minecraft.Util.backgroundExecutor()) : future.thenApply(function); ++ return future == null ? CompletableFuture.supplyAsync(() -> function.apply(chunk), executor) : future.thenApply(function); // Paper - run with supplied executor + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java +index 7ed861cd67889e525ab4987c0afed245aca08833..86a20c91beff6b27e6ec886e49ba902b216106f2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java +@@ -826,19 +826,39 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { + @Nullable + @Override + public BlockState getBlockStateIfLoaded(final BlockPos blockposition) { +- return null; ++ return this.handle.getBlockStateIfLoaded(blockposition); + } + + @Nullable + @Override + public FluidState getFluidIfLoaded(final BlockPos blockposition) { +- return null; ++ return this.handle.getFluidIfLoaded(blockposition); + } + + @Nullable + @Override + public ChunkAccess getChunkIfLoadedImmediately(final int x, final int z) { +- return null; ++ return this.handle.getChunkIfLoadedImmediately(x, z); ++ } ++ ++ @Override ++ public void getHardCollidingEntities(final Entity except, final AABB box, final Predicate predicate, final List into) { ++ this.handle.getHardCollidingEntities(except, box, predicate, into); ++ } ++ ++ @Override ++ public List getHardCollidingEntities(final Entity except, final AABB box, final Predicate predicate) { ++ return this.handle.getHardCollidingEntities(except, box, predicate); ++ } ++ ++ @Override ++ public void getEntities(final Entity except, final AABB box, final Predicate predicate, final List into) { ++ this.handle.getEntities(except, box, predicate, into); ++ } ++ ++ @Override ++ public void getEntitiesByClass(final Class clazz, final Entity except, final AABB box, final List into, final Predicate predicate) { ++ this.handle.getEntitiesByClass(clazz, except, box, into, predicate); + } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +index 7956002e2d4d583c27e277562312d27ea6871557..819a67aa19c6bd624f5ed28d09b35ff2c151749a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +@@ -268,4 +268,19 @@ public class DummyGeneratorAccess implements WorldGenLevel { + @Override + public void scheduleTick(BlockPos pos, Fluid fluid, int delay, net.minecraft.world.ticks.TickPriority priority) {} + // Paper end - add more methods ++ // Paper start ++ @Override ++ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { ++ return java.util.Collections.emptyList(); ++ } ++ ++ @Override ++ public void getEntities(Entity except, AABB box, Predicate predicate, List into) {} ++ ++ @Override ++ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) {} ++ ++ @Override ++ public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} ++ // Paper end + } +diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java +index e8e3cc48cf1c58bd8151d1f28df28781859cd0e3..2e074c16dab1ead47914070329da0398c3274048 100644 +--- a/src/main/java/org/spigotmc/AsyncCatcher.java ++++ b/src/main/java/org/spigotmc/AsyncCatcher.java +@@ -9,7 +9,7 @@ public class AsyncCatcher + + public static void catchOp(String reason) + { +- if ( (AsyncCatcher.enabled || io.papermc.paper.util.TickThread.STRICT_THREAD_CHECKS) && Thread.currentThread() != MinecraftServer.getServer().serverThread ) // Paper ++ if (!(io.papermc.paper.util.TickThread.isTickThread())) // Paper + { + MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper + throw new IllegalStateException( "Asynchronous " + reason + "!" ); +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index b1ac7338fa632611ea8332044b09070f78f8f5f1..a284d3b8526a743ba4389ec5b44d80af6d0e5a5f 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -8,7 +8,7 @@ import java.util.logging.Logger; + import net.minecraft.server.MinecraftServer; + import org.bukkit.Bukkit; + +-public class WatchdogThread extends Thread ++public final class WatchdogThread extends io.papermc.paper.util.TickThread // Paper - rewrite chunk system + { + + private static WatchdogThread instance; +@@ -115,6 +115,7 @@ public class WatchdogThread extends Thread + // Paper end - Different message for short timeout + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper ++ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(isLongTimeout); // Paper - rewrite chunk system + WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // diff --git a/patches/server/0986-incremental-chunk-and-player-saving.patch b/patches/server/0986-incremental-chunk-and-player-saving.patch new file mode 100644 index 000000000000..8fe25f56ce21 --- /dev/null +++ b/patches/server/0986-incremental-chunk-and-player-saving.patch @@ -0,0 +1,167 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 9 Jun 2019 03:53:22 +0100 +Subject: [PATCH] incremental chunk and player saving + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 48e3b0f065b370f780f8a6145fba9f3f7976badb..79de3c639795cfc0bd86f842446e2bb3ab71d23a 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -908,7 +908,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && this.ticksUntilAutosave <= 0) { +- this.ticksUntilAutosave = this.autosavePeriod; +- // CraftBukkit end +- MinecraftServer.LOGGER.debug("Autosave started"); +- this.profiler.push("save"); +- this.saveEverything(true, false, false); +- this.profiler.pop(); +- MinecraftServer.LOGGER.debug("Autosave finished"); ++ // Paper start - Incremental chunk and player saving ++ int playerSaveInterval = io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.rate; ++ if (playerSaveInterval < 0) { ++ playerSaveInterval = autosavePeriod; + } ++ this.profiler.push("save"); ++ final boolean fullSave = autosavePeriod > 0 && this.tickCount % autosavePeriod == 0; ++ try { ++ this.isSaving = true; ++ if (playerSaveInterval > 0) { ++ this.playerList.saveAll(playerSaveInterval); ++ } ++ for (ServerLevel level : this.getAllLevels()) { ++ if (level.paperConfig().chunks.autoSaveInterval.value() > 0) { ++ level.saveIncrementally(fullSave); ++ } ++ } ++ } finally { ++ this.isSaving = false; ++ } ++ this.profiler.pop(); ++ // Paper end - Incremental chunk and player saving + io.papermc.paper.util.CachedLists.reset(); // Paper + // Paper start - move executeAll() into full server tick timing + try (co.aikar.timings.Timing ignored = MinecraftTimings.processTasksTimer.startTiming()) { +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 9bb4223fbb665211df11dc89fcd13cb7a92cd5dd..20cdfd2bbd5dc71fd37ccedaf3a8d06b45553c9b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -435,6 +435,15 @@ public class ServerChunkCache extends ChunkSource { + } // Paper - Timings + } + ++ // Paper start - Incremental chunk and player saving; duplicate save, but call incremental ++ public void saveIncrementally() { ++ this.runDistanceManagerUpdates(); ++ try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings ++ this.chunkMap.saveIncrementally(); ++ } // Paper - Timings ++ } ++ // Paper end - Incremental chunk and player saving ++ + @Override + public void close() throws IOException { + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index dff2dfbe9cc04894d42181c6691e27ad061beb40..09a9452705cc8d4133940c081583d6d38d226f71 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1290,6 +1290,37 @@ public class ServerLevel extends Level implements WorldGenLevel { + return !this.server.isUnderSpawnProtection(this, pos, player) && this.getWorldBorder().isWithinBounds(pos); + } + ++ // Paper start - Incremental chunk and player saving ++ public void saveIncrementally(boolean doFull) { ++ ServerChunkCache chunkproviderserver = this.getChunkSource(); ++ ++ if (doFull) { ++ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); ++ } ++ ++ try (co.aikar.timings.Timing ignored = this.timings.worldSave.startTiming()) { ++ if (doFull) { ++ this.saveLevelData(); ++ } ++ ++ this.timings.worldSaveChunks.startTiming(); // Paper ++ if (!this.noSave()) chunkproviderserver.saveIncrementally(); ++ this.timings.worldSaveChunks.stopTiming(); // Paper ++ ++ // Copied from save() ++ // CraftBukkit start - moved from MinecraftServer.saveChunks ++ if (doFull) { // Paper ++ ServerLevel worldserver1 = this; ++ ++ this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings()); ++ this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save()); ++ this.convertable.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData()); ++ } ++ // CraftBukkit end ++ } ++ } ++ // Paper end - Incremental chunk and player saving ++ + public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled) { + // Paper start - rewrite chunk system - add close param + this.save(progressListener, flush, savingDisabled, false); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 5657f1ecbadda96a79978f918393c0c9a58dca83..910c5087406837033e580ec2a23f5d30d807b723 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -191,6 +191,7 @@ import org.bukkit.inventory.MainHand; + public class ServerPlayer extends Player { + + private static final Logger LOGGER = LogUtils.getLogger(); ++ public long lastSave = MinecraftServer.currentTick; // Paper - Incremental chunk and player saving + private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32; + private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10; + private static final int FLY_STAT_RECORDING_SPEED = 25; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index d2e65c105c38c71a6b1739b95547772511a36345..23443444ae0c52392bd9cdd758057437d99e376a 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -576,6 +576,7 @@ public abstract class PlayerList { + + protected void save(ServerPlayer player) { + if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit ++ player.lastSave = MinecraftServer.currentTick; // Paper - Incremental chunk and player saving + this.playerIo.save(player); + ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit + +@@ -1227,10 +1228,22 @@ public abstract class PlayerList { + } + + public void saveAll() { ++ // Paper start - Incremental chunk and player saving ++ this.saveAll(-1); ++ } ++ ++ public void saveAll(int interval) { + io.papermc.paper.util.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main + MinecraftTimings.savePlayers.startTiming(); // Paper ++ int numSaved = 0; ++ long now = MinecraftServer.currentTick; + for (int i = 0; i < this.players.size(); ++i) { +- this.save(this.players.get(i)); ++ ServerPlayer entityplayer = this.players.get(i); ++ if (interval == -1 || now - entityplayer.lastSave >= interval) { ++ this.save(entityplayer); ++ if (interval != -1 && ++numSaved >= io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.maxPerTick()) { break; } ++ } ++ // Paper end - Incremental chunk and player saving + } + MinecraftTimings.savePlayers.stopTiming(); // Paper + return null; }); // Paper - ensure main diff --git a/patches/server/0993-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch b/patches/server/0987-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch similarity index 100% rename from patches/server/0993-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch rename to patches/server/0987-Optimize-isInWorldBounds-and-getBlockState-for-inlin.patch diff --git a/patches/server/0988-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch b/patches/server/0988-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch new file mode 100644 index 000000000000..4575405a7d5e --- /dev/null +++ b/patches/server/0988-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch @@ -0,0 +1,128 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 29 Apr 2016 20:02:00 -0400 +Subject: [PATCH] Improve Maps (in item frames) performance and bug fixes + +Maps used a modified version of rendering to support plugin controlled +imaging on maps. The Craft Map Renderer is much slower than Vanilla, +causing maps in item frames to cause a noticeable hit on server performance. + +This updates the map system to not use the Craft system if we detect that no +custom renderers are in use, defaulting to the much simpler Vanilla system. + +Additionally, numerous issues to player position tracking on maps has been fixed. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 09a9452705cc8d4133940c081583d6d38d226f71..5f3502b148588a76079c1d9f55e4203f6de56406 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2620,6 +2620,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + { + if ( iter.next().player == entity ) + { ++ map.decorations.remove(entity.getName().getString()); // Paper + iter.remove(); + } + } +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 ade64d3af069abdb5c94895fe699ac5eee603a6e..5ca1f834f311a87323ced2578535e66efa14e47f 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -789,6 +789,14 @@ public abstract class Player extends LivingEntity { + return null; + } + // CraftBukkit end ++ // Paper start - remove player from map on drop ++ if (itemstack.getItem() == Items.FILLED_MAP) { ++ net.minecraft.world.level.saveddata.maps.MapItemSavedData worldmap = net.minecraft.world.item.MapItem.getSavedData(itemstack, this.level()); ++ if (worldmap != null) { ++ worldmap.tickCarriedBy(this, itemstack); ++ } ++ } ++ // Paper end + + return entityitem; + } +diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index ed57ce12d4d1cc632431a654cad648a8015402b1..45269115e63cfc3bd7dc740a5694e2cc7c35bcb1 100644 +--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -66,6 +66,7 @@ public class MapItemSavedData extends SavedData { + public final Map decorations = Maps.newLinkedHashMap(); + private final Map frameMarkers = Maps.newHashMap(); + private int trackedDecorationCount; ++ private org.bukkit.craftbukkit.map.RenderData vanillaRender = new org.bukkit.craftbukkit.map.RenderData(); // Paper + + // CraftBukkit start + public final CraftMapView mapView; +@@ -92,6 +93,7 @@ public class MapItemSavedData extends SavedData { + // CraftBukkit start + this.mapView = new CraftMapView(this); + this.server = (CraftServer) org.bukkit.Bukkit.getServer(); ++ this.vanillaRender.buffer = colors; // Paper + // CraftBukkit end + } + +@@ -166,6 +168,7 @@ public class MapItemSavedData extends SavedData { + if (abyte.length == 16384) { + worldmap.colors = abyte; + } ++ worldmap.vanillaRender.buffer = abyte; // Paper + + ListTag nbttaglist = nbt.getList("banners", 10); + +@@ -578,6 +581,21 @@ public class MapItemSavedData extends SavedData { + + public class HoldingPlayer { + ++ // Paper start ++ private void addSeenPlayers(java.util.Collection icons) { ++ org.bukkit.entity.Player player = (org.bukkit.entity.Player) this.player.getBukkitEntity(); ++ MapItemSavedData.this.decorations.forEach((name, mapIcon) -> { ++ // If this cursor is for a player check visibility with vanish system ++ org.bukkit.entity.Player other = org.bukkit.Bukkit.getPlayerExact(name); // Spigot ++ if (other == null || player.canSee(other)) { ++ icons.add(mapIcon); ++ } ++ }); ++ } ++ private boolean shouldUseVanillaMap() { ++ return mapView.getRenderers().size() == 1 && mapView.getRenderers().get(0).getClass() == org.bukkit.craftbukkit.map.CraftMapRenderer.class; ++ } ++ // Paper end + public final Player player; + private boolean dirtyData = true; + private int minDirtyX; +@@ -611,7 +629,9 @@ public class MapItemSavedData extends SavedData { + @Nullable + Packet nextUpdatePacket(int mapId) { + MapItemSavedData.MapPatch worldmap_b; +- org.bukkit.craftbukkit.map.RenderData render = MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()); // CraftBukkit ++ if (!this.dirtyData && this.tick % 5 != 0) { this.tick++; return null; } // Paper - this won't end up sending, so don't render it! ++ boolean vanillaMaps = shouldUseVanillaMap(); // Paper ++ org.bukkit.craftbukkit.map.RenderData render = !vanillaMaps ? MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()) : MapItemSavedData.this.vanillaRender; // CraftBukkit // Paper + + if (this.dirtyData) { + this.dirtyData = false; +@@ -627,6 +647,8 @@ public class MapItemSavedData extends SavedData { + // CraftBukkit start + java.util.Collection icons = new java.util.ArrayList(); + ++ if (vanillaMaps) addSeenPlayers(icons); // Paper ++ + for (org.bukkit.map.MapCursor cursor : render.cursors) { + if (cursor.isVisible()) { + icons.add(new MapDecoration(MapDecoration.Type.byIcon(cursor.getRawType()), cursor.getX(), cursor.getY(), cursor.getDirection(), PaperAdventure.asVanilla(cursor.caption()))); // Paper - Adventure +diff --git a/src/main/java/org/bukkit/craftbukkit/map/RenderData.java b/src/main/java/org/bukkit/craftbukkit/map/RenderData.java +index 256a131781721c86dd6cdbc329335964570cbe8c..5768cd512ec166f1e8d1f4a28792015347297c3f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/map/RenderData.java ++++ b/src/main/java/org/bukkit/craftbukkit/map/RenderData.java +@@ -5,7 +5,7 @@ import org.bukkit.map.MapCursor; + + public class RenderData { + +- public final byte[] buffer; ++ public byte[] buffer; // Paper + public final ArrayList cursors; + + public RenderData() { diff --git a/patches/server/0989-Rewrite-dataconverter-system.patch b/patches/server/0989-Rewrite-dataconverter-system.patch deleted file mode 100644 index ef1dfaa9bd29..000000000000 --- a/patches/server/0989-Rewrite-dataconverter-system.patch +++ /dev/null @@ -1,24963 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 19 Jun 2021 10:43:01 -0700 -Subject: [PATCH] Rewrite dataconverter system - -Please see https://github.com/PaperMC/DataConverter -for details. - -diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java b/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1863c606be715683d53863a0c9293525d199c9cf ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java -@@ -0,0 +1,54 @@ -+package ca.spottedleaf.dataconverter.converters; -+ -+import java.util.Comparator; -+ -+public abstract class DataConverter { -+ -+ public static final Comparator> LOWEST_VERSION_COMPARATOR = (x, y) -> { -+ return Long.compare(x.getEncodedVersion(), y.getEncodedVersion()); -+ }; -+ -+ protected final int toVersion; -+ protected final int versionStep; -+ -+ public DataConverter(final int toVersion) { -+ this.toVersion = toVersion; -+ this.versionStep = 0; -+ } -+ -+ public DataConverter(final int toVersion, final int versionStep) { -+ this.toVersion = toVersion; -+ this.versionStep = versionStep; -+ } -+ -+ public final int getToVersion() { -+ return this.toVersion; -+ } -+ -+ public final int getVersionStep() { -+ return this.versionStep; -+ } -+ -+ public final long getEncodedVersion() { -+ return encodeVersions(this.toVersion, this.versionStep); -+ } -+ -+ public abstract R convert(final T data, final long sourceVersion, final long toVersion); -+ -+ // step must be in the lower bits, so that encodeVersions(version, step) < encodeVersions(version, step + 1) -+ public static long encodeVersions(final int version, final int step) { -+ return ((long)version << 32) | (step & 0xFFFFFFFFL); -+ } -+ -+ public static int getVersion(final long encoded) { -+ return (int)(encoded >>> 32); -+ } -+ -+ public static int getStep(final long encoded) { -+ return (int)encoded; -+ } -+ -+ public static String encodedToString(final long encoded) { -+ return getVersion(encoded) + "." + getStep(encoded); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0b92c5c66ad3a5198873f98287a5ced71c231d09 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java -@@ -0,0 +1,9 @@ -+package ca.spottedleaf.dataconverter.converters.datatypes; -+ -+public interface DataHook { -+ -+ public R preHook(final T data, final long fromVersion, final long toVersion); -+ -+ public R postHook(final T data, final long fromVersion, final long toVersion); -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b56a7f9ace3b947fed49101b6e9936721fb99ea5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java -@@ -0,0 +1,7 @@ -+package ca.spottedleaf.dataconverter.converters.datatypes; -+ -+public abstract class DataType { -+ -+ public abstract R convert(final T data, final long fromVersion, final long toVersion); -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cf9fae4451ead4860343b915fb70e3a7cdf0de31 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java -@@ -0,0 +1,9 @@ -+package ca.spottedleaf.dataconverter.converters.datatypes; -+ -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public interface DataWalker { -+ -+ public MapType walk(final MapType data, final long fromVersion, final long toVersion); -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dde9d36bf6212196caa18f3c9c535aec330a33ed ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java -@@ -0,0 +1,79 @@ -+package ca.spottedleaf.dataconverter.minecraft; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataType; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCDataType; -+import ca.spottedleaf.dataconverter.types.json.JsonMapType; -+import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; -+import com.google.gson.JsonObject; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import net.minecraft.nbt.CompoundTag; -+ -+public final class MCDataConverter { -+ -+ private static final LongArrayList BREAKPOINTS = MCVersionRegistry.getBreakpoints(); -+ -+ public static T copy(final T type) { -+ if (type instanceof CompoundTag) { -+ return (T)((CompoundTag)type).copy(); -+ } else if (type instanceof JsonObject) { -+ return (T)((JsonObject)type).deepCopy(); -+ } -+ -+ return type; -+ } -+ -+ public static CompoundTag convertTag(final MCDataType type, final CompoundTag data, final int fromVersion, final int toVersion) { -+ final NBTMapType wrapped = new NBTMapType(data); -+ -+ final NBTMapType replaced = (NBTMapType)convert(type, wrapped, fromVersion, toVersion); -+ -+ return replaced == null ? wrapped.getTag() : replaced.getTag(); -+ } -+ -+ public static JsonObject convertJson(final MCDataType type, final JsonObject data, final boolean compressed, final int fromVersion, final int toVersion) { -+ final JsonMapType wrapped = new JsonMapType(data, compressed); -+ -+ final JsonMapType replaced = (JsonMapType)convert(type, wrapped, fromVersion, toVersion); -+ -+ return replaced == null ? wrapped.getJson() : replaced.getJson(); -+ } -+ -+ public static R convert(final DataType type, final T data, int fromVersion, final int toVersion) { -+ Object ret = data; -+ -+ long currentVersion = DataConverter.encodeVersions(fromVersion < 99 ? 99 : fromVersion, Integer.MAX_VALUE); -+ final long nextVersion = DataConverter.encodeVersions(toVersion, Integer.MAX_VALUE); -+ -+ for (int i = 0, len = BREAKPOINTS.size(); i < len; ++i) { -+ final long breakpoint = BREAKPOINTS.getLong(i); -+ -+ if (currentVersion >= breakpoint) { -+ continue; -+ } -+ -+ final Object converted = type.convert((T)ret, currentVersion, Math.min(nextVersion, breakpoint - 1)); -+ if (converted != null) { -+ ret = converted; -+ } -+ -+ currentVersion = Math.min(nextVersion, breakpoint - 1); -+ -+ if (currentVersion == nextVersion) { -+ break; -+ } -+ } -+ -+ if (currentVersion != nextVersion) { -+ final Object converted = type.convert((T)ret, currentVersion, nextVersion); -+ if (converted != null) { -+ ret = converted; -+ } -+ } -+ -+ return (R)ret; -+ } -+ -+ private MCDataConverter() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4a392b3d53e330bf22100d57aec7ee1755e80a11 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java -@@ -0,0 +1,388 @@ -+package ca.spottedleaf.dataconverter.minecraft; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import com.mojang.logging.LogUtils; -+import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.ints.IntArrayList; -+import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet; -+import it.unimi.dsi.fastutil.ints.IntRBTreeSet; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import it.unimi.dsi.fastutil.longs.LongComparator; -+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; -+import org.slf4j.Logger; -+import java.lang.reflect.Field; -+import java.util.Arrays; -+import java.util.Locale; -+ -+public final class MCVersionRegistry { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final Int2ObjectLinkedOpenHashMap VERSION_NAMES = new Int2ObjectLinkedOpenHashMap<>(); -+ protected static final IntArrayList VERSION_LIST; -+ protected static final LongArrayList DATA_VERSION_LIST; -+ -+ protected static final IntArrayList DATACONVERTER_VERSIONS_LIST; -+ protected static final IntLinkedOpenHashSet DATACONVERTER_VERSIONS_MAJOR = new IntLinkedOpenHashSet(); -+ protected static final LongLinkedOpenHashSet DATACONVERTER_VERSIONS = new LongLinkedOpenHashSet(); -+ protected static final Int2ObjectLinkedOpenHashMap SUBVERSIONS = new Int2ObjectLinkedOpenHashMap<>(); -+ protected static final LongArrayList BREAKPOINTS = new LongArrayList(); -+ static { -+ // Note: Some of these are nameless. -+ // Unless a data version is specified here, it will NOT have converters ran for it. Please add them on update! -+ final int[] converterVersions = new int[] { -+ 99, -+ 100, -+ 101, -+ 102, -+ 105, -+ 106, -+ 107, -+ 108, -+ 109, -+ 110, -+ 111, -+ 113, -+ 135, -+ 143, -+ 147, -+ 165, -+ 501, -+ 502, -+ 505, -+ 700, -+ 701, -+ 702, -+ 703, -+ 704, -+ 705, -+ 804, -+ 806, -+ 808, -+ 808, -+ 813, -+ 816, -+ 820, -+ 1022, -+ 1125, -+ 1344, -+ 1446, -+ 1450, -+ 1451, -+ 1451, -+ 1451, -+ 1451, -+ 1451, -+ 1451, -+ 1451, -+ 1451, -+ 1451, -+ 1456, -+ 1458, -+ 1460, -+ 1466, -+ 1470, -+ 1474, -+ 1475, -+ 1480, -+ 1481, -+ 1483, -+ 1484, -+ 1486, -+ 1487, -+ 1488, -+ 1490, -+ 1492, -+ 1494, -+ 1496, -+ 1500, -+ 1501, -+ 1502, -+ 1506, -+ 1510, -+ 1514, -+ 1515, -+ 1624, -+ 1800, -+ 1801, -+ 1802, -+ 1803, -+ 1904, -+ 1905, -+ 1906, -+ 1909, -+ 1911, -+ 1914, -+ 1917, -+ 1918, -+ 1920, -+ 1925, -+ 1928, -+ 1929, -+ 1931, -+ 1936, -+ 1946, -+ 1948, -+ 1953, -+ 1955, -+ 1961, -+ 1963, -+ 2100, -+ 2202, -+ 2209, -+ 2211, -+ 2218, -+ 2501, -+ 2502, -+ 2503, -+ 2505, -+ 2508, -+ 2509, -+ 2511, -+ 2514, -+ 2516, -+ 2518, -+ 2519, -+ 2522, -+ 2523, -+ 2527, -+ 2528, -+ 2529, -+ 2531, -+ 2533, -+ 2535, -+ 2538, -+ 2550, -+ 2551, -+ 2552, -+ 2553, -+ 2558, -+ 2568, -+ 2671, -+ 2679, -+ 2680, -+ 2684, -+ 2686, -+ 2688, -+ 2690, -+ 2691, -+ 2693, -+ 2696, -+ 2700, -+ 2701, -+ 2702, -+ 2704, -+ 2707, -+ 2710, -+ 2717, -+ 2825, -+ 2831, -+ 2832, -+ 2833, -+ 2838, -+ 2841, -+ 2842, -+ 2843, -+ 2846, -+ 2852, -+ 2967, -+ 2970, -+ 3077, -+ 3078, -+ 3081, -+ 3082, -+ 3083, -+ 3084, -+ 3086, -+ 3087, -+ 3088, -+ 3090, -+ 3093, -+ 3094, -+ 3097, -+ 3108, -+ 3201, -+ 3203, -+ 3204, -+ 3209, -+ 3214, -+ 3319, -+ 3322, -+ 3438, -+ 3439, -+ 3440, -+ 3441, -+ 3447, -+ 3448, -+ 3450, -+ 3451, -+ 3459, -+ 3564, -+ 3565, -+ 3566, -+ 3568, -+ 3683, -+ 3685, -+ 3692, -+ // All up to 1.20.3 -+ }; -+ Arrays.sort(converterVersions); -+ -+ DATACONVERTER_VERSIONS_MAJOR.addAll(DATACONVERTER_VERSIONS_LIST = new IntArrayList(converterVersions)); -+ -+ // add sub versions -+ registerSubVersion(MCVersions.V16W38A + 1, 1); -+ -+ registerSubVersion(MCVersions.V17W47A, 1); -+ registerSubVersion(MCVersions.V17W47A, 2); -+ registerSubVersion(MCVersions.V17W47A, 3); -+ registerSubVersion(MCVersions.V17W47A, 4); -+ registerSubVersion(MCVersions.V17W47A, 5); -+ registerSubVersion(MCVersions.V17W47A, 6); -+ registerSubVersion(MCVersions.V17W47A, 7); -+ -+ // register breakpoints here -+ // for all major releases after 1.16, add them. this reduces the work required to determine if a breakpoint -+ // is needed for new converters -+ -+ // Too much changed in this version. -+ registerBreakpoint(MCVersions.V17W47A); -+ registerBreakpoint(MCVersions.V17W47A, Integer.MAX_VALUE); -+ -+ // final release of major version -+ registerBreakpoint(MCVersions.V1_17_1, Integer.MAX_VALUE); -+ -+ // final release of major version -+ registerBreakpoint(MCVersions.V1_18_2, Integer.MAX_VALUE); -+ -+ // final release of major version -+ registerBreakpoint(MCVersions.V1_19_4, Integer.MAX_VALUE); -+ } -+ -+ static { -+ final Field[] fields = MCVersions.class.getDeclaredFields(); -+ for (final Field field : fields) { -+ final String name = field.getName(); -+ final int value; -+ try { -+ value = field.getInt(null); -+ } catch (final Exception ex) { -+ throw new RuntimeException(ex); -+ } -+ -+ if (VERSION_NAMES.containsKey(value) && value != MCVersions.V15W33B) { // Mojang registered 15w33a and 15w33b under the same id. -+ LOGGER.warn("Error registering version \"" + name + "\", version number '" + value + "' is already associated with \"" + VERSION_NAMES.get(value) + "\""); -+ } -+ -+ VERSION_NAMES.put(value, name.substring(1).replace("_PRE", "-PRE").replace("_RC", "-RC").replace('_', '.').toLowerCase(Locale.ROOT)); -+ } -+ -+ for (final int version : DATACONVERTER_VERSIONS_MAJOR) { -+ if (VERSION_NAMES.containsKey(version)) { -+ continue; -+ } -+ -+ // find closest greatest version above this one -+ int closest = Integer.MAX_VALUE; -+ String closestName = null; -+ for (final int v : VERSION_NAMES.keySet()) { -+ if (v > version && v < closest) { -+ closest = v; -+ closestName = VERSION_NAMES.get(v); -+ } -+ } -+ -+ if (closestName == null) { -+ VERSION_NAMES.put(version, "unregistered_v" + version); -+ } else { -+ VERSION_NAMES.put(version, closestName + "-dev" + (closest - version)); -+ } -+ } -+ -+ // Explicit override for V99, as 99 is very special. -+ VERSION_NAMES.put(99, "pre_converter"); -+ -+ VERSION_LIST = new IntArrayList(new IntRBTreeSet(VERSION_NAMES.keySet())); -+ -+ DATA_VERSION_LIST = new LongArrayList(); -+ for (final int version : VERSION_LIST) { -+ DATA_VERSION_LIST.add(DataConverter.encodeVersions(version, 0)); -+ -+ final IntArrayList subVersions = SUBVERSIONS.get(version); -+ if (subVersions == null) { -+ continue; -+ } -+ -+ for (final int step : subVersions) { -+ DATA_VERSION_LIST.add(DataConverter.encodeVersions(version, step)); -+ } -+ } -+ -+ DATA_VERSION_LIST.sort((LongComparator)null); -+ -+ for (final int version : DATACONVERTER_VERSIONS_MAJOR) { -+ DATACONVERTER_VERSIONS.add(DataConverter.encodeVersions(version, 0)); -+ -+ final IntArrayList subVersions = SUBVERSIONS.get(version); -+ if (subVersions == null) { -+ continue; -+ } -+ -+ for (final int step : subVersions) { -+ DATACONVERTER_VERSIONS.add(DataConverter.encodeVersions(version, step)); -+ } -+ } -+ } -+ -+ private static void registerSubVersion(final int version, final int step) { -+ if (DATA_VERSION_LIST != null) { -+ throw new IllegalStateException("Added too late!"); -+ } -+ SUBVERSIONS.computeIfAbsent(version, (final int keyInMap) -> { -+ return new IntArrayList(); -+ }).add(step); -+ } -+ -+ private static void registerBreakpoint(final int version) { -+ registerBreakpoint(version, 0); -+ } -+ -+ private static void registerBreakpoint(final int version, final int step) { -+ BREAKPOINTS.add(DataConverter.encodeVersions(version, step)); -+ } -+ -+ // returns only versions that have dataconverters -+ public static boolean hasDataConverters(final int version) { -+ return DATACONVERTER_VERSIONS_MAJOR.contains(version); -+ } -+ -+ public String getVersionName(final int version) { -+ return VERSION_NAMES.get(version); -+ } -+ -+ public boolean isRegisteredVersion(final int version) { -+ return VERSION_NAMES.containsKey(version); -+ } -+ -+ public static IntArrayList getVersionList() { -+ return VERSION_LIST; -+ } -+ -+ public static LongArrayList getDataVersionList() { -+ return DATA_VERSION_LIST; -+ } -+ -+ public static int getMaxVersion() { -+ return VERSION_LIST.getInt(VERSION_LIST.size() - 1); -+ } -+ -+ public static LongArrayList getBreakpoints() { -+ return BREAKPOINTS; -+ } -+ -+ public static void checkVersion(final long version) { -+ if (!DATACONVERTER_VERSIONS.contains(version)) { -+ throw new IllegalStateException("Version " + DataConverter.encodedToString(version) + " is not registered to have dataconverters, yet has a dataconverter"); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9b92547b72c0e188293fcc0c7b8ad1e133520c70 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java -@@ -0,0 +1,499 @@ -+package ca.spottedleaf.dataconverter.minecraft; -+ -+@SuppressWarnings("unused") -+public final class MCVersions { -+ -+ /* https://minecraft.wiki/wiki/Data_version */ -+ -+ public static final int V15W32A = 100; -+ public static final int V15W32B = 103; -+ public static final int V15W32C = 104; -+ public static final int V15W33A = 111; -+ public static final int V15W33B = 111; -+ public static final int V15W33C = 112; -+ public static final int V15W34A = 114; -+ public static final int V15W34B = 115; -+ public static final int V15W34C = 116; -+ public static final int V15W34D = 117; -+ public static final int V15W35A = 118; -+ public static final int V15W35B = 119; -+ public static final int V15W35C = 120; -+ public static final int V15W35D = 121; -+ public static final int V15W35E = 122; -+ public static final int V15W36A = 123; -+ public static final int V15W36B = 124; -+ public static final int V15W36C = 125; -+ public static final int V15W36D = 126; -+ public static final int V15W37A = 127; -+ public static final int V15W38A = 128; -+ public static final int V15W38B = 129; -+ public static final int V15W39A = 130; -+ public static final int V15W39B = 131; -+ public static final int V15W39C = 132; -+ public static final int V15W40A = 133; -+ public static final int V15W40B = 134; -+ public static final int V15W41A = 136; -+ public static final int V15W41B = 137; -+ public static final int V15W42A = 138; -+ public static final int V15W43A = 139; -+ public static final int V15W43B = 140; -+ public static final int V15W43C = 141; -+ public static final int V15W44A = 142; -+ public static final int V15W44B = 143; -+ public static final int V15W45A = 145; -+ public static final int V15W46A = 146; -+ public static final int V15W47A = 148; -+ public static final int V15W47B = 149; -+ public static final int V15W47C = 150; -+ public static final int V15W49A = 151; -+ public static final int V15W49B = 152; -+ public static final int V15W50A = 153; -+ public static final int V15W51A = 154; -+ public static final int V15W51B = 155; -+ public static final int V16W02A = 156; -+ public static final int V16W03A = 157; -+ public static final int V16W04A = 158; -+ public static final int V16W05A = 159; -+ public static final int V16W05B = 160; -+ public static final int V16W06A = 161; -+ public static final int V16W07A = 162; -+ public static final int V16W07B = 163; -+ public static final int V1_9_PRE1 = 164; -+ public static final int V1_9_PRE2 = 165; -+ public static final int V1_9_PRE3 = 167; -+ public static final int V1_9_PRE4 = 168; -+ public static final int V1_9 = 169; -+ public static final int V1_9_1_PRE1 = 170; -+ public static final int V1_9_1_PRE2 = 171; -+ public static final int V1_9_1_PRE3 = 172; -+ public static final int V1_9_1 = 175; -+ public static final int V1_9_2 = 176; -+ public static final int V16W14A = 177; -+ public static final int V16W15A = 178; -+ public static final int V16W15B = 179; -+ public static final int V1_9_3_PRE1 = 180; -+ public static final int V1_9_3_PRE2 = 181; -+ public static final int V1_9_3_PRE3 = 182; -+ public static final int V1_9_3 = 183; -+ public static final int V1_9_4 = 184; -+ public static final int V16W20A = 501; -+ public static final int V16W21A = 503; -+ public static final int V16W21B = 504; -+ public static final int V1_10_PRE1 = 506; -+ public static final int V1_10_PRE2 = 507; -+ public static final int V1_10 = 510; -+ public static final int V1_10_1 = 511; -+ public static final int V1_10_2 = 512; -+ public static final int V16W32A = 800; -+ public static final int V16W32B = 801; -+ public static final int V16W33A = 802; -+ public static final int V16W35A = 803; -+ public static final int V16W36A = 805; -+ public static final int V16W38A = 807; -+ public static final int V16W39A = 809; -+ public static final int V16W39B = 811; -+ public static final int V16W39C = 812; -+ public static final int V16W40A = 813; -+ public static final int V16W41A = 814; -+ public static final int V16W42A = 815; -+ public static final int V16W43A = 816; -+ public static final int V16W44A = 817; -+ public static final int V1_11_PRE1 = 818; -+ public static final int V1_11 = 819; -+ public static final int V16W50A = 920; -+ public static final int V1_11_1 = 921; -+ public static final int V1_11_2 = 922; -+ public static final int V17W06A = 1022; -+ public static final int V17W13A = 1122; -+ public static final int V17W13B = 1123; -+ public static final int V17W14A = 1124; -+ public static final int V17W15A = 1125; -+ public static final int V17W16A = 1126; -+ public static final int V17W16B = 1127; -+ public static final int V17W17A = 1128; -+ public static final int V17W17B = 1129; -+ public static final int V17W18A = 1130; -+ public static final int V17W18B = 1131; -+ public static final int V1_12_PRE1 = 1132; -+ public static final int V1_12_PRE2 = 1133; -+ public static final int V1_12_PRE3 = 1134; -+ public static final int V1_12_PRE4 = 1135; -+ public static final int V1_12_PRE5 = 1136; -+ public static final int V1_12_PRE6 = 1137; -+ public static final int V1_12_PRE7 = 1138; -+ public static final int V1_12 = 1139; -+ public static final int V17W31A = 1239; -+ public static final int V1_12_1_PRE1 = 1240; -+ public static final int V1_12_1 = 1241; -+ public static final int V1_12_2_PRE1 = 1341; -+ public static final int V1_12_2_PRE2 = 1342; -+ public static final int V1_12_2 = 1343; -+ public static final int V17W43A = 1444; -+ public static final int V17W43B = 1445; -+ public static final int V17W45A = 1447; -+ public static final int V17W45B = 1448; -+ public static final int V17W46A = 1449; -+ public static final int V17W47A = 1451; -+ public static final int V17W47B = 1452; -+ public static final int V17W48A = 1453; -+ public static final int V17W49A = 1454; -+ public static final int V17W49B = 1455; -+ public static final int V17W50A = 1457; -+ public static final int V18W01A = 1459; -+ public static final int V18W02A = 1461; -+ public static final int V18W03A = 1462; -+ public static final int V18W03B = 1463; -+ public static final int V18W05A = 1464; -+ public static final int V18W06A = 1466; -+ public static final int V18W07A = 1467; -+ public static final int V18W07B = 1468; -+ public static final int V18W07C = 1469; -+ public static final int V18W08A = 1470; -+ public static final int V18W08B = 1471; -+ public static final int V18W09A = 1472; -+ public static final int V18W10A = 1473; -+ public static final int V18W10B = 1474; -+ public static final int V18W10C = 1476; -+ public static final int V18W10D = 1477; -+ public static final int V18W11A = 1478; -+ public static final int V18W14A = 1479; -+ public static final int V18W14B = 1481; -+ public static final int V18W15A = 1482; -+ public static final int V18W16A = 1483; -+ public static final int V18W19A = 1484; -+ public static final int V18W19B = 1485; -+ public static final int V18W20A = 1489; -+ public static final int V18W20B = 1491; -+ public static final int V18W20C = 1493; -+ public static final int V18W21A = 1495; -+ public static final int V18W21B = 1496; -+ public static final int V18W22A = 1497; -+ public static final int V18W22B = 1498; -+ public static final int V18W22C = 1499; -+ public static final int V1_13_PRE1 = 1501; -+ public static final int V1_13_PRE2 = 1502; -+ public static final int V1_13_PRE3 = 1503; -+ public static final int V1_13_PRE4 = 1504; -+ public static final int V1_13_PRE5 = 1511; -+ public static final int V1_13_PRE6 = 1512; -+ public static final int V1_13_PRE7 = 1513; -+ public static final int V1_13_PRE8 = 1516; -+ public static final int V1_13_PRE9 = 1517; -+ public static final int V1_13_PRE10 = 1518; -+ public static final int V1_13 = 1519; -+ public static final int V18W30A = 1620; -+ public static final int V18W30B = 1621; -+ public static final int V18W31A = 1622; -+ public static final int V18W32A = 1623; -+ public static final int V18W33A = 1625; -+ public static final int V1_13_1_PRE1 = 1626; -+ public static final int V1_13_1_PRE2 = 1627; -+ public static final int V1_13_1 = 1628; -+ public static final int V1_13_2_PRE1 = 1629; -+ public static final int V1_13_2_PRE2 = 1630; -+ public static final int V1_13_2 = 1631; -+ public static final int V18W43A = 1901; -+ public static final int V18W43B = 1902; -+ public static final int V18W43C = 1903; -+ public static final int V18W44A = 1907; -+ public static final int V18W45A = 1908; -+ public static final int V18W46A = 1910; -+ public static final int V18W47A = 1912; -+ public static final int V18W47B = 1913; -+ public static final int V18W48A = 1914; -+ public static final int V18W48B = 1915; -+ public static final int V18W49A = 1916; -+ public static final int V18W50A = 1919; -+ public static final int V19W02A = 1921; -+ public static final int V19W03A = 1922; -+ public static final int V19W03B = 1923; -+ public static final int V19W03C = 1924; -+ public static final int V19W04A = 1926; -+ public static final int V19W04B = 1927; -+ public static final int V19W05A = 1930; -+ public static final int V19W06A = 1931; -+ public static final int V19W07A = 1932; -+ public static final int V19W08A = 1933; -+ public static final int V19W08B = 1934; -+ public static final int V19W09A = 1935; -+ public static final int V19W11A = 1937; -+ public static final int V19W11B = 1938; -+ public static final int V19W12A = 1940; -+ public static final int V19W12B = 1941; -+ public static final int V19W13A = 1942; -+ public static final int V19W13B = 1943; -+ public static final int V19W14A = 1944; -+ public static final int V19W14B = 1945; -+ public static final int V1_14_PRE1 = 1947; -+ public static final int V1_14_PRE2 = 1948; -+ public static final int V1_14_PRE3 = 1949; -+ public static final int V1_14_PRE4 = 1950; -+ public static final int V1_14_PRE5 = 1951; -+ public static final int V1_14 = 1952; -+ public static final int V1_14_1_PRE1 = 1955; -+ public static final int V1_14_1_PRE2 = 1956; -+ public static final int V1_14_1 = 1957; -+ public static final int V1_14_2_PRE1 = 1958; -+ public static final int V1_14_2_PRE2 = 1959; -+ public static final int V1_14_2_PRE3 = 1960; -+ public static final int V1_14_2_PRE4 = 1962; -+ public static final int V1_14_2 = 1963; -+ public static final int V1_14_3_PRE1 = 1964; -+ public static final int V1_14_3_PRE2 = 1965; -+ public static final int V1_14_3_PRE3 = 1966; -+ public static final int V1_14_3_PRE4 = 1967; -+ public static final int V1_14_3 = 1968; -+ public static final int V1_14_4_PRE1 = 1969; -+ public static final int V1_14_4_PRE2 = 1970; -+ public static final int V1_14_4_PRE3 = 1971; -+ public static final int V1_14_4_PRE4 = 1972; -+ public static final int V1_14_4_PRE5 = 1973; -+ public static final int V1_14_4_PRE6 = 1974; -+ public static final int V1_14_4_PRE7 = 1975; -+ public static final int V1_14_4 = 1976; -+ public static final int V19W34A = 2200; -+ public static final int V19W35A = 2201; -+ public static final int V19W36A = 2203; -+ public static final int V19W37A = 2204; -+ public static final int V19W38A = 2205; -+ public static final int V19W38B = 2206; -+ public static final int V19W39A = 2207; -+ public static final int V19W40A = 2208; -+ public static final int V19W41A = 2210; -+ public static final int V19W42A = 2212; -+ public static final int V19W44A = 2213; -+ public static final int V19W45A = 2214; -+ public static final int V19W45B = 2215; -+ public static final int V19W46A = 2216; -+ public static final int V19W46B = 2217; -+ public static final int V1_15_PRE1 = 2218; -+ public static final int V1_15_PRE2 = 2219; -+ public static final int V1_15_PRE3 = 2220; -+ public static final int V1_15_PRE4 = 2221; -+ public static final int V1_15_PRE5 = 2222; -+ public static final int V1_15_PRE6 = 2223; -+ public static final int V1_15_PRE7 = 2224; -+ public static final int V1_15 = 2225; -+ public static final int V1_15_1_PRE1 = 2226; -+ public static final int V1_15_1 = 2227; -+ public static final int V1_15_2_PRE1 = 2228; -+ public static final int V1_15_2_PRE2 = 2229; -+ public static final int V1_15_2 = 2230; -+ public static final int V20W06A = 2504; -+ public static final int V20W07A = 2506; -+ public static final int V20W08A = 2507; -+ public static final int V20W09A = 2510; -+ public static final int V20W10A = 2512; -+ public static final int V20W11A = 2513; -+ public static final int V20W12A = 2515; -+ public static final int V20W13A = 2520; -+ public static final int V20W13B = 2521; -+ public static final int V20W14A = 2524; -+ public static final int V20W15A = 2525; -+ public static final int V20W16A = 2526; -+ public static final int V20W17A = 2529; -+ public static final int V20W18A = 2532; -+ public static final int V20W19A = 2534; -+ public static final int V20W20A = 2536; -+ public static final int V20W20B = 2537; -+ public static final int V20W21A = 2554; -+ public static final int V20W22A = 2555; -+ public static final int V1_16_PRE1 = 2556; -+ public static final int V1_16_PRE2 = 2557; -+ public static final int V1_16_PRE3 = 2559; -+ public static final int V1_16_PRE4 = 2560; -+ public static final int V1_16_PRE5 = 2561; -+ public static final int V1_16_PRE6 = 2562; -+ public static final int V1_16_PRE7 = 2563; -+ public static final int V1_16_PRE8 = 2564; -+ public static final int V1_16_RC1 = 2565; -+ public static final int V1_16 = 2566; -+ public static final int V1_16_1 = 2567; -+ public static final int V20W27A = 2569; -+ public static final int V20W28A = 2570; -+ public static final int V20W29A = 2571; -+ public static final int V20W30A = 2572; -+ public static final int V1_16_2_PRE1 = 2573; -+ public static final int V1_16_2_PRE2 = 2574; -+ public static final int V1_16_2_PRE3 = 2575; -+ public static final int V1_16_2_RC1 = 2576; -+ public static final int V1_16_2_RC2 = 2577; -+ public static final int V1_16_2 = 2578; -+ public static final int V1_16_3_RC1 = 2579; -+ public static final int V1_16_3 = 2580; -+ public static final int V1_16_4_PRE1 = 2581; -+ public static final int V1_16_4_PRE2 = 2582; -+ public static final int V1_16_4_RC1 = 2583; -+ public static final int V1_16_4 = 2584; -+ public static final int V1_16_5_RC1 = 2585; -+ public static final int V1_16_5 = 2586; -+ public static final int V20W45A = 2681; -+ public static final int V20W46A = 2682; -+ public static final int V20W48A = 2683; -+ public static final int V20W49A = 2685; -+ public static final int V20W51A = 2687; -+ public static final int V21W03A = 2689; -+ public static final int V21W05A = 2690; -+ public static final int V21W05B = 2692; -+ public static final int V21W06A = 2694; -+ public static final int V21W07A = 2695; -+ public static final int V21W08A = 2697; -+ public static final int V21W08B = 2698; -+ public static final int V21W10A = 2699; -+ public static final int V21W11A = 2703; -+ public static final int V21W13A = 2705; -+ public static final int V21W14A = 2706; -+ public static final int V21W15A = 2709; -+ public static final int V21W16A = 2711; -+ public static final int V21W17A = 2712; -+ public static final int V21W18A = 2713; -+ public static final int V21W19A = 2714; -+ public static final int V21W20A = 2715; -+ public static final int V1_17_PRE1 = 2716; -+ public static final int V1_17_PRE2 = 2718; -+ public static final int V1_17_PRE3 = 2719; -+ public static final int V1_17_PRE4 = 2720; -+ public static final int V1_17_PRE5 = 2721; -+ public static final int V1_17_RC1 = 2722; -+ public static final int V1_17_RC2 = 2723; -+ public static final int V1_17 = 2724; -+ public static final int V1_17_1_PRE1 = 2725; -+ public static final int V1_17_1_PRE2 = 2726; -+ public static final int V1_17_1_PRE3 = 2727; -+ public static final int V1_17_1_RC1 = 2728; -+ public static final int V1_17_1_RC2 = 2729; -+ public static final int V1_17_1 = 2730; -+ public static final int V21W37A = 2834; -+ public static final int V21W38A = 2835; -+ public static final int V21W39A = 2836; -+ public static final int V21W40A = 2838; -+ public static final int V21W41A = 2839; -+ public static final int V21W42A = 2840; -+ public static final int V21W43A = 2844; -+ public static final int V21W44A = 2845; -+ public static final int V1_18_PRE1 = 2847; -+ public static final int V1_18_PRE2 = 2848; -+ public static final int V1_18_PRE3 = 2849; -+ public static final int V1_18_PRE4 = 2850; -+ public static final int V1_18_PRE5 = 2851; -+ public static final int V1_18_PRE6 = 2853; -+ public static final int V1_18_PRE7 = 2854; -+ public static final int V1_18_PRE8 = 2855; -+ public static final int V1_18_RC1 = 2856; -+ public static final int V1_18_RC2 = 2857; -+ public static final int V1_18_RC3 = 2858; -+ public static final int V1_18_RC4 = 2859; -+ public static final int V1_18 = 2860; -+ public static final int V1_18_1_PRE1 = 2861; -+ public static final int V1_18_1_RC1 = 2862; -+ public static final int V1_18_1_RC2 = 2863; -+ public static final int V1_18_1_RC3 = 2864; -+ public static final int V1_18_1 = 2865; -+ public static final int V22W03A = 2966; -+ public static final int V22W05A = 2967; -+ public static final int V22W06A = 2968; -+ public static final int V22W07A = 2969; -+ public static final int V1_18_2_PRE1 = 2971; -+ public static final int V1_18_2_PRE2 = 2972; -+ public static final int V1_18_2_PRE3 = 2973; -+ public static final int V1_18_2_RC1 = 2974; -+ public static final int V1_18_2 = 2975; -+ public static final int V22W11A = 3080; -+ public static final int V22W12A = 3082; -+ public static final int V22W13A = 3085; -+ public static final int V22W14A = 3088; -+ public static final int V22W15A = 3089; -+ public static final int V22W16A = 3091; -+ public static final int V22W16B = 3092; -+ public static final int V22W17A = 3093; -+ public static final int V22W18A = 3095; -+ public static final int V22W19A = 3096; -+ public static final int V1_19_PRE1 = 3098; -+ public static final int V1_19_PRE2 = 3099; -+ public static final int V1_19_PRE3 = 3100; -+ public static final int V1_19_PRE4 = 3101; -+ public static final int V1_19_PRE5 = 3102; -+ public static final int V1_19_RC1 = 3103; -+ public static final int V1_19_RC2 = 3104; -+ public static final int V1_19 = 3105; -+ public static final int V22W24A = 3106; -+ public static final int V1_19_1_PRE1 = 3107; -+ public static final int V1_19_1_RC1 = 3109; -+ public static final int V1_19_1_PRE2 = 3110; -+ public static final int V1_19_1_PRE3 = 3111; -+ public static final int V1_19_1_PRE4 = 3112; -+ public static final int V1_19_1_PRE5 = 3113; -+ public static final int V1_19_1_PRE6 = 3114; -+ public static final int V1_19_1_RC2 = 3115; -+ public static final int V1_19_1_RC3 = 3116; -+ public static final int V1_19_1 = 3117; -+ public static final int V1_19_2_RC1 = 3118; -+ public static final int V1_19_2_RC2 = 3119; -+ public static final int V1_19_2 = 3120; -+ public static final int V22W42A = 3205; -+ public static final int V22W43A = 3206; -+ public static final int V22W44A = 3207; -+ public static final int V22W45A = 3208; -+ public static final int V22W46A = 3210; -+ public static final int V1_19_3_PRE1 = 3211; -+ public static final int V1_19_3_PRE2 = 3212; -+ public static final int V1_19_3_PRE3 = 3213; -+ public static final int V1_19_3_RC1 = 3215; -+ public static final int V1_19_3 = 3218; -+ public static final int V23W03A = 3320; -+ public static final int V23W04A = 3321; -+ public static final int V23W05A = 3323; -+ public static final int V23W06A = 3326; -+ public static final int V23W07A = 3329; -+ public static final int V1_19_4_PRE1 = 3330; -+ public static final int V1_19_4_PRE2 = 3331; -+ public static final int V1_19_4_PRE3 = 3332; -+ public static final int V1_19_4_PRE4 = 3333; -+ public static final int V1_19_4_RC1 = 3334; -+ public static final int V1_19_4_RC2 = 3335; -+ public static final int V1_19_4_RC3 = 3336; -+ public static final int V1_19_4 = 3337; -+ public static final int V23W12A = 3442; -+ public static final int V23W13A = 3443; -+ public static final int V23W14A = 3445; -+ public static final int V23W16A = 3449; -+ public static final int V23W17A = 3452; -+ public static final int V23W19A = 3453; -+ public static final int V1_20_PRE1 = 3454; -+ public static final int V1_20_PRE2 = 3455; -+ public static final int V1_20_PRE3 = 3456; -+ public static final int V1_20_PRE4 = 3457; -+ public static final int V1_20_PRE5 = 3458; -+ public static final int V1_20_PRE6 = 3460; -+ public static final int V1_20_PRE7 = 3461; -+ public static final int V1_20_RC1 = 3462; -+ public static final int V1_20 = 3463; -+ public static final int V1_20_1_RC1 = 3464; -+ public static final int V1_20_1 = 3465; -+ public static final int V23W31A = 3567; -+ public static final int V23W32A = 3569; -+ public static final int V23W33A = 3570; -+ public static final int V23W35A = 3571; -+ public static final int V1_20_2_PRE1 = 3572; -+ public static final int V1_20_2_PRE2 = 3573; -+ public static final int V1_20_2_PRE3 = 3574; -+ public static final int V1_20_2_PRE4 = 3575; -+ public static final int V1_20_2_RC1 = 3576; -+ public static final int V1_20_2_RC2 = 3577; -+ public static final int V1_20_2 = 3578; -+ public static final int V23W40A = 3679; -+ public static final int V23W41A = 3681; -+ public static final int V23W42A = 3684; -+ public static final int V23W43A = 3686; -+ public static final int V23W43B = 3687; -+ public static final int V23W44A = 3688; -+ public static final int V23W45A = 3690; -+ public static final int V23W46A = 3691; -+ public static final int V1_20_3_PRE1 = 3693; -+ public static final int V1_20_3_PRE2 = 3694; -+ public static final int V1_20_3_PRE3 = 3695; -+ public static final int V1_20_3_PRE4 = 3696; -+ public static final int V1_20_3_RC1 = 3697; -+ public static final int V1_20_3 = 3698; -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ae3aed21c1fccb688e9a1665e2d317a77508d157 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java -@@ -0,0 +1,28 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.advancements; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.ArrayList; -+import java.util.function.Function; -+ -+public final class ConverterAbstractAdvancementsRename { -+ -+ private ConverterAbstractAdvancementsRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ RenameHelper.renameKeys(data, renamer); -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b2a4d16e6a2f9d71dbfa692922671581c2bec136 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java -@@ -0,0 +1,42 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.advancements; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.function.Function; -+ -+public final class ConverterCriteriaRename extends DataConverter, MapType> { -+ -+ public final String path; -+ public final Function conversion; -+ -+ public ConverterCriteriaRename(final int toVersion, final String path, final Function conversion) { -+ super(toVersion); -+ this.path = path; -+ this.conversion = conversion; -+ } -+ -+ public ConverterCriteriaRename(final int toVersion, final int versionStep, final String path, final Function conversion) { -+ super(toVersion, versionStep); -+ this.path = path; -+ this.conversion = conversion; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType advancement = data.getMap(this.path); -+ if (advancement == null) { -+ return null; -+ } -+ -+ final MapType criteria = advancement.getMap("criteria"); -+ if (criteria == null) { -+ return null; -+ } -+ -+ RenameHelper.renameKeys(criteria, this.conversion); -+ -+ return null; -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ba9daaab1abd53a3fbdebd78e05ba363251188c6 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java -@@ -0,0 +1,73 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.blockname; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.function.Function; -+ -+public final class ConverterAbstractBlockRename { -+ -+ private ConverterAbstractBlockRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.BLOCK_NAME, renamer); -+ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String name = data.getString("Name"); -+ if (name != null) { -+ final String converted = renamer.apply(name); -+ if (converted != null) { -+ data.setString("Name", converted); -+ } -+ } -+ return null; -+ } -+ }); -+ } -+ -+ public static void registerAndFixJigsaw(final int version, final Function renamer) { -+ registerAndFixJigsaw(version, 0, renamer); -+ } -+ -+ public static void registerAndFixJigsaw(final int version, final int subVersion, final Function renamer) { -+ register(version, subVersion, renamer); -+ // TODO check on update, minecraft:jigsaw can change -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jigsaw", new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String finalState = data.getString("final_state"); -+ if (finalState == null || finalState.isEmpty()) { -+ return null; -+ } -+ -+ final int nbtStart1 = finalState.indexOf('['); -+ final int nbtStart2 = finalState.indexOf('{'); -+ int stateNameEnd = finalState.length(); -+ if (nbtStart1 > 0) { -+ stateNameEnd = Math.min(stateNameEnd, nbtStart1); -+ } -+ -+ if (nbtStart2 > 0) { -+ stateNameEnd = Math.min(stateNameEnd, nbtStart2); -+ } -+ -+ final String blockStateName = finalState.substring(0, stateNameEnd); -+ final String converted = renamer.apply(blockStateName); -+ if (converted == null) { -+ return null; -+ } -+ -+ final String convertedState = converted.concat(finalState.substring(stateNameEnd)); -+ data.setString("final_state", convertedState); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d4cd5362e77eb71cb8eb45ffcc73185e01be1157 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterAddBlendingData.java -@@ -0,0 +1,65 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.chunk; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+import ca.spottedleaf.dataconverter.util.NamespaceUtil; -+import java.util.Arrays; -+import java.util.HashSet; -+import java.util.Set; -+ -+public final class ConverterAddBlendingData extends DataConverter, MapType> { -+ -+ private static final Set STATUSES_TO_SKIP_BLENDING = new HashSet<>( -+ Arrays.asList( -+ "minecraft:empty", -+ "minecraft:structure_starts", -+ "minecraft:structure_references", -+ "minecraft:biomes" -+ ) -+ ); -+ -+ public ConverterAddBlendingData(final int toVersion) { -+ super(toVersion); -+ } -+ -+ public ConverterAddBlendingData(final int toVersion, final int versionStep) { -+ super(toVersion, versionStep); -+ } -+ -+ private static MapType createBlendingData(final int height, final int minY) { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setInt("min_section", minY >> 4); -+ ret.setInt("max_section", (minY + height) >> 4); -+ -+ return ret; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.remove("blending_data"); -+ final MapType context = data.getMap("__context"); -+ if (!"minecraft:overworld".equals(context == null ? null : context.getString("dimension"))) { -+ return null; -+ } -+ -+ final String status = NamespaceUtil.correctNamespace(data.getString("Status")); -+ if (status == null) { -+ return null; -+ } -+ -+ final MapType belowZeroRetrogen = data.getMap("below_zero_retrogen"); -+ -+ if (!STATUSES_TO_SKIP_BLENDING.contains(status)) { -+ data.setMap("blending_data", createBlendingData(384, -64)); -+ } else if (belowZeroRetrogen != null) { -+ final String realStatus = NamespaceUtil.correctNamespace(belowZeroRetrogen.getString("target_status", "empty")); -+ if (!STATUSES_TO_SKIP_BLENDING.contains(realStatus)) { -+ data.setMap("blending_data", createBlendingData(256, 0)); -+ } -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java -new file mode 100644 -index 0000000000000000000000000000000000000000..300c2d14818b1e0cfe7341aba573ec75d0581b26 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java -@@ -0,0 +1,1016 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.chunk; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.mojang.datafixers.DataFixUtils; -+import com.mojang.logging.LogUtils; -+import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.ints.IntArrayList; -+import it.unimi.dsi.fastutil.ints.IntIterator; -+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; -+import net.minecraft.util.datafix.PackedBitStorage; -+import org.slf4j.Logger; -+import java.util.Arrays; -+import java.util.BitSet; -+import java.util.HashMap; -+import java.util.Iterator; -+import java.util.Map; -+import java.util.Objects; -+ -+import static it.unimi.dsi.fastutil.HashCommon.arraySize; -+ -+public final class ConverterFlattenChunk extends DataConverter, MapType> { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ static final BitSet VIRTUAL_SET = new BitSet(256); -+ static final BitSet IDS_NEEDING_FIX_SET = new BitSet(256); -+ -+ static { -+ IDS_NEEDING_FIX_SET.set(2); -+ IDS_NEEDING_FIX_SET.set(3); -+ IDS_NEEDING_FIX_SET.set(110); -+ IDS_NEEDING_FIX_SET.set(140); -+ IDS_NEEDING_FIX_SET.set(144); -+ IDS_NEEDING_FIX_SET.set(25); -+ IDS_NEEDING_FIX_SET.set(86); -+ IDS_NEEDING_FIX_SET.set(26); -+ IDS_NEEDING_FIX_SET.set(176); -+ IDS_NEEDING_FIX_SET.set(177); -+ IDS_NEEDING_FIX_SET.set(175); -+ IDS_NEEDING_FIX_SET.set(64); -+ IDS_NEEDING_FIX_SET.set(71); -+ IDS_NEEDING_FIX_SET.set(193); -+ IDS_NEEDING_FIX_SET.set(194); -+ IDS_NEEDING_FIX_SET.set(195); -+ IDS_NEEDING_FIX_SET.set(196); -+ IDS_NEEDING_FIX_SET.set(197); -+ -+ VIRTUAL_SET.set(54); -+ VIRTUAL_SET.set(146); -+ VIRTUAL_SET.set(25); -+ VIRTUAL_SET.set(26); -+ VIRTUAL_SET.set(51); -+ VIRTUAL_SET.set(53); -+ VIRTUAL_SET.set(67); -+ VIRTUAL_SET.set(108); -+ VIRTUAL_SET.set(109); -+ VIRTUAL_SET.set(114); -+ VIRTUAL_SET.set(128); -+ VIRTUAL_SET.set(134); -+ VIRTUAL_SET.set(135); -+ VIRTUAL_SET.set(136); -+ VIRTUAL_SET.set(156); -+ VIRTUAL_SET.set(163); -+ VIRTUAL_SET.set(164); -+ VIRTUAL_SET.set(180); -+ VIRTUAL_SET.set(203); -+ VIRTUAL_SET.set(55); -+ VIRTUAL_SET.set(85); -+ VIRTUAL_SET.set(113); -+ VIRTUAL_SET.set(188); -+ VIRTUAL_SET.set(189); -+ VIRTUAL_SET.set(190); -+ VIRTUAL_SET.set(191); -+ VIRTUAL_SET.set(192); -+ VIRTUAL_SET.set(93); -+ VIRTUAL_SET.set(94); -+ VIRTUAL_SET.set(101); -+ VIRTUAL_SET.set(102); -+ VIRTUAL_SET.set(160); -+ VIRTUAL_SET.set(106); -+ VIRTUAL_SET.set(107); -+ VIRTUAL_SET.set(183); -+ VIRTUAL_SET.set(184); -+ VIRTUAL_SET.set(185); -+ VIRTUAL_SET.set(186); -+ VIRTUAL_SET.set(187); -+ VIRTUAL_SET.set(132); -+ VIRTUAL_SET.set(139); -+ VIRTUAL_SET.set(199); -+ } -+ -+ static final boolean[] VIRTUAL = toBooleanArray(VIRTUAL_SET); -+ static final boolean[] IDS_NEEDING_FIX = toBooleanArray(IDS_NEEDING_FIX_SET); -+ -+ private static boolean[] toBooleanArray(final BitSet set) { -+ final boolean[] ret = new boolean[4096]; -+ for (int i = 0; i < 4096; ++i) { -+ ret[i] = set.get(i); -+ } -+ -+ return ret; -+ } -+ -+ static final MapType PUMPKIN = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:pumpkin'}"); -+ static final MapType SNOWY_PODZOL = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:podzol',Properties:{snowy:'true'}}"); -+ static final MapType SNOWY_GRASS = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:grass_block',Properties:{snowy:'true'}}"); -+ static final MapType SNOWY_MYCELIUM = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:mycelium',Properties:{snowy:'true'}}"); -+ static final MapType UPPER_SUNFLOWER = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:sunflower',Properties:{half:'upper'}}"); -+ static final MapType UPPER_LILAC = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:lilac',Properties:{half:'upper'}}"); -+ static final MapType UPPER_TALL_GRASS = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:tall_grass',Properties:{half:'upper'}}"); -+ static final MapType UPPER_LARGE_FERN = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:large_fern',Properties:{half:'upper'}}"); -+ static final MapType UPPER_ROSE_BUSH = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:rose_bush',Properties:{half:'upper'}}"); -+ static final MapType UPPER_PEONY = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:peony',Properties:{half:'upper'}}"); -+ -+ static final Map> FLOWER_POT_MAP = new HashMap<>(); -+ static { -+ FLOWER_POT_MAP.put("minecraft:air0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:flower_pot'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_poppy'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower1", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_blue_orchid'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower2", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_allium'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower3", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_azure_bluet'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower4", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_red_tulip'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower5", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_orange_tulip'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower6", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_white_tulip'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower7", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_pink_tulip'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower8", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_oxeye_daisy'}")); -+ FLOWER_POT_MAP.put("minecraft:yellow_flower0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_dandelion'}")); -+ FLOWER_POT_MAP.put("minecraft:sapling0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_oak_sapling'}")); -+ FLOWER_POT_MAP.put("minecraft:sapling1", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_spruce_sapling'}")); -+ FLOWER_POT_MAP.put("minecraft:sapling2", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_birch_sapling'}")); -+ FLOWER_POT_MAP.put("minecraft:sapling3", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_jungle_sapling'}")); -+ FLOWER_POT_MAP.put("minecraft:sapling4", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_acacia_sapling'}")); -+ FLOWER_POT_MAP.put("minecraft:sapling5", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_dark_oak_sapling'}")); -+ FLOWER_POT_MAP.put("minecraft:red_mushroom0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_red_mushroom'}")); -+ FLOWER_POT_MAP.put("minecraft:brown_mushroom0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_brown_mushroom'}")); -+ FLOWER_POT_MAP.put("minecraft:deadbush0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_dead_bush'}")); -+ FLOWER_POT_MAP.put("minecraft:tallgrass2", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_fern'}")); -+ FLOWER_POT_MAP.put("minecraft:cactus0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_cactus'}")); // we change default to empty -+ } -+ -+ static final Map> SKULL_MAP = new HashMap<>(); -+ static { -+ mapSkull(SKULL_MAP, 0, "skeleton", "skull"); -+ mapSkull(SKULL_MAP, 1, "wither_skeleton", "skull"); -+ mapSkull(SKULL_MAP, 2, "zombie", "head"); -+ mapSkull(SKULL_MAP, 3, "player", "head"); -+ mapSkull(SKULL_MAP, 4, "creeper", "head"); -+ mapSkull(SKULL_MAP, 5, "dragon", "head"); -+ }; -+ -+ private static void mapSkull(final Map> into, final int oldId, final String newId, final String skullType) { -+ into.put(oldId + "north", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'north'}}")); -+ into.put(oldId + "east", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'east'}}")); -+ into.put(oldId + "south", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'south'}}")); -+ into.put(oldId + "west", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'west'}}")); -+ -+ for (int rotation = 0; rotation < 16; ++rotation) { -+ into.put(oldId + "" + rotation, -+ HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_" + skullType + "',Properties:{rotation:'" + rotation + "'}}")); -+ } -+ } -+ -+ static final Map> DOOR_MAP = new HashMap<>(); -+ static { -+ mapDoor(DOOR_MAP, "oak_door", 1024); -+ mapDoor(DOOR_MAP, "iron_door", 1136); -+ mapDoor(DOOR_MAP, "spruce_door", 3088); -+ mapDoor(DOOR_MAP, "birch_door", 3104); -+ mapDoor(DOOR_MAP, "jungle_door", 3120); -+ mapDoor(DOOR_MAP, "acacia_door", 3136); -+ mapDoor(DOOR_MAP, "dark_oak_door", 3152); -+ }; -+ -+ private static void mapDoor(final Map> into, final String type, final int oldId) { -+ into.put("minecraft:" + type + "eastlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "eastlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "eastlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "eastlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "eastlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId))); -+ into.put("minecraft:" + type + "eastlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "eastlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 4))); -+ into.put("minecraft:" + type + "eastlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "eastupperleftfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 8))); -+ into.put("minecraft:" + type + "eastupperleftfalsetrue", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 10))); -+ into.put("minecraft:" + type + "eastupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "eastupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "eastupperrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 9))); -+ into.put("minecraft:" + type + "eastupperrightfalsetrue", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 11))); -+ into.put("minecraft:" + type + "eastupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "eastupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "northlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "northlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "northlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "northlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "northlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 3))); -+ into.put("minecraft:" + type + "northlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "northlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 7))); -+ into.put("minecraft:" + type + "northlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "northupperleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "northupperleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "northupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "northupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "northupperrightfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "northupperrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "northupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "northupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "southlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "southlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "southlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "southlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "southlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 1))); -+ into.put("minecraft:" + type + "southlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "southlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 5))); -+ into.put("minecraft:" + type + "southlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "southupperleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "southupperleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "southupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "southupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "southupperrightfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "southupperrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "southupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "southupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "westlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "westlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "westlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "westlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "westlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 2))); -+ into.put("minecraft:" + type + "westlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "westlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 6))); -+ into.put("minecraft:" + type + "westlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "westupperleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "westupperleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "westupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "westupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "westupperrightfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "westupperrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "westupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "westupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}")); -+ } -+ -+ static final Map> NOTE_BLOCK_MAP = new HashMap<>(); -+ static { -+ for(int note = 0; note < 26; ++note) { -+ NOTE_BLOCK_MAP.put("true" + note, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:note_block',Properties:{powered:'true',note:'" + note + "'}}")); -+ NOTE_BLOCK_MAP.put("false" + note, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:note_block',Properties:{powered:'false',note:'" + note + "'}}")); -+ } -+ } -+ -+ static final Int2ObjectOpenHashMap DYE_COLOR_MAP = new Int2ObjectOpenHashMap<>(); -+ static { -+ DYE_COLOR_MAP.put(0, "white"); -+ DYE_COLOR_MAP.put(1, "orange"); -+ DYE_COLOR_MAP.put(2, "magenta"); -+ DYE_COLOR_MAP.put(3, "light_blue"); -+ DYE_COLOR_MAP.put(4, "yellow"); -+ DYE_COLOR_MAP.put(5, "lime"); -+ DYE_COLOR_MAP.put(6, "pink"); -+ DYE_COLOR_MAP.put(7, "gray"); -+ DYE_COLOR_MAP.put(8, "light_gray"); -+ DYE_COLOR_MAP.put(9, "cyan"); -+ DYE_COLOR_MAP.put(10, "purple"); -+ DYE_COLOR_MAP.put(11, "blue"); -+ DYE_COLOR_MAP.put(12, "brown"); -+ DYE_COLOR_MAP.put(13, "green"); -+ DYE_COLOR_MAP.put(14, "red"); -+ DYE_COLOR_MAP.put(15, "black"); -+ } -+ -+ static final Map> BED_BLOCK_MAP = new HashMap<>(); -+ -+ static { -+ for (final Int2ObjectMap.Entry entry : DYE_COLOR_MAP.int2ObjectEntrySet()) { -+ if (!Objects.equals(entry.getValue(), "red")) { -+ addBeds(BED_BLOCK_MAP, entry.getIntKey(), entry.getValue()); -+ } -+ } -+ } -+ -+ private static void addBeds(final Map> into, final int colourId, final String colourName) { -+ into.put("southfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'south',occupied:'false',part:'foot'}}")); -+ into.put("westfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'west',occupied:'false',part:'foot'}}")); -+ into.put("northfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'north',occupied:'false',part:'foot'}}")); -+ into.put("eastfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'east',occupied:'false',part:'foot'}}")); -+ into.put("southfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'south',occupied:'false',part:'head'}}")); -+ into.put("westfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'west',occupied:'false',part:'head'}}")); -+ into.put("northfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'north',occupied:'false',part:'head'}}")); -+ into.put("eastfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'east',occupied:'false',part:'head'}}")); -+ into.put("southtruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'south',occupied:'true',part:'head'}}")); -+ into.put("westtruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'west',occupied:'true',part:'head'}}")); -+ into.put("northtruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'north',occupied:'true',part:'head'}}")); -+ into.put("easttruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'east',occupied:'true',part:'head'}}")); -+ } -+ -+ static final Map> BANNER_BLOCK_MAP = new HashMap<>(); -+ -+ static { -+ for (final Int2ObjectMap.Entry entry : DYE_COLOR_MAP.int2ObjectEntrySet()) { -+ if (!Objects.equals(entry.getValue(), "white")) { -+ addBanners(BANNER_BLOCK_MAP, 15 - entry.getIntKey(), entry.getValue()); -+ } -+ } -+ } -+ -+ private static void addBanners(final Map> into, final int colourId, final String colourName) { -+ for(int rotation = 0; rotation < 16; ++rotation) { -+ into.put("" + rotation + "_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_banner',Properties:{rotation:'" + rotation + "'}}")); -+ } -+ -+ into.put("north_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'north'}}")); -+ into.put("south_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'south'}}")); -+ into.put("west_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'west'}}")); -+ into.put("east_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'east'}}")); -+ } -+ -+ static final MapType AIR = Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(0)); -+ -+ public ConverterFlattenChunk() { -+ super(MCVersions.V17W47A, 1); -+ } -+ -+ static String getName(final MapType blockState) { -+ return blockState.getString("Name"); -+ } -+ -+ static String getProperty(final MapType blockState, final String propertyName) { -+ final MapType properties = blockState.getMap("Properties"); -+ if (properties == null) { -+ return ""; -+ } -+ -+ return properties.getString(propertyName, ""); -+ } -+ -+ static int getSideMask(final boolean noLeft, final boolean noRight, final boolean noBack, final boolean noForward) { -+ if (noBack) { -+ if (noRight) { -+ return 2; -+ } else if (noLeft) { -+ return 128; -+ } else { -+ return 1; -+ } -+ } else if (noForward) { -+ if (noLeft) { -+ return 32; -+ } else if (noRight) { -+ return 8; -+ } else { -+ return 16; -+ } -+ } else if (noRight) { -+ return 4; -+ } else if (noLeft) { -+ return 64; -+ } else { -+ return 0; -+ } -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ if (!level.hasKey("Sections", ObjectType.LIST)) { -+ return null; -+ } -+ -+ data.setMap("Level", new UpgradeChunk(level).writeBackToLevel()); -+ -+ return null; -+ } -+ -+ static enum Direction { -+ DOWN(AxisDirection.NEGATIVE, Axis.Y), -+ UP(AxisDirection.POSITIVE, Axis.Y), -+ NORTH(AxisDirection.NEGATIVE, Axis.Z), -+ SOUTH(AxisDirection.POSITIVE, Axis.Z), -+ WEST(AxisDirection.NEGATIVE, Axis.X), -+ EAST(AxisDirection.POSITIVE, Axis.X); -+ -+ private final Axis axis; -+ private final AxisDirection axisDirection; -+ -+ private Direction(final AxisDirection axisDirection, final Axis axis) { -+ this.axis = axis; -+ this.axisDirection = axisDirection; -+ } -+ -+ public AxisDirection getAxisDirection() { -+ return this.axisDirection; -+ } -+ -+ public Axis getAxis() { -+ return this.axis; -+ } -+ -+ public static enum AxisDirection { -+ POSITIVE(1), -+ NEGATIVE(-1); -+ -+ private final int step; -+ -+ private AxisDirection(final int step) { -+ this.step = step; -+ } -+ -+ public int getStep() { -+ return this.step; -+ } -+ } -+ -+ public static enum Axis { -+ X, Y, Z; -+ } -+ } -+ -+ static class DataLayer { -+ private final byte[] data; -+ -+ public DataLayer() { -+ this.data = new byte[2048]; -+ } -+ -+ public DataLayer(final byte[] data) { -+ this.data = data; -+ if (data.length != 2048) { -+ throw new IllegalArgumentException("ChunkNibbleArrays should be 2048 bytes not: " + data.length); -+ } -+ } -+ -+ public static DataLayer getOrNull(final byte[] data) { -+ return data == null ? null : new DataLayer(data); -+ } -+ -+ public static DataLayer getOrCreate(final byte[] data) { -+ return data == null ? new DataLayer() : new DataLayer(data); -+ } -+ -+ public int get(final int index) { -+ final byte value = this.data[index >>> 1]; -+ -+ // if we are an even index, we want lower 4 bits -+ // if we are an odd index, we want upper 4 bits -+ return ((value >>> ((index & 1) << 2)) & 0xF); -+ } -+ -+ public int get(final int x, final int y, final int z) { -+ final int index = y << 8 | z << 4 | x; -+ final byte value = this.data[index >>> 1]; -+ -+ // if we are an even index, we want lower 4 bits -+ // if we are an odd index, we want upper 4 bits -+ return ((value >>> ((index & 1) << 2)) & 0xF); -+ } -+ } -+ -+ static final class UpgradeChunk { -+ int sides; -+ -+ final Section[] sections = new Section[16]; -+ final MapType level; -+ final int blockX; -+ final int blockZ; -+ final Int2ObjectLinkedOpenHashMap> tileEntities = new Int2ObjectLinkedOpenHashMap<>(16); -+ -+ public UpgradeChunk(final MapType level) { -+ this.level = level; -+ this.blockX = level.getInt("xPos") << 4; -+ this.blockZ = level.getInt("zPos") << 4; -+ -+ final ListType tileEntities = level.getList("TileEntities", ObjectType.MAP); -+ if (tileEntities != null) { -+ for (int i = 0, len = tileEntities.size(); i < len; ++i) { -+ final MapType tileEntity = tileEntities.getMap(i); -+ -+ final int x = (tileEntity.getInt("x") - this.blockX) & 15; -+ final int y = tileEntity.getInt("y"); -+ final int z = (tileEntity.getInt("z") - this.blockZ) & 15; -+ final int index = (y << 8) | (z << 4) | x; -+ if (this.tileEntities.put(index, tileEntity) != null) { -+ LOGGER.warn("In chunk: {}x{} found a duplicate block entity at position (ConverterFlattenChunk): [{}, {}, {}]", this.blockX, this.blockZ, x, y, z); -+ } -+ } -+ } -+ -+ final boolean convertedFromAlphaFormat = level.getBoolean("convertedFromAlphaFormat"); -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType sectionData = sections.getMap(i); -+ final Section section = new Section(sectionData); -+ -+ if (section.y < 0 || section.y > 15) { -+ LOGGER.warn("In chunk: {}x{} found an invalid chunk section y (ConverterFlattenChunk): {}", this.blockX, this.blockZ, section.y); -+ continue; -+ } -+ -+ if (this.sections[section.y] != null) { -+ LOGGER.warn("In chunk: {}x{} found a duplicate chunk section (ConverterFlattenChunk): {}", this.blockX, this.blockZ, section.y); -+ } -+ -+ this.sides = section.upgrade(this.sides); -+ this.sections[section.y] = section; -+ } -+ } -+ -+ for (final Section section : this.sections) { -+ if (section == null) { -+ continue; -+ } -+ -+ final int yIndex = section.y << (8 + 4); -+ -+ for (final Iterator> iterator = section.toFix.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Int2ObjectMap.Entry fixEntry = iterator.next(); -+ final IntIterator positionIterator = fixEntry.getValue().iterator(); -+ switch (fixEntry.getIntKey()) { -+ case 2: { // grass block -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType blockState = this.getBlock(position); -+ if (!"minecraft:grass_block".equals(getName(blockState))) { -+ continue; -+ } -+ -+ final String blockAbove = getName(getBlock(relative(position, Direction.UP))); -+ if ("minecraft:snow".equals(blockAbove) || "minecraft:snow_layer".equals(blockAbove)) { -+ this.setBlock(position, SNOWY_GRASS); -+ } -+ } -+ break; -+ } -+ case 3: { // dirt -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType blockState = this.getBlock(position); -+ if (!"minecraft:podzol".equals(getName(blockState))) { -+ continue; -+ } -+ -+ final String blockAbove = getName(getBlock(relative(position, Direction.UP))); -+ if ("minecraft:snow".equals(blockAbove) || "minecraft:snow_layer".equals(blockAbove)) { -+ this.setBlock(position, SNOWY_PODZOL); -+ } -+ } -+ break; -+ } -+ case 25: { // note block -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType tile = this.removeBlockEntity(position); -+ if (tile != null) { -+ final String state = Boolean.toString(tile.getBoolean("powered")) + (byte) Math.min(Math.max(tile.getInt("note"), 0), 24); -+ this.setBlock(position, NOTE_BLOCK_MAP.getOrDefault(state, NOTE_BLOCK_MAP.get("false0"))); -+ } -+ } -+ break; -+ } -+ case 26: { // bed -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType tile = this.getBlockEntity(position); -+ -+ if (tile == null) { -+ continue; -+ } -+ -+ final MapType blockState = this.getBlock(position); -+ -+ final int colour = tile.getInt("color"); -+ if (colour != 14 && colour >= 0 && colour < 16) { -+ final String state = getProperty(blockState, "facing") + getProperty(blockState, "occupied") + getProperty(blockState, "part") + colour; -+ -+ final MapType update = BED_BLOCK_MAP.get(state); -+ if (update != null) { -+ this.setBlock(position, update); -+ } -+ } -+ } -+ break; -+ } -+ case 64: // oak door -+ case 71: // iron door -+ case 193: // spruce door -+ case 194: // birch door -+ case 195: // jungle door -+ case 196: // acacia door -+ case 197: { // dark oak door -+ // aka the door updater -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType blockState = this.getBlock(position); -+ if (!getName(blockState).endsWith("_door")) { -+ continue; -+ } -+ -+ if (!"lower".equals(getProperty(blockState, "half"))) { -+ continue; -+ } -+ -+ final int positionAbove = relative(position, Direction.UP); -+ final MapType blockStateAbove = this.getBlock(positionAbove); -+ -+ final String name = getName(blockState); -+ if (name.equals(getName(blockStateAbove))) { -+ final String facingBelow = getProperty(blockState, "facing"); -+ final String openBelow = getProperty(blockState, "open"); -+ final String hingeAbove = convertedFromAlphaFormat ? "left" : getProperty(blockStateAbove, "hinge"); -+ final String poweredAbove = convertedFromAlphaFormat ? "false" : getProperty(blockStateAbove, "powered"); -+ -+ this.setBlock(position, DOOR_MAP.get(name + facingBelow + "lower" + hingeAbove + openBelow + poweredAbove)); -+ this.setBlock(positionAbove, DOOR_MAP.get(name + facingBelow + "upper" + hingeAbove + openBelow + poweredAbove)); -+ } -+ } -+ break; -+ } -+ case 86: { // pumpkin -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType blockState = this.getBlock(position); -+ -+ // I guess this is some terrible hack to convert carved pumpkins from world gen into -+ // regular pumpkins? -+ -+ if ("minecraft:carved_pumpkin".equals(getName(blockState))) { -+ final String downName = getName(this.getBlock(relative(position, Direction.DOWN))); -+ if ("minecraft:grass_block".equals(downName) || "minecraft:dirt".equals(downName)) { -+ this.setBlock(position, PUMPKIN); -+ } -+ } -+ } -+ break; -+ } -+ case 110: { // mycelium -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType blockState = this.getBlock(position); -+ if ("minecraft:mycelium".equals(getName(blockState))) { -+ final String nameAbove = getName(this.getBlock(relative(position, Direction.UP))); -+ if ("minecraft:snow".equals(nameAbove) || "minecraft:snow_layer".equals(nameAbove)) { -+ this.setBlock(position, SNOWY_MYCELIUM); -+ } -+ } -+ } -+ break; -+ } -+ case 140: { // flower pot -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType tile = this.removeBlockEntity(position); -+ if (tile == null) { -+ continue; -+ } -+ -+ final String item; -+ if (tile.hasKey("Item", ObjectType.NUMBER)) { -+ // the item name converter should have migrated to number, however no legacy converter -+ // ever did this. so we can get data with versions above v102 (old worlds, converted prior to DFU) -+ // that didn't convert. so just do it here. -+ item = HelperItemNameV102.getNameFromId(tile.getInt("Item")); -+ } else { -+ item = tile.getString("Item", ""); -+ } -+ -+ final String state = item + tile.getInt("Data"); -+ this.setBlock(position, FLOWER_POT_MAP.getOrDefault(state, FLOWER_POT_MAP.get("minecraft:air0"))); -+ } -+ break; -+ } -+ case 144: { // mob head -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType tile = this.getBlockEntity(position); -+ if (tile == null) { -+ continue; -+ } -+ -+ final String typeString = Integer.toString(tile.getInt("SkullType")); -+ final String facing = getProperty(this.getBlock(position), "facing"); -+ final String state; -+ if (!"up".equals(facing) && !"down".equals(facing)) { -+ state = typeString + facing; -+ } else { -+ state = typeString + tile.getInt("Rot"); -+ } -+ -+ tile.remove("SkullType"); -+ tile.remove("facing"); -+ tile.remove("Rot"); -+ -+ this.setBlock(position, SKULL_MAP.getOrDefault(state, SKULL_MAP.get("0north"))); -+ } -+ break; -+ } -+ case 175: { // sunflower -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType blockState = this.getBlock(position); -+ if (!"upper".equals(getProperty(blockState, "half"))) { -+ continue; -+ } -+ -+ final MapType blockStateBelow = this.getBlock(relative(position, Direction.DOWN)); -+ final String nameBelow = getName(blockStateBelow); -+ switch (nameBelow) { -+ case "minecraft:sunflower": -+ this.setBlock(position, UPPER_SUNFLOWER); -+ break; -+ case "minecraft:lilac": -+ this.setBlock(position, UPPER_LILAC); -+ break; -+ case "minecraft:tall_grass": -+ this.setBlock(position, UPPER_TALL_GRASS); -+ break; -+ case "minecraft:large_fern": -+ this.setBlock(position, UPPER_LARGE_FERN); -+ break; -+ case "minecraft:rose_bush": -+ this.setBlock(position, UPPER_ROSE_BUSH); -+ break; -+ case "minecraft:peony": -+ this.setBlock(position, UPPER_PEONY); -+ break; -+ } -+ } -+ break; -+ } -+ case 176: // free standing banner -+ case 177: { // wall mounted banner -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType tile = this.getBlockEntity(position); -+ -+ if (tile == null) { -+ continue; -+ } -+ -+ final MapType blockState = this.getBlock(position); -+ -+ final int base = tile.getInt("Base"); -+ if (base != 15 && base >= 0 && base < 16) { -+ final String state = getProperty(blockState, fixEntry.getIntKey() == 176 ? "rotation" : "facing") + "_" + base; -+ final MapType update = BANNER_BLOCK_MAP.get(state); -+ if (update != null) { -+ this.setBlock(position, update); -+ } -+ } -+ } -+ break; -+ } -+ } -+ } -+ } -+ } -+ -+ private MapType getBlockEntity(final int index) { -+ return this.tileEntities.get(index); -+ } -+ -+ private MapType removeBlockEntity(final int index) { -+ return this.tileEntities.remove(index); -+ } -+ -+ public static int relative(final int index, final Direction direction) { -+ switch (direction.getAxis()) { -+ case X: -+ int j = (index & 15) + direction.getAxisDirection().getStep(); -+ return j >= 0 && j <= 15 ? index & -16 | j : -1; -+ case Y: -+ int k = (index >> 8) + direction.getAxisDirection().getStep(); -+ return k >= 0 && k <= 255 ? index & 255 | k << 8 : -1; -+ case Z: -+ int l = (index >> 4 & 15) + direction.getAxisDirection().getStep(); -+ return l >= 0 && l <= 15 ? index & -241 | l << 4 : -1; -+ default: -+ return -1; -+ } -+ } -+ -+ private void setBlock(final int index, final MapType blockState) { -+ if (index >= 0 && index <= 65535) { -+ final Section section = this.getSection(index); -+ if (section != null) { -+ section.setBlock(index & 4095, blockState); -+ } -+ } -+ } -+ -+ private Section getSection(final int index) { -+ final int y = index >> 12; -+ return y < this.sections.length ? this.sections[y] : null; -+ } -+ -+ public MapType getBlock(int i) { -+ if (i >= 0 && i <= 65535) { -+ final Section section = this.getSection(i); -+ return section == null ? AIR : section.getBlock(i & 4095); -+ } else { -+ return AIR; -+ } -+ } -+ -+ public MapType writeBackToLevel() { -+ if (this.tileEntities.isEmpty()) { -+ this.level.remove("TileEntities"); -+ } else { -+ final ListType tileEntities = Types.NBT.createEmptyList(); -+ this.tileEntities.values().forEach(tileEntities::addMap); -+ this.level.setList("TileEntities", tileEntities); -+ } -+ -+ final MapType indices = Types.NBT.createEmptyMap(); -+ final ListType sections = Types.NBT.createEmptyList(); -+ for (final Section section : this.sections) { -+ if (section == null) { -+ continue; -+ } -+ -+ sections.addMap(section.writeBackToSection()); -+ indices.setInts(Integer.toString(section.y), Arrays.copyOf(section.update.elements(), section.update.size())); -+ } -+ -+ this.level.setList("Sections", sections); -+ -+ final MapType upgradeData = Types.NBT.createEmptyMap(); -+ upgradeData.setByte("Sides", (byte)this.sides); -+ upgradeData.setMap("Indices", indices); -+ -+ this.level.setMap("UpgradeData", upgradeData); -+ -+ return this.level; -+ } -+ } -+ -+ static class Section { -+ final Palette palette = new Palette(); -+ -+ static final class Palette extends Reference2IntOpenHashMap> { -+ -+ final ListType paletteStates = Types.NBT.createEmptyList(); -+ -+ private int find(final MapType k) { -+ if (((k) == (null))) -+ return containsNullKey ? n : -(n + 1); -+ MapType curr; -+ final Object[] key = this.key; -+ int pos; -+ // The starting point. -+ if (((curr = (MapType)key[pos = (it.unimi.dsi.fastutil.HashCommon.mix(System.identityHashCode(k))) & mask]) == (null))) -+ return -(pos + 1); -+ if (((k) == (curr))) -+ return pos; -+ // There's always an unused entry. -+ while (true) { -+ if (((curr = (MapType)key[pos = (pos + 1) & mask]) == (null))) -+ return -(pos + 1); -+ if (((k) == (curr))) -+ return pos; -+ } -+ } -+ -+ private void insert(final int pos, final MapType k, final int v) { -+ if (pos == n) -+ containsNullKey = true; -+ ((Object[])key)[pos] = k; -+ value[pos] = v; -+ if (size++ >= maxFill) -+ rehash(arraySize(size + 1, f)); -+ } -+ -+ private MapType[] byId = new MapType[4]; -+ private MapType last = null; -+ -+ public int getOrCreateId(final MapType k) { -+ if (k == this.last) { -+ return this.size - 1; -+ } -+ final int pos = find(k); -+ if (pos >= 0) { -+ return this.value[pos]; -+ } -+ -+ final int insert = this.size; -+ MapType inPalette = k; -+ -+ if ("%%FILTER_ME%%".equals(getName(k))) { -+ inPalette = AIR; -+ } -+ -+ if (insert >= this.byId.length) { -+ this.byId = Arrays.copyOf(this.byId, this.byId.length * 2); -+ this.byId[insert] = k; -+ } else { -+ this.byId[insert] = k; -+ } -+ this.paletteStates.addMap(inPalette); -+ -+ this.last = k; -+ -+ this.insert(-pos - 1, k, insert); -+ -+ return insert; -+ } -+ -+ } -+ -+ final MapType section; -+ final boolean hasData; -+ final Int2ObjectLinkedOpenHashMap toFix = new Int2ObjectLinkedOpenHashMap<>(); -+ final IntArrayList update = new IntArrayList(); -+ final int y; -+ final int[] buffer = new int[4096]; -+ -+ public Section(final MapType section) { -+ this.section = section; -+ this.y = section.getInt("Y"); -+ this.hasData = section.hasKey("Blocks", ObjectType.BYTE_ARRAY); -+ } -+ -+ public MapType getBlock(final int index) { -+ if (index >= 0 && index <= 4095) { -+ final MapType state = this.palette.byId[this.buffer[index]]; -+ return state == null ? AIR : state; -+ } else { -+ return AIR; -+ } -+ } -+ -+ public void setBlock(final int index, final MapType blockState) { -+ this.buffer[index] = this.palette.getOrCreateId(blockState); -+ } -+ -+ public int upgrade(int sides) { -+ if (!this.hasData) { -+ return sides; -+ } -+ -+ final byte[] blocks = this.section.getBytes("Blocks"); -+ final DataLayer data = DataLayer.getOrNull(this.section.getBytes("Data")); -+ final DataLayer add = DataLayer.getOrNull(this.section.getBytes("Add")); -+ -+ this.palette.getOrCreateId(AIR); -+ -+ for (int index = 0; index < 4096; ++index) { -+ final int x = index & 15; -+ final int z = index >> 4 & 15; -+ -+ int blockStateId = (blocks[index] & 255) << 4; -+ if (data != null) { -+ blockStateId |= data.get(index); -+ } -+ if (add != null) { -+ blockStateId |= add.get(index) << 12; -+ } -+ if (IDS_NEEDING_FIX[blockStateId >>> 4]) { -+ this.addFix(blockStateId >>> 4, index); -+ } -+ -+ if (VIRTUAL[blockStateId >>> 4]) { -+ final int additionalSides = getSideMask(x == 0, x == 15, z == 0, z == 15); -+ if (additionalSides == 0) { -+ this.update.add(index); -+ } else { -+ sides |= additionalSides; -+ } -+ } -+ -+ this.setBlock(index, HelperBlockFlatteningV1450.getNBTForId(blockStateId)); -+ } -+ -+ return sides; -+ } -+ -+ private void addFix(final int block, final int index) { -+ this.toFix.computeIfAbsent(block, (final int keyInMap) -> { -+ return new IntArrayList(); -+ }).add(index); -+ } -+ -+ // Note: modifies the current section and returns it. -+ public MapType writeBackToSection() { -+ if (!this.hasData) { -+ return this.section; -+ } -+ -+ this.section.setList("Palette", this.palette.paletteStates.copy()); // deep copy to ensure palette compound tags are NOT shared -+ -+ final int bitSize = Math.max(4, DataFixUtils.ceillog2(this.palette.size())); -+ final PackedBitStorage packedIds = new PackedBitStorage(bitSize, 4096); -+ -+ for(int index = 0; index < this.buffer.length; ++index) { -+ packedIds.set(index, this.buffer[index]); -+ } -+ -+ this.section.setLongs("BlockStates", packedIds.getRaw()); -+ -+ this.section.remove("Blocks"); -+ this.section.remove("Data"); -+ this.section.remove("Add"); -+ -+ return this.section; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java -new file mode 100644 -index 0000000000000000000000000000000000000000..084c67a46bc5ec7f5a4bef3216805a87b32c83d0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterRenameStatus.java -@@ -0,0 +1,32 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.chunk; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.util.NamespaceUtil; -+import java.util.function.Function; -+ -+public final class ConverterRenameStatus extends DataConverter, MapType> { -+ -+ private final Function renamer; -+ -+ public ConverterRenameStatus(final int toVersion, final Function renamer) { -+ this(toVersion, 0, renamer); -+ } -+ -+ public ConverterRenameStatus(final int toVersion, final int versionStep, final Function renamer) { -+ super(toVersion, versionStep); -+ this.renamer = renamer; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ // Note: DFU technically enforces namespace due to how they wrote their converter, so we will do the same. -+ NamespaceUtil.enforceForPath(data, "Status"); -+ RenameHelper.renameString(data, "Status", this.renamer); -+ -+ NamespaceUtil.enforceForPath(data.getMap("below_zero_retrogen"), "target_status"); -+ RenameHelper.renameString(data.getMap("below_zero_retrogen"), "target_status", this.renamer); -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6684915d6c0c44328a9296dc3ceb530e69482083 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java -@@ -0,0 +1,38 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.entity; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.function.Function; -+ -+public final class ConverterAbstractEntityRename { -+ -+ private ConverterAbstractEntityRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String id = data.getString("id"); -+ if (id == null) { -+ return null; -+ } -+ -+ final String converted = renamer.apply(id); -+ -+ if (converted != null) { -+ data.setString("id", converted); -+ } -+ -+ return null; -+ } -+ }); -+ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.ENTITY_NAME, renamer); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java -new file mode 100644 -index 0000000000000000000000000000000000000000..985af815e3c23ad7c8b774eac46a7202d3020234 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java -@@ -0,0 +1,44 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.entity; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.function.IntFunction; -+ -+public final class ConverterEntityToVariant extends DataConverter, MapType> { -+ -+ public final String path; -+ public final IntFunction conversion; -+ -+ public ConverterEntityToVariant(final int toVersion, final String path, final IntFunction conversion) { -+ super(toVersion); -+ this.path = path; -+ this.conversion = conversion; -+ } -+ -+ public ConverterEntityToVariant(final int toVersion, final int versionStep, final String path, final IntFunction conversion) { -+ super(toVersion, versionStep); -+ this.path = path; -+ this.conversion = conversion; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final Number value = data.getNumber(this.path); -+ if (value == null) { -+ // nothing to do, DFU does the same -+ return null; -+ } -+ -+ final String converted = this.conversion.apply(value.intValue()); -+ -+ if (converted == null) { -+ throw new NullPointerException("Conversion " + this.conversion + " cannot return null value!"); -+ } -+ -+ // DFU doesn't appear to remove the old field, so why should we? -+ -+ data.setString("variant", converted); -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ed5dcf6f8160742c07e23e98c85409209350a7d4 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java -@@ -0,0 +1,37 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.entity; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.function.Function; -+ -+public final class ConverterEntityVariantRename extends DataConverter, MapType> { -+ -+ private final Function renamer; -+ -+ public ConverterEntityVariantRename(final int toVersion, final Function renamer) { -+ super(toVersion); -+ this.renamer = renamer; -+ } -+ -+ public ConverterEntityVariantRename(final int toVersion, final int versionStep, final Function renamer) { -+ super(toVersion, versionStep); -+ this.renamer = renamer; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String variant = data.getString("variant"); -+ -+ if (variant == null) { -+ return null; -+ } -+ -+ final String rename = this.renamer.apply(variant); -+ -+ if (rename != null) { -+ data.setString("variant", rename); -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java -new file mode 100644 -index 0000000000000000000000000000000000000000..afad2d92f78d4727ff4440ad2778f018d5a2a609 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java -@@ -0,0 +1,371 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.entity; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class ConverterFlattenEntity extends DataConverter, MapType> { -+ -+ private static final Map BLOCK_NAME_TO_ID = new HashMap<>(); -+ static { -+ BLOCK_NAME_TO_ID.put("minecraft:air", 0); -+ BLOCK_NAME_TO_ID.put("minecraft:stone", 1); -+ BLOCK_NAME_TO_ID.put("minecraft:grass", 2); -+ BLOCK_NAME_TO_ID.put("minecraft:dirt", 3); -+ BLOCK_NAME_TO_ID.put("minecraft:cobblestone", 4); -+ BLOCK_NAME_TO_ID.put("minecraft:planks", 5); -+ BLOCK_NAME_TO_ID.put("minecraft:sapling", 6); -+ BLOCK_NAME_TO_ID.put("minecraft:bedrock", 7); -+ BLOCK_NAME_TO_ID.put("minecraft:flowing_water", 8); -+ BLOCK_NAME_TO_ID.put("minecraft:water", 9); -+ BLOCK_NAME_TO_ID.put("minecraft:flowing_lava", 10); -+ BLOCK_NAME_TO_ID.put("minecraft:lava", 11); -+ BLOCK_NAME_TO_ID.put("minecraft:sand", 12); -+ BLOCK_NAME_TO_ID.put("minecraft:gravel", 13); -+ BLOCK_NAME_TO_ID.put("minecraft:gold_ore", 14); -+ BLOCK_NAME_TO_ID.put("minecraft:iron_ore", 15); -+ BLOCK_NAME_TO_ID.put("minecraft:coal_ore", 16); -+ BLOCK_NAME_TO_ID.put("minecraft:log", 17); -+ BLOCK_NAME_TO_ID.put("minecraft:leaves", 18); -+ BLOCK_NAME_TO_ID.put("minecraft:sponge", 19); -+ BLOCK_NAME_TO_ID.put("minecraft:glass", 20); -+ BLOCK_NAME_TO_ID.put("minecraft:lapis_ore", 21); -+ BLOCK_NAME_TO_ID.put("minecraft:lapis_block", 22); -+ BLOCK_NAME_TO_ID.put("minecraft:dispenser", 23); -+ BLOCK_NAME_TO_ID.put("minecraft:sandstone", 24); -+ BLOCK_NAME_TO_ID.put("minecraft:noteblock", 25); -+ BLOCK_NAME_TO_ID.put("minecraft:bed", 26); -+ BLOCK_NAME_TO_ID.put("minecraft:golden_rail", 27); -+ BLOCK_NAME_TO_ID.put("minecraft:detector_rail", 28); -+ BLOCK_NAME_TO_ID.put("minecraft:sticky_piston", 29); -+ BLOCK_NAME_TO_ID.put("minecraft:web", 30); -+ BLOCK_NAME_TO_ID.put("minecraft:tallgrass", 31); -+ BLOCK_NAME_TO_ID.put("minecraft:deadbush", 32); -+ BLOCK_NAME_TO_ID.put("minecraft:piston", 33); -+ BLOCK_NAME_TO_ID.put("minecraft:piston_head", 34); -+ BLOCK_NAME_TO_ID.put("minecraft:wool", 35); -+ BLOCK_NAME_TO_ID.put("minecraft:piston_extension", 36); -+ BLOCK_NAME_TO_ID.put("minecraft:yellow_flower", 37); -+ BLOCK_NAME_TO_ID.put("minecraft:red_flower", 38); -+ BLOCK_NAME_TO_ID.put("minecraft:brown_mushroom", 39); -+ BLOCK_NAME_TO_ID.put("minecraft:red_mushroom", 40); -+ BLOCK_NAME_TO_ID.put("minecraft:gold_block", 41); -+ BLOCK_NAME_TO_ID.put("minecraft:iron_block", 42); -+ BLOCK_NAME_TO_ID.put("minecraft:double_stone_slab", 43); -+ BLOCK_NAME_TO_ID.put("minecraft:stone_slab", 44); -+ BLOCK_NAME_TO_ID.put("minecraft:brick_block", 45); -+ BLOCK_NAME_TO_ID.put("minecraft:tnt", 46); -+ BLOCK_NAME_TO_ID.put("minecraft:bookshelf", 47); -+ BLOCK_NAME_TO_ID.put("minecraft:mossy_cobblestone", 48); -+ BLOCK_NAME_TO_ID.put("minecraft:obsidian", 49); -+ BLOCK_NAME_TO_ID.put("minecraft:torch", 50); -+ BLOCK_NAME_TO_ID.put("minecraft:fire", 51); -+ BLOCK_NAME_TO_ID.put("minecraft:mob_spawner", 52); -+ BLOCK_NAME_TO_ID.put("minecraft:oak_stairs", 53); -+ BLOCK_NAME_TO_ID.put("minecraft:chest", 54); -+ BLOCK_NAME_TO_ID.put("minecraft:redstone_wire", 55); -+ BLOCK_NAME_TO_ID.put("minecraft:diamond_ore", 56); -+ BLOCK_NAME_TO_ID.put("minecraft:diamond_block", 57); -+ BLOCK_NAME_TO_ID.put("minecraft:crafting_table", 58); -+ BLOCK_NAME_TO_ID.put("minecraft:wheat", 59); -+ BLOCK_NAME_TO_ID.put("minecraft:farmland", 60); -+ BLOCK_NAME_TO_ID.put("minecraft:furnace", 61); -+ BLOCK_NAME_TO_ID.put("minecraft:lit_furnace", 62); -+ BLOCK_NAME_TO_ID.put("minecraft:standing_sign", 63); -+ BLOCK_NAME_TO_ID.put("minecraft:wooden_door", 64); -+ BLOCK_NAME_TO_ID.put("minecraft:ladder", 65); -+ BLOCK_NAME_TO_ID.put("minecraft:rail", 66); -+ BLOCK_NAME_TO_ID.put("minecraft:stone_stairs", 67); -+ BLOCK_NAME_TO_ID.put("minecraft:wall_sign", 68); -+ BLOCK_NAME_TO_ID.put("minecraft:lever", 69); -+ BLOCK_NAME_TO_ID.put("minecraft:stone_pressure_plate", 70); -+ BLOCK_NAME_TO_ID.put("minecraft:iron_door", 71); -+ BLOCK_NAME_TO_ID.put("minecraft:wooden_pressure_plate", 72); -+ BLOCK_NAME_TO_ID.put("minecraft:redstone_ore", 73); -+ BLOCK_NAME_TO_ID.put("minecraft:lit_redstone_ore", 74); -+ BLOCK_NAME_TO_ID.put("minecraft:unlit_redstone_torch", 75); -+ BLOCK_NAME_TO_ID.put("minecraft:redstone_torch", 76); -+ BLOCK_NAME_TO_ID.put("minecraft:stone_button", 77); -+ BLOCK_NAME_TO_ID.put("minecraft:snow_layer", 78); -+ BLOCK_NAME_TO_ID.put("minecraft:ice", 79); -+ BLOCK_NAME_TO_ID.put("minecraft:snow", 80); -+ BLOCK_NAME_TO_ID.put("minecraft:cactus", 81); -+ BLOCK_NAME_TO_ID.put("minecraft:clay", 82); -+ BLOCK_NAME_TO_ID.put("minecraft:reeds", 83); -+ BLOCK_NAME_TO_ID.put("minecraft:jukebox", 84); -+ BLOCK_NAME_TO_ID.put("minecraft:fence", 85); -+ BLOCK_NAME_TO_ID.put("minecraft:pumpkin", 86); -+ BLOCK_NAME_TO_ID.put("minecraft:netherrack", 87); -+ BLOCK_NAME_TO_ID.put("minecraft:soul_sand", 88); -+ BLOCK_NAME_TO_ID.put("minecraft:glowstone", 89); -+ BLOCK_NAME_TO_ID.put("minecraft:portal", 90); -+ BLOCK_NAME_TO_ID.put("minecraft:lit_pumpkin", 91); -+ BLOCK_NAME_TO_ID.put("minecraft:cake", 92); -+ BLOCK_NAME_TO_ID.put("minecraft:unpowered_repeater", 93); -+ BLOCK_NAME_TO_ID.put("minecraft:powered_repeater", 94); -+ BLOCK_NAME_TO_ID.put("minecraft:stained_glass", 95); -+ BLOCK_NAME_TO_ID.put("minecraft:trapdoor", 96); -+ BLOCK_NAME_TO_ID.put("minecraft:monster_egg", 97); -+ BLOCK_NAME_TO_ID.put("minecraft:stonebrick", 98); -+ BLOCK_NAME_TO_ID.put("minecraft:brown_mushroom_block", 99); -+ BLOCK_NAME_TO_ID.put("minecraft:red_mushroom_block", 100); -+ BLOCK_NAME_TO_ID.put("minecraft:iron_bars", 101); -+ BLOCK_NAME_TO_ID.put("minecraft:glass_pane", 102); -+ BLOCK_NAME_TO_ID.put("minecraft:melon_block", 103); -+ BLOCK_NAME_TO_ID.put("minecraft:pumpkin_stem", 104); -+ BLOCK_NAME_TO_ID.put("minecraft:melon_stem", 105); -+ BLOCK_NAME_TO_ID.put("minecraft:vine", 106); -+ BLOCK_NAME_TO_ID.put("minecraft:fence_gate", 107); -+ BLOCK_NAME_TO_ID.put("minecraft:brick_stairs", 108); -+ BLOCK_NAME_TO_ID.put("minecraft:stone_brick_stairs", 109); -+ BLOCK_NAME_TO_ID.put("minecraft:mycelium", 110); -+ BLOCK_NAME_TO_ID.put("minecraft:waterlily", 111); -+ BLOCK_NAME_TO_ID.put("minecraft:nether_brick", 112); -+ BLOCK_NAME_TO_ID.put("minecraft:nether_brick_fence", 113); -+ BLOCK_NAME_TO_ID.put("minecraft:nether_brick_stairs", 114); -+ BLOCK_NAME_TO_ID.put("minecraft:nether_wart", 115); -+ BLOCK_NAME_TO_ID.put("minecraft:enchanting_table", 116); -+ BLOCK_NAME_TO_ID.put("minecraft:brewing_stand", 117); -+ BLOCK_NAME_TO_ID.put("minecraft:cauldron", 118); -+ BLOCK_NAME_TO_ID.put("minecraft:end_portal", 119); -+ BLOCK_NAME_TO_ID.put("minecraft:end_portal_frame", 120); -+ BLOCK_NAME_TO_ID.put("minecraft:end_stone", 121); -+ BLOCK_NAME_TO_ID.put("minecraft:dragon_egg", 122); -+ BLOCK_NAME_TO_ID.put("minecraft:redstone_lamp", 123); -+ BLOCK_NAME_TO_ID.put("minecraft:lit_redstone_lamp", 124); -+ BLOCK_NAME_TO_ID.put("minecraft:double_wooden_slab", 125); -+ BLOCK_NAME_TO_ID.put("minecraft:wooden_slab", 126); -+ BLOCK_NAME_TO_ID.put("minecraft:cocoa", 127); -+ BLOCK_NAME_TO_ID.put("minecraft:sandstone_stairs", 128); -+ BLOCK_NAME_TO_ID.put("minecraft:emerald_ore", 129); -+ BLOCK_NAME_TO_ID.put("minecraft:ender_chest", 130); -+ BLOCK_NAME_TO_ID.put("minecraft:tripwire_hook", 131); -+ BLOCK_NAME_TO_ID.put("minecraft:tripwire", 132); -+ BLOCK_NAME_TO_ID.put("minecraft:emerald_block", 133); -+ BLOCK_NAME_TO_ID.put("minecraft:spruce_stairs", 134); -+ BLOCK_NAME_TO_ID.put("minecraft:birch_stairs", 135); -+ BLOCK_NAME_TO_ID.put("minecraft:jungle_stairs", 136); -+ BLOCK_NAME_TO_ID.put("minecraft:command_block", 137); -+ BLOCK_NAME_TO_ID.put("minecraft:beacon", 138); -+ BLOCK_NAME_TO_ID.put("minecraft:cobblestone_wall", 139); -+ BLOCK_NAME_TO_ID.put("minecraft:flower_pot", 140); -+ BLOCK_NAME_TO_ID.put("minecraft:carrots", 141); -+ BLOCK_NAME_TO_ID.put("minecraft:potatoes", 142); -+ BLOCK_NAME_TO_ID.put("minecraft:wooden_button", 143); -+ BLOCK_NAME_TO_ID.put("minecraft:skull", 144); -+ BLOCK_NAME_TO_ID.put("minecraft:anvil", 145); -+ BLOCK_NAME_TO_ID.put("minecraft:trapped_chest", 146); -+ BLOCK_NAME_TO_ID.put("minecraft:light_weighted_pressure_plate", 147); -+ BLOCK_NAME_TO_ID.put("minecraft:heavy_weighted_pressure_plate", 148); -+ BLOCK_NAME_TO_ID.put("minecraft:unpowered_comparator", 149); -+ BLOCK_NAME_TO_ID.put("minecraft:powered_comparator", 150); -+ BLOCK_NAME_TO_ID.put("minecraft:daylight_detector", 151); -+ BLOCK_NAME_TO_ID.put("minecraft:redstone_block", 152); -+ BLOCK_NAME_TO_ID.put("minecraft:quartz_ore", 153); -+ BLOCK_NAME_TO_ID.put("minecraft:hopper", 154); -+ BLOCK_NAME_TO_ID.put("minecraft:quartz_block", 155); -+ BLOCK_NAME_TO_ID.put("minecraft:quartz_stairs", 156); -+ BLOCK_NAME_TO_ID.put("minecraft:activator_rail", 157); -+ BLOCK_NAME_TO_ID.put("minecraft:dropper", 158); -+ BLOCK_NAME_TO_ID.put("minecraft:stained_hardened_clay", 159); -+ BLOCK_NAME_TO_ID.put("minecraft:stained_glass_pane", 160); -+ BLOCK_NAME_TO_ID.put("minecraft:leaves2", 161); -+ BLOCK_NAME_TO_ID.put("minecraft:log2", 162); -+ BLOCK_NAME_TO_ID.put("minecraft:acacia_stairs", 163); -+ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_stairs", 164); -+ BLOCK_NAME_TO_ID.put("minecraft:slime", 165); -+ BLOCK_NAME_TO_ID.put("minecraft:barrier", 166); -+ BLOCK_NAME_TO_ID.put("minecraft:iron_trapdoor", 167); -+ BLOCK_NAME_TO_ID.put("minecraft:prismarine", 168); -+ BLOCK_NAME_TO_ID.put("minecraft:sea_lantern", 169); -+ BLOCK_NAME_TO_ID.put("minecraft:hay_block", 170); -+ BLOCK_NAME_TO_ID.put("minecraft:carpet", 171); -+ BLOCK_NAME_TO_ID.put("minecraft:hardened_clay", 172); -+ BLOCK_NAME_TO_ID.put("minecraft:coal_block", 173); -+ BLOCK_NAME_TO_ID.put("minecraft:packed_ice", 174); -+ BLOCK_NAME_TO_ID.put("minecraft:double_plant", 175); -+ BLOCK_NAME_TO_ID.put("minecraft:standing_banner", 176); -+ BLOCK_NAME_TO_ID.put("minecraft:wall_banner", 177); -+ BLOCK_NAME_TO_ID.put("minecraft:daylight_detector_inverted", 178); -+ BLOCK_NAME_TO_ID.put("minecraft:red_sandstone", 179); -+ BLOCK_NAME_TO_ID.put("minecraft:red_sandstone_stairs", 180); -+ BLOCK_NAME_TO_ID.put("minecraft:double_stone_slab2", 181); -+ BLOCK_NAME_TO_ID.put("minecraft:stone_slab2", 182); -+ BLOCK_NAME_TO_ID.put("minecraft:spruce_fence_gate", 183); -+ BLOCK_NAME_TO_ID.put("minecraft:birch_fence_gate", 184); -+ BLOCK_NAME_TO_ID.put("minecraft:jungle_fence_gate", 185); -+ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_fence_gate", 186); -+ BLOCK_NAME_TO_ID.put("minecraft:acacia_fence_gate", 187); -+ BLOCK_NAME_TO_ID.put("minecraft:spruce_fence", 188); -+ BLOCK_NAME_TO_ID.put("minecraft:birch_fence", 189); -+ BLOCK_NAME_TO_ID.put("minecraft:jungle_fence", 190); -+ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_fence", 191); -+ BLOCK_NAME_TO_ID.put("minecraft:acacia_fence", 192); -+ BLOCK_NAME_TO_ID.put("minecraft:spruce_door", 193); -+ BLOCK_NAME_TO_ID.put("minecraft:birch_door", 194); -+ BLOCK_NAME_TO_ID.put("minecraft:jungle_door", 195); -+ BLOCK_NAME_TO_ID.put("minecraft:acacia_door", 196); -+ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_door", 197); -+ BLOCK_NAME_TO_ID.put("minecraft:end_rod", 198); -+ BLOCK_NAME_TO_ID.put("minecraft:chorus_plant", 199); -+ BLOCK_NAME_TO_ID.put("minecraft:chorus_flower", 200); -+ BLOCK_NAME_TO_ID.put("minecraft:purpur_block", 201); -+ BLOCK_NAME_TO_ID.put("minecraft:purpur_pillar", 202); -+ BLOCK_NAME_TO_ID.put("minecraft:purpur_stairs", 203); -+ BLOCK_NAME_TO_ID.put("minecraft:purpur_double_slab", 204); -+ BLOCK_NAME_TO_ID.put("minecraft:purpur_slab", 205); -+ BLOCK_NAME_TO_ID.put("minecraft:end_bricks", 206); -+ BLOCK_NAME_TO_ID.put("minecraft:beetroots", 207); -+ BLOCK_NAME_TO_ID.put("minecraft:grass_path", 208); -+ BLOCK_NAME_TO_ID.put("minecraft:end_gateway", 209); -+ BLOCK_NAME_TO_ID.put("minecraft:repeating_command_block", 210); -+ BLOCK_NAME_TO_ID.put("minecraft:chain_command_block", 211); -+ BLOCK_NAME_TO_ID.put("minecraft:frosted_ice", 212); -+ BLOCK_NAME_TO_ID.put("minecraft:magma", 213); -+ BLOCK_NAME_TO_ID.put("minecraft:nether_wart_block", 214); -+ BLOCK_NAME_TO_ID.put("minecraft:red_nether_brick", 215); -+ BLOCK_NAME_TO_ID.put("minecraft:bone_block", 216); -+ BLOCK_NAME_TO_ID.put("minecraft:structure_void", 217); -+ BLOCK_NAME_TO_ID.put("minecraft:observer", 218); -+ BLOCK_NAME_TO_ID.put("minecraft:white_shulker_box", 219); -+ BLOCK_NAME_TO_ID.put("minecraft:orange_shulker_box", 220); -+ BLOCK_NAME_TO_ID.put("minecraft:magenta_shulker_box", 221); -+ BLOCK_NAME_TO_ID.put("minecraft:light_blue_shulker_box", 222); -+ BLOCK_NAME_TO_ID.put("minecraft:yellow_shulker_box", 223); -+ BLOCK_NAME_TO_ID.put("minecraft:lime_shulker_box", 224); -+ BLOCK_NAME_TO_ID.put("minecraft:pink_shulker_box", 225); -+ BLOCK_NAME_TO_ID.put("minecraft:gray_shulker_box", 226); -+ BLOCK_NAME_TO_ID.put("minecraft:silver_shulker_box", 227); -+ BLOCK_NAME_TO_ID.put("minecraft:cyan_shulker_box", 228); -+ BLOCK_NAME_TO_ID.put("minecraft:purple_shulker_box", 229); -+ BLOCK_NAME_TO_ID.put("minecraft:blue_shulker_box", 230); -+ BLOCK_NAME_TO_ID.put("minecraft:brown_shulker_box", 231); -+ BLOCK_NAME_TO_ID.put("minecraft:green_shulker_box", 232); -+ BLOCK_NAME_TO_ID.put("minecraft:red_shulker_box", 233); -+ BLOCK_NAME_TO_ID.put("minecraft:black_shulker_box", 234); -+ BLOCK_NAME_TO_ID.put("minecraft:white_glazed_terracotta", 235); -+ BLOCK_NAME_TO_ID.put("minecraft:orange_glazed_terracotta", 236); -+ BLOCK_NAME_TO_ID.put("minecraft:magenta_glazed_terracotta", 237); -+ BLOCK_NAME_TO_ID.put("minecraft:light_blue_glazed_terracotta", 238); -+ BLOCK_NAME_TO_ID.put("minecraft:yellow_glazed_terracotta", 239); -+ BLOCK_NAME_TO_ID.put("minecraft:lime_glazed_terracotta", 240); -+ BLOCK_NAME_TO_ID.put("minecraft:pink_glazed_terracotta", 241); -+ BLOCK_NAME_TO_ID.put("minecraft:gray_glazed_terracotta", 242); -+ BLOCK_NAME_TO_ID.put("minecraft:silver_glazed_terracotta", 243); -+ BLOCK_NAME_TO_ID.put("minecraft:cyan_glazed_terracotta", 244); -+ BLOCK_NAME_TO_ID.put("minecraft:purple_glazed_terracotta", 245); -+ BLOCK_NAME_TO_ID.put("minecraft:blue_glazed_terracotta", 246); -+ BLOCK_NAME_TO_ID.put("minecraft:brown_glazed_terracotta", 247); -+ BLOCK_NAME_TO_ID.put("minecraft:green_glazed_terracotta", 248); -+ BLOCK_NAME_TO_ID.put("minecraft:red_glazed_terracotta", 249); -+ BLOCK_NAME_TO_ID.put("minecraft:black_glazed_terracotta", 250); -+ BLOCK_NAME_TO_ID.put("minecraft:concrete", 251); -+ BLOCK_NAME_TO_ID.put("minecraft:concrete_powder", 252); -+ BLOCK_NAME_TO_ID.put("minecraft:structure_block", 255); -+ } -+ -+ protected static final int VERSION = MCVersions.V17W47A; -+ -+ protected final String[] paths; -+ -+ public ConverterFlattenEntity(final String... paths) { -+ super(VERSION, 3); -+ this.paths = paths; -+ } -+ -+ private static void register(final String id, final String... paths) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, new ConverterFlattenEntity(paths)); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:falling_block", new DataConverter<>(VERSION, 3) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int blockId; -+ if (data.hasKey("Block")) { -+ final Number id = data.getNumber("Block"); -+ if (id != null) { -+ blockId = id.intValue(); -+ } else { -+ blockId = getBlockId(data.getString("Block")); -+ } -+ } else { -+ final Number tileId = data.getNumber("TileID"); -+ if (tileId != null) { -+ blockId = tileId.intValue(); -+ } else { -+ blockId = data.getByte("Tile") & 255; -+ } -+ } -+ -+ final int blockData = data.getInt("Data") & 15; -+ -+ data.remove("Block"); // from type update -+ data.remove("Data"); -+ data.remove("TileID"); -+ data.remove("Tile"); -+ -+ // key is from type update -+ data.setMap("BlockState", HelperBlockFlatteningV1450.getNBTForId((blockId << 4) | blockData).copy()); // copy to avoid problems with later state datafixers -+ -+ return null; -+ } -+ }); -+ register("minecraft:enderman", "carried", "carriedData", "carriedBlockState"); -+ register("minecraft:arrow", "inTile", "inData", "inBlockState"); -+ register("minecraft:spectral_arrow", "inTile", "inData", "inBlockState"); -+ register("minecraft:egg", "inTile"); -+ register("minecraft:ender_pearl", "inTile"); -+ register("minecraft:fireball", "inTile"); -+ register("minecraft:potion", "inTile"); -+ register("minecraft:small_fireball", "inTile"); -+ register("minecraft:snowball", "inTile"); -+ register("minecraft:wither_skull", "inTile"); -+ register("minecraft:xp_bottle", "inTile"); -+ register("minecraft:commandblock_minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ register("minecraft:minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ register("minecraft:chest_minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ register("minecraft:furnace_minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ register("minecraft:tnt_minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ register("minecraft:hopper_minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ register("minecraft:spawner_minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ } -+ -+ public static int getBlockId(final String block) { -+ final Integer ret = BLOCK_NAME_TO_ID.get(block); -+ return ret == null ? 0 : ret.intValue(); -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (this.paths.length == 1) { -+ data.remove(this.paths[0]); -+ return null; -+ } -+ final String idPath = this.paths[0]; -+ final String dataPath = this.paths[1]; -+ final String outputStatePath = this.paths[2]; -+ -+ final int blockId; -+ if (data.hasKey(idPath, ObjectType.NUMBER)) { -+ blockId = data.getInt(idPath); -+ } else { -+ blockId = getBlockId(data.getString(idPath)); -+ } -+ -+ final int blockData = data.getInt(dataPath) & 15; -+ -+ data.remove(idPath); // from type update -+ data.remove(dataPath); -+ -+ data.setMap(outputStatePath, HelperBlockFlatteningV1450.getNBTForId((blockId << 4) | blockData).copy()); // copy to avoid problems with later state datafixers -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4ab607f946782cc483535564e86fa9753dd7897a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java -@@ -0,0 +1,30 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.helpers; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class AddFlagIfAbsent extends DataConverter, MapType> { -+ -+ public final String path; -+ public final boolean dfl; -+ -+ public AddFlagIfAbsent(final int toVersion, final String path, final boolean dfl) { -+ super(toVersion); -+ this.path = path; -+ this.dfl = dfl; -+ } -+ -+ public AddFlagIfAbsent(final int toVersion, final int versionStep, final String path, final boolean dfl) { -+ super(toVersion, versionStep); -+ this.path = path; -+ this.dfl = dfl; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!data.hasKey(this.path)) { -+ data.setBoolean(this.path, this.dfl); -+ } -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bc79670f47aaa413ea3e96ef6a32e14099ad8a58 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java -@@ -0,0 +1,24 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.helpers; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCValueType; -+import java.util.function.Function; -+ -+public final class ConverterAbstractStringValueTypeRename { -+ -+ private ConverterAbstractStringValueTypeRename() {} -+ -+ public static void register(final int version, final MCValueType type, final Function renamer) { -+ register(version, 0, type, renamer); -+ } -+ public static void register(final int version, final int subVersion, final MCValueType type, final Function renamer) { -+ type.addConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public Object convert(final Object data, final long sourceVersion, final long toVersion) { -+ final String ret = (data instanceof String) ? renamer.apply((String)data) : null; -+ return ret == data ? null : ret; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4f4f4cb6037c2a46ffcf427f5812164bbb98b8b7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java -@@ -0,0 +1,1829 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.helpers; -+ -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; -+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -+import net.minecraft.nbt.TagParser; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class HelperBlockFlatteningV1450 { -+ -+ protected static final MapType[] FLATTENED_BY_ID = new MapType[4096]; -+ protected static final MapType[] BLOCK_DEFAULTS = new MapType[4096]; -+ -+ private static final Object2IntOpenHashMap> ID_BY_OLD_NBT = new Object2IntOpenHashMap>(64, 0.7f) { -+ @Override -+ public int put(final MapType o, final int v) { -+ if (this.containsKey(o)) { -+ throw new RuntimeException("Already contains mapping for " + o); -+ } -+ -+ return super.put(o, v); -+ } -+ }; -+ static { -+ ID_BY_OLD_NBT.defaultReturnValue(-1); -+ } -+ -+ private static final Object2IntOpenHashMap ID_BY_OLD_NAME = new Object2IntOpenHashMap(64, 0.7f) { -+ @Override -+ public int put(final String o, final int v) { -+ if (this.containsKey(o)) { -+ throw new RuntimeException("Already contains mapping for " + o); -+ } -+ -+ return super.put(o, v); -+ } -+ }; -+ static { -+ ID_BY_OLD_NAME.defaultReturnValue(-1); -+ } -+ -+ // map used to ensure that each parsed block state contains no duplicates -+ protected static final Map, MapType> IDENTITY_ENSURE = new HashMap<>(); -+ -+ public static MapType parseTag(final String blockstate) { -+ try { -+ final MapType ret = new NBTMapType(TagParser.parseTag(blockstate.replace('\'', '"'))); -+ -+ synchronized (IDENTITY_ENSURE) { -+ final MapType identity = IDENTITY_ENSURE.putIfAbsent(ret, ret); -+ -+ return identity == null ? ret : identity; -+ } -+ -+ } catch (final Exception ex) { -+ throw new RuntimeException("Exception parsing " + blockstate, ex); -+ } -+ } -+ -+ private static void register(final int id, final String flattened, final String... preFlattenings) { -+ final MapType flattenedNBT = parseTag(flattened); -+ if (FLATTENED_BY_ID[id] != null) { -+ throw new RuntimeException("Mapping already exists for id " + id); -+ } -+ FLATTENED_BY_ID[id] = flattenedNBT; -+ -+ // it's important that we register ids from smallest to largest, so that -+ // the default is going to be correct -+ final int block = id >> 4; -+ if (BLOCK_DEFAULTS[block] == null) { -+ BLOCK_DEFAULTS[block] = flattenedNBT; -+ } -+ -+ for (final String preFlattening : preFlattenings) { -+ final MapType preFlatteningNBT = parseTag(preFlattening); -+ final String name = preFlatteningNBT.getString("Name"); -+ if (name == null) { -+ throw new RuntimeException("Name does not exist for pre flattenings for id " + id); -+ } -+ -+ // putIfAbsent so we default to the lowest id, which is going to be the block default -+ ID_BY_OLD_NAME.putIfAbsent(name, id); -+ ID_BY_OLD_NBT.put(preFlatteningNBT, id); -+ } -+ } -+ -+ private static void finalizeMaps() { -+ for(int i = 0; i < FLATTENED_BY_ID.length; ++i) { -+ if (FLATTENED_BY_ID[i] == null) { -+ FLATTENED_BY_ID[i] = BLOCK_DEFAULTS[i >> 4]; -+ } -+ } -+ } -+ -+ public static MapType flattenNBT(final MapType old) { -+ final int id = ID_BY_OLD_NBT.getInt(old); -+ final MapType ret = getNBTForIdRaw(id); -+ -+ return ret == null ? old : ret; -+ } -+ -+ public static String getNewBlockName(final String old) { -+ final int id = ID_BY_OLD_NAME.getInt(old); -+ final MapType ret = getNBTForIdRaw(id); -+ return ret == null ? old : ret.getString("Name"); -+ } -+ -+ public static String getNameForId(final int block) { -+ final MapType nbt = getNBTForIdRaw(block); -+ return nbt == null ? "minecraft:air" : nbt.getString("Name"); -+ } -+ -+ protected static MapType getNBTForIdRaw(final int block) { -+ return block >= 0 && block < FLATTENED_BY_ID.length ? FLATTENED_BY_ID[block] : null; -+ } -+ -+ public static MapType getNBTForId(final int block) { -+ MapType ret = getNBTForIdRaw(block); -+ return ret == null ? FLATTENED_BY_ID[0] : ret; -+ } -+ -+ private HelperBlockFlatteningV1450() {} -+ -+ static { -+ ID_BY_OLD_NBT.defaultReturnValue(-1); -+ register(0, "{Name:'minecraft:air'}", "{Name:'minecraft:air'}"); -+ register(16, "{Name:'minecraft:stone'}", "{Name:'minecraft:stone',Properties:{variant:'stone'}}"); -+ register(17, "{Name:'minecraft:granite'}", "{Name:'minecraft:stone',Properties:{variant:'granite'}}"); -+ register(18, "{Name:'minecraft:polished_granite'}", "{Name:'minecraft:stone',Properties:{variant:'smooth_granite'}}"); -+ register(19, "{Name:'minecraft:diorite'}", "{Name:'minecraft:stone',Properties:{variant:'diorite'}}"); -+ register(20, "{Name:'minecraft:polished_diorite'}", "{Name:'minecraft:stone',Properties:{variant:'smooth_diorite'}}"); -+ register(21, "{Name:'minecraft:andesite'}", "{Name:'minecraft:stone',Properties:{variant:'andesite'}}"); -+ register(22, "{Name:'minecraft:polished_andesite'}", "{Name:'minecraft:stone',Properties:{variant:'smooth_andesite'}}"); -+ register(32, "{Name:'minecraft:grass_block',Properties:{snowy:'false'}}", "{Name:'minecraft:grass',Properties:{snowy:'false'}}", "{Name:'minecraft:grass',Properties:{snowy:'true'}}"); -+ register(48, "{Name:'minecraft:dirt'}", "{Name:'minecraft:dirt',Properties:{snowy:'false',variant:'dirt'}}", "{Name:'minecraft:dirt',Properties:{snowy:'true',variant:'dirt'}}"); -+ register(49, "{Name:'minecraft:coarse_dirt'}", "{Name:'minecraft:dirt',Properties:{snowy:'false',variant:'coarse_dirt'}}", "{Name:'minecraft:dirt',Properties:{snowy:'true',variant:'coarse_dirt'}}"); -+ register(50, "{Name:'minecraft:podzol',Properties:{snowy:'false'}}", "{Name:'minecraft:dirt',Properties:{snowy:'false',variant:'podzol'}}", "{Name:'minecraft:dirt',Properties:{snowy:'true',variant:'podzol'}}"); -+ register(64, "{Name:'minecraft:cobblestone'}", "{Name:'minecraft:cobblestone'}"); -+ register(80, "{Name:'minecraft:oak_planks'}", "{Name:'minecraft:planks',Properties:{variant:'oak'}}"); -+ register(81, "{Name:'minecraft:spruce_planks'}", "{Name:'minecraft:planks',Properties:{variant:'spruce'}}"); -+ register(82, "{Name:'minecraft:birch_planks'}", "{Name:'minecraft:planks',Properties:{variant:'birch'}}"); -+ register(83, "{Name:'minecraft:jungle_planks'}", "{Name:'minecraft:planks',Properties:{variant:'jungle'}}"); -+ register(84, "{Name:'minecraft:acacia_planks'}", "{Name:'minecraft:planks',Properties:{variant:'acacia'}}"); -+ register(85, "{Name:'minecraft:dark_oak_planks'}", "{Name:'minecraft:planks',Properties:{variant:'dark_oak'}}"); -+ register(96, "{Name:'minecraft:oak_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'oak'}}"); -+ register(97, "{Name:'minecraft:spruce_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'spruce'}}"); -+ register(98, "{Name:'minecraft:birch_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'birch'}}"); -+ register(99, "{Name:'minecraft:jungle_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'jungle'}}"); -+ register(100, "{Name:'minecraft:acacia_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'acacia'}}"); -+ register(101, "{Name:'minecraft:dark_oak_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'dark_oak'}}"); -+ register(104, "{Name:'minecraft:oak_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'oak'}}"); -+ register(105, "{Name:'minecraft:spruce_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'spruce'}}"); -+ register(106, "{Name:'minecraft:birch_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'birch'}}"); -+ register(107, "{Name:'minecraft:jungle_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'jungle'}}"); -+ register(108, "{Name:'minecraft:acacia_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'acacia'}}"); -+ register(109, "{Name:'minecraft:dark_oak_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'dark_oak'}}"); -+ register(112, "{Name:'minecraft:bedrock'}", "{Name:'minecraft:bedrock'}"); -+ register(128, "{Name:'minecraft:water',Properties:{level:'0'}}", "{Name:'minecraft:flowing_water',Properties:{level:'0'}}"); -+ register(129, "{Name:'minecraft:water',Properties:{level:'1'}}", "{Name:'minecraft:flowing_water',Properties:{level:'1'}}"); -+ register(130, "{Name:'minecraft:water',Properties:{level:'2'}}", "{Name:'minecraft:flowing_water',Properties:{level:'2'}}"); -+ register(131, "{Name:'minecraft:water',Properties:{level:'3'}}", "{Name:'minecraft:flowing_water',Properties:{level:'3'}}"); -+ register(132, "{Name:'minecraft:water',Properties:{level:'4'}}", "{Name:'minecraft:flowing_water',Properties:{level:'4'}}"); -+ register(133, "{Name:'minecraft:water',Properties:{level:'5'}}", "{Name:'minecraft:flowing_water',Properties:{level:'5'}}"); -+ register(134, "{Name:'minecraft:water',Properties:{level:'6'}}", "{Name:'minecraft:flowing_water',Properties:{level:'6'}}"); -+ register(135, "{Name:'minecraft:water',Properties:{level:'7'}}", "{Name:'minecraft:flowing_water',Properties:{level:'7'}}"); -+ register(136, "{Name:'minecraft:water',Properties:{level:'8'}}", "{Name:'minecraft:flowing_water',Properties:{level:'8'}}"); -+ register(137, "{Name:'minecraft:water',Properties:{level:'9'}}", "{Name:'minecraft:flowing_water',Properties:{level:'9'}}"); -+ register(138, "{Name:'minecraft:water',Properties:{level:'10'}}", "{Name:'minecraft:flowing_water',Properties:{level:'10'}}"); -+ register(139, "{Name:'minecraft:water',Properties:{level:'11'}}", "{Name:'minecraft:flowing_water',Properties:{level:'11'}}"); -+ register(140, "{Name:'minecraft:water',Properties:{level:'12'}}", "{Name:'minecraft:flowing_water',Properties:{level:'12'}}"); -+ register(141, "{Name:'minecraft:water',Properties:{level:'13'}}", "{Name:'minecraft:flowing_water',Properties:{level:'13'}}"); -+ register(142, "{Name:'minecraft:water',Properties:{level:'14'}}", "{Name:'minecraft:flowing_water',Properties:{level:'14'}}"); -+ register(143, "{Name:'minecraft:water',Properties:{level:'15'}}", "{Name:'minecraft:flowing_water',Properties:{level:'15'}}"); -+ register(144, "{Name:'minecraft:water',Properties:{level:'0'}}", "{Name:'minecraft:water',Properties:{level:'0'}}"); -+ register(145, "{Name:'minecraft:water',Properties:{level:'1'}}", "{Name:'minecraft:water',Properties:{level:'1'}}"); -+ register(146, "{Name:'minecraft:water',Properties:{level:'2'}}", "{Name:'minecraft:water',Properties:{level:'2'}}"); -+ register(147, "{Name:'minecraft:water',Properties:{level:'3'}}", "{Name:'minecraft:water',Properties:{level:'3'}}"); -+ register(148, "{Name:'minecraft:water',Properties:{level:'4'}}", "{Name:'minecraft:water',Properties:{level:'4'}}"); -+ register(149, "{Name:'minecraft:water',Properties:{level:'5'}}", "{Name:'minecraft:water',Properties:{level:'5'}}"); -+ register(150, "{Name:'minecraft:water',Properties:{level:'6'}}", "{Name:'minecraft:water',Properties:{level:'6'}}"); -+ register(151, "{Name:'minecraft:water',Properties:{level:'7'}}", "{Name:'minecraft:water',Properties:{level:'7'}}"); -+ register(152, "{Name:'minecraft:water',Properties:{level:'8'}}", "{Name:'minecraft:water',Properties:{level:'8'}}"); -+ register(153, "{Name:'minecraft:water',Properties:{level:'9'}}", "{Name:'minecraft:water',Properties:{level:'9'}}"); -+ register(154, "{Name:'minecraft:water',Properties:{level:'10'}}", "{Name:'minecraft:water',Properties:{level:'10'}}"); -+ register(155, "{Name:'minecraft:water',Properties:{level:'11'}}", "{Name:'minecraft:water',Properties:{level:'11'}}"); -+ register(156, "{Name:'minecraft:water',Properties:{level:'12'}}", "{Name:'minecraft:water',Properties:{level:'12'}}"); -+ register(157, "{Name:'minecraft:water',Properties:{level:'13'}}", "{Name:'minecraft:water',Properties:{level:'13'}}"); -+ register(158, "{Name:'minecraft:water',Properties:{level:'14'}}", "{Name:'minecraft:water',Properties:{level:'14'}}"); -+ register(159, "{Name:'minecraft:water',Properties:{level:'15'}}", "{Name:'minecraft:water',Properties:{level:'15'}}"); -+ register(160, "{Name:'minecraft:lava',Properties:{level:'0'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'0'}}"); -+ register(161, "{Name:'minecraft:lava',Properties:{level:'1'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'1'}}"); -+ register(162, "{Name:'minecraft:lava',Properties:{level:'2'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'2'}}"); -+ register(163, "{Name:'minecraft:lava',Properties:{level:'3'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'3'}}"); -+ register(164, "{Name:'minecraft:lava',Properties:{level:'4'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'4'}}"); -+ register(165, "{Name:'minecraft:lava',Properties:{level:'5'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'5'}}"); -+ register(166, "{Name:'minecraft:lava',Properties:{level:'6'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'6'}}"); -+ register(167, "{Name:'minecraft:lava',Properties:{level:'7'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'7'}}"); -+ register(168, "{Name:'minecraft:lava',Properties:{level:'8'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'8'}}"); -+ register(169, "{Name:'minecraft:lava',Properties:{level:'9'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'9'}}"); -+ register(170, "{Name:'minecraft:lava',Properties:{level:'10'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'10'}}"); -+ register(171, "{Name:'minecraft:lava',Properties:{level:'11'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'11'}}"); -+ register(172, "{Name:'minecraft:lava',Properties:{level:'12'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'12'}}"); -+ register(173, "{Name:'minecraft:lava',Properties:{level:'13'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'13'}}"); -+ register(174, "{Name:'minecraft:lava',Properties:{level:'14'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'14'}}"); -+ register(175, "{Name:'minecraft:lava',Properties:{level:'15'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'15'}}"); -+ register(176, "{Name:'minecraft:lava',Properties:{level:'0'}}", "{Name:'minecraft:lava',Properties:{level:'0'}}"); -+ register(177, "{Name:'minecraft:lava',Properties:{level:'1'}}", "{Name:'minecraft:lava',Properties:{level:'1'}}"); -+ register(178, "{Name:'minecraft:lava',Properties:{level:'2'}}", "{Name:'minecraft:lava',Properties:{level:'2'}}"); -+ register(179, "{Name:'minecraft:lava',Properties:{level:'3'}}", "{Name:'minecraft:lava',Properties:{level:'3'}}"); -+ register(180, "{Name:'minecraft:lava',Properties:{level:'4'}}", "{Name:'minecraft:lava',Properties:{level:'4'}}"); -+ register(181, "{Name:'minecraft:lava',Properties:{level:'5'}}", "{Name:'minecraft:lava',Properties:{level:'5'}}"); -+ register(182, "{Name:'minecraft:lava',Properties:{level:'6'}}", "{Name:'minecraft:lava',Properties:{level:'6'}}"); -+ register(183, "{Name:'minecraft:lava',Properties:{level:'7'}}", "{Name:'minecraft:lava',Properties:{level:'7'}}"); -+ register(184, "{Name:'minecraft:lava',Properties:{level:'8'}}", "{Name:'minecraft:lava',Properties:{level:'8'}}"); -+ register(185, "{Name:'minecraft:lava',Properties:{level:'9'}}", "{Name:'minecraft:lava',Properties:{level:'9'}}"); -+ register(186, "{Name:'minecraft:lava',Properties:{level:'10'}}", "{Name:'minecraft:lava',Properties:{level:'10'}}"); -+ register(187, "{Name:'minecraft:lava',Properties:{level:'11'}}", "{Name:'minecraft:lava',Properties:{level:'11'}}"); -+ register(188, "{Name:'minecraft:lava',Properties:{level:'12'}}", "{Name:'minecraft:lava',Properties:{level:'12'}}"); -+ register(189, "{Name:'minecraft:lava',Properties:{level:'13'}}", "{Name:'minecraft:lava',Properties:{level:'13'}}"); -+ register(190, "{Name:'minecraft:lava',Properties:{level:'14'}}", "{Name:'minecraft:lava',Properties:{level:'14'}}"); -+ register(191, "{Name:'minecraft:lava',Properties:{level:'15'}}", "{Name:'minecraft:lava',Properties:{level:'15'}}"); -+ register(192, "{Name:'minecraft:sand'}", "{Name:'minecraft:sand',Properties:{variant:'sand'}}"); -+ register(193, "{Name:'minecraft:red_sand'}", "{Name:'minecraft:sand',Properties:{variant:'red_sand'}}"); -+ register(208, "{Name:'minecraft:gravel'}", "{Name:'minecraft:gravel'}"); -+ register(224, "{Name:'minecraft:gold_ore'}", "{Name:'minecraft:gold_ore'}"); -+ register(240, "{Name:'minecraft:iron_ore'}", "{Name:'minecraft:iron_ore'}"); -+ register(256, "{Name:'minecraft:coal_ore'}", "{Name:'minecraft:coal_ore'}"); -+ register(272, "{Name:'minecraft:oak_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'oak'}}"); -+ register(273, "{Name:'minecraft:spruce_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'spruce'}}"); -+ register(274, "{Name:'minecraft:birch_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'birch'}}"); -+ register(275, "{Name:'minecraft:jungle_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'jungle'}}"); -+ register(276, "{Name:'minecraft:oak_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'oak'}}"); -+ register(277, "{Name:'minecraft:spruce_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'spruce'}}"); -+ register(278, "{Name:'minecraft:birch_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'birch'}}"); -+ register(279, "{Name:'minecraft:jungle_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'jungle'}}"); -+ register(280, "{Name:'minecraft:oak_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'oak'}}"); -+ register(281, "{Name:'minecraft:spruce_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'spruce'}}"); -+ register(282, "{Name:'minecraft:birch_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'birch'}}"); -+ register(283, "{Name:'minecraft:jungle_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'jungle'}}"); -+ register(284, "{Name:'minecraft:oak_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'oak'}}"); -+ register(285, "{Name:'minecraft:spruce_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'spruce'}}"); -+ register(286, "{Name:'minecraft:birch_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'birch'}}"); -+ register(287, "{Name:'minecraft:jungle_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'jungle'}}"); -+ register(288, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'oak'}}"); -+ register(289, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'spruce'}}"); -+ register(290, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'birch'}}"); -+ register(291, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'jungle'}}"); -+ register(292, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'oak'}}"); -+ register(293, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'spruce'}}"); -+ register(294, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'birch'}}"); -+ register(295, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'jungle'}}"); -+ register(296, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'oak'}}"); -+ register(297, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'spruce'}}"); -+ register(298, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'birch'}}"); -+ register(299, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'jungle'}}"); -+ register(300, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'oak'}}"); -+ register(301, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'spruce'}}"); -+ register(302, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'birch'}}"); -+ register(303, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'jungle'}}"); -+ register(304, "{Name:'minecraft:sponge'}", "{Name:'minecraft:sponge',Properties:{wet:'false'}}"); -+ register(305, "{Name:'minecraft:wet_sponge'}", "{Name:'minecraft:sponge',Properties:{wet:'true'}}"); -+ register(320, "{Name:'minecraft:glass'}", "{Name:'minecraft:glass'}"); -+ register(336, "{Name:'minecraft:lapis_ore'}", "{Name:'minecraft:lapis_ore'}"); -+ register(352, "{Name:'minecraft:lapis_block'}", "{Name:'minecraft:lapis_block'}"); -+ register(368, "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'false'}}"); -+ register(369, "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'false'}}"); -+ register(370, "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'false'}}"); -+ register(371, "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'false'}}"); -+ register(372, "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'false'}}"); -+ register(373, "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'false'}}"); -+ register(376, "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'true'}}"); -+ register(377, "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'true'}}"); -+ register(378, "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'true'}}"); -+ register(379, "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'true'}}"); -+ register(380, "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'true'}}"); -+ register(381, "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'true'}}"); -+ register(384, "{Name:'minecraft:sandstone'}", "{Name:'minecraft:sandstone',Properties:{type:'sandstone'}}"); -+ register(385, "{Name:'minecraft:chiseled_sandstone'}", "{Name:'minecraft:sandstone',Properties:{type:'chiseled_sandstone'}}"); -+ register(386, "{Name:'minecraft:cut_sandstone'}", "{Name:'minecraft:sandstone',Properties:{type:'smooth_sandstone'}}"); -+ register(400, "{Name:'minecraft:note_block'}", "{Name:'minecraft:noteblock'}"); -+ register(416, "{Name:'minecraft:red_bed',Properties:{facing:'south',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'true',part:'foot'}}"); -+ register(417, "{Name:'minecraft:red_bed',Properties:{facing:'west',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'true',part:'foot'}}"); -+ register(418, "{Name:'minecraft:red_bed',Properties:{facing:'north',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'true',part:'foot'}}"); -+ register(419, "{Name:'minecraft:red_bed',Properties:{facing:'east',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'true',part:'foot'}}"); -+ register(424, "{Name:'minecraft:red_bed',Properties:{facing:'south',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'false',part:'head'}}"); -+ register(425, "{Name:'minecraft:red_bed',Properties:{facing:'west',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'false',part:'head'}}"); -+ register(426, "{Name:'minecraft:red_bed',Properties:{facing:'north',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'false',part:'head'}}"); -+ register(427, "{Name:'minecraft:red_bed',Properties:{facing:'east',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'false',part:'head'}}"); -+ register(428, "{Name:'minecraft:red_bed',Properties:{facing:'south',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'true',part:'head'}}"); -+ register(429, "{Name:'minecraft:red_bed',Properties:{facing:'west',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'true',part:'head'}}"); -+ register(430, "{Name:'minecraft:red_bed',Properties:{facing:'north',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'true',part:'head'}}"); -+ register(431, "{Name:'minecraft:red_bed',Properties:{facing:'east',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'true',part:'head'}}"); -+ register(432, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'north_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'north_south'}}"); -+ register(433, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'east_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'east_west'}}"); -+ register(434, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_east'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_east'}}"); -+ register(435, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_west'}}"); -+ register(436, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_north'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_north'}}"); -+ register(437, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_south'}}"); -+ register(440, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'north_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'north_south'}}"); -+ register(441, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'east_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'east_west'}}"); -+ register(442, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_east'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_east'}}"); -+ register(443, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_west'}}"); -+ register(444, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_north'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_north'}}"); -+ register(445, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_south'}}"); -+ register(448, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'north_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'north_south'}}"); -+ register(449, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'east_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'east_west'}}"); -+ register(450, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_east'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_east'}}"); -+ register(451, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_west'}}"); -+ register(452, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_north'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_north'}}"); -+ register(453, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_south'}}"); -+ register(456, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'north_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'north_south'}}"); -+ register(457, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'east_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'east_west'}}"); -+ register(458, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_east'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_east'}}"); -+ register(459, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_west'}}"); -+ register(460, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_north'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_north'}}"); -+ register(461, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_south'}}"); -+ register(464, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'down'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'down'}}"); -+ register(465, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'up'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'up'}}"); -+ register(466, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'north'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'north'}}"); -+ register(467, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'south'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'south'}}"); -+ register(468, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'west'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'west'}}"); -+ register(469, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'east'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'east'}}"); -+ register(472, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'down'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'down'}}"); -+ register(473, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'up'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'up'}}"); -+ register(474, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'north'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'north'}}"); -+ register(475, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'south'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'south'}}"); -+ register(476, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'west'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'west'}}"); -+ register(477, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'east'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'east'}}"); -+ register(480, "{Name:'minecraft:cobweb'}", "{Name:'minecraft:web'}"); -+ register(496, "{Name:'minecraft:dead_bush'}", "{Name:'minecraft:tallgrass',Properties:{type:'dead_bush'}}"); -+ register(497, "{Name:'minecraft:grass'}", "{Name:'minecraft:tallgrass',Properties:{type:'tall_grass'}}"); -+ register(498, "{Name:'minecraft:fern'}", "{Name:'minecraft:tallgrass',Properties:{type:'fern'}}"); -+ register(512, "{Name:'minecraft:dead_bush'}", "{Name:'minecraft:deadbush'}"); -+ register(528, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'down'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'down'}}"); -+ register(529, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'up'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'up'}}"); -+ register(530, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'north'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'north'}}"); -+ register(531, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'south'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'south'}}"); -+ register(532, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'west'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'west'}}"); -+ register(533, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'east'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'east'}}"); -+ register(536, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'down'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'down'}}"); -+ register(537, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'up'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'up'}}"); -+ register(538, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'north'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'north'}}"); -+ register(539, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'south'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'south'}}"); -+ register(540, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'west'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'west'}}"); -+ register(541, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'east'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'east'}}"); -+ register(544, "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'true',type:'normal'}}"); -+ register(545, "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'true',type:'normal'}}"); -+ register(546, "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'true',type:'normal'}}"); -+ register(547, "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'true',type:'normal'}}"); -+ register(548, "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'true',type:'normal'}}"); -+ register(549, "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'true',type:'normal'}}"); -+ register(552, "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'true',type:'sticky'}}"); -+ register(553, "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'true',type:'sticky'}}"); -+ register(554, "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'true',type:'sticky'}}"); -+ register(555, "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'true',type:'sticky'}}"); -+ register(556, "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'true',type:'sticky'}}"); -+ register(557, "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'true',type:'sticky'}}"); -+ register(560, "{Name:'minecraft:white_wool'}", "{Name:'minecraft:wool',Properties:{color:'white'}}"); -+ register(561, "{Name:'minecraft:orange_wool'}", "{Name:'minecraft:wool',Properties:{color:'orange'}}"); -+ register(562, "{Name:'minecraft:magenta_wool'}", "{Name:'minecraft:wool',Properties:{color:'magenta'}}"); -+ register(563, "{Name:'minecraft:light_blue_wool'}", "{Name:'minecraft:wool',Properties:{color:'light_blue'}}"); -+ register(564, "{Name:'minecraft:yellow_wool'}", "{Name:'minecraft:wool',Properties:{color:'yellow'}}"); -+ register(565, "{Name:'minecraft:lime_wool'}", "{Name:'minecraft:wool',Properties:{color:'lime'}}"); -+ register(566, "{Name:'minecraft:pink_wool'}", "{Name:'minecraft:wool',Properties:{color:'pink'}}"); -+ register(567, "{Name:'minecraft:gray_wool'}", "{Name:'minecraft:wool',Properties:{color:'gray'}}"); -+ register(568, "{Name:'minecraft:light_gray_wool'}", "{Name:'minecraft:wool',Properties:{color:'silver'}}"); -+ register(569, "{Name:'minecraft:cyan_wool'}", "{Name:'minecraft:wool',Properties:{color:'cyan'}}"); -+ register(570, "{Name:'minecraft:purple_wool'}", "{Name:'minecraft:wool',Properties:{color:'purple'}}"); -+ register(571, "{Name:'minecraft:blue_wool'}", "{Name:'minecraft:wool',Properties:{color:'blue'}}"); -+ register(572, "{Name:'minecraft:brown_wool'}", "{Name:'minecraft:wool',Properties:{color:'brown'}}"); -+ register(573, "{Name:'minecraft:green_wool'}", "{Name:'minecraft:wool',Properties:{color:'green'}}"); -+ register(574, "{Name:'minecraft:red_wool'}", "{Name:'minecraft:wool',Properties:{color:'red'}}"); -+ register(575, "{Name:'minecraft:black_wool'}", "{Name:'minecraft:wool',Properties:{color:'black'}}"); -+ register(576, "{Name:'minecraft:moving_piston',Properties:{facing:'down',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'down',type:'normal'}}"); -+ register(577, "{Name:'minecraft:moving_piston',Properties:{facing:'up',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'up',type:'normal'}}"); -+ register(578, "{Name:'minecraft:moving_piston',Properties:{facing:'north',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'north',type:'normal'}}"); -+ register(579, "{Name:'minecraft:moving_piston',Properties:{facing:'south',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'south',type:'normal'}}"); -+ register(580, "{Name:'minecraft:moving_piston',Properties:{facing:'west',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'west',type:'normal'}}"); -+ register(581, "{Name:'minecraft:moving_piston',Properties:{facing:'east',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'east',type:'normal'}}"); -+ register(584, "{Name:'minecraft:moving_piston',Properties:{facing:'down',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'down',type:'sticky'}}"); -+ register(585, "{Name:'minecraft:moving_piston',Properties:{facing:'up',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'up',type:'sticky'}}"); -+ register(586, "{Name:'minecraft:moving_piston',Properties:{facing:'north',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'north',type:'sticky'}}"); -+ register(587, "{Name:'minecraft:moving_piston',Properties:{facing:'south',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'south',type:'sticky'}}"); -+ register(588, "{Name:'minecraft:moving_piston',Properties:{facing:'west',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'west',type:'sticky'}}"); -+ register(589, "{Name:'minecraft:moving_piston',Properties:{facing:'east',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'east',type:'sticky'}}"); -+ register(592, "{Name:'minecraft:dandelion'}", "{Name:'minecraft:yellow_flower',Properties:{type:'dandelion'}}"); -+ register(608, "{Name:'minecraft:poppy'}", "{Name:'minecraft:red_flower',Properties:{type:'poppy'}}"); -+ register(609, "{Name:'minecraft:blue_orchid'}", "{Name:'minecraft:red_flower',Properties:{type:'blue_orchid'}}"); -+ register(610, "{Name:'minecraft:allium'}", "{Name:'minecraft:red_flower',Properties:{type:'allium'}}"); -+ register(611, "{Name:'minecraft:azure_bluet'}", "{Name:'minecraft:red_flower',Properties:{type:'houstonia'}}"); -+ register(612, "{Name:'minecraft:red_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'red_tulip'}}"); -+ register(613, "{Name:'minecraft:orange_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'orange_tulip'}}"); -+ register(614, "{Name:'minecraft:white_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'white_tulip'}}"); -+ register(615, "{Name:'minecraft:pink_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'pink_tulip'}}"); -+ register(616, "{Name:'minecraft:oxeye_daisy'}", "{Name:'minecraft:red_flower',Properties:{type:'oxeye_daisy'}}"); -+ register(624, "{Name:'minecraft:brown_mushroom'}", "{Name:'minecraft:brown_mushroom'}"); -+ register(640, "{Name:'minecraft:red_mushroom'}", "{Name:'minecraft:red_mushroom'}"); -+ register(656, "{Name:'minecraft:gold_block'}", "{Name:'minecraft:gold_block'}"); -+ register(672, "{Name:'minecraft:iron_block'}", "{Name:'minecraft:iron_block'}"); -+ register(688, "{Name:'minecraft:stone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'stone'}}"); -+ register(689, "{Name:'minecraft:sandstone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'sandstone'}}"); -+ register(690, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'wood_old'}}"); -+ register(691, "{Name:'minecraft:cobblestone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'cobblestone'}}"); -+ register(692, "{Name:'minecraft:brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'brick'}}"); -+ register(693, "{Name:'minecraft:stone_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'stone_brick'}}"); -+ register(694, "{Name:'minecraft:nether_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'nether_brick'}}"); -+ register(695, "{Name:'minecraft:quartz_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'quartz'}}"); -+ register(696, "{Name:'minecraft:smooth_stone'}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'stone'}}"); -+ register(697, "{Name:'minecraft:smooth_sandstone'}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'sandstone'}}"); -+ register(698, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'wood_old'}}"); -+ register(699, "{Name:'minecraft:cobblestone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'cobblestone'}}"); -+ register(700, "{Name:'minecraft:brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'brick'}}"); -+ register(701, "{Name:'minecraft:stone_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'stone_brick'}}"); -+ register(702, "{Name:'minecraft:nether_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'nether_brick'}}"); -+ register(703, "{Name:'minecraft:smooth_quartz'}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'quartz'}}"); -+ register(704, "{Name:'minecraft:stone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'stone'}}"); -+ register(705, "{Name:'minecraft:sandstone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'sandstone'}}"); -+ register(706, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'wood_old'}}"); -+ register(707, "{Name:'minecraft:cobblestone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'cobblestone'}}"); -+ register(708, "{Name:'minecraft:brick_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'brick'}}"); -+ register(709, "{Name:'minecraft:stone_brick_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'stone_brick'}}"); -+ register(710, "{Name:'minecraft:nether_brick_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'nether_brick'}}"); -+ register(711, "{Name:'minecraft:quartz_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'quartz'}}"); -+ register(712, "{Name:'minecraft:stone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'stone'}}"); -+ register(713, "{Name:'minecraft:sandstone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'sandstone'}}"); -+ register(714, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'wood_old'}}"); -+ register(715, "{Name:'minecraft:cobblestone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'cobblestone'}}"); -+ register(716, "{Name:'minecraft:brick_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'brick'}}"); -+ register(717, "{Name:'minecraft:stone_brick_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'stone_brick'}}"); -+ register(718, "{Name:'minecraft:nether_brick_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'nether_brick'}}"); -+ register(719, "{Name:'minecraft:quartz_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'quartz'}}"); -+ register(720, "{Name:'minecraft:bricks'}", "{Name:'minecraft:brick_block'}"); -+ register(736, "{Name:'minecraft:tnt',Properties:{unstable:'false'}}", "{Name:'minecraft:tnt',Properties:{explode:'false'}}"); -+ register(737, "{Name:'minecraft:tnt',Properties:{unstable:'true'}}", "{Name:'minecraft:tnt',Properties:{explode:'true'}}"); -+ register(752, "{Name:'minecraft:bookshelf'}", "{Name:'minecraft:bookshelf'}"); -+ register(768, "{Name:'minecraft:mossy_cobblestone'}", "{Name:'minecraft:mossy_cobblestone'}"); -+ register(784, "{Name:'minecraft:obsidian'}", "{Name:'minecraft:obsidian'}"); -+ register(801, "{Name:'minecraft:wall_torch',Properties:{facing:'east'}}", "{Name:'minecraft:torch',Properties:{facing:'east'}}"); -+ register(802, "{Name:'minecraft:wall_torch',Properties:{facing:'west'}}", "{Name:'minecraft:torch',Properties:{facing:'west'}}"); -+ register(803, "{Name:'minecraft:wall_torch',Properties:{facing:'south'}}", "{Name:'minecraft:torch',Properties:{facing:'south'}}"); -+ register(804, "{Name:'minecraft:wall_torch',Properties:{facing:'north'}}", "{Name:'minecraft:torch',Properties:{facing:'north'}}"); -+ register(805, "{Name:'minecraft:torch'}", "{Name:'minecraft:torch',Properties:{facing:'up'}}"); -+ register(816, "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(817, "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(818, "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(819, "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(820, "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(821, "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(822, "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(823, "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(824, "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(825, "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(826, "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(827, "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(828, "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(829, "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(830, "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(831, "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(832, "{Name:'minecraft:mob_spawner'}", "{Name:'minecraft:mob_spawner'}"); -+ register(848, "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(849, "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(850, "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(851, "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(852, "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(853, "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(854, "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(855, "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(866, "{Name:'minecraft:chest',Properties:{facing:'north',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'north'}}"); -+ register(867, "{Name:'minecraft:chest',Properties:{facing:'south',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'south'}}"); -+ register(868, "{Name:'minecraft:chest',Properties:{facing:'west',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'west'}}"); -+ register(869, "{Name:'minecraft:chest',Properties:{facing:'east',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'east'}}"); -+ register(880, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'up',west:'up'}}"); -+ register(881, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'up',west:'up'}}"); -+ register(882, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'up',west:'up'}}"); -+ register(883, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'up',west:'up'}}"); -+ register(884, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'up',west:'up'}}"); -+ register(885, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'up',west:'up'}}"); -+ register(886, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'up',west:'up'}}"); -+ register(887, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'up',west:'up'}}"); -+ register(888, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'up',west:'up'}}"); -+ register(889, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'up',west:'up'}}"); -+ register(890, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'up',west:'up'}}"); -+ register(891, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'up',west:'up'}}"); -+ register(892, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'up',west:'up'}}"); -+ register(893, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'up',west:'up'}}"); -+ register(894, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'up',west:'up'}}"); -+ register(895, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'up',west:'up'}}"); -+ register(896, "{Name:'minecraft:diamond_ore'}", "{Name:'minecraft:diamond_ore'}"); -+ register(912, "{Name:'minecraft:diamond_block'}", "{Name:'minecraft:diamond_block'}"); -+ register(928, "{Name:'minecraft:crafting_table'}", "{Name:'minecraft:crafting_table'}"); -+ register(944, "{Name:'minecraft:wheat',Properties:{age:'0'}}", "{Name:'minecraft:wheat',Properties:{age:'0'}}"); -+ register(945, "{Name:'minecraft:wheat',Properties:{age:'1'}}", "{Name:'minecraft:wheat',Properties:{age:'1'}}"); -+ register(946, "{Name:'minecraft:wheat',Properties:{age:'2'}}", "{Name:'minecraft:wheat',Properties:{age:'2'}}"); -+ register(947, "{Name:'minecraft:wheat',Properties:{age:'3'}}", "{Name:'minecraft:wheat',Properties:{age:'3'}}"); -+ register(948, "{Name:'minecraft:wheat',Properties:{age:'4'}}", "{Name:'minecraft:wheat',Properties:{age:'4'}}"); -+ register(949, "{Name:'minecraft:wheat',Properties:{age:'5'}}", "{Name:'minecraft:wheat',Properties:{age:'5'}}"); -+ register(950, "{Name:'minecraft:wheat',Properties:{age:'6'}}", "{Name:'minecraft:wheat',Properties:{age:'6'}}"); -+ register(951, "{Name:'minecraft:wheat',Properties:{age:'7'}}", "{Name:'minecraft:wheat',Properties:{age:'7'}}"); -+ register(960, "{Name:'minecraft:farmland',Properties:{moisture:'0'}}", "{Name:'minecraft:farmland',Properties:{moisture:'0'}}"); -+ register(961, "{Name:'minecraft:farmland',Properties:{moisture:'1'}}", "{Name:'minecraft:farmland',Properties:{moisture:'1'}}"); -+ register(962, "{Name:'minecraft:farmland',Properties:{moisture:'2'}}", "{Name:'minecraft:farmland',Properties:{moisture:'2'}}"); -+ register(963, "{Name:'minecraft:farmland',Properties:{moisture:'3'}}", "{Name:'minecraft:farmland',Properties:{moisture:'3'}}"); -+ register(964, "{Name:'minecraft:farmland',Properties:{moisture:'4'}}", "{Name:'minecraft:farmland',Properties:{moisture:'4'}}"); -+ register(965, "{Name:'minecraft:farmland',Properties:{moisture:'5'}}", "{Name:'minecraft:farmland',Properties:{moisture:'5'}}"); -+ register(966, "{Name:'minecraft:farmland',Properties:{moisture:'6'}}", "{Name:'minecraft:farmland',Properties:{moisture:'6'}}"); -+ register(967, "{Name:'minecraft:farmland',Properties:{moisture:'7'}}", "{Name:'minecraft:farmland',Properties:{moisture:'7'}}"); -+ register(978, "{Name:'minecraft:furnace',Properties:{facing:'north',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'north'}}"); -+ register(979, "{Name:'minecraft:furnace',Properties:{facing:'south',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'south'}}"); -+ register(980, "{Name:'minecraft:furnace',Properties:{facing:'west',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'west'}}"); -+ register(981, "{Name:'minecraft:furnace',Properties:{facing:'east',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'east'}}"); -+ register(994, "{Name:'minecraft:furnace',Properties:{facing:'north',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'north'}}"); -+ register(995, "{Name:'minecraft:furnace',Properties:{facing:'south',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'south'}}"); -+ register(996, "{Name:'minecraft:furnace',Properties:{facing:'west',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'west'}}"); -+ register(997, "{Name:'minecraft:furnace',Properties:{facing:'east',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'east'}}"); -+ register(1008, "{Name:'minecraft:sign',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'0'}}"); -+ register(1009, "{Name:'minecraft:sign',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'1'}}"); -+ register(1010, "{Name:'minecraft:sign',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'2'}}"); -+ register(1011, "{Name:'minecraft:sign',Properties:{rotation:'3'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'3'}}"); -+ register(1012, "{Name:'minecraft:sign',Properties:{rotation:'4'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'4'}}"); -+ register(1013, "{Name:'minecraft:sign',Properties:{rotation:'5'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'5'}}"); -+ register(1014, "{Name:'minecraft:sign',Properties:{rotation:'6'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'6'}}"); -+ register(1015, "{Name:'minecraft:sign',Properties:{rotation:'7'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'7'}}"); -+ register(1016, "{Name:'minecraft:sign',Properties:{rotation:'8'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'8'}}"); -+ register(1017, "{Name:'minecraft:sign',Properties:{rotation:'9'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'9'}}"); -+ register(1018, "{Name:'minecraft:sign',Properties:{rotation:'10'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'10'}}"); -+ register(1019, "{Name:'minecraft:sign',Properties:{rotation:'11'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'11'}}"); -+ register(1020, "{Name:'minecraft:sign',Properties:{rotation:'12'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'12'}}"); -+ register(1021, "{Name:'minecraft:sign',Properties:{rotation:'13'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'13'}}"); -+ register(1022, "{Name:'minecraft:sign',Properties:{rotation:'14'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'14'}}"); -+ register(1023, "{Name:'minecraft:sign',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'15'}}"); -+ register(1024, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1025, "{Name:'minecraft:oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1026, "{Name:'minecraft:oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1027, "{Name:'minecraft:oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1028, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1029, "{Name:'minecraft:oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1030, "{Name:'minecraft:oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1031, "{Name:'minecraft:oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1032, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1033, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(1034, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(1035, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(1036, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1037, "{Name:'minecraft:oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1038, "{Name:'minecraft:oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1039, "{Name:'minecraft:oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1042, "{Name:'minecraft:ladder',Properties:{facing:'north'}}", "{Name:'minecraft:ladder',Properties:{facing:'north'}}"); -+ register(1043, "{Name:'minecraft:ladder',Properties:{facing:'south'}}", "{Name:'minecraft:ladder',Properties:{facing:'south'}}"); -+ register(1044, "{Name:'minecraft:ladder',Properties:{facing:'west'}}", "{Name:'minecraft:ladder',Properties:{facing:'west'}}"); -+ register(1045, "{Name:'minecraft:ladder',Properties:{facing:'east'}}", "{Name:'minecraft:ladder',Properties:{facing:'east'}}"); -+ register(1056, "{Name:'minecraft:rail',Properties:{shape:'north_south'}}", "{Name:'minecraft:rail',Properties:{shape:'north_south'}}"); -+ register(1057, "{Name:'minecraft:rail',Properties:{shape:'east_west'}}", "{Name:'minecraft:rail',Properties:{shape:'east_west'}}"); -+ register(1058, "{Name:'minecraft:rail',Properties:{shape:'ascending_east'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_east'}}"); -+ register(1059, "{Name:'minecraft:rail',Properties:{shape:'ascending_west'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_west'}}"); -+ register(1060, "{Name:'minecraft:rail',Properties:{shape:'ascending_north'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_north'}}"); -+ register(1061, "{Name:'minecraft:rail',Properties:{shape:'ascending_south'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_south'}}"); -+ register(1062, "{Name:'minecraft:rail',Properties:{shape:'south_east'}}", "{Name:'minecraft:rail',Properties:{shape:'south_east'}}"); -+ register(1063, "{Name:'minecraft:rail',Properties:{shape:'south_west'}}", "{Name:'minecraft:rail',Properties:{shape:'south_west'}}"); -+ register(1064, "{Name:'minecraft:rail',Properties:{shape:'north_west'}}", "{Name:'minecraft:rail',Properties:{shape:'north_west'}}"); -+ register(1065, "{Name:'minecraft:rail',Properties:{shape:'north_east'}}", "{Name:'minecraft:rail',Properties:{shape:'north_east'}}"); -+ register(1072, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(1073, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(1074, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(1075, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(1076, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(1077, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(1078, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(1079, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(1090, "{Name:'minecraft:wall_sign',Properties:{facing:'north'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'north'}}"); -+ register(1091, "{Name:'minecraft:wall_sign',Properties:{facing:'south'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'south'}}"); -+ register(1092, "{Name:'minecraft:wall_sign',Properties:{facing:'west'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'west'}}"); -+ register(1093, "{Name:'minecraft:wall_sign',Properties:{facing:'east'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'east'}}"); -+ register(1104, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'west',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'down_x',powered:'false'}}"); -+ register(1105, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'east',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'east',powered:'false'}}"); -+ register(1106, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'west',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'west',powered:'false'}}"); -+ register(1107, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'south',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'south',powered:'false'}}"); -+ register(1108, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'north',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'north',powered:'false'}}"); -+ register(1109, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'north',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'up_z',powered:'false'}}"); -+ register(1110, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'west',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'up_x',powered:'false'}}"); -+ register(1111, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'north',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'down_z',powered:'false'}}"); -+ register(1112, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'west',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'down_x',powered:'true'}}"); -+ register(1113, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'east',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'east',powered:'true'}}"); -+ register(1114, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'west',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'west',powered:'true'}}"); -+ register(1115, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'south',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'south',powered:'true'}}"); -+ register(1116, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'north',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'north',powered:'true'}}"); -+ register(1117, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'north',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'up_z',powered:'true'}}"); -+ register(1118, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'west',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'up_x',powered:'true'}}"); -+ register(1119, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'north',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'down_z',powered:'true'}}"); -+ register(1120, "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'false'}}", "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'false'}}"); -+ register(1121, "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'true'}}", "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'true'}}"); -+ register(1136, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1137, "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1138, "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1139, "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1140, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1141, "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1142, "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1143, "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1144, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1145, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(1146, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(1147, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(1148, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1149, "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1150, "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1151, "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1152, "{Name:'minecraft:oak_pressure_plate',Properties:{powered:'false'}}", "{Name:'minecraft:wooden_pressure_plate',Properties:{powered:'false'}}"); -+ register(1153, "{Name:'minecraft:oak_pressure_plate',Properties:{powered:'true'}}", "{Name:'minecraft:wooden_pressure_plate',Properties:{powered:'true'}}"); -+ register(1168, "{Name:'minecraft:redstone_ore',Properties:{lit:'false'}}", "{Name:'minecraft:redstone_ore'}"); -+ register(1184, "{Name:'minecraft:redstone_ore',Properties:{lit:'true'}}", "{Name:'minecraft:lit_redstone_ore'}"); -+ register(1201, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'east',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'east'}}"); -+ register(1202, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'west',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'west'}}"); -+ register(1203, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'south',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'south'}}"); -+ register(1204, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'north',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'north'}}"); -+ register(1205, "{Name:'minecraft:redstone_torch',Properties:{lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'up'}}"); -+ register(1217, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'east',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'east'}}"); -+ register(1218, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'west',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'west'}}"); -+ register(1219, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'south',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'south'}}"); -+ register(1220, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'north',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'north'}}"); -+ register(1221, "{Name:'minecraft:redstone_torch',Properties:{lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'up'}}"); -+ register(1232, "{Name:'minecraft:stone_button',Properties:{face:'ceiling',facing:'north',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'down',powered:'false'}}"); -+ register(1233, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'east',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'east',powered:'false'}}"); -+ register(1234, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'west',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'west',powered:'false'}}"); -+ register(1235, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'south',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'south',powered:'false'}}"); -+ register(1236, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'north',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'north',powered:'false'}}"); -+ register(1237, "{Name:'minecraft:stone_button',Properties:{face:'floor',facing:'north',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'up',powered:'false'}}"); -+ register(1240, "{Name:'minecraft:stone_button',Properties:{face:'ceiling',facing:'north',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'down',powered:'true'}}"); -+ register(1241, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'east',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'east',powered:'true'}}"); -+ register(1242, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'west',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'west',powered:'true'}}"); -+ register(1243, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'south',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'south',powered:'true'}}"); -+ register(1244, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'north',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'north',powered:'true'}}"); -+ register(1245, "{Name:'minecraft:stone_button',Properties:{face:'floor',facing:'north',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'up',powered:'true'}}"); -+ register(1248, "{Name:'minecraft:snow',Properties:{layers:'1'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'1'}}"); -+ register(1249, "{Name:'minecraft:snow',Properties:{layers:'2'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'2'}}"); -+ register(1250, "{Name:'minecraft:snow',Properties:{layers:'3'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'3'}}"); -+ register(1251, "{Name:'minecraft:snow',Properties:{layers:'4'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'4'}}"); -+ register(1252, "{Name:'minecraft:snow',Properties:{layers:'5'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'5'}}"); -+ register(1253, "{Name:'minecraft:snow',Properties:{layers:'6'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'6'}}"); -+ register(1254, "{Name:'minecraft:snow',Properties:{layers:'7'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'7'}}"); -+ register(1255, "{Name:'minecraft:snow',Properties:{layers:'8'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'8'}}"); -+ register(1264, "{Name:'minecraft:ice'}", "{Name:'minecraft:ice'}"); -+ register(1280, "{Name:'minecraft:snow_block'}", "{Name:'minecraft:snow'}"); -+ register(1296, "{Name:'minecraft:cactus',Properties:{age:'0'}}", "{Name:'minecraft:cactus',Properties:{age:'0'}}"); -+ register(1297, "{Name:'minecraft:cactus',Properties:{age:'1'}}", "{Name:'minecraft:cactus',Properties:{age:'1'}}"); -+ register(1298, "{Name:'minecraft:cactus',Properties:{age:'2'}}", "{Name:'minecraft:cactus',Properties:{age:'2'}}"); -+ register(1299, "{Name:'minecraft:cactus',Properties:{age:'3'}}", "{Name:'minecraft:cactus',Properties:{age:'3'}}"); -+ register(1300, "{Name:'minecraft:cactus',Properties:{age:'4'}}", "{Name:'minecraft:cactus',Properties:{age:'4'}}"); -+ register(1301, "{Name:'minecraft:cactus',Properties:{age:'5'}}", "{Name:'minecraft:cactus',Properties:{age:'5'}}"); -+ register(1302, "{Name:'minecraft:cactus',Properties:{age:'6'}}", "{Name:'minecraft:cactus',Properties:{age:'6'}}"); -+ register(1303, "{Name:'minecraft:cactus',Properties:{age:'7'}}", "{Name:'minecraft:cactus',Properties:{age:'7'}}"); -+ register(1304, "{Name:'minecraft:cactus',Properties:{age:'8'}}", "{Name:'minecraft:cactus',Properties:{age:'8'}}"); -+ register(1305, "{Name:'minecraft:cactus',Properties:{age:'9'}}", "{Name:'minecraft:cactus',Properties:{age:'9'}}"); -+ register(1306, "{Name:'minecraft:cactus',Properties:{age:'10'}}", "{Name:'minecraft:cactus',Properties:{age:'10'}}"); -+ register(1307, "{Name:'minecraft:cactus',Properties:{age:'11'}}", "{Name:'minecraft:cactus',Properties:{age:'11'}}"); -+ register(1308, "{Name:'minecraft:cactus',Properties:{age:'12'}}", "{Name:'minecraft:cactus',Properties:{age:'12'}}"); -+ register(1309, "{Name:'minecraft:cactus',Properties:{age:'13'}}", "{Name:'minecraft:cactus',Properties:{age:'13'}}"); -+ register(1310, "{Name:'minecraft:cactus',Properties:{age:'14'}}", "{Name:'minecraft:cactus',Properties:{age:'14'}}"); -+ register(1311, "{Name:'minecraft:cactus',Properties:{age:'15'}}", "{Name:'minecraft:cactus',Properties:{age:'15'}}"); -+ register(1312, "{Name:'minecraft:clay'}", "{Name:'minecraft:clay'}"); -+ register(1328, "{Name:'minecraft:sugar_cane',Properties:{age:'0'}}", "{Name:'minecraft:reeds',Properties:{age:'0'}}"); -+ register(1329, "{Name:'minecraft:sugar_cane',Properties:{age:'1'}}", "{Name:'minecraft:reeds',Properties:{age:'1'}}"); -+ register(1330, "{Name:'minecraft:sugar_cane',Properties:{age:'2'}}", "{Name:'minecraft:reeds',Properties:{age:'2'}}"); -+ register(1331, "{Name:'minecraft:sugar_cane',Properties:{age:'3'}}", "{Name:'minecraft:reeds',Properties:{age:'3'}}"); -+ register(1332, "{Name:'minecraft:sugar_cane',Properties:{age:'4'}}", "{Name:'minecraft:reeds',Properties:{age:'4'}}"); -+ register(1333, "{Name:'minecraft:sugar_cane',Properties:{age:'5'}}", "{Name:'minecraft:reeds',Properties:{age:'5'}}"); -+ register(1334, "{Name:'minecraft:sugar_cane',Properties:{age:'6'}}", "{Name:'minecraft:reeds',Properties:{age:'6'}}"); -+ register(1335, "{Name:'minecraft:sugar_cane',Properties:{age:'7'}}", "{Name:'minecraft:reeds',Properties:{age:'7'}}"); -+ register(1336, "{Name:'minecraft:sugar_cane',Properties:{age:'8'}}", "{Name:'minecraft:reeds',Properties:{age:'8'}}"); -+ register(1337, "{Name:'minecraft:sugar_cane',Properties:{age:'9'}}", "{Name:'minecraft:reeds',Properties:{age:'9'}}"); -+ register(1338, "{Name:'minecraft:sugar_cane',Properties:{age:'10'}}", "{Name:'minecraft:reeds',Properties:{age:'10'}}"); -+ register(1339, "{Name:'minecraft:sugar_cane',Properties:{age:'11'}}", "{Name:'minecraft:reeds',Properties:{age:'11'}}"); -+ register(1340, "{Name:'minecraft:sugar_cane',Properties:{age:'12'}}", "{Name:'minecraft:reeds',Properties:{age:'12'}}"); -+ register(1341, "{Name:'minecraft:sugar_cane',Properties:{age:'13'}}", "{Name:'minecraft:reeds',Properties:{age:'13'}}"); -+ register(1342, "{Name:'minecraft:sugar_cane',Properties:{age:'14'}}", "{Name:'minecraft:reeds',Properties:{age:'14'}}"); -+ register(1343, "{Name:'minecraft:sugar_cane',Properties:{age:'15'}}", "{Name:'minecraft:reeds',Properties:{age:'15'}}"); -+ register(1344, "{Name:'minecraft:jukebox',Properties:{has_record:'false'}}", "{Name:'minecraft:jukebox',Properties:{has_record:'false'}}"); -+ register(1345, "{Name:'minecraft:jukebox',Properties:{has_record:'true'}}", "{Name:'minecraft:jukebox',Properties:{has_record:'true'}}"); -+ register(1360, "{Name:'minecraft:oak_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(1376, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'south'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'south'}}"); -+ register(1377, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'west'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'west'}}"); -+ register(1378, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'north'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'north'}}"); -+ register(1379, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'east'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'east'}}"); -+ register(1392, "{Name:'minecraft:netherrack'}", "{Name:'minecraft:netherrack'}"); -+ register(1408, "{Name:'minecraft:soul_sand'}", "{Name:'minecraft:soul_sand'}"); -+ register(1424, "{Name:'minecraft:glowstone'}", "{Name:'minecraft:glowstone'}"); -+ register(1441, "{Name:'minecraft:portal',Properties:{axis:'x'}}", "{Name:'minecraft:portal',Properties:{axis:'x'}}"); -+ register(1442, "{Name:'minecraft:portal',Properties:{axis:'z'}}", "{Name:'minecraft:portal',Properties:{axis:'z'}}"); -+ register(1456, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'south'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'south'}}"); -+ register(1457, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'west'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'west'}}"); -+ register(1458, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'north'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'north'}}"); -+ register(1459, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'east'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'east'}}"); -+ register(1472, "{Name:'minecraft:cake',Properties:{bites:'0'}}", "{Name:'minecraft:cake',Properties:{bites:'0'}}"); -+ register(1473, "{Name:'minecraft:cake',Properties:{bites:'1'}}", "{Name:'minecraft:cake',Properties:{bites:'1'}}"); -+ register(1474, "{Name:'minecraft:cake',Properties:{bites:'2'}}", "{Name:'minecraft:cake',Properties:{bites:'2'}}"); -+ register(1475, "{Name:'minecraft:cake',Properties:{bites:'3'}}", "{Name:'minecraft:cake',Properties:{bites:'3'}}"); -+ register(1476, "{Name:'minecraft:cake',Properties:{bites:'4'}}", "{Name:'minecraft:cake',Properties:{bites:'4'}}"); -+ register(1477, "{Name:'minecraft:cake',Properties:{bites:'5'}}", "{Name:'minecraft:cake',Properties:{bites:'5'}}"); -+ register(1478, "{Name:'minecraft:cake',Properties:{bites:'6'}}", "{Name:'minecraft:cake',Properties:{bites:'6'}}"); -+ register(1488, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'south',locked:'true'}}"); -+ register(1489, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'west',locked:'true'}}"); -+ register(1490, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'north',locked:'true'}}"); -+ register(1491, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'east',locked:'true'}}"); -+ register(1492, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'south',locked:'true'}}"); -+ register(1493, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'west',locked:'true'}}"); -+ register(1494, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'north',locked:'true'}}"); -+ register(1495, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'east',locked:'true'}}"); -+ register(1496, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'south',locked:'true'}}"); -+ register(1497, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'west',locked:'true'}}"); -+ register(1498, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'north',locked:'true'}}"); -+ register(1499, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'east',locked:'true'}}"); -+ register(1500, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'south',locked:'true'}}"); -+ register(1501, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'west',locked:'true'}}"); -+ register(1502, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'north',locked:'true'}}"); -+ register(1503, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'east',locked:'true'}}"); -+ register(1504, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'south',locked:'true'}}"); -+ register(1505, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'west',locked:'true'}}"); -+ register(1506, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'north',locked:'true'}}"); -+ register(1507, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'east',locked:'true'}}"); -+ register(1508, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'south',locked:'true'}}"); -+ register(1509, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'west',locked:'true'}}"); -+ register(1510, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'north',locked:'true'}}"); -+ register(1511, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'east',locked:'true'}}"); -+ register(1512, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'south',locked:'true'}}"); -+ register(1513, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'west',locked:'true'}}"); -+ register(1514, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'north',locked:'true'}}"); -+ register(1515, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'east',locked:'true'}}"); -+ register(1516, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'south',locked:'true'}}"); -+ register(1517, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'west',locked:'true'}}"); -+ register(1518, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'north',locked:'true'}}"); -+ register(1519, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'east',locked:'true'}}"); -+ register(1520, "{Name:'minecraft:white_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'white'}}"); -+ register(1521, "{Name:'minecraft:orange_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'orange'}}"); -+ register(1522, "{Name:'minecraft:magenta_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'magenta'}}"); -+ register(1523, "{Name:'minecraft:light_blue_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'light_blue'}}"); -+ register(1524, "{Name:'minecraft:yellow_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'yellow'}}"); -+ register(1525, "{Name:'minecraft:lime_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'lime'}}"); -+ register(1526, "{Name:'minecraft:pink_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'pink'}}"); -+ register(1527, "{Name:'minecraft:gray_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'gray'}}"); -+ register(1528, "{Name:'minecraft:light_gray_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'silver'}}"); -+ register(1529, "{Name:'minecraft:cyan_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'cyan'}}"); -+ register(1530, "{Name:'minecraft:purple_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'purple'}}"); -+ register(1531, "{Name:'minecraft:blue_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'blue'}}"); -+ register(1532, "{Name:'minecraft:brown_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'brown'}}"); -+ register(1533, "{Name:'minecraft:green_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'green'}}"); -+ register(1534, "{Name:'minecraft:red_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'red'}}"); -+ register(1535, "{Name:'minecraft:black_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'black'}}"); -+ register(1536, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}"); -+ register(1537, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}"); -+ register(1538, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}"); -+ register(1539, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}"); -+ register(1540, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}"); -+ register(1541, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}"); -+ register(1542, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}"); -+ register(1543, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}"); -+ register(1544, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'top',open:'false'}}"); -+ register(1545, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'top',open:'false'}}"); -+ register(1546, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'top',open:'false'}}"); -+ register(1547, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'top',open:'false'}}"); -+ register(1548, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'top',open:'true'}}"); -+ register(1549, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'top',open:'true'}}"); -+ register(1550, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'top',open:'true'}}"); -+ register(1551, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'top',open:'true'}}"); -+ register(1552, "{Name:'minecraft:infested_stone'}", "{Name:'minecraft:monster_egg',Properties:{variant:'stone'}}"); -+ register(1553, "{Name:'minecraft:infested_cobblestone'}", "{Name:'minecraft:monster_egg',Properties:{variant:'cobblestone'}}"); -+ register(1554, "{Name:'minecraft:infested_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'stone_brick'}}"); -+ register(1555, "{Name:'minecraft:infested_mossy_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'mossy_brick'}}"); -+ register(1556, "{Name:'minecraft:infested_cracked_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'cracked_brick'}}"); -+ register(1557, "{Name:'minecraft:infested_chiseled_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'chiseled_brick'}}"); -+ register(1568, "{Name:'minecraft:stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'stonebrick'}}"); -+ register(1569, "{Name:'minecraft:mossy_stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'mossy_stonebrick'}}"); -+ register(1570, "{Name:'minecraft:cracked_stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'cracked_stonebrick'}}"); -+ register(1571, "{Name:'minecraft:chiseled_stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'chiseled_stonebrick'}}"); -+ register(1584, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'all_inside'}}"); -+ register(1585, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'north_west'}}"); -+ register(1586, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'north'}}"); -+ register(1587, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'north_east'}}"); -+ register(1588, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'west'}}"); -+ register(1589, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'center'}}"); -+ register(1590, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'east'}}"); -+ register(1591, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'south_west'}}"); -+ register(1592, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'south'}}"); -+ register(1593, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'true',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'south_east'}}"); -+ register(1594, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'false',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'stem'}}"); -+ register(1595, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); -+ register(1596, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); -+ register(1597, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); -+ register(1598, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'all_outside'}}"); -+ register(1599, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'all_stem'}}"); -+ register(1600, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'all_inside'}}"); -+ register(1601, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'north_west'}}"); -+ register(1602, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'north'}}"); -+ register(1603, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'north_east'}}"); -+ register(1604, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'west'}}"); -+ register(1605, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'center'}}"); -+ register(1606, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'east'}}"); -+ register(1607, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'south_west'}}"); -+ register(1608, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'south'}}"); -+ register(1609, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'true',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'south_east'}}"); -+ register(1610, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'false',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'stem'}}"); -+ register(1611, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); -+ register(1612, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); -+ register(1613, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); -+ register(1614, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'all_outside'}}"); -+ register(1615, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'all_stem'}}"); -+ register(1616, "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(1632, "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(1648, "{Name:'minecraft:melon_block'}", "{Name:'minecraft:melon_block'}"); -+ register(1664, "{Name:'minecraft:pumpkin_stem',Properties:{age:'0'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'west'}}"); -+ register(1665, "{Name:'minecraft:pumpkin_stem',Properties:{age:'1'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'west'}}"); -+ register(1666, "{Name:'minecraft:pumpkin_stem',Properties:{age:'2'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'west'}}"); -+ register(1667, "{Name:'minecraft:pumpkin_stem',Properties:{age:'3'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'west'}}"); -+ register(1668, "{Name:'minecraft:pumpkin_stem',Properties:{age:'4'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'west'}}"); -+ register(1669, "{Name:'minecraft:pumpkin_stem',Properties:{age:'5'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'west'}}"); -+ register(1670, "{Name:'minecraft:pumpkin_stem',Properties:{age:'6'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'west'}}"); -+ register(1671, "{Name:'minecraft:pumpkin_stem',Properties:{age:'7'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'west'}}"); -+ register(1680, "{Name:'minecraft:melon_stem',Properties:{age:'0'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'west'}}"); -+ register(1681, "{Name:'minecraft:melon_stem',Properties:{age:'1'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'west'}}"); -+ register(1682, "{Name:'minecraft:melon_stem',Properties:{age:'2'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'west'}}"); -+ register(1683, "{Name:'minecraft:melon_stem',Properties:{age:'3'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'west'}}"); -+ register(1684, "{Name:'minecraft:melon_stem',Properties:{age:'4'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'west'}}"); -+ register(1685, "{Name:'minecraft:melon_stem',Properties:{age:'5'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'west'}}"); -+ register(1686, "{Name:'minecraft:melon_stem',Properties:{age:'6'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'west'}}"); -+ register(1687, "{Name:'minecraft:melon_stem',Properties:{age:'7'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'west'}}"); -+ register(1696, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'false'}}"); -+ register(1697, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'false'}}"); -+ register(1698, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'true'}}"); -+ register(1699, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'true'}}"); -+ register(1700, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'false'}}"); -+ register(1701, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'false'}}"); -+ register(1702, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'true'}}"); -+ register(1703, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(1704, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'false'}}"); -+ register(1705, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'false'}}"); -+ register(1706, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'true'}}"); -+ register(1707, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'true'}}"); -+ register(1708, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'false'}}"); -+ register(1709, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'false'}}"); -+ register(1710, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'true'}}"); -+ register(1711, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(1712, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); -+ register(1713, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); -+ register(1714, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); -+ register(1715, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); -+ register(1716, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); -+ register(1717, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); -+ register(1718, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); -+ register(1719, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); -+ register(1720, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); -+ register(1721, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); -+ register(1722, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); -+ register(1723, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); -+ register(1724, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); -+ register(1725, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); -+ register(1726, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); -+ register(1727, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); -+ register(1728, "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(1729, "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(1730, "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(1731, "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(1732, "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(1733, "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(1734, "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(1735, "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(1744, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(1745, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(1746, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(1747, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(1748, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(1749, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(1750, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(1751, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(1760, "{Name:'minecraft:mycelium',Properties:{snowy:'false'}}", "{Name:'minecraft:mycelium',Properties:{snowy:'false'}}", "{Name:'minecraft:mycelium',Properties:{snowy:'true'}}"); -+ register(1776, "{Name:'minecraft:lily_pad'}", "{Name:'minecraft:waterlily'}"); -+ register(1792, "{Name:'minecraft:nether_bricks'}", "{Name:'minecraft:nether_brick'}"); -+ register(1808, "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(1824, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(1825, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(1826, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(1827, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(1828, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(1829, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(1830, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(1831, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(1840, "{Name:'minecraft:nether_wart',Properties:{age:'0'}}", "{Name:'minecraft:nether_wart',Properties:{age:'0'}}"); -+ register(1841, "{Name:'minecraft:nether_wart',Properties:{age:'1'}}", "{Name:'minecraft:nether_wart',Properties:{age:'1'}}"); -+ register(1842, "{Name:'minecraft:nether_wart',Properties:{age:'2'}}", "{Name:'minecraft:nether_wart',Properties:{age:'2'}}"); -+ register(1843, "{Name:'minecraft:nether_wart',Properties:{age:'3'}}", "{Name:'minecraft:nether_wart',Properties:{age:'3'}}"); -+ register(1856, "{Name:'minecraft:enchanting_table'}", "{Name:'minecraft:enchanting_table'}"); -+ register(1872, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'false'}}"); -+ register(1873, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'false'}}"); -+ register(1874, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'false'}}"); -+ register(1875, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'false'}}"); -+ register(1876, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'true'}}"); -+ register(1877, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'true'}}"); -+ register(1878, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'true'}}"); -+ register(1879, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'true'}}"); -+ register(1888, "{Name:'minecraft:cauldron',Properties:{level:'0'}}", "{Name:'minecraft:cauldron',Properties:{level:'0'}}"); -+ register(1889, "{Name:'minecraft:cauldron',Properties:{level:'1'}}", "{Name:'minecraft:cauldron',Properties:{level:'1'}}"); -+ register(1890, "{Name:'minecraft:cauldron',Properties:{level:'2'}}", "{Name:'minecraft:cauldron',Properties:{level:'2'}}"); -+ register(1891, "{Name:'minecraft:cauldron',Properties:{level:'3'}}", "{Name:'minecraft:cauldron',Properties:{level:'3'}}"); -+ register(1904, "{Name:'minecraft:end_portal'}", "{Name:'minecraft:end_portal'}"); -+ register(1920, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'south'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'south'}}"); -+ register(1921, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'west'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'west'}}"); -+ register(1922, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'north'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'north'}}"); -+ register(1923, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'east'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'east'}}"); -+ register(1924, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'south'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'south'}}"); -+ register(1925, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'west'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'west'}}"); -+ register(1926, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'north'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'north'}}"); -+ register(1927, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'east'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'east'}}"); -+ register(1936, "{Name:'minecraft:end_stone'}", "{Name:'minecraft:end_stone'}"); -+ register(1952, "{Name:'minecraft:dragon_egg'}", "{Name:'minecraft:dragon_egg'}"); -+ register(1968, "{Name:'minecraft:redstone_lamp',Properties:{lit:'false'}}", "{Name:'minecraft:redstone_lamp'}"); -+ register(1984, "{Name:'minecraft:redstone_lamp',Properties:{lit:'true'}}", "{Name:'minecraft:lit_redstone_lamp'}"); -+ register(2000, "{Name:'minecraft:oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'oak'}}"); -+ register(2001, "{Name:'minecraft:spruce_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'spruce'}}"); -+ register(2002, "{Name:'minecraft:birch_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'birch'}}"); -+ register(2003, "{Name:'minecraft:jungle_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'jungle'}}"); -+ register(2004, "{Name:'minecraft:acacia_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'acacia'}}"); -+ register(2005, "{Name:'minecraft:dark_oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'dark_oak'}}"); -+ register(2016, "{Name:'minecraft:oak_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'oak'}}"); -+ register(2017, "{Name:'minecraft:spruce_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'spruce'}}"); -+ register(2018, "{Name:'minecraft:birch_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'birch'}}"); -+ register(2019, "{Name:'minecraft:jungle_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'jungle'}}"); -+ register(2020, "{Name:'minecraft:acacia_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'acacia'}}"); -+ register(2021, "{Name:'minecraft:dark_oak_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'dark_oak'}}"); -+ register(2024, "{Name:'minecraft:oak_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'oak'}}"); -+ register(2025, "{Name:'minecraft:spruce_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'spruce'}}"); -+ register(2026, "{Name:'minecraft:birch_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'birch'}}"); -+ register(2027, "{Name:'minecraft:jungle_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'jungle'}}"); -+ register(2028, "{Name:'minecraft:acacia_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'acacia'}}"); -+ register(2029, "{Name:'minecraft:dark_oak_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'dark_oak'}}"); -+ register(2032, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'south'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'south'}}"); -+ register(2033, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'west'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'west'}}"); -+ register(2034, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'north'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'north'}}"); -+ register(2035, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'east'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'east'}}"); -+ register(2036, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'south'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'south'}}"); -+ register(2037, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'west'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'west'}}"); -+ register(2038, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'north'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'north'}}"); -+ register(2039, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'east'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'east'}}"); -+ register(2040, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'south'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'south'}}"); -+ register(2041, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'west'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'west'}}"); -+ register(2042, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'north'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'north'}}"); -+ register(2043, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'east'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'east'}}"); -+ register(2048, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2049, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2050, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2051, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2052, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2053, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2054, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2055, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2064, "{Name:'minecraft:emerald_ore'}", "{Name:'minecraft:emerald_ore'}"); -+ register(2082, "{Name:'minecraft:ender_chest',Properties:{facing:'north'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'north'}}"); -+ register(2083, "{Name:'minecraft:ender_chest',Properties:{facing:'south'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'south'}}"); -+ register(2084, "{Name:'minecraft:ender_chest',Properties:{facing:'west'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'west'}}"); -+ register(2085, "{Name:'minecraft:ender_chest',Properties:{facing:'east'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'east'}}"); -+ register(2096, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'false'}}"); -+ register(2097, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'false'}}"); -+ register(2098, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'false'}}"); -+ register(2099, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'false'}}"); -+ register(2100, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'false'}}"); -+ register(2101, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'false'}}"); -+ register(2102, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'false'}}"); -+ register(2103, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'false'}}"); -+ register(2104, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'true'}}"); -+ register(2105, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'true'}}"); -+ register(2106, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'true'}}"); -+ register(2107, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'true'}}"); -+ register(2108, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'true'}}"); -+ register(2109, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'true'}}"); -+ register(2110, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'true'}}"); -+ register(2111, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'true'}}"); -+ register(2112, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); -+ register(2113, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); -+ register(2114, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); -+ register(2115, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}"); -+ register(2116, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); -+ register(2117, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); -+ register(2118, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); -+ register(2119, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}"); -+ register(2120, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); -+ register(2121, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); -+ register(2122, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); -+ register(2123, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}"); -+ register(2124, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); -+ register(2125, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); -+ register(2126, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); -+ register(2128, "{Name:'minecraft:emerald_block'}", "{Name:'minecraft:emerald_block'}"); -+ register(2144, "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2145, "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2146, "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2147, "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2148, "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2149, "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2150, "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2151, "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2160, "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2161, "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2162, "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2163, "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2164, "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2165, "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2166, "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2167, "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2176, "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2177, "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2178, "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2179, "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2180, "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2181, "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2182, "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2183, "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2192, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'down'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'down'}}"); -+ register(2193, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'up'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'up'}}"); -+ register(2194, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'north'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'north'}}"); -+ register(2195, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'south'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'south'}}"); -+ register(2196, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'west'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'west'}}"); -+ register(2197, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'east'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'east'}}"); -+ register(2200, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'down'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'down'}}"); -+ register(2201, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'up'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'up'}}"); -+ register(2202, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'north'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'north'}}"); -+ register(2203, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'south'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'south'}}"); -+ register(2204, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'west'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'west'}}"); -+ register(2205, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'east'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'east'}}"); -+ register(2208, "{Name:'minecraft:beacon'}", "{Name:'minecraft:beacon'}"); -+ register(2224, "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'cobblestone',west:'true'}}"); -+ register(2225, "{Name:'minecraft:mossy_cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}"); -+ // There are a few changes made to flower pot here, notably handling how legacy data is handled. -+ // The TE itself should contain the target item and from there the proper state can be determined. However, there are -+ // blocks that do not contain a TE. So we need to make sure there is a default to fall on. -+ // I simply followed the legacy handling from BlockFlowerPot from 1.8.8 to find what legacy data mapped to what. -+ // It's better than defaulting everything to a cactus. -+ register(2240, "{Name:'minecraft:flower_pot'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'0'}}"); -+ register(2241, "{Name:'minecraft:potted_poppy'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'1'}}"); -+ register(2242, "{Name:'minecraft:potted_dandelion'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'2'}}"); -+ register(2243, "{Name:'minecraft:potted_oak_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'3'}}"); -+ register(2244, "{Name:'minecraft:potted_spruce_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'4'}}"); -+ register(2245, "{Name:'minecraft:potted_birch_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'5'}}"); -+ register(2246, "{Name:'minecraft:potted_jungle_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'6'}}"); -+ register(2247, "{Name:'minecraft:potted_red_mushroom'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'7'}}"); -+ register(2248, "{Name:'minecraft:potted_brown_mushroom'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'8'}}"); -+ register(2249, "{Name:'minecraft:potted_cactus'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'9'}}"); -+ register(2250, "{Name:'minecraft:potted_dead_bush'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'10'}}"); -+ register(2251, "{Name:'minecraft:potted_fern'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'11'}}"); -+ register(2252, "{Name:'minecraft:potted_acacia_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'12'}}"); -+ register(2253, "{Name:'minecraft:potted_dark_oak_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'13'}}"); -+ register(2254, "{Name:'minecraft:flower_pot'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'14'}}"); -+ register(2255, "{Name:'minecraft:flower_pot'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'15'}}"); -+ register(2256, "{Name:'minecraft:carrots',Properties:{age:'0'}}", "{Name:'minecraft:carrots',Properties:{age:'0'}}"); -+ register(2257, "{Name:'minecraft:carrots',Properties:{age:'1'}}", "{Name:'minecraft:carrots',Properties:{age:'1'}}"); -+ register(2258, "{Name:'minecraft:carrots',Properties:{age:'2'}}", "{Name:'minecraft:carrots',Properties:{age:'2'}}"); -+ register(2259, "{Name:'minecraft:carrots',Properties:{age:'3'}}", "{Name:'minecraft:carrots',Properties:{age:'3'}}"); -+ register(2260, "{Name:'minecraft:carrots',Properties:{age:'4'}}", "{Name:'minecraft:carrots',Properties:{age:'4'}}"); -+ register(2261, "{Name:'minecraft:carrots',Properties:{age:'5'}}", "{Name:'minecraft:carrots',Properties:{age:'5'}}"); -+ register(2262, "{Name:'minecraft:carrots',Properties:{age:'6'}}", "{Name:'minecraft:carrots',Properties:{age:'6'}}"); -+ register(2263, "{Name:'minecraft:carrots',Properties:{age:'7'}}", "{Name:'minecraft:carrots',Properties:{age:'7'}}"); -+ register(2272, "{Name:'minecraft:potatoes',Properties:{age:'0'}}", "{Name:'minecraft:potatoes',Properties:{age:'0'}}"); -+ register(2273, "{Name:'minecraft:potatoes',Properties:{age:'1'}}", "{Name:'minecraft:potatoes',Properties:{age:'1'}}"); -+ register(2274, "{Name:'minecraft:potatoes',Properties:{age:'2'}}", "{Name:'minecraft:potatoes',Properties:{age:'2'}}"); -+ register(2275, "{Name:'minecraft:potatoes',Properties:{age:'3'}}", "{Name:'minecraft:potatoes',Properties:{age:'3'}}"); -+ register(2276, "{Name:'minecraft:potatoes',Properties:{age:'4'}}", "{Name:'minecraft:potatoes',Properties:{age:'4'}}"); -+ register(2277, "{Name:'minecraft:potatoes',Properties:{age:'5'}}", "{Name:'minecraft:potatoes',Properties:{age:'5'}}"); -+ register(2278, "{Name:'minecraft:potatoes',Properties:{age:'6'}}", "{Name:'minecraft:potatoes',Properties:{age:'6'}}"); -+ register(2279, "{Name:'minecraft:potatoes',Properties:{age:'7'}}", "{Name:'minecraft:potatoes',Properties:{age:'7'}}"); -+ register(2288, "{Name:'minecraft:oak_button',Properties:{face:'ceiling',facing:'north',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'down',powered:'false'}}"); -+ register(2289, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'east',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'east',powered:'false'}}"); -+ register(2290, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'west',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'west',powered:'false'}}"); -+ register(2291, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'south',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'south',powered:'false'}}"); -+ register(2292, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'north',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'north',powered:'false'}}"); -+ register(2293, "{Name:'minecraft:oak_button',Properties:{face:'floor',facing:'north',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'up',powered:'false'}}"); -+ register(2296, "{Name:'minecraft:oak_button',Properties:{face:'ceiling',facing:'north',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'down',powered:'true'}}"); -+ register(2297, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'east',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'east',powered:'true'}}"); -+ register(2298, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'west',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'west',powered:'true'}}"); -+ register(2299, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'south',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'south',powered:'true'}}"); -+ register(2300, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'north',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'north',powered:'true'}}"); -+ register(2301, "{Name:'minecraft:oak_button',Properties:{face:'floor',facing:'north',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'up',powered:'true'}}"); -+ register(2304, "{Name:'%%FILTER_ME%%',Properties:{facing:'down',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'down',nodrop:'false'}}"); -+ register(2305, "{Name:'%%FILTER_ME%%',Properties:{facing:'up',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'up',nodrop:'false'}}"); -+ register(2306, "{Name:'%%FILTER_ME%%',Properties:{facing:'north',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'north',nodrop:'false'}}"); -+ register(2307, "{Name:'%%FILTER_ME%%',Properties:{facing:'south',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'south',nodrop:'false'}}"); -+ register(2308, "{Name:'%%FILTER_ME%%',Properties:{facing:'west',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'west',nodrop:'false'}}"); -+ register(2309, "{Name:'%%FILTER_ME%%',Properties:{facing:'east',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'east',nodrop:'false'}}"); -+ register(2312, "{Name:'%%FILTER_ME%%',Properties:{facing:'down',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'down',nodrop:'true'}}"); -+ register(2313, "{Name:'%%FILTER_ME%%',Properties:{facing:'up',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'up',nodrop:'true'}}"); -+ register(2314, "{Name:'%%FILTER_ME%%',Properties:{facing:'north',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'north',nodrop:'true'}}"); -+ register(2315, "{Name:'%%FILTER_ME%%',Properties:{facing:'south',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'south',nodrop:'true'}}"); -+ register(2316, "{Name:'%%FILTER_ME%%',Properties:{facing:'west',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'west',nodrop:'true'}}"); -+ register(2317, "{Name:'%%FILTER_ME%%',Properties:{facing:'east',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'east',nodrop:'true'}}"); -+ register(2320, "{Name:'minecraft:anvil',Properties:{facing:'south'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'south'}}"); -+ register(2321, "{Name:'minecraft:anvil',Properties:{facing:'west'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'west'}}"); -+ register(2322, "{Name:'minecraft:anvil',Properties:{facing:'north'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'north'}}"); -+ register(2323, "{Name:'minecraft:anvil',Properties:{facing:'east'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'east'}}"); -+ register(2324, "{Name:'minecraft:chipped_anvil',Properties:{facing:'south'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'south'}}"); -+ register(2325, "{Name:'minecraft:chipped_anvil',Properties:{facing:'west'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'west'}}"); -+ register(2326, "{Name:'minecraft:chipped_anvil',Properties:{facing:'north'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'north'}}"); -+ register(2327, "{Name:'minecraft:chipped_anvil',Properties:{facing:'east'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'east'}}"); -+ register(2328, "{Name:'minecraft:damaged_anvil',Properties:{facing:'south'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'south'}}"); -+ register(2329, "{Name:'minecraft:damaged_anvil',Properties:{facing:'west'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'west'}}"); -+ register(2330, "{Name:'minecraft:damaged_anvil',Properties:{facing:'north'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'north'}}"); -+ register(2331, "{Name:'minecraft:damaged_anvil',Properties:{facing:'east'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'east'}}"); -+ register(2338, "{Name:'minecraft:trapped_chest',Properties:{facing:'north',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'north'}}"); -+ register(2339, "{Name:'minecraft:trapped_chest',Properties:{facing:'south',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'south'}}"); -+ register(2340, "{Name:'minecraft:trapped_chest',Properties:{facing:'west',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'west'}}"); -+ register(2341, "{Name:'minecraft:trapped_chest',Properties:{facing:'east',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'east'}}"); -+ register(2352, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'0'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'0'}}"); -+ register(2353, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'1'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'1'}}"); -+ register(2354, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'2'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'2'}}"); -+ register(2355, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'3'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'3'}}"); -+ register(2356, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'4'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'4'}}"); -+ register(2357, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'5'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'5'}}"); -+ register(2358, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'6'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'6'}}"); -+ register(2359, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'7'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'7'}}"); -+ register(2360, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'8'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'8'}}"); -+ register(2361, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'9'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'9'}}"); -+ register(2362, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'10'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'10'}}"); -+ register(2363, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'11'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'11'}}"); -+ register(2364, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'12'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'12'}}"); -+ register(2365, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'13'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'13'}}"); -+ register(2366, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'14'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'14'}}"); -+ register(2367, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'15'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'15'}}"); -+ register(2368, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'0'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'0'}}"); -+ register(2369, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'1'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'1'}}"); -+ register(2370, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'2'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'2'}}"); -+ register(2371, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'3'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'3'}}"); -+ register(2372, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'4'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'4'}}"); -+ register(2373, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'5'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'5'}}"); -+ register(2374, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'6'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'6'}}"); -+ register(2375, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'7'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'7'}}"); -+ register(2376, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'8'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'8'}}"); -+ register(2377, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'9'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'9'}}"); -+ register(2378, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'10'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'10'}}"); -+ register(2379, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'11'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'11'}}"); -+ register(2380, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'12'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'12'}}"); -+ register(2381, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'13'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'13'}}"); -+ register(2382, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'14'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'14'}}"); -+ register(2383, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'15'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'15'}}"); -+ register(2384, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}"); -+ register(2385, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}"); -+ register(2386, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}"); -+ register(2387, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}"); -+ register(2388, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}"); -+ register(2389, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}"); -+ register(2390, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}"); -+ register(2391, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}"); -+ register(2392, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}"); -+ register(2393, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}"); -+ register(2394, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}"); -+ register(2395, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}"); -+ register(2396, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}"); -+ register(2397, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}"); -+ register(2398, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}"); -+ register(2399, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}"); -+ register(2400, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}"); -+ register(2401, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}"); -+ register(2402, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}"); -+ register(2403, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}"); -+ register(2404, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}"); -+ register(2405, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}"); -+ register(2406, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}"); -+ register(2407, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}"); -+ register(2408, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}"); -+ register(2409, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}"); -+ register(2410, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}"); -+ register(2411, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}"); -+ register(2412, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}"); -+ register(2413, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}"); -+ register(2414, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}"); -+ register(2415, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}"); -+ register(2416, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'0'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'0'}}"); -+ register(2417, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'1'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'1'}}"); -+ register(2418, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'2'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'2'}}"); -+ register(2419, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'3'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'3'}}"); -+ register(2420, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'4'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'4'}}"); -+ register(2421, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'5'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'5'}}"); -+ register(2422, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'6'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'6'}}"); -+ register(2423, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'7'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'7'}}"); -+ register(2424, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'8'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'8'}}"); -+ register(2425, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'9'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'9'}}"); -+ register(2426, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'10'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'10'}}"); -+ register(2427, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'11'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'11'}}"); -+ register(2428, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'12'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'12'}}"); -+ register(2429, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'13'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'13'}}"); -+ register(2430, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'14'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'14'}}"); -+ register(2431, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'15'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'15'}}"); -+ register(2432, "{Name:'minecraft:redstone_block'}", "{Name:'minecraft:redstone_block'}"); -+ register(2448, "{Name:'minecraft:nether_quartz_ore'}", "{Name:'minecraft:quartz_ore'}"); -+ register(2464, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'down'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'down'}}"); -+ register(2466, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'north'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'north'}}"); -+ register(2467, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'south'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'south'}}"); -+ register(2468, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'west'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'west'}}"); -+ register(2469, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'east'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'east'}}"); -+ register(2472, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'down'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'down'}}"); -+ register(2474, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'north'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'north'}}"); -+ register(2475, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'south'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'south'}}"); -+ register(2476, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'west'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'west'}}"); -+ register(2477, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'east'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'east'}}"); -+ register(2480, "{Name:'minecraft:quartz_block'}", "{Name:'minecraft:quartz_block',Properties:{variant:'default'}}"); -+ register(2481, "{Name:'minecraft:chiseled_quartz_block'}", "{Name:'minecraft:quartz_block',Properties:{variant:'chiseled'}}"); -+ register(2482, "{Name:'minecraft:quartz_pillar',Properties:{axis:'y'}}", "{Name:'minecraft:quartz_block',Properties:{variant:'lines_y'}}"); -+ register(2483, "{Name:'minecraft:quartz_pillar',Properties:{axis:'x'}}", "{Name:'minecraft:quartz_block',Properties:{variant:'lines_x'}}"); -+ register(2484, "{Name:'minecraft:quartz_pillar',Properties:{axis:'z'}}", "{Name:'minecraft:quartz_block',Properties:{variant:'lines_z'}}"); -+ register(2496, "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2497, "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2498, "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2499, "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2500, "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2501, "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2502, "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2503, "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2512, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'north_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'north_south'}}"); -+ register(2513, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'east_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'east_west'}}"); -+ register(2514, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_east'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_east'}}"); -+ register(2515, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_west'}}"); -+ register(2516, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_north'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_north'}}"); -+ register(2517, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_south'}}"); -+ register(2520, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'north_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'north_south'}}"); -+ register(2521, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'east_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'east_west'}}"); -+ register(2522, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_east'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_east'}}"); -+ register(2523, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_west'}}"); -+ register(2524, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_north'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_north'}}"); -+ register(2525, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_south'}}"); -+ register(2528, "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'false'}}"); -+ register(2529, "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'false'}}"); -+ register(2530, "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'false'}}"); -+ register(2531, "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'false'}}"); -+ register(2532, "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'false'}}"); -+ register(2533, "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'false'}}"); -+ register(2536, "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'true'}}"); -+ register(2537, "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'true'}}"); -+ register(2538, "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'true'}}"); -+ register(2539, "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'true'}}"); -+ register(2540, "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'true'}}"); -+ register(2541, "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'true'}}"); -+ register(2544, "{Name:'minecraft:white_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'white'}}"); -+ register(2545, "{Name:'minecraft:orange_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'orange'}}"); -+ register(2546, "{Name:'minecraft:magenta_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'magenta'}}"); -+ register(2547, "{Name:'minecraft:light_blue_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'light_blue'}}"); -+ register(2548, "{Name:'minecraft:yellow_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'yellow'}}"); -+ register(2549, "{Name:'minecraft:lime_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'lime'}}"); -+ register(2550, "{Name:'minecraft:pink_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'pink'}}"); -+ register(2551, "{Name:'minecraft:gray_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'gray'}}"); -+ register(2552, "{Name:'minecraft:light_gray_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'silver'}}"); -+ register(2553, "{Name:'minecraft:cyan_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'cyan'}}"); -+ register(2554, "{Name:'minecraft:purple_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'purple'}}"); -+ register(2555, "{Name:'minecraft:blue_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'blue'}}"); -+ register(2556, "{Name:'minecraft:brown_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'brown'}}"); -+ register(2557, "{Name:'minecraft:green_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'green'}}"); -+ register(2558, "{Name:'minecraft:red_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'red'}}"); -+ register(2559, "{Name:'minecraft:black_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'black'}}"); -+ register(2560, "{Name:'minecraft:white_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2561, "{Name:'minecraft:orange_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2562, "{Name:'minecraft:magenta_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2563, "{Name:'minecraft:light_blue_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2564, "{Name:'minecraft:yellow_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2565, "{Name:'minecraft:lime_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2566, "{Name:'minecraft:pink_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2567, "{Name:'minecraft:gray_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2568, "{Name:'minecraft:light_gray_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2569, "{Name:'minecraft:cyan_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2570, "{Name:'minecraft:purple_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2571, "{Name:'minecraft:blue_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2572, "{Name:'minecraft:brown_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2573, "{Name:'minecraft:green_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2574, "{Name:'minecraft:red_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2575, "{Name:'minecraft:black_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2576, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'true',variant:'acacia'}}"); -+ register(2577, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'true',variant:'dark_oak'}}"); -+ register(2580, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'false',variant:'acacia'}}"); -+ register(2581, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'false',variant:'dark_oak'}}"); -+ register(2584, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'true',variant:'acacia'}}"); -+ register(2585, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'true',variant:'dark_oak'}}"); -+ register(2588, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'false',variant:'acacia'}}"); -+ register(2589, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'false',variant:'dark_oak'}}"); -+ register(2592, "{Name:'minecraft:acacia_log',Properties:{axis:'y'}}", "{Name:'minecraft:log2',Properties:{axis:'y',variant:'acacia'}}"); -+ register(2593, "{Name:'minecraft:dark_oak_log',Properties:{axis:'y'}}", "{Name:'minecraft:log2',Properties:{axis:'y',variant:'dark_oak'}}"); -+ register(2596, "{Name:'minecraft:acacia_log',Properties:{axis:'x'}}", "{Name:'minecraft:log2',Properties:{axis:'x',variant:'acacia'}}"); -+ register(2597, "{Name:'minecraft:dark_oak_log',Properties:{axis:'x'}}", "{Name:'minecraft:log2',Properties:{axis:'x',variant:'dark_oak'}}"); -+ register(2600, "{Name:'minecraft:acacia_log',Properties:{axis:'z'}}", "{Name:'minecraft:log2',Properties:{axis:'z',variant:'acacia'}}"); -+ register(2601, "{Name:'minecraft:dark_oak_log',Properties:{axis:'z'}}", "{Name:'minecraft:log2',Properties:{axis:'z',variant:'dark_oak'}}"); -+ register(2604, "{Name:'minecraft:acacia_bark'}", "{Name:'minecraft:log2',Properties:{axis:'none',variant:'acacia'}}"); -+ register(2605, "{Name:'minecraft:dark_oak_bark'}", "{Name:'minecraft:log2',Properties:{axis:'none',variant:'dark_oak'}}"); -+ register(2608, "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2609, "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2610, "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2611, "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2612, "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2613, "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2614, "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2615, "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2624, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2625, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2626, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2627, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2628, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2629, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2630, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2631, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2640, "{Name:'minecraft:slime_block'}", "{Name:'minecraft:slime'}"); -+ register(2656, "{Name:'minecraft:barrier'}", "{Name:'minecraft:barrier'}"); -+ register(2672, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}"); -+ register(2673, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}"); -+ register(2674, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}"); -+ register(2675, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}"); -+ register(2676, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}"); -+ register(2677, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}"); -+ register(2678, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}"); -+ register(2679, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}"); -+ register(2680, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'false'}}"); -+ register(2681, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'false'}}"); -+ register(2682, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'false'}}"); -+ register(2683, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'false'}}"); -+ register(2684, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'true'}}"); -+ register(2685, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'true'}}"); -+ register(2686, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'true'}}"); -+ register(2687, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'true'}}"); -+ register(2688, "{Name:'minecraft:prismarine'}", "{Name:'minecraft:prismarine',Properties:{variant:'prismarine'}}"); -+ register(2689, "{Name:'minecraft:prismarine_bricks'}", "{Name:'minecraft:prismarine',Properties:{variant:'prismarine_bricks'}}"); -+ register(2690, "{Name:'minecraft:dark_prismarine'}", "{Name:'minecraft:prismarine',Properties:{variant:'dark_prismarine'}}"); -+ register(2704, "{Name:'minecraft:sea_lantern'}", "{Name:'minecraft:sea_lantern'}"); -+ register(2720, "{Name:'minecraft:hay_block',Properties:{axis:'y'}}", "{Name:'minecraft:hay_block',Properties:{axis:'y'}}"); -+ register(2724, "{Name:'minecraft:hay_block',Properties:{axis:'x'}}", "{Name:'minecraft:hay_block',Properties:{axis:'x'}}"); -+ register(2728, "{Name:'minecraft:hay_block',Properties:{axis:'z'}}", "{Name:'minecraft:hay_block',Properties:{axis:'z'}}"); -+ register(2736, "{Name:'minecraft:white_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'white'}}"); -+ register(2737, "{Name:'minecraft:orange_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'orange'}}"); -+ register(2738, "{Name:'minecraft:magenta_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'magenta'}}"); -+ register(2739, "{Name:'minecraft:light_blue_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'light_blue'}}"); -+ register(2740, "{Name:'minecraft:yellow_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'yellow'}}"); -+ register(2741, "{Name:'minecraft:lime_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'lime'}}"); -+ register(2742, "{Name:'minecraft:pink_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'pink'}}"); -+ register(2743, "{Name:'minecraft:gray_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'gray'}}"); -+ register(2744, "{Name:'minecraft:light_gray_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'silver'}}"); -+ register(2745, "{Name:'minecraft:cyan_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'cyan'}}"); -+ register(2746, "{Name:'minecraft:purple_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'purple'}}"); -+ register(2747, "{Name:'minecraft:blue_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'blue'}}"); -+ register(2748, "{Name:'minecraft:brown_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'brown'}}"); -+ register(2749, "{Name:'minecraft:green_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'green'}}"); -+ register(2750, "{Name:'minecraft:red_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'red'}}"); -+ register(2751, "{Name:'minecraft:black_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'black'}}"); -+ register(2752, "{Name:'minecraft:terracotta'}", "{Name:'minecraft:hardened_clay'}"); -+ register(2768, "{Name:'minecraft:coal_block'}", "{Name:'minecraft:coal_block'}"); -+ register(2784, "{Name:'minecraft:packed_ice'}", "{Name:'minecraft:packed_ice'}"); -+ register(2800, "{Name:'minecraft:sunflower',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'sunflower'}}"); -+ register(2801, "{Name:'minecraft:lilac',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'syringa'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'syringa'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'syringa'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'syringa'}}"); -+ register(2802, "{Name:'minecraft:tall_grass',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'double_grass'}}"); -+ register(2803, "{Name:'minecraft:large_fern',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'double_fern'}}"); -+ register(2804, "{Name:'minecraft:rose_bush',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'double_rose'}}"); -+ register(2805, "{Name:'minecraft:peony',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'paeonia'}}"); -+ register(2808, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'syringa'}}"); -+ register(2809, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'syringa'}}"); -+ register(2810, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'syringa'}}"); -+ register(2811, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'syringa'}}"); -+ register(2816, "{Name:'minecraft:white_banner',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'0'}}"); -+ register(2817, "{Name:'minecraft:white_banner',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'1'}}"); -+ register(2818, "{Name:'minecraft:white_banner',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'2'}}"); -+ register(2819, "{Name:'minecraft:white_banner',Properties:{rotation:'3'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'3'}}"); -+ register(2820, "{Name:'minecraft:white_banner',Properties:{rotation:'4'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'4'}}"); -+ register(2821, "{Name:'minecraft:white_banner',Properties:{rotation:'5'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'5'}}"); -+ register(2822, "{Name:'minecraft:white_banner',Properties:{rotation:'6'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'6'}}"); -+ register(2823, "{Name:'minecraft:white_banner',Properties:{rotation:'7'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'7'}}"); -+ register(2824, "{Name:'minecraft:white_banner',Properties:{rotation:'8'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'8'}}"); -+ register(2825, "{Name:'minecraft:white_banner',Properties:{rotation:'9'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'9'}}"); -+ register(2826, "{Name:'minecraft:white_banner',Properties:{rotation:'10'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'10'}}"); -+ register(2827, "{Name:'minecraft:white_banner',Properties:{rotation:'11'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'11'}}"); -+ register(2828, "{Name:'minecraft:white_banner',Properties:{rotation:'12'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'12'}}"); -+ register(2829, "{Name:'minecraft:white_banner',Properties:{rotation:'13'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'13'}}"); -+ register(2830, "{Name:'minecraft:white_banner',Properties:{rotation:'14'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'14'}}"); -+ register(2831, "{Name:'minecraft:white_banner',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'15'}}"); -+ register(2834, "{Name:'minecraft:white_wall_banner',Properties:{facing:'north'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'north'}}"); -+ register(2835, "{Name:'minecraft:white_wall_banner',Properties:{facing:'south'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'south'}}"); -+ register(2836, "{Name:'minecraft:white_wall_banner',Properties:{facing:'west'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'west'}}"); -+ register(2837, "{Name:'minecraft:white_wall_banner',Properties:{facing:'east'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'east'}}"); -+ register(2848, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'0'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'0'}}"); -+ register(2849, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'1'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'1'}}"); -+ register(2850, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'2'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'2'}}"); -+ register(2851, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'3'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'3'}}"); -+ register(2852, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'4'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'4'}}"); -+ register(2853, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'5'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'5'}}"); -+ register(2854, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'6'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'6'}}"); -+ register(2855, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'7'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'7'}}"); -+ register(2856, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'8'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'8'}}"); -+ register(2857, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'9'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'9'}}"); -+ register(2858, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'10'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'10'}}"); -+ register(2859, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'11'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'11'}}"); -+ register(2860, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'12'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'12'}}"); -+ register(2861, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'13'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'13'}}"); -+ register(2862, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'14'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'14'}}"); -+ register(2863, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'15'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'15'}}"); -+ register(2864, "{Name:'minecraft:red_sandstone'}", "{Name:'minecraft:red_sandstone',Properties:{type:'red_sandstone'}}"); -+ register(2865, "{Name:'minecraft:chiseled_red_sandstone'}", "{Name:'minecraft:red_sandstone',Properties:{type:'chiseled_red_sandstone'}}"); -+ register(2866, "{Name:'minecraft:cut_red_sandstone'}", "{Name:'minecraft:red_sandstone',Properties:{type:'smooth_red_sandstone'}}"); -+ register(2880, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2881, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2882, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2883, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2884, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2885, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2886, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2887, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2896, "{Name:'minecraft:red_sandstone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab2',Properties:{seamless:'false',variant:'red_sandstone'}}"); -+ register(2904, "{Name:'minecraft:smooth_red_sandstone'}", "{Name:'minecraft:double_stone_slab2',Properties:{seamless:'true',variant:'red_sandstone'}}"); -+ register(2912, "{Name:'minecraft:red_sandstone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab2',Properties:{half:'bottom',variant:'red_sandstone'}}"); -+ register(2920, "{Name:'minecraft:red_sandstone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab2',Properties:{half:'top',variant:'red_sandstone'}}"); -+ register(2928, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2929, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2930, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2931, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2932, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2933, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2934, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2935, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2936, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2937, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2938, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2939, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2940, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2941, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2942, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2943, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2944, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2945, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2946, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2947, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2948, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2949, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2950, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2951, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2952, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2953, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2954, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2955, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2956, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2957, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2958, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2959, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2960, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2961, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2962, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2963, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2964, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2965, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2966, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2967, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2968, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2969, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2970, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2971, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2972, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2973, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2974, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2975, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2976, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2977, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2978, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2979, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2980, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2981, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2982, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2983, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2984, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2985, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2986, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2987, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2988, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2989, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2990, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2991, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2992, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2993, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2994, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2995, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2996, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2997, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2998, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2999, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); -+ register(3000, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); -+ register(3001, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); -+ register(3002, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); -+ register(3003, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); -+ register(3004, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); -+ register(3005, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); -+ register(3006, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); -+ register(3007, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); -+ register(3008, "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(3024, "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(3040, "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(3056, "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(3072, "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(3088, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3089, "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3090, "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3091, "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3092, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3093, "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3094, "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3095, "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3096, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(3097, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(3098, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(3099, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(3104, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3105, "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3106, "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3107, "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3108, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3109, "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3110, "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3111, "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3112, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(3113, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(3114, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(3115, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(3120, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3121, "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3122, "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3123, "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3124, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3125, "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3126, "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3127, "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3128, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(3129, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(3130, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(3131, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(3136, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3137, "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3138, "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3139, "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3140, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3141, "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3142, "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3143, "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3144, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(3145, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(3146, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(3147, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(3152, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3153, "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3154, "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3155, "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3156, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3157, "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3158, "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3159, "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3160, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(3161, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(3162, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(3163, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(3168, "{Name:'minecraft:end_rod',Properties:{facing:'down'}}", "{Name:'minecraft:end_rod',Properties:{facing:'down'}}"); -+ register(3169, "{Name:'minecraft:end_rod',Properties:{facing:'up'}}", "{Name:'minecraft:end_rod',Properties:{facing:'up'}}"); -+ register(3170, "{Name:'minecraft:end_rod',Properties:{facing:'north'}}", "{Name:'minecraft:end_rod',Properties:{facing:'north'}}"); -+ register(3171, "{Name:'minecraft:end_rod',Properties:{facing:'south'}}", "{Name:'minecraft:end_rod',Properties:{facing:'south'}}"); -+ register(3172, "{Name:'minecraft:end_rod',Properties:{facing:'west'}}", "{Name:'minecraft:end_rod',Properties:{facing:'west'}}"); -+ register(3173, "{Name:'minecraft:end_rod',Properties:{facing:'east'}}", "{Name:'minecraft:end_rod',Properties:{facing:'east'}}"); -+ register(3184, "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(3200, "{Name:'minecraft:chorus_flower',Properties:{age:'0'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'0'}}"); -+ register(3201, "{Name:'minecraft:chorus_flower',Properties:{age:'1'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'1'}}"); -+ register(3202, "{Name:'minecraft:chorus_flower',Properties:{age:'2'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'2'}}"); -+ register(3203, "{Name:'minecraft:chorus_flower',Properties:{age:'3'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'3'}}"); -+ register(3204, "{Name:'minecraft:chorus_flower',Properties:{age:'4'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'4'}}"); -+ register(3205, "{Name:'minecraft:chorus_flower',Properties:{age:'5'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'5'}}"); -+ register(3216, "{Name:'minecraft:purpur_block'}", "{Name:'minecraft:purpur_block'}"); -+ register(3232, "{Name:'minecraft:purpur_pillar',Properties:{axis:'y'}}", "{Name:'minecraft:purpur_pillar',Properties:{axis:'y'}}"); -+ register(3236, "{Name:'minecraft:purpur_pillar',Properties:{axis:'x'}}", "{Name:'minecraft:purpur_pillar',Properties:{axis:'x'}}"); -+ register(3240, "{Name:'minecraft:purpur_pillar',Properties:{axis:'z'}}", "{Name:'minecraft:purpur_pillar',Properties:{axis:'z'}}"); -+ register(3248, "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(3249, "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(3250, "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(3251, "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(3252, "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(3253, "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(3254, "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(3255, "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(3264, "{Name:'minecraft:purpur_slab',Properties:{type:'double'}}", "{Name:'minecraft:purpur_double_slab',Properties:{variant:'default'}}"); -+ register(3280, "{Name:'minecraft:purpur_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:purpur_slab',Properties:{half:'bottom',variant:'default'}}"); -+ register(3288, "{Name:'minecraft:purpur_slab',Properties:{type:'top'}}", "{Name:'minecraft:purpur_slab',Properties:{half:'top',variant:'default'}}"); -+ register(3296, "{Name:'minecraft:end_stone_bricks'}", "{Name:'minecraft:end_bricks'}"); -+ register(3312, "{Name:'minecraft:beetroots',Properties:{age:'0'}}", "{Name:'minecraft:beetroots',Properties:{age:'0'}}"); -+ register(3313, "{Name:'minecraft:beetroots',Properties:{age:'1'}}", "{Name:'minecraft:beetroots',Properties:{age:'1'}}"); -+ register(3314, "{Name:'minecraft:beetroots',Properties:{age:'2'}}", "{Name:'minecraft:beetroots',Properties:{age:'2'}}"); -+ register(3315, "{Name:'minecraft:beetroots',Properties:{age:'3'}}", "{Name:'minecraft:beetroots',Properties:{age:'3'}}"); -+ register(3328, "{Name:'minecraft:grass_path'}", "{Name:'minecraft:grass_path'}"); -+ register(3344, "{Name:'minecraft:end_gateway'}", "{Name:'minecraft:end_gateway'}"); -+ register(3360, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'down'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'down'}}"); -+ register(3361, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'up'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'up'}}"); -+ register(3362, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'north'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'north'}}"); -+ register(3363, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'south'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'south'}}"); -+ register(3364, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'west'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'west'}}"); -+ register(3365, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'east'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'east'}}"); -+ register(3368, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'down'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'down'}}"); -+ register(3369, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'up'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'up'}}"); -+ register(3370, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'north'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'north'}}"); -+ register(3371, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'south'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'south'}}"); -+ register(3372, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'west'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'west'}}"); -+ register(3373, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'east'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'east'}}"); -+ register(3376, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'down'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'down'}}"); -+ register(3377, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'up'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'up'}}"); -+ register(3378, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'north'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'north'}}"); -+ register(3379, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'south'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'south'}}"); -+ register(3380, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'west'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'west'}}"); -+ register(3381, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'east'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'east'}}"); -+ register(3384, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'down'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'down'}}"); -+ register(3385, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'up'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'up'}}"); -+ register(3386, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'north'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'north'}}"); -+ register(3387, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'south'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'south'}}"); -+ register(3388, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'west'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'west'}}"); -+ register(3389, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'east'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'east'}}"); -+ register(3392, "{Name:'minecraft:frosted_ice',Properties:{age:'0'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'0'}}"); -+ register(3393, "{Name:'minecraft:frosted_ice',Properties:{age:'1'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'1'}}"); -+ register(3394, "{Name:'minecraft:frosted_ice',Properties:{age:'2'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'2'}}"); -+ register(3395, "{Name:'minecraft:frosted_ice',Properties:{age:'3'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'3'}}"); -+ register(3408, "{Name:'minecraft:magma_block'}", "{Name:'minecraft:magma'}"); -+ register(3424, "{Name:'minecraft:nether_wart_block'}", "{Name:'minecraft:nether_wart_block'}"); -+ register(3440, "{Name:'minecraft:red_nether_bricks'}", "{Name:'minecraft:red_nether_brick'}"); -+ register(3456, "{Name:'minecraft:bone_block',Properties:{axis:'y'}}", "{Name:'minecraft:bone_block',Properties:{axis:'y'}}"); -+ register(3460, "{Name:'minecraft:bone_block',Properties:{axis:'x'}}", "{Name:'minecraft:bone_block',Properties:{axis:'x'}}"); -+ register(3464, "{Name:'minecraft:bone_block',Properties:{axis:'z'}}", "{Name:'minecraft:bone_block',Properties:{axis:'z'}}"); -+ register(3472, "{Name:'minecraft:structure_void'}", "{Name:'minecraft:structure_void'}"); -+ register(3488, "{Name:'minecraft:observer',Properties:{facing:'down',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'down',powered:'false'}}"); -+ register(3489, "{Name:'minecraft:observer',Properties:{facing:'up',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'up',powered:'false'}}"); -+ register(3490, "{Name:'minecraft:observer',Properties:{facing:'north',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'north',powered:'false'}}"); -+ register(3491, "{Name:'minecraft:observer',Properties:{facing:'south',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'south',powered:'false'}}"); -+ register(3492, "{Name:'minecraft:observer',Properties:{facing:'west',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'west',powered:'false'}}"); -+ register(3493, "{Name:'minecraft:observer',Properties:{facing:'east',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'east',powered:'false'}}"); -+ register(3496, "{Name:'minecraft:observer',Properties:{facing:'down',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'down',powered:'true'}}"); -+ register(3497, "{Name:'minecraft:observer',Properties:{facing:'up',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'up',powered:'true'}}"); -+ register(3498, "{Name:'minecraft:observer',Properties:{facing:'north',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'north',powered:'true'}}"); -+ register(3499, "{Name:'minecraft:observer',Properties:{facing:'south',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'south',powered:'true'}}"); -+ register(3500, "{Name:'minecraft:observer',Properties:{facing:'west',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'west',powered:'true'}}"); -+ register(3501, "{Name:'minecraft:observer',Properties:{facing:'east',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'east',powered:'true'}}"); -+ register(3504, "{Name:'minecraft:white_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'down'}}"); -+ register(3505, "{Name:'minecraft:white_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'up'}}"); -+ register(3506, "{Name:'minecraft:white_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'north'}}"); -+ register(3507, "{Name:'minecraft:white_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'south'}}"); -+ register(3508, "{Name:'minecraft:white_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'west'}}"); -+ register(3509, "{Name:'minecraft:white_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'east'}}"); -+ register(3520, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'down'}}"); -+ register(3521, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'up'}}"); -+ register(3522, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'north'}}"); -+ register(3523, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'south'}}"); -+ register(3524, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'west'}}"); -+ register(3525, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'east'}}"); -+ register(3536, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'down'}}"); -+ register(3537, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'up'}}"); -+ register(3538, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'north'}}"); -+ register(3539, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'south'}}"); -+ register(3540, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'west'}}"); -+ register(3541, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'east'}}"); -+ register(3552, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'down'}}"); -+ register(3553, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'up'}}"); -+ register(3554, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'north'}}"); -+ register(3555, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'south'}}"); -+ register(3556, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'west'}}"); -+ register(3557, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'east'}}"); -+ register(3568, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'down'}}"); -+ register(3569, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'up'}}"); -+ register(3570, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'north'}}"); -+ register(3571, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'south'}}"); -+ register(3572, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'west'}}"); -+ register(3573, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'east'}}"); -+ register(3584, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'down'}}"); -+ register(3585, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'up'}}"); -+ register(3586, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'north'}}"); -+ register(3587, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'south'}}"); -+ register(3588, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'west'}}"); -+ register(3589, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'east'}}"); -+ register(3600, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'down'}}"); -+ register(3601, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'up'}}"); -+ register(3602, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'north'}}"); -+ register(3603, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'south'}}"); -+ register(3604, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'west'}}"); -+ register(3605, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'east'}}"); -+ register(3616, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'down'}}"); -+ register(3617, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'up'}}"); -+ register(3618, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'north'}}"); -+ register(3619, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'south'}}"); -+ register(3620, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'west'}}"); -+ register(3621, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'east'}}"); -+ register(3632, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'down'}}"); -+ register(3633, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'up'}}"); -+ register(3634, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'north'}}"); -+ register(3635, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'south'}}"); -+ register(3636, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'west'}}"); -+ register(3637, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'east'}}"); -+ register(3648, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'down'}}"); -+ register(3649, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'up'}}"); -+ register(3650, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'north'}}"); -+ register(3651, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'south'}}"); -+ register(3652, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'west'}}"); -+ register(3653, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'east'}}"); -+ register(3664, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'down'}}"); -+ register(3665, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'up'}}"); -+ register(3666, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'north'}}"); -+ register(3667, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'south'}}"); -+ register(3668, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'west'}}"); -+ register(3669, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'east'}}"); -+ register(3680, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'down'}}"); -+ register(3681, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'up'}}"); -+ register(3682, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'north'}}"); -+ register(3683, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'south'}}"); -+ register(3684, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'west'}}"); -+ register(3685, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'east'}}"); -+ register(3696, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'down'}}"); -+ register(3697, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'up'}}"); -+ register(3698, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'north'}}"); -+ register(3699, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'south'}}"); -+ register(3700, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'west'}}"); -+ register(3701, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'east'}}"); -+ register(3712, "{Name:'minecraft:green_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'down'}}"); -+ register(3713, "{Name:'minecraft:green_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'up'}}"); -+ register(3714, "{Name:'minecraft:green_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'north'}}"); -+ register(3715, "{Name:'minecraft:green_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'south'}}"); -+ register(3716, "{Name:'minecraft:green_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'west'}}"); -+ register(3717, "{Name:'minecraft:green_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'east'}}"); -+ register(3728, "{Name:'minecraft:red_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'down'}}"); -+ register(3729, "{Name:'minecraft:red_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'up'}}"); -+ register(3730, "{Name:'minecraft:red_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'north'}}"); -+ register(3731, "{Name:'minecraft:red_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'south'}}"); -+ register(3732, "{Name:'minecraft:red_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'west'}}"); -+ register(3733, "{Name:'minecraft:red_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'east'}}"); -+ register(3744, "{Name:'minecraft:black_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'down'}}"); -+ register(3745, "{Name:'minecraft:black_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'up'}}"); -+ register(3746, "{Name:'minecraft:black_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'north'}}"); -+ register(3747, "{Name:'minecraft:black_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'south'}}"); -+ register(3748, "{Name:'minecraft:black_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'west'}}"); -+ register(3749, "{Name:'minecraft:black_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'east'}}"); -+ register(3760, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3761, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3762, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3763, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3776, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3777, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3778, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3779, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3792, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3793, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3794, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3795, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3808, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3809, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3810, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3811, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3824, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3825, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3826, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3827, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3840, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3841, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3842, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3843, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3856, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3857, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3858, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3859, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3872, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3873, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3874, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3875, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3888, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3889, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3890, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3891, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3904, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3905, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3906, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3907, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3920, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3921, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3922, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3923, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3936, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3937, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3938, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3939, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3952, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3953, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3954, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3955, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3968, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3969, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3970, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3971, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3984, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3985, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3986, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3987, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(4000, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(4001, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(4002, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(4003, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(4016, "{Name:'minecraft:white_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'white'}}"); -+ register(4017, "{Name:'minecraft:orange_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'orange'}}"); -+ register(4018, "{Name:'minecraft:magenta_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'magenta'}}"); -+ register(4019, "{Name:'minecraft:light_blue_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'light_blue'}}"); -+ register(4020, "{Name:'minecraft:yellow_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'yellow'}}"); -+ register(4021, "{Name:'minecraft:lime_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'lime'}}"); -+ register(4022, "{Name:'minecraft:pink_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'pink'}}"); -+ register(4023, "{Name:'minecraft:gray_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'gray'}}"); -+ register(4024, "{Name:'minecraft:light_gray_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'silver'}}"); -+ register(4025, "{Name:'minecraft:cyan_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'cyan'}}"); -+ register(4026, "{Name:'minecraft:purple_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'purple'}}"); -+ register(4027, "{Name:'minecraft:blue_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'blue'}}"); -+ register(4028, "{Name:'minecraft:brown_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'brown'}}"); -+ register(4029, "{Name:'minecraft:green_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'green'}}"); -+ register(4030, "{Name:'minecraft:red_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'red'}}"); -+ register(4031, "{Name:'minecraft:black_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'black'}}"); -+ register(4032, "{Name:'minecraft:white_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'white'}}"); -+ register(4033, "{Name:'minecraft:orange_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'orange'}}"); -+ register(4034, "{Name:'minecraft:magenta_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'magenta'}}"); -+ register(4035, "{Name:'minecraft:light_blue_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'light_blue'}}"); -+ register(4036, "{Name:'minecraft:yellow_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'yellow'}}"); -+ register(4037, "{Name:'minecraft:lime_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'lime'}}"); -+ register(4038, "{Name:'minecraft:pink_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'pink'}}"); -+ register(4039, "{Name:'minecraft:gray_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'gray'}}"); -+ register(4040, "{Name:'minecraft:light_gray_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'silver'}}"); -+ register(4041, "{Name:'minecraft:cyan_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'cyan'}}"); -+ register(4042, "{Name:'minecraft:purple_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'purple'}}"); -+ register(4043, "{Name:'minecraft:blue_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'blue'}}"); -+ register(4044, "{Name:'minecraft:brown_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'brown'}}"); -+ register(4045, "{Name:'minecraft:green_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'green'}}"); -+ register(4046, "{Name:'minecraft:red_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'red'}}"); -+ register(4047, "{Name:'minecraft:black_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'black'}}"); -+ register(4080, "{Name:'minecraft:structure_block',Properties:{mode:'save'}}", "{Name:'minecraft:structure_block',Properties:{mode:'save'}}"); -+ register(4081, "{Name:'minecraft:structure_block',Properties:{mode:'load'}}", "{Name:'minecraft:structure_block',Properties:{mode:'load'}}"); -+ register(4082, "{Name:'minecraft:structure_block',Properties:{mode:'corner'}}", "{Name:'minecraft:structure_block',Properties:{mode:'corner'}}"); -+ register(4083, "{Name:'minecraft:structure_block',Properties:{mode:'data'}}", "{Name:'minecraft:structure_block',Properties:{mode:'data'}}"); -+ finalizeMaps(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java -new file mode 100644 -index 0000000000000000000000000000000000000000..86f6aa3e3fa886976809f350fc5eb16f6a026ed9 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java -@@ -0,0 +1,533 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.helpers; -+ -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+ -+public final class HelperItemNameV102 { -+ -+ // This class is responsible for mapping the id -> string update in itemstacks and potions -+ -+ private static final Int2ObjectOpenHashMap ITEM_NAMES = new Int2ObjectOpenHashMap() { -+ @Override -+ public String put(final int k, final String o) { -+ final String ret = super.put(k, o); -+ -+ if (ret != null) { -+ throw new IllegalStateException("Mapping already exists for " + k + ": prev: " + ret + ", new: " + o); -+ } -+ -+ return ret; -+ } -+ }; -+ -+ static { -+ ITEM_NAMES.put(0, "minecraft:air"); -+ ITEM_NAMES.put(1, "minecraft:stone"); -+ ITEM_NAMES.put(2, "minecraft:grass"); -+ ITEM_NAMES.put(3, "minecraft:dirt"); -+ ITEM_NAMES.put(4, "minecraft:cobblestone"); -+ ITEM_NAMES.put(5, "minecraft:planks"); -+ ITEM_NAMES.put(6, "minecraft:sapling"); -+ ITEM_NAMES.put(7, "minecraft:bedrock"); -+ ITEM_NAMES.put(8, "minecraft:flowing_water"); -+ ITEM_NAMES.put(9, "minecraft:water"); -+ ITEM_NAMES.put(10, "minecraft:flowing_lava"); -+ ITEM_NAMES.put(11, "minecraft:lava"); -+ ITEM_NAMES.put(12, "minecraft:sand"); -+ ITEM_NAMES.put(13, "minecraft:gravel"); -+ ITEM_NAMES.put(14, "minecraft:gold_ore"); -+ ITEM_NAMES.put(15, "minecraft:iron_ore"); -+ ITEM_NAMES.put(16, "minecraft:coal_ore"); -+ ITEM_NAMES.put(17, "minecraft:log"); -+ ITEM_NAMES.put(18, "minecraft:leaves"); -+ ITEM_NAMES.put(19, "minecraft:sponge"); -+ ITEM_NAMES.put(20, "minecraft:glass"); -+ ITEM_NAMES.put(21, "minecraft:lapis_ore"); -+ ITEM_NAMES.put(22, "minecraft:lapis_block"); -+ ITEM_NAMES.put(23, "minecraft:dispenser"); -+ ITEM_NAMES.put(24, "minecraft:sandstone"); -+ ITEM_NAMES.put(25, "minecraft:noteblock"); -+ ITEM_NAMES.put(27, "minecraft:golden_rail"); -+ ITEM_NAMES.put(28, "minecraft:detector_rail"); -+ ITEM_NAMES.put(29, "minecraft:sticky_piston"); -+ ITEM_NAMES.put(30, "minecraft:web"); -+ ITEM_NAMES.put(31, "minecraft:tallgrass"); -+ ITEM_NAMES.put(32, "minecraft:deadbush"); -+ ITEM_NAMES.put(33, "minecraft:piston"); -+ ITEM_NAMES.put(35, "minecraft:wool"); -+ ITEM_NAMES.put(37, "minecraft:yellow_flower"); -+ ITEM_NAMES.put(38, "minecraft:red_flower"); -+ ITEM_NAMES.put(39, "minecraft:brown_mushroom"); -+ ITEM_NAMES.put(40, "minecraft:red_mushroom"); -+ ITEM_NAMES.put(41, "minecraft:gold_block"); -+ ITEM_NAMES.put(42, "minecraft:iron_block"); -+ ITEM_NAMES.put(43, "minecraft:double_stone_slab"); -+ ITEM_NAMES.put(44, "minecraft:stone_slab"); -+ ITEM_NAMES.put(45, "minecraft:brick_block"); -+ ITEM_NAMES.put(46, "minecraft:tnt"); -+ ITEM_NAMES.put(47, "minecraft:bookshelf"); -+ ITEM_NAMES.put(48, "minecraft:mossy_cobblestone"); -+ ITEM_NAMES.put(49, "minecraft:obsidian"); -+ ITEM_NAMES.put(50, "minecraft:torch"); -+ ITEM_NAMES.put(51, "minecraft:fire"); -+ ITEM_NAMES.put(52, "minecraft:mob_spawner"); -+ ITEM_NAMES.put(53, "minecraft:oak_stairs"); -+ ITEM_NAMES.put(54, "minecraft:chest"); -+ ITEM_NAMES.put(56, "minecraft:diamond_ore"); -+ ITEM_NAMES.put(57, "minecraft:diamond_block"); -+ ITEM_NAMES.put(58, "minecraft:crafting_table"); -+ ITEM_NAMES.put(60, "minecraft:farmland"); -+ ITEM_NAMES.put(61, "minecraft:furnace"); -+ ITEM_NAMES.put(62, "minecraft:lit_furnace"); -+ ITEM_NAMES.put(65, "minecraft:ladder"); -+ ITEM_NAMES.put(66, "minecraft:rail"); -+ ITEM_NAMES.put(67, "minecraft:stone_stairs"); -+ ITEM_NAMES.put(69, "minecraft:lever"); -+ ITEM_NAMES.put(70, "minecraft:stone_pressure_plate"); -+ ITEM_NAMES.put(72, "minecraft:wooden_pressure_plate"); -+ ITEM_NAMES.put(73, "minecraft:redstone_ore"); -+ ITEM_NAMES.put(76, "minecraft:redstone_torch"); -+ ITEM_NAMES.put(77, "minecraft:stone_button"); -+ ITEM_NAMES.put(78, "minecraft:snow_layer"); -+ ITEM_NAMES.put(79, "minecraft:ice"); -+ ITEM_NAMES.put(80, "minecraft:snow"); -+ ITEM_NAMES.put(81, "minecraft:cactus"); -+ ITEM_NAMES.put(82, "minecraft:clay"); -+ ITEM_NAMES.put(84, "minecraft:jukebox"); -+ ITEM_NAMES.put(85, "minecraft:fence"); -+ ITEM_NAMES.put(86, "minecraft:pumpkin"); -+ ITEM_NAMES.put(87, "minecraft:netherrack"); -+ ITEM_NAMES.put(88, "minecraft:soul_sand"); -+ ITEM_NAMES.put(89, "minecraft:glowstone"); -+ ITEM_NAMES.put(90, "minecraft:portal"); -+ ITEM_NAMES.put(91, "minecraft:lit_pumpkin"); -+ ITEM_NAMES.put(95, "minecraft:stained_glass"); -+ ITEM_NAMES.put(96, "minecraft:trapdoor"); -+ ITEM_NAMES.put(97, "minecraft:monster_egg"); -+ ITEM_NAMES.put(98, "minecraft:stonebrick"); -+ ITEM_NAMES.put(99, "minecraft:brown_mushroom_block"); -+ ITEM_NAMES.put(100, "minecraft:red_mushroom_block"); -+ ITEM_NAMES.put(101, "minecraft:iron_bars"); -+ ITEM_NAMES.put(102, "minecraft:glass_pane"); -+ ITEM_NAMES.put(103, "minecraft:melon_block"); -+ ITEM_NAMES.put(106, "minecraft:vine"); -+ ITEM_NAMES.put(107, "minecraft:fence_gate"); -+ ITEM_NAMES.put(108, "minecraft:brick_stairs"); -+ ITEM_NAMES.put(109, "minecraft:stone_brick_stairs"); -+ ITEM_NAMES.put(110, "minecraft:mycelium"); -+ ITEM_NAMES.put(111, "minecraft:waterlily"); -+ ITEM_NAMES.put(112, "minecraft:nether_brick"); -+ ITEM_NAMES.put(113, "minecraft:nether_brick_fence"); -+ ITEM_NAMES.put(114, "minecraft:nether_brick_stairs"); -+ ITEM_NAMES.put(116, "minecraft:enchanting_table"); -+ ITEM_NAMES.put(119, "minecraft:end_portal"); -+ ITEM_NAMES.put(120, "minecraft:end_portal_frame"); -+ ITEM_NAMES.put(121, "minecraft:end_stone"); -+ ITEM_NAMES.put(122, "minecraft:dragon_egg"); -+ ITEM_NAMES.put(123, "minecraft:redstone_lamp"); -+ ITEM_NAMES.put(125, "minecraft:double_wooden_slab"); -+ ITEM_NAMES.put(126, "minecraft:wooden_slab"); -+ ITEM_NAMES.put(127, "minecraft:cocoa"); -+ ITEM_NAMES.put(128, "minecraft:sandstone_stairs"); -+ ITEM_NAMES.put(129, "minecraft:emerald_ore"); -+ ITEM_NAMES.put(130, "minecraft:ender_chest"); -+ ITEM_NAMES.put(131, "minecraft:tripwire_hook"); -+ ITEM_NAMES.put(133, "minecraft:emerald_block"); -+ ITEM_NAMES.put(134, "minecraft:spruce_stairs"); -+ ITEM_NAMES.put(135, "minecraft:birch_stairs"); -+ ITEM_NAMES.put(136, "minecraft:jungle_stairs"); -+ ITEM_NAMES.put(137, "minecraft:command_block"); -+ ITEM_NAMES.put(138, "minecraft:beacon"); -+ ITEM_NAMES.put(139, "minecraft:cobblestone_wall"); -+ ITEM_NAMES.put(141, "minecraft:carrots"); -+ ITEM_NAMES.put(142, "minecraft:potatoes"); -+ ITEM_NAMES.put(143, "minecraft:wooden_button"); -+ ITEM_NAMES.put(145, "minecraft:anvil"); -+ ITEM_NAMES.put(146, "minecraft:trapped_chest"); -+ ITEM_NAMES.put(147, "minecraft:light_weighted_pressure_plate"); -+ ITEM_NAMES.put(148, "minecraft:heavy_weighted_pressure_plate"); -+ ITEM_NAMES.put(151, "minecraft:daylight_detector"); -+ ITEM_NAMES.put(152, "minecraft:redstone_block"); -+ ITEM_NAMES.put(153, "minecraft:quartz_ore"); -+ ITEM_NAMES.put(154, "minecraft:hopper"); -+ ITEM_NAMES.put(155, "minecraft:quartz_block"); -+ ITEM_NAMES.put(156, "minecraft:quartz_stairs"); -+ ITEM_NAMES.put(157, "minecraft:activator_rail"); -+ ITEM_NAMES.put(158, "minecraft:dropper"); -+ ITEM_NAMES.put(159, "minecraft:stained_hardened_clay"); -+ ITEM_NAMES.put(160, "minecraft:stained_glass_pane"); -+ ITEM_NAMES.put(161, "minecraft:leaves2"); -+ ITEM_NAMES.put(162, "minecraft:log2"); -+ ITEM_NAMES.put(163, "minecraft:acacia_stairs"); -+ ITEM_NAMES.put(164, "minecraft:dark_oak_stairs"); -+ ITEM_NAMES.put(170, "minecraft:hay_block"); -+ ITEM_NAMES.put(171, "minecraft:carpet"); -+ ITEM_NAMES.put(172, "minecraft:hardened_clay"); -+ ITEM_NAMES.put(173, "minecraft:coal_block"); -+ ITEM_NAMES.put(174, "minecraft:packed_ice"); -+ ITEM_NAMES.put(175, "minecraft:double_plant"); -+ ITEM_NAMES.put(256, "minecraft:iron_shovel"); -+ ITEM_NAMES.put(257, "minecraft:iron_pickaxe"); -+ ITEM_NAMES.put(258, "minecraft:iron_axe"); -+ ITEM_NAMES.put(259, "minecraft:flint_and_steel"); -+ ITEM_NAMES.put(260, "minecraft:apple"); -+ ITEM_NAMES.put(261, "minecraft:bow"); -+ ITEM_NAMES.put(262, "minecraft:arrow"); -+ ITEM_NAMES.put(263, "minecraft:coal"); -+ ITEM_NAMES.put(264, "minecraft:diamond"); -+ ITEM_NAMES.put(265, "minecraft:iron_ingot"); -+ ITEM_NAMES.put(266, "minecraft:gold_ingot"); -+ ITEM_NAMES.put(267, "minecraft:iron_sword"); -+ ITEM_NAMES.put(268, "minecraft:wooden_sword"); -+ ITEM_NAMES.put(269, "minecraft:wooden_shovel"); -+ ITEM_NAMES.put(270, "minecraft:wooden_pickaxe"); -+ ITEM_NAMES.put(271, "minecraft:wooden_axe"); -+ ITEM_NAMES.put(272, "minecraft:stone_sword"); -+ ITEM_NAMES.put(273, "minecraft:stone_shovel"); -+ ITEM_NAMES.put(274, "minecraft:stone_pickaxe"); -+ ITEM_NAMES.put(275, "minecraft:stone_axe"); -+ ITEM_NAMES.put(276, "minecraft:diamond_sword"); -+ ITEM_NAMES.put(277, "minecraft:diamond_shovel"); -+ ITEM_NAMES.put(278, "minecraft:diamond_pickaxe"); -+ ITEM_NAMES.put(279, "minecraft:diamond_axe"); -+ ITEM_NAMES.put(280, "minecraft:stick"); -+ ITEM_NAMES.put(281, "minecraft:bowl"); -+ ITEM_NAMES.put(282, "minecraft:mushroom_stew"); -+ ITEM_NAMES.put(283, "minecraft:golden_sword"); -+ ITEM_NAMES.put(284, "minecraft:golden_shovel"); -+ ITEM_NAMES.put(285, "minecraft:golden_pickaxe"); -+ ITEM_NAMES.put(286, "minecraft:golden_axe"); -+ ITEM_NAMES.put(287, "minecraft:string"); -+ ITEM_NAMES.put(288, "minecraft:feather"); -+ ITEM_NAMES.put(289, "minecraft:gunpowder"); -+ ITEM_NAMES.put(290, "minecraft:wooden_hoe"); -+ ITEM_NAMES.put(291, "minecraft:stone_hoe"); -+ ITEM_NAMES.put(292, "minecraft:iron_hoe"); -+ ITEM_NAMES.put(293, "minecraft:diamond_hoe"); -+ ITEM_NAMES.put(294, "minecraft:golden_hoe"); -+ ITEM_NAMES.put(295, "minecraft:wheat_seeds"); -+ ITEM_NAMES.put(296, "minecraft:wheat"); -+ ITEM_NAMES.put(297, "minecraft:bread"); -+ ITEM_NAMES.put(298, "minecraft:leather_helmet"); -+ ITEM_NAMES.put(299, "minecraft:leather_chestplate"); -+ ITEM_NAMES.put(300, "minecraft:leather_leggings"); -+ ITEM_NAMES.put(301, "minecraft:leather_boots"); -+ ITEM_NAMES.put(302, "minecraft:chainmail_helmet"); -+ ITEM_NAMES.put(303, "minecraft:chainmail_chestplate"); -+ ITEM_NAMES.put(304, "minecraft:chainmail_leggings"); -+ ITEM_NAMES.put(305, "minecraft:chainmail_boots"); -+ ITEM_NAMES.put(306, "minecraft:iron_helmet"); -+ ITEM_NAMES.put(307, "minecraft:iron_chestplate"); -+ ITEM_NAMES.put(308, "minecraft:iron_leggings"); -+ ITEM_NAMES.put(309, "minecraft:iron_boots"); -+ ITEM_NAMES.put(310, "minecraft:diamond_helmet"); -+ ITEM_NAMES.put(311, "minecraft:diamond_chestplate"); -+ ITEM_NAMES.put(312, "minecraft:diamond_leggings"); -+ ITEM_NAMES.put(313, "minecraft:diamond_boots"); -+ ITEM_NAMES.put(314, "minecraft:golden_helmet"); -+ ITEM_NAMES.put(315, "minecraft:golden_chestplate"); -+ ITEM_NAMES.put(316, "minecraft:golden_leggings"); -+ ITEM_NAMES.put(317, "minecraft:golden_boots"); -+ ITEM_NAMES.put(318, "minecraft:flint"); -+ ITEM_NAMES.put(319, "minecraft:porkchop"); -+ ITEM_NAMES.put(320, "minecraft:cooked_porkchop"); -+ ITEM_NAMES.put(321, "minecraft:painting"); -+ ITEM_NAMES.put(322, "minecraft:golden_apple"); -+ ITEM_NAMES.put(323, "minecraft:sign"); -+ ITEM_NAMES.put(324, "minecraft:wooden_door"); -+ ITEM_NAMES.put(325, "minecraft:bucket"); -+ ITEM_NAMES.put(326, "minecraft:water_bucket"); -+ ITEM_NAMES.put(327, "minecraft:lava_bucket"); -+ ITEM_NAMES.put(328, "minecraft:minecart"); -+ ITEM_NAMES.put(329, "minecraft:saddle"); -+ ITEM_NAMES.put(330, "minecraft:iron_door"); -+ ITEM_NAMES.put(331, "minecraft:redstone"); -+ ITEM_NAMES.put(332, "minecraft:snowball"); -+ ITEM_NAMES.put(333, "minecraft:boat"); -+ ITEM_NAMES.put(334, "minecraft:leather"); -+ ITEM_NAMES.put(335, "minecraft:milk_bucket"); -+ ITEM_NAMES.put(336, "minecraft:brick"); -+ ITEM_NAMES.put(337, "minecraft:clay_ball"); -+ ITEM_NAMES.put(338, "minecraft:reeds"); -+ ITEM_NAMES.put(339, "minecraft:paper"); -+ ITEM_NAMES.put(340, "minecraft:book"); -+ ITEM_NAMES.put(341, "minecraft:slime_ball"); -+ ITEM_NAMES.put(342, "minecraft:chest_minecart"); -+ ITEM_NAMES.put(343, "minecraft:furnace_minecart"); -+ ITEM_NAMES.put(344, "minecraft:egg"); -+ ITEM_NAMES.put(345, "minecraft:compass"); -+ ITEM_NAMES.put(346, "minecraft:fishing_rod"); -+ ITEM_NAMES.put(347, "minecraft:clock"); -+ ITEM_NAMES.put(348, "minecraft:glowstone_dust"); -+ ITEM_NAMES.put(349, "minecraft:fish"); -+ ITEM_NAMES.put(350, "minecraft:cooked_fish"); // Fix typo, the game never recognized cooked_fished -+ ITEM_NAMES.put(351, "minecraft:dye"); -+ ITEM_NAMES.put(352, "minecraft:bone"); -+ ITEM_NAMES.put(353, "minecraft:sugar"); -+ ITEM_NAMES.put(354, "minecraft:cake"); -+ ITEM_NAMES.put(355, "minecraft:bed"); -+ ITEM_NAMES.put(356, "minecraft:repeater"); -+ ITEM_NAMES.put(357, "minecraft:cookie"); -+ ITEM_NAMES.put(358, "minecraft:filled_map"); -+ ITEM_NAMES.put(359, "minecraft:shears"); -+ ITEM_NAMES.put(360, "minecraft:melon"); -+ ITEM_NAMES.put(361, "minecraft:pumpkin_seeds"); -+ ITEM_NAMES.put(362, "minecraft:melon_seeds"); -+ ITEM_NAMES.put(363, "minecraft:beef"); -+ ITEM_NAMES.put(364, "minecraft:cooked_beef"); -+ ITEM_NAMES.put(365, "minecraft:chicken"); -+ ITEM_NAMES.put(366, "minecraft:cooked_chicken"); -+ ITEM_NAMES.put(367, "minecraft:rotten_flesh"); -+ ITEM_NAMES.put(368, "minecraft:ender_pearl"); -+ ITEM_NAMES.put(369, "minecraft:blaze_rod"); -+ ITEM_NAMES.put(370, "minecraft:ghast_tear"); -+ ITEM_NAMES.put(371, "minecraft:gold_nugget"); -+ ITEM_NAMES.put(372, "minecraft:nether_wart"); -+ ITEM_NAMES.put(373, "minecraft:potion"); -+ ITEM_NAMES.put(374, "minecraft:glass_bottle"); -+ ITEM_NAMES.put(375, "minecraft:spider_eye"); -+ ITEM_NAMES.put(376, "minecraft:fermented_spider_eye"); -+ ITEM_NAMES.put(377, "minecraft:blaze_powder"); -+ ITEM_NAMES.put(378, "minecraft:magma_cream"); -+ ITEM_NAMES.put(379, "minecraft:brewing_stand"); -+ ITEM_NAMES.put(380, "minecraft:cauldron"); -+ ITEM_NAMES.put(381, "minecraft:ender_eye"); -+ ITEM_NAMES.put(382, "minecraft:speckled_melon"); -+ ITEM_NAMES.put(383, "minecraft:spawn_egg"); -+ ITEM_NAMES.put(384, "minecraft:experience_bottle"); -+ ITEM_NAMES.put(385, "minecraft:fire_charge"); -+ ITEM_NAMES.put(386, "minecraft:writable_book"); -+ ITEM_NAMES.put(387, "minecraft:written_book"); -+ ITEM_NAMES.put(388, "minecraft:emerald"); -+ ITEM_NAMES.put(389, "minecraft:item_frame"); -+ ITEM_NAMES.put(390, "minecraft:flower_pot"); -+ ITEM_NAMES.put(391, "minecraft:carrot"); -+ ITEM_NAMES.put(392, "minecraft:potato"); -+ ITEM_NAMES.put(393, "minecraft:baked_potato"); -+ ITEM_NAMES.put(394, "minecraft:poisonous_potato"); -+ ITEM_NAMES.put(395, "minecraft:map"); -+ ITEM_NAMES.put(396, "minecraft:golden_carrot"); -+ ITEM_NAMES.put(397, "minecraft:skull"); -+ ITEM_NAMES.put(398, "minecraft:carrot_on_a_stick"); -+ ITEM_NAMES.put(399, "minecraft:nether_star"); -+ ITEM_NAMES.put(400, "minecraft:pumpkin_pie"); -+ ITEM_NAMES.put(401, "minecraft:fireworks"); -+ ITEM_NAMES.put(402, "minecraft:firework_charge"); -+ ITEM_NAMES.put(403, "minecraft:enchanted_book"); -+ ITEM_NAMES.put(404, "minecraft:comparator"); -+ ITEM_NAMES.put(405, "minecraft:netherbrick"); -+ ITEM_NAMES.put(406, "minecraft:quartz"); -+ ITEM_NAMES.put(407, "minecraft:tnt_minecart"); -+ ITEM_NAMES.put(408, "minecraft:hopper_minecart"); -+ ITEM_NAMES.put(417, "minecraft:iron_horse_armor"); -+ ITEM_NAMES.put(418, "minecraft:golden_horse_armor"); -+ ITEM_NAMES.put(419, "minecraft:diamond_horse_armor"); -+ ITEM_NAMES.put(420, "minecraft:lead"); -+ ITEM_NAMES.put(421, "minecraft:name_tag"); -+ ITEM_NAMES.put(422, "minecraft:command_block_minecart"); -+ ITEM_NAMES.put(2256, "minecraft:record_13"); -+ ITEM_NAMES.put(2257, "minecraft:record_cat"); -+ ITEM_NAMES.put(2258, "minecraft:record_blocks"); -+ ITEM_NAMES.put(2259, "minecraft:record_chirp"); -+ ITEM_NAMES.put(2260, "minecraft:record_far"); -+ ITEM_NAMES.put(2261, "minecraft:record_mall"); -+ ITEM_NAMES.put(2262, "minecraft:record_mellohi"); -+ ITEM_NAMES.put(2263, "minecraft:record_stal"); -+ ITEM_NAMES.put(2264, "minecraft:record_strad"); -+ ITEM_NAMES.put(2265, "minecraft:record_ward"); -+ ITEM_NAMES.put(2266, "minecraft:record_11"); -+ ITEM_NAMES.put(2267, "minecraft:record_wait"); -+ // https://github.com/starlis/empirecraft/commit/2da59d1901407fc0c135ef0458c0fe9b016570b3 -+ // It's likely that this is a result of old CB/Spigot behavior still writing ids into items as ints. -+ // These ids do not appear to be used by regular MC anyways, so I do not see the harm of porting it here. -+ // Extras can be added if needed -+ String[] extra = new String[4_000]; -+ // EMC start -+ extra[409] = "minecraft:prismarine_shard"; -+ extra[410] = "minecraft:prismarine_crystals"; -+ extra[411] = "minecraft:rabbit"; -+ extra[412] = "minecraft:cooked_rabbit"; -+ extra[413] = "minecraft:rabbit_stew"; -+ extra[414] = "minecraft:rabbit_foot"; -+ extra[415] = "minecraft:rabbit_hide"; -+ extra[416] = "minecraft:armor_stand"; -+ extra[423] = "minecraft:mutton"; -+ extra[424] = "minecraft:cooked_mutton"; -+ extra[425] = "minecraft:banner"; -+ extra[426] = "minecraft:end_crystal"; -+ extra[427] = "minecraft:spruce_door"; -+ extra[428] = "minecraft:birch_door"; -+ extra[429] = "minecraft:jungle_door"; -+ extra[430] = "minecraft:acacia_door"; -+ extra[431] = "minecraft:dark_oak_door"; -+ extra[432] = "minecraft:chorus_fruit"; -+ extra[433] = "minecraft:chorus_fruit_popped"; -+ extra[434] = "minecraft:beetroot"; -+ extra[435] = "minecraft:beetroot_seeds"; -+ extra[436] = "minecraft:beetroot_soup"; -+ extra[437] = "minecraft:dragon_breath"; -+ extra[438] = "minecraft:splash_potion"; -+ extra[439] = "minecraft:spectral_arrow"; -+ extra[440] = "minecraft:tipped_arrow"; -+ extra[441] = "minecraft:lingering_potion"; -+ extra[442] = "minecraft:shield"; -+ extra[443] = "minecraft:elytra"; -+ extra[444] = "minecraft:spruce_boat"; -+ extra[445] = "minecraft:birch_boat"; -+ extra[446] = "minecraft:jungle_boat"; -+ extra[447] = "minecraft:acacia_boat"; -+ extra[448] = "minecraft:dark_oak_boat"; -+ extra[449] = "minecraft:totem_of_undying"; -+ extra[450] = "minecraft:shulker_shell"; -+ extra[452] = "minecraft:iron_nugget"; -+ extra[453] = "minecraft:knowledge_book"; -+ // EMC end -+ -+ // dump extra into map -+ for (int i = 0; i < extra.length; ++i) { -+ if (extra[i] != null) { -+ ITEM_NAMES.put(i, extra[i]); -+ } -+ } -+ } -+ -+ private static final String[] POTION_NAMES = new String[128]; -+ static { -+ POTION_NAMES[0] = "minecraft:water"; -+ POTION_NAMES[1] = "minecraft:regeneration"; -+ POTION_NAMES[2] = "minecraft:swiftness"; -+ POTION_NAMES[3] = "minecraft:fire_resistance"; -+ POTION_NAMES[4] = "minecraft:poison"; -+ POTION_NAMES[5] = "minecraft:healing"; -+ POTION_NAMES[6] = "minecraft:night_vision"; -+ POTION_NAMES[7] = null; -+ POTION_NAMES[8] = "minecraft:weakness"; -+ POTION_NAMES[9] = "minecraft:strength"; -+ POTION_NAMES[10] = "minecraft:slowness"; -+ POTION_NAMES[11] = "minecraft:leaping"; -+ POTION_NAMES[12] = "minecraft:harming"; -+ POTION_NAMES[13] = "minecraft:water_breathing"; -+ POTION_NAMES[14] = "minecraft:invisibility"; -+ POTION_NAMES[15] = null; -+ POTION_NAMES[16] = "minecraft:awkward"; -+ POTION_NAMES[17] = "minecraft:regeneration"; -+ POTION_NAMES[18] = "minecraft:swiftness"; -+ POTION_NAMES[19] = "minecraft:fire_resistance"; -+ POTION_NAMES[20] = "minecraft:poison"; -+ POTION_NAMES[21] = "minecraft:healing"; -+ POTION_NAMES[22] = "minecraft:night_vision"; -+ POTION_NAMES[23] = null; -+ POTION_NAMES[24] = "minecraft:weakness"; -+ POTION_NAMES[25] = "minecraft:strength"; -+ POTION_NAMES[26] = "minecraft:slowness"; -+ POTION_NAMES[27] = "minecraft:leaping"; -+ POTION_NAMES[28] = "minecraft:harming"; -+ POTION_NAMES[29] = "minecraft:water_breathing"; -+ POTION_NAMES[30] = "minecraft:invisibility"; -+ POTION_NAMES[31] = null; -+ POTION_NAMES[32] = "minecraft:thick"; -+ POTION_NAMES[33] = "minecraft:strong_regeneration"; -+ POTION_NAMES[34] = "minecraft:strong_swiftness"; -+ POTION_NAMES[35] = "minecraft:fire_resistance"; -+ POTION_NAMES[36] = "minecraft:strong_poison"; -+ POTION_NAMES[37] = "minecraft:strong_healing"; -+ POTION_NAMES[38] = "minecraft:night_vision"; -+ POTION_NAMES[39] = null; -+ POTION_NAMES[40] = "minecraft:weakness"; -+ POTION_NAMES[41] = "minecraft:strong_strength"; -+ POTION_NAMES[42] = "minecraft:slowness"; -+ POTION_NAMES[43] = "minecraft:strong_leaping"; -+ POTION_NAMES[44] = "minecraft:strong_harming"; -+ POTION_NAMES[45] = "minecraft:water_breathing"; -+ POTION_NAMES[46] = "minecraft:invisibility"; -+ POTION_NAMES[47] = null; -+ POTION_NAMES[48] = null; -+ POTION_NAMES[49] = "minecraft:strong_regeneration"; -+ POTION_NAMES[50] = "minecraft:strong_swiftness"; -+ POTION_NAMES[51] = "minecraft:fire_resistance"; -+ POTION_NAMES[52] = "minecraft:strong_poison"; -+ POTION_NAMES[53] = "minecraft:strong_healing"; -+ POTION_NAMES[54] = "minecraft:night_vision"; -+ POTION_NAMES[55] = null; -+ POTION_NAMES[56] = "minecraft:weakness"; -+ POTION_NAMES[57] = "minecraft:strong_strength"; -+ POTION_NAMES[58] = "minecraft:slowness"; -+ POTION_NAMES[59] = "minecraft:strong_leaping"; -+ POTION_NAMES[60] = "minecraft:strong_harming"; -+ POTION_NAMES[61] = "minecraft:water_breathing"; -+ POTION_NAMES[62] = "minecraft:invisibility"; -+ POTION_NAMES[63] = null; -+ POTION_NAMES[64] = "minecraft:mundane"; -+ POTION_NAMES[65] = "minecraft:long_regeneration"; -+ POTION_NAMES[66] = "minecraft:long_swiftness"; -+ POTION_NAMES[67] = "minecraft:long_fire_resistance"; -+ POTION_NAMES[68] = "minecraft:long_poison"; -+ POTION_NAMES[69] = "minecraft:healing"; -+ POTION_NAMES[70] = "minecraft:long_night_vision"; -+ POTION_NAMES[71] = null; -+ POTION_NAMES[72] = "minecraft:long_weakness"; -+ POTION_NAMES[73] = "minecraft:long_strength"; -+ POTION_NAMES[74] = "minecraft:long_slowness"; -+ POTION_NAMES[75] = "minecraft:long_leaping"; -+ POTION_NAMES[76] = "minecraft:harming"; -+ POTION_NAMES[77] = "minecraft:long_water_breathing"; -+ POTION_NAMES[78] = "minecraft:long_invisibility"; -+ POTION_NAMES[79] = null; -+ POTION_NAMES[80] = "minecraft:awkward"; -+ POTION_NAMES[81] = "minecraft:long_regeneration"; -+ POTION_NAMES[82] = "minecraft:long_swiftness"; -+ POTION_NAMES[83] = "minecraft:long_fire_resistance"; -+ POTION_NAMES[84] = "minecraft:long_poison"; -+ POTION_NAMES[85] = "minecraft:healing"; -+ POTION_NAMES[86] = "minecraft:long_night_vision"; -+ POTION_NAMES[87] = null; -+ POTION_NAMES[88] = "minecraft:long_weakness"; -+ POTION_NAMES[89] = "minecraft:long_strength"; -+ POTION_NAMES[90] = "minecraft:long_slowness"; -+ POTION_NAMES[91] = "minecraft:long_leaping"; -+ POTION_NAMES[92] = "minecraft:harming"; -+ POTION_NAMES[93] = "minecraft:long_water_breathing"; -+ POTION_NAMES[94] = "minecraft:long_invisibility"; -+ POTION_NAMES[95] = null; -+ POTION_NAMES[96] = "minecraft:thick"; -+ POTION_NAMES[97] = "minecraft:regeneration"; -+ POTION_NAMES[98] = "minecraft:swiftness"; -+ POTION_NAMES[99] = "minecraft:long_fire_resistance"; -+ POTION_NAMES[100] = "minecraft:poison"; -+ POTION_NAMES[101] = "minecraft:strong_healing"; -+ POTION_NAMES[102] = "minecraft:long_night_vision"; -+ POTION_NAMES[103] = null; -+ POTION_NAMES[104] = "minecraft:long_weakness"; -+ POTION_NAMES[105] = "minecraft:strength"; -+ POTION_NAMES[106] = "minecraft:long_slowness"; -+ POTION_NAMES[107] = "minecraft:leaping"; -+ POTION_NAMES[108] = "minecraft:strong_harming"; -+ POTION_NAMES[109] = "minecraft:long_water_breathing"; -+ POTION_NAMES[110] = "minecraft:long_invisibility"; -+ POTION_NAMES[111] = null; -+ POTION_NAMES[112] = null; -+ POTION_NAMES[113] = "minecraft:regeneration"; -+ POTION_NAMES[114] = "minecraft:swiftness"; -+ POTION_NAMES[115] = "minecraft:long_fire_resistance"; -+ POTION_NAMES[116] = "minecraft:poison"; -+ POTION_NAMES[117] = "minecraft:strong_healing"; -+ POTION_NAMES[118] = "minecraft:long_night_vision"; -+ POTION_NAMES[119] = null; -+ POTION_NAMES[120] = "minecraft:long_weakness"; -+ POTION_NAMES[121] = "minecraft:strength"; -+ POTION_NAMES[122] = "minecraft:long_slowness"; -+ POTION_NAMES[123] = "minecraft:leaping"; -+ POTION_NAMES[124] = "minecraft:strong_harming"; -+ POTION_NAMES[125] = "minecraft:long_water_breathing"; -+ POTION_NAMES[126] = "minecraft:long_invisibility"; -+ POTION_NAMES[127] = null; -+ } -+ -+ // ret is nullable, you are supposed to log when it does not exist, NOT HIDE IT! -+ public static String getNameFromId(final int id) { -+ return ITEM_NAMES.get(id); -+ } -+ -+ public static String getPotionNameFromId(final short id) { -+ return POTION_NAMES[id & 127]; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5008c6d28b7f9b730bfaf257a264edcb45c78487 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java -@@ -0,0 +1,79 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.helpers; -+ -+public final class HelperSpawnEggNameV105 { -+ -+ private static final String[] ID_TO_STRING = new String[256]; -+ static { -+ ID_TO_STRING[1] = "Item"; -+ ID_TO_STRING[2] = "XPOrb"; -+ ID_TO_STRING[7] = "ThrownEgg"; -+ ID_TO_STRING[8] = "LeashKnot"; -+ ID_TO_STRING[9] = "Painting"; -+ ID_TO_STRING[10] = "Arrow"; -+ ID_TO_STRING[11] = "Snowball"; -+ ID_TO_STRING[12] = "Fireball"; -+ ID_TO_STRING[13] = "SmallFireball"; -+ ID_TO_STRING[14] = "ThrownEnderpearl"; -+ ID_TO_STRING[15] = "EyeOfEnderSignal"; -+ ID_TO_STRING[16] = "ThrownPotion"; -+ ID_TO_STRING[17] = "ThrownExpBottle"; -+ ID_TO_STRING[18] = "ItemFrame"; -+ ID_TO_STRING[19] = "WitherSkull"; -+ ID_TO_STRING[20] = "PrimedTnt"; -+ ID_TO_STRING[21] = "FallingSand"; -+ ID_TO_STRING[22] = "FireworksRocketEntity"; -+ ID_TO_STRING[23] = "TippedArrow"; -+ ID_TO_STRING[24] = "SpectralArrow"; -+ ID_TO_STRING[25] = "ShulkerBullet"; -+ ID_TO_STRING[26] = "DragonFireball"; -+ ID_TO_STRING[30] = "ArmorStand"; -+ ID_TO_STRING[41] = "Boat"; -+ ID_TO_STRING[42] = "MinecartRideable"; -+ ID_TO_STRING[43] = "MinecartChest"; -+ ID_TO_STRING[44] = "MinecartFurnace"; -+ ID_TO_STRING[45] = "MinecartTNT"; -+ ID_TO_STRING[46] = "MinecartHopper"; -+ ID_TO_STRING[47] = "MinecartSpawner"; -+ ID_TO_STRING[40] = "MinecartCommandBlock"; -+ ID_TO_STRING[48] = "Mob"; -+ ID_TO_STRING[49] = "Monster"; -+ ID_TO_STRING[50] = "Creeper"; -+ ID_TO_STRING[51] = "Skeleton"; -+ ID_TO_STRING[52] = "Spider"; -+ ID_TO_STRING[53] = "Giant"; -+ ID_TO_STRING[54] = "Zombie"; -+ ID_TO_STRING[55] = "Slime"; -+ ID_TO_STRING[56] = "Ghast"; -+ ID_TO_STRING[57] = "PigZombie"; -+ ID_TO_STRING[58] = "Enderman"; -+ ID_TO_STRING[59] = "CaveSpider"; -+ ID_TO_STRING[60] = "Silverfish"; -+ ID_TO_STRING[61] = "Blaze"; -+ ID_TO_STRING[62] = "LavaSlime"; -+ ID_TO_STRING[63] = "EnderDragon"; -+ ID_TO_STRING[64] = "WitherBoss"; -+ ID_TO_STRING[65] = "Bat"; -+ ID_TO_STRING[66] = "Witch"; -+ ID_TO_STRING[67] = "Endermite"; -+ ID_TO_STRING[68] = "Guardian"; -+ ID_TO_STRING[69] = "Shulker"; -+ ID_TO_STRING[90] = "Pig"; -+ ID_TO_STRING[91] = "Sheep"; -+ ID_TO_STRING[92] = "Cow"; -+ ID_TO_STRING[93] = "Chicken"; -+ ID_TO_STRING[94] = "Squid"; -+ ID_TO_STRING[95] = "Wolf"; -+ ID_TO_STRING[96] = "MushroomCow"; -+ ID_TO_STRING[97] = "SnowMan"; -+ ID_TO_STRING[98] = "Ozelot"; -+ ID_TO_STRING[99] = "VillagerGolem"; -+ ID_TO_STRING[100] = "EntityHorse"; -+ ID_TO_STRING[101] = "Rabbit"; -+ ID_TO_STRING[120] = "Villager"; -+ ID_TO_STRING[200] = "EnderCrystal"; -+ } -+ -+ public static String getSpawnNameFromId(final short id) { -+ return ID_TO_STRING[id & 255]; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java -new file mode 100644 -index 0000000000000000000000000000000000000000..051b44ea226db849faf821567f0b5321d360fe45 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java -@@ -0,0 +1,89 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.helpers; -+ -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.function.Function; -+ -+public final class RenameHelper { -+ -+ // assumes no two or more entries are renamed to a single value, otherwise result will be only one of them will win -+ // and there is no defined winner in such a case -+ public static void renameKeys(final MapType data, final Function renamer) { -+ if (data == null) { -+ return; -+ } -+ -+ List newKeys = null; -+ List newValues = null; -+ boolean needsRename = false; -+ for (final String key : data.keys()) { -+ final String renamed = renamer.apply(key); -+ if (renamed != null) { -+ newKeys = new ArrayList<>(); -+ newValues = new ArrayList<>(); -+ newValues.add(data.getGeneric(key)); -+ newKeys.add(renamed); -+ data.remove(key); -+ needsRename = true; -+ break; -+ } -+ } -+ -+ if (!needsRename) { -+ return; -+ } -+ -+ for (final String key : new ArrayList<>(data.keys())) { -+ final String renamed = renamer.apply(key); -+ -+ if (renamed != null) { -+ newValues.add(data.getGeneric(key)); -+ newKeys.add(renamed); -+ data.remove(key); -+ } -+ } -+ -+ // insert new keys -+ for (int i = 0, len = newKeys.size(); i < len; ++i) { -+ final String key = newKeys.get(i); -+ final Object value = newValues.get(i); -+ -+ data.setGeneric(key, value); -+ } -+ } -+ -+ // Clobbers anything in toKey if fromKey exists -+ public static void renameSingle(final MapType data, final String fromKey, final String toKey) { -+ if (data == null) { -+ return; -+ } -+ -+ final Object value = data.getGeneric(fromKey); -+ if (value != null) { -+ data.remove(fromKey); -+ data.setGeneric(toKey, value); -+ } -+ } -+ -+ public static void renameString(final MapType data, final String key, final Function renamer) { -+ if (data == null) { -+ return; -+ } -+ -+ final String value = data.getString(key); -+ if (value == null) { -+ return; -+ } -+ -+ final String renamed = renamer.apply(value); -+ if (renamed == null) { -+ return; -+ } -+ -+ data.setString(key, renamed); -+ } -+ -+ private RenameHelper() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..94569f0ccff0d3a09eafd4ba73572d9db0a0ac5b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.itemname; -+ -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import java.util.function.Function; -+ -+public final class ConverterAbstractItemRename { -+ -+ private ConverterAbstractItemRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.ITEM_NAME, renamer); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java -new file mode 100644 -index 0000000000000000000000000000000000000000..21176b8b96be6cb93d3dc1a74ae9f53f1ad4740c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java -@@ -0,0 +1,460 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.itemstack; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.mojang.logging.LogUtils; -+import org.slf4j.Logger; -+import java.util.Arrays; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+ -+public final class ConverterFlattenItemStack extends DataConverter, MapType> { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ // Map of "id.damage" -> "flattened id" -+ private static final Map FLATTEN_MAP = new HashMap<>(); -+ static { -+ FLATTEN_MAP.put("minecraft:stone.0", "minecraft:stone"); -+ FLATTEN_MAP.put("minecraft:stone.1", "minecraft:granite"); -+ FLATTEN_MAP.put("minecraft:stone.2", "minecraft:polished_granite"); -+ FLATTEN_MAP.put("minecraft:stone.3", "minecraft:diorite"); -+ FLATTEN_MAP.put("minecraft:stone.4", "minecraft:polished_diorite"); -+ FLATTEN_MAP.put("minecraft:stone.5", "minecraft:andesite"); -+ FLATTEN_MAP.put("minecraft:stone.6", "minecraft:polished_andesite"); -+ FLATTEN_MAP.put("minecraft:dirt.0", "minecraft:dirt"); -+ FLATTEN_MAP.put("minecraft:dirt.1", "minecraft:coarse_dirt"); -+ FLATTEN_MAP.put("minecraft:dirt.2", "minecraft:podzol"); -+ FLATTEN_MAP.put("minecraft:leaves.0", "minecraft:oak_leaves"); -+ FLATTEN_MAP.put("minecraft:leaves.1", "minecraft:spruce_leaves"); -+ FLATTEN_MAP.put("minecraft:leaves.2", "minecraft:birch_leaves"); -+ FLATTEN_MAP.put("minecraft:leaves.3", "minecraft:jungle_leaves"); -+ FLATTEN_MAP.put("minecraft:leaves2.0", "minecraft:acacia_leaves"); -+ FLATTEN_MAP.put("minecraft:leaves2.1", "minecraft:dark_oak_leaves"); -+ FLATTEN_MAP.put("minecraft:log.0", "minecraft:oak_log"); -+ FLATTEN_MAP.put("minecraft:log.1", "minecraft:spruce_log"); -+ FLATTEN_MAP.put("minecraft:log.2", "minecraft:birch_log"); -+ FLATTEN_MAP.put("minecraft:log.3", "minecraft:jungle_log"); -+ FLATTEN_MAP.put("minecraft:log2.0", "minecraft:acacia_log"); -+ FLATTEN_MAP.put("minecraft:log2.1", "minecraft:dark_oak_log"); -+ FLATTEN_MAP.put("minecraft:sapling.0", "minecraft:oak_sapling"); -+ FLATTEN_MAP.put("minecraft:sapling.1", "minecraft:spruce_sapling"); -+ FLATTEN_MAP.put("minecraft:sapling.2", "minecraft:birch_sapling"); -+ FLATTEN_MAP.put("minecraft:sapling.3", "minecraft:jungle_sapling"); -+ FLATTEN_MAP.put("minecraft:sapling.4", "minecraft:acacia_sapling"); -+ FLATTEN_MAP.put("minecraft:sapling.5", "minecraft:dark_oak_sapling"); -+ FLATTEN_MAP.put("minecraft:planks.0", "minecraft:oak_planks"); -+ FLATTEN_MAP.put("minecraft:planks.1", "minecraft:spruce_planks"); -+ FLATTEN_MAP.put("minecraft:planks.2", "minecraft:birch_planks"); -+ FLATTEN_MAP.put("minecraft:planks.3", "minecraft:jungle_planks"); -+ FLATTEN_MAP.put("minecraft:planks.4", "minecraft:acacia_planks"); -+ FLATTEN_MAP.put("minecraft:planks.5", "minecraft:dark_oak_planks"); -+ FLATTEN_MAP.put("minecraft:sand.0", "minecraft:sand"); -+ FLATTEN_MAP.put("minecraft:sand.1", "minecraft:red_sand"); -+ FLATTEN_MAP.put("minecraft:quartz_block.0", "minecraft:quartz_block"); -+ FLATTEN_MAP.put("minecraft:quartz_block.1", "minecraft:chiseled_quartz_block"); -+ FLATTEN_MAP.put("minecraft:quartz_block.2", "minecraft:quartz_pillar"); -+ FLATTEN_MAP.put("minecraft:anvil.0", "minecraft:anvil"); -+ FLATTEN_MAP.put("minecraft:anvil.1", "minecraft:chipped_anvil"); -+ FLATTEN_MAP.put("minecraft:anvil.2", "minecraft:damaged_anvil"); -+ FLATTEN_MAP.put("minecraft:wool.0", "minecraft:white_wool"); -+ FLATTEN_MAP.put("minecraft:wool.1", "minecraft:orange_wool"); -+ FLATTEN_MAP.put("minecraft:wool.2", "minecraft:magenta_wool"); -+ FLATTEN_MAP.put("minecraft:wool.3", "minecraft:light_blue_wool"); -+ FLATTEN_MAP.put("minecraft:wool.4", "minecraft:yellow_wool"); -+ FLATTEN_MAP.put("minecraft:wool.5", "minecraft:lime_wool"); -+ FLATTEN_MAP.put("minecraft:wool.6", "minecraft:pink_wool"); -+ FLATTEN_MAP.put("minecraft:wool.7", "minecraft:gray_wool"); -+ FLATTEN_MAP.put("minecraft:wool.8", "minecraft:light_gray_wool"); -+ FLATTEN_MAP.put("minecraft:wool.9", "minecraft:cyan_wool"); -+ FLATTEN_MAP.put("minecraft:wool.10", "minecraft:purple_wool"); -+ FLATTEN_MAP.put("minecraft:wool.11", "minecraft:blue_wool"); -+ FLATTEN_MAP.put("minecraft:wool.12", "minecraft:brown_wool"); -+ FLATTEN_MAP.put("minecraft:wool.13", "minecraft:green_wool"); -+ FLATTEN_MAP.put("minecraft:wool.14", "minecraft:red_wool"); -+ FLATTEN_MAP.put("minecraft:wool.15", "minecraft:black_wool"); -+ FLATTEN_MAP.put("minecraft:carpet.0", "minecraft:white_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.1", "minecraft:orange_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.2", "minecraft:magenta_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.3", "minecraft:light_blue_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.4", "minecraft:yellow_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.5", "minecraft:lime_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.6", "minecraft:pink_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.7", "minecraft:gray_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.8", "minecraft:light_gray_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.9", "minecraft:cyan_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.10", "minecraft:purple_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.11", "minecraft:blue_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.12", "minecraft:brown_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.13", "minecraft:green_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.14", "minecraft:red_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.15", "minecraft:black_carpet"); -+ FLATTEN_MAP.put("minecraft:hardened_clay.0", "minecraft:terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.0", "minecraft:white_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.1", "minecraft:orange_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.2", "minecraft:magenta_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.3", "minecraft:light_blue_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.4", "minecraft:yellow_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.5", "minecraft:lime_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.6", "minecraft:pink_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.7", "minecraft:gray_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.8", "minecraft:light_gray_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.9", "minecraft:cyan_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.10", "minecraft:purple_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.11", "minecraft:blue_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.12", "minecraft:brown_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.13", "minecraft:green_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.14", "minecraft:red_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.15", "minecraft:black_terracotta"); -+ FLATTEN_MAP.put("minecraft:silver_glazed_terracotta.0", "minecraft:light_gray_glazed_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_glass.0", "minecraft:white_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.1", "minecraft:orange_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.2", "minecraft:magenta_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.3", "minecraft:light_blue_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.4", "minecraft:yellow_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.5", "minecraft:lime_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.6", "minecraft:pink_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.7", "minecraft:gray_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.8", "minecraft:light_gray_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.9", "minecraft:cyan_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.10", "minecraft:purple_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.11", "minecraft:blue_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.12", "minecraft:brown_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.13", "minecraft:green_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.14", "minecraft:red_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.15", "minecraft:black_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.0", "minecraft:white_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.1", "minecraft:orange_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.2", "minecraft:magenta_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.3", "minecraft:light_blue_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.4", "minecraft:yellow_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.5", "minecraft:lime_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.6", "minecraft:pink_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.7", "minecraft:gray_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.8", "minecraft:light_gray_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.9", "minecraft:cyan_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.10", "minecraft:purple_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.11", "minecraft:blue_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.12", "minecraft:brown_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.13", "minecraft:green_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.14", "minecraft:red_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.15", "minecraft:black_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:prismarine.0", "minecraft:prismarine"); -+ FLATTEN_MAP.put("minecraft:prismarine.1", "minecraft:prismarine_bricks"); -+ FLATTEN_MAP.put("minecraft:prismarine.2", "minecraft:dark_prismarine"); -+ FLATTEN_MAP.put("minecraft:concrete.0", "minecraft:white_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.1", "minecraft:orange_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.2", "minecraft:magenta_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.3", "minecraft:light_blue_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.4", "minecraft:yellow_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.5", "minecraft:lime_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.6", "minecraft:pink_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.7", "minecraft:gray_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.8", "minecraft:light_gray_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.9", "minecraft:cyan_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.10", "minecraft:purple_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.11", "minecraft:blue_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.12", "minecraft:brown_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.13", "minecraft:green_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.14", "minecraft:red_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.15", "minecraft:black_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.0", "minecraft:white_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.1", "minecraft:orange_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.2", "minecraft:magenta_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.3", "minecraft:light_blue_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.4", "minecraft:yellow_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.5", "minecraft:lime_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.6", "minecraft:pink_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.7", "minecraft:gray_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.8", "minecraft:light_gray_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.9", "minecraft:cyan_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.10", "minecraft:purple_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.11", "minecraft:blue_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.12", "minecraft:brown_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.13", "minecraft:green_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.14", "minecraft:red_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.15", "minecraft:black_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:cobblestone_wall.0", "minecraft:cobblestone_wall"); -+ FLATTEN_MAP.put("minecraft:cobblestone_wall.1", "minecraft:mossy_cobblestone_wall"); -+ FLATTEN_MAP.put("minecraft:sandstone.0", "minecraft:sandstone"); -+ FLATTEN_MAP.put("minecraft:sandstone.1", "minecraft:chiseled_sandstone"); -+ FLATTEN_MAP.put("minecraft:sandstone.2", "minecraft:cut_sandstone"); -+ FLATTEN_MAP.put("minecraft:red_sandstone.0", "minecraft:red_sandstone"); -+ FLATTEN_MAP.put("minecraft:red_sandstone.1", "minecraft:chiseled_red_sandstone"); -+ FLATTEN_MAP.put("minecraft:red_sandstone.2", "minecraft:cut_red_sandstone"); -+ FLATTEN_MAP.put("minecraft:stonebrick.0", "minecraft:stone_bricks"); -+ FLATTEN_MAP.put("minecraft:stonebrick.1", "minecraft:mossy_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:stonebrick.2", "minecraft:cracked_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:stonebrick.3", "minecraft:chiseled_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:monster_egg.0", "minecraft:infested_stone"); -+ FLATTEN_MAP.put("minecraft:monster_egg.1", "minecraft:infested_cobblestone"); -+ FLATTEN_MAP.put("minecraft:monster_egg.2", "minecraft:infested_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:monster_egg.3", "minecraft:infested_mossy_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:monster_egg.4", "minecraft:infested_cracked_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:monster_egg.5", "minecraft:infested_chiseled_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:yellow_flower.0", "minecraft:dandelion"); -+ FLATTEN_MAP.put("minecraft:red_flower.0", "minecraft:poppy"); -+ FLATTEN_MAP.put("minecraft:red_flower.1", "minecraft:blue_orchid"); -+ FLATTEN_MAP.put("minecraft:red_flower.2", "minecraft:allium"); -+ FLATTEN_MAP.put("minecraft:red_flower.3", "minecraft:azure_bluet"); -+ FLATTEN_MAP.put("minecraft:red_flower.4", "minecraft:red_tulip"); -+ FLATTEN_MAP.put("minecraft:red_flower.5", "minecraft:orange_tulip"); -+ FLATTEN_MAP.put("minecraft:red_flower.6", "minecraft:white_tulip"); -+ FLATTEN_MAP.put("minecraft:red_flower.7", "minecraft:pink_tulip"); -+ FLATTEN_MAP.put("minecraft:red_flower.8", "minecraft:oxeye_daisy"); -+ FLATTEN_MAP.put("minecraft:double_plant.0", "minecraft:sunflower"); -+ FLATTEN_MAP.put("minecraft:double_plant.1", "minecraft:lilac"); -+ FLATTEN_MAP.put("minecraft:double_plant.2", "minecraft:tall_grass"); -+ FLATTEN_MAP.put("minecraft:double_plant.3", "minecraft:large_fern"); -+ FLATTEN_MAP.put("minecraft:double_plant.4", "minecraft:rose_bush"); -+ FLATTEN_MAP.put("minecraft:double_plant.5", "minecraft:peony"); -+ FLATTEN_MAP.put("minecraft:deadbush.0", "minecraft:dead_bush"); -+ FLATTEN_MAP.put("minecraft:tallgrass.0", "minecraft:dead_bush"); -+ FLATTEN_MAP.put("minecraft:tallgrass.1", "minecraft:grass"); -+ FLATTEN_MAP.put("minecraft:tallgrass.2", "minecraft:fern"); -+ FLATTEN_MAP.put("minecraft:sponge.0", "minecraft:sponge"); -+ FLATTEN_MAP.put("minecraft:sponge.1", "minecraft:wet_sponge"); -+ FLATTEN_MAP.put("minecraft:purpur_slab.0", "minecraft:purpur_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.0", "minecraft:stone_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.1", "minecraft:sandstone_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.2", "minecraft:petrified_oak_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.3", "minecraft:cobblestone_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.4", "minecraft:brick_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.5", "minecraft:stone_brick_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.6", "minecraft:nether_brick_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.7", "minecraft:quartz_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab2.0", "minecraft:red_sandstone_slab"); -+ FLATTEN_MAP.put("minecraft:wooden_slab.0", "minecraft:oak_slab"); -+ FLATTEN_MAP.put("minecraft:wooden_slab.1", "minecraft:spruce_slab"); -+ FLATTEN_MAP.put("minecraft:wooden_slab.2", "minecraft:birch_slab"); -+ FLATTEN_MAP.put("minecraft:wooden_slab.3", "minecraft:jungle_slab"); -+ FLATTEN_MAP.put("minecraft:wooden_slab.4", "minecraft:acacia_slab"); -+ FLATTEN_MAP.put("minecraft:wooden_slab.5", "minecraft:dark_oak_slab"); -+ FLATTEN_MAP.put("minecraft:coal.0", "minecraft:coal"); -+ FLATTEN_MAP.put("minecraft:coal.1", "minecraft:charcoal"); -+ FLATTEN_MAP.put("minecraft:fish.0", "minecraft:cod"); -+ FLATTEN_MAP.put("minecraft:fish.1", "minecraft:salmon"); -+ FLATTEN_MAP.put("minecraft:fish.2", "minecraft:clownfish"); -+ FLATTEN_MAP.put("minecraft:fish.3", "minecraft:pufferfish"); -+ FLATTEN_MAP.put("minecraft:cooked_fish.0", "minecraft:cooked_cod"); -+ FLATTEN_MAP.put("minecraft:cooked_fish.1", "minecraft:cooked_salmon"); -+ FLATTEN_MAP.put("minecraft:skull.0", "minecraft:skeleton_skull"); -+ FLATTEN_MAP.put("minecraft:skull.1", "minecraft:wither_skeleton_skull"); -+ FLATTEN_MAP.put("minecraft:skull.2", "minecraft:zombie_head"); -+ FLATTEN_MAP.put("minecraft:skull.3", "minecraft:player_head"); -+ FLATTEN_MAP.put("minecraft:skull.4", "minecraft:creeper_head"); -+ FLATTEN_MAP.put("minecraft:skull.5", "minecraft:dragon_head"); -+ FLATTEN_MAP.put("minecraft:golden_apple.0", "minecraft:golden_apple"); -+ FLATTEN_MAP.put("minecraft:golden_apple.1", "minecraft:enchanted_golden_apple"); -+ FLATTEN_MAP.put("minecraft:fireworks.0", "minecraft:firework_rocket"); -+ FLATTEN_MAP.put("minecraft:firework_charge.0", "minecraft:firework_star"); -+ FLATTEN_MAP.put("minecraft:dye.0", "minecraft:ink_sac"); -+ FLATTEN_MAP.put("minecraft:dye.1", "minecraft:rose_red"); -+ FLATTEN_MAP.put("minecraft:dye.2", "minecraft:cactus_green"); -+ FLATTEN_MAP.put("minecraft:dye.3", "minecraft:cocoa_beans"); -+ FLATTEN_MAP.put("minecraft:dye.4", "minecraft:lapis_lazuli"); -+ FLATTEN_MAP.put("minecraft:dye.5", "minecraft:purple_dye"); -+ FLATTEN_MAP.put("minecraft:dye.6", "minecraft:cyan_dye"); -+ FLATTEN_MAP.put("minecraft:dye.7", "minecraft:light_gray_dye"); -+ FLATTEN_MAP.put("minecraft:dye.8", "minecraft:gray_dye"); -+ FLATTEN_MAP.put("minecraft:dye.9", "minecraft:pink_dye"); -+ FLATTEN_MAP.put("minecraft:dye.10", "minecraft:lime_dye"); -+ FLATTEN_MAP.put("minecraft:dye.11", "minecraft:dandelion_yellow"); -+ FLATTEN_MAP.put("minecraft:dye.12", "minecraft:light_blue_dye"); -+ FLATTEN_MAP.put("minecraft:dye.13", "minecraft:magenta_dye"); -+ FLATTEN_MAP.put("minecraft:dye.14", "minecraft:orange_dye"); -+ FLATTEN_MAP.put("minecraft:dye.15", "minecraft:bone_meal"); -+ FLATTEN_MAP.put("minecraft:silver_shulker_box.0", "minecraft:light_gray_shulker_box"); -+ FLATTEN_MAP.put("minecraft:fence.0", "minecraft:oak_fence"); -+ FLATTEN_MAP.put("minecraft:fence_gate.0", "minecraft:oak_fence_gate"); -+ FLATTEN_MAP.put("minecraft:wooden_door.0", "minecraft:oak_door"); -+ FLATTEN_MAP.put("minecraft:boat.0", "minecraft:oak_boat"); -+ FLATTEN_MAP.put("minecraft:lit_pumpkin.0", "minecraft:jack_o_lantern"); -+ FLATTEN_MAP.put("minecraft:pumpkin.0", "minecraft:carved_pumpkin"); -+ FLATTEN_MAP.put("minecraft:trapdoor.0", "minecraft:oak_trapdoor"); -+ FLATTEN_MAP.put("minecraft:nether_brick.0", "minecraft:nether_bricks"); -+ FLATTEN_MAP.put("minecraft:red_nether_brick.0", "minecraft:red_nether_bricks"); -+ FLATTEN_MAP.put("minecraft:netherbrick.0", "minecraft:nether_brick"); -+ FLATTEN_MAP.put("minecraft:wooden_button.0", "minecraft:oak_button"); -+ FLATTEN_MAP.put("minecraft:wooden_pressure_plate.0", "minecraft:oak_pressure_plate"); -+ FLATTEN_MAP.put("minecraft:noteblock.0", "minecraft:note_block"); -+ FLATTEN_MAP.put("minecraft:bed.0", "minecraft:white_bed"); -+ FLATTEN_MAP.put("minecraft:bed.1", "minecraft:orange_bed"); -+ FLATTEN_MAP.put("minecraft:bed.2", "minecraft:magenta_bed"); -+ FLATTEN_MAP.put("minecraft:bed.3", "minecraft:light_blue_bed"); -+ FLATTEN_MAP.put("minecraft:bed.4", "minecraft:yellow_bed"); -+ FLATTEN_MAP.put("minecraft:bed.5", "minecraft:lime_bed"); -+ FLATTEN_MAP.put("minecraft:bed.6", "minecraft:pink_bed"); -+ FLATTEN_MAP.put("minecraft:bed.7", "minecraft:gray_bed"); -+ FLATTEN_MAP.put("minecraft:bed.8", "minecraft:light_gray_bed"); -+ FLATTEN_MAP.put("minecraft:bed.9", "minecraft:cyan_bed"); -+ FLATTEN_MAP.put("minecraft:bed.10", "minecraft:purple_bed"); -+ FLATTEN_MAP.put("minecraft:bed.11", "minecraft:blue_bed"); -+ FLATTEN_MAP.put("minecraft:bed.12", "minecraft:brown_bed"); -+ FLATTEN_MAP.put("minecraft:bed.13", "minecraft:green_bed"); -+ FLATTEN_MAP.put("minecraft:bed.14", "minecraft:red_bed"); -+ FLATTEN_MAP.put("minecraft:bed.15", "minecraft:black_bed"); -+ FLATTEN_MAP.put("minecraft:banner.15", "minecraft:white_banner"); -+ FLATTEN_MAP.put("minecraft:banner.14", "minecraft:orange_banner"); -+ FLATTEN_MAP.put("minecraft:banner.13", "minecraft:magenta_banner"); -+ FLATTEN_MAP.put("minecraft:banner.12", "minecraft:light_blue_banner"); -+ FLATTEN_MAP.put("minecraft:banner.11", "minecraft:yellow_banner"); -+ FLATTEN_MAP.put("minecraft:banner.10", "minecraft:lime_banner"); -+ FLATTEN_MAP.put("minecraft:banner.9", "minecraft:pink_banner"); -+ FLATTEN_MAP.put("minecraft:banner.8", "minecraft:gray_banner"); -+ FLATTEN_MAP.put("minecraft:banner.7", "minecraft:light_gray_banner"); -+ FLATTEN_MAP.put("minecraft:banner.6", "minecraft:cyan_banner"); -+ FLATTEN_MAP.put("minecraft:banner.5", "minecraft:purple_banner"); -+ FLATTEN_MAP.put("minecraft:banner.4", "minecraft:blue_banner"); -+ FLATTEN_MAP.put("minecraft:banner.3", "minecraft:brown_banner"); -+ FLATTEN_MAP.put("minecraft:banner.2", "minecraft:green_banner"); -+ FLATTEN_MAP.put("minecraft:banner.1", "minecraft:red_banner"); -+ FLATTEN_MAP.put("minecraft:banner.0", "minecraft:black_banner"); -+ FLATTEN_MAP.put("minecraft:grass.0", "minecraft:grass_block"); -+ FLATTEN_MAP.put("minecraft:brick_block.0", "minecraft:bricks"); -+ FLATTEN_MAP.put("minecraft:end_bricks.0", "minecraft:end_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:golden_rail.0", "minecraft:powered_rail"); -+ FLATTEN_MAP.put("minecraft:magma.0", "minecraft:magma_block"); -+ FLATTEN_MAP.put("minecraft:quartz_ore.0", "minecraft:nether_quartz_ore"); -+ FLATTEN_MAP.put("minecraft:reeds.0", "minecraft:sugar_cane"); -+ FLATTEN_MAP.put("minecraft:slime.0", "minecraft:slime_block"); -+ FLATTEN_MAP.put("minecraft:stone_stairs.0", "minecraft:cobblestone_stairs"); -+ FLATTEN_MAP.put("minecraft:waterlily.0", "minecraft:lily_pad"); -+ FLATTEN_MAP.put("minecraft:web.0", "minecraft:cobweb"); -+ FLATTEN_MAP.put("minecraft:snow.0", "minecraft:snow_block"); -+ FLATTEN_MAP.put("minecraft:snow_layer.0", "minecraft:snow"); -+ FLATTEN_MAP.put("minecraft:record_11.0", "minecraft:music_disc_11"); -+ FLATTEN_MAP.put("minecraft:record_13.0", "minecraft:music_disc_13"); -+ FLATTEN_MAP.put("minecraft:record_blocks.0", "minecraft:music_disc_blocks"); -+ FLATTEN_MAP.put("minecraft:record_cat.0", "minecraft:music_disc_cat"); -+ FLATTEN_MAP.put("minecraft:record_chirp.0", "minecraft:music_disc_chirp"); -+ FLATTEN_MAP.put("minecraft:record_far.0", "minecraft:music_disc_far"); -+ FLATTEN_MAP.put("minecraft:record_mall.0", "minecraft:music_disc_mall"); -+ FLATTEN_MAP.put("minecraft:record_mellohi.0", "minecraft:music_disc_mellohi"); -+ FLATTEN_MAP.put("minecraft:record_stal.0", "minecraft:music_disc_stal"); -+ FLATTEN_MAP.put("minecraft:record_strad.0", "minecraft:music_disc_strad"); -+ FLATTEN_MAP.put("minecraft:record_wait.0", "minecraft:music_disc_wait"); -+ FLATTEN_MAP.put("minecraft:record_ward.0", "minecraft:music_disc_ward"); -+ } -+ -+ // maps out ids requiring flattening -+ private static final Set IDS_REQUIRING_FLATTENING = new HashSet<>(); -+ static { -+ for (final String key : FLATTEN_MAP.keySet()) { -+ IDS_REQUIRING_FLATTENING.add(key.substring(0, key.indexOf('.'))); -+ } -+ } -+ -+ // Damage tag is moved from the ItemStack base tag to the ItemStack tag, and we only want to migrate that -+ // for items that actually require it for damage purposes (Remember, old damage was used to differentiate item types) -+ // It should be noted that this ID set should not be included in the flattening map, because damage for these items -+ // is actual damage and not a subtype specifier -+ private static final Set ITEMS_WITH_DAMAGE = new HashSet<>(Arrays.asList( -+ "minecraft:bow", -+ "minecraft:carrot_on_a_stick", -+ "minecraft:chainmail_boots", -+ "minecraft:chainmail_chestplate", -+ "minecraft:chainmail_helmet", -+ "minecraft:chainmail_leggings", -+ "minecraft:diamond_axe", -+ "minecraft:diamond_boots", -+ "minecraft:diamond_chestplate", -+ "minecraft:diamond_helmet", -+ "minecraft:diamond_hoe", -+ "minecraft:diamond_leggings", -+ "minecraft:diamond_pickaxe", -+ "minecraft:diamond_shovel", -+ "minecraft:diamond_sword", -+ "minecraft:elytra", -+ "minecraft:fishing_rod", -+ "minecraft:flint_and_steel", -+ "minecraft:golden_axe", -+ "minecraft:golden_boots", -+ "minecraft:golden_chestplate", -+ "minecraft:golden_helmet", -+ "minecraft:golden_hoe", -+ "minecraft:golden_leggings", -+ "minecraft:golden_pickaxe", -+ "minecraft:golden_shovel", -+ "minecraft:golden_sword", -+ "minecraft:iron_axe", -+ "minecraft:iron_boots", -+ "minecraft:iron_chestplate", -+ "minecraft:iron_helmet", -+ "minecraft:iron_hoe", -+ "minecraft:iron_leggings", -+ "minecraft:iron_pickaxe", -+ "minecraft:iron_shovel", -+ "minecraft:iron_sword", -+ "minecraft:leather_boots", -+ "minecraft:leather_chestplate", -+ "minecraft:leather_helmet", -+ "minecraft:leather_leggings", -+ "minecraft:shears", -+ "minecraft:shield", -+ "minecraft:stone_axe", -+ "minecraft:stone_hoe", -+ "minecraft:stone_pickaxe", -+ "minecraft:stone_shovel", -+ "minecraft:stone_sword", -+ "minecraft:wooden_axe", -+ "minecraft:wooden_hoe", -+ "minecraft:wooden_pickaxe", -+ "minecraft:wooden_shovel", -+ "minecraft:wooden_sword" -+ )); -+ -+ public ConverterFlattenItemStack() { -+ super(MCVersions.V17W47A, 4); -+ } -+ -+ public static String flattenItem(final String oldName, final int data) { -+ if (IDS_REQUIRING_FLATTENING.contains(oldName)) { -+ final String flattened = FLATTEN_MAP.get(oldName + '.' + data); -+ return flattened == null ? FLATTEN_MAP.get(oldName.concat(".0")) : flattened; -+ } else { -+ return null; -+ } -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String id = data.getString("id"); -+ -+ if (id == null) { -+ return null; -+ } -+ -+ final int damage = data.getInt("Damage"); -+ data.remove("Damage"); -+ -+ if (IDS_REQUIRING_FLATTENING.contains(id)) { -+ String remap = FLATTEN_MAP.get(id + '.' + damage); -+ if (remap == null) { -+ remap = FLATTEN_MAP.get(id.concat(".0")); -+ // this shouldn't be null -+ } -+ if (remap != null) { -+ data.setString("id", remap); -+ } else { -+ LOGGER.warn("Item '" + id + "' requires flattening but found no mapping for it! (ConverterFlattenItemStack)"); -+ } -+ } -+ -+ if (damage != 0 && ITEMS_WITH_DAMAGE.contains(id)) { -+ // migrate damage -+ MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ tag = Types.NBT.createEmptyMap(); -+ data.setMap("tag", tag); -+ } -+ tag.setInt("Damage", damage); -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4fa31e40b0a6f571a853299b4e242de921ccbda0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java -@@ -0,0 +1,87 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.itemstack; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class ConverterFlattenSpawnEgg extends DataConverter, MapType> { -+ -+ private static final Map ENTITY_ID_TO_NEW_EGG_ID = new HashMap<>(); -+ static { -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:bat", "minecraft:bat_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:blaze", "minecraft:blaze_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:cave_spider", "minecraft:cave_spider_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:chicken", "minecraft:chicken_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:cow", "minecraft:cow_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:creeper", "minecraft:creeper_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:donkey", "minecraft:donkey_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:elder_guardian", "minecraft:elder_guardian_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:enderman", "minecraft:enderman_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:endermite", "minecraft:endermite_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:evocation_illager", "minecraft:evocation_illager_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:ghast", "minecraft:ghast_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:guardian", "minecraft:guardian_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:ender_dragon", "minecraft:ender_dragon_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:horse", "minecraft:horse_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:husk", "minecraft:husk_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:iron_golem", "minecraft:iron_golem_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:llama", "minecraft:llama_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:magma_cube", "minecraft:magma_cube_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:mooshroom", "minecraft:mooshroom_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:mule", "minecraft:mule_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:ocelot", "minecraft:ocelot_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:pufferfish", "minecraft:pufferfish_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:parrot", "minecraft:parrot_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:pig", "minecraft:pig_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:polar_bear", "minecraft:polar_bear_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:rabbit", "minecraft:rabbit_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:sheep", "minecraft:sheep_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:shulker", "minecraft:shulker_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:silverfish", "minecraft:silverfish_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:skeleton", "minecraft:skeleton_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:skeleton_horse", "minecraft:skeleton_horse_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:slime", "minecraft:slime_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:snow_golem", "minecraft:snow_golem_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:spider", "minecraft:spider_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:squid", "minecraft:squid_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:stray", "minecraft:stray_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:turtle", "minecraft:turtle_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:vex", "minecraft:vex_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:villager", "minecraft:villager_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:vindication_illager", "minecraft:vindication_illager_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:witch", "minecraft:witch_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:wither", "minecraft:wither_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:wither_skeleton", "minecraft:wither_skeleton_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:wolf", "minecraft:wolf_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie", "minecraft:zombie_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie_horse", "minecraft:zombie_horse_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie_pigman", "minecraft:zombie_pigman_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie_villager", "minecraft:zombie_villager_spawn_egg"); -+ } -+ -+ public ConverterFlattenSpawnEgg(final int version, final int versionStep) { -+ super(version, versionStep); -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final MapType entityTag = tag.getMap("EntityTag"); -+ if (entityTag == null) { -+ return null; -+ } -+ -+ final String id = entityTag.getString("id"); -+ if (id != null) { -+ data.setString("id", ENTITY_ID_TO_NEW_EGG_ID.getOrDefault(id, "minecraft:pig_spawn_egg")); -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4c537b661b7a28193add3267ec2d639add49423b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java -@@ -0,0 +1,46 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.leveldat; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import java.util.Set; -+ -+public final class ConverterRemoveFeatureFlag extends DataConverter, MapType> { -+ -+ private final Set flags; -+ -+ public ConverterRemoveFeatureFlag(final int toVersion, final Set flags) { -+ this(toVersion, 0, flags); -+ } -+ -+ public ConverterRemoveFeatureFlag(final int toVersion, final int versionStep, final Set flags) { -+ super(toVersion, versionStep); -+ this.flags = flags; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType enabledFeatures = data.getList("enabled_features", ObjectType.STRING); -+ if (enabledFeatures == null) { -+ return null; -+ } -+ -+ ListType removedFeatures = null; -+ -+ for (int i = 0; i < enabledFeatures.size(); ++i) { -+ final String flag = enabledFeatures.getString(i); -+ if (!this.flags.contains(flag)) { -+ continue; -+ } -+ enabledFeatures.remove(i--); -+ -+ if (removedFeatures == null) { -+ removedFeatures = data.getOrCreateList("removed_features", ObjectType.STRING); -+ } -+ removedFeatures.addString(flag); -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..769dd8447976b66dcfc36283ede4ae16f1e4206d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java -@@ -0,0 +1,28 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.options; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.ArrayList; -+import java.util.function.Function; -+ -+public final class ConverterAbstractOptionsRename { -+ -+ private ConverterAbstractOptionsRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ RenameHelper.renameKeys(data, renamer); -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..57e210bf2bb189b15a32899011c4800b19668a5e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java -@@ -0,0 +1,53 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.poi; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import java.util.function.Function; -+ -+public final class ConverterAbstractPOIRename { -+ -+ private ConverterAbstractPOIRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType sections = data.getMap("Sections"); -+ if (sections == null) { -+ return null; -+ } -+ -+ for (final String key : sections.keys()) { -+ final MapType section = sections.getMap(key); -+ -+ final ListType records = section.getList("Records", ObjectType.MAP); -+ -+ if (records == null) { -+ continue; -+ } -+ -+ for (int i = 0, len = records.size(); i < len; ++i) { -+ final MapType record = records.getMap(i); -+ -+ final String type = record.getString("type"); -+ if (type != null) { -+ final String converted = renamer.apply(type); -+ if (converted != null) { -+ record.setString("type", converted); -+ } -+ } -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java -new file mode 100644 -index 0000000000000000000000000000000000000000..36aa9c3eedb3f2e2f577efed3622fed74268bce1 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java -@@ -0,0 +1,53 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.poi; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import java.util.function.Predicate; -+ -+public final class ConverterPoiDelete extends DataConverter, MapType> { -+ -+ private final Predicate delete; -+ -+ public ConverterPoiDelete(final int toVersion, final Predicate delete) { -+ super(toVersion); -+ this.delete = delete; -+ } -+ -+ public ConverterPoiDelete(final int toVersion, final int versionStep, final Predicate delete) { -+ super(toVersion, versionStep); -+ this.delete = delete; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType sections = data.getMap("Sections"); -+ if (sections == null) { -+ return null; -+ } -+ -+ for (final String key : sections.keys()) { -+ final MapType section = sections.getMap(key); -+ -+ final ListType records = section.getList("Records", ObjectType.MAP); -+ -+ if (records == null) { -+ continue; -+ } -+ -+ for (int i = 0; i < records.size();) { -+ final MapType record = records.getMap(i); -+ -+ final String type = record.getString("type"); -+ if (type != null && this.delete.test(type)) { -+ records.remove(i); -+ continue; -+ } -+ ++i; -+ } -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8f35cbbd78a629712f9ae3cd5d180269f015a11d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.recipe; -+ -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import java.util.function.Function; -+ -+public final class ConverterAbstractRecipeRename { -+ -+ private ConverterAbstractRecipeRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.RECIPE, renamer); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a1985c85aa9193699d7d20e6f4f11b6e9744ee70 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java -@@ -0,0 +1,66 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.stats; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.ArrayList; -+import java.util.function.Function; -+ -+public final class ConverterAbstractStatsRename { -+ -+ private ConverterAbstractStatsRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ MCTypeRegistry.OBJECTIVE.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType criteriaType = data.getMap("CriteriaType"); -+ if (criteriaType == null) { -+ return null; -+ } -+ -+ final String type = criteriaType.getString("type"); -+ if (!"minecraft:custom".equals(type)) { -+ return null; -+ } -+ -+ final String id = criteriaType.getString("id"); -+ if (id == null) { -+ return null; -+ } -+ -+ final String rename = renamer.apply(id); -+ if (rename != null) { -+ criteriaType.setString("id", rename); -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.STATS.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType stats = data.getMap("stats"); -+ -+ if (stats == null) { -+ return null; -+ } -+ -+ final MapType custom = stats.getMap("minecraft:custom"); -+ if (custom == null) { -+ return null; -+ } -+ -+ RenameHelper.renameKeys(custom, renamer); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java -new file mode 100644 -index 0000000000000000000000000000000000000000..891be75bf5c4af56e839c88b26f0a828554ae5c4 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java -@@ -0,0 +1,321 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.stats; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenItemStack; -+import ca.spottedleaf.dataconverter.minecraft.versions.V1451; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.common.collect.ImmutableMap; -+import com.google.common.collect.ImmutableSet; -+import org.apache.commons.lang3.StringUtils; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+ -+public final class ConverterFlattenStats { -+ -+ private static final int VERSION = MCVersions.V17W47A; -+ private static final int VERSION_STEP = 6; -+ -+ private static final Set SPECIAL_OBJECTIVE_CRITERIA = new HashSet<>( -+ Set.of( -+ "dummy", -+ "trigger", -+ "deathCount", -+ "playerKillCount", -+ "totalKillCount", -+ "health", -+ "food", -+ "air", -+ "armor", -+ "xp", -+ "level", -+ "killedByTeam.aqua", -+ "killedByTeam.black", -+ "killedByTeam.blue", -+ "killedByTeam.dark_aqua", -+ "killedByTeam.dark_blue", -+ "killedByTeam.dark_gray", -+ "killedByTeam.dark_green", -+ "killedByTeam.dark_purple", -+ "killedByTeam.dark_red", -+ "killedByTeam.gold", -+ "killedByTeam.gray", -+ "killedByTeam.green", -+ "killedByTeam.light_purple", -+ "killedByTeam.red", -+ "killedByTeam.white", -+ "killedByTeam.yellow", -+ "teamkill.aqua", -+ "teamkill.black", -+ "teamkill.blue", -+ "teamkill.dark_aqua", -+ "teamkill.dark_blue", -+ "teamkill.dark_gray", -+ "teamkill.dark_green", -+ "teamkill.dark_purple", -+ "teamkill.dark_red", -+ "teamkill.gold", -+ "teamkill.gray", -+ "teamkill.green", -+ "teamkill.light_purple", -+ "teamkill.red", -+ "teamkill.white", -+ "teamkill.yellow" -+ ) -+ ); -+ -+ private static final Set SKIP = new HashSet<>( -+ ImmutableSet.builder() -+ .add("stat.craftItem.minecraft.spawn_egg") -+ .add("stat.useItem.minecraft.spawn_egg") -+ .add("stat.breakItem.minecraft.spawn_egg") -+ .add("stat.pickup.minecraft.spawn_egg") -+ .add("stat.drop.minecraft.spawn_egg") -+ .build() -+ ); -+ -+ private static final Map CUSTOM_MAP = new HashMap<>( -+ ImmutableMap.builder() -+ .put("stat.leaveGame", "minecraft:leave_game") -+ .put("stat.playOneMinute", "minecraft:play_one_minute") -+ .put("stat.timeSinceDeath", "minecraft:time_since_death") -+ .put("stat.sneakTime", "minecraft:sneak_time") -+ .put("stat.walkOneCm", "minecraft:walk_one_cm") -+ .put("stat.crouchOneCm", "minecraft:crouch_one_cm") -+ .put("stat.sprintOneCm", "minecraft:sprint_one_cm") -+ .put("stat.swimOneCm", "minecraft:swim_one_cm") -+ .put("stat.fallOneCm", "minecraft:fall_one_cm") -+ .put("stat.climbOneCm", "minecraft:climb_one_cm") -+ .put("stat.flyOneCm", "minecraft:fly_one_cm") -+ .put("stat.diveOneCm", "minecraft:dive_one_cm") -+ .put("stat.minecartOneCm", "minecraft:minecart_one_cm") -+ .put("stat.boatOneCm", "minecraft:boat_one_cm") -+ .put("stat.pigOneCm", "minecraft:pig_one_cm") -+ .put("stat.horseOneCm", "minecraft:horse_one_cm") -+ .put("stat.aviateOneCm", "minecraft:aviate_one_cm") -+ .put("stat.jump", "minecraft:jump") -+ .put("stat.drop", "minecraft:drop") -+ .put("stat.damageDealt", "minecraft:damage_dealt") -+ .put("stat.damageTaken", "minecraft:damage_taken") -+ .put("stat.deaths", "minecraft:deaths") -+ .put("stat.mobKills", "minecraft:mob_kills") -+ .put("stat.animalsBred", "minecraft:animals_bred") -+ .put("stat.playerKills", "minecraft:player_kills") -+ .put("stat.fishCaught", "minecraft:fish_caught") -+ .put("stat.talkedToVillager", "minecraft:talked_to_villager") -+ .put("stat.tradedWithVillager", "minecraft:traded_with_villager") -+ .put("stat.cakeSlicesEaten", "minecraft:eat_cake_slice") -+ .put("stat.cauldronFilled", "minecraft:fill_cauldron") -+ .put("stat.cauldronUsed", "minecraft:use_cauldron") -+ .put("stat.armorCleaned", "minecraft:clean_armor") -+ .put("stat.bannerCleaned", "minecraft:clean_banner") -+ .put("stat.brewingstandInteraction", "minecraft:interact_with_brewingstand") -+ .put("stat.beaconInteraction", "minecraft:interact_with_beacon") -+ .put("stat.dropperInspected", "minecraft:inspect_dropper") -+ .put("stat.hopperInspected", "minecraft:inspect_hopper") -+ .put("stat.dispenserInspected", "minecraft:inspect_dispenser") -+ .put("stat.noteblockPlayed", "minecraft:play_noteblock") -+ .put("stat.noteblockTuned", "minecraft:tune_noteblock") -+ .put("stat.flowerPotted", "minecraft:pot_flower") -+ .put("stat.trappedChestTriggered", "minecraft:trigger_trapped_chest") -+ .put("stat.enderchestOpened", "minecraft:open_enderchest") -+ .put("stat.itemEnchanted", "minecraft:enchant_item") -+ .put("stat.recordPlayed", "minecraft:play_record") -+ .put("stat.furnaceInteraction", "minecraft:interact_with_furnace") -+ .put("stat.craftingTableInteraction", "minecraft:interact_with_crafting_table") -+ .put("stat.chestOpened", "minecraft:open_chest") -+ .put("stat.sleepInBed", "minecraft:sleep_in_bed") -+ .put("stat.shulkerBoxOpened", "minecraft:open_shulker_box") -+ .build() -+ ); -+ -+ private static final String BLOCK_KEY = "stat.mineBlock"; -+ private static final String NEW_BLOCK_KEY = "minecraft:mined"; -+ -+ private static final Map ITEM_KEYS = new HashMap<>( -+ ImmutableMap.builder() -+ .put("stat.craftItem", "minecraft:crafted") -+ .put("stat.useItem", "minecraft:used") -+ .put("stat.breakItem", "minecraft:broken") -+ .put("stat.pickup", "minecraft:picked_up") -+ .put("stat.drop", "minecraft:dropped") -+ .build() -+ ); -+ -+ private static final Map ENTITY_KEYS = new HashMap<>( -+ ImmutableMap.builder() -+ .put("stat.entityKilledBy", "minecraft:killed_by") -+ .put("stat.killEntity", "minecraft:killed") -+ .build() -+ ); -+ -+ private static final Map ENTITIES = new HashMap<>( -+ ImmutableMap.builder() -+ .put("Bat", "minecraft:bat") -+ .put("Blaze", "minecraft:blaze") -+ .put("CaveSpider", "minecraft:cave_spider") -+ .put("Chicken", "minecraft:chicken") -+ .put("Cow", "minecraft:cow") -+ .put("Creeper", "minecraft:creeper") -+ .put("Donkey", "minecraft:donkey") -+ .put("ElderGuardian", "minecraft:elder_guardian") -+ .put("Enderman", "minecraft:enderman") -+ .put("Endermite", "minecraft:endermite") -+ .put("EvocationIllager", "minecraft:evocation_illager") -+ .put("Ghast", "minecraft:ghast") -+ .put("Guardian", "minecraft:guardian") -+ .put("Horse", "minecraft:horse") -+ .put("Husk", "minecraft:husk") -+ .put("Llama", "minecraft:llama") -+ .put("LavaSlime", "minecraft:magma_cube") -+ .put("MushroomCow", "minecraft:mooshroom") -+ .put("Mule", "minecraft:mule") -+ .put("Ozelot", "minecraft:ocelot") -+ .put("Parrot", "minecraft:parrot") -+ .put("Pig", "minecraft:pig") -+ .put("PolarBear", "minecraft:polar_bear") -+ .put("Rabbit", "minecraft:rabbit") -+ .put("Sheep", "minecraft:sheep") -+ .put("Shulker", "minecraft:shulker") -+ .put("Silverfish", "minecraft:silverfish") -+ .put("SkeletonHorse", "minecraft:skeleton_horse") -+ .put("Skeleton", "minecraft:skeleton") -+ .put("Slime", "minecraft:slime") -+ .put("Spider", "minecraft:spider") -+ .put("Squid", "minecraft:squid") -+ .put("Stray", "minecraft:stray") -+ .put("Vex", "minecraft:vex") -+ .put("Villager", "minecraft:villager") -+ .put("VindicationIllager", "minecraft:vindication_illager") -+ .put("Witch", "minecraft:witch") -+ .put("WitherSkeleton", "minecraft:wither_skeleton") -+ .put("Wolf", "minecraft:wolf") -+ .put("ZombieHorse", "minecraft:zombie_horse") -+ .put("PigZombie", "minecraft:zombie_pigman") -+ .put("ZombieVillager", "minecraft:zombie_villager") -+ .put("Zombie", "minecraft:zombie") -+ .build() -+ ); -+ -+ private static final String NEW_CUSTOM_KEY = "minecraft:custom"; -+ -+ private ConverterFlattenStats() {} -+ -+ private static String upgradeItem(final String itemName) { -+ return ConverterFlattenItemStack.flattenItem(itemName, 0); -+ } -+ -+ private static String upgradeBlock(final String block) { -+ return HelperBlockFlatteningV1450.getNewBlockName(block); -+ } -+ -+ private static record StatType(String category, String key) {} -+ -+ private static StatType convertLegacyKey(final String key) { -+ if (SKIP.contains(key)) { -+ return null; -+ } -+ -+ final String custom = CUSTOM_MAP.get(key); -+ if (custom != null) { -+ return new StatType(NEW_CUSTOM_KEY, custom); -+ } -+ -+ final int i = StringUtils.ordinalIndexOf(key, ".", 2); -+ if (i < 0) { -+ return null; -+ } -+ -+ final String stat = key.substring(0, i); -+ -+ if (BLOCK_KEY.equals(stat)) { -+ return new StatType(NEW_BLOCK_KEY, upgradeBlock(key.substring(i + 1).replace('.', ':'))); -+ } -+ -+ final String itemStat = ITEM_KEYS.get(stat); -+ -+ if (itemStat != null) { -+ final String itemId = key.substring(i + 1).replace('.', ':'); -+ final String flattenedItem = upgradeItem(itemId); -+ -+ return new StatType(itemStat, flattenedItem == null ? itemId : flattenedItem); -+ } -+ -+ final String entityStat = ENTITY_KEYS.get(stat); -+ if (entityStat != null) { -+ final String entityId = key.substring(i + 1).replace('.', ':'); -+ -+ return new StatType(entityStat, ENTITIES.getOrDefault(entityId, entityId)); -+ } -+ -+ return null; -+ } -+ -+ public static DataConverter, MapType> makeStatsConverter() { -+ return new DataConverter<>(VERSION, VERSION_STEP) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType stats = Types.NBT.createEmptyMap(); -+ -+ for (final String statKey : data.keys()) { -+ final Number value = data.getNumber(statKey); -+ if (value == null) { -+ continue; -+ } -+ -+ final StatType converted = convertLegacyKey(statKey); -+ -+ if (converted == null) { -+ continue; -+ } -+ -+ stats.getOrCreateMap(converted.category()).setGeneric(converted.key(), value); -+ } -+ -+ data.clear(); -+ data.setMap("stats", stats); -+ -+ return null; -+ } -+ }; -+ } -+ -+ public static DataConverter, MapType> makeObjectiveConverter() { -+ return new DataConverter<>(VERSION, VERSION_STEP) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ convertCriteriaName(data, "CriteriaName"); -+ -+ // We also need to update CriteriaType that is created by the data hook in V1451, -+ // otherwise that data hook will overwrite our CriteriaName -+ final MapType criteriaType = data.getMap("CriteriaType"); -+ if (criteriaType != null) { -+ if ("_special".equals(criteriaType.getString("type"))) { -+ convertCriteriaName(criteriaType, "id"); -+ } -+ } -+ -+ return null; -+ } -+ -+ private void convertCriteriaName(final MapType data, final String key) { -+ final String criteriaName = data.getString(key); -+ -+ if (criteriaName == null) { -+ return; -+ } -+ -+ if (SPECIAL_OBJECTIVE_CRITERIA.contains(criteriaName)) { -+ return; -+ } -+ -+ final StatType converted = convertLegacyKey(criteriaName); -+ data.setString(key, converted == null ? "dummy" : V1451.packWithDot(converted.category()) + ":" + V1451.packWithDot(converted.key())); -+ } -+ }; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ab05dda0cc2083418443d0dee23ccc0a6f754ea0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/tileentity/ConverterAbstractTileEntityRename.java -@@ -0,0 +1,34 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.tileentity; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.function.Function; -+ -+public final class ConverterAbstractTileEntityRename { -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ MCTypeRegistry.TILE_ENTITY.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String id = data.getString("id"); -+ if (id == null) { -+ return null; -+ } -+ -+ final String converted = renamer.apply(id); -+ -+ if (converted != null) { -+ data.setString("id", converted); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b2c2b4c4ae83f14639fa53e38f2c75ccd284c2d2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java -@@ -0,0 +1,166 @@ -+package ca.spottedleaf.dataconverter.minecraft.datatypes; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; -+import ca.spottedleaf.dataconverter.minecraft.MCVersionRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap; -+ -+import java.util.ArrayList; -+import java.util.HashMap; -+import java.util.List; -+import java.util.Map; -+ -+public class IDDataType extends MCDataType { -+ -+ protected final Map>>> walkersById = new HashMap<>(); -+ -+ public IDDataType(final String name) { -+ super(name); -+ } -+ -+ public void addConverterForId(final String id, final DataConverter, MapType> converter) { -+ this.addStructureConverter(new DataConverter<>(converter.getToVersion(), converter.getVersionStep()) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!id.equals(data.getString("id"))) { -+ return null; -+ } -+ return converter.convert(data, sourceVersion, toVersion); -+ } -+ }); -+ } -+ -+ public void addWalker(final int minVersion, final String id, final DataWalker walker) { -+ this.addWalker(minVersion, 0, id, walker); -+ } -+ -+ public void addWalker(final int minVersion, final int versionStep, final String id, final DataWalker walker) { -+ this.walkersById.computeIfAbsent(id, (final String keyInMap) -> { -+ return new Long2ObjectArraySortedMap<>(); -+ }).computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { -+ return new ArrayList<>(); -+ }).add(walker); -+ } -+ -+ public void copyWalkers(final int minVersion, final String fromId, final String toId) { -+ this.copyWalkers(minVersion, 0, fromId, toId); -+ } -+ -+ public void copyWalkers(final int minVersion, final int versionStep, final String fromId, final String toId) { -+ final long version = DataConverter.encodeVersions(minVersion, versionStep); -+ final Long2ObjectArraySortedMap>> walkersForId = this.walkersById.get(fromId); -+ if (walkersForId == null) { -+ return; -+ } -+ -+ final List> nearest = walkersForId.getFloor(version); -+ -+ if (nearest == null) { -+ return; -+ } -+ -+ for (final DataWalker walker : nearest) { -+ this.addWalker(minVersion, versionStep, toId, walker); -+ } -+ } -+ -+ @Override -+ public MapType convert(MapType data, final long fromVersion, final long toVersion) { -+ MapType ret = null; -+ -+ final List, MapType>> converters = this.structureConverters; -+ for (int i = 0, len = converters.size(); i < len; ++i) { -+ final DataConverter, MapType> converter = converters.get(i); -+ final long converterVersion = converter.getEncodedVersion(); -+ -+ if (converterVersion <= fromVersion) { -+ continue; -+ } -+ -+ if (converterVersion > toVersion) { -+ break; -+ } -+ -+ List, MapType>> hooks = this.structureHooks.getFloor(converterVersion); -+ -+ if (hooks != null) { -+ for (int k = 0, klen = hooks.size(); k < klen; ++k) { -+ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ -+ final MapType replace = converter.convert(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ -+ // possibly new data format, update hooks -+ hooks = this.structureHooks.getFloor(toVersion); -+ -+ if (hooks != null) { -+ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { -+ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); -+ if (postReplace != null) { -+ ret = data = postReplace; -+ } -+ } -+ } -+ } -+ -+ final List, MapType>> hooks = this.structureHooks.getFloor(toVersion); -+ -+ // run pre hooks -+ -+ if (hooks != null) { -+ for (int k = 0, klen = hooks.size(); k < klen; ++k) { -+ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ -+ // run all walkers -+ -+ final List> walkers = this.structureWalkers.getFloor(toVersion); -+ if (walkers != null) { -+ for (int i = 0, len = walkers.size(); i < len; ++i) { -+ final MapType replace = walkers.get(i).walk(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ -+ final Long2ObjectArraySortedMap>> walkersByVersion = this.walkersById.get(data.getString("id")); -+ if (walkersByVersion != null) { -+ final List> walkersForId = walkersByVersion.getFloor(toVersion); -+ if (walkersForId != null) { -+ for (int i = 0, len = walkersForId.size(); i < len; ++i) { -+ final MapType replace = walkersForId.get(i).walk(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ } -+ -+ // run post hooks -+ -+ if (hooks != null) { -+ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { -+ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); -+ if (postReplace != null) { -+ ret = data = postReplace; -+ } -+ } -+ } -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..76a6e3efa5c69150e8f5e0063cb6357bed1bffae ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java -@@ -0,0 +1,129 @@ -+package ca.spottedleaf.dataconverter.minecraft.datatypes; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataType; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; -+import ca.spottedleaf.dataconverter.minecraft.MCVersionRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap; -+import java.util.ArrayList; -+import java.util.List; -+ -+public class MCDataType extends DataType, MapType> { -+ -+ public final String name; -+ -+ protected final ArrayList, MapType>> structureConverters = new ArrayList<>(); -+ protected final Long2ObjectArraySortedMap>> structureWalkers = new Long2ObjectArraySortedMap<>(); -+ protected final Long2ObjectArraySortedMap, MapType>>> structureHooks = new Long2ObjectArraySortedMap<>(); -+ -+ public MCDataType(final String name) { -+ this.name = name; -+ } -+ -+ public void addStructureConverter(final DataConverter, MapType> converter) { -+ MCVersionRegistry.checkVersion(converter.getEncodedVersion()); -+ this.structureConverters.add(converter); -+ this.structureConverters.sort(DataConverter.LOWEST_VERSION_COMPARATOR); -+ } -+ -+ public void addStructureWalker(final int minVersion, final DataWalker walker) { -+ this.addStructureWalker(minVersion, 0, walker); -+ } -+ -+ public void addStructureWalker(final int minVersion, final int versionStep, final DataWalker walker) { -+ this.structureWalkers.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { -+ return new ArrayList<>(); -+ }).add(walker); -+ } -+ -+ public void addStructureHook(final int minVersion, final DataHook, MapType> hook) { -+ this.addStructureHook(minVersion, 0, hook); -+ } -+ -+ public void addStructureHook(final int minVersion, final int versionStep, final DataHook, MapType> hook) { -+ this.structureHooks.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { -+ return new ArrayList<>(); -+ }).add(hook); -+ } -+ -+ @Override -+ public MapType convert(MapType data, final long fromVersion, final long toVersion) { -+ MapType ret = null; -+ -+ final List, MapType>> converters = this.structureConverters; -+ for (int i = 0, len = converters.size(); i < len; ++i) { -+ final DataConverter, MapType> converter = converters.get(i); -+ final long converterVersion = converter.getEncodedVersion(); -+ -+ if (converterVersion <= fromVersion) { -+ continue; -+ } -+ -+ if (converterVersion > toVersion) { -+ break; -+ } -+ -+ List, MapType>> hooks = this.structureHooks.getFloor(converterVersion); -+ -+ if (hooks != null) { -+ for (int k = 0, klen = hooks.size(); k < klen; ++k) { -+ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ -+ final MapType replace = converter.convert(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ -+ // possibly new data format, update hooks -+ hooks = this.structureHooks.getFloor(toVersion); -+ -+ if (hooks != null) { -+ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { -+ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); -+ if (postReplace != null) { -+ ret = data = postReplace; -+ } -+ } -+ } -+ } -+ -+ final List, MapType>> hooks = this.structureHooks.getFloor(toVersion); -+ -+ if (hooks != null) { -+ for (int k = 0, klen = hooks.size(); k < klen; ++k) { -+ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ -+ final List> walkers = this.structureWalkers.getFloor(toVersion); -+ if (walkers != null) { -+ for (int i = 0, len = walkers.size(); i < len; ++i) { -+ final MapType replace = walkers.get(i).walk(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ -+ if (hooks != null) { -+ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { -+ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); -+ if (postReplace != null) { -+ ret = data = postReplace; -+ } -+ } -+ } -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bc7303964625d740b902dda72d9543c5f4120475 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java -@@ -0,0 +1,275 @@ -+package ca.spottedleaf.dataconverter.minecraft.datatypes; -+ -+import ca.spottedleaf.dataconverter.minecraft.versions.*; -+import com.mojang.logging.LogUtils; -+import org.slf4j.Logger; -+ -+public final class MCTypeRegistry { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ public static final MCDataType LEVEL = new MCDataType("Level"); -+ public static final MCDataType PLAYER = new MCDataType("Player"); -+ public static final MCDataType CHUNK = new MCDataType("Chunk"); -+ public static final MCDataType HOTBAR = new MCDataType("CreativeHotbar"); -+ public static final MCDataType OPTIONS = new MCDataType("Options"); -+ public static final MCDataType STRUCTURE = new MCDataType("Structure"); -+ public static final MCDataType STATS = new MCDataType("Stats"); -+ public static final MCDataType ADVANCEMENTS = new MCDataType("Advancements"); -+ public static final MCDataType POI_CHUNK = new MCDataType("PoiChunk"); -+ public static final MCDataType ENTITY_CHUNK = new MCDataType("EntityChunk"); -+ public static final IDDataType TILE_ENTITY = new IDDataType("TileEntity"); -+ public static final IDDataType ITEM_STACK = new IDDataType("ItemStack"); -+ public static final MCDataType BLOCK_STATE = new MCDataType("BlockState"); -+ public static final MCValueType ENTITY_NAME = new MCValueType("EntityName"); -+ public static final IDDataType ENTITY = new IDDataType("Entity"); -+ public static final MCValueType BLOCK_NAME = new MCValueType("BlockName"); -+ public static final MCValueType ITEM_NAME = new MCValueType("ItemName"); -+ public static final MCDataType UNTAGGED_SPAWNER = new MCDataType("Spawner"); -+ public static final MCDataType STRUCTURE_FEATURE = new MCDataType("StructureFeature"); -+ public static final MCDataType OBJECTIVE = new MCDataType("Objective"); -+ public static final MCDataType TEAM = new MCDataType("Team"); -+ public static final MCValueType RECIPE = new MCValueType("RecipeName"); -+ public static final MCValueType BIOME = new MCValueType("Biome"); -+ public static final MCDataType WORLD_GEN_SETTINGS = new MCDataType("WorldGenSettings"); -+ public static final MCValueType GAME_EVENT_NAME = new MCValueType("GameEventName"); -+ -+ public static final MCValueType MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST = new MCValueType("MultiNoiseBiomeSourceParameterList"); -+ -+ public static final MCDataType SAVED_DATA_RANDOM_SEQUENCES = new MCDataType("SavedData/RandomSequences"); -+ public static final MCDataType SAVED_DATA_SCOREBOARD = new MCDataType("SavedData/Scoreboard"); -+ public static final MCDataType SAVED_DATA_STRUCTURE_FEATURE_INDICES = new MCDataType("SavedData/StructureFeatureIndices"); -+ public static final MCDataType SAVED_DATA_MAP_DATA = new MCDataType("SavedData/MapData"); -+ public static final MCDataType SAVED_DATA_RAIDS = new MCDataType("SavedData/Raids"); -+ -+ static { -+ try { -+ registerAll(); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error(LogUtils.FATAL_MARKER, "Failed to register data converters", thr); -+ throw new RuntimeException(thr); -+ } -+ } -+ -+ private static void registerAll() { -+ // General notes: -+ // - Structure converters run before everything. -+ // - ID specific converters run after structure converters. -+ // - Structure walkers run after id specific converters. -+ // - ID specific walkers run after structure walkers. -+ -+ V99.register(); // all legacy data before converters existed -+ V100.register(); // first version with version id -+ V101.register(); -+ V102.register(); -+ V105.register(); -+ V106.register(); -+ V107.register(); -+ V108.register(); -+ V109.register(); -+ V110.register(); -+ V111.register(); -+ V113.register(); -+ V135.register(); -+ V143.register(); -+ V147.register(); -+ V165.register(); -+ V501.register(); -+ V502.register(); -+ V505.register(); -+ V700.register(); -+ V701.register(); -+ V702.register(); -+ V703.register(); -+ V704.register(); -+ V705.register(); -+ V804.register(); -+ V806.register(); -+ V808.register(); -+ V813.register(); -+ V816.register(); -+ V820.register(); -+ V1022.register(); -+ V1125.register(); -+ // END OF LEGACY DATA CONVERTERS -+ -+ // V1.13 -+ V1344.register(); -+ V1446.register(); -+ // START THE FLATTENING -+ V1450.register(); -+ V1451.register(); -+ // END THE FLATTENING -+ -+ V1456.register(); -+ V1458.register(); -+ V1460.register(); -+ V1466.register(); -+ V1470.register(); -+ V1474.register(); -+ V1475.register(); -+ V1480.register(); -+ // V1481 is adding simple block entity -+ V1483.register(); -+ V1484.register(); -+ V1486.register(); -+ V1487.register(); -+ V1488.register(); -+ V1490.register(); -+ V1492.register(); -+ V1494.register(); -+ V1496.register(); -+ V1500.register(); -+ V1501.register(); -+ V1502.register(); -+ V1506.register(); -+ V1510.register(); -+ V1514.register(); -+ V1515.register(); -+ V1624.register(); -+ // V1.14 -+ V1800.register(); -+ V1801.register(); -+ V1802.register(); -+ V1803.register(); -+ V1904.register(); -+ V1905.register(); -+ V1906.register(); -+ // V1909 is just adding a simple block entity (jigsaw) -+ V1911.register(); -+ V1914.register(); -+ V1917.register(); -+ V1918.register(); -+ V1920.register(); -+ V1925.register(); -+ V1928.register(); -+ V1929.register(); -+ V1931.register(); -+ V1936.register(); -+ V1946.register(); -+ V1948.register(); -+ V1953.register(); -+ V1955.register(); -+ V1961.register(); -+ V1963.register(); -+ // V1.15 -+ V2100.register(); -+ V2202.register(); -+ V2209.register(); -+ V2211.register(); -+ V2218.register(); -+ // V1.16 -+ V2501.register(); -+ V2502.register(); -+ V2503.register(); -+ V2505.register(); -+ V2508.register(); -+ V2509.register(); -+ V2511.register(); -+ V2514.register(); -+ V2516.register(); -+ V2518.register(); -+ V2519.register(); -+ V2522.register(); -+ V2523.register(); -+ V2527.register(); -+ V2528.register(); -+ V2529.register(); -+ V2531.register(); -+ V2533.register(); -+ V2535.register(); -+ V2538.register(); -+ V2550.register(); -+ V2551.register(); -+ V2552.register(); -+ V2553.register(); -+ V2558.register(); -+ V2568.register(); -+ // V1.17 -+ // WARN: Mojang registers V2671 under 2571, but that version predates 1.16.5? So it looks like a typo... -+ // I changed it to 2671, just so that it's after 1.16.5, but even then this looks misplaced... Thankfully this is -+ // the first datafixer, and all it does is add a walker, so I think even if the version here is just wrong it will -+ // work. -+ V2671.register(); -+ V2679.register(); -+ V2680.register(); -+ V2684.register(); -+ V2686.register(); -+ V2688.register(); -+ V2690.register(); -+ V2691.register(); -+ V2693.register(); -+ V2696.register(); -+ V2700.register(); -+ V2701.register(); -+ V2702.register(); -+ // In reference to V2671, why the fuck is goat being registered again? For this obvious reason, V2704 is absent. -+ V2707.register(); -+ V2710.register(); -+ V2717.register(); -+ // V1.18 -+ V2825.register(); -+ V2831.register(); -+ V2832.register(); -+ V2833.register(); -+ V2838.register(); -+ V2841.register(); -+ V2842.register(); -+ V2843.register(); -+ V2846.register(); -+ V2852.register(); -+ V2967.register(); -+ V2970.register(); -+ // V1.19 -+ // V3076 is registering a simple tile entity (sculk_catalyst) -+ V3077.register(); -+ V3078.register(); -+ V3081.register(); -+ V3082.register(); -+ V3083.register(); -+ V3084.register(); -+ V3086.register(); -+ V3087.register(); -+ V3088.register(); -+ V3090.register(); -+ V3093.register(); -+ V3094.register(); -+ V3097.register(); -+ V3108.register(); -+ V3201.register(); -+ // V3202 registers a simple tile entity -+ V3203.register(); -+ V3204.register(); -+ V3209.register(); -+ V3214.register(); -+ V3319.register(); -+ V3322.register(); -+ V3325.register(); -+ V3326.register(); -+ V3327.register(); -+ V3328.register(); -+ // V1.20 -+ V3438.register(); -+ V3439.register(); -+ V3440.register(); -+ V3441.register(); -+ V3447.register(); -+ V3448.register(); -+ V3450.register(); -+ V3451.register(); -+ V3459.register(); -+ V3564.register(); -+ V3565.register(); -+ V3566.register(); -+ V3568.register(); -+ V3682.register(); -+ V3683.register(); -+ V3685.register(); -+ V3689.register(); -+ V3692.register(); -+ } -+ -+ private MCTypeRegistry() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..13c1381261909ef672fbeb665907f01f2d5c1ced ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java -@@ -0,0 +1,86 @@ -+package ca.spottedleaf.dataconverter.minecraft.datatypes; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataType; -+import ca.spottedleaf.dataconverter.minecraft.MCVersionRegistry; -+import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap; -+import java.util.ArrayList; -+import java.util.List; -+ -+public class MCValueType extends DataType { -+ -+ public final String name; -+ -+ protected final ArrayList> converters = new ArrayList<>(); -+ protected final Long2ObjectArraySortedMap>> structureHooks = new Long2ObjectArraySortedMap<>(); -+ -+ public MCValueType(final String name) { -+ this.name = name; -+ } -+ -+ public void addStructureHook(final int minVersion, final DataHook hook) { -+ this.addStructureHook(minVersion, 0, hook); -+ } -+ -+ public void addStructureHook(final int minVersion, final int versionStep, final DataHook hook) { -+ this.structureHooks.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { -+ return new ArrayList<>(); -+ }).add(hook); -+ } -+ -+ public void addConverter(final DataConverter converter) { -+ MCVersionRegistry.checkVersion(converter.getEncodedVersion()); -+ this.converters.add(converter); -+ this.converters.sort(DataConverter.LOWEST_VERSION_COMPARATOR); -+ } -+ -+ @Override -+ public Object convert(final Object data, final long fromVersion, final long toVersion) { -+ Object ret = null; -+ final List> converters = this.converters; -+ -+ for (int i = 0, len = converters.size(); i < len; ++i) { -+ final DataConverter converter = converters.get(i); -+ final long converterVersion = converter.getEncodedVersion(); -+ -+ if (converterVersion <= fromVersion) { -+ continue; -+ } -+ -+ if (converterVersion > toVersion) { -+ break; -+ } -+ -+ List> hooks = this.structureHooks.getFloor(converterVersion); -+ -+ if (hooks != null) { -+ for (int k = 0, klen = hooks.size(); k < klen; ++k) { -+ final Object replace = hooks.get(k).preHook(ret == null ? data : ret, fromVersion, toVersion); -+ if (replace != null) { -+ ret = replace; -+ } -+ } -+ } -+ -+ final Object converted = converter.convert(ret == null ? data : ret, fromVersion, toVersion); -+ if (converted != null) { -+ ret = converted; -+ } -+ -+ // possibly new data format, update hooks -+ hooks = this.structureHooks.getFloor(toVersion); -+ -+ if (hooks != null) { -+ for (int k = 0, klen = hooks.size(); k < klen; ++k) { -+ final Object replace = hooks.get(k).postHook(ret == null ? data : ret, fromVersion, toVersion); -+ if (replace != null) { -+ ret = replace; -+ } -+ } -+ } -+ } -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f7dced8a47ebdd262ae815ff9bc453312343ce49 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java -@@ -0,0 +1,29 @@ -+package ca.spottedleaf.dataconverter.minecraft.hooks; -+ -+import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.util.NamespaceUtil; -+ -+public class DataHookEnforceNamespacedID implements DataHook, MapType> { -+ -+ private final String path; -+ -+ public DataHookEnforceNamespacedID() { -+ this("id"); -+ } -+ -+ public DataHookEnforceNamespacedID(final String path) { -+ this.path = path; -+ } -+ -+ @Override -+ public MapType preHook(final MapType data, final long fromVersion, final long toVersion) { -+ NamespaceUtil.enforceForPath(data, this.path); -+ return null; -+ } -+ -+ @Override -+ public MapType postHook(final MapType data, final long fromVersion, final long toVersion) { -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7f88487e7db589070512fafef1eb243ae29a379a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.hooks; -+ -+import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; -+import ca.spottedleaf.dataconverter.util.NamespaceUtil; -+ -+public class DataHookValueTypeEnforceNamespaced implements DataHook { -+ -+ @Override -+ public Object preHook(final Object data, final long fromVersion, final long toVersion) { -+ if (data instanceof String) { -+ return NamespaceUtil.correctNamespaceOrNull((String)data); -+ } -+ return null; -+ } -+ -+ @Override -+ public Object postHook(final Object data, final long fromVersion, final long toVersion) { -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java -new file mode 100644 -index 0000000000000000000000000000000000000000..952c64fbc4b21a2d85d66c5183e8094a584e0d17 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/util/ComponentUtils.java -@@ -0,0 +1,27 @@ -+package ca.spottedleaf.dataconverter.minecraft.util; -+ -+import com.google.gson.JsonObject; -+import net.minecraft.util.GsonHelper; -+ -+public final class ComponentUtils { -+ -+ public static final String EMPTY = createPlainTextComponent(""); -+ -+ public static String createPlainTextComponent(final String text) { -+ final JsonObject ret = new JsonObject(); -+ -+ ret.addProperty("text", text); -+ -+ return GsonHelper.toStableString(ret); -+ } -+ -+ public static String createTranslatableComponent(final String key) { -+ final JsonObject ret = new JsonObject(); -+ -+ ret.addProperty("translate", key); -+ -+ return GsonHelper.toStableString(ret); -+ } -+ -+ private ComponentUtils() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7e8f42eb57c12c885a1c17eafab1c9d9be4d8963 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java -@@ -0,0 +1,159 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V100 { -+ -+ protected static final int VERSION = MCVersions.V15W32A; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType equipment = data.getList("Equipment", ObjectType.MAP); -+ data.remove("Equipment"); -+ -+ if (equipment != null) { -+ if (equipment.size() > 0 && data.getListUnchecked("HandItems") == null) { -+ final ListType handItems = Types.NBT.createEmptyList(); -+ data.setList("HandItems", handItems); -+ handItems.addMap(equipment.getMap(0)); -+ handItems.addMap(Types.NBT.createEmptyMap()); -+ } -+ -+ if (equipment.size() > 1 && data.getListUnchecked("ArmorItems") == null) { -+ final ListType armorItems = Types.NBT.createEmptyList(); -+ data.setList("ArmorItems", armorItems); -+ for (int i = 1; i < Math.min(equipment.size(), 5); ++i) { -+ armorItems.addMap(equipment.getMap(i)); -+ } -+ } -+ } -+ -+ final ListType dropChances = data.getList("DropChances", ObjectType.FLOAT); -+ data.remove("DropChances"); -+ -+ if (dropChances != null) { -+ if (data.getListUnchecked("HandDropChances") == null) { -+ final ListType handDropChances = Types.NBT.createEmptyList(); -+ data.setList("HandDropChances", handDropChances); -+ if (0 < dropChances.size()) { -+ handDropChances.addFloat(dropChances.getFloat(0)); -+ } else { -+ handDropChances.addFloat(0.0F); -+ } -+ handDropChances.addFloat(0.0F); -+ } -+ -+ if (data.getListUnchecked("ArmorDropChances") == null) { -+ final ListType armorDropChances = Types.NBT.createEmptyList(); -+ data.setList("ArmorDropChances", armorDropChances); -+ for (int i = 1; i < 5; ++i) { -+ if (i < dropChances.size()) { -+ armorDropChances.addFloat(dropChances.getFloat(i)); -+ } else { -+ armorDropChances.addFloat(0.0F); -+ } -+ } -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ registerMob("ArmorStand"); -+ registerMob("Creeper"); -+ registerMob("Skeleton"); -+ registerMob("Spider"); -+ registerMob("Giant"); -+ registerMob("Zombie"); -+ registerMob("Slime"); -+ registerMob("Ghast"); -+ registerMob("PigZombie"); -+ registerMob("Enderman"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Enderman", new DataWalkerBlockNames("carried")); -+ registerMob("CaveSpider"); -+ registerMob("Silverfish"); -+ registerMob("Blaze"); -+ registerMob("LavaSlime"); -+ registerMob("EnderDragon"); -+ registerMob("WitherBoss"); -+ registerMob("Bat"); -+ registerMob("Witch"); -+ registerMob("Endermite"); -+ registerMob("Guardian"); -+ registerMob("Pig"); -+ registerMob("Sheep"); -+ registerMob("Cow"); -+ registerMob("Chicken"); -+ registerMob("Squid"); -+ registerMob("Wolf"); -+ registerMob("MushroomCow"); -+ registerMob("SnowMan"); -+ registerMob("Ozelot"); -+ registerMob("VillagerGolem"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItems("ArmorItem", "SaddleItem")); -+ registerMob("Rabbit"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); -+ -+ final MapType offers = data.getMap("Offers"); -+ if (offers != null) { -+ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); -+ if (recipes != null) { -+ for (int i = 0, len = recipes.size(); i < len; ++i) { -+ final MapType recipe = recipes.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); -+ } -+ } -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); -+ -+ return null; -+ }); -+ registerMob("Shulker"); -+ -+ MCTypeRegistry.STRUCTURE.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final ListType entities = data.getList("entities", ObjectType.MAP); -+ if (entities != null) { -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, entities.getMap(i), "nbt", fromVersion, toVersion); -+ } -+ } -+ -+ final ListType blocks = data.getList("blocks", ObjectType.MAP); -+ if (blocks != null) { -+ for (int i = 0, len = blocks.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.TILE_ENTITY, blocks.getMap(i), "nbt", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, data, "palette", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+ -+ private V100() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java -new file mode 100644 -index 0000000000000000000000000000000000000000..98fbbf59ca00dbf58982d93f561f7d5f5b81951f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java -@@ -0,0 +1,73 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.gson.JsonParseException; -+import net.minecraft.network.chat.CommonComponents; -+import net.minecraft.network.chat.Component; -+import net.minecraft.util.GsonHelper; -+import net.minecraft.util.datafix.fixes.BlockEntitySignTextStrictJsonFix; -+ -+public final class V101 { -+ -+ protected static final int VERSION = MCVersions.V15W32A + 1; -+ -+ protected static void updateLine(final MapType data, final String path) { -+ final String textString = data.getString(path); -+ if (textString == null || textString.isEmpty() || "null".equals(textString)) { -+ data.setString(path, Component.Serializer.toJson(CommonComponents.EMPTY)); -+ return; -+ } -+ -+ Component component = null; -+ -+ if (textString.charAt(0) == '"' && textString.charAt(textString.length() - 1) == '"' -+ || textString.charAt(0) == '{' && textString.charAt(textString.length() - 1) == '}') { -+ try { -+ component = GsonHelper.fromNullableJson(BlockEntitySignTextStrictJsonFix.GSON, textString, Component.class, true); -+ if (component == null) { -+ component = CommonComponents.EMPTY; -+ } -+ } catch (final JsonParseException ignored) {} -+ -+ if (component == null) { -+ try { -+ component = Component.Serializer.fromJson(textString); -+ } catch (final JsonParseException ignored) {} -+ } -+ -+ if (component == null) { -+ try { -+ component = Component.Serializer.fromJsonLenient(textString); -+ } catch (final JsonParseException ignored) {} -+ } -+ -+ if (component == null) { -+ component = Component.literal(textString); -+ } -+ } else { -+ component = Component.literal(textString); -+ } -+ -+ data.setString(path, Component.Serializer.toJson(component)); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("Sign", new DataConverter<>(VERSION) { -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateLine(data, "Text1"); -+ updateLine(data, "Text2"); -+ updateLine(data, "Text3"); -+ updateLine(data, "Text4"); -+ return null; -+ } -+ }); -+ } -+ -+ private V101() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java -new file mode 100644 -index 0000000000000000000000000000000000000000..76374ca99efdf898dee0829fd8eb5fac26dc9a22 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java -@@ -0,0 +1,87 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.mojang.logging.LogUtils; -+import org.slf4j.Logger; -+ -+public final class V102 { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V15W32A + 2; -+ -+ public static void register() { -+ // V102 -> V15W32A + 2 -+ // V102 schema only modifies ITEM_STACK to have only a string ID, but our ITEM_NAME is generic (int or String) so we don't -+ // actually need to update the walker -+ -+ MCTypeRegistry.ITEM_NAME.addConverter(new DataConverter<>(VERSION) { -+ @Override -+ public Object convert(final Object data, final long sourceVersion, final long toVersion) { -+ if (!(data instanceof Number)) { -+ return null; -+ } -+ final int id = ((Number)data).intValue(); -+ final String remap = HelperItemNameV102.getNameFromId(id); -+ if (remap == null) { -+ LOGGER.warn("Unknown legacy integer id (V102) " + id); -+ } -+ return remap == null ? HelperItemNameV102.getNameFromId(0) : remap; -+ } -+ }); -+ -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!data.hasKey("id", ObjectType.NUMBER)) { -+ return null; -+ } -+ -+ final int id = data.getInt("id"); -+ -+ String remap = HelperItemNameV102.getNameFromId(id); -+ if (remap == null) { -+ LOGGER.warn("Unknown legacy integer id (V102) " + id); -+ remap = HelperItemNameV102.getNameFromId(0); -+ } -+ -+ data.setString("id", remap); -+ -+ return null; -+ } -+ }); -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:potion", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final short damage = data.getShort("Damage"); -+ if (damage != 0) { -+ data.setShort("Damage", (short)0); -+ } -+ MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ tag = Types.NBT.createEmptyMap(); -+ data.setMap("tag", tag); -+ } -+ -+ if (!tag.hasKey("Potion", ObjectType.STRING)) { -+ final String converted = HelperItemNameV102.getPotionNameFromId(damage); -+ tag.setString("Potion", converted == null ? "minecraft:water" : converted); -+ if ((damage & 16384) == 16384) { -+ data.setString("id", "minecraft:splash_potion"); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V102() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e251ead28d7d90937ae5871ffac489c1161e6e87 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java -@@ -0,0 +1,45 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1022 { -+ -+ protected static final int VERSION = MCVersions.V17W06A; -+ -+ public static void register() { -+ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType rootVehicle = data.getMap("RootVehicle"); -+ if (rootVehicle != null) { -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, rootVehicle, "Entity", fromVersion, toVersion); -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "EnderItems", fromVersion, toVersion); -+ -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "ShoulderEntityLeft", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "ShoulderEntityRight", fromVersion, toVersion); -+ -+ final MapType recipeBook = data.getMap("recipeBook"); -+ if (recipeBook != null) { -+ WalkerUtils.convertList(MCTypeRegistry.RECIPE, recipeBook, "recipes", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.RECIPE, recipeBook, "toBeDisplayed", fromVersion, toVersion); -+ } -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.HOTBAR.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ for (final String key : data.keys()) { -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, key, fromVersion, toVersion); -+ } -+ -+ return null; -+ }); -+ } -+ -+ private V1022() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java -new file mode 100644 -index 0000000000000000000000000000000000000000..544f6a54041147a8c9ee3ff52c31c480a3696924 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java -@@ -0,0 +1,49 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperSpawnEggNameV105; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V105 { -+ -+ protected static final int VERSION = MCVersions.V15W32C + 1; -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:spawn_egg", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ tag = Types.NBT.createEmptyMap(); -+ } -+ -+ final short damage = data.getShort("Damage"); -+ if (damage != 0) { -+ data.setShort("Damage", (short)0); -+ } -+ -+ MapType entityTag = tag.getMap("EntityTag"); -+ if (entityTag == null) { -+ entityTag = Types.NBT.createEmptyMap(); -+ } -+ -+ if (!entityTag.hasKey("id", ObjectType.STRING)) { -+ final String converted = HelperSpawnEggNameV105.getSpawnNameFromId(damage); -+ if (converted != null) { -+ entityTag.setString("id", converted); -+ tag.setMap("EntityTag", entityTag); -+ data.setMap("tag", tag); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V105() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java -new file mode 100644 -index 0000000000000000000000000000000000000000..951838b0f4f2b4ed82d707706ef15d779f3f41eb ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java -@@ -0,0 +1,84 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V106 { -+ -+ protected static final int VERSION = MCVersions.V15W32C + 2; -+ -+ public static void register() { -+ // V106 -> V15W32C + 2 -+ -+ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ // While all converters for spawners check the id for this version, we don't because spawners exist in minecarts. ooops! Loading a chunk -+ // with a minecart spawner from 1.7.10 in 1.16.5 vanilla will fail to convert! Clearly there was a mistake in how they -+ // used and applied spawner converters. In anycase, do not check the id - we are not guaranteed to be a tile -+ // entity. We can be a regular old minecart spawner. And we know we are a spawner because this is only called from data walkers. -+ -+ final String entityId = data.getString("EntityId"); -+ if (entityId != null) { -+ data.remove("EntityId"); -+ MapType spawnData = data.getMap("SpawnData"); -+ if (spawnData == null) { -+ spawnData = Types.NBT.createEmptyMap(); -+ data.setMap("SpawnData", spawnData); -+ } -+ spawnData.setString("id", entityId.isEmpty() ? "Pig" : entityId); -+ } -+ -+ final ListType spawnPotentials = data.getList("SpawnPotentials", ObjectType.MAP); -+ if (spawnPotentials != null) { -+ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { -+ // convert to standard entity format (it's not a coincidence a walker for spawners is only added -+ // in this version) -+ final MapType spawn = spawnPotentials.getMap(i); -+ final String spawnType = spawn.getString("Type"); -+ if (spawnType == null) { -+ continue; -+ } -+ spawn.remove("Type"); -+ -+ MapType properties = spawn.getMap("Properties"); -+ if (properties == null) { -+ properties = Types.NBT.createEmptyMap(); -+ } else { -+ spawn.remove("Properties"); -+ } -+ -+ properties.setString("id", spawnType); -+ -+ spawn.setMap("Entity", properties); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final ListType spawnPotentials = data.getList("SpawnPotentials", ObjectType.MAP); -+ if (spawnPotentials != null) { -+ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { -+ final MapType spawnPotential = spawnPotentials.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, spawnPotential, "Entity", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "SpawnData", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+ -+ private V106() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java -new file mode 100644 -index 0000000000000000000000000000000000000000..aa8c8d22ee2a77604d923b62f5a93ede9b3f333f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java -@@ -0,0 +1,44 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V107 { -+ -+ protected static final int VERSION = MCVersions.V15W32C + 3; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("Minecart", new DataConverter<>(VERSION) { -+ protected final String[] MINECART_IDS = new String[] { -+ "MinecartRideable", // 0 -+ "MinecartChest", // 1 -+ "MinecartFurnace", // 2 -+ "MinecartTNT", // 3 -+ "MinecartSpawner", // 4 -+ "MinecartHopper", // 5 -+ "MinecartCommandBlock" // 6 -+ }; -+ // Vanilla does not use all of the IDs here. The legacy (pre DFU) code does, so I'm going to use them. -+ // No harm in catching more cases here. -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ String newId = "MinecartRideable"; // dfl -+ final int type = data.getInt("Type"); -+ data.remove("Type"); -+ -+ if (type >= 0 && type < MINECART_IDS.length) { -+ newId = MINECART_IDS[type]; -+ } -+ data.setString("id", newId); -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V107() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6bc4e2939bd26538492a7b94b743957d56ddc575 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java -@@ -0,0 +1,48 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.mojang.logging.LogUtils; -+import org.slf4j.Logger; -+ -+import java.util.UUID; -+ -+public final class V108 { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V15W32C + 4; -+ -+ public static void register() { -+ // Convert String UUID into UUIDMost and UUIDLeast -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String uuidString = data.getString("UUID"); -+ -+ if (uuidString == null) { -+ return null; -+ } -+ data.remove("UUID"); -+ -+ final UUID uuid; -+ try { -+ uuid = UUID.fromString(uuidString); -+ } catch (final Exception ex) { -+ LOGGER.warn("Failed to parse UUID for legacy entity (V108): " + uuidString, ex); -+ return null; -+ } -+ -+ data.setLong("UUIDMost", uuid.getMostSignificantBits()); -+ data.setLong("UUIDLeast", uuid.getLeastSignificantBits()); -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V108() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5e0e52fa2d8988ca973f8a97b2374a8c3d4ef80c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java -@@ -0,0 +1,83 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.common.collect.Sets; -+import java.util.Set; -+ -+public final class V109 { -+ -+ protected static final int VERSION = MCVersions.V15W32C + 5; -+ -+ // DFU declares this exact field but leaves it unused. Not sure why, legacy conversion system checked if the ID matched. -+ // I'm going to leave it here unused as well, just in case it's needed in the future. -+ protected static final Set ENTITIES = Sets.newHashSet( -+ "ArmorStand", -+ "Bat", -+ "Blaze", -+ "CaveSpider", -+ "Chicken", -+ "Cow", -+ "Creeper", -+ "EnderDragon", -+ "Enderman", -+ "Endermite", -+ "EntityHorse", -+ "Ghast", -+ "Giant", -+ "Guardian", -+ "LavaSlime", -+ "MushroomCow", -+ "Ozelot", -+ "Pig", -+ "PigZombie", -+ "Rabbit", -+ "Sheep", -+ "Shulker", -+ "Silverfish", -+ "Skeleton", -+ "Slime", -+ "SnowMan", -+ "Spider", -+ "Squid", -+ "Villager", -+ "VillagerGolem", -+ "Witch", -+ "WitherBoss", -+ "Wolf", -+ "Zombie" -+ ); -+ -+ public static void register() { -+ // Converts health to be in float, and cleans up whatever the hell was going on with HealF and Health... -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final Number healF = data.getNumber("HealF"); -+ final Number heal = data.getNumber("Health"); -+ -+ final float newHealth; -+ -+ if (healF != null) { -+ data.remove("HealF"); -+ newHealth = healF.floatValue(); -+ } else { -+ if (heal == null) { -+ return null; -+ } -+ -+ newHealth = heal.floatValue(); -+ } -+ -+ data.setFloat("Health", newHealth); -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V109() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9771810a1f1cbf760fd9a8a5fd575f6052f40ea9 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java -@@ -0,0 +1,39 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V110 { -+ -+ protected static final int VERSION = MCVersions.V15W32C + 6; -+ -+ public static void register() { -+ // Moves the Saddle boolean to be an actual saddle item. Note: The data walker for the SaddleItem exists -+ // in V99, it doesn't need to be added here. -+ MCTypeRegistry.ENTITY.addConverterForId("EntityHorse", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!data.getBoolean("Saddle") || data.hasKey("SaddleItem", ObjectType.MAP)) { -+ return null; -+ } -+ -+ final MapType saddleItem = Types.NBT.createEmptyMap(); -+ data.remove("Saddle"); -+ data.setMap("SaddleItem", saddleItem); -+ -+ saddleItem.setString("id", "minecraft:saddle"); -+ saddleItem.setByte("Count", (byte)1); -+ saddleItem.setShort("Damage", (short)0); -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V110() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5bae7effda7761a3f2a0a2ce550d867cb2c18b99 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java -@@ -0,0 +1,67 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V111 { -+ -+ protected static final int VERSION = MCVersions.V15W33B; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("Painting", new EntityRotationFix("Painting")); -+ MCTypeRegistry.ENTITY.addConverterForId("ItemFrame", new EntityRotationFix("ItemFrame")); -+ } -+ -+ private V111() {} -+ -+ protected static final class EntityRotationFix extends DataConverter, MapType> { -+ -+ private static final int[][] DIRECTIONS = new int[][] { -+ {0, 0, 1}, -+ {-1, 0, 0}, -+ {0, 0, -1}, -+ {1, 0, 0} -+ }; -+ -+ protected final String id; -+ -+ public EntityRotationFix(final String id) { -+ super(VERSION); -+ this.id = id; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getNumber("Facing") != null) { -+ return null; -+ } -+ -+ final Number direction = data.getNumber("Direction"); -+ final int facing; -+ if (direction != null) { -+ data.remove("Direction"); -+ facing = direction.intValue() % DIRECTIONS.length; -+ final int[] offsets = DIRECTIONS[facing]; -+ data.setInt("TileX", data.getInt("TileX") + offsets[0]); -+ data.setInt("TileY", data.getInt("TileY") + offsets[1]); -+ data.setInt("TileZ", data.getInt("TileZ") + offsets[2]); -+ if ("ItemFrame".equals(data.getString("id"))) { -+ final Number rotation = data.getNumber("ItemRotation"); -+ if (rotation != null) { -+ data.setByte("ItemRotation", (byte)(rotation.byteValue() * 2)); -+ } -+ } -+ } else { -+ facing = data.getByte("Dir") % DIRECTIONS.length; -+ data.remove("Dir"); -+ } -+ -+ data.setByte("Facing", (byte)facing); -+ -+ return null; -+ } -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a6d3c28e8c97b53f388c03ccad3449390937d3b2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java -@@ -0,0 +1,102 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V1125 { -+ -+ protected static final int VERSION = MCVersions.V17W15A; -+ protected static final int BED_BLOCK_ID = 416; -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ final int chunkX = level.getInt("xPos"); -+ final int chunkZ = level.getInt("zPos"); -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections == null) { -+ return null; -+ } -+ -+ ListType tileEntities = level.getList("TileEntities", ObjectType.MAP); -+ if (tileEntities == null) { -+ tileEntities = Types.NBT.createEmptyList(); -+ level.setList("TileEntities", tileEntities); -+ } -+ -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ final byte sectionY = section.getByte("Y"); -+ final byte[] blocks = section.getBytes("Blocks"); -+ -+ if (blocks == null) { -+ continue; -+ } -+ -+ for (int blockIndex = 0; blockIndex < blocks.length; ++blockIndex) { -+ if (BED_BLOCK_ID != ((blocks[blockIndex] & 255) << 4)) { -+ continue; -+ } -+ -+ final int localX = blockIndex & 15; -+ final int localZ = (blockIndex >> 4) & 15; -+ final int localY = (blockIndex >> 8) & 15; -+ -+ final MapType newTile = Types.NBT.createEmptyMap(); -+ newTile.setString("id", "minecraft:bed"); -+ newTile.setInt("x", localX + (chunkX << 4)); -+ newTile.setInt("y", localY + (sectionY << 4)); -+ newTile.setInt("z", localZ + (chunkZ << 4)); -+ newTile.setShort("color", (short)14); // Red -+ -+ tileEntities.addMap(newTile); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:bed", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getShort("Damage") == 0) { -+ data.setShort("Damage", (short)14); // Red -+ } -+ -+ return null; -+ } -+ }); -+ -+ -+ MCTypeRegistry.ADVANCEMENTS.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertKeys(MCTypeRegistry.BIOME, data.getMap("minecraft:adventure/adventuring_time"), "criteria", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, data.getMap("minecraft:adventure/kill_a_mob"), "criteria", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, data.getMap("minecraft:adventure/kill_all_mobs"), "criteria", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, data.getMap("minecraft:adventure/bred_all_animals"), "criteria", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ // Enforce namespacing for ids -+ MCTypeRegistry.BIOME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); -+ } -+ -+ private V1125() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8b93bba2b084e20d346461f53d2f7662c3d6238b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java -@@ -0,0 +1,41 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V113 { -+ -+ protected static final int VERSION = MCVersions.V15W33C + 1; -+ -+ protected static void checkList(final MapType data, final String id, final int requiredLength) { -+ final ListType list = data.getList(id, ObjectType.FLOAT); -+ if (list != null && list.size() == requiredLength) { -+ for (int i = 0; i < requiredLength; ++i) { -+ if (list.getFloat(i) != 0.0F) { -+ return; -+ } -+ } -+ } -+ -+ data.remove(id); -+ } -+ -+ public static void register() { -+ // Removes "HandDropChances" and "ArmorDropChances" if they're empty. -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ checkList(data, "HandDropChances", 2); -+ checkList(data, "ArmorDropChances", 4); -+ return null; -+ } -+ }); -+ } -+ -+ private V113() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ac390a6111ba1a4aae3d5726747f60f4929fa254 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java -@@ -0,0 +1,177 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+ -+public final class V1344 { -+ -+ protected static final int VERSION = MCVersions.V1_12_2 + 1; -+ -+ private static final Int2ObjectOpenHashMap BUTTON_ID_TO_NAME = new Int2ObjectOpenHashMap<>(); -+ static { -+ BUTTON_ID_TO_NAME.put(0, "key.unknown"); -+ BUTTON_ID_TO_NAME.put(11, "key.0"); -+ BUTTON_ID_TO_NAME.put(2, "key.1"); -+ BUTTON_ID_TO_NAME.put(3, "key.2"); -+ BUTTON_ID_TO_NAME.put(4, "key.3"); -+ BUTTON_ID_TO_NAME.put(5, "key.4"); -+ BUTTON_ID_TO_NAME.put(6, "key.5"); -+ BUTTON_ID_TO_NAME.put(7, "key.6"); -+ BUTTON_ID_TO_NAME.put(8, "key.7"); -+ BUTTON_ID_TO_NAME.put(9, "key.8"); -+ BUTTON_ID_TO_NAME.put(10, "key.9"); -+ BUTTON_ID_TO_NAME.put(30, "key.a"); -+ BUTTON_ID_TO_NAME.put(40, "key.apostrophe"); -+ BUTTON_ID_TO_NAME.put(48, "key.b"); -+ BUTTON_ID_TO_NAME.put(43, "key.backslash"); -+ BUTTON_ID_TO_NAME.put(14, "key.backspace"); -+ BUTTON_ID_TO_NAME.put(46, "key.c"); -+ BUTTON_ID_TO_NAME.put(58, "key.caps.lock"); -+ BUTTON_ID_TO_NAME.put(51, "key.comma"); -+ BUTTON_ID_TO_NAME.put(32, "key.d"); -+ BUTTON_ID_TO_NAME.put(211, "key.delete"); -+ BUTTON_ID_TO_NAME.put(208, "key.down"); -+ BUTTON_ID_TO_NAME.put(18, "key.e"); -+ BUTTON_ID_TO_NAME.put(207, "key.end"); -+ BUTTON_ID_TO_NAME.put(28, "key.enter"); -+ BUTTON_ID_TO_NAME.put(13, "key.equal"); -+ BUTTON_ID_TO_NAME.put(1, "key.escape"); -+ BUTTON_ID_TO_NAME.put(33, "key.f"); -+ BUTTON_ID_TO_NAME.put(59, "key.f1"); -+ BUTTON_ID_TO_NAME.put(68, "key.f10"); -+ BUTTON_ID_TO_NAME.put(87, "key.f11"); -+ BUTTON_ID_TO_NAME.put(88, "key.f12"); -+ BUTTON_ID_TO_NAME.put(100, "key.f13"); -+ BUTTON_ID_TO_NAME.put(101, "key.f14"); -+ BUTTON_ID_TO_NAME.put(102, "key.f15"); -+ BUTTON_ID_TO_NAME.put(103, "key.f16"); -+ BUTTON_ID_TO_NAME.put(104, "key.f17"); -+ BUTTON_ID_TO_NAME.put(105, "key.f18"); -+ BUTTON_ID_TO_NAME.put(113, "key.f19"); -+ BUTTON_ID_TO_NAME.put(60, "key.f2"); -+ BUTTON_ID_TO_NAME.put(61, "key.f3"); -+ BUTTON_ID_TO_NAME.put(62, "key.f4"); -+ BUTTON_ID_TO_NAME.put(63, "key.f5"); -+ BUTTON_ID_TO_NAME.put(64, "key.f6"); -+ BUTTON_ID_TO_NAME.put(65, "key.f7"); -+ BUTTON_ID_TO_NAME.put(66, "key.f8"); -+ BUTTON_ID_TO_NAME.put(67, "key.f9"); -+ BUTTON_ID_TO_NAME.put(34, "key.g"); -+ BUTTON_ID_TO_NAME.put(41, "key.grave.accent"); -+ BUTTON_ID_TO_NAME.put(35, "key.h"); -+ BUTTON_ID_TO_NAME.put(199, "key.home"); -+ BUTTON_ID_TO_NAME.put(23, "key.i"); -+ BUTTON_ID_TO_NAME.put(210, "key.insert"); -+ BUTTON_ID_TO_NAME.put(36, "key.j"); -+ BUTTON_ID_TO_NAME.put(37, "key.k"); -+ BUTTON_ID_TO_NAME.put(82, "key.keypad.0"); -+ BUTTON_ID_TO_NAME.put(79, "key.keypad.1"); -+ BUTTON_ID_TO_NAME.put(80, "key.keypad.2"); -+ BUTTON_ID_TO_NAME.put(81, "key.keypad.3"); -+ BUTTON_ID_TO_NAME.put(75, "key.keypad.4"); -+ BUTTON_ID_TO_NAME.put(76, "key.keypad.5"); -+ BUTTON_ID_TO_NAME.put(77, "key.keypad.6"); -+ BUTTON_ID_TO_NAME.put(71, "key.keypad.7"); -+ BUTTON_ID_TO_NAME.put(72, "key.keypad.8"); -+ BUTTON_ID_TO_NAME.put(73, "key.keypad.9"); -+ BUTTON_ID_TO_NAME.put(78, "key.keypad.add"); -+ BUTTON_ID_TO_NAME.put(83, "key.keypad.decimal"); -+ BUTTON_ID_TO_NAME.put(181, "key.keypad.divide"); -+ BUTTON_ID_TO_NAME.put(156, "key.keypad.enter"); -+ BUTTON_ID_TO_NAME.put(141, "key.keypad.equal"); -+ BUTTON_ID_TO_NAME.put(55, "key.keypad.multiply"); -+ BUTTON_ID_TO_NAME.put(74, "key.keypad.subtract"); -+ BUTTON_ID_TO_NAME.put(38, "key.l"); -+ BUTTON_ID_TO_NAME.put(203, "key.left"); -+ BUTTON_ID_TO_NAME.put(56, "key.left.alt"); -+ BUTTON_ID_TO_NAME.put(26, "key.left.bracket"); -+ BUTTON_ID_TO_NAME.put(29, "key.left.control"); -+ BUTTON_ID_TO_NAME.put(42, "key.left.shift"); -+ BUTTON_ID_TO_NAME.put(219, "key.left.win"); -+ BUTTON_ID_TO_NAME.put(50, "key.m"); -+ BUTTON_ID_TO_NAME.put(12, "key.minus"); -+ BUTTON_ID_TO_NAME.put(49, "key.n"); -+ BUTTON_ID_TO_NAME.put(69, "key.num.lock"); -+ BUTTON_ID_TO_NAME.put(24, "key.o"); -+ BUTTON_ID_TO_NAME.put(25, "key.p"); -+ BUTTON_ID_TO_NAME.put(209, "key.page.down"); -+ BUTTON_ID_TO_NAME.put(201, "key.page.up"); -+ BUTTON_ID_TO_NAME.put(197, "key.pause"); -+ BUTTON_ID_TO_NAME.put(52, "key.period"); -+ BUTTON_ID_TO_NAME.put(183, "key.print.screen"); -+ BUTTON_ID_TO_NAME.put(16, "key.q"); -+ BUTTON_ID_TO_NAME.put(19, "key.r"); -+ BUTTON_ID_TO_NAME.put(205, "key.right"); -+ BUTTON_ID_TO_NAME.put(184, "key.right.alt"); -+ BUTTON_ID_TO_NAME.put(27, "key.right.bracket"); -+ BUTTON_ID_TO_NAME.put(157, "key.right.control"); -+ BUTTON_ID_TO_NAME.put(54, "key.right.shift"); -+ BUTTON_ID_TO_NAME.put(220, "key.right.win"); -+ BUTTON_ID_TO_NAME.put(31, "key.s"); -+ BUTTON_ID_TO_NAME.put(70, "key.scroll.lock"); -+ BUTTON_ID_TO_NAME.put(39, "key.semicolon"); -+ BUTTON_ID_TO_NAME.put(53, "key.slash"); -+ BUTTON_ID_TO_NAME.put(57, "key.space"); -+ BUTTON_ID_TO_NAME.put(20, "key.t"); -+ BUTTON_ID_TO_NAME.put(15, "key.tab"); -+ BUTTON_ID_TO_NAME.put(22, "key.u"); -+ BUTTON_ID_TO_NAME.put(200, "key.up"); -+ BUTTON_ID_TO_NAME.put(47, "key.v"); -+ BUTTON_ID_TO_NAME.put(17, "key.w"); -+ BUTTON_ID_TO_NAME.put(45, "key.x"); -+ BUTTON_ID_TO_NAME.put(21, "key.y"); -+ BUTTON_ID_TO_NAME.put(44, "key.z"); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ for (final String key : data.keys()) { -+ if (!key.startsWith("key_")) { -+ continue; -+ } -+ final String value = data.getString(key); -+ final int code; -+ try { -+ code = Integer.parseInt(value); -+ } catch (final NumberFormatException ex) { -+ continue; -+ } -+ -+ final String newEntry; -+ -+ if (code < 0) { -+ final int mouseCode = code + 100; -+ switch (mouseCode) { -+ case 0: -+ newEntry = "key.mouse.left"; -+ break; -+ case 1: -+ newEntry = "key.mouse.right"; -+ break; -+ case 2: -+ newEntry = "key.mouse.middle"; -+ break; -+ default: -+ newEntry = "key.mouse." + (mouseCode + 1); -+ break; -+ } -+ } else { -+ newEntry = BUTTON_ID_TO_NAME.getOrDefault(code, "key.unknown"); -+ } -+ -+ // No CMEs occur for existing entries in maps. -+ data.setString(key, newEntry); -+ } -+ return null; -+ } -+ }); -+ } -+ -+ private V1344() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java -new file mode 100644 -index 0000000000000000000000000000000000000000..764a56fda5ee909ac47a0c1b3b581c8c26deb591 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java -@@ -0,0 +1,61 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V135 { -+ -+ protected static final int VERSION = MCVersions.V15W40B + 1; -+ -+ public static void register() { -+ // In this update they changed the "Riding" value to be "Passengers", which is now a list. So it added -+ // support for multiple entities riding. Of course, Riding and Passenger are opposites - so it also will -+ // switch the data layout to be from highest rider to lowest rider, in terms of depth. -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(MapType data, final long sourceVersion, final long toVersion) { -+ MapType ret = null; -+ while (data.hasKey("Riding", ObjectType.MAP)) { -+ final MapType riding = data.getMap("Riding"); -+ data.remove("Riding"); -+ -+ final ListType passengers = Types.NBT.createEmptyList(); -+ riding.setList("Passengers", passengers); -+ passengers.addMap(data); -+ -+ ret = data = riding; -+ } -+ -+ return ret; -+ } -+ }); -+ -+ -+ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, new DataWalkerItemLists("Inventory", "EnderItems")); -+ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType rootVehicle = data.getMap("RootVehicle"); -+ if (rootVehicle != null) { -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, rootVehicle, "Entity", fromVersion, toVersion); -+ } -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.ENTITY.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "Passengers", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ } -+ -+ private V135() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java -new file mode 100644 -index 0000000000000000000000000000000000000000..451e837349e10f1e76ac7d9f5d49cbe0ff630f4d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+ -+public final class V143 { -+ -+ protected static final int VERSION = MCVersions.V15W44B; -+ -+ public static void register() { -+ ConverterAbstractEntityRename.register(VERSION, (final String input) -> { -+ return "TippedArrow".equals(input) ? "Arrow" : null; -+ }); -+ } -+ -+ private V143() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fc0ece569baed94bbf3cbbaa21a397fdc37e51e8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java -@@ -0,0 +1,36 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1446 { -+ -+ protected static final int VERSION = MCVersions.V17W43B + 1; -+ -+ public static void register() { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ for (final String key : data.keys()) { -+ if (!key.startsWith("key_")) { -+ continue; -+ } -+ -+ final String value = data.getString(key); -+ -+ if (value.startsWith("key.mouse") || value.startsWith("scancode.")) { -+ continue; -+ } -+ -+ data.setString(key, "key.keyboard." + value.substring("key.".length())); -+ } -+ return null; -+ } -+ }); -+ } -+ -+ private V1446() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java -new file mode 100644 -index 0000000000000000000000000000000000000000..711222cd33ee557b7f3d1f6ae73ad45d1caf6768 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java -@@ -0,0 +1,25 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1450 { -+ -+ protected static final int VERSION = MCVersions.V17W46A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType ret = HelperBlockFlatteningV1450.flattenNBT(data); -+ return ret == data ? null : ret.copy(); // copy to avoid problems with later state datafixers -+ } -+ }); -+ } -+ -+ private V1450() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java -new file mode 100644 -index 0000000000000000000000000000000000000000..aff3f47430cc85d12080c116610e4328e31acbe6 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java -@@ -0,0 +1,513 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.chunk.ConverterFlattenChunk; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenItemStack; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenSpawnEgg; -+import ca.spottedleaf.dataconverter.minecraft.converters.stats.ConverterFlattenStats; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterFlattenEntity; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity.DataWalkerTileEntities; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.common.base.Splitter; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.util.datafix.fixes.BlockStateData; -+import net.minecraft.util.datafix.fixes.EntityBlockStateFix; -+import org.apache.commons.lang3.math.NumberUtils; -+import java.util.Iterator; -+import java.util.List; -+import java.util.stream.Collectors; -+import java.util.stream.StreamSupport; -+ -+public final class V1451 { -+ -+ protected static final int VERSION = MCVersions.V17W47A; -+ -+ public static String packWithDot(final String string) { -+ final ResourceLocation resourceLocation = ResourceLocation.tryParse(string); -+ return resourceLocation != null ? resourceLocation.getNamespace() + "." + resourceLocation.getPath() : string; -+ } -+ -+ public static void register() { -+ // V0 -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, 0, "minecraft:trapped_chest", new DataWalkerItemLists("Items")); -+ -+ // V1 -+ MCTypeRegistry.CHUNK.addStructureConverter(new ConverterFlattenChunk()); -+ -+ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, 1, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); -+ -+ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); -+ if (tileTicks != null) { -+ for (int i = 0, len = tileTicks.size(); i < len; ++i) { -+ final MapType tileTick = tileTicks.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); -+ } -+ } -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section, "Palette", fromVersion, toVersion); -+ } -+ } -+ -+ return null; -+ }); -+ -+ // V2 -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:piston", new DataConverter<>(VERSION, 2) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int blockId = data.getInt("blockId"); -+ final int blockData = data.getInt("blockData") & 15; -+ -+ data.remove("blockId"); -+ data.remove("blockData"); -+ -+ data.setMap("blockState", HelperBlockFlatteningV1450.getNBTForId((blockId << 4) | blockData).copy()); // copy to avoid problems with later state datafixers -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, 2, "minecraft:piston", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "blockState")); -+ -+ // V3 -+ ConverterFlattenEntity.register(); -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:filled_map", new DataConverter<>(VERSION, 3) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ tag = Types.NBT.createEmptyMap(); -+ data.setMap("tag", tag); -+ } -+ -+ if (!tag.hasKey("map", ObjectType.NUMBER)) { // This if is from CB. as usual, no documentation from CB. I'm guessing it just wants to avoid possibly overwriting it. seems fine. -+ tag.setInt("map", data.getInt("Damage")); -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:potion", new DataWalkerItems("Potion")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:arrow", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:enderman", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:enderman", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "carriedBlockState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:falling_block", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "BlockState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:falling_block", new DataWalkerTileEntities("TileEntityData")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:spectral_arrow", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:chest_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:chest_minecart", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:commandblock_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:furnace_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:hopper_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:hopper_minecart", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:spawner_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:spawner_minecart", (final MapType data, final long fromVersion, final long toVersion) -> { -+ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); -+ return null; -+ }); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:tnt_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ -+ // V4 -+ MCTypeRegistry.BLOCK_NAME.addConverter(new DataConverter<>(VERSION, 4) { -+ @Override -+ public Object convert(final Object data, final long sourceVersion, final long toVersion) { -+ if (data instanceof Number) { -+ return HelperBlockFlatteningV1450.getNameForId(((Number)data).intValue()); -+ } else if (data instanceof String) { -+ return HelperBlockFlatteningV1450.getNewBlockName((String)data); // structure hook ensured data is namespaced -+ } -+ return null; -+ } -+ }); -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new ConverterFlattenItemStack()); -+ -+ // V5 -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:spawn_egg", new ConverterFlattenSpawnEgg(VERSION, 5)); -+ /* This datafixer has been disabled because the collar colour handler did not change from 1.12 -> 1.13 at all. -+ // So clearly somebody fucked up. This fixes wolf colours incorrectly converting between versions -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:wolf", new DataConverter<>(VERSION, 5) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final Number colour = data.getNumber("CollarColor"); -+ -+ if (colour != null) { -+ data.setByte("CollarColor", (byte)(15 - colour.intValue())); -+ } -+ -+ return null; -+ } -+ }); -+ */ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:banner", new DataConverter<>(VERSION, 5) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final Number base = data.getNumber("Base"); -+ if (base != null) { -+ data.setInt("Base", 15 - base.intValue()); -+ } -+ -+ final ListType patterns = data.getList("Patterns", ObjectType.MAP); -+ if (patterns != null) { -+ for (int i = 0, len = patterns.size(); i < len; ++i) { -+ final MapType pattern = patterns.getMap(i); -+ final Number colour = pattern.getNumber("Color"); -+ if (colour != null) { -+ pattern.setInt("Color", 15 - colour.intValue()); -+ } -+ } -+ } -+ -+ return null; -+ } -+ }); -+ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION, 5) { -+ private final Splitter SPLITTER = Splitter.on(';').limit(5); -+ private final Splitter LAYER_SPLITTER = Splitter.on(','); -+ private final Splitter OLD_AMOUNT_SPLITTER = Splitter.on('x').limit(2); -+ private final Splitter AMOUNT_SPLITTER = Splitter.on('*').limit(2); -+ private final Splitter BLOCK_SPLITTER = Splitter.on(':').limit(3); -+ -+ // idk man i just copy and pasted this one -+ private String fixGeneratorSettings(final String generatorSettings) { -+ if (generatorSettings.isEmpty()) { -+ return "minecraft:bedrock,2*minecraft:dirt,minecraft:grass_block;1;village"; -+ } else { -+ Iterator iterator = SPLITTER.split(generatorSettings).iterator(); -+ String string2 = (String)iterator.next(); -+ int j; -+ String string4; -+ if (iterator.hasNext()) { -+ j = NumberUtils.toInt(string2, 0); -+ string4 = (String)iterator.next(); -+ } else { -+ j = 0; -+ string4 = string2; -+ } -+ -+ if (j >= 0 && j <= 3) { -+ StringBuilder stringBuilder = new StringBuilder(); -+ Splitter splitter = j < 3 ? OLD_AMOUNT_SPLITTER : AMOUNT_SPLITTER; -+ stringBuilder.append((String) StreamSupport.stream(LAYER_SPLITTER.split(string4).spliterator(), false).map((stringx) -> { -+ List list = splitter.splitToList(stringx); -+ int k; -+ String string3; -+ if (list.size() == 2) { -+ k = NumberUtils.toInt((String)list.get(0)); -+ string3 = (String)list.get(1); -+ } else { -+ k = 1; -+ string3 = (String)list.get(0); -+ } -+ -+ List list2 = BLOCK_SPLITTER.splitToList(string3); -+ int l = ((String)list2.get(0)).equals("minecraft") ? 1 : 0; -+ String string5 = (String)list2.get(l); -+ int m = j == 3 ? EntityBlockStateFix.getBlockId("minecraft:" + string5) : NumberUtils.toInt(string5, 0); -+ int n = l + 1; -+ int o = list2.size() > n ? NumberUtils.toInt((String)list2.get(n), 0) : 0; -+ return (k == 1 ? "" : k + "*") + BlockStateData.getTag(m << 4 | o).get("Name").asString(""); -+ }).collect(Collectors.joining(","))); -+ -+ while(iterator.hasNext()) { -+ stringBuilder.append(';').append((String)iterator.next()); -+ } -+ -+ return stringBuilder.toString(); -+ } else { -+ return "minecraft:bedrock,2*minecraft:dirt,minecraft:grass_block;1;village"; -+ } -+ } -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!"flat".equalsIgnoreCase(data.getString("generatorName"))) { -+ return null; -+ } -+ -+ final String generatorOptions = data.getString("generatorOptions"); -+ if (generatorOptions == null) { -+ return null; -+ } -+ -+ data.setString("generatorOptions", this.fixGeneratorSettings(generatorOptions)); -+ -+ return null; -+ } -+ }); -+ -+ // V6 -+ MCTypeRegistry.STATS.addStructureConverter(ConverterFlattenStats.makeStatsConverter()); -+ MCTypeRegistry.OBJECTIVE.addStructureConverter(ConverterFlattenStats.makeObjectiveConverter()); -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jukebox", new DataConverter<>(VERSION, 6) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int record = data.getInt("Record"); -+ if (record <= 0) { -+ return null; -+ } -+ -+ data.remove("Record"); -+ -+ final String newItemId = ConverterFlattenItemStack.flattenItem(HelperItemNameV102.getNameFromId(record), 0); -+ if (newItemId == null) { -+ return null; -+ } -+ -+ final MapType recordItem = Types.NBT.createEmptyMap(); -+ data.setMap("RecordItem", recordItem); -+ -+ recordItem.setString("id", newItemId); -+ recordItem.setByte("Count", (byte)1); -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.STATS.addStructureWalker(VERSION, 6, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType stats = data.getMap("stats"); -+ if (stats == null) { -+ return null; -+ } -+ -+ WalkerUtils.convertKeys(MCTypeRegistry.BLOCK_NAME, stats, "minecraft:mined", fromVersion, toVersion); -+ -+ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:crafted", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:used", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:broken", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:picked_up", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:dropped", fromVersion, toVersion); -+ -+ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, stats, "minecraft:killed", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, stats, "minecraft:killed_by", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.OBJECTIVE.addStructureHook(VERSION, 6, new DataHook<>() { -+ @Override -+ public MapType preHook(final MapType data, final long fromVersion, final long toVersion) { -+ // unpack -+ final String criteriaName = data.getString("CriteriaName"); -+ String type; -+ String id; -+ -+ if (criteriaName != null) { -+ final int index = criteriaName.indexOf(':'); -+ if (index < 0) { -+ type = "_special"; -+ id = criteriaName; -+ } else { -+ try { -+ type = ResourceLocation.of(criteriaName.substring(0, index), '.').toString(); -+ id = ResourceLocation.of(criteriaName.substring(index + 1), '.').toString(); -+ } catch (final Exception ex) { -+ type = "_special"; -+ id = criteriaName; -+ } -+ } -+ } else { -+ type = null; -+ id = null; -+ } -+ -+ if (type != null && id != null) { -+ final MapType criteriaType = Types.NBT.createEmptyMap(); -+ data.setMap("CriteriaType", criteriaType); -+ -+ criteriaType.setString("type", type); -+ criteriaType.setString("id", id); -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public MapType postHook(final MapType data, final long fromVersion, final long toVersion) { -+ // repack -+ final MapType criteriaType = data.getMap("CriteriaType"); -+ -+ final String newName; -+ if (criteriaType == null) { -+ newName = null; -+ } else { -+ final String type = criteriaType.getString("type"); -+ final String id = criteriaType.getString("id"); -+ if (type != null && id != null) { -+ if ("_special".equals(type)) { -+ newName = id; -+ } else { -+ newName = packWithDot(type) + ":" + packWithDot(id); -+ } -+ } else { -+ newName = null; -+ } -+ } -+ -+ if (newName != null) { -+ data.remove("CriteriaType"); -+ data.setString("CriteriaName", newName); -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.OBJECTIVE.addStructureWalker(VERSION, 6, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType criteriaType = data.getMap("CriteriaType"); -+ if (criteriaType == null) { -+ return null; -+ } -+ -+ final String type = criteriaType.getString("type"); -+ -+ if (type == null) { -+ return null; -+ } -+ -+ switch (type) { -+ case "minecraft:mined": { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, criteriaType, "id", fromVersion, toVersion); -+ break; -+ } -+ -+ case "minecraft:crafted": -+ case "minecraft:used": -+ case "minecraft:broken": -+ case "minecraft:picked_up": -+ case "minecraft:dropped": { -+ WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, criteriaType, "id", fromVersion, toVersion); -+ break; -+ } -+ -+ case "minecraft:killed": -+ case "minecraft:killed_by": { -+ WalkerUtils.convert(MCTypeRegistry.ENTITY_NAME, criteriaType, "id", fromVersion, toVersion); -+ break; -+ } -+ } -+ -+ return null; -+ }); -+ -+ -+ // V7 -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION, 7) { -+ private static void convertToBlockState(final MapType data, final String path) { -+ final Number number = data.getNumber(path); -+ if (number == null) { -+ return; -+ } -+ -+ data.setMap(path, HelperBlockFlatteningV1450.getNBTForId(number.intValue() << 4).copy()); // copy to avoid problems with later state datafixers -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType children = data.getList("Children", ObjectType.MAP); -+ if (children == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = children.size(); i < len; ++i) { -+ final MapType child = children.getMap(i); -+ -+ final String id = child.getString("id"); -+ -+ switch (id) { -+ case "ViF": -+ convertToBlockState(child, "CA"); -+ convertToBlockState(child, "CB"); -+ break; -+ case "ViDF": -+ convertToBlockState(child, "CA"); -+ convertToBlockState(child, "CB"); -+ convertToBlockState(child, "CC"); -+ convertToBlockState(child, "CD"); -+ break; -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ // convert villagers to trade with pumpkins and not the carved pumpkin -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION, 7) { -+ private static void convertPumpkin(final MapType data, final String path) { -+ final MapType item = data.getMap(path); -+ if (item == null) { -+ return; -+ } -+ -+ final String id = item.getString("id"); -+ -+ if (id.equals("minecraft:carved_pumpkin")) { -+ item.setString("id", "minecraft:pumpkin"); -+ } -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType offers = data.getMap("Offers"); -+ if (offers != null) { -+ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); -+ if (recipes != null) { -+ for (int i = 0, len = recipes.size(); i < len; ++i) { -+ final MapType recipe = recipes.getMap(i); -+ -+ convertPumpkin(recipe, "buy"); -+ convertPumpkin(recipe, "buyB"); -+ convertPumpkin(recipe, "sell"); -+ } -+ } -+ } -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureWalker(VERSION, 7, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final ListType list = data.getList("Children", ObjectType.MAP); -+ if (list == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = list.size(); i < len; ++i) { -+ final MapType child = list.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CA", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CB", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CC", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CD", fromVersion, toVersion); -+ } -+ -+ return null; -+ }); -+ } -+ -+ private V1451() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8ca5b9d7292ba9c81f7f0fdfb6ca8fd17f796990 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java -@@ -0,0 +1,38 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1456 { -+ -+ protected static final int VERSION = MCVersions.V17W49B + 1; -+ -+ protected static byte direction2dTo3d(final byte old) { -+ switch (old) { -+ case 0: -+ return 3; -+ case 1: -+ return 4; -+ case 2: -+ default: -+ return 2; -+ case 3: -+ return 5; -+ } -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:item_frame", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.setByte("Facing", direction2dTo3d(data.getByte("Facing"))); -+ return null; -+ } -+ }); -+ } -+ -+ private V1456() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a7cc825c1e5d01ef3eb2fce0931230d936286cb0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java -@@ -0,0 +1,88 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1458 { -+ -+ protected static final int VERSION = MCVersions.V17W50A + 1; -+ -+ public static MapType updateCustomName(final MapType data) { -+ final String customName = data.getString("CustomName", ""); -+ -+ if (customName.isEmpty()) { -+ data.remove("CustomName"); -+ } else { -+ data.setString("CustomName", ComponentUtils.createPlainTextComponent(customName)); -+ } -+ -+ return null; -+ } -+ -+ public static void register() { -+ // From CB -+ MCTypeRegistry.PLAYER.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ return updateCustomName(data); -+ } -+ }); -+ -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if ("minecraft:commandblock_minecart".equals(data.getString("id"))) { -+ return null; -+ } -+ -+ return updateCustomName(data); -+ } -+ }); -+ -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final MapType display = tag.getMap("display"); -+ if (display == null) { -+ return null; -+ } -+ -+ final String name = display.getString("Name"); -+ if (name != null) { -+ display.setString("Name", ComponentUtils.createPlainTextComponent(name)); -+ } else { -+ final String localisedName = display.getString("LocName"); -+ if (localisedName != null) { -+ display.setString("Name", ComponentUtils.createTranslatableComponent(localisedName)); -+ display.remove("LocName"); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.TILE_ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if ("minecraft:command_block".equals(data.getString("id"))) { -+ return null; -+ } -+ -+ return updateCustomName(data); -+ } -+ }); -+ -+ } -+ -+ private V1458() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f68b561b2bb750d5f632f17e538337fa38108472 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java -@@ -0,0 +1,53 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+import net.minecraft.resources.ResourceLocation; -+import java.util.HashMap; -+import java.util.Locale; -+import java.util.Map; -+ -+public final class V1460 { -+ -+ private static final Map MOTIVE_REMAP = new HashMap<>(); -+ -+ static { -+ MOTIVE_REMAP.put("donkeykong", "donkey_kong"); -+ MOTIVE_REMAP.put("burningskull", "burning_skull"); -+ MOTIVE_REMAP.put("skullandroses", "skull_and_roses"); -+ }; -+ -+ protected static final int VERSION = MCVersions.V18W01A + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ private static void registerThrowableProjectile(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerBlockNames("inTile")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:painting", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ String motive = data.getString("Motive"); -+ if (motive != null) { -+ motive = motive.toLowerCase(Locale.ROOT); -+ data.setString("Motive", new ResourceLocation(MOTIVE_REMAP.getOrDefault(motive, motive)).toString()); -+ } -+ return null; -+ } -+ }); -+ -+ // No idea why so many type redefines exist here in Vanilla. nothing about the data structure changed, it's literally a copy of -+ // the existing types. -+ } -+ -+ private V1460() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c4fa8e36fb68a610106cee8bae1af243e51fae2e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java -@@ -0,0 +1,143 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V1466 { -+ -+ protected static final int VERSION = MCVersions.V18W06A; -+ -+ protected static short packOffsetCoordinates(final int x, final int y, final int z) { -+ return (short)((x & 15) | ((y & 15) << 4) | ((z & 15) << 8)); -+ } -+ -+ public static void register() { -+ // There is a rather critical change I've made to this converter: changing the chunk status determination. -+ // In Vanilla, this is determined by whether the terrain has been populated and whether the chunk is lit. -+ // For reference, here is the full status progression (at the time of 18w06a): -+ // empty -> base -> carved -> decorated -> lighted -> mobs_spawned -> finalized -> fullchunk -> postprocessed -+ // So one of those must be picked. -+ // If the chunk is lit and terrain is populated, the Vanilla converter will set the status to "mobs_spawned." -+ // If it is anything else, it will be "empty" -+ // I've changed it to the following: if terrain is populated, it is set to at least decorated. If it is populated -+ // and lit, it is set to "mobs_spawned" -+ // But what if it is not populated? If it is not populated, ignore the lit field - obviously that's just broken. -+ // It can't be lit and not populated. -+ // Let's take a look at chunk generation logic for a chunk that is not populated, or even near a populated chunk. -+ // It actually will generate a chunk up to the "carved" stage. It generates the base terrain, (i.e using noise -+ // to figure out where stone is, dirt, grass) and it will generate caves. Nothing else though. No populators. -+ // So "carved" is the correct stage to use, not empty. Setting it to empty would clobber chunk data, when we don't -+ // need to. If it is populated, at least set it to decorated. If it is lit and populated, set it to mobs_spawned. Else, -+ // it is carved. -+ // This change also fixes the random light check "bug" (really this is Mojang's fault for fucking up the status conversion here) -+ // caused by spigot, which would not set the lit value for some chunks. Now those chunks will not be regenerated. -+ -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ final boolean terrainPopulated = level.getBoolean("TerrainPopulated"); -+ final boolean lightPopulated = level.getBoolean("LightPopulated") || level.getNumber("LightPopulated") == null; -+ final String newStatus = !terrainPopulated ? "carved" : (lightPopulated ? "mobs_spawned" : "decorated"); -+ -+ level.setString("Status", newStatus); -+ level.setBoolean("hasLegacyStructureData", true); -+ -+ // convert biome byte[] into int[] -+ final byte[] biomes = level.getBytes("Biomes"); -+ if (biomes != null) { -+ final int[] newBiomes = new int[256]; -+ for (int i = 0, len = Math.min(newBiomes.length, biomes.length); i < len; ++i) { -+ newBiomes[i] = biomes[i] & 255; -+ } -+ level.setInts("Biomes", newBiomes); -+ } -+ -+ // ProtoChunks have their own dedicated tick list, so we must convert the TileTicks to that. -+ final ListType ticks = level.getList("TileTicks", ObjectType.MAP); -+ if (ticks != null) { -+ final ListType sections = Types.NBT.createEmptyList(); -+ final ListType[] sectionAccess = new ListType[16]; -+ for (int i = 0; i < sectionAccess.length; ++i) { -+ sections.addList(sectionAccess[i] = Types.NBT.createEmptyList()); -+ } -+ level.setList("ToBeTicked", sections); -+ -+ for (int i = 0, len = ticks.size(); i < len; ++i) { -+ final MapType tick = ticks.getMap(i); -+ -+ final int x = tick.getInt("x"); -+ final int y = tick.getInt("y"); -+ final int z = tick.getInt("z"); -+ final short coordinate = packOffsetCoordinates(x, y, z); -+ -+ sectionAccess[y >> 4].addShort(coordinate); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ -+ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); -+ -+ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); -+ if (tileTicks != null) { -+ for (int i = 0, len = tileTicks.size(); i < len; ++i) { -+ final MapType tileTick = tileTicks.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); -+ } -+ } -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section, "Palette", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, level.getMap("Structures"), "Starts", fromVersion, toVersion); -+ -+ return null; -+ }); -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final ListType list = data.getList("Children", ObjectType.MAP); -+ if (list != null) { -+ for (int i = 0, len = list.size(); i < len; ++i) { -+ final MapType child = list.getMap(i); -+ -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CA", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CB", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CC", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CD", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convert(MCTypeRegistry.BIOME, data, "biome", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+ -+ private V1466() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java -new file mode 100644 -index 0000000000000000000000000000000000000000..68dd3ce7709a998bc50a5080fe9c805b71a88365 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java -@@ -0,0 +1,27 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V147 { -+ -+ protected static final int VERSION = MCVersions.V15W46A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("ArmorStand", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getBoolean("Silent") && !data.getBoolean("Marker")) { -+ data.remove("Silent"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V147() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d29c79b44f619891ed07d7f13a63c960dfa985cd ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java -@@ -0,0 +1,33 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+ -+public final class V1470 { -+ -+ protected static final int VERSION = MCVersions.V18W08A; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:turtle"); -+ registerMob("minecraft:cod_mob"); -+ registerMob("minecraft:tropical_fish"); -+ registerMob("minecraft:salmon_mob"); -+ registerMob("minecraft:puffer_fish"); -+ registerMob("minecraft:phantom"); -+ registerMob("minecraft:dolphin"); -+ registerMob("minecraft:drowned"); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:trident", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:trident", new DataWalkerItems("Trident")); -+ } -+ -+ private V1470() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4cf1085b4392c9b348ebe65590cdbf287a908a38 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java -@@ -0,0 +1,36 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1474 { -+ -+ protected static final int VERSION = MCVersions.V18W10B; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getInt("Color") == 10) { -+ data.setByte("Color", (byte)16); -+ } -+ return null; -+ } -+ }); -+ // data hooks ensure the inputs are namespaced -+ ConverterAbstractBlockRename.register(VERSION, (final String old) -> { -+ return "minecraft:purple_shulker_box".equals(old) ? "minecraft:shulker_box" : null; -+ }); -+ ConverterAbstractItemRename.register(VERSION, (final String old) -> { -+ return "minecraft:purple_shulker_box".equals(old) ? "minecraft:shulker_box" : null; -+ }); -+ -+ } -+ -+ private V1474() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java -new file mode 100644 -index 0000000000000000000000000000000000000000..40b64efb8717b2de0ff13af87bcc99119c9f7c9d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V1475 { -+ -+ protected static final int VERSION = MCVersions.V18W10B + 1; -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, -+ ImmutableMap.of( -+ "minecraft:flowing_water", "minecraft:water", -+ "minecraft:flowing_lava", "minecraft:lava" -+ )::get); -+ } -+ -+ private V1475() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e4777730c6969d5a964daedce99c06a858615bc7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java -@@ -0,0 +1,45 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V1480 { -+ -+ protected static final int VERSION = MCVersions.V18W14A + 1; -+ -+ public static final Map RENAMED_IDS = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:blue_coral", "minecraft:tube_coral_block") -+ .put("minecraft:pink_coral", "minecraft:brain_coral_block") -+ .put("minecraft:purple_coral", "minecraft:bubble_coral_block") -+ .put("minecraft:red_coral", "minecraft:fire_coral_block") -+ .put("minecraft:yellow_coral", "minecraft:horn_coral_block") -+ .put("minecraft:blue_coral_plant", "minecraft:tube_coral") -+ .put("minecraft:pink_coral_plant", "minecraft:brain_coral") -+ .put("minecraft:purple_coral_plant", "minecraft:bubble_coral") -+ .put("minecraft:red_coral_plant", "minecraft:fire_coral") -+ .put("minecraft:yellow_coral_plant", "minecraft:horn_coral") -+ .put("minecraft:blue_coral_fan", "minecraft:tube_coral_fan") -+ .put("minecraft:pink_coral_fan", "minecraft:brain_coral_fan") -+ .put("minecraft:purple_coral_fan", "minecraft:bubble_coral_fan") -+ .put("minecraft:red_coral_fan", "minecraft:fire_coral_fan") -+ .put("minecraft:yellow_coral_fan", "minecraft:horn_coral_fan") -+ .put("minecraft:blue_dead_coral", "minecraft:dead_tube_coral") -+ .put("minecraft:pink_dead_coral", "minecraft:dead_brain_coral") -+ .put("minecraft:purple_dead_coral", "minecraft:dead_bubble_coral") -+ .put("minecraft:red_dead_coral", "minecraft:dead_fire_coral") -+ .put("minecraft:yellow_dead_coral", "minecraft:dead_horn_coral") -+ .build() -+ ); -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, RENAMED_IDS::get); -+ ConverterAbstractItemRename.register(VERSION, RENAMED_IDS::get); -+ } -+ -+ private V1480() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e6fb5d6870514a509f7f1aa5343ed7e762af8a72 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java -@@ -0,0 +1,31 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V1483 { -+ -+ protected static final int VERSION = MCVersions.V18W16A; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of( -+ "minecraft:puffer_fish", "minecraft:pufferfish" -+ )::get); -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:puffer_fish_spawn_egg", "minecraft:pufferfish_spawn_egg" -+ )::get); -+ -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:puffer_fish", "minecraft:pufferfish"); -+ } -+ -+ private V1483() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f5b9c166304930e095bfc00e8f6b93edb706df48 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java -@@ -0,0 +1,73 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V1484 { -+ -+ protected static final int VERSION = MCVersions.V18W19A; -+ -+ public static void register() { -+ final Map renamed = ImmutableMap.of( -+ "minecraft:sea_grass", "minecraft:seagrass", -+ "minecraft:tall_sea_grass", "minecraft:tall_seagrass" -+ ); -+ -+ ConverterAbstractItemRename.register(VERSION, renamed::get); -+ ConverterAbstractBlockRename.register(VERSION, renamed::get); -+ -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ -+ if (level == null) { -+ return null; -+ } -+ -+ final MapType heightmaps = level.getMap("Heightmaps"); -+ -+ if (heightmaps == null) { -+ return null; -+ } -+ -+ final Object liquid = heightmaps.getGeneric("LIQUID"); -+ if (liquid != null) { -+ heightmaps.remove("LIQUID"); -+ heightmaps.setGeneric("WORLD_SURFACE_WG", liquid); -+ } -+ -+ final Object solid = heightmaps.getGeneric("SOLID"); -+ if (solid != null) { -+ heightmaps.remove("SOLID"); -+ heightmaps.setGeneric("OCEAN_FLOOR_WG", solid); -+ heightmaps.setGeneric("OCEAN_FLOOR", solid); -+ } -+ -+ final Object light = heightmaps.getGeneric("LIGHT"); -+ if (light != null) { -+ heightmaps.remove("LIGHT"); -+ heightmaps.setGeneric("LIGHT_BLOCKING", light); -+ } -+ -+ final Object rain = heightmaps.getGeneric("RAIN"); -+ if (rain != null) { -+ heightmaps.remove("RAIN"); -+ heightmaps.setGeneric("MOTION_BLOCKING", rain); -+ heightmaps.setGeneric("MOTION_BLOCKING_NO_LEAVES", rain); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V1484() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e87551aa76c1434c2c81da828cd69a9bf32b5e04 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java -@@ -0,0 +1,44 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V1486 { -+ -+ protected static final int VERSION = MCVersions.V18W19B + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static final Map RENAMED_ENTITY_IDS = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:salmon_mob", "minecraft:salmon") -+ .put("minecraft:cod_mob", "minecraft:cod") -+ .build() -+ ); -+ public static final Map RENAMED_ITEM_IDS = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:salmon_mob_spawn_egg", "minecraft:salmon_spawn_egg") -+ .put("minecraft:cod_mob_spawn_egg", "minecraft:cod_spawn_egg") -+ .build() -+ ); -+ -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:cod_mob", "minecraft:cod"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:salmon_mob", "minecraft:salmon"); -+ -+ ConverterAbstractEntityRename.register(VERSION, RENAMED_ENTITY_IDS::get); -+ ConverterAbstractItemRename.register(VERSION, RENAMED_ITEM_IDS::get); -+ } -+ -+ private V1486() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dba4a64f61b27f1eb820e0e0a3fddb81517acc16 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java -@@ -0,0 +1,25 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V1487 { -+ -+ protected static final int VERSION = MCVersions.V18W19B + 2; -+ -+ public static void register() { -+ final Map remap = ImmutableMap.of( -+ "minecraft:prismarine_bricks_slab", "minecraft:prismarine_brick_slab", -+ "minecraft:prismarine_bricks_stairs", "minecraft:prismarine_brick_stairs" -+ ); -+ -+ ConverterAbstractItemRename.register(VERSION, remap::get); -+ ConverterAbstractBlockRename.register(VERSION, remap::get); -+ } -+ -+ private V1487() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cf3579524a9ba96f2065d98bca928bf920da081c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java -@@ -0,0 +1,88 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V1488 { -+ -+ protected static final int VERSION = MCVersions.V18W19B + 3; -+ -+ protected static boolean isIglooPiece(final MapType piece) { -+ return "Iglu".equals(piece.getString("id")); -+ } -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( -+ "minecraft:kelp_top", "minecraft:kelp", -+ "minecraft:kelp", "minecraft:kelp_plant" -+ )::get); -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:kelp_top", "minecraft:kelp" -+ )::get); -+ -+ // Don't ask me why in V1458 they wrote the converter to NOT do command blocks and THEN in THIS version -+ // to ONLY do command blocks. I don't know. -+ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:command_block", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ return V1458.updateCustomName(data); -+ } -+ }); -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:commandblock_minecart", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ return V1458.updateCustomName(data); -+ } -+ }); -+ -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType children = data.getList("Children", ObjectType.MAP); -+ boolean isIgloo; -+ if (children != null) { -+ isIgloo = true; -+ for (int i = 0, len = children.size(); i < len; ++i) { -+ if (!isIglooPiece(children.getMap(i))) { -+ isIgloo = false; -+ break; -+ } -+ } -+ } else { -+ isIgloo = false; -+ } -+ -+ if (isIgloo) { -+ data.remove("Children"); -+ data.setString("id", "Igloo"); -+ return null; -+ } -+ -+ if (children != null) { -+ for (int i = 0; i < children.size();) { -+ final MapType child = children.getMap(i); -+ if (isIglooPiece(child)) { -+ children.remove(i); -+ continue; -+ } -+ ++i; -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V1488() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cb3afa0634cb47cbd5b324a66d140375b1c23e07 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java -@@ -0,0 +1,25 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V1490 { -+ -+ protected static final int VERSION = MCVersions.V18W20A + 1; -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( -+ "minecraft:melon_block", "minecraft:melon" -+ )::get); -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:melon_block", "minecraft:melon", -+ "minecraft:melon", "minecraft:melon_slice", -+ "minecraft:speckled_melon", "minecraft:glistering_melon_slice" -+ )::get); -+ } -+ -+ private V1490() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b8732c2035ee0659173a8299cc2b0a5f86ace7b0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java -@@ -0,0 +1,152 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.google.common.collect.ImmutableMap; -+import com.mojang.datafixers.util.Pair; -+ -+public final class V1492 { -+ -+ private static final ImmutableMap>> RENAMES = ImmutableMap.>>builder() -+ .put("EndCity", Pair.of( -+ "ECP", -+ ImmutableMap.builder() -+ .put("second_floor", "second_floor_1") -+ .put("third_floor", "third_floor_1") -+ .put("third_floor_c", "third_floor_2") -+ .build() -+ ) -+ ) -+ -+ .put("Mansion", Pair.of( -+ "WMP", -+ ImmutableMap.builder() -+ .put("carpet_south", "carpet_south_1") -+ .put("carpet_west", "carpet_west_1") -+ .put("indoors_door", "indoors_door_1") -+ .put("indoors_wall", "indoors_wall_1") -+ .build() -+ ) -+ ) -+ -+ .put("Igloo", Pair.of( -+ "Iglu", -+ ImmutableMap.builder() -+ .put("minecraft:igloo/igloo_bottom", "minecraft:igloo/bottom") -+ .put("minecraft:igloo/igloo_middle", "minecraft:igloo/middle") -+ .put("minecraft:igloo/igloo_top", "minecraft:igloo/top") -+ .build() -+ ) -+ ) -+ .put("Ocean_Ruin", Pair.of( -+ "ORP", -+ ImmutableMap.builder() -+ .put("minecraft:ruin/big_ruin1_brick", "minecraft:underwater_ruin/big_brick_1") -+ .put("minecraft:ruin/big_ruin2_brick", "minecraft:underwater_ruin/big_brick_2") -+ .put("minecraft:ruin/big_ruin3_brick", "minecraft:underwater_ruin/big_brick_3") -+ .put("minecraft:ruin/big_ruin8_brick", "minecraft:underwater_ruin/big_brick_8") -+ .put("minecraft:ruin/big_ruin1_cracked", "minecraft:underwater_ruin/big_cracked_1") -+ .put("minecraft:ruin/big_ruin2_cracked", "minecraft:underwater_ruin/big_cracked_2") -+ .put("minecraft:ruin/big_ruin3_cracked", "minecraft:underwater_ruin/big_cracked_3") -+ .put("minecraft:ruin/big_ruin8_cracked", "minecraft:underwater_ruin/big_cracked_8") -+ .put("minecraft:ruin/big_ruin1_mossy", "minecraft:underwater_ruin/big_mossy_1") -+ .put("minecraft:ruin/big_ruin2_mossy", "minecraft:underwater_ruin/big_mossy_2") -+ .put("minecraft:ruin/big_ruin3_mossy", "minecraft:underwater_ruin/big_mossy_3") -+ .put("minecraft:ruin/big_ruin8_mossy", "minecraft:underwater_ruin/big_mossy_8") -+ .put("minecraft:ruin/big_ruin_warm4", "minecraft:underwater_ruin/big_warm_4") -+ .put("minecraft:ruin/big_ruin_warm5", "minecraft:underwater_ruin/big_warm_5") -+ .put("minecraft:ruin/big_ruin_warm6", "minecraft:underwater_ruin/big_warm_6") -+ .put("minecraft:ruin/big_ruin_warm7", "minecraft:underwater_ruin/big_warm_7") -+ .put("minecraft:ruin/ruin1_brick", "minecraft:underwater_ruin/brick_1") -+ .put("minecraft:ruin/ruin2_brick", "minecraft:underwater_ruin/brick_2") -+ .put("minecraft:ruin/ruin3_brick", "minecraft:underwater_ruin/brick_3") -+ .put("minecraft:ruin/ruin4_brick", "minecraft:underwater_ruin/brick_4") -+ .put("minecraft:ruin/ruin5_brick", "minecraft:underwater_ruin/brick_5") -+ .put("minecraft:ruin/ruin6_brick", "minecraft:underwater_ruin/brick_6") -+ .put("minecraft:ruin/ruin7_brick", "minecraft:underwater_ruin/brick_7") -+ .put("minecraft:ruin/ruin8_brick", "minecraft:underwater_ruin/brick_8") -+ .put("minecraft:ruin/ruin1_cracked", "minecraft:underwater_ruin/cracked_1") -+ .put("minecraft:ruin/ruin2_cracked", "minecraft:underwater_ruin/cracked_2") -+ .put("minecraft:ruin/ruin3_cracked", "minecraft:underwater_ruin/cracked_3") -+ .put("minecraft:ruin/ruin4_cracked", "minecraft:underwater_ruin/cracked_4") -+ .put("minecraft:ruin/ruin5_cracked", "minecraft:underwater_ruin/cracked_5") -+ .put("minecraft:ruin/ruin6_cracked", "minecraft:underwater_ruin/cracked_6") -+ .put("minecraft:ruin/ruin7_cracked", "minecraft:underwater_ruin/cracked_7") -+ .put("minecraft:ruin/ruin8_cracked", "minecraft:underwater_ruin/cracked_8") -+ .put("minecraft:ruin/ruin1_mossy", "minecraft:underwater_ruin/mossy_1") -+ .put("minecraft:ruin/ruin2_mossy", "minecraft:underwater_ruin/mossy_2") -+ .put("minecraft:ruin/ruin3_mossy", "minecraft:underwater_ruin/mossy_3") -+ .put("minecraft:ruin/ruin4_mossy", "minecraft:underwater_ruin/mossy_4") -+ .put("minecraft:ruin/ruin5_mossy", "minecraft:underwater_ruin/mossy_5") -+ .put("minecraft:ruin/ruin6_mossy", "minecraft:underwater_ruin/mossy_6") -+ .put("minecraft:ruin/ruin7_mossy", "minecraft:underwater_ruin/mossy_7") -+ .put("minecraft:ruin/ruin8_mossy", "minecraft:underwater_ruin/mossy_8") -+ .put("minecraft:ruin/ruin_warm1", "minecraft:underwater_ruin/warm_1") -+ .put("minecraft:ruin/ruin_warm2", "minecraft:underwater_ruin/warm_2") -+ .put("minecraft:ruin/ruin_warm3", "minecraft:underwater_ruin/warm_3") -+ .put("minecraft:ruin/ruin_warm4", "minecraft:underwater_ruin/warm_4") -+ .put("minecraft:ruin/ruin_warm5", "minecraft:underwater_ruin/warm_5") -+ .put("minecraft:ruin/ruin_warm6", "minecraft:underwater_ruin/warm_6") -+ .put("minecraft:ruin/ruin_warm7", "minecraft:underwater_ruin/warm_7") -+ .put("minecraft:ruin/ruin_warm8", "minecraft:underwater_ruin/warm_8") -+ .put("minecraft:ruin/big_brick_1", "minecraft:underwater_ruin/big_brick_1") -+ .put("minecraft:ruin/big_brick_2", "minecraft:underwater_ruin/big_brick_2") -+ .put("minecraft:ruin/big_brick_3", "minecraft:underwater_ruin/big_brick_3") -+ .put("minecraft:ruin/big_brick_8", "minecraft:underwater_ruin/big_brick_8") -+ .put("minecraft:ruin/big_mossy_1", "minecraft:underwater_ruin/big_mossy_1") -+ .put("minecraft:ruin/big_mossy_2", "minecraft:underwater_ruin/big_mossy_2") -+ .put("minecraft:ruin/big_mossy_3", "minecraft:underwater_ruin/big_mossy_3") -+ .put("minecraft:ruin/big_mossy_8", "minecraft:underwater_ruin/big_mossy_8") -+ .put("minecraft:ruin/big_cracked_1", "minecraft:underwater_ruin/big_cracked_1") -+ .put("minecraft:ruin/big_cracked_2", "minecraft:underwater_ruin/big_cracked_2") -+ .put("minecraft:ruin/big_cracked_3", "minecraft:underwater_ruin/big_cracked_3") -+ .put("minecraft:ruin/big_cracked_8", "minecraft:underwater_ruin/big_cracked_8") -+ .put("minecraft:ruin/big_warm_4", "minecraft:underwater_ruin/big_warm_4") -+ .put("minecraft:ruin/big_warm_5", "minecraft:underwater_ruin/big_warm_5") -+ .put("minecraft:ruin/big_warm_6", "minecraft:underwater_ruin/big_warm_6") -+ .put("minecraft:ruin/big_warm_7", "minecraft:underwater_ruin/big_warm_7") -+ .build() -+ ) -+ ) -+ -+ .build(); -+ -+ protected static final int VERSION = MCVersions.V18W20B + 1; -+ -+ public static void register() { -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType children = data.getList("Children", ObjectType.MAP); -+ if (children == null) { -+ return null; -+ } -+ -+ final String id = data.getString("id"); -+ -+ final Pair> renames = RENAMES.get(id); -+ if (renames == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = children.size(); i < len; ++i) { -+ final MapType child = children.getMap(i); -+ -+ if (renames.getFirst().equals(child.getString("id"))) { -+ final String template = child.getString("Template", ""); -+ child.setString("Template", renames.getSecond().getOrDefault(template, template)); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V1492() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java -new file mode 100644 -index 0000000000000000000000000000000000000000..50411042a83d58c4c36768a8f5196b4b41b4d095 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java -@@ -0,0 +1,89 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+ -+public final class V1494 { -+ -+ protected static final int VERSION = MCVersions.V18W20C + 1; -+ -+ private static final Int2ObjectOpenHashMap ENCH_ID_TO_NAME = new Int2ObjectOpenHashMap<>(); -+ static { -+ ENCH_ID_TO_NAME.put(0, "minecraft:protection"); -+ ENCH_ID_TO_NAME.put(1, "minecraft:fire_protection"); -+ ENCH_ID_TO_NAME.put(2, "minecraft:feather_falling"); -+ ENCH_ID_TO_NAME.put(3, "minecraft:blast_protection"); -+ ENCH_ID_TO_NAME.put(4, "minecraft:projectile_protection"); -+ ENCH_ID_TO_NAME.put(5, "minecraft:respiration"); -+ ENCH_ID_TO_NAME.put(6, "minecraft:aqua_affinity"); -+ ENCH_ID_TO_NAME.put(7, "minecraft:thorns"); -+ ENCH_ID_TO_NAME.put(8, "minecraft:depth_strider"); -+ ENCH_ID_TO_NAME.put(9, "minecraft:frost_walker"); -+ ENCH_ID_TO_NAME.put(10, "minecraft:binding_curse"); -+ ENCH_ID_TO_NAME.put(16, "minecraft:sharpness"); -+ ENCH_ID_TO_NAME.put(17, "minecraft:smite"); -+ ENCH_ID_TO_NAME.put(18, "minecraft:bane_of_arthropods"); -+ ENCH_ID_TO_NAME.put(19, "minecraft:knockback"); -+ ENCH_ID_TO_NAME.put(20, "minecraft:fire_aspect"); -+ ENCH_ID_TO_NAME.put(21, "minecraft:looting"); -+ ENCH_ID_TO_NAME.put(22, "minecraft:sweeping"); -+ ENCH_ID_TO_NAME.put(32, "minecraft:efficiency"); -+ ENCH_ID_TO_NAME.put(33, "minecraft:silk_touch"); -+ ENCH_ID_TO_NAME.put(34, "minecraft:unbreaking"); -+ ENCH_ID_TO_NAME.put(35, "minecraft:fortune"); -+ ENCH_ID_TO_NAME.put(48, "minecraft:power"); -+ ENCH_ID_TO_NAME.put(49, "minecraft:punch"); -+ ENCH_ID_TO_NAME.put(50, "minecraft:flame"); -+ ENCH_ID_TO_NAME.put(51, "minecraft:infinity"); -+ ENCH_ID_TO_NAME.put(61, "minecraft:luck_of_the_sea"); -+ ENCH_ID_TO_NAME.put(62, "minecraft:lure"); -+ ENCH_ID_TO_NAME.put(65, "minecraft:loyalty"); -+ ENCH_ID_TO_NAME.put(66, "minecraft:impaling"); -+ ENCH_ID_TO_NAME.put(67, "minecraft:riptide"); -+ ENCH_ID_TO_NAME.put(68, "minecraft:channeling"); -+ ENCH_ID_TO_NAME.put(70, "minecraft:mending"); -+ ENCH_ID_TO_NAME.put(71, "minecraft:vanishing_curse"); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final ListType enchants = tag.getList("ench", ObjectType.MAP); -+ if (enchants != null) { -+ tag.remove("ench"); -+ tag.setList("Enchantments", enchants); -+ -+ for (int i = 0, len = enchants.size(); i < len; ++i) { -+ final MapType enchant = enchants.getMap(i); -+ enchant.setString("id", ENCH_ID_TO_NAME.getOrDefault(enchant.getInt("id"), "null")); -+ } -+ } -+ -+ final ListType storedEnchants = tag.getList("StoredEnchantments", ObjectType.MAP); -+ if (storedEnchants != null) { -+ for (int i = 0, len = storedEnchants.size(); i < len; ++i) { -+ final MapType enchant = storedEnchants.getMap(i); -+ enchant.setString("id", ENCH_ID_TO_NAME.getOrDefault(enchant.getInt("id"), "null")); -+ } -+ } -+ -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V1494() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c56d50c552d4609474f5b3b6b0b8be8b575764ea ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java -@@ -0,0 +1,370 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.mojang.datafixers.DataFixUtils; -+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -+import it.unimi.dsi.fastutil.ints.IntIterator; -+import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -+import net.minecraft.util.datafix.PackedBitStorage; -+import java.util.Arrays; -+import java.util.HashSet; -+import java.util.Set; -+ -+public final class V1496 { -+ -+ private V1496() {} -+ -+ protected static final int VERSION = MCVersions.V18W21B; -+ -+ private static final int[][] DIRECTIONS = new int[][] { -+ new int[] {-1, 0, 0}, -+ new int[] {1, 0, 0}, -+ new int[] {0, -1, 0}, -+ new int[] {0, 1, 0}, -+ new int[] {0, 0, -1}, -+ new int[] {0, 0, 1} -+ }; -+ -+ private static final Object2IntOpenHashMap LEAVES_TO_ID = new Object2IntOpenHashMap<>(); -+ static { -+ LEAVES_TO_ID.put("minecraft:acacia_leaves", 0); -+ LEAVES_TO_ID.put("minecraft:birch_leaves", 1); -+ LEAVES_TO_ID.put("minecraft:dark_oak_leaves", 2); -+ LEAVES_TO_ID.put("minecraft:jungle_leaves", 3); -+ LEAVES_TO_ID.put("minecraft:oak_leaves", 4); -+ LEAVES_TO_ID.put("minecraft:spruce_leaves", 5); -+ } -+ -+ private static final Set LOGS = new HashSet<>( -+ Arrays.asList( -+ "minecraft:acacia_bark", -+ "minecraft:birch_bark", -+ "minecraft:dark_oak_bark", -+ "minecraft:jungle_bark", -+ "minecraft:oak_bark", -+ "minecraft:spruce_bark", -+ "minecraft:acacia_log", -+ "minecraft:birch_log", -+ "minecraft:dark_oak_log", -+ "minecraft:jungle_log", -+ "minecraft:oak_log", -+ "minecraft:spruce_log", -+ "minecraft:stripped_acacia_log", -+ "minecraft:stripped_birch_log", -+ "minecraft:stripped_dark_oak_log", -+ "minecraft:stripped_jungle_log", -+ "minecraft:stripped_oak_log", -+ "minecraft:stripped_spruce_log" -+ ) -+ ); -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ final ListType sectionsNBT = level.getList("Sections", ObjectType.MAP); -+ if (sectionsNBT == null) { -+ return null; -+ } -+ -+ int newSides = 0; -+ -+ final LeavesSection[] sections = new LeavesSection[16]; -+ boolean skippable = true; -+ for (int i = 0, len = sectionsNBT.size(); i < len; ++i) { -+ final LeavesSection section = new LeavesSection(sectionsNBT.getMap(i)); -+ sections[section.sectionY] = section; -+ -+ skippable &= section.isSkippable(); -+ } -+ -+ if (skippable) { -+ return null; -+ } -+ -+ final IntOpenHashSet[] positionsByDistance = new IntOpenHashSet[7]; -+ for (int i = 0; i < positionsByDistance.length; ++i) { -+ positionsByDistance[i] = new IntOpenHashSet(); -+ } -+ -+ for (final LeavesSection section : sections) { -+ if (section == null || section.isSkippable()) { -+ continue; -+ } -+ -+ for (int index = 0; index < 4096; ++index) { -+ final int block = section.getBlock(index); -+ if (section.isLog(block)) { -+ positionsByDistance[0].add(section.getSectionY() << 12 | index); -+ } else if (section.isLeaf(block)) { -+ int x = getX(index); -+ int z = getZ(index); -+ newSides |= getSideMask(x == 0, x == 15, z == 0, z == 15); -+ } -+ } -+ } -+ -+ // this is basically supposed to recalculate the distances, because a higher cap was added -+ for (int distance = 1; distance < 7; ++distance) { -+ final IntOpenHashSet positionsLess = positionsByDistance[distance - 1]; -+ final IntOpenHashSet positionsEqual = positionsByDistance[distance]; -+ -+ for (final IntIterator iterator = positionsLess.iterator(); iterator.hasNext();) { -+ final int position = iterator.nextInt(); -+ final int fromX = getX(position); -+ final int fromY = getY(position); -+ final int fromZ = getZ(position); -+ -+ for (final int[] direction : DIRECTIONS) { -+ final int toX = fromX + direction[0]; -+ final int toY = fromY + direction[1]; -+ final int toZ = fromZ + direction[2]; -+ -+ if (!(toX >= 0 && toX <= 15 && toZ >= 0 && toZ <= 15 && toY >= 0 && toY <= 255)) { -+ continue; -+ } -+ -+ final LeavesSection toSection = sections[toY >> 4]; -+ if (toSection == null || toSection.isSkippable()) { -+ continue; -+ } -+ -+ final int sectionLocalIndex = getIndex(toX, toY & 15, toZ); -+ final int toBlock = toSection.getBlock(sectionLocalIndex); -+ -+ if (toSection.isLeaf(toBlock)) { -+ final int newDistance = toSection.getDistance(toBlock); -+ if (newDistance > distance) { -+ toSection.setDistance(sectionLocalIndex, toBlock, distance); -+ positionsEqual.add(getIndex(toX, toY, toZ)); -+ } -+ } -+ } -+ } -+ } -+ -+ // done updating blocks, now just update the blockstates and palette -+ for (int i = 0, len = sectionsNBT.size(); i < len; ++i) { -+ final MapType sectionNBT = sectionsNBT.getMap(i); -+ final int y = sectionNBT.getInt("Y"); -+ final LeavesSection section = sections[y]; -+ -+ section.writeInto(sectionNBT); -+ } -+ -+ // if sides changed during process, update it now -+ if (newSides != 0) { -+ MapType upgradeData = level.getMap("UpgradeData"); -+ if (upgradeData == null) { -+ level.setMap("UpgradeData", upgradeData = Types.NBT.createEmptyMap()); -+ } -+ -+ upgradeData.setByte("Sides", (byte)(upgradeData.getByte("Sides") | newSides)); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ public static int getIndex(final int x, final int y, final int z) { -+ return y << 8 | z << 4 | x; -+ } -+ -+ public static int getX(final int index) { -+ return index & 15; -+ } -+ -+ public static int getY(final int index) { -+ return index >> 8 & 255; -+ } -+ -+ public static int getZ(final int index) { -+ return index >> 4 & 15; -+ } -+ -+ public static int getSideMask(final boolean noLeft, final boolean noRight, final boolean noBack, final boolean noForward) { -+ final int ret; -+ -+ if (noBack) { -+ if (noRight) { -+ ret = 2; -+ } else if (noLeft) { -+ ret = 128; -+ } else { -+ ret = 1; -+ } -+ } else if (noForward) { -+ if (noLeft) { -+ ret = 32; -+ } else if (noRight) { -+ ret = 8; -+ } else { -+ ret = 16; -+ } -+ } else if (noRight) { -+ ret = 4; -+ } else if (noLeft) { -+ ret = 64; -+ } else { -+ ret = 0; -+ } -+ -+ return ret; -+ } -+ -+ public abstract static class Section { -+ protected final ListType palette; -+ protected final int sectionY; -+ protected PackedBitStorage storage; -+ -+ public Section(final MapType section) { -+ this.palette = section.getList("Palette", ObjectType.MAP); -+ this.sectionY = section.getInt("Y"); -+ this.readStorage(section); -+ } -+ -+ protected void readStorage(final MapType section) { -+ if (this.initSkippable()) { -+ this.storage = null; -+ } else { -+ final long[] states = section.getLongs("BlockStates"); -+ final int bits = Math.max(4, DataFixUtils.ceillog2(this.palette.size())); -+ this.storage = new PackedBitStorage(bits, 4096, states); -+ } -+ } -+ -+ public void writeInto(final MapType section) { -+ if (this.isSkippable()) { -+ return; -+ } -+ -+ section.setList("Palette", this.palette); -+ section.setLongs("BlockStates", this.storage.getRaw()); -+ } -+ -+ public boolean isSkippable() { -+ return this.storage == null; -+ } -+ -+ public int getBlock(final int index) { -+ return this.storage.get(index); -+ } -+ -+ protected int getStateId(final String name, final boolean persistent, final int distance) { -+ return LEAVES_TO_ID.getInt(name) << 5 | (persistent ? 16 : 0) | distance; -+ } -+ -+ protected int getSectionY() { -+ return this.sectionY; -+ } -+ -+ protected abstract boolean initSkippable(); -+ } -+ -+ public static final class LeavesSection extends Section { -+ private IntOpenHashSet leaveIds; -+ private IntOpenHashSet logIds; -+ private Int2IntOpenHashMap stateToIdMap; -+ -+ public LeavesSection(final MapType section) { -+ super(section); -+ } -+ -+ @Override -+ protected boolean initSkippable() { -+ this.leaveIds = new IntOpenHashSet(); -+ this.logIds = new IntOpenHashSet(); -+ this.stateToIdMap = new Int2IntOpenHashMap(); -+ this.stateToIdMap.defaultReturnValue(-1); -+ -+ for(int i = 0; i < this.palette.size(); ++i) { -+ final MapType blockState = this.palette.getMap(i); -+ final String name = blockState.getString("Name", ""); -+ if (LEAVES_TO_ID.containsKey(name)) { -+ final MapType properties = blockState.getMap("Properties"); -+ final boolean notDecayable = properties != null && "false".equals(properties.getString("decayable")); -+ -+ this.leaveIds.add(i); -+ this.stateToIdMap.put(this.getStateId(name, notDecayable, 7), i); -+ this.palette.setMap(i, this.makeNewLeafTag(name, notDecayable, 7)); -+ } -+ -+ if (LOGS.contains(name)) { -+ this.logIds.add(i); -+ } -+ } -+ -+ return this.leaveIds.isEmpty() && this.logIds.isEmpty(); -+ } -+ -+ private MapType makeNewLeafTag(final String name, final boolean notDecayable, final int distance) { -+ final MapType properties = Types.NBT.createEmptyMap(); -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setString("Name", name); -+ ret.setMap("Properties", properties); -+ -+ properties.setString("persistent", Boolean.toString(notDecayable)); -+ properties.setString("distance", Integer.toString(distance)); -+ -+ return ret; -+ } -+ -+ public boolean isLog(final int id) { -+ return this.logIds.contains(id); -+ } -+ -+ public boolean isLeaf(final int id) { -+ return this.leaveIds.contains(id); -+ } -+ -+ // only call for logs or leaves, will throw otherwise! -+ private int getDistance(final int id) { -+ if (this.isLog(id)) { -+ return 0; -+ } -+ -+ return Integer.parseInt(this.palette.getMap(id).getMap("Properties").getString("distance")); -+ } -+ -+ private void setDistance(final int index, final int id, final int distance) { -+ final MapType state = this.palette.getMap(id); -+ final String name = state.getString("Name"); -+ final boolean persistent = "true".equals(state.getMap("Properties").getString("persistent")); -+ final int newState = this.getStateId(name, persistent, distance); -+ int newStateId; -+ if ((newStateId = this.stateToIdMap.get(newState)) == -1) { -+ newStateId = this.palette.size(); -+ this.leaveIds.add(newStateId); -+ this.stateToIdMap.put(newState, newStateId); -+ this.palette.addMap(this.makeNewLeafTag(name, persistent, distance)); -+ } -+ -+ if (1 << this.storage.getBits() <= newStateId) { -+ // need to widen storage -+ final PackedBitStorage newStorage = new PackedBitStorage(this.storage.getBits() + 1, 4096); -+ -+ for(int i = 0; i < 4096; ++i) { -+ newStorage.set(i, this.storage.get(i)); -+ } -+ -+ this.storage = newStorage; -+ } -+ -+ this.storage.set(index, newStateId); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9208152e2a158470f37b0eb022478e8e5287c12b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java -@@ -0,0 +1,24 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1500 { -+ -+ protected static final int VERSION = MCVersions.V18W22C + 1; -+ -+ private V1500() {} -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("DUMMY", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.setBoolean("keepPacked", true); -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3a1bf6d864e10273c87209bbcdbdb889aec1103d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java -@@ -0,0 +1,78 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V1501 { -+ -+ protected static final int VERSION = MCVersions.V1_13_PRE1; -+ -+ private static final Map RENAMES = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:recipes/brewing/speckled_melon", "minecraft:recipes/brewing/glistering_melon_slice") -+ .put("minecraft:recipes/building_blocks/black_stained_hardened_clay", "minecraft:recipes/building_blocks/black_terracotta") -+ .put("minecraft:recipes/building_blocks/blue_stained_hardened_clay", "minecraft:recipes/building_blocks/blue_terracotta") -+ .put("minecraft:recipes/building_blocks/brown_stained_hardened_clay", "minecraft:recipes/building_blocks/brown_terracotta") -+ .put("minecraft:recipes/building_blocks/cyan_stained_hardened_clay", "minecraft:recipes/building_blocks/cyan_terracotta") -+ .put("minecraft:recipes/building_blocks/gray_stained_hardened_clay", "minecraft:recipes/building_blocks/gray_terracotta") -+ .put("minecraft:recipes/building_blocks/green_stained_hardened_clay", "minecraft:recipes/building_blocks/green_terracotta") -+ .put("minecraft:recipes/building_blocks/light_blue_stained_hardened_clay", "minecraft:recipes/building_blocks/light_blue_terracotta") -+ .put("minecraft:recipes/building_blocks/light_gray_stained_hardened_clay", "minecraft:recipes/building_blocks/light_gray_terracotta") -+ .put("minecraft:recipes/building_blocks/lime_stained_hardened_clay", "minecraft:recipes/building_blocks/lime_terracotta") -+ .put("minecraft:recipes/building_blocks/magenta_stained_hardened_clay", "minecraft:recipes/building_blocks/magenta_terracotta") -+ .put("minecraft:recipes/building_blocks/orange_stained_hardened_clay", "minecraft:recipes/building_blocks/orange_terracotta") -+ .put("minecraft:recipes/building_blocks/pink_stained_hardened_clay", "minecraft:recipes/building_blocks/pink_terracotta") -+ .put("minecraft:recipes/building_blocks/purple_stained_hardened_clay", "minecraft:recipes/building_blocks/purple_terracotta") -+ .put("minecraft:recipes/building_blocks/red_stained_hardened_clay", "minecraft:recipes/building_blocks/red_terracotta") -+ .put("minecraft:recipes/building_blocks/white_stained_hardened_clay", "minecraft:recipes/building_blocks/white_terracotta") -+ .put("minecraft:recipes/building_blocks/yellow_stained_hardened_clay", "minecraft:recipes/building_blocks/yellow_terracotta") -+ .put("minecraft:recipes/building_blocks/acacia_wooden_slab", "minecraft:recipes/building_blocks/acacia_slab") -+ .put("minecraft:recipes/building_blocks/birch_wooden_slab", "minecraft:recipes/building_blocks/birch_slab") -+ .put("minecraft:recipes/building_blocks/dark_oak_wooden_slab", "minecraft:recipes/building_blocks/dark_oak_slab") -+ .put("minecraft:recipes/building_blocks/jungle_wooden_slab", "minecraft:recipes/building_blocks/jungle_slab") -+ .put("minecraft:recipes/building_blocks/oak_wooden_slab", "minecraft:recipes/building_blocks/oak_slab") -+ .put("minecraft:recipes/building_blocks/spruce_wooden_slab", "minecraft:recipes/building_blocks/spruce_slab") -+ .put("minecraft:recipes/building_blocks/brick_block", "minecraft:recipes/building_blocks/bricks") -+ .put("minecraft:recipes/building_blocks/chiseled_stonebrick", "minecraft:recipes/building_blocks/chiseled_stone_bricks") -+ .put("minecraft:recipes/building_blocks/end_bricks", "minecraft:recipes/building_blocks/end_stone_bricks") -+ .put("minecraft:recipes/building_blocks/lit_pumpkin", "minecraft:recipes/building_blocks/jack_o_lantern") -+ .put("minecraft:recipes/building_blocks/magma", "minecraft:recipes/building_blocks/magma_block") -+ .put("minecraft:recipes/building_blocks/melon_block", "minecraft:recipes/building_blocks/melon") -+ .put("minecraft:recipes/building_blocks/mossy_stonebrick", "minecraft:recipes/building_blocks/mossy_stone_bricks") -+ .put("minecraft:recipes/building_blocks/nether_brick", "minecraft:recipes/building_blocks/nether_bricks") -+ .put("minecraft:recipes/building_blocks/pillar_quartz_block", "minecraft:recipes/building_blocks/quartz_pillar") -+ .put("minecraft:recipes/building_blocks/red_nether_brick", "minecraft:recipes/building_blocks/red_nether_bricks") -+ .put("minecraft:recipes/building_blocks/snow", "minecraft:recipes/building_blocks/snow_block") -+ .put("minecraft:recipes/building_blocks/smooth_red_sandstone", "minecraft:recipes/building_blocks/cut_red_sandstone") -+ .put("minecraft:recipes/building_blocks/smooth_sandstone", "minecraft:recipes/building_blocks/cut_sandstone") -+ .put("minecraft:recipes/building_blocks/stonebrick", "minecraft:recipes/building_blocks/stone_bricks") -+ .put("minecraft:recipes/building_blocks/stone_stairs", "minecraft:recipes/building_blocks/cobblestone_stairs") -+ .put("minecraft:recipes/building_blocks/string_to_wool", "minecraft:recipes/building_blocks/white_wool_from_string") -+ .put("minecraft:recipes/decorations/fence", "minecraft:recipes/decorations/oak_fence") -+ .put("minecraft:recipes/decorations/purple_shulker_box", "minecraft:recipes/decorations/shulker_box") -+ .put("minecraft:recipes/decorations/slime", "minecraft:recipes/decorations/slime_block") -+ .put("minecraft:recipes/decorations/snow_layer", "minecraft:recipes/decorations/snow") -+ .put("minecraft:recipes/misc/bone_meal_from_block", "minecraft:recipes/misc/bone_meal_from_bone_block") -+ .put("minecraft:recipes/misc/bone_meal_from_bone", "minecraft:recipes/misc/bone_meal") -+ .put("minecraft:recipes/misc/gold_ingot_from_block", "minecraft:recipes/misc/gold_ingot_from_gold_block") -+ .put("minecraft:recipes/misc/iron_ingot_from_block", "minecraft:recipes/misc/iron_ingot_from_iron_block") -+ .put("minecraft:recipes/redstone/fence_gate", "minecraft:recipes/redstone/oak_fence_gate") -+ .put("minecraft:recipes/redstone/noteblock", "minecraft:recipes/redstone/note_block") -+ .put("minecraft:recipes/redstone/trapdoor", "minecraft:recipes/redstone/oak_trapdoor") -+ .put("minecraft:recipes/redstone/wooden_button", "minecraft:recipes/redstone/oak_button") -+ .put("minecraft:recipes/redstone/wooden_door", "minecraft:recipes/redstone/oak_door") -+ .put("minecraft:recipes/redstone/wooden_pressure_plate", "minecraft:recipes/redstone/oak_pressure_plate") -+ .put("minecraft:recipes/transportation/boat", "minecraft:recipes/transportation/oak_boat") -+ .put("minecraft:recipes/transportation/golden_rail", "minecraft:recipes/transportation/powered_rail") -+ .build() -+ ); -+ -+ private V1501() {} -+ -+ public static void register() { -+ ConverterAbstractAdvancementsRename.register(VERSION, RENAMES::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java -new file mode 100644 -index 0000000000000000000000000000000000000000..514bb43a219f4d731e7d51804fa2595b9c56da96 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java -@@ -0,0 +1,77 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.recipe.ConverterAbstractRecipeRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V1502 { -+ -+ protected static final int VERSION = MCVersions.V1_13_PRE2; -+ -+ private static final Map RECIPES_UPDATES = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:acacia_wooden_slab", "minecraft:acacia_slab") -+ .put("minecraft:birch_wooden_slab", "minecraft:birch_slab") -+ .put("minecraft:black_stained_hardened_clay", "minecraft:black_terracotta") -+ .put("minecraft:blue_stained_hardened_clay", "minecraft:blue_terracotta") -+ .put("minecraft:boat", "minecraft:oak_boat") -+ .put("minecraft:bone_meal_from_block", "minecraft:bone_meal_from_bone_block") -+ .put("minecraft:bone_meal_from_bone", "minecraft:bone_meal") -+ .put("minecraft:brick_block", "minecraft:bricks") -+ .put("minecraft:brown_stained_hardened_clay", "minecraft:brown_terracotta") -+ .put("minecraft:chiseled_stonebrick", "minecraft:chiseled_stone_bricks") -+ .put("minecraft:cyan_stained_hardened_clay", "minecraft:cyan_terracotta") -+ .put("minecraft:dark_oak_wooden_slab", "minecraft:dark_oak_slab") -+ .put("minecraft:end_bricks", "minecraft:end_stone_bricks") -+ .put("minecraft:fence_gate", "minecraft:oak_fence_gate") -+ .put("minecraft:fence", "minecraft:oak_fence") -+ .put("minecraft:golden_rail", "minecraft:powered_rail") -+ .put("minecraft:gold_ingot_from_block", "minecraft:gold_ingot_from_gold_block") -+ .put("minecraft:gray_stained_hardened_clay", "minecraft:gray_terracotta") -+ .put("minecraft:green_stained_hardened_clay", "minecraft:green_terracotta") -+ .put("minecraft:iron_ingot_from_block", "minecraft:iron_ingot_from_iron_block") -+ .put("minecraft:jungle_wooden_slab", "minecraft:jungle_slab") -+ .put("minecraft:light_blue_stained_hardened_clay", "minecraft:light_blue_terracotta") -+ .put("minecraft:light_gray_stained_hardened_clay", "minecraft:light_gray_terracotta") -+ .put("minecraft:lime_stained_hardened_clay", "minecraft:lime_terracotta") -+ .put("minecraft:lit_pumpkin", "minecraft:jack_o_lantern") -+ .put("minecraft:magenta_stained_hardened_clay", "minecraft:magenta_terracotta") -+ .put("minecraft:magma", "minecraft:magma_block") -+ .put("minecraft:melon_block", "minecraft:melon") -+ .put("minecraft:mossy_stonebrick", "minecraft:mossy_stone_bricks") -+ .put("minecraft:noteblock", "minecraft:note_block") -+ .put("minecraft:oak_wooden_slab", "minecraft:oak_slab") -+ .put("minecraft:orange_stained_hardened_clay", "minecraft:orange_terracotta") -+ .put("minecraft:pillar_quartz_block", "minecraft:quartz_pillar") -+ .put("minecraft:pink_stained_hardened_clay", "minecraft:pink_terracotta") -+ .put("minecraft:purple_shulker_box", "minecraft:shulker_box") -+ .put("minecraft:purple_stained_hardened_clay", "minecraft:purple_terracotta") -+ .put("minecraft:red_nether_brick", "minecraft:red_nether_bricks") -+ .put("minecraft:red_stained_hardened_clay", "minecraft:red_terracotta") -+ .put("minecraft:slime", "minecraft:slime_block") -+ .put("minecraft:smooth_red_sandstone", "minecraft:cut_red_sandstone") -+ .put("minecraft:smooth_sandstone", "minecraft:cut_sandstone") -+ .put("minecraft:snow_layer", "minecraft:snow") -+ .put("minecraft:snow", "minecraft:snow_block") -+ .put("minecraft:speckled_melon", "minecraft:glistering_melon_slice") -+ .put("minecraft:spruce_wooden_slab", "minecraft:spruce_slab") -+ .put("minecraft:stonebrick", "minecraft:stone_bricks") -+ .put("minecraft:stone_stairs", "minecraft:cobblestone_stairs") -+ .put("minecraft:string_to_wool", "minecraft:white_wool_from_string") -+ .put("minecraft:trapdoor", "minecraft:oak_trapdoor") -+ .put("minecraft:white_stained_hardened_clay", "minecraft:white_terracotta") -+ .put("minecraft:wooden_button", "minecraft:oak_button") -+ .put("minecraft:wooden_door", "minecraft:oak_door") -+ .put("minecraft:wooden_pressure_plate", "minecraft:oak_pressure_plate") -+ .put("minecraft:yellow_stained_hardened_clay", "minecraft:yellow_terracotta") -+ .build() -+ ); -+ -+ private V1502() {} -+ -+ public static void register() { -+ ConverterAbstractRecipeRename.register(VERSION, RECIPES_UPDATES::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ef679762aec326e5e1310390bca46971b548e7cd ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java -@@ -0,0 +1,219 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.json.JsonMapType; -+import ca.spottedleaf.dataconverter.types.json.JsonTypeUtil; -+import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; -+import com.google.common.base.Splitter; -+import com.google.common.collect.ImmutableMap; -+import com.google.common.collect.Lists; -+import com.google.common.collect.Maps; -+import com.mojang.datafixers.util.Pair; -+import com.mojang.serialization.Dynamic; -+import com.mojang.serialization.DynamicOps; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.NbtOps; -+import net.minecraft.nbt.Tag; -+import net.minecraft.util.GsonHelper; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.HashMap; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Locale; -+import java.util.Map; -+import java.util.stream.Collectors; -+ -+public final class V1506 { -+ -+ protected static final int VERSION = MCVersions.V1_13_PRE4 + 2; -+ -+ static final Map MAP = new HashMap<>(); -+ static { -+ MAP.put("0", "minecraft:ocean"); -+ MAP.put("1", "minecraft:plains"); -+ MAP.put("2", "minecraft:desert"); -+ MAP.put("3", "minecraft:mountains"); -+ MAP.put("4", "minecraft:forest"); -+ MAP.put("5", "minecraft:taiga"); -+ MAP.put("6", "minecraft:swamp"); -+ MAP.put("7", "minecraft:river"); -+ MAP.put("8", "minecraft:nether"); -+ MAP.put("9", "minecraft:the_end"); -+ MAP.put("10", "minecraft:frozen_ocean"); -+ MAP.put("11", "minecraft:frozen_river"); -+ MAP.put("12", "minecraft:snowy_tundra"); -+ MAP.put("13", "minecraft:snowy_mountains"); -+ MAP.put("14", "minecraft:mushroom_fields"); -+ MAP.put("15", "minecraft:mushroom_field_shore"); -+ MAP.put("16", "minecraft:beach"); -+ MAP.put("17", "minecraft:desert_hills"); -+ MAP.put("18", "minecraft:wooded_hills"); -+ MAP.put("19", "minecraft:taiga_hills"); -+ MAP.put("20", "minecraft:mountain_edge"); -+ MAP.put("21", "minecraft:jungle"); -+ MAP.put("22", "minecraft:jungle_hills"); -+ MAP.put("23", "minecraft:jungle_edge"); -+ MAP.put("24", "minecraft:deep_ocean"); -+ MAP.put("25", "minecraft:stone_shore"); -+ MAP.put("26", "minecraft:snowy_beach"); -+ MAP.put("27", "minecraft:birch_forest"); -+ MAP.put("28", "minecraft:birch_forest_hills"); -+ MAP.put("29", "minecraft:dark_forest"); -+ MAP.put("30", "minecraft:snowy_taiga"); -+ MAP.put("31", "minecraft:snowy_taiga_hills"); -+ MAP.put("32", "minecraft:giant_tree_taiga"); -+ MAP.put("33", "minecraft:giant_tree_taiga_hills"); -+ MAP.put("34", "minecraft:wooded_mountains"); -+ MAP.put("35", "minecraft:savanna"); -+ MAP.put("36", "minecraft:savanna_plateau"); -+ MAP.put("37", "minecraft:badlands"); -+ MAP.put("38", "minecraft:wooded_badlands_plateau"); -+ MAP.put("39", "minecraft:badlands_plateau"); -+ MAP.put("40", "minecraft:small_end_islands"); -+ MAP.put("41", "minecraft:end_midlands"); -+ MAP.put("42", "minecraft:end_highlands"); -+ MAP.put("43", "minecraft:end_barrens"); -+ MAP.put("44", "minecraft:warm_ocean"); -+ MAP.put("45", "minecraft:lukewarm_ocean"); -+ MAP.put("46", "minecraft:cold_ocean"); -+ MAP.put("47", "minecraft:deep_warm_ocean"); -+ MAP.put("48", "minecraft:deep_lukewarm_ocean"); -+ MAP.put("49", "minecraft:deep_cold_ocean"); -+ MAP.put("50", "minecraft:deep_frozen_ocean"); -+ MAP.put("127", "minecraft:the_void"); -+ MAP.put("129", "minecraft:sunflower_plains"); -+ MAP.put("130", "minecraft:desert_lakes"); -+ MAP.put("131", "minecraft:gravelly_mountains"); -+ MAP.put("132", "minecraft:flower_forest"); -+ MAP.put("133", "minecraft:taiga_mountains"); -+ MAP.put("134", "minecraft:swamp_hills"); -+ MAP.put("140", "minecraft:ice_spikes"); -+ MAP.put("149", "minecraft:modified_jungle"); -+ MAP.put("151", "minecraft:modified_jungle_edge"); -+ MAP.put("155", "minecraft:tall_birch_forest"); -+ MAP.put("156", "minecraft:tall_birch_hills"); -+ MAP.put("157", "minecraft:dark_forest_hills"); -+ MAP.put("158", "minecraft:snowy_taiga_mountains"); -+ MAP.put("160", "minecraft:giant_spruce_taiga"); -+ MAP.put("161", "minecraft:giant_spruce_taiga_hills"); -+ MAP.put("162", "minecraft:modified_gravelly_mountains"); -+ MAP.put("163", "minecraft:shattered_savanna"); -+ MAP.put("164", "minecraft:shattered_savanna_plateau"); -+ MAP.put("165", "minecraft:eroded_badlands"); -+ MAP.put("166", "minecraft:modified_wooded_badlands_plateau"); -+ MAP.put("167", "minecraft:modified_badlands_plateau"); -+ } -+ -+ private V1506() {} -+ -+ public static void register() { -+ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String generatorOptions = data.getString("generatorOptions"); -+ final String generatorName = data.getString("generatorName"); -+ if ("flat".equalsIgnoreCase(generatorName)) { -+ data.setMap("generatorOptions", V1506.convert(generatorOptions == null ? "" : generatorOptions)); -+ } else if ("buffet".equalsIgnoreCase(generatorName) && generatorOptions != null) { -+ data.setMap("generatorOptions", JsonTypeUtil.convertJsonToNBT(new JsonMapType(GsonHelper.parse(generatorOptions, true), false))); -+ } -+ return null; -+ } -+ }); -+ } -+ -+ private static MapType convert(final String param0) { -+ final Dynamic dynamic = convert(param0, NbtOps.INSTANCE); -+ -+ return new NBTMapType((CompoundTag)dynamic.getValue()); -+ } -+ -+ // Yeah I ain't touching that. This is basically magic value hell. -+ private static Dynamic convert(final String generatorSettings, final DynamicOps ops) { -+ final Iterator splitSettings = Splitter.on(';').split(generatorSettings).iterator(); -+ String biome = "minecraft:plains"; -+ final Map> structures = Maps.newHashMap(); -+ final List> layers; -+ if (!generatorSettings.isEmpty() && splitSettings.hasNext()) { -+ layers = getLayersInfoFromString(splitSettings.next()); -+ if (!layers.isEmpty()) { -+ // biome is next -+ if (splitSettings.hasNext()) { -+ biome = MAP.getOrDefault(splitSettings.next(), "minecraft:plains"); -+ } -+ -+ // structures is next -+ if (splitSettings.hasNext()) { -+ final String[] structuresSplit = splitSettings.next().toLowerCase(Locale.ROOT).split(","); -+ -+ for (final String structureString : structuresSplit) { -+ final String[] structureInfo = structureString.split("\\(", 2); -+ if (!structureInfo[0].isEmpty()) { -+ structures.put(structureInfo[0], Maps.newHashMap()); -+ if (structureInfo.length > 1 && structureInfo[1].endsWith(")") && structureInfo[1].length() > 1) { -+ // I can't even guess the mappings for these. Not worth my time, it will work regardless of the mappings -+ final String[] var7 = structureInfo[1].substring(0, structureInfo[1].length() - 1).split(" "); -+ -+ for (final String var8 : var7) { -+ String[] var9 = var8.split("=", 2); -+ if (var9.length == 2) { -+ structures.get(structureInfo[0]).put(var9[0], var9[1]); -+ } -+ } -+ } -+ } -+ } -+ } else { -+ structures.put("village", Maps.newHashMap()); -+ } -+ } -+ } else { -+ layers = Lists.newArrayList(); -+ layers.add(Pair.of(1, "minecraft:bedrock")); -+ layers.add(Pair.of(2, "minecraft:dirt")); -+ layers.add(Pair.of(1, "minecraft:grass_block")); -+ structures.put("village", Maps.newHashMap()); -+ } -+ -+ final T layerTag = ops.createList(layers.stream().map((param1x) -> ops.createMap(ImmutableMap.of(ops.createString("height"), ops.createInt(param1x.getFirst()), ops.createString("block"), ops.createString(param1x.getSecond()))))); -+ final T structuresTag = ops.createMap(structures.entrySet().stream().map((param1x) -> Pair.of(ops.createString(param1x.getKey().toLowerCase(Locale.ROOT)), ops.createMap(param1x.getValue().entrySet().stream().map((param1xx) -> Pair.of(ops.createString(param1xx.getKey()), ops.createString(param1xx.getValue()))).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond))))).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond))); -+ return new Dynamic<>(ops, ops.createMap(ImmutableMap.of(ops.createString("layers"), layerTag, ops.createString("biome"), ops.createString(biome), ops.createString("structures"), structuresTag))); -+ } -+ -+ private static Pair getLayerInfoFromString(final String layerString) { -+ final String[] split = layerString.split("\\*", 2); -+ int layerCount; -+ if (split.length == 2) { -+ try { -+ layerCount = Integer.parseInt(split[0]); -+ } catch (final NumberFormatException ex) { -+ return null; -+ } -+ } else { -+ layerCount = 1; -+ } -+ -+ final String blockName = split[split.length - 1]; -+ return Pair.of(layerCount, blockName); -+ } -+ -+ private static List> getLayersInfoFromString(final String layersString) { -+ final List> ret = new ArrayList<>(); -+ final String[] layers = layersString.split(","); -+ -+ for (final String layerString : layers) { -+ final Pair layer = getLayerInfoFromString(layerString); -+ if (layer == null) { -+ return Collections.emptyList(); -+ } -+ -+ ret.add(layer); -+ } -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java -new file mode 100644 -index 0000000000000000000000000000000000000000..97f92a4ee54364616181a2803351481df224d3dc ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java -@@ -0,0 +1,111 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.recipe.ConverterAbstractRecipeRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.stats.ConverterAbstractStatsRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V1510 { -+ -+ public static final Map RENAMED_ENTITY_IDS = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:commandblock_minecart", "minecraft:command_block_minecart") -+ .put("minecraft:ender_crystal", "minecraft:end_crystal") -+ .put("minecraft:snowman", "minecraft:snow_golem") -+ .put("minecraft:evocation_illager", "minecraft:evoker") -+ .put("minecraft:evocation_fangs", "minecraft:evoker_fangs") -+ .put("minecraft:illusion_illager", "minecraft:illusioner") -+ .put("minecraft:vindication_illager", "minecraft:vindicator") -+ .put("minecraft:villager_golem", "minecraft:iron_golem") -+ .put("minecraft:xp_orb", "minecraft:experience_orb") -+ .put("minecraft:xp_bottle", "minecraft:experience_bottle") -+ .put("minecraft:eye_of_ender_signal", "minecraft:eye_of_ender") -+ .put("minecraft:fireworks_rocket", "minecraft:firework_rocket") -+ .build() -+ ); -+ -+ public static final Map RENAMED_BLOCKS = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:portal", "minecraft:nether_portal") -+ .put("minecraft:oak_bark", "minecraft:oak_wood") -+ .put("minecraft:spruce_bark", "minecraft:spruce_wood") -+ .put("minecraft:birch_bark", "minecraft:birch_wood") -+ .put("minecraft:jungle_bark", "minecraft:jungle_wood") -+ .put("minecraft:acacia_bark", "minecraft:acacia_wood") -+ .put("minecraft:dark_oak_bark", "minecraft:dark_oak_wood") -+ .put("minecraft:stripped_oak_bark", "minecraft:stripped_oak_wood") -+ .put("minecraft:stripped_spruce_bark", "minecraft:stripped_spruce_wood") -+ .put("minecraft:stripped_birch_bark", "minecraft:stripped_birch_wood") -+ .put("minecraft:stripped_jungle_bark", "minecraft:stripped_jungle_wood") -+ .put("minecraft:stripped_acacia_bark", "minecraft:stripped_acacia_wood") -+ .put("minecraft:stripped_dark_oak_bark", "minecraft:stripped_dark_oak_wood") -+ .put("minecraft:mob_spawner", "minecraft:spawner") -+ .build() -+ ); -+ -+ public static final Map RENAMED_ITEMS = new HashMap<>( -+ ImmutableMap.builder() -+ .putAll(RENAMED_BLOCKS) -+ .put("minecraft:clownfish", "minecraft:tropical_fish") -+ .put("minecraft:chorus_fruit_popped", "minecraft:popped_chorus_fruit") -+ .put("minecraft:evocation_illager_spawn_egg", "minecraft:evoker_spawn_egg") -+ .put("minecraft:vindication_illager_spawn_egg", "minecraft:vindicator_spawn_egg") -+ .build() -+ ); -+ -+ private static final Map RECIPES_UPDATES = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:acacia_bark", "minecraft:acacia_wood") -+ .put("minecraft:birch_bark", "minecraft:birch_wood") -+ .put("minecraft:dark_oak_bark", "minecraft:dark_oak_wood") -+ .put("minecraft:jungle_bark", "minecraft:jungle_wood") -+ .put("minecraft:oak_bark", "minecraft:oak_wood") -+ .put("minecraft:spruce_bark", "minecraft:spruce_wood") -+ .build() -+ ); -+ -+ protected static final int VERSION = MCVersions.V1_13_PRE4 + 6; -+ -+ private V1510() {} -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, RENAMED_BLOCKS::get); -+ ConverterAbstractItemRename.register(VERSION, RENAMED_ITEMS::get); -+ ConverterAbstractRecipeRename.register(VERSION, RECIPES_UPDATES::get); -+ -+ ConverterAbstractEntityRename.register(VERSION, (String input) -> { -+ if (input.startsWith("minecraft:bred_")) { -+ input = "minecraft:".concat(input.substring("minecraft:bred_".length())); -+ } -+ -+ return RENAMED_ENTITY_IDS.get(input); -+ }); -+ -+ ConverterAbstractStatsRename.register(VERSION, new HashMap<>( -+ ImmutableMap.of( -+ "minecraft:swim_one_cm", "minecraft:walk_on_water_one_cm", -+ "minecraft:dive_one_cm", "minecraft:walk_under_water_one_cm" -+ ) -+ )::get); -+ -+ -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:commandblock_minecart", "minecraft:command_block_minecart"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:ender_crystal", "minecraft:end_crystal"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:snowman", "minecraft:snow_golem"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:evocation_illager", "minecraft:evoker"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:evocation_fangs", "minecraft:evoker_fangs"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:illusion_illager", "minecraft:illusioner"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:vindication_illager", "minecraft:vindicator"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:villager_golem", "minecraft:iron_golem"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:xp_orb", "minecraft:experience_orb"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:xp_bottle", "minecraft:experience_bottle"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:eye_of_ender_signal", "minecraft:eye_of_ender"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:fireworks_rocket", "minecraft:firework_rocket"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2a159164a6ce37d9c0900d4e8d95c26a38bea041 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java -@@ -0,0 +1,68 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1514 { -+ -+ protected static final int VERSION = MCVersions.V1_13_PRE7 + 1; -+ -+ private V1514() {} -+ -+ public static void register() { -+ MCTypeRegistry.OBJECTIVE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String displayName = data.getString("DisplayName"); -+ if (displayName == null) { -+ return null; -+ } -+ -+ final String update = ComponentUtils.createPlainTextComponent(displayName); -+ -+ data.setString("DisplayName", update); -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.TEAM.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String displayName = data.getString("DisplayName"); -+ if (displayName == null) { -+ return null; -+ } -+ -+ final String update = ComponentUtils.createPlainTextComponent(displayName); -+ -+ data.setString("DisplayName", update); -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.OBJECTIVE.addStructureConverter(new DataConverter<>(VERSION) { -+ private static String getRenderType(String string) { -+ return string.equals("health") ? "hearts" : "integer"; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String renderType = data.getString("RenderType"); -+ if (renderType != null) { -+ return null; -+ } -+ -+ final String criteriaName = data.getString("CriteriaName", ""); -+ -+ data.setString("RenderType", getRenderType(criteriaName)); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java -new file mode 100644 -index 0000000000000000000000000000000000000000..82e3aec90f868cc1def5462bff801527daaa4362 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java -@@ -0,0 +1,28 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V1515 { -+ -+ protected static final int VERSION = MCVersions.V1_13_PRE7 + 2; -+ -+ public static final Map RENAMED_BLOCK_IDS = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:tube_coral_fan", "minecraft:tube_coral_wall_fan") -+ .put("minecraft:brain_coral_fan", "minecraft:brain_coral_wall_fan") -+ .put("minecraft:bubble_coral_fan", "minecraft:bubble_coral_wall_fan") -+ .put("minecraft:fire_coral_fan", "minecraft:fire_coral_wall_fan") -+ .put("minecraft:horn_coral_fan", "minecraft:horn_coral_wall_fan") -+ .build() -+ ); -+ -+ private V1515() {} -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, RENAMED_BLOCK_IDS::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7b304031e1e8af120c6535e599c2ee4fdbce1682 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java -@@ -0,0 +1,110 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.mojang.logging.LogUtils; -+import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -+import org.slf4j.Logger; -+ -+public final class V1624 { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V18W32A + 1; -+ -+ private V1624() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ -+ if (level == null) { -+ return null; -+ } -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections == null) { -+ return null; -+ } -+ -+ final IntOpenHashSet positionsToLook = new IntOpenHashSet(); -+ -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final TrappedChestSection section = new TrappedChestSection(sections.getMap(i)); -+ if (section.isSkippable()) { -+ continue; -+ } -+ -+ for (int index = 0; index < 4096; ++index) { -+ if (section.isTrappedChest(section.getBlock(index))) { -+ positionsToLook.add(section.getSectionY() << 12 | index); -+ } -+ } -+ } -+ -+ final int chunkX = level.getInt("xPos"); -+ final int chunkZ = level.getInt("zPos"); -+ -+ final ListType tileEntities = level.getList("TileEntities", ObjectType.MAP); -+ -+ if (tileEntities != null) { -+ for (int i = 0, len = tileEntities.size(); i < len; ++i) { -+ final MapType tile = tileEntities.getMap(i); -+ -+ final int x = tile.getInt("x"); -+ final int y = tile.getInt("y"); -+ final int z = tile.getInt("z"); -+ -+ final int index = V1496.getIndex(x - (chunkX << 4), y, z - (chunkZ << 4)); -+ if (!positionsToLook.contains(index)) { -+ continue; -+ } -+ -+ final String id = tile.getString("id"); -+ if (!"minecraft:chest".equals(id)) { -+ LOGGER.warn("Block Entity ({},{},{}) was expected to be a chest (V1624)", x, y, z); -+ } -+ -+ tile.setString("id", "minecraft:trapped_chest"); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ public static final class TrappedChestSection extends V1496.Section { -+ -+ private IntOpenHashSet chestIds; -+ -+ public TrappedChestSection(final MapType section) { -+ super(section); -+ } -+ -+ @Override -+ protected boolean initSkippable() { -+ this.chestIds = new IntOpenHashSet(); -+ -+ for (int i = 0; i < this.palette.size(); ++i) { -+ final MapType blockState = this.palette.getMap(i); -+ final String name = blockState.getString("Name"); -+ if ("minecraft:trapped_chest".equals(name)) { -+ this.chestIds.add(i); -+ } -+ } -+ -+ return this.chestIds.isEmpty(); -+ } -+ -+ public boolean isTrappedChest(final int id) { -+ return this.chestIds.contains(id); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java -new file mode 100644 -index 0000000000000000000000000000000000000000..20eebfbbf913c92886a21fa4790c64cca8d8ba88 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java -@@ -0,0 +1,79 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.gson.JsonParseException; -+import net.minecraft.network.chat.CommonComponents; -+import net.minecraft.network.chat.Component; -+import net.minecraft.util.GsonHelper; -+import net.minecraft.util.datafix.fixes.BlockEntitySignTextStrictJsonFix; -+import org.apache.commons.lang3.StringUtils; -+ -+public final class V165 { -+ -+ protected static final int VERSION = MCVersions.V1_9_PRE2; -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final ListType pages = tag.getList("pages", ObjectType.STRING); -+ if (pages == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = pages.size(); i < len; ++i) { -+ final String page = pages.getString(i); -+ Component component = null; -+ -+ if (!"null".equals(page) && !StringUtils.isEmpty(page)) { -+ if (page.charAt(0) == '"' && page.charAt(page.length() - 1) == '"' || page.charAt(0) == '{' && page.charAt(page.length() - 1) == '}') { -+ try { -+ component = GsonHelper.fromNullableJson(BlockEntitySignTextStrictJsonFix.GSON, page, Component.class, true); -+ if (component == null) { -+ component = CommonComponents.EMPTY; -+ } -+ } catch (final JsonParseException ignored) {} -+ -+ if (component == null) { -+ try { -+ component = Component.Serializer.fromJson(page); -+ } catch (final JsonParseException ignored) {} -+ } -+ -+ if (component == null) { -+ try { -+ component = Component.Serializer.fromJsonLenient(page); -+ } catch (JsonParseException ignored) {} -+ } -+ -+ if (component == null) { -+ component = Component.literal(page); -+ } -+ } else { -+ component = Component.literal(page); -+ } -+ } else { -+ component = CommonComponents.EMPTY; -+ } -+ -+ pages.setString(i, Component.Serializer.toJson(component)); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V165() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1be2154d9f9e8f33266aa745790f3bff30260f6f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java -@@ -0,0 +1,36 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V1800 { -+ -+ protected static final int VERSION = MCVersions.V1_13_2 + 169; -+ -+ public static final Map RENAMED_ITEM_IDS = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:cactus_green", "minecraft:green_dye") -+ .put("minecraft:rose_red", "minecraft:red_dye") -+ .put("minecraft:dandelion_yellow", "minecraft:yellow_dye") -+ .build() -+ ); -+ -+ private V1800() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, RENAMED_ITEM_IDS::get); -+ -+ registerMob("minecraft:panda"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:pillager", new DataWalkerItemLists("Inventory", "ArmorItems", "HandItems")); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a4e2fa4ed6ede8d1783b5591ef1b5ebf3cd28bf0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V1801 { -+ -+ protected static final int VERSION = MCVersions.V1_13_2 + 170; -+ -+ private V1801() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:illager_beast"); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cd5110ef3c18662871020456b60edfb3aeb67166 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java -@@ -0,0 +1,25 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V1802 { -+ -+ protected static final int VERSION = MCVersions.V1_13_2 + 171; -+ -+ private V1802() {} -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( -+ "minecraft:stone_slab", "minecraft:smooth_stone_slab", -+ "minecraft:sign", "minecraft:oak_sign", "minecraft:wall_sign", "minecraft:oak_wall_sign" -+ )::get); -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:stone_slab", "minecraft:smooth_stone_slab", -+ "minecraft:sign", "minecraft:oak_sign" -+ )::get); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9ca850f1bfc9138c68a127a9c90fd33ca81e5dbc ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java -@@ -0,0 +1,47 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V1803 { -+ -+ protected static final int VERSION = MCVersions.V1_13_2 + 172; -+ -+ private V1803() {} -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ -+ if (tag == null) { -+ return null; -+ } -+ -+ final MapType display = tag.getMap("display"); -+ -+ if (display == null) { -+ return null; -+ } -+ -+ final ListType lore = display.getList("Lore", ObjectType.STRING); -+ if (lore == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = lore.size(); i < len; ++i) { -+ lore.setString(i, ComponentUtils.createPlainTextComponent(lore.getString(i))); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java -new file mode 100644 -index 0000000000000000000000000000000000000000..09955e0c2245d8d42ce6ae664ae81e97db8a85f2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java -@@ -0,0 +1,44 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1904 { -+ -+ protected static final int VERSION = MCVersions.V18W43C + 1; -+ -+ private V1904() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:ocelot", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int catType = data.getInt("CatType"); -+ -+ if (catType == 0) { -+ final String owner = data.getString("Owner"); -+ final String ownerUUID = data.getString("OwnerUUID"); -+ if ((owner != null && owner.length() > 0) || (ownerUUID != null && ownerUUID.length() > 0)) { -+ data.setBoolean("Trusting", true); -+ } -+ } else if (catType > 0 && catType < 4) { -+ data.setString("id", "minecraft:cat"); -+ data.setString("OwnerUUID", data.getString("OwnerUUID", "")); -+ } -+ -+ return null; -+ } -+ }); -+ -+ registerMob("minecraft:cat"); -+ } -+ -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2eeec0d9cbd35ff20ba239ea7fd9c2f52f7e4f9e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java -@@ -0,0 +1,35 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1905 { -+ -+ protected static final int VERSION = MCVersions.V18W43C + 2; -+ -+ private V1905() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ -+ if (level == null) { -+ return null; -+ } -+ -+ final String status = level.getString("Status"); -+ -+ if ("postprocessed".equals(status)) { -+ level.setString("Status", "fullchunk"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6358c2e0861a3743a3ea6d46a644870892256a79 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+ -+public final class V1906 { -+ -+ protected static final int VERSION = MCVersions.V18W43C + 3; -+ -+ private V1906() {} -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:barrel", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:smoker", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:blast_furnace", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:lectern", new DataWalkerItems("Book")); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c3207b60967225f875b7cf763c2c6634d6886a34 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java -@@ -0,0 +1,51 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V1911 { -+ -+ protected static final int VERSION = MCVersions.V18W46A + 1; -+ -+ private static final Map CHUNK_STATUS_REMAP = new HashMap<>( -+ ImmutableMap.builder() -+ .put("structure_references", "empty") -+ .put("biomes", "empty") -+ .put("base", "surface") -+ .put("carved", "carvers") -+ .put("liquid_carved", "liquid_carvers") -+ .put("decorated", "features") -+ .put("lighted", "light") -+ .put("mobs_spawned", "spawn") -+ .put("finalized", "heightmaps") -+ .put("fullchunk", "full") -+ .build() -+ ); -+ -+ -+ private V1911() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ -+ if (level == null) { -+ return null; -+ } -+ -+ final String status = level.getString("Status", "empty"); -+ level.setString("Status", CHUNK_STATUS_REMAP.getOrDefault(status, "empty")); -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8f5a48c4824080827d2dad057ae70dfd7a11818f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java -@@ -0,0 +1,27 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1914 { -+ -+ protected static final int VERSION = MCVersions.V18W48A; -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:chest", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String lootTable = data.getString("LootTable"); -+ -+ if ("minecraft:chests/village_blacksmith".equals(lootTable)) { -+ data.setString("LootTable", "minecraft:chests/village/village_weaponsmith"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java -new file mode 100644 -index 0000000000000000000000000000000000000000..71538d858a681c91f7193003e0808cdb4fd1f847 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java -@@ -0,0 +1,26 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1917 { -+ -+ protected static final int VERSION = MCVersions.V18W49A + 1; -+ -+ private V1917() {} -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getInt("CatType") == 9) { -+ data.setInt("CatType", 10); -+ } -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java -new file mode 100644 -index 0000000000000000000000000000000000000000..28fc06da723792e9abc4999376c0941f9a835aff ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java -@@ -0,0 +1,65 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V1918 { -+ -+ protected static final int VERSION = MCVersions.V18W49A + 2; -+ -+ private V1918() {} -+ -+ private static String getProfessionString(final int professionId, final int careerId) { -+ if (professionId == 0) { -+ if (careerId == 2) { -+ return "minecraft:fisherman"; -+ } else if (careerId == 3) { -+ return "minecraft:shepherd"; -+ } else { -+ return careerId == 4 ? "minecraft:fletcher" : "minecraft:farmer"; -+ } -+ } else if (professionId == 1) { -+ return careerId == 2 ? "minecraft:cartographer" : "minecraft:librarian"; -+ } else if (professionId == 2) { -+ return "minecraft:cleric"; -+ } else if (professionId == 3) { -+ if (careerId == 2) { -+ return "minecraft:weaponsmith"; -+ } else { -+ return careerId == 3 ? "minecraft:toolsmith" : "minecraft:armorer"; -+ } -+ } else if (professionId == 4) { -+ return careerId == 2 ? "minecraft:leatherworker" : "minecraft:butcher"; -+ } else { -+ return professionId == 5 ? "minecraft:nitwit" : "minecraft:none"; -+ } -+ } -+ -+ public static void register() { -+ final DataConverter, MapType> converter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int profession = data.getInt("Profession"); -+ final int career = data.getInt("Career"); -+ final int careerLevel = data.getInt("CareerLevel", 1); -+ data.remove("Profession"); -+ data.remove("Career"); -+ data.remove("CareerLevel"); -+ -+ final MapType villagerData = Types.NBT.createEmptyMap(); -+ data.setMap("VillagerData", villagerData); -+ villagerData.setString("type", "minecraft:plains"); -+ villagerData.setString("profession", getProfessionString(profession, career)); -+ villagerData.setInt("level", careerLevel); -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", converter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", converter); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java -new file mode 100644 -index 0000000000000000000000000000000000000000..224d35620e9d9e65f0642fdb13f80fcb2667a2ee ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java -@@ -0,0 +1,75 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.util.NamespaceUtil; -+ -+public final class V1920 { -+ -+ protected static final int VERSION = MCVersions.V18W50A + 1; -+ -+ private V1920() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ final MapType structures = level.getMap("Structures"); -+ if (structures == null) { -+ return null; -+ } -+ -+ final MapType starts = structures.getMap("Starts"); -+ if (starts != null) { -+ final MapType village = starts.getMap("New_Village"); -+ if (village != null) { -+ starts.remove("New_Village"); -+ starts.setMap("Village", village); -+ } else { -+ starts.remove("Village"); -+ } -+ } -+ -+ final MapType references = structures.getMap("References"); -+ if (references != null) { -+ final MapType newVillage = references.getMap("New_Village"); -+ // I believe Mojang had a typo here, removing Village from references only made sense -+ // if the new village didn't exist. DFU removes it whether or not it exists, but still relocates -+ // New_Village to Village first. It doesn't make sense to me to relocate it just to remove it, so it -+ // must be a typo. -+ if (newVillage == null) { -+ references.remove("Village"); -+ } else { -+ references.remove("New_Village"); -+ references.setMap("Village", newVillage); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String id = data.getString("id"); -+ -+ if ("minecraft:new_village".equals(NamespaceUtil.correctNamespace(id))) { -+ data.setString("id", "minecraft:village"); -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:campfire", new DataWalkerItemLists("Items")); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9a063df2f4d09bd561f5a538c440b43b1c0fb581 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java -@@ -0,0 +1,29 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V1925 { -+ -+ protected static final int VERSION = MCVersions.V19W03C + 1; -+ -+ public static void register() { -+ MCTypeRegistry.SAVED_DATA_MAP_DATA.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { -+ final MapType data = root.getMap("data"); -+ if (data == null) { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ ret.setMap("data", root); -+ -+ return ret; -+ } -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java -new file mode 100644 -index 0000000000000000000000000000000000000000..86caefba615a917d3530112edcc083caaaea4bb9 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java -@@ -0,0 +1,30 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V1928 { -+ -+ protected static final int VERSION = MCVersions.V19W04B + 1; -+ -+ private V1928() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of( -+ "minecraft:illager_beast", "minecraft:ravager" -+ )::get); -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:illager_beast_spawn_egg", "minecraft:ravager_spawn_egg" -+ )::get); -+ -+ registerMob("minecraft:ravager"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d58c32aa89e416e40cfef7c5840b772dd4991173 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java -@@ -0,0 +1,49 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V1929 { -+ -+ protected static final int VERSION = MCVersions.V19W04B + 2; -+ -+ private V1929() {} -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:wandering_trader", (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); -+ -+ final MapType offers = data.getMap("Offers"); -+ if (offers != null) { -+ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); -+ if (recipes != null) { -+ for (int i = 0, len = recipes.size(); i < len; ++i) { -+ final MapType recipe = recipes.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); -+ } -+ } -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); -+ -+ return null; -+ }); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:trader_llama", (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, data, "SaddleItem", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, data, "DecorItem", fromVersion, toVersion); -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Items", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java -new file mode 100644 -index 0000000000000000000000000000000000000000..da845df91d42f1059490d749b235758850ce7254 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V1931 { -+ -+ protected static final int VERSION = MCVersions.V19W06A; -+ -+ private V1931() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:fox"); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4e7b22874f17f531b583146db3aa4e57bdd5f27c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java -@@ -0,0 +1,37 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1936 { -+ -+ protected static final int VERSION = MCVersions.V19W09A + 1; -+ -+ private V1936() {} -+ -+ public static void register() { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String chatOpacity = data.getString("chatOpacity"); -+ if (chatOpacity != null) { -+ // Vanilla uses createDouble here, but options is always string -> string. I presume they made -+ // a mistake with this converter. -+ data.setString("textBackgroundOpacity", Double.toString(calculateBackground(chatOpacity))); -+ } -+ return null; -+ } -+ }); -+ } -+ -+ private static double calculateBackground(final String opacity) { -+ try { -+ final double d = 0.9D * Double.parseDouble(opacity) + 0.1D; -+ return d / 2.0D; -+ } catch (final NumberFormatException ex) { -+ return 0.5D; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java -new file mode 100644 -index 0000000000000000000000000000000000000000..00e4bd45b04feee990d9d3414c34c0966e65e4ea ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java -@@ -0,0 +1,42 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V1946 { -+ -+ protected static final int VERSION = MCVersions.V19W14B + 1; -+ -+ private V1946() {} -+ -+ public static void register() { -+ MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType sections = Types.NBT.createEmptyMap(); -+ data.setMap("Sections", sections); -+ -+ for (int y = 0; y < 16; ++y) { -+ final String key = Integer.toString(y); -+ final Object records = data.getGeneric(key); -+ -+ if (records == null) { -+ continue; -+ } -+ -+ data.remove(key); -+ -+ final MapType section = Types.NBT.createEmptyMap(); -+ section.setGeneric("Records", records); -+ sections.setMap(key, section); // integer keys convert to string in DFU (at least for NBT ops) -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6b4af1fe7c53e6122d7db952770d14a753f8cab3 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java -@@ -0,0 +1,39 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1948 { -+ -+ protected static final int VERSION = MCVersions.V1_14_PRE2; -+ -+ private V1948() {} -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:white_banner", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final MapType display = tag.getMap("display"); -+ if (display == null) { -+ return null; -+ } -+ -+ final String name = display.getString("Name"); -+ if (name == null) { -+ return null; -+ } -+ -+ display.setString("Name", name.replace("\"translate\":\"block.minecraft.illager_banner\"", "\"translate\":\"block.minecraft.ominous_banner\"")); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a73471e8f3df3b22349b2f842c3e98c2ff8bb5e1 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java -@@ -0,0 +1,27 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1953 { -+ -+ protected static final int VERSION = MCVersions.V1_14 + 1; -+ -+ private V1953() {} -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:banner", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String name = data.getString("CustomName"); -+ if (name != null) { -+ data.setString("CustomName", name.replace("\"translate\":\"block.minecraft.illager_banner\"", "\"translate\":\"block.minecraft.ominous_banner\"")); -+ } -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java -new file mode 100644 -index 0000000000000000000000000000000000000000..33bfe82709b507c4fd57199f5d8a44d131718d7f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java -@@ -0,0 +1,94 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import net.minecraft.util.Mth; -+ -+public final class V1955 { -+ -+ protected static final int VERSION = MCVersions.V1_14_1_PRE1; -+ -+ private static final int[] LEVEL_XP_THRESHOLDS = new int[] { -+ 0, -+ 10, -+ 50, -+ 100, -+ 150 -+ }; -+ -+ private V1955() {} -+ -+ static int getMinXpPerLevel(final int level) { -+ return LEVEL_XP_THRESHOLDS[Mth.clamp(level - 1, 0, LEVEL_XP_THRESHOLDS.length - 1)]; -+ } -+ -+ static void addLevel(final MapType data, final int level) { -+ MapType villagerData = data.getMap("VillagerData"); -+ if (villagerData == null) { -+ villagerData = Types.NBT.createEmptyMap(); -+ data.setMap("VillagerData", villagerData); -+ } -+ villagerData.setInt("level", level); -+ } -+ -+ static void addXpFromLevel(final MapType data, final int level) { -+ data.setInt("Xp", getMinXpPerLevel(level)); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType villagerData = data.getMap("VillagerData"); -+ int level = villagerData == null ? 0 : villagerData.getInt("level"); -+ if (level == 0 || level == 1) { -+ // count recipes -+ final MapType offers = data.getMap("Offers"); -+ final ListType recipes = offers == null ? null : offers.getList("Recipes", ObjectType.MAP); -+ final int recipeCount; -+ if (recipes != null) { -+ recipeCount = recipes.size(); -+ } else { -+ recipeCount = 0; -+ } -+ -+ level = Mth.clamp(recipeCount / 2, 1, 5); -+ if (level > 1) { -+ addLevel(data, level); -+ } -+ } -+ -+ if (!data.hasKey("Xp", ObjectType.NUMBER)) { -+ addXpFromLevel(data, level); -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final Number xp = data.getNumber("Xp"); -+ if (xp == null) { -+ final int level; -+ final MapType villagerData = data.getMap("VillagerData"); -+ if (villagerData == null) { -+ level = 1; -+ } else { -+ level = villagerData.getInt("level", 1); -+ } -+ -+ data.setInt("Xp", getMinXpPerLevel(level)); -+ } -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8bc6a8734034942a81b282b8766b21fddbe2b304 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java -@@ -0,0 +1,30 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1961 { -+ -+ protected static final int VERSION = MCVersions.V1_14_2_PRE3 + 1; -+ -+ private V1961() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ level.remove("isLightOn"); -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5e4e7299cec1d3809d1b55ae460e64ec6d2bb477 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java -@@ -0,0 +1,40 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V1963 { -+ -+ protected static final int VERSION = MCVersions.V1_14_2; -+ -+ private V1963() {} -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType gossips = data.getList("Gossips", ObjectType.MAP); -+ if (gossips == null) { -+ return null; -+ } -+ -+ for (int i = 0; i < gossips.size();) { -+ final MapType gossip = gossips.getMap(i); -+ if ("golem".equals(gossip.getString("Type"))) { -+ gossips.remove(i); -+ continue; -+ } -+ -+ ++i; -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d905043c4f3071e8dc340ac2afe2b81aac345e1e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java -@@ -0,0 +1,46 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.recipe.ConverterAbstractRecipeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V2100 { -+ -+ protected static final int VERSION = MCVersions.V1_14_4 + 124; -+ protected static final Map RECIPE_RENAMES = ImmutableMap.of( -+ "minecraft:sugar", "sugar_from_sugar_cane" -+ ); -+ -+ private V2100() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ ConverterAbstractRecipeRename.register(VERSION, RECIPE_RENAMES::get); -+ ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of( -+ "minecraft:recipes/misc/sugar", "minecraft:recipes/misc/sugar_from_sugar_cane" -+ )::get); -+ -+ registerMob("minecraft:bee"); -+ registerMob("minecraft:bee_stinger"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:beehive", (data, fromVersion, toVersion) -> { -+ final ListType bees = data.getList("Bees", ObjectType.MAP); -+ if (bees != null) { -+ for (int i = 0, len = bees.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, bees.getMap(i), "EntityData", fromVersion, toVersion); -+ } -+ } -+ -+ return null; -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0bb378ac8e8d0a087359361281644a7f39cecfbe ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java -@@ -0,0 +1,50 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2202 { -+ -+ protected static final int VERSION = MCVersions.V19W35A + 1; -+ -+ private V2202() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ final int[] oldBiomes = level.getInts("Biomes"); -+ -+ if (oldBiomes == null || oldBiomes.length != 256) { -+ return null; -+ } -+ -+ final int[] newBiomes = new int[1024]; -+ level.setInts("Biomes", newBiomes); -+ -+ int n; -+ for(n = 0; n < 4; ++n) { -+ for(int j = 0; j < 4; ++j) { -+ int k = (j << 2) + 2; -+ int l = (n << 2) + 2; -+ int m = l << 4 | k; -+ newBiomes[n << 2 | j] = oldBiomes[m]; -+ } -+ } -+ -+ for(n = 1; n < 64; ++n) { -+ System.arraycopy(newBiomes, 0, newBiomes, n * 16, 16); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6054cd31cb2e140f05b92736405a7d073be5cf5c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java -@@ -0,0 +1,26 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.poi.ConverterAbstractPOIRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V2209 { -+ -+ protected static final int VERSION = MCVersions.V19W40A + 1; -+ -+ private V2209() {} -+ -+ public static void register() { -+ final Map renamedIds = ImmutableMap.of( -+ "minecraft:bee_hive", "minecraft:beehive" -+ ); -+ -+ ConverterAbstractBlockRename.register(VERSION, renamedIds::get); -+ ConverterAbstractItemRename.register(VERSION, renamedIds::get); -+ ConverterAbstractPOIRename.register(VERSION, renamedIds::get); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6cff6d723616e0a38811872f7b5d28799240ddfe ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java -@@ -0,0 +1,32 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V2211 { -+ -+ protected static final int VERSION = MCVersions.V19W41A + 1; -+ -+ private V2211() {} -+ -+ public static void register() { -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!data.hasKey("references", ObjectType.NUMBER)) { -+ return null; -+ } -+ -+ final int references = data.getInt("references"); -+ if (references <= 0) { -+ data.setInt("references", 1); -+ } -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e06a98a01086c9d6eb9fc80a151f0403247b0033 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java -@@ -0,0 +1,33 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2218 { -+ -+ protected static final int VERSION = MCVersions.V1_15_PRE1; -+ -+ private V2218() {} -+ -+ public static void register() { -+ MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType sections = data.getMap("Sections"); -+ if (sections == null) { -+ return null; -+ } -+ -+ for (final String key : sections.keys()) { -+ final MapType section = sections.getMap(key); -+ -+ section.remove("Valid"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c8901f95e1076ae8be220c03efd83ce9bd18d2a8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java -@@ -0,0 +1,65 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V2501 { -+ -+ protected static final int VERSION = MCVersions.V1_15_2 + 271; -+ -+ private V2501() {} -+ -+ private static void registerFurnace(final String id) { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, (data, fromVersion, toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Items", fromVersion, toVersion); -+ -+ WalkerUtils.convertKeys(MCTypeRegistry.RECIPE, data, "RecipesUsed", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+ -+ public static void register() { -+ final DataConverter, MapType> converter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int recipesUsedSize = data.getInt("RecipesUsedSize"); -+ data.remove("RecipesUsedSize"); -+ -+ if (recipesUsedSize <= 0) { -+ return null; -+ } -+ -+ final MapType newRecipes = Types.NBT.createEmptyMap(); -+ data.setMap("RecipesUsed", newRecipes); -+ -+ for (int i = 0; i < recipesUsedSize; ++i) { -+ final String recipeKey = data.getString("RecipeLocation" + i); -+ data.remove("RecipeLocation" + i); -+ final int recipeAmount = data.getInt("RecipeAmount" + i); -+ data.remove("RecipeAmount" + i); -+ -+ if (i <= 0 || recipeKey == null) { -+ continue; -+ } -+ -+ newRecipes.setInt(recipeKey, recipeAmount); -+ } -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:furnace", converter); -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:blast_furnace", converter); -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:smoker", converter); -+ -+ registerFurnace("minecraft:furnace"); -+ registerFurnace("minecraft:smoker"); -+ registerFurnace("minecraft:blast_furnace"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7075f7beb8aacfdadd68f4353d2cf32edaa1905d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2502 { -+ -+ protected static final int VERSION = MCVersions.V1_15_2 + 272; -+ -+ private V2502() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:hoglin"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cd1bca807236b917244bbacd8df6f25fd3c42407 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java -@@ -0,0 +1,67 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.common.collect.ImmutableMap; -+import com.google.common.collect.ImmutableSet; -+import java.util.Set; -+ -+public final class V2503 { -+ -+ protected static final int VERSION = MCVersions.V1_15_2 + 273; -+ -+ private static final Set WALL_BLOCKS = ImmutableSet.of( -+ "minecraft:andesite_wall", -+ "minecraft:brick_wall", -+ "minecraft:cobblestone_wall", -+ "minecraft:diorite_wall", -+ "minecraft:end_stone_brick_wall", -+ "minecraft:granite_wall", -+ "minecraft:mossy_cobblestone_wall", -+ "minecraft:mossy_stone_brick_wall", -+ "minecraft:nether_brick_wall", -+ "minecraft:prismarine_wall", -+ "minecraft:red_nether_brick_wall", -+ "minecraft:red_sandstone_wall", -+ "minecraft:sandstone_wall", -+ "minecraft:stone_brick_wall" -+ ); -+ -+ private V2503() {} -+ -+ private static void changeWallProperty(final MapType properties, final String path) { -+ final String property = properties.getString(path); -+ if (property != null) { -+ properties.setString(path, "true".equals(property) ? "low" : "none"); -+ } -+ } -+ -+ public static void register() { -+ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!WALL_BLOCKS.contains(data.getString("Name"))) { -+ return null; -+ } -+ -+ final MapType properties = data.getMap("Properties"); -+ if (properties == null) { -+ return null; -+ } -+ -+ changeWallProperty(properties, "east"); -+ changeWallProperty(properties, "west"); -+ changeWallProperty(properties, "north"); -+ changeWallProperty(properties, "south"); -+ -+ return null; -+ } -+ }); -+ ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of( -+ "minecraft:recipes/misc/composter", "minecraft:recipes/decorations/composter" -+ )::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java -new file mode 100644 -index 0000000000000000000000000000000000000000..350f5cca06d2a864b5a3cc028753fd6489a28ad5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java -@@ -0,0 +1,49 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V2505 { -+ -+ protected static final int VERSION = MCVersions.V20W06A + 1; -+ -+ private V2505() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType brain = data.getMap("Brain"); -+ if (brain == null) { -+ return null; -+ } -+ -+ final MapType memories = brain.getMap("memories"); -+ if (memories == null) { -+ return null; -+ } -+ -+ for (final String key : memories.keys()) { -+ final Object value = memories.getGeneric(key); -+ -+ final MapType wrapped = Types.NBT.createEmptyMap(); -+ wrapped.setGeneric("value", value); -+ -+ memories.setMap(key, wrapped); -+ } -+ -+ return null; -+ } -+ }); -+ -+ registerMob("minecraft:piglin"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8cfd380e870ceea4892b8cd7bf855a6e5dc8cc6f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java -@@ -0,0 +1,24 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V2508 { -+ -+ protected static final int VERSION = MCVersions.V20W08A + 1; -+ -+ private V2508() {} -+ -+ public static void register() { -+ final Map remap = ImmutableMap.of( -+ "minecraft:warped_fungi", "minecraft:warped_fungus", -+ "minecraft:crimson_fungi", "minecraft:crimson_fungus" -+ ); -+ -+ ConverterAbstractBlockRename.register(VERSION, remap::get); -+ ConverterAbstractItemRename.register(VERSION, remap::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1fa93dec8a81719a19c0f7db589778935ce44d56 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java -@@ -0,0 +1,30 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2509 { -+ -+ protected static final int VERSION = MCVersions.V20W08A + 2; -+ -+ private V2509() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:zombie_pigman_spawn_egg", "minecraft:zombified_piglin_spawn_egg" -+ )::get); -+ ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of( -+ "minecraft:zombie_pigman", "minecraft:zombified_piglin" -+ )::get); -+ -+ registerMob("minecraft:zombified_piglin"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java -new file mode 100644 -index 0000000000000000000000000000000000000000..183ab7ed77e30bf87e71e5f682a59fc3a64a7672 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java -@@ -0,0 +1,97 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V2511 { -+ -+ protected static final int VERSION = MCVersions.V20W09A + 1; -+ -+ private V2511() {} -+ -+ private static int[] createUUIDArray(final long most, final long least) { -+ return new int[] { -+ (int)(most >>> 32), -+ (int)most, -+ (int)(least >>> 32), -+ (int)least -+ }; -+ } -+ -+ private static void setUUID(final MapType data, final long most, final long least) { -+ if (most != 0L && least != 0L) { -+ data.setInts("OwnerUUID", createUUIDArray(most, least)); -+ } -+ } -+ -+ public static void register() { -+ final DataConverter, MapType> throwableConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType owner = data.getMap("owner"); -+ data.remove("owner"); -+ if (owner == null) { -+ return null; -+ } -+ -+ setUUID(data, owner.getLong("M"), owner.getLong("L")); -+ -+ return null; -+ } -+ }; -+ final DataConverter, MapType> potionConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType potion = data.getMap("Potion"); -+ data.remove("Potion"); -+ -+ data.setMap("Item", potion == null ? Types.NBT.createEmptyMap() : potion); -+ -+ return null; -+ } -+ }; -+ final DataConverter, MapType> llamaSpitConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType owner = data.getMap("Owner"); -+ data.remove("Owner"); -+ if (owner == null) { -+ return null; -+ } -+ -+ setUUID(data, owner.getLong("OwnerUUIDMost"), owner.getLong("OwnerUUIDLeast")); -+ -+ return null; -+ } -+ }; -+ final DataConverter, MapType> arrowConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ setUUID(data, data.getLong("OwnerUUIDMost"), data.getLong("OwnerUUIDLeast")); -+ -+ data.remove("OwnerUUIDMost"); -+ data.remove("OwnerUUIDLeast"); -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:egg", throwableConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:ender_pearl", throwableConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:experience_bottle", throwableConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:snowball", throwableConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:potion", throwableConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:potion", potionConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:llama_spit", llamaSpitConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:arrow", arrowConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:spectral_arrow", arrowConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:trident", arrowConverter); -+ -+ // Vanilla migrates the potion item but does not change the schema. -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:potion", new DataWalkerItems("Item")); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a76bb0d8d27a734b397eefd587d1baf79bc55c82 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java -@@ -0,0 +1,590 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.common.collect.Sets; -+import java.util.Set; -+import java.util.UUID; -+ -+public final class V2514 { -+ -+ protected static final int VERSION = MCVersions.V20W11A + 1; -+ -+ private static final Set ABSTRACT_HORSES = Sets.newHashSet(); -+ private static final Set TAMEABLE_ANIMALS = Sets.newHashSet(); -+ private static final Set ANIMALS = Sets.newHashSet(); -+ private static final Set MOBS = Sets.newHashSet(); -+ private static final Set LIVING_ENTITIES = Sets.newHashSet(); -+ private static final Set PROJECTILES = Sets.newHashSet(); -+ static { -+ ABSTRACT_HORSES.add("minecraft:donkey"); -+ ABSTRACT_HORSES.add("minecraft:horse"); -+ ABSTRACT_HORSES.add("minecraft:llama"); -+ ABSTRACT_HORSES.add("minecraft:mule"); -+ ABSTRACT_HORSES.add("minecraft:skeleton_horse"); -+ ABSTRACT_HORSES.add("minecraft:trader_llama"); -+ ABSTRACT_HORSES.add("minecraft:zombie_horse"); -+ -+ TAMEABLE_ANIMALS.add("minecraft:cat"); -+ TAMEABLE_ANIMALS.add("minecraft:parrot"); -+ TAMEABLE_ANIMALS.add("minecraft:wolf"); -+ -+ ANIMALS.add("minecraft:bee"); -+ ANIMALS.add("minecraft:chicken"); -+ ANIMALS.add("minecraft:cow"); -+ ANIMALS.add("minecraft:fox"); -+ ANIMALS.add("minecraft:mooshroom"); -+ ANIMALS.add("minecraft:ocelot"); -+ ANIMALS.add("minecraft:panda"); -+ ANIMALS.add("minecraft:pig"); -+ ANIMALS.add("minecraft:polar_bear"); -+ ANIMALS.add("minecraft:rabbit"); -+ ANIMALS.add("minecraft:sheep"); -+ ANIMALS.add("minecraft:turtle"); -+ ANIMALS.add("minecraft:hoglin"); -+ -+ MOBS.add("minecraft:bat"); -+ MOBS.add("minecraft:blaze"); -+ MOBS.add("minecraft:cave_spider"); -+ MOBS.add("minecraft:cod"); -+ MOBS.add("minecraft:creeper"); -+ MOBS.add("minecraft:dolphin"); -+ MOBS.add("minecraft:drowned"); -+ MOBS.add("minecraft:elder_guardian"); -+ MOBS.add("minecraft:ender_dragon"); -+ MOBS.add("minecraft:enderman"); -+ MOBS.add("minecraft:endermite"); -+ MOBS.add("minecraft:evoker"); -+ MOBS.add("minecraft:ghast"); -+ MOBS.add("minecraft:giant"); -+ MOBS.add("minecraft:guardian"); -+ MOBS.add("minecraft:husk"); -+ MOBS.add("minecraft:illusioner"); -+ MOBS.add("minecraft:magma_cube"); -+ MOBS.add("minecraft:pufferfish"); -+ MOBS.add("minecraft:zombified_piglin"); -+ MOBS.add("minecraft:salmon"); -+ MOBS.add("minecraft:shulker"); -+ MOBS.add("minecraft:silverfish"); -+ MOBS.add("minecraft:skeleton"); -+ MOBS.add("minecraft:slime"); -+ MOBS.add("minecraft:snow_golem"); -+ MOBS.add("minecraft:spider"); -+ MOBS.add("minecraft:squid"); -+ MOBS.add("minecraft:stray"); -+ MOBS.add("minecraft:tropical_fish"); -+ MOBS.add("minecraft:vex"); -+ MOBS.add("minecraft:villager"); -+ MOBS.add("minecraft:iron_golem"); -+ MOBS.add("minecraft:vindicator"); -+ MOBS.add("minecraft:pillager"); -+ MOBS.add("minecraft:wandering_trader"); -+ MOBS.add("minecraft:witch"); -+ MOBS.add("minecraft:wither"); -+ MOBS.add("minecraft:wither_skeleton"); -+ MOBS.add("minecraft:zombie"); -+ MOBS.add("minecraft:zombie_villager"); -+ MOBS.add("minecraft:phantom"); -+ MOBS.add("minecraft:ravager"); -+ MOBS.add("minecraft:piglin"); -+ -+ LIVING_ENTITIES.add("minecraft:armor_stand"); -+ -+ PROJECTILES.add("minecraft:arrow"); -+ PROJECTILES.add("minecraft:dragon_fireball"); -+ PROJECTILES.add("minecraft:firework_rocket"); -+ PROJECTILES.add("minecraft:fireball"); -+ PROJECTILES.add("minecraft:llama_spit"); -+ PROJECTILES.add("minecraft:small_fireball"); -+ PROJECTILES.add("minecraft:snowball"); -+ PROJECTILES.add("minecraft:spectral_arrow"); -+ PROJECTILES.add("minecraft:egg"); -+ PROJECTILES.add("minecraft:ender_pearl"); -+ PROJECTILES.add("minecraft:experience_bottle"); -+ PROJECTILES.add("minecraft:potion"); -+ PROJECTILES.add("minecraft:trident"); -+ PROJECTILES.add("minecraft:wither_skull"); -+ } -+ -+ static int[] createUUIDArray(final long most, final long least) { -+ return new int[] { -+ (int)(most >>> 32), -+ (int)most, -+ (int)(least >>> 32), -+ (int)least -+ }; -+ } -+ -+ static int[] createUUIDFromString(final MapType data, final String path) { -+ if (data == null) { -+ return null; -+ } -+ -+ final String uuidString = data.getString(path); -+ if (uuidString == null) { -+ return null; -+ } -+ -+ try { -+ final UUID uuid = UUID.fromString(uuidString); -+ return createUUIDArray(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()); -+ } catch (final IllegalArgumentException ignore) { -+ return null; -+ } -+ } -+ -+ static int[] createUUIDFromLongs(final MapType data, final String most, final String least) { -+ if (data == null) { -+ return null; -+ } -+ -+ final long mostBits = data.getLong(most); -+ final long leastBits = data.getLong(least); -+ -+ return (mostBits != 0 || leastBits != 0) ? createUUIDArray(mostBits, leastBits) : null; -+ } -+ -+ static void replaceUUIDString(final MapType data, final String oldPath, final String newPath) { -+ final int[] newUUID = createUUIDFromString(data, oldPath); -+ if (newUUID != null) { -+ data.remove(oldPath); -+ data.setInts(newPath, newUUID); -+ } -+ } -+ -+ static void replaceUUIDMLTag(final MapType data, final String oldPath, final String newPath) { -+ final int[] uuid = createUUIDFromLongs(data.getMap(oldPath), "M", "L"); -+ if (uuid != null) { -+ data.remove(oldPath); -+ data.setInts(newPath, uuid); -+ } -+ } -+ -+ static void replaceUUIDLeastMost(final MapType data, final String prefix, final String newPath) { -+ final String mostPath = prefix.concat("Most"); -+ final String leastPath = prefix.concat("Least"); -+ -+ final int[] uuid = createUUIDFromLongs(data, mostPath, leastPath); -+ if (uuid != null) { -+ data.remove(mostPath); -+ data.remove(leastPath); -+ data.setInts(newPath, uuid); -+ } -+ } -+ -+ private V2514() {} -+ -+ private static void updatePiglin(final MapType data) { -+ final MapType brain = data.getMap("Brain"); -+ if (brain == null) { -+ return; -+ } -+ -+ final MapType memories = brain.getMap("memories"); -+ if (memories == null) { -+ return; -+ } -+ -+ final MapType angryAt = memories.getMap("minecraft:angry_at"); -+ -+ replaceUUIDString(angryAt, "value", "value"); -+ } -+ -+ private static void updateEvokerFangs(final MapType data) { -+ replaceUUIDLeastMost(data, "OwnerUUID", "Owner"); -+ } -+ -+ private static void updateZombieVillager(final MapType data) { -+ replaceUUIDLeastMost(data, "ConversionPlayer", "ConversionPlayer"); -+ } -+ -+ private static void updateAreaEffectCloud(final MapType data) { -+ replaceUUIDLeastMost(data, "OwnerUUID", "Owner"); -+ } -+ -+ private static void updateShulkerBullet(final MapType data) { -+ replaceUUIDMLTag(data, "Owner", "Owner"); -+ replaceUUIDMLTag(data, "Target", "Target"); -+ } -+ -+ private static void updateItem(final MapType data) { -+ replaceUUIDMLTag(data, "Owner", "Owner"); -+ replaceUUIDMLTag(data, "Thrower", "Thrower"); -+ } -+ -+ private static void updateFox(final MapType data) { -+ final ListType trustedUUIDS = data.getList("TrustedUUIDs", ObjectType.MAP); -+ if (trustedUUIDS == null) { -+ return; -+ } -+ -+ final ListType newUUIDs = Types.NBT.createEmptyList(); -+ data.remove("TrustedUUIDs"); -+ data.setList("Trusted", newUUIDs); -+ -+ for (int i = 0, len = trustedUUIDS.size(); i < len; ++i) { -+ final MapType uuid = trustedUUIDS.getMap(i); -+ final int[] newUUID = createUUIDFromLongs(uuid, "M", "L"); -+ if (newUUID != null) { -+ newUUIDs.addIntArray(newUUID); -+ } -+ } -+ } -+ -+ private static void updateHurtBy(final MapType data) { -+ replaceUUIDString(data, "HurtBy", "HurtBy"); -+ } -+ -+ private static void updateAnimalOwner(final MapType data) { -+ updateAnimal(data); -+ -+ replaceUUIDString(data, "OwnerUUID", "Owner"); -+ } -+ -+ private static void updateAnimal(final MapType data) { -+ updateMob(data); -+ -+ replaceUUIDLeastMost(data, "LoveCause", "LoveCause"); -+ } -+ -+ private static void updateMob(final MapType data) { -+ updateLivingEntity(data); -+ -+ final MapType leash = data.getMap("Leash"); -+ if (leash == null) { -+ return; -+ } -+ -+ replaceUUIDLeastMost(leash, "UUID", "UUID"); -+ } -+ -+ private static void updateLivingEntity(final MapType data) { -+ final ListType attributes = data.getList("Attributes", ObjectType.MAP); -+ if (attributes == null) { -+ return; -+ } -+ -+ for (int i = 0, len = attributes.size(); i < len; ++i) { -+ final MapType attribute = attributes.getMap(i); -+ -+ final ListType modifiers = attribute.getList("Modifiers", ObjectType.MAP); -+ if (modifiers == null) { -+ continue; -+ } -+ -+ for (int k = 0; k < modifiers.size(); ++k) { -+ replaceUUIDLeastMost(modifiers.getMap(k), "UUID", "UUID"); -+ } -+ } -+ } -+ -+ private static void updateProjectile(final MapType data) { -+ final Object ownerUUID = data.getGeneric("OwnerUUID"); -+ if (ownerUUID != null) { -+ data.remove("OwnerUUID"); -+ data.setGeneric("Owner", ownerUUID); -+ } -+ } -+ -+ private static void updateEntityUUID(final MapType data) { -+ replaceUUIDLeastMost(data, "UUID", "UUID"); -+ } -+ -+ public static void register() { -+ // Entity UUID fixes -+ -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateEntityUUID(data); -+ return null; -+ } -+ }); -+ -+ final DataConverter, MapType> animalOwnerConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateAnimalOwner(data); -+ return null; -+ } -+ }; -+ final DataConverter, MapType> animalConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateAnimal(data); -+ return null; -+ } -+ }; -+ final DataConverter, MapType> mobConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateMob(data); -+ return null; -+ } -+ }; -+ final DataConverter, MapType> livingEntityConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateLivingEntity(data); -+ return null; -+ } -+ }; -+ final DataConverter, MapType> projectileConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateProjectile(data); -+ return null; -+ } -+ }; -+ for (final String id : ABSTRACT_HORSES) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, animalOwnerConverter); -+ } -+ for (final String id : TAMEABLE_ANIMALS) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, animalOwnerConverter); -+ } -+ for (final String id : ANIMALS) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, animalConverter); -+ } -+ for (final String id : MOBS) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, mobConverter); -+ } -+ for (final String id : LIVING_ENTITIES) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, livingEntityConverter); -+ } -+ for (final String id : PROJECTILES) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, projectileConverter); -+ } -+ -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:bee", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateHurtBy(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombified_piglin", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateHurtBy(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:fox", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateFox(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:item", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateItem(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker_bullet", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateShulkerBullet(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:area_effect_cloud", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateAreaEffectCloud(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateZombieVillager(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:evoker_fangs", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateEvokerFangs(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:piglin", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updatePiglin(data); -+ return null; -+ } -+ }); -+ -+ -+ // Update TE -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:conduit", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ replaceUUIDMLTag(data, "target_uuid", "Target"); -+ return null; -+ } -+ }); -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:skull", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType owner = data.getMap("Owner"); -+ if (owner == null) { -+ return null; -+ } -+ -+ data.remove("Owner"); -+ -+ replaceUUIDString(owner, "Id", "Id"); -+ -+ data.setMap("SkullOwner", owner); -+ -+ return null; -+ } -+ }); -+ -+ // Player UUID -+ MCTypeRegistry.PLAYER.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateLivingEntity(data); -+ updateEntityUUID(data); -+ -+ final MapType rootVehicle = data.getMap("RootVehicle"); -+ if (rootVehicle == null) { -+ return null; -+ } -+ -+ replaceUUIDLeastMost(rootVehicle, "Attach", "Attach"); -+ -+ return null; -+ } -+ }); -+ -+ // Level.dat -+ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ replaceUUIDString(data, "WanderingTraderId", "WanderingTraderId"); -+ -+ final MapType dimensionData = data.getMap("DimensionData"); -+ if (dimensionData != null) { -+ for (final String key : dimensionData.keys()) { -+ final MapType dimension = dimensionData.getMap(key); -+ -+ final MapType dragonFight = dimension.getMap("DragonFight"); -+ if (dragonFight == null) { -+ continue; -+ } -+ -+ replaceUUIDLeastMost(dragonFight, "DragonUUID", "Dragon"); -+ } -+ } -+ -+ final MapType customBossEvents = data.getMap("CustomBossEvents"); -+ if (customBossEvents != null) { -+ for (final String key : customBossEvents.keys()) { -+ final MapType customBossEvent = customBossEvents.getMap(key); -+ -+ final ListType players = customBossEvent.getList("Players", ObjectType.MAP); -+ if (players == null) { -+ continue; -+ } -+ -+ final ListType newPlayers = Types.NBT.createEmptyList(); -+ customBossEvent.setList("Players", newPlayers); -+ -+ for (int i = 0, len = players.size(); i < len; ++i) { -+ final int[] newUUID = createUUIDFromLongs(players.getMap(i), "M", "L"); -+ if (newUUID != null) { -+ newPlayers.addIntArray(newUUID); -+ } -+ } -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.SAVED_DATA_RAIDS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { -+ final MapType data = root.getMap("data"); -+ if (data == null) { -+ return null; -+ } -+ -+ final ListType raids = data.getList("Raids", ObjectType.MAP); -+ if (raids == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = raids.size(); i < len; ++i) { -+ final MapType raid = raids.getMap(i); -+ -+ final ListType heros = raid.getList("HeroesOfTheVillage", ObjectType.MAP); -+ -+ if (heros == null) { -+ continue; -+ } -+ -+ final ListType newHeros = Types.NBT.createEmptyList(); -+ raid.setList("HeroesOfTheVillage", newHeros); -+ -+ for (int k = 0, klen = heros.size(); k < klen; ++k) { -+ final MapType uuidOld = heros.getMap(i); -+ final int[] uuidNew = createUUIDFromLongs(uuidOld, "UUIDMost", "UUIDLeast"); -+ if (uuidNew != null) { -+ newHeros.addIntArray(uuidNew); -+ } -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ updateAttributeModifiers(tag); -+ -+ if ("minecraft:player_head".equals(data.getString("id"))) { -+ updateSkullOwner(tag); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private static void updateAttributeModifiers(final MapType tag) { -+ final ListType attributes = tag.getList("AttributeModifiers", ObjectType.MAP); -+ if (attributes == null) { -+ return; -+ } -+ -+ for (int i = 0, len = attributes.size(); i < len; ++i) { -+ replaceUUIDLeastMost(attributes.getMap(i), "UUID", "UUID"); -+ } -+ } -+ -+ private static void updateSkullOwner(final MapType tag) { -+ replaceUUIDString(tag.getMap("SkullOwner"), "Id", "Id"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java -new file mode 100644 -index 0000000000000000000000000000000000000000..40bf0a1788520bbf1d66da53b6532bdd8af246f4 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java -@@ -0,0 +1,37 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V2516 { -+ -+ protected static final int VERSION = MCVersions.V20W12A + 1; -+ -+ private V2516() {} -+ -+ public static void register() { -+ final DataConverter, MapType> gossipUUIDConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType gossips = data.getList("Gossips", ObjectType.MAP); -+ -+ if (gossips == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = gossips.size(); i < len; ++i) { -+ V2514.replaceUUIDLeastMost(gossips.getMap(i), "Target", "Target"); -+ } -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", gossipUUIDConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", gossipUUIDConverter); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4d8692bbb497b1e6f0aa655e8f9d1fb743864f7d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java -@@ -0,0 +1,66 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V2518 { -+ -+ protected static final int VERSION = MCVersions.V20W12A + 3; -+ -+ private static final Map FACING_RENAMES = new HashMap<>( -+ ImmutableMap.builder() -+ .put("down", "down_south") -+ .put("up", "up_north") -+ .put("north", "north_up") -+ .put("south", "south_up") -+ .put("west", "west_up") -+ .put("east", "east_up") -+ .build() -+ ); -+ -+ -+ private V2518() {} -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jigsaw", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String type = data.getString("attachement_type", "minecraft:empty"); -+ final String pool = data.getString("target_pool", "minecraft:empty"); -+ data.remove("attachement_type"); -+ data.remove("target_pool"); -+ -+ data.setString("name", type); -+ data.setString("target", type); -+ data.setString("pool", pool); -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!"minecraft:jigsaw".equals(data.getString("Name"))) { -+ return null; -+ } -+ -+ final MapType properties = data.getMap("Properties"); -+ if (properties == null) { -+ return null; -+ } -+ -+ final String facing = properties.getString("facing", "north"); -+ properties.remove("facing"); -+ properties.setString("orientation", FACING_RENAMES.getOrDefault(facing, facing)); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java -new file mode 100644 -index 0000000000000000000000000000000000000000..47a8bb340e72e48fd237d4001b55c81b1182a72f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2519 { -+ -+ protected static final int VERSION = MCVersions.V20W12A + 4; -+ -+ private V2519() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:strider"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3a91600427cb013cdfc03b084017b6f32d9e05fa ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2522 { -+ -+ protected static final int VERSION = MCVersions.V20W13B + 1; -+ -+ private V2522() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:zoglin"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2c64d65c8cb34fbdd79688697e9a7f5ecb615bff ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java -@@ -0,0 +1,95 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V2523 { -+ -+ protected static final int VERSION = MCVersions.V20W13B + 2; -+ -+ private static final Map RENAMES = new HashMap<>( -+ ImmutableMap.builder() -+ .put("generic.maxHealth", "generic.max_health") -+ .put("Max Health", "generic.max_health") -+ .put("zombie.spawnReinforcements", "zombie.spawn_reinforcements") -+ .put("Spawn Reinforcements Chance", "zombie.spawn_reinforcements") -+ .put("horse.jumpStrength", "horse.jump_strength") -+ .put("Jump Strength", "horse.jump_strength") -+ .put("generic.followRange", "generic.follow_range") -+ .put("Follow Range", "generic.follow_range") -+ .put("generic.knockbackResistance", "generic.knockback_resistance") -+ .put("Knockback Resistance", "generic.knockback_resistance") -+ .put("generic.movementSpeed", "generic.movement_speed") -+ .put("Movement Speed", "generic.movement_speed") -+ .put("generic.flyingSpeed", "generic.flying_speed") -+ .put("Flying Speed", "generic.flying_speed") -+ .put("generic.attackDamage", "generic.attack_damage") -+ .put("generic.attackKnockback", "generic.attack_knockback") -+ .put("generic.attackSpeed", "generic.attack_speed") -+ .put("generic.armorToughness", "generic.armor_toughness") -+ .build() -+ ); -+ -+ private V2523() {} -+ -+ private static void updateName(final MapType data, final String path) { -+ if (data == null) { -+ return; -+ } -+ -+ final String name = data.getString(path); -+ if (name != null) { -+ final String renamed = RENAMES.get(name); -+ if (renamed != null) { -+ data.setString(path, renamed); -+ } -+ } -+ } -+ -+ public static void register() { -+ final DataConverter, MapType> entityConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType attributes = data.getList("Attributes", ObjectType.MAP); -+ -+ if (attributes == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = attributes.size(); i < len; ++i) { -+ updateName(attributes.getMap(i), "Name"); -+ } -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.ENTITY.addStructureConverter(entityConverter); -+ MCTypeRegistry.PLAYER.addStructureConverter(entityConverter); -+ -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType attributes = data.getList("AttributeModifiers", ObjectType.MAP); -+ -+ if (attributes == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = attributes.size(); i < len; ++i) { -+ updateName(attributes.getMap(i), "AttributeName"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5e951f91d03f95ed671bb7403592960690c65879 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java -@@ -0,0 +1,123 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.mojang.datafixers.DataFixUtils; -+import net.minecraft.util.Mth; -+ -+public final class V2527 { -+ -+ protected static final int VERSION = MCVersions.V20W16A + 1; -+ -+ private V2527() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ -+ if (level == null) { -+ return null; -+ } -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ final ListType palette = section.getList("Palette", ObjectType.MAP); -+ -+ if (palette == null) { -+ continue; -+ } -+ -+ final int bits = Math.max(4, DataFixUtils.ceillog2(palette.size())); -+ -+ if (Mth.isPowerOfTwo(bits)) { -+ // fits perfectly -+ continue; -+ } -+ -+ final long[] states = section.getLongs("BlockStates"); -+ if (states == null) { -+ // wat -+ continue; -+ } -+ -+ section.setLongs("BlockStates", addPadding(4096, bits, states)); -+ } -+ } -+ -+ final MapType heightMaps = level.getMap("Heightmaps"); -+ if (heightMaps != null) { -+ for (final String key : heightMaps.keys()) { -+ final long[] old = heightMaps.getLongs(key); -+ heightMaps.setLongs(key, addPadding(256, 9, old)); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ public static long[] addPadding(final int indices, final int bits, final long[] old) { -+ int k = old.length; -+ if (k == 0) { -+ return old; -+ } else { -+ long l = (1L << bits) - 1L; -+ int m = 64 / bits; -+ int n = (indices + m - 1) / m; -+ long[] padded = new long[n]; -+ int o = 0; -+ int p = 0; -+ long q = 0L; -+ int r = 0; -+ long s = old[0]; -+ long t = k > 1 ? old[1] : 0L; -+ -+ for(int u = 0; u < indices; ++u) { -+ int v = u * bits; -+ int w = v >> 6; -+ int x = (u + 1) * bits - 1 >> 6; -+ int y = v ^ w << 6; -+ if (w != r) { -+ s = t; -+ t = w + 1 < k ? old[w + 1] : 0L; -+ r = w; -+ } -+ -+ long ab; -+ int ac; -+ if (w == x) { -+ ab = s >>> y & l; -+ } else { -+ ac = 64 - y; -+ ab = (s >>> y | t << ac) & l; -+ } -+ -+ ac = p + bits; -+ if (ac >= 64) { -+ padded[o++] = q; -+ q = ab; -+ p = bits; -+ } else { -+ q |= ab << p; -+ p = ac; -+ } -+ } -+ -+ if (q != 0L) { -+ padded[o] = q; -+ } -+ -+ return padded; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f7f2e3f75a9f8a5533fe010bc23e8384878d7ce8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java -@@ -0,0 +1,25 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2528 { -+ -+ protected static final int VERSION = MCVersions.V20W16A + 2; -+ -+ private V2528() {} -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:soul_fire_torch", "minecraft:soul_torch", -+ "minecraft:soul_fire_lantern", "minecraft:soul_lantern" -+ )::get); -+ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( -+ "minecraft:soul_fire_torch", "minecraft:soul_torch", -+ "minecraft:soul_fire_wall_torch", "minecraft:soul_wall_torch", -+ "minecraft:soul_fire_lantern", "minecraft:soul_lantern" -+ )::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f239dbf4beb87efd1fff4e5d8d6f041fa8687f01 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java -@@ -0,0 +1,25 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2529 { -+ -+ protected static final int VERSION = MCVersions.V20W17A; -+ -+ private V2529() {} -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:strider", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getBoolean("NoGravity")) { -+ data.setBoolean("NoGravity", false); -+ } -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7783d75578d29e09029b26c8c8c0b053c5526eb9 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java -@@ -0,0 +1,63 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2531 { -+ -+ protected static final int VERSION = MCVersions.V20W17A + 2; -+ -+ private V2531() {} -+ -+ private static boolean isConnected(final String facing) { -+ return !"none".equals(facing); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!"minecraft:redstone_wire".equals(data.getString("Name"))) { -+ return null; -+ } -+ -+ final MapType properties = data.getMap("Properties"); -+ -+ if (properties == null) { -+ return null; -+ } -+ -+ -+ final String east = properties.getString("east", "none"); -+ final String west = properties.getString("west", "none"); -+ final String north = properties.getString("north", "none"); -+ final String south = properties.getString("south", "none"); -+ -+ final boolean connectedX = isConnected(east) || isConnected(west); -+ final boolean connectedZ = isConnected(north) || isConnected(south); -+ -+ final String newEast = !isConnected(east) && !connectedZ ? "side" : east; -+ final String newWest = !isConnected(west) && !connectedZ ? "side" : west; -+ final String newNorth = !isConnected(north) && !connectedX ? "side" : north; -+ final String newSouth = !isConnected(south) && !connectedX ? "side" : south; -+ -+ if (properties.hasKey("east")) { -+ properties.setString("east", newEast); -+ } -+ if (properties.hasKey("west")) { -+ properties.setString("west", newWest); -+ } -+ if (properties.hasKey("north")) { -+ properties.setString("north", newNorth); -+ } -+ if (properties.hasKey("south")) { -+ properties.setString("south", newSouth); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ece1cd5afab80a8271b2ebac95dcc0a6239cd42c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java -@@ -0,0 +1,43 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V2533 { -+ -+ protected static final int VERSION = MCVersions.V20W18A + 1; -+ -+ private V2533() {} -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType attributes = data.getList("Attributes", ObjectType.MAP); -+ -+ if (attributes == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = attributes.size(); i < len; ++i) { -+ final MapType attribute = attributes.getMap(i); -+ -+ if (!"generic.follow_range".equals(attribute.getString("Name"))) { -+ continue; -+ } -+ -+ if (attribute.getDouble("Base") == 16.0) { -+ attribute.setDouble("Base", 48.0); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9648299bb96c20c783bb7c7010173a0f007584e0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java -@@ -0,0 +1,34 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V2535 { -+ -+ protected static final int VERSION = MCVersions.V20W19A + 1; -+ -+ private V2535() {} -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ // Mojang uses doubles for whatever reason... rotation is in FLOAT. by using double here -+ // the entity load will just ignore rotation and set it to 0... -+ final ListType rotation = data.getList("Rotation", ObjectType.FLOAT); -+ -+ if (rotation == null || rotation.size() == 0) { -+ return null; -+ } -+ -+ rotation.setFloat(0, rotation.getFloat(0) - 180.0F); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java -new file mode 100644 -index 0000000000000000000000000000000000000000..17ec2cdecdd794739f5eca5242b3a12211adf1bc ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java -@@ -0,0 +1,41 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2538 { -+ -+ private static final int VERSION = MCVersions.V20W20B + 1; -+ private static final String[] MERGE_KEYS = new String[] { -+ "RandomSeed", -+ "generatorName", -+ "generatorOptions", -+ "generatorVersion", -+ "legacy_custom_options", -+ "MapFeatures", -+ "BonusChest" -+ }; -+ -+ public static void register() { -+ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType worldGenSettings = data.getOrCreateMap("WorldGenSettings"); -+ -+ for (final String key : MERGE_KEYS) { -+ final Object value = data.getGeneric(key); -+ if (value == null) { -+ continue; -+ } -+ -+ data.remove(key); -+ worldGenSettings.setGeneric(key, value); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java -new file mode 100644 -index 0000000000000000000000000000000000000000..682b6f16c23ac9ce1a683bac6d36e5d07804b35d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java -@@ -0,0 +1,344 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.common.collect.ImmutableMap; -+import org.apache.commons.lang3.math.NumberUtils; -+import java.util.HashMap; -+import java.util.Locale; -+import java.util.Map; -+ -+public final class V2550 { -+ -+ protected static final int VERSION = MCVersions.V20W20B + 13; -+ -+ private static final Map DEFAULTS = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:village", new StructureFeatureConfiguration(32, 8, 10387312)) -+ .put("minecraft:desert_pyramid", new StructureFeatureConfiguration(32, 8, 14357617)) -+ .put("minecraft:igloo", new StructureFeatureConfiguration(32, 8, 14357618)) -+ .put("minecraft:jungle_pyramid", new StructureFeatureConfiguration(32, 8, 14357619)) -+ .put("minecraft:swamp_hut", new StructureFeatureConfiguration(32, 8, 14357620)) -+ .put("minecraft:pillager_outpost", new StructureFeatureConfiguration(32, 8, 165745296)) -+ .put("minecraft:monument", new StructureFeatureConfiguration(32, 5, 10387313)) -+ .put("minecraft:endcity", new StructureFeatureConfiguration(20, 11, 10387313)) -+ .put("minecraft:mansion", new StructureFeatureConfiguration(80, 20, 10387319)) -+ .build() -+ ); -+ -+ record StructureFeatureConfiguration(int spacing, int separation, int salt) { -+ -+ public MapType serialize() { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setInt("spacing", this.spacing); -+ ret.setInt("separation", this.separation); -+ ret.setInt("salt", this.salt); -+ -+ return ret; -+ } -+ } -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final long seed = data.getLong("RandomSeed"); -+ String generatorName = data.getString("generatorName"); -+ if (generatorName != null) { -+ generatorName = generatorName.toLowerCase(Locale.ROOT); -+ } -+ String legacyCustomOptions = data.getString("legacy_custom_options"); -+ if (legacyCustomOptions == null) { -+ legacyCustomOptions = "customized".equals(generatorName) ? data.getString("generatorOptions") : null; -+ } -+ -+ final MapType generator; -+ boolean caves = false; -+ -+ if ("customized".equals(generatorName) || generatorName == null) { -+ generator = defaultOverworld(seed); -+ } else { -+ switch (generatorName) { -+ case "flat": { -+ final MapType generatorOptions = data.getMap("generatorOptions"); -+ -+ final MapType structures = fixFlatStructures(generatorOptions); -+ final MapType settings = Types.NBT.createEmptyMap(); -+ generator = Types.NBT.createEmptyMap(); -+ generator.setString("type", "minecraft:flat"); -+ generator.setMap("settings", settings); -+ -+ settings.setMap("structures", structures); -+ -+ ListType layers = generatorOptions.getList("layers", ObjectType.MAP); -+ if (layers == null) { -+ layers = Types.NBT.createEmptyList(); -+ -+ final int[] heights = new int[] { 1, 2, 1 }; -+ final String[] blocks = new String[] { "minecraft:bedrock", "minecraft:dirt", "minecraft:grass_block" }; -+ for (int i = 0; i < 3; ++i) { -+ final MapType layer = Types.NBT.createEmptyMap(); -+ layer.setInt("height", heights[i]); -+ layer.setString("block", blocks[i]); -+ layers.addMap(layer); -+ } -+ } -+ -+ settings.setList("layers", layers); -+ settings.setString("biome", generatorOptions.getString("biome", "minecraft:plains")); -+ -+ break; -+ } -+ -+ case "debug_all_block_states": { -+ generator = Types.NBT.createEmptyMap(); -+ generator.setString("type", "minecraft:debug"); -+ break; -+ } -+ -+ case "buffet": { -+ final MapType generatorOptions = data.getMap("generatorOptions"); -+ final MapType chunkGenerator = generatorOptions == null ? null : generatorOptions.getMap("chunk_generator"); -+ final String chunkGeneratorType = chunkGenerator == null ? null : chunkGenerator.getString("type"); -+ -+ final String newType; -+ if ("minecraft:caves".equals(chunkGeneratorType)) { -+ newType = "minecraft:caves"; -+ caves = true; -+ } else if ("minecraft:floating_islands".equals(chunkGeneratorType)) { -+ newType = "minecraft:floating_islands"; -+ } else { -+ newType = "minecraft:overworld"; -+ } -+ -+ MapType biomeSource = generatorOptions == null ? null : generatorOptions.getMap("biome_source"); -+ if (biomeSource == null) { -+ biomeSource = Types.NBT.createEmptyMap(); -+ biomeSource.setString("type", "minecraft:fixed"); -+ } -+ -+ if ("minecraft:fixed".equals(biomeSource.getString("type"))) { -+ final MapType options = biomeSource.getMap("options"); -+ final ListType biomes = options == null ? null : options.getList("biomes", ObjectType.STRING); -+ final String biome = biomes == null || biomes.size() == 0 ? "minecraft:ocean" : biomes.getString(0); -+ biomeSource.remove("options"); -+ biomeSource.setString("biome", biome); -+ } -+ -+ generator = noise(seed, newType, biomeSource); -+ break; -+ } -+ -+ default: { -+ boolean defaultGen = generatorName.equals("default"); -+ boolean default11Gen = generatorName.equals("default_1_1") || defaultGen && data.getInt("generatorVersion") == 0; -+ boolean amplified = generatorName.equals("amplified"); -+ boolean largeBiomes = generatorName.equals("largebiomes"); -+ -+ generator = noise(seed, amplified ? "minecraft:amplified" : "minecraft:overworld", -+ vanillaBiomeSource(seed, default11Gen, largeBiomes)); -+ break; -+ } -+ } -+ } -+ -+ final boolean mapFeatures = data.getBoolean("MapFeatures", true); -+ final boolean bonusChest = data.getBoolean("BonusChest", false); -+ -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setLong("seed", seed); -+ ret.setBoolean("generate_features", mapFeatures); -+ ret.setBoolean("bonus_chest", bonusChest); -+ ret.setMap("dimensions", vanillaLevels(seed, generator, caves)); -+ if (legacyCustomOptions != null) { -+ ret.setString("legacy_custom_options", legacyCustomOptions); -+ } -+ -+ return ret; -+ } -+ }); -+ } -+ -+ public static MapType noise(final long seed, final String worldType, final MapType biomeSource) { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setString("type", "minecraft:noise"); -+ ret.setMap("biome_source", biomeSource); -+ ret.setLong("seed", seed); -+ ret.setString("settings", worldType); -+ -+ return ret; -+ } -+ -+ public static MapType vanillaBiomeSource(final long seed, final boolean default11Gen, final boolean largeBiomes) { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setString("type", "minecraft:vanilla_layered"); -+ ret.setLong("seed", seed); -+ ret.setBoolean("large_biomes", largeBiomes); -+ if (default11Gen) { -+ ret.setBoolean("legacy_biome_init_layer", default11Gen); -+ } -+ -+ return ret; -+ } -+ -+ public static MapType fixFlatStructures(final MapType generatorOptions) { -+ int distance = 32; -+ int spread = 3; -+ int count = 128; -+ boolean stronghold = false; -+ final Map newStructures = new HashMap<>(); -+ -+ if (generatorOptions == null) { -+ stronghold = true; -+ newStructures.put("minecraft:village", DEFAULTS.get("minecraft:village")); -+ } -+ -+ final MapType oldStructures = generatorOptions == null ? null : generatorOptions.getMap("structures"); -+ if (oldStructures != null) { -+ for (final String structureName : oldStructures.keys()) { -+ final MapType structureValues = oldStructures.getMap(structureName); -+ if (structureValues == null) { -+ continue; -+ } -+ -+ for (final String structureValueKey : structureValues.keys()) { -+ final String structureValue = structureValues.getString(structureValueKey); -+ -+ if ("stronghold".equals(structureName)) { -+ stronghold = true; -+ switch (structureValueKey) { -+ case "distance": -+ distance = getInt(structureValue, distance, 1); -+ break; -+ case "spread": -+ spread = getInt(structureValue, spread, 1); -+ break; -+ case "count": -+ count = getInt(structureValue, count, 1); -+ break; -+ } -+ } else { -+ switch (structureValueKey) { -+ case "distance": -+ switch (structureName) { -+ case "village": -+ setSpacing(newStructures, "minecraft:village", structureValue, 9); -+ break; -+ case "biome_1": -+ setSpacing(newStructures, "minecraft:desert_pyramid", structureValue, 9); -+ setSpacing(newStructures, "minecraft:igloo", structureValue, 9); -+ setSpacing(newStructures, "minecraft:jungle_pyramid", structureValue, 9); -+ setSpacing(newStructures, "minecraft:swamp_hut", structureValue, 9); -+ setSpacing(newStructures, "minecraft:pillager_outpost", structureValue, 9); -+ break; -+ case "endcity": -+ setSpacing(newStructures, "minecraft:endcity", structureValue, 1); -+ break; -+ case "mansion": -+ setSpacing(newStructures, "minecraft:mansion", structureValue, 1); -+ break; -+ default: -+ break; -+ } -+ case "separation": -+ if ("oceanmonument".equals(structureName)) { -+ final StructureFeatureConfiguration structure = newStructures.getOrDefault("minecraft:monument", DEFAULTS.get("minecraft:monument")); -+ final int newSpacing = getInt(structureValue, structure.separation, 1); -+ newStructures.put("minecraft:monument", new StructureFeatureConfiguration(newSpacing, structure.separation, structure.salt)); -+ } -+ -+ break; -+ case "spacing": -+ if ("oceanmonument".equals(structureName)) { -+ setSpacing(newStructures, "minecraft:monument", structureValue, 1); -+ } -+ -+ break; -+ } -+ } -+ } -+ } -+ } -+ -+ final MapType ret = Types.NBT.createEmptyMap(); -+ final MapType structuresSerialized = Types.NBT.createEmptyMap(); -+ ret.setMap("structures", structuresSerialized); -+ for (final String key : newStructures.keySet()) { -+ structuresSerialized.setMap(key, newStructures.get(key).serialize()); -+ } -+ -+ if (stronghold) { -+ final MapType strongholdData = Types.NBT.createEmptyMap(); -+ ret.setMap("stronghold", strongholdData); -+ -+ strongholdData.setInt("distance", distance); -+ strongholdData.setInt("spread", spread); -+ strongholdData.setInt("count", count); -+ } -+ -+ return ret; -+ } -+ -+ public static MapType vanillaLevels(final long seed, final MapType generator, final boolean caves) { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ final MapType overworld = Types.NBT.createEmptyMap(); -+ final MapType nether = Types.NBT.createEmptyMap(); -+ final MapType end = Types.NBT.createEmptyMap(); -+ -+ ret.setMap("minecraft:overworld", overworld); -+ ret.setMap("minecraft:the_nether", nether); -+ ret.setMap("minecraft:the_end", end); -+ -+ // overworld -+ overworld.setString("type", caves ? "minecraft:overworld_caves" : "minecraft:overworld"); -+ overworld.setMap("generator", generator); -+ -+ // nether -+ nether.setString("type", "minecraft:the_nether"); -+ final MapType netherBiomeSource = Types.NBT.createEmptyMap(); -+ netherBiomeSource.setString("type", "minecraft:multi_noise"); -+ netherBiomeSource.setLong("seed", seed); -+ netherBiomeSource.setString("preset", "minecraft:nether"); -+ -+ nether.setMap("generator", noise(seed, "minecraft:nether", netherBiomeSource)); -+ -+ // end -+ end.setString("type", "minecraft:the_end"); -+ final MapType endBiomeSource = Types.NBT.createEmptyMap(); -+ endBiomeSource.setString("type", "minecraft:the_end"); -+ endBiomeSource.setLong("seed", seed); -+ end.setMap("generator", noise(seed,"minecraft:end", endBiomeSource)); -+ -+ return ret; -+ } -+ -+ public static MapType defaultOverworld(final long seed) { -+ return noise(seed, "minecraft:overworld", vanillaBiomeSource(seed, false, false)); -+ } -+ -+ private static int getInt(final String value, final int dfl) { -+ return NumberUtils.toInt(value, dfl); -+ } -+ -+ private static int getInt(final String value, final int dfl, final int minVal) { -+ return Math.max(minVal, getInt(value, dfl)); -+ } -+ -+ private static void setSpacing(final Map structures, final String structureName, -+ final String value, final int minVal) { -+ final StructureFeatureConfiguration structure = structures.getOrDefault(structureName, DEFAULTS.get(structureName)); -+ final int newSpacing = getInt(value, structure.spacing, minVal); -+ -+ structures.put(structureName, new StructureFeatureConfiguration(newSpacing, structure.separation, structure.salt)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ac0c4475556fe5202a6aa5724cb47b35c0cc9c00 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java -@@ -0,0 +1,101 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V2551 { -+ -+ protected static final int VERSION = MCVersions.V20W20B + 14; -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType dimensions = data.getMap("dimensions"); -+ -+ if (dimensions == null) { -+ return null; -+ } -+ -+ for (final String dimension : dimensions.keys()) { -+ final MapType dimensionData = dimensions.getMap(dimension); -+ if (dimensionData == null) { -+ continue; -+ } -+ -+ final MapType generator = dimensionData.getMap("generator"); -+ if (generator == null) { -+ continue; -+ } -+ -+ final String type = generator.getString("type"); -+ if (type == null) { -+ continue; -+ } -+ -+ switch (type) { -+ case "minecraft:flat": { -+ final MapType settings = generator.getMap("settings"); -+ if (settings == null) { -+ continue; -+ } -+ -+ WalkerUtils.convert(MCTypeRegistry.BIOME, settings, "biome", fromVersion, toVersion); -+ -+ final ListType layers = settings.getList("layers", ObjectType.MAP); -+ if (layers != null) { -+ for (int i = 0, len = layers.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, layers.getMap(i), "block", fromVersion, toVersion); -+ } -+ } -+ -+ break; -+ } -+ case "minecraft:noise": { -+ final MapType settings = generator.getMap("settings"); -+ if (settings != null) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_block", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_fluid", fromVersion, toVersion); -+ } -+ -+ final MapType biomeSource = generator.getMap("biome_source"); -+ if (biomeSource != null) { -+ final String biomeSourceType = biomeSource.getString("type", ""); -+ switch (biomeSourceType) { -+ case "minecraft:fixed": { -+ WalkerUtils.convert(MCTypeRegistry.BIOME, biomeSource, "biome", fromVersion, toVersion); -+ break; -+ } -+ -+ case "minecraft:multi_noise": { -+ // Vanilla's schema is wrong. It should be DSL.fields("biomes", DSL.list(DSL.fields("biome"))) -+ // But it just contains the list part. That obviously can never be the case, because -+ // the root object is a compound, not a list. -+ -+ final ListType biomes = biomeSource.getList("biomes", ObjectType.MAP); -+ if (biomes != null) { -+ for (int i = 0, len = biomes.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BIOME, biomes.getMap(i), "biome", fromVersion, toVersion); -+ } -+ } -+ break; -+ } -+ -+ case "minecraft:checkerboard": { -+ WalkerUtils.convertList(MCTypeRegistry.BIOME, biomeSource, "biomes", fromVersion, toVersion); -+ break; -+ } -+ } -+ } -+ -+ break; -+ } -+ } -+ } -+ -+ return null; -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c06aa2099494f82bad2c1f212b2db07405f8f6ff ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2552 { -+ -+ protected static final int VERSION = MCVersions.V20W20B + 15; -+ -+ private V2552() {} -+ -+ public static void register() { -+ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, ImmutableMap.of( -+ "minecraft:nether", "minecraft:nether_wastes" -+ )::get); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java -new file mode 100644 -index 0000000000000000000000000000000000000000..492f615ff66d7f7ea820492dadefec2dd65cc051 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java -@@ -0,0 +1,78 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V2553 { -+ -+ protected static final int VERSION = MCVersions.V20W20B + 16; -+ -+ public static final Map BIOME_RENAMES = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:extreme_hills", "minecraft:mountains") -+ .put("minecraft:swampland", "minecraft:swamp") -+ .put("minecraft:hell", "minecraft:nether_wastes") -+ .put("minecraft:sky", "minecraft:the_end") -+ .put("minecraft:ice_flats", "minecraft:snowy_tundra") -+ .put("minecraft:ice_mountains", "minecraft:snowy_mountains") -+ .put("minecraft:mushroom_island", "minecraft:mushroom_fields") -+ .put("minecraft:mushroom_island_shore", "minecraft:mushroom_field_shore") -+ .put("minecraft:beaches", "minecraft:beach") -+ .put("minecraft:forest_hills", "minecraft:wooded_hills") -+ .put("minecraft:smaller_extreme_hills", "minecraft:mountain_edge") -+ .put("minecraft:stone_beach", "minecraft:stone_shore") -+ .put("minecraft:cold_beach", "minecraft:snowy_beach") -+ .put("minecraft:roofed_forest", "minecraft:dark_forest") -+ .put("minecraft:taiga_cold", "minecraft:snowy_taiga") -+ .put("minecraft:taiga_cold_hills", "minecraft:snowy_taiga_hills") -+ .put("minecraft:redwood_taiga", "minecraft:giant_tree_taiga") -+ .put("minecraft:redwood_taiga_hills", "minecraft:giant_tree_taiga_hills") -+ .put("minecraft:extreme_hills_with_trees", "minecraft:wooded_mountains") -+ .put("minecraft:savanna_rock", "minecraft:savanna_plateau") -+ .put("minecraft:mesa", "minecraft:badlands") -+ .put("minecraft:mesa_rock", "minecraft:wooded_badlands_plateau") -+ .put("minecraft:mesa_clear_rock", "minecraft:badlands_plateau") -+ .put("minecraft:sky_island_low", "minecraft:small_end_islands") -+ .put("minecraft:sky_island_medium", "minecraft:end_midlands") -+ .put("minecraft:sky_island_high", "minecraft:end_highlands") -+ .put("minecraft:sky_island_barren", "minecraft:end_barrens") -+ .put("minecraft:void", "minecraft:the_void") -+ .put("minecraft:mutated_plains", "minecraft:sunflower_plains") -+ .put("minecraft:mutated_desert", "minecraft:desert_lakes") -+ .put("minecraft:mutated_extreme_hills", "minecraft:gravelly_mountains") -+ .put("minecraft:mutated_forest", "minecraft:flower_forest") -+ .put("minecraft:mutated_taiga", "minecraft:taiga_mountains") -+ .put("minecraft:mutated_swampland", "minecraft:swamp_hills") -+ .put("minecraft:mutated_ice_flats", "minecraft:ice_spikes") -+ .put("minecraft:mutated_jungle", "minecraft:modified_jungle") -+ .put("minecraft:mutated_jungle_edge", "minecraft:modified_jungle_edge") -+ .put("minecraft:mutated_birch_forest", "minecraft:tall_birch_forest") -+ .put("minecraft:mutated_birch_forest_hills", "minecraft:tall_birch_hills") -+ .put("minecraft:mutated_roofed_forest", "minecraft:dark_forest_hills") -+ .put("minecraft:mutated_taiga_cold", "minecraft:snowy_taiga_mountains") -+ .put("minecraft:mutated_redwood_taiga", "minecraft:giant_spruce_taiga") -+ .put("minecraft:mutated_redwood_taiga_hills", "minecraft:giant_spruce_taiga_hills") -+ .put("minecraft:mutated_extreme_hills_with_trees", "minecraft:modified_gravelly_mountains") -+ .put("minecraft:mutated_savanna", "minecraft:shattered_savanna") -+ .put("minecraft:mutated_savanna_rock", "minecraft:shattered_savanna_plateau") -+ .put("minecraft:mutated_mesa", "minecraft:eroded_badlands") -+ .put("minecraft:mutated_mesa_rock", "minecraft:modified_wooded_badlands_plateau") -+ .put("minecraft:mutated_mesa_clear_rock", "minecraft:modified_badlands_plateau") -+ .put("minecraft:warm_deep_ocean", "minecraft:deep_warm_ocean") -+ .put("minecraft:lukewarm_deep_ocean", "minecraft:deep_lukewarm_ocean") -+ .put("minecraft:cold_deep_ocean", "minecraft:deep_cold_ocean") -+ .put("minecraft:frozen_deep_ocean", "minecraft:deep_frozen_ocean") -+ .build() -+ ); -+ -+ -+ private V2553() {} -+ -+ public static void register() { -+ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, BIOME_RENAMES::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0e228fd642cbca13e8682950b5f0ec4e3e8a4da7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java -@@ -0,0 +1,45 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.options.ConverterAbstractOptionsRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2558 { -+ -+ protected static final int VERSION = MCVersions.V1_16_PRE2 + 1; -+ -+ private V2558() {} -+ -+ public static void register() { -+ ConverterAbstractOptionsRename.register(VERSION, ImmutableMap.of( -+ "key_key.swapHands", "key_key.swapOffhand" -+ )::get); -+ -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ MapType dimensions = data.getMap("dimensions"); -+ if (dimensions == null) { -+ dimensions = Types.NBT.createEmptyMap(); -+ data.setMap("dimensions", dimensions); -+ } -+ -+ if (dimensions.isEmpty()) { -+ data.setMap("dimensions", recreateSettings(data)); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private static MapType recreateSettings(final MapType data) { -+ final long seed = data.getLong("seed"); -+ -+ return V2550.vanillaLevels(seed, V2550.defaultOverworld(seed), false); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5835159d7016fb4f05d137acba709fa7d8e8e752 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2568 { -+ -+ protected static final int VERSION = MCVersions.V1_16_1 + 1; -+ -+ private V2568() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:piglin_brute"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java -new file mode 100644 -index 0000000000000000000000000000000000000000..374d24db80f3ed8cd241e18d776c39a8da72d8fd ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2671 { -+ -+ protected static final int VERSION = MCVersions.V1_16_5 + 85; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:goat"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6c788f51be0439797bf9fc8711d4cf8e382f5c11 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java -@@ -0,0 +1,36 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2679 { -+ -+ protected static final int VERSION = MCVersions.V1_16_5 + 93; -+ -+ public static void register() { -+ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!"minecraft:cauldron".equals(data.getString("Name"))) { -+ return null; -+ } -+ -+ final MapType properties = data.getMap("Properties"); -+ -+ if (properties == null) { -+ return null; -+ } -+ -+ if (properties.getString("level", "0").equals("0")) { -+ data.remove("Properties"); -+ return null; -+ } else { -+ data.setString("Name", "minecraft:water_cauldron"); -+ return null; -+ } -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java -new file mode 100644 -index 0000000000000000000000000000000000000000..232a28c07c6341a996253282d9872d76b3fce0e3 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2680 { -+ -+ protected static final int VERSION = MCVersions.V1_16_5 + 94; -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:grass_path", "minecraft:dirt_path" -+ )::get); -+ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, ImmutableMap.of( -+ "minecraft:grass_path", "minecraft:dirt_path" -+ )::get); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4706a7cfb97d3d5c521914f8dfc894db277bcfe0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java -@@ -0,0 +1,14 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; -+ -+public final class V2684 { -+ -+ protected static final int VERSION = MCVersions.V20W48A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:sculk_sensor", new GameEventListenerWalker()); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f6a6f33d4f701f4188828994c8e56dea21950366 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2686 { -+ -+ protected static final int VERSION = MCVersions.V20W49A + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:axolotl"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6fcfcb66e1fd9291abad47e41ee076a7816b4244 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+ -+public final class V2688 { -+ -+ protected static final int VERSION = MCVersions.V20W51A + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:glow_squid"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:glow_item_frame", new DataWalkerItems("Item")); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c464ee01de5bcfcee9bcf18cc73ae8d6e0354b50 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java -@@ -0,0 +1,43 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V2690 { -+ -+ protected static final int VERSION = MCVersions.V21W05A; -+ -+ protected static final Map RENAMES = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:weathered_copper_block", "minecraft:oxidized_copper_block") -+ .put("minecraft:semi_weathered_copper_block", "minecraft:weathered_copper_block") -+ .put("minecraft:lightly_weathered_copper_block", "minecraft:exposed_copper_block") -+ .put("minecraft:weathered_cut_copper", "minecraft:oxidized_cut_copper") -+ .put("minecraft:semi_weathered_cut_copper", "minecraft:weathered_cut_copper") -+ .put("minecraft:lightly_weathered_cut_copper", "minecraft:exposed_cut_copper") -+ .put("minecraft:weathered_cut_copper_stairs", "minecraft:oxidized_cut_copper_stairs") -+ .put("minecraft:semi_weathered_cut_copper_stairs", "minecraft:weathered_cut_copper_stairs") -+ .put("minecraft:lightly_weathered_cut_copper_stairs", "minecraft:exposed_cut_copper_stairs") -+ .put("minecraft:weathered_cut_copper_slab", "minecraft:oxidized_cut_copper_slab") -+ .put("minecraft:semi_weathered_cut_copper_slab", "minecraft:weathered_cut_copper_slab") -+ .put("minecraft:lightly_weathered_cut_copper_slab", "minecraft:exposed_cut_copper_slab") -+ .put("minecraft:waxed_semi_weathered_copper", "minecraft:waxed_weathered_copper") -+ .put("minecraft:waxed_lightly_weathered_copper", "minecraft:waxed_exposed_copper") -+ .put("minecraft:waxed_semi_weathered_cut_copper", "minecraft:waxed_weathered_cut_copper") -+ .put("minecraft:waxed_lightly_weathered_cut_copper", "minecraft:waxed_exposed_cut_copper") -+ .put("minecraft:waxed_semi_weathered_cut_copper_stairs", "minecraft:waxed_weathered_cut_copper_stairs") -+ .put("minecraft:waxed_lightly_weathered_cut_copper_stairs", "minecraft:waxed_exposed_cut_copper_stairs") -+ .put("minecraft:waxed_semi_weathered_cut_copper_slab", "minecraft:waxed_weathered_cut_copper_slab") -+ .put("minecraft:waxed_lightly_weathered_cut_copper_slab", "minecraft:waxed_exposed_cut_copper_slab") -+ .build() -+ ); -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, RENAMES::get); -+ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d64cd0fa59e8581ac22b61d759658abe4b9e013b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java -@@ -0,0 +1,27 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V2691 { -+ -+ protected static final int VERSION = MCVersions.V21W05A + 1; -+ -+ protected static final Map RENAMES = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:waxed_copper", "minecraft:waxed_copper_block") -+ .put("minecraft:oxidized_copper_block", "minecraft:oxidized_copper") -+ .put("minecraft:weathered_copper_block", "minecraft:weathered_copper") -+ .put("minecraft:exposed_copper_block", "minecraft:exposed_copper") -+ .build() -+ ); -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, RENAMES::get); -+ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java -new file mode 100644 -index 0000000000000000000000000000000000000000..deac34afe6a3681db9a7630ad6526f71d4dd6e1f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java -@@ -0,0 +1,15 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.AddFlagIfAbsent; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+ -+public final class V2693 { -+ -+ protected static final int VERSION = MCVersions.V21W05B + 1; -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", false)); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9502cfd7a4dd536fb2e2fa114691faef30f757aa ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java -@@ -0,0 +1,40 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V2696 { -+ -+ protected static final int VERSION = MCVersions.V21W07A + 1; -+ -+ protected static final Map RENAMES = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:grimstone", "minecraft:deepslate") -+ .put("minecraft:grimstone_slab", "minecraft:cobbled_deepslate_slab") -+ .put("minecraft:grimstone_stairs", "minecraft:cobbled_deepslate_stairs") -+ .put("minecraft:grimstone_wall", "minecraft:cobbled_deepslate_wall") -+ .put("minecraft:polished_grimstone", "minecraft:polished_deepslate") -+ .put("minecraft:polished_grimstone_slab", "minecraft:polished_deepslate_slab") -+ .put("minecraft:polished_grimstone_stairs", "minecraft:polished_deepslate_stairs") -+ .put("minecraft:polished_grimstone_wall", "minecraft:polished_deepslate_wall") -+ .put("minecraft:grimstone_tiles", "minecraft:deepslate_tiles") -+ .put("minecraft:grimstone_tile_slab", "minecraft:deepslate_tile_slab") -+ .put("minecraft:grimstone_tile_stairs", "minecraft:deepslate_tile_stairs") -+ .put("minecraft:grimstone_tile_wall", "minecraft:deepslate_tile_wall") -+ .put("minecraft:grimstone_bricks", "minecraft:deepslate_bricks") -+ .put("minecraft:grimstone_brick_slab", "minecraft:deepslate_brick_slab") -+ .put("minecraft:grimstone_brick_stairs", "minecraft:deepslate_brick_stairs") -+ .put("minecraft:grimstone_brick_wall", "minecraft:deepslate_brick_wall") -+ .put("minecraft:chiseled_grimstone", "minecraft:chiseled_deepslate") -+ .build() -+ ); -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, RENAMES::get); -+ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c37142033061a3e4865686ee64d8f15f040d7d41 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java -@@ -0,0 +1,17 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2700 { -+ -+ protected static final int VERSION = MCVersions.V21W10A + 1; -+ -+ public static void register() { -+ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, ImmutableMap.of( -+ "minecraft:cave_vines_head", "minecraft:cave_vines", -+ "minecraft:cave_vines_body", "minecraft:cave_vines_plant" -+ )::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9d6b03410c4665e19a2a35226d11f77b2cae3bbf ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java -@@ -0,0 +1,203 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.google.common.collect.Sets; -+import java.util.Set; -+import java.util.regex.Matcher; -+import java.util.regex.Pattern; -+ -+public final class V2701 { -+ -+ protected static final int VERSION = MCVersions.V21W10A + 2; -+ -+ private static final Pattern INDEX_PATTERN = Pattern.compile("\\[(\\d+)\\]"); -+ -+ private static final Set PIECE_TYPE = Sets.newHashSet( -+ "minecraft:jigsaw", -+ "minecraft:nvi", -+ "minecraft:pcp", -+ "minecraft:bastionremnant", -+ "minecraft:runtime" -+ ); -+ private static final Set FEATURES = Sets.newHashSet( -+ "minecraft:tree", -+ "minecraft:flower", -+ "minecraft:block_pile", -+ "minecraft:random_patch" -+ ); -+ -+ public static void register() { -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType children = data.getList("Children", ObjectType.MAP); -+ -+ if (children == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = children.size(); i < len; ++i) { -+ final MapType child = children.getMap(i); -+ -+ if (!PIECE_TYPE.contains(child.getString("id"))) { -+ continue; -+ } -+ -+ final String poolElement = child.getString("pool_element"); -+ if (!"minecraft:feature_pool_element".equals(poolElement)) { -+ continue; -+ } -+ -+ final MapType feature = child.getMap("feature"); -+ if (feature == null) { -+ continue; -+ } -+ -+ final String replacement = convertToString(feature); -+ -+ if (replacement != null) { -+ child.setString("feature", replacement); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private static String getNestedString(final MapType root, final String... paths) { -+ if (paths.length == 0) { -+ throw new IllegalArgumentException("Missing path"); -+ } -+ -+ Object current = root.getGeneric(paths[0]); -+ -+ for (int i = 1; i < paths.length; ++i) { -+ final String path = paths[i]; -+ -+ final Matcher indexMatcher = INDEX_PATTERN.matcher(path); -+ if (!indexMatcher.matches()) { -+ current = (current instanceof MapType) ? ((MapType)current).getGeneric(path) : null; -+ if (current == null) { -+ break; -+ } -+ continue; -+ } -+ -+ final int index = Integer.parseInt(indexMatcher.group(1)); -+ if (!(current instanceof ListType)) { -+ current = null; -+ break; -+ } else { -+ final ListType list = (ListType)current; -+ if (index >= 0 && index < list.size()) { -+ current = list.getGeneric(index); -+ } else { -+ current = null; -+ break; -+ } -+ } -+ } -+ -+ return current instanceof String ? (String)current : ""; -+ } -+ -+ protected static String convertToString(final MapType feature) { -+ return getReplacement( -+ getNestedString(feature, "type"), -+ getNestedString(feature, "name"), -+ getNestedString(feature, "config", "state_provider", "type"), -+ getNestedString(feature, "config", "state_provider", "state", "Name"), -+ getNestedString(feature, "config", "state_provider", "entries", "[0]", "data", "Name"), -+ getNestedString(feature, "config", "foliage_placer", "type"), -+ getNestedString(feature, "config", "leaves_provider", "state", "Name") -+ ); -+ } -+ -+ private static String getReplacement(final String type, final String name, final String stateType, final String stateName, -+ final String firstEntryName, final String foliageName, final String leavesName) { -+ final String actualType; -+ if (!type.isEmpty()) { -+ actualType = type; -+ } else { -+ if (name.isEmpty()) { -+ return null; -+ } -+ -+ if ("minecraft:normal_tree".equals(name)) { -+ actualType = "minecraft:tree"; -+ } else { -+ actualType = name; -+ } -+ } -+ -+ if (FEATURES.contains(actualType)) { -+ if ("minecraft:random_patch".equals(actualType)) { -+ if ("minecraft:simple_state_provider".equals(stateType)) { -+ if ("minecraft:sweet_berry_bush".equals(stateName)) { -+ return "minecraft:patch_berry_bush"; -+ } -+ -+ if ("minecraft:cactus".equals(stateName)) { -+ return "minecraft:patch_cactus"; -+ } -+ } else if ("minecraft:weighted_state_provider".equals(stateType) && ("minecraft:grass".equals(firstEntryName) || "minecraft:fern".equals(firstEntryName))) { -+ return "minecraft:patch_taiga_grass"; -+ } -+ } else if ("minecraft:block_pile".equals(actualType)) { -+ if (!"minecraft:simple_state_provider".equals(stateType) && !"minecraft:rotated_block_provider".equals(stateType)) { -+ if ("minecraft:weighted_state_provider".equals(stateType)) { -+ if ("minecraft:packed_ice".equals(firstEntryName) || "minecraft:blue_ice".equals(firstEntryName)) { -+ return "minecraft:pile_ice"; -+ } -+ -+ if ("minecraft:jack_o_lantern".equals(firstEntryName) || "minecraft:pumpkin".equals(firstEntryName)) { -+ return "minecraft:pile_pumpkin"; -+ } -+ } -+ } else { -+ if ("minecraft:hay_block".equals(stateName)) { -+ return "minecraft:pile_hay"; -+ } -+ -+ if ("minecraft:melon".equals(stateName)) { -+ return "minecraft:pile_melon"; -+ } -+ -+ if ("minecraft:snow".equals(stateName)) { -+ return "minecraft:pile_snow"; -+ } -+ } -+ } else { -+ if ("minecraft:flower".equals(actualType)) { -+ return "minecraft:flower_plain"; -+ } -+ -+ if ("minecraft:tree".equals(actualType)) { -+ if ("minecraft:acacia_foliage_placer".equals(foliageName)) { -+ return "minecraft:acacia"; -+ } -+ -+ if ("minecraft:blob_foliage_placer".equals(foliageName) && "minecraft:oak_leaves".equals(leavesName)) { -+ return "minecraft:oak"; -+ } -+ -+ if ("minecraft:pine_foliage_placer".equals(foliageName)) { -+ return "minecraft:pine"; -+ } -+ -+ if ("minecraft:spruce_foliage_placer".equals(foliageName)) { -+ return "minecraft:spruce"; -+ } -+ } -+ } -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java -new file mode 100644 -index 0000000000000000000000000000000000000000..53e45b14c05dab35cd5725998458d47e28718075 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java -@@ -0,0 +1,33 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2702 { -+ -+ protected static final int VERSION = MCVersions.V21W10A + 3; -+ -+ public static void register() { -+ final DataConverter, MapType> arrowConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.hasKey("pickup")) { -+ return null; -+ } -+ -+ final boolean player = data.getBoolean("player", true); -+ data.remove("player"); -+ -+ data.setByte("pickup", player ? (byte)1 : (byte)0); -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:arrow", arrowConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:spectral_arrow", arrowConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:trident", arrowConverter); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java -new file mode 100644 -index 0000000000000000000000000000000000000000..74c1df97036059b3a5147f7cf94752ef4516a33d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.AddFlagIfAbsent; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2707 { -+ -+ protected static final int VERSION = MCVersions.V21W14A + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", true)); -+ -+ registerMob("minecraft:marker"); // ????????????? -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0d31b10a4f93c0eb8bff66dd062ffb950b2182ec ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java -@@ -0,0 +1,17 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.stats.ConverterAbstractStatsRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2710 { -+ -+ protected static final int VERSION = MCVersions.V21W15A + 1; -+ -+ public static void register() { -+ ConverterAbstractStatsRename.register(VERSION, ImmutableMap.of( -+ "minecraft:play_one_minute", "minecraft:play_time" -+ )::get); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8678ba95b5abe96b399a310623078f8827dfa0f0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2717 { -+ -+ protected static final int VERSION = MCVersions.V1_17_PRE1 + 1; -+ -+ public static void register() { -+ final ImmutableMap rename = ImmutableMap.of( -+ "minecraft:azalea_leaves_flowers", "minecraft:flowering_azalea_leaves" -+ ); -+ ConverterAbstractItemRename.register(VERSION, rename::get); -+ ConverterAbstractBlockRename.register(VERSION, rename::get); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c2d2b7c10e5b988b1111b20b778c475a12bef353 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java -@@ -0,0 +1,15 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.AddFlagIfAbsent; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+ -+public final class V2825 { -+ -+ protected static final int VERSION = MCVersions.V1_17_1 + 95; -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", false)); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d28ade80499dce882a9a84309a2a0da527fe01a0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java -@@ -0,0 +1,69 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V2831 { -+ -+ protected static final int VERSION = MCVersions.V1_17_1 + 101; -+ -+ public static void register() { -+ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureWalker(VERSION, (final MapType root, final long fromVersion, final long toVersion) -> { -+ final ListType spawnPotentials = root.getList("SpawnPotentials", ObjectType.MAP); -+ if (spawnPotentials != null) { -+ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { -+ final MapType spawnPotential = spawnPotentials.getMap(i); -+ -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, spawnPotential.getMap("data"), "entity", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, root.getMap("SpawnData"), "entity", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { -+ final MapType spawnData = root.getMap("SpawnData"); -+ if (spawnData != null) { -+ final MapType wrapped = Types.NBT.createEmptyMap(); -+ root.setMap("SpawnData", wrapped); -+ -+ wrapped.setMap("entity", spawnData); -+ } -+ -+ final ListType spawnPotentials = root.getList("SpawnPotentials", ObjectType.MAP); -+ if (spawnPotentials != null) { -+ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { -+ final MapType spawnPotential = spawnPotentials.getMap(i); -+ -+ // new format of weighted list (SpawnPotentials): -+ // root.data -> data -+ // root.weight -> weight -+ -+ final MapType entity = spawnPotential.getMap("Entity"); -+ final int weight = spawnPotential.getInt("Weight", 1); -+ spawnPotential.remove("Entity"); -+ spawnPotential.remove("Weight"); -+ spawnPotential.setInt("weight", weight); -+ -+ final MapType data = Types.NBT.createEmptyMap(); -+ spawnPotential.setMap("data", data); -+ -+ data.setMap("entity", entity); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1569cf06aa1dd73961d053a57722aac4c36a4148 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java -@@ -0,0 +1,927 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.mojang.logging.LogUtils; -+import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.ints.IntIterator; -+import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -+import org.apache.commons.lang3.mutable.MutableBoolean; -+import org.slf4j.Logger; -+import java.util.Arrays; -+import java.util.BitSet; -+import java.util.HashSet; -+import java.util.Set; -+ -+public final class V2832 { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V1_17_1 + 102; -+ -+ private static final String[] BIOMES_BY_ID = new String[256]; // rip datapacks -+ static { -+ BIOMES_BY_ID[0] = "minecraft:ocean"; -+ BIOMES_BY_ID[1] = "minecraft:plains"; -+ BIOMES_BY_ID[2] = "minecraft:desert"; -+ BIOMES_BY_ID[3] = "minecraft:mountains"; -+ BIOMES_BY_ID[4] = "minecraft:forest"; -+ BIOMES_BY_ID[5] = "minecraft:taiga"; -+ BIOMES_BY_ID[6] = "minecraft:swamp"; -+ BIOMES_BY_ID[7] = "minecraft:river"; -+ BIOMES_BY_ID[8] = "minecraft:nether_wastes"; -+ BIOMES_BY_ID[9] = "minecraft:the_end"; -+ BIOMES_BY_ID[10] = "minecraft:frozen_ocean"; -+ BIOMES_BY_ID[11] = "minecraft:frozen_river"; -+ BIOMES_BY_ID[12] = "minecraft:snowy_tundra"; -+ BIOMES_BY_ID[13] = "minecraft:snowy_mountains"; -+ BIOMES_BY_ID[14] = "minecraft:mushroom_fields"; -+ BIOMES_BY_ID[15] = "minecraft:mushroom_field_shore"; -+ BIOMES_BY_ID[16] = "minecraft:beach"; -+ BIOMES_BY_ID[17] = "minecraft:desert_hills"; -+ BIOMES_BY_ID[18] = "minecraft:wooded_hills"; -+ BIOMES_BY_ID[19] = "minecraft:taiga_hills"; -+ BIOMES_BY_ID[20] = "minecraft:mountain_edge"; -+ BIOMES_BY_ID[21] = "minecraft:jungle"; -+ BIOMES_BY_ID[22] = "minecraft:jungle_hills"; -+ BIOMES_BY_ID[23] = "minecraft:jungle_edge"; -+ BIOMES_BY_ID[24] = "minecraft:deep_ocean"; -+ BIOMES_BY_ID[25] = "minecraft:stone_shore"; -+ BIOMES_BY_ID[26] = "minecraft:snowy_beach"; -+ BIOMES_BY_ID[27] = "minecraft:birch_forest"; -+ BIOMES_BY_ID[28] = "minecraft:birch_forest_hills"; -+ BIOMES_BY_ID[29] = "minecraft:dark_forest"; -+ BIOMES_BY_ID[30] = "minecraft:snowy_taiga"; -+ BIOMES_BY_ID[31] = "minecraft:snowy_taiga_hills"; -+ BIOMES_BY_ID[32] = "minecraft:giant_tree_taiga"; -+ BIOMES_BY_ID[33] = "minecraft:giant_tree_taiga_hills"; -+ BIOMES_BY_ID[34] = "minecraft:wooded_mountains"; -+ BIOMES_BY_ID[35] = "minecraft:savanna"; -+ BIOMES_BY_ID[36] = "minecraft:savanna_plateau"; -+ BIOMES_BY_ID[37] = "minecraft:badlands"; -+ BIOMES_BY_ID[38] = "minecraft:wooded_badlands_plateau"; -+ BIOMES_BY_ID[39] = "minecraft:badlands_plateau"; -+ BIOMES_BY_ID[40] = "minecraft:small_end_islands"; -+ BIOMES_BY_ID[41] = "minecraft:end_midlands"; -+ BIOMES_BY_ID[42] = "minecraft:end_highlands"; -+ BIOMES_BY_ID[43] = "minecraft:end_barrens"; -+ BIOMES_BY_ID[44] = "minecraft:warm_ocean"; -+ BIOMES_BY_ID[45] = "minecraft:lukewarm_ocean"; -+ BIOMES_BY_ID[46] = "minecraft:cold_ocean"; -+ BIOMES_BY_ID[47] = "minecraft:deep_warm_ocean"; -+ BIOMES_BY_ID[48] = "minecraft:deep_lukewarm_ocean"; -+ BIOMES_BY_ID[49] = "minecraft:deep_cold_ocean"; -+ BIOMES_BY_ID[50] = "minecraft:deep_frozen_ocean"; -+ BIOMES_BY_ID[127] = "minecraft:the_void"; -+ BIOMES_BY_ID[129] = "minecraft:sunflower_plains"; -+ BIOMES_BY_ID[130] = "minecraft:desert_lakes"; -+ BIOMES_BY_ID[131] = "minecraft:gravelly_mountains"; -+ BIOMES_BY_ID[132] = "minecraft:flower_forest"; -+ BIOMES_BY_ID[133] = "minecraft:taiga_mountains"; -+ BIOMES_BY_ID[134] = "minecraft:swamp_hills"; -+ BIOMES_BY_ID[140] = "minecraft:ice_spikes"; -+ BIOMES_BY_ID[149] = "minecraft:modified_jungle"; -+ BIOMES_BY_ID[151] = "minecraft:modified_jungle_edge"; -+ BIOMES_BY_ID[155] = "minecraft:tall_birch_forest"; -+ BIOMES_BY_ID[156] = "minecraft:tall_birch_hills"; -+ BIOMES_BY_ID[157] = "minecraft:dark_forest_hills"; -+ BIOMES_BY_ID[158] = "minecraft:snowy_taiga_mountains"; -+ BIOMES_BY_ID[160] = "minecraft:giant_spruce_taiga"; -+ BIOMES_BY_ID[161] = "minecraft:giant_spruce_taiga_hills"; -+ BIOMES_BY_ID[162] = "minecraft:modified_gravelly_mountains"; -+ BIOMES_BY_ID[163] = "minecraft:shattered_savanna"; -+ BIOMES_BY_ID[164] = "minecraft:shattered_savanna_plateau"; -+ BIOMES_BY_ID[165] = "minecraft:eroded_badlands"; -+ BIOMES_BY_ID[166] = "minecraft:modified_wooded_badlands_plateau"; -+ BIOMES_BY_ID[167] = "minecraft:modified_badlands_plateau"; -+ BIOMES_BY_ID[168] = "minecraft:bamboo_jungle"; -+ BIOMES_BY_ID[169] = "minecraft:bamboo_jungle_hills"; -+ BIOMES_BY_ID[170] = "minecraft:soul_sand_valley"; -+ BIOMES_BY_ID[171] = "minecraft:crimson_forest"; -+ BIOMES_BY_ID[172] = "minecraft:warped_forest"; -+ BIOMES_BY_ID[173] = "minecraft:basalt_deltas"; -+ BIOMES_BY_ID[174] = "minecraft:dripstone_caves"; -+ BIOMES_BY_ID[175] = "minecraft:lush_caves"; -+ BIOMES_BY_ID[177] = "minecraft:meadow"; -+ BIOMES_BY_ID[178] = "minecraft:grove"; -+ BIOMES_BY_ID[179] = "minecraft:snowy_slopes"; -+ BIOMES_BY_ID[180] = "minecraft:snowcapped_peaks"; -+ BIOMES_BY_ID[181] = "minecraft:lofty_peaks"; -+ BIOMES_BY_ID[182] = "minecraft:stony_peaks"; -+ } -+ -+ private static final String[] HEIGHTMAP_TYPES = new String[] { -+ "WORLD_SURFACE_WG", -+ "WORLD_SURFACE", -+ "WORLD_SURFACE_IGNORE_SNOW", -+ "OCEAN_FLOOR_WG", -+ "OCEAN_FLOOR", -+ "MOTION_BLOCKING", -+ "MOTION_BLOCKING_NO_LEAVES" -+ }; -+ -+ private static final Set STATUS_IS_OR_AFTER_SURFACE = new HashSet<>(Arrays.asList( -+ "surface", -+ "carvers", -+ "liquid_carvers", -+ "features", -+ "light", -+ "spawn", -+ "heightmaps", -+ "full" -+ )); -+ private static final Set STATUS_IS_OR_AFTER_NOISE = new HashSet<>(Arrays.asList( -+ "noise", -+ "surface", -+ "carvers", -+ "liquid_carvers", -+ "features", -+ "light", -+ "spawn", -+ "heightmaps", -+ "full" -+ )); -+ private static final Set BLOCKS_BEFORE_FEATURE_STATUS = new HashSet<>(Arrays.asList( -+ "minecraft:air", -+ "minecraft:basalt", -+ "minecraft:bedrock", -+ "minecraft:blackstone", -+ "minecraft:calcite", -+ "minecraft:cave_air", -+ "minecraft:coarse_dirt", -+ "minecraft:crimson_nylium", -+ "minecraft:dirt", -+ "minecraft:end_stone", -+ "minecraft:grass_block", -+ "minecraft:gravel", -+ "minecraft:ice", -+ "minecraft:lava", -+ "minecraft:mycelium", -+ "minecraft:nether_wart_block", -+ "minecraft:netherrack", -+ "minecraft:orange_terracotta", -+ "minecraft:packed_ice", -+ "minecraft:podzol", -+ "minecraft:powder_snow", -+ "minecraft:red_sand", -+ "minecraft:red_sandstone", -+ "minecraft:sand", -+ "minecraft:sandstone", -+ "minecraft:snow_block", -+ "minecraft:soul_sand", -+ "minecraft:soul_soil", -+ "minecraft:stone", -+ "minecraft:terracotta", -+ "minecraft:warped_nylium", -+ "minecraft:warped_wart_block", -+ "minecraft:water", -+ "minecraft:white_terracotta" -+ )); -+ -+ private static int getObjectsPerValue(final long[] val) { -+ return (4096 + val.length - 1) / (val.length); // expression is invalid if it returns > 64 -+ } -+ -+ private static long[] resize(final long[] val, final int oldBitsPerObject, final int newBitsPerObject) { -+ final long oldMask = (1L << oldBitsPerObject) - 1; // works even if bitsPerObject == 64 -+ final long newMask = (1L << newBitsPerObject) - 1; -+ final int oldObjectsPerValue = 64 / oldBitsPerObject; -+ final int newObjectsPerValue = 64 / newBitsPerObject; -+ -+ if (newBitsPerObject == oldBitsPerObject) { -+ return val; -+ } -+ -+ final int items = 4096; -+ -+ final long[] ret = new long[(items + newObjectsPerValue - 1) / newObjectsPerValue]; -+ -+ final int expectedSize = ((items + oldObjectsPerValue - 1) / oldObjectsPerValue); -+ if (val.length != expectedSize) { -+ throw new IllegalStateException("Expected size: " + expectedSize + ", got: " + val.length); -+ } -+ -+ int shift = 0; -+ int idx = 0; -+ long newCurr = 0L; -+ -+ int currItem = 0; -+ for (int i = 0; i < val.length; ++i) { -+ final long oldCurr = val[i]; -+ -+ for (int objIdx = 0; currItem < items && objIdx + oldBitsPerObject <= 64; objIdx += oldBitsPerObject, ++currItem) { -+ final long value = (oldCurr >> objIdx) & oldMask; -+ -+ if ((value & newMask) != value) { -+ throw new IllegalStateException("Old data storage has values that cannot be moved into new palette (would erase data)!"); -+ } -+ -+ newCurr |= value << shift; -+ shift += newBitsPerObject; -+ -+ if (shift + newBitsPerObject > 64) { // will next write overflow? -+ // must move to next idx -+ ret[idx++] = newCurr; -+ shift = 0; -+ newCurr = 0L; -+ } -+ } -+ } -+ -+ // don't forget to write the last one -+ if (shift != 0) { -+ ret[idx] = newCurr; -+ } -+ -+ return ret; -+ } -+ -+ private static void fixLithiumChunks(final MapType data) { -+ // See https://github.com/CaffeineMC/lithium-fabric/issues/279 -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return; -+ } -+ -+ final int chunkX = level.getInt("xPos"); -+ final int chunkZ = level.getInt("zPos"); -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections == null) { -+ return; -+ } -+ -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ final int sectionY = section.getInt("Y"); -+ -+ final ListType palette = section.getList("Palette", ObjectType.MAP); -+ final long[] blockStates = section.getLongs("BlockStates"); -+ -+ if (palette == null || blockStates == null) { -+ continue; -+ } -+ -+ final int expectedBits = Math.max(4, ceilLog2(palette.size())); -+ final int gotObjectsPerValue = getObjectsPerValue(blockStates); -+ final int gotBits = 64 / gotObjectsPerValue; -+ -+ if (expectedBits == gotBits) { -+ continue; -+ } -+ -+ try { -+ section.setLongs("BlockStates", resize(blockStates, gotBits, expectedBits)); -+ } catch (final Exception ex) { -+ LOGGER.error("Failed to rewrite mismatched palette and data storage for section y: " + sectionY -+ + " for chunk [" + chunkX + "," + chunkZ + "], palette entries: " + palette.size() + ", data storage size: " -+ + blockStates.length, -+ ex -+ ); -+ } -+ } -+ } -+ -+ public static void register() { -+ // See V2551 for the layout of world gen settings -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ // converters were added to older versions note whether the world has increased height already or not -+ final boolean noHeightFlag = !data.hasKey("has_increased_height_already"); -+ final boolean hasIncreasedHeight = data.getBoolean("has_increased_height_already", true); -+ data.remove("has_increased_height_already"); -+ -+ final MapType dimensions = data.getMap("dimensions"); -+ if (dimensions == null) { -+ // wat -+ return null; -+ } -+ -+ // only care about overworld -+ final MapType overworld = dimensions.getMap("minecraft:overworld"); -+ if (overworld == null) { -+ // wat -+ return null; -+ } -+ -+ final MapType generator = overworld.getMap("generator"); -+ if (generator == null) { -+ // wat -+ return null; -+ } -+ -+ final String type = generator.getString("type", ""); -+ switch (type) { -+ case "minecraft:noise": { -+ final MapType biomeSource = generator.getMap("biome_source"); -+ final String sourceType = biomeSource.getString("type"); -+ -+ boolean largeBiomes = false; -+ -+ if ("minecraft:vanilla_layered".equals(sourceType) || (noHeightFlag && "minecraft:multi_noise".equals(sourceType))) { -+ largeBiomes = biomeSource.getBoolean("large_biomes"); -+ -+ final MapType newBiomeSource = Types.NBT.createEmptyMap(); -+ generator.setMap("biome_source", newBiomeSource); -+ -+ newBiomeSource.setString("preset", "minecraft:overworld"); -+ newBiomeSource.setString("type", "minecraft:multi_noise"); -+ } -+ -+ if (largeBiomes) { -+ if ("minecraft:overworld".equals(generator.getString("settings"))) { -+ generator.setString("settings", "minecraft:large_biomes"); -+ } -+ } -+ -+ break; -+ } -+ case "minecraft:flat": { -+ if (!hasIncreasedHeight) { -+ final MapType settings = generator.getMap("settings"); -+ if (settings == null) { -+ break; -+ } -+ -+ updateLayers(settings.getList("layers", ObjectType.MAP)); -+ } -+ break; -+ } -+ default: -+ // do nothing -+ break; -+ } -+ -+ return null; -+ } -+ }); -+ -+ -+ // It looks like DFU will only support worlds in the old height format or the new one, any custom one isn't supported -+ // and by not supported I mean it will just treat it as the old format... maybe at least throw in that case? -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ // The below covers padPaletteEntries - this was written BEFORE that code was added to the datafixer - -+ // and this still works, so I'm keeping it. Don't fix what isn't broken. -+ fixLithiumChunks(data); // See https://github.com/CaffeineMC/lithium-fabric/issues/279 -+ -+ final MapType level = data.getMap("Level"); -+ -+ if (level == null) { -+ return null; -+ } -+ -+ final MapType context = data.getMap("__context"); // Passed through by ChunkStorage -+ final String dimension = context == null ? "" : context.getString("dimension", ""); -+ final String generator = context == null ? "" : context.getString("generator", ""); -+ final boolean isOverworld = "minecraft:overworld".equals(dimension); -+ final int minSection = isOverworld ? -4 : 0; -+ final MutableBoolean isAlreadyExtended = new MutableBoolean(); -+ -+ final MapType[] newBiomes = createBiomeSections(level, isOverworld, minSection, isAlreadyExtended); -+ final MapType wrappedEmptyBlockPalette = getEmptyBlockPalette(); -+ -+ ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections == null) { -+ level.setList("Sections", sections = Types.NBT.createEmptyList()); -+ } -+ -+ // must update sections for two things: -+ // 1. the biomes are now stored per section, so we must insert the biomes palette into each section (and create them if they don't exist) -+ // 2. each section must now have block states (or at least DFU is ensuring they do, but current code does not require) -+ V2841.SimplePaletteReader bottomSection = null; -+ final Set allBlocks = new HashSet<>(); -+ final IntOpenHashSet existingSections = new IntOpenHashSet(); -+ -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ final int y = section.getInt("Y"); -+ final int sectionIndex = y - minSection; -+ -+ existingSections.add(y); -+ -+ // add in relevant biome section -+ if (sectionIndex >= 0 && sectionIndex < newBiomes.length) { -+ // exclude out of bounds sections (i.e the light sections above and below the world) -+ section.setMap("biomes", newBiomes[sectionIndex]); -+ } -+ -+ // update palette -+ final ListType palette = section.getList("Palette", ObjectType.MAP); -+ final long[] blockStates = section.getLongs("BlockStates"); -+ -+ section.remove("Palette"); -+ section.remove("BlockStates"); -+ -+ if (palette != null) { -+ for (int j = 0, len2 = palette.size(); j < len2; ++j) { -+ allBlocks.add(V2841.getBlockId(palette.getMap(j))); -+ } -+ } -+ -+ final MapType palettedContainer; -+ if (palette != null && blockStates != null) { -+ // only if both exist, same as DFU, same as legacy chunk loading code -+ section.setMap("block_states", palettedContainer = wrapPaletteOptimised(palette, blockStates)); -+ } else { -+ section.setMap("block_states", palettedContainer = wrappedEmptyBlockPalette.copy()); // must write a palette now, copy so that later edits do not edit them all -+ } -+ -+ if (section.getInt("Y", Integer.MAX_VALUE) == 0) { -+ bottomSection = new V2841.SimplePaletteReader(palettedContainer.getList("palette", ObjectType.MAP), palettedContainer.getLongs("data")); -+ } -+ } -+ -+ // all existing sections updated, now we must create new sections just for the biomes migration -+ for (int sectionIndex = 0; sectionIndex < newBiomes.length; ++sectionIndex) { -+ final int sectionY = sectionIndex + minSection; -+ if (!existingSections.add(sectionY)) { -+ // exists already -+ continue; -+ } -+ -+ final MapType newSection = Types.NBT.createEmptyMap(); -+ sections.addMap(newSection); -+ -+ newSection.setByte("Y", (byte)sectionY); -+ // must write a palette now, copy so that later edits do not edit them all -+ newSection.setMap("block_states", wrappedEmptyBlockPalette.copy()); -+ -+ newSection.setGeneric("biomes", newBiomes[sectionIndex]); -+ } -+ -+ // update status so interpolation can take place -+ predictChunkStatusBeforeSurface(level, allBlocks); -+ -+ // done with sections, update the rest of the chunk -+ updateChunkData(level, isOverworld, isAlreadyExtended.getValue(), "minecraft:noise".equals(generator), bottomSection); -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType dimensions = data.getMap("dimensions"); -+ -+ if (dimensions == null) { -+ return null; -+ } -+ -+ for (final String dimension : dimensions.keys()) { -+ final MapType dimensionData = dimensions.getMap(dimension); -+ if (dimensionData == null) { -+ continue; -+ } -+ -+ final MapType generator = dimensionData.getMap("generator"); -+ if (generator == null) { -+ continue; -+ } -+ -+ final String type = generator.getString("type"); -+ if (type == null) { -+ continue; -+ } -+ -+ switch (type) { -+ case "minecraft:flat": { -+ final MapType settings = generator.getMap("settings"); -+ if (settings == null) { -+ continue; -+ } -+ -+ WalkerUtils.convert(MCTypeRegistry.BIOME, settings, "biome", fromVersion, toVersion); -+ -+ final ListType layers = settings.getList("layers", ObjectType.MAP); -+ if (layers != null) { -+ for (int i = 0, len = layers.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, layers.getMap(i), "block", fromVersion, toVersion); -+ } -+ } -+ -+ break; -+ } -+ case "minecraft:noise": { -+ final MapType settings = generator.getMap("settings"); -+ if (settings != null) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_block", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_fluid", fromVersion, toVersion); -+ } -+ -+ final MapType biomeSource = generator.getMap("biome_source"); -+ if (biomeSource != null) { -+ final String biomeSourceType = biomeSource.getString("type", ""); -+ switch (biomeSourceType) { -+ case "minecraft:fixed": { -+ WalkerUtils.convert(MCTypeRegistry.BIOME, biomeSource, "biome", fromVersion, toVersion); -+ break; -+ } -+ -+ case "minecraft:multi_noise": { -+ WalkerUtils.convert(MCTypeRegistry.MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST, biomeSource, "preset", fromVersion, toVersion); -+ -+ // Vanilla's schema is _still_ wrong. It should be DSL.fields("biomes", DSL.list(DSL.fields("biome"))) -+ // But it just contains the list part. That obviously can never be the case, because -+ // the root object is a compound, not a list. -+ -+ final ListType biomes = biomeSource.getList("biomes", ObjectType.MAP); -+ if (biomes != null) { -+ for (int i = 0, len = biomes.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BIOME, biomes.getMap(i), "biome", fromVersion, toVersion); -+ } -+ } -+ -+ break; -+ } -+ -+ case "minecraft:checkerboard": { -+ WalkerUtils.convertList(MCTypeRegistry.BIOME, biomeSource, "biomes", fromVersion, toVersion); -+ break; -+ } -+ } -+ } -+ -+ break; -+ } -+ } -+ } -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); -+ -+ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); -+ if (tileTicks != null) { -+ for (int i = 0, len = tileTicks.size(); i < len; ++i) { -+ final MapType tileTick = tileTicks.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); -+ } -+ } -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ WalkerUtils.convertList(MCTypeRegistry.BIOME, section.getMap("biomes"), "palette", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section.getMap("block_states"), "palette", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, level.getMap("Structures"), "Starts", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+ -+ private static void predictChunkStatusBeforeSurface(final MapType level, final Set chunkBlocks) { -+ final String status = level.getString("Status", "empty"); -+ if (STATUS_IS_OR_AFTER_SURFACE.contains(status)) { -+ return; -+ } -+ -+ chunkBlocks.remove("minecraft:air"); -+ final boolean chunkNotEmpty = !chunkBlocks.isEmpty(); -+ chunkBlocks.removeAll(BLOCKS_BEFORE_FEATURE_STATUS); -+ final boolean chunkFeatureStatus = !chunkBlocks.isEmpty(); -+ -+ final String update; -+ if (chunkFeatureStatus) { -+ update = "liquid_carvers"; -+ } else if (!"noise".equals(status) && !chunkNotEmpty) { -+ update = "biomes".equals(status) ? "structure_references" : status; -+ } else { -+ update = "noise"; -+ } -+ -+ level.setString("Status", update); -+ } -+ -+ private static MapType getEmptyBlockPalette() { -+ final MapType airBlockState = Types.NBT.createEmptyMap(); -+ airBlockState.setString("Name", "minecraft:air"); -+ -+ final ListType emptyBlockPalette = Types.NBT.createEmptyList(); -+ emptyBlockPalette.addMap(airBlockState); -+ -+ return V2832.wrapPalette(emptyBlockPalette); -+ } -+ -+ private static void shiftUpgradeData(final MapType upgradeData, final int shift) { -+ if (upgradeData == null) { -+ return; -+ } -+ -+ final MapType indices = upgradeData.getMap("Indices"); -+ if (indices == null) { -+ return; -+ } -+ -+ RenameHelper.renameKeys(indices, (final String input) -> { -+ return Integer.toString(Integer.parseInt(input) + shift); -+ }); -+ } -+ -+ private static void updateChunkData(final MapType level, final boolean wantExtendedHeight, final boolean isAlreadyExtended, -+ final boolean onNoiseGenerator, final V2841.SimplePaletteReader bottomSection) { -+ level.remove("Biomes"); -+ if (!wantExtendedHeight) { -+ padCarvingMasks(level, 16, 0); -+ return; -+ } -+ -+ if (isAlreadyExtended) { -+ padCarvingMasks(level, 24, 0); -+ return; -+ } -+ -+ offsetHeightmaps(level); -+ // Difference from DFU: Still convert the Lights data. Just because it's being removed in a later version doesn't mean -+ // that it should be removed here. -+ // Generally, converters act only on the current version to bring it to the next. This principle allows the converter -+ // for the next version to assume that it acts on its current version, not some in-between of the current version -+ // and some future version that did not exist at the time it was written. This allows converters to be written and tested -+ // only with knowledge of the current version and the next version. -+ addEmptyListPadding(level, "Lights"); -+ addEmptyListPadding(level, "LiquidsToBeTicked"); -+ addEmptyListPadding(level, "PostProcessing"); -+ addEmptyListPadding(level, "ToBeTicked"); -+ shiftUpgradeData(level.getMap("UpgradeData"), 4); // https://bugs.mojang.com/browse/MC-238076 - fixed now, Mojang fix is identical. No change required. -+ padCarvingMasks(level, 24, 4); -+ -+ if (!onNoiseGenerator) { -+ return; -+ } -+ -+ final String status = level.getString("Status"); -+ if (status == null || "empty".equals(status)) { -+ return; -+ } -+ -+ final MapType blendingData = Types.NBT.createEmptyMap(); -+ level.setMap("blending_data", blendingData); -+ -+ blendingData.setBoolean("old_noise", STATUS_IS_OR_AFTER_NOISE.contains(status)); -+ -+ if (bottomSection == null) { -+ return; -+ } -+ -+ final BitSet missingBedrock = new BitSet(256); -+ boolean hasBedrock = status.equals("noise"); -+ -+ for (int z = 0; z <= 15; ++z) { -+ for (int x = 0; x <= 15; ++x) { -+ final MapType state = bottomSection.getState(x, 0, z); -+ final String blockId = V2841.getBlockId(state); -+ final boolean isBedrock = state != null && "minecraft:bedrock".equals(blockId); -+ final boolean isAir = state != null && "minecraft:air".equals(blockId); -+ if (isAir) { -+ missingBedrock.set((z << 4) | x); -+ } -+ -+ hasBedrock |= isBedrock; -+ } -+ } -+ -+ if (hasBedrock && missingBedrock.cardinality() != missingBedrock.size()) { -+ final String targetStatus = "full".equals(status) ? "heightmaps" : status; -+ -+ final MapType belowZeroRetrogen = Types.NBT.createEmptyMap(); -+ level.setMap("below_zero_retrogen", belowZeroRetrogen); -+ -+ belowZeroRetrogen.setString("target_status", targetStatus); -+ belowZeroRetrogen.setLongs("missing_bedrock", missingBedrock.toLongArray()); -+ -+ level.setString("Status", "empty"); -+ } -+ -+ level.setBoolean("isLightOn", false); -+ } -+ -+ private static void padCarvingMasks(final MapType level, final int newSize, final int offset) { -+ final MapType carvingMasks = level.getMap("CarvingMasks"); -+ if (carvingMasks == null) { -+ // if empty, DFU still writes -+ level.setMap("CarvingMasks", Types.NBT.createEmptyMap()); -+ return; -+ } -+ -+ for (final String key : carvingMasks.keys()) { -+ final long[] old = BitSet.valueOf(carvingMasks.getBytes(key)).toLongArray(); -+ final long[] newVal = new long[64 * newSize]; -+ -+ System.arraycopy(old, 0, newVal, 64 * offset, old.length); -+ -+ carvingMasks.setLongs(key, newVal); // no CME: key exists already -+ } -+ } -+ -+ private static void addEmptyListPadding(final MapType level, final String path) { -+ ListType list = level.getListUnchecked(path); -+ if (list != null && list.size() == 24) { -+ return; -+ } -+ -+ if (list == null) { -+ // difference from DFU: Don't create the damn thing! -+ return; -+ } -+ -+ -+ // offset the section array to the new format -+ for (int i = 0; i < 4; ++i) { -+ // always create new copies, so that modifying one doesn't modify ALL of them! -+ list.addList(0, Types.NBT.createEmptyList()); // add below -+ list.addList(Types.NBT.createEmptyList()); // add above -+ } -+ } -+ -+ private static void offsetHeightmaps(final MapType level) { -+ final MapType heightmaps = level.getMap("Heightmaps"); -+ if (heightmaps == null) { -+ return; -+ } -+ -+ for (final String key : HEIGHTMAP_TYPES) { -+ offsetHeightmap(heightmaps.getLongs(key)); -+ } -+ } -+ -+ private static void offsetHeightmap(final long[] heightmap) { -+ if (heightmap == null) { -+ return; -+ } -+ -+ // heightmaps are configured to have 9 bits per value, with 256 total values -+ // heightmaps are also relative to the lowest position -+ for (int idx = 0, len = heightmap.length; idx < len; ++idx) { -+ long curr = heightmap[idx]; -+ long next = 0L; -+ -+ for (int objIdx = 0; objIdx + 9 <= 64; objIdx += 9) { -+ final long value = (curr >> objIdx) & 511L; -+ if (value != 0L) { -+ final long offset = Math.min(511L, value + 64L); -+ -+ next |= (offset << objIdx); -+ } -+ } -+ -+ heightmap[idx] = next; -+ } -+ } -+ -+ private static MapType[] createBiomeSections(final MapType level, final boolean wantExtendedHeight, -+ final int minSection, final MutableBoolean isAlreadyExtended) { -+ final MapType[] ret = new MapType[wantExtendedHeight ? 24 : 16]; -+ -+ final int[] biomes = level.getInts("Biomes"); -+ -+ if (biomes != null && biomes.length == 1536) { // magic value for 24 sections of biomes (24 * 4^3) -+ isAlreadyExtended.setValue(true); -+ for (int sectionIndex = 0; sectionIndex < 24; ++sectionIndex) { -+ ret[sectionIndex] = createBiomeSection(biomes, sectionIndex * 64, -1); // -1 is all 1s -+ } -+ } else if (biomes != null && biomes.length == 1024) { // magic value for 24 sections of biomes (16 * 4^3) -+ for (int sectionY = 0; sectionY < 16; ++sectionY) { -+ ret[sectionY - minSection] = createBiomeSection(biomes, sectionY * 64, -1); // -1 is all 1s -+ } -+ -+ if (wantExtendedHeight) { -+ // must set the new sections at top and bottom -+ final MapType bottomCopy = createBiomeSection(biomes, 0, 15); // just want the biomes at y = 0 -+ final MapType topCopy = createBiomeSection(biomes, 1008, 15); // just want the biomes at y = 252 -+ -+ for (int sectionIndex = 0; sectionIndex < 4; ++sectionIndex) { -+ ret[sectionIndex] = bottomCopy.copy(); // copy palette so that later possible modifications don't trash all sections -+ } -+ -+ for (int sectionIndex = 20; sectionIndex < 24; ++sectionIndex) { -+ ret[sectionIndex] = topCopy.copy(); // copy palette so that later possible modifications don't trash all sections -+ } -+ } -+ } else { -+ final ListType palette = Types.NBT.createEmptyList(); -+ palette.addString("minecraft:plains"); -+ -+ for (int i = 0; i < ret.length; ++i) { -+ ret[i] = wrapPalette(palette.copy()); // copy palette so that later possible modifications don't trash all sections -+ } -+ } -+ -+ return ret; -+ } -+ -+ private static MapType createBiomeSection(final int[] biomes, final int offset, final int mask) { -+ final Int2IntLinkedOpenHashMap paletteId = new Int2IntLinkedOpenHashMap(); -+ -+ for (int idx = 0; idx < 64; ++idx) { -+ final int biome = biomes[offset + (idx & mask)]; -+ paletteId.putIfAbsent(biome, paletteId.size()); -+ } -+ -+ final ListType paletteString = Types.NBT.createEmptyList(); -+ for (final IntIterator iterator = paletteId.keySet().iterator(); iterator.hasNext();) { -+ final int biomeId = iterator.nextInt(); -+ final String biome = biomeId >= 0 && biomeId < BIOMES_BY_ID.length ? BIOMES_BY_ID[biomeId] : null; -+ paletteString.addString(biome == null ? "minecraft:plains" : biome); -+ } -+ -+ final int bitsPerObject = ceilLog2(paletteString.size()); -+ if (bitsPerObject == 0) { -+ return wrapPalette(paletteString); -+ } -+ -+ // manually create packed integer data -+ final int objectsPerValue = 64 / bitsPerObject; -+ final long[] packed = new long[(64 + objectsPerValue - 1) / objectsPerValue]; -+ -+ int shift = 0; -+ int idx = 0; -+ long curr = 0; -+ -+ for (int biome_idx = 0; biome_idx < 64; ++biome_idx) { -+ final int biome = biomes[offset + (biome_idx & mask)]; -+ -+ curr |= ((long)paletteId.get(biome)) << shift; -+ -+ shift += bitsPerObject; -+ -+ if (shift + bitsPerObject > 64) { // will next write overflow? -+ // must move to next idx -+ packed[idx++] = curr; -+ shift = 0; -+ curr = 0L; -+ } -+ } -+ -+ // don't forget to write the last one -+ if (shift != 0) { -+ packed[idx] = curr; -+ } -+ -+ return wrapPalette(paletteString, packed); -+ } -+ -+ private static MapType wrapPalette(final ListType palette) { -+ return wrapPalette(palette, null); -+ } -+ -+ private static MapType wrapPalette(final ListType palette, final long[] blockStates) { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ ret.setList("palette", palette); -+ if (blockStates != null) { -+ ret.setLongs("data", blockStates); -+ } -+ -+ return ret; -+ } -+ -+ private static MapType wrapPaletteOptimised(final ListType palette, final long[] blockStates) { -+ if (palette.size() == 1) { -+ return wrapPalette(palette); -+ } -+ -+ return wrapPalette(palette, blockStates); -+ } -+ -+ public static int ceilLog2(final int value) { -+ return value == 0 ? 0 : Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ private static void updateLayers(final ListType layers) { -+ if (layers == null) { -+ return; -+ } -+ -+ layers.addMap(0, createEmptyLayer()); // add at the bottom -+ } -+ -+ private static MapType createEmptyLayer() { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ ret.setInt("height", 64); -+ ret.setString("block", "minecraft:air"); -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4bdac86810c51e9f87ea82ba9f6c6d8ae8ce2bdf ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java -@@ -0,0 +1,30 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2833 { -+ -+ protected static final int VERSION = MCVersions.V1_17_1 + 103; -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType dimensions = data.getMap("dimensions"); -+ -+ for (final String dimensionKey : dimensions.keys()) { -+ final MapType dimension = dimensions.getMap(dimensionKey); -+ if (!dimension.hasKey("type")) { -+ throw new IllegalStateException("Unable load old custom worlds."); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ad25696e94da4706a9a59c89ba896b35894b285d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java -@@ -0,0 +1,60 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V2838 { -+ -+ protected static final int VERSION = MCVersions.V21W40A; -+ -+ public static final Map BIOME_UPDATE = new HashMap<>( -+ ImmutableMap.builder() -+ .put("minecraft:badlands_plateau", "minecraft:badlands") -+ .put("minecraft:bamboo_jungle_hills", "minecraft:bamboo_jungle") -+ .put("minecraft:birch_forest_hills", "minecraft:birch_forest") -+ .put("minecraft:dark_forest_hills", "minecraft:dark_forest") -+ .put("minecraft:desert_hills", "minecraft:desert") -+ .put("minecraft:desert_lakes", "minecraft:desert") -+ .put("minecraft:giant_spruce_taiga_hills", "minecraft:old_growth_spruce_taiga") -+ .put("minecraft:giant_spruce_taiga", "minecraft:old_growth_spruce_taiga") -+ .put("minecraft:giant_tree_taiga_hills", "minecraft:old_growth_pine_taiga") -+ .put("minecraft:giant_tree_taiga", "minecraft:old_growth_pine_taiga") -+ .put("minecraft:gravelly_mountains", "minecraft:windswept_gravelly_hills") -+ .put("minecraft:jungle_edge", "minecraft:sparse_jungle") -+ .put("minecraft:jungle_hills", "minecraft:jungle") -+ .put("minecraft:modified_badlands_plateau", "minecraft:badlands") -+ .put("minecraft:modified_gravelly_mountains", "minecraft:windswept_gravelly_hills") -+ .put("minecraft:modified_jungle_edge", "minecraft:sparse_jungle") -+ .put("minecraft:modified_jungle", "minecraft:jungle") -+ .put("minecraft:modified_wooded_badlands_plateau", "minecraft:wooded_badlands") -+ .put("minecraft:mountain_edge", "minecraft:windswept_hills") -+ .put("minecraft:mountains", "minecraft:windswept_hills") -+ .put("minecraft:mushroom_field_shore", "minecraft:mushroom_fields") -+ .put("minecraft:shattered_savanna", "minecraft:windswept_savanna") -+ .put("minecraft:shattered_savanna_plateau", "minecraft:windswept_savanna") -+ .put("minecraft:snowy_mountains", "minecraft:snowy_plains") -+ .put("minecraft:snowy_taiga_hills", "minecraft:snowy_taiga") -+ .put("minecraft:snowy_taiga_mountains", "minecraft:snowy_taiga") -+ .put("minecraft:snowy_tundra", "minecraft:snowy_plains") -+ .put("minecraft:stone_shore", "minecraft:stony_shore") -+ .put("minecraft:swamp_hills", "minecraft:swamp") -+ .put("minecraft:taiga_hills", "minecraft:taiga") -+ .put("minecraft:taiga_mountains", "minecraft:taiga") -+ .put("minecraft:tall_birch_forest", "minecraft:old_growth_birch_forest") -+ .put("minecraft:tall_birch_hills", "minecraft:old_growth_birch_forest") -+ .put("minecraft:wooded_badlands_plateau", "minecraft:wooded_badlands") -+ .put("minecraft:wooded_hills", "minecraft:forest") -+ .put("minecraft:wooded_mountains", "minecraft:windswept_forest") -+ .put("minecraft:lofty_peaks", "minecraft:jagged_peaks") -+ .put("minecraft:snowcapped_peaks", "minecraft:frozen_peaks") -+ .build() -+ ); -+ -+ public static void register() { -+ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, BIOME_UPDATE::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java -new file mode 100644 -index 0000000000000000000000000000000000000000..41b41ff084662bbc2e323713473e4e13b8e50cd7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java -@@ -0,0 +1,205 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import ca.spottedleaf.dataconverter.util.IntegerUtil; -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+import java.util.Arrays; -+import java.util.HashSet; -+import java.util.Set; -+ -+public final class V2841 { -+ -+ protected static final int VERSION = MCVersions.V21W42A + 1; -+ -+ protected static final Set ALWAYS_WATERLOGGED = new HashSet<>(Arrays.asList( -+ "minecraft:bubble_column", -+ "minecraft:kelp", -+ "minecraft:kelp_plant", -+ "minecraft:seagrass", -+ "minecraft:tall_seagrass" -+ )); -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { -+ final MapType level = root.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ { -+ // Why it's renamed here and not the next data version is beyond me. -+ final MapType liquidTicks = level.getMap("LiquidTicks"); -+ if (liquidTicks != null) { -+ level.remove("LiquidTicks"); -+ level.setMap("fluid_ticks", liquidTicks); -+ } -+ } -+ -+ final Int2ObjectOpenHashMap sectionBlocks = new Int2ObjectOpenHashMap<>(); -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ int minSection = 0; // TODO wtf is this -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ final int sectionY = section.getInt("Y"); -+ if (sectionY < minSection && section.hasKey("biomes")) { -+ minSection = sectionY; -+ } -+ -+ final MapType blockStates = section.getMap("block_states"); -+ if (blockStates == null) { -+ continue; -+ } -+ -+ sectionBlocks.put(sectionY, new SimplePaletteReader(section.getList("palette", ObjectType.MAP), section.getLongs("data"))); -+ } -+ } -+ -+ level.setByte("yPos", (byte)minSection); // TODO ??????????????????????????????????????? -+ -+ if (level.hasKey("fluid_ticks") || level.hasKey("TileTicks")) { -+ return null; -+ } -+ -+ final int sectionX = level.getInt("xPos"); -+ final int sectionZ = level.getInt("zPos"); -+ -+ final ListType fluidTicks = level.getList("LiquidsToBeTicked", ObjectType.LIST); -+ final ListType blockTicks = level.getList("ToBeTicked", ObjectType.LIST); -+ level.remove("LiquidsToBeTicked"); -+ level.remove("ToBeTicked"); -+ -+ level.setList("fluid_ticks", migrateTickList(fluidTicks, false, sectionBlocks, sectionX, minSection, sectionZ)); -+ level.setList("TileTicks", migrateTickList(blockTicks, true, sectionBlocks, sectionX, minSection, sectionZ)); -+ -+ return null; -+ } -+ }); -+ } -+ -+ public static ListType migrateTickList(final ListType ticks, final boolean blockTicks, final Int2ObjectOpenHashMap sectionBlocks, -+ final int sectionX, final int minSection, final int sectionZ) { -+ final ListType ret = Types.NBT.createEmptyList(); -+ -+ if (ticks == null) { -+ return ret; -+ } -+ -+ for (int sectionIndex = 0, totalSections = ticks.size(); sectionIndex < totalSections; ++sectionIndex) { -+ final int sectionY = sectionIndex + minSection; -+ final ListType sectionTicks = ticks.getList(sectionIndex); -+ final SimplePaletteReader palette = sectionBlocks.get(sectionY); -+ -+ for (int i = 0, len = sectionTicks.size(); i < len; ++i) { -+ final int localIndex = sectionTicks.getShort(i) & 0xFFFF; -+ final MapType blockState = palette == null ? null : palette.getState(localIndex); -+ final String subjectId = blockTicks ? getBlockId(blockState) : getLiquidId(blockState); -+ -+ ret.addMap(createNewTick(subjectId, localIndex, sectionX, sectionY, sectionZ)); -+ } -+ } -+ -+ return ret; -+ } -+ -+ public static MapType createNewTick(final String subjectId, final int localIndex, final int sectionX, final int sectionY, final int sectionZ) { -+ final int newX = (localIndex & 15) + (sectionX << 4); -+ final int newZ = ((localIndex >> 4) & 15) + (sectionZ << 4); -+ final int newY = ((localIndex >> 8) & 15) + (sectionY << 4); -+ -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setString("i", subjectId); -+ ret.setInt("x", newX); -+ ret.setInt("y", newY); -+ ret.setInt("z", newZ); -+ ret.setInt("t", 0); -+ ret.setInt("p", 0); -+ -+ return ret; -+ } -+ -+ public static String getBlockId(final MapType blockState) { -+ return blockState == null ? "minecraft:air" : blockState.getString("Name", "minecraft:air"); -+ } -+ -+ private static String getLiquidId(final MapType blockState) { -+ if (blockState == null) { -+ return "minecraft:empty"; -+ } -+ -+ final String name = blockState.getString("Name"); -+ if (ALWAYS_WATERLOGGED.contains(name)) { -+ return "minecraft:water"; -+ } -+ -+ final MapType properties = blockState.getMap("Properties"); -+ if ("minecraft:water".equals(name)) { -+ return properties != null && properties.getInt("level") == 0 ? "minecraft:water" : "minecraft:flowing_water"; -+ } else if ("minecraft:lava".equals(name)) { -+ return properties != null && properties.getInt("level") == 0 ? "minecraft:lava" : "minecraft:flowing_lava"; -+ } -+ -+ return (properties != null && properties.getBoolean("waterlogged")) ? "minecraft:water" : "minecraft:empty"; -+ } -+ -+ public static final class SimplePaletteReader { -+ -+ public final ListType palette; -+ public final long[] data; -+ private final int bitsPerValue; -+ private final long mask; -+ private final int valuesPerLong; -+ -+ public SimplePaletteReader(final ListType palette, final long[] data) { -+ this.palette = palette == null ? null : (palette.size() == 0 ? null : palette); -+ this.data = data; -+ this.bitsPerValue = Math.max(4, IntegerUtil.ceilLog2(this.palette == null ? 0 : this.palette.size())); -+ this.mask = (1L << this.bitsPerValue) - 1L; -+ this.valuesPerLong = (int)(64L / this.bitsPerValue); -+ } -+ -+ public MapType getState(final int x, final int y, final int z) { -+ final int index = x | (z << 4) | (y << 8); -+ return this.getState(index); -+ } -+ -+ public MapType getState(final int index) { -+ final ListType palette = this.palette; -+ if (palette == null) { -+ return null; -+ } -+ -+ final int paletteSize = palette.size(); -+ if (paletteSize == 1) { -+ return palette.getMap(0); -+ } -+ -+ // x86 div computes mod. no loss here using mod -+ // if needed, can compute magic mul and shift for div values using IntegerUtil -+ final int dataIndex = index / this.valuesPerLong; -+ final int localIndex = (index % this.valuesPerLong) * this.bitsPerValue; -+ final long[] data = this.data; -+ if (dataIndex < 0 || dataIndex >= data.length) { -+ return null; -+ } -+ -+ final long value = data[dataIndex]; -+ final int paletteIndex = (int)((value >>> localIndex) & this.mask); -+ if (paletteIndex < 0 || paletteIndex >= paletteSize) { -+ return null; -+ } -+ -+ return palette.getMap(paletteIndex); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f06e24bb87baf01b1386fb7a6af1ea04f4d6f2ef ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java -@@ -0,0 +1,76 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V2842 { -+ -+ protected static final int VERSION = MCVersions.V21W42A + 2; -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { -+ final MapType level = root.getMap("Level"); -+ root.remove("Level"); -+ -+ if (!root.isEmpty()) { -+ for (final String key : root.keys()) { -+ if (level.hasKey(key)) { -+ // Don't clobber level's data -+ continue; -+ } -+ level.setGeneric(key, root.getGeneric(key)); -+ } -+ } -+ -+ // Rename top level first -+ RenameHelper.renameSingle(level, "TileEntities", "block_entities"); -+ RenameHelper.renameSingle(level, "TileTicks", "block_ticks"); -+ RenameHelper.renameSingle(level, "Entities", "entities"); -+ RenameHelper.renameSingle(level, "Sections", "sections"); -+ RenameHelper.renameSingle(level, "Structures", "structures"); -+ -+ // 2nd level -+ final MapType structures = level.getMap("structures"); -+ if (structures != null) { -+ RenameHelper.renameSingle(structures, "Starts", "starts"); -+ } -+ -+ return level; // Level is now root tag. -+ } -+ }); -+ -+ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "entities", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, data, "block_entities", fromVersion, toVersion); -+ -+ final ListType blockTicks = data.getList("block_ticks", ObjectType.MAP); -+ if (blockTicks != null) { -+ for (int i = 0, len = blockTicks.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, blockTicks.getMap(i), "i", fromVersion, toVersion); -+ } -+ } -+ -+ final ListType sections = data.getList("sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ WalkerUtils.convertList(MCTypeRegistry.BIOME, section.getMap("biomes"), "palette", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section.getMap("block_states"), "palette", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, data.getMap("structures"), "starts", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java -new file mode 100644 -index 0000000000000000000000000000000000000000..69783c4ca366e4895d4f1c5909656b4f41fee3a1 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java -@@ -0,0 +1,105 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import java.util.Map; -+ -+public final class V2843 { -+ -+ protected static final int VERSION = MCVersions.V21W42A + 3; -+ -+ public static void register() { -+ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, Map.of("minecraft:deep_warm_ocean", "minecraft:warm_ocean")::get); -+ -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ private static void moveOutOfBoundTicks(final ListType ticks, final MapType chunkRoot, final int chunkX, final int chunkZ, final String intoKey) { -+ if (ticks == null) { -+ return; -+ } -+ -+ ListType outOfBounds = null; -+ for (int i = 0, len = ticks.size(); i < len; ++i) { -+ final MapType tick = ticks.getMap(i); -+ final int x = tick.getInt("x"); -+ final int z = tick.getInt("z"); -+ // anything > 1 is lost, anything less than 1 stays. -+ if (Math.max(Math.abs(chunkX - (x >> 4)), Math.abs(chunkZ - (z >> 4))) == 1) { -+ // DFU doesn't remove, so neither do we. -+ if (outOfBounds == null) { -+ outOfBounds = Types.NBT.createEmptyList(); -+ } -+ outOfBounds.addMap(tick.copy()); -+ } -+ } -+ -+ if (outOfBounds != null) { -+ MapType upgradeData = chunkRoot.getMap("UpgradeData"); -+ if (upgradeData == null) { -+ chunkRoot.setMap("UpgradeData", upgradeData = Types.NBT.createEmptyMap()); -+ } -+ upgradeData.setList(intoKey, outOfBounds); -+ } -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ // After renames, so use new names -+ final int x = data.getInt("xPos"); -+ final int z = data.getInt("zPos"); -+ -+ moveOutOfBoundTicks(data.getList("block_ticks", ObjectType.MAP), data, x, z, "neighbor_block_ticks"); -+ moveOutOfBoundTicks(data.getList("fluid_ticks", ObjectType.MAP), data, x, z, "neighbor_fluid_ticks"); -+ -+ return null; -+ } -+ }); -+ -+ // DFU is missing schema for UpgradeData block names -+ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "entities", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, data, "block_entities", fromVersion, toVersion); -+ -+ final ListType blockTicks = data.getList("block_ticks", ObjectType.MAP); -+ if (blockTicks != null) { -+ for (int i = 0, len = blockTicks.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, blockTicks.getMap(i), "i", fromVersion, toVersion); -+ } -+ } -+ -+ final MapType upgradeData = data.getMap("UpgradeData"); -+ if (upgradeData != null) { -+ // Even though UpgradeData will retrieve the block from the World when the type no longer exists, -+ // the type from the world may not match what was actually queued. So, even though it may look like we -+ // can skip the walker here, we actually don't if we want to be thorough. -+ final ListType neighbourBlockTicks = upgradeData.getList("neighbor_block_ticks", ObjectType.MAP); -+ if (neighbourBlockTicks != null) { -+ for (int i = 0, len = neighbourBlockTicks.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, neighbourBlockTicks.getMap(i), "i", fromVersion, toVersion); -+ } -+ } -+ } -+ -+ final ListType sections = data.getList("sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ WalkerUtils.convertList(MCTypeRegistry.BIOME, section.getMap("biomes"), "palette", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section.getMap("block_states"), "palette", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, data.getMap("structures"), "starts", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java -new file mode 100644 -index 0000000000000000000000000000000000000000..236327249d2b95b799b90172d457601167492249 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2846 { -+ -+ protected static final int VERSION = MCVersions.V21W44A + 1; -+ -+ public static void register() { -+ ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of( -+ "minecraft:husbandry/play_jukebox_in_meadows", "minecraft:adventure/play_jukebox_in_meadows", -+ "minecraft:adventure/caves_and_cliff", "minecraft:adventure/fall_from_world_height", -+ "minecraft:adventure/ride_strider_in_overworld_lava", "minecraft:nether/ride_strider_in_overworld_lava" -+ )::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java -new file mode 100644 -index 0000000000000000000000000000000000000000..94ab7be8c34d2ebb557df5a0864130f7f12c2185 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java -@@ -0,0 +1,29 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2852 { -+ -+ protected static final int VERSION = MCVersions.V1_18_PRE5 + 1; -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType dimensions = data.getMap("dimensions"); -+ -+ for (final String dimensionKey : dimensions.keys()) { -+ final MapType dimension = dimensions.getMap(dimensionKey); -+ if (!dimension.hasKey("type")) { -+ throw new IllegalStateException("Unable load old custom worlds."); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7af7bf450080f65b8b7d7a8d2f941846c029e504 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java -@@ -0,0 +1,56 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2967 { -+ -+ protected static final int VERSION = MCVersions.V22W05A; -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType dimensions = data.getMap("dimensions"); -+ -+ if (dimensions == null) { -+ return null; -+ } -+ -+ for (final String dimension : dimensions.keys()) { -+ final MapType dimensionData = dimensions.getMap(dimension); -+ if (dimensionData == null) { -+ continue; -+ } -+ -+ final MapType generator = dimensionData.getMap("generator"); -+ if (generator == null) { -+ continue; -+ } -+ -+ final MapType settings = generator.getMap("settings"); -+ if (settings == null) { -+ continue; -+ } -+ -+ final MapType structures = settings.getMap("structures"); -+ if (structures == null) { -+ continue; -+ } -+ -+ for (final String structureKey : structures.keys()) { -+ structures.getMap(structureKey).setString("type", "minecraft:random_spread"); -+ } -+ -+ final MapType stronghold = structures.getMap("stronghold"); -+ stronghold.setString("type", "minecraft:concentric_rings"); -+ structures.setMap("minecraft:stronghold", stronghold.copy()); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fa824cdf629caec745eff7c09eb4570c62263752 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java -@@ -0,0 +1,192 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.common.collect.ImmutableMap; -+import it.unimi.dsi.fastutil.objects.Object2IntMap; -+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -+import java.util.HashMap; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Locale; -+import java.util.Map; -+ -+public final class V2970 { -+ -+ protected static final int VERSION = MCVersions.V22W07A + 1; -+ private static final Map CONVERSION_MAP = new HashMap<>( -+ ImmutableMap.builder() -+ .put("mineshaft", BiomeRemap.create(Map.of(List.of("minecraft:badlands", "minecraft:eroded_badlands", "minecraft:wooded_badlands"), "minecraft:mineshaft_mesa"), "minecraft:mineshaft")) -+ .put("shipwreck", BiomeRemap.create(Map.of(List.of("minecraft:beach", "minecraft:snowy_beach"), "minecraft:shipwreck_beached"), "minecraft:shipwreck")) -+ .put("ocean_ruin", BiomeRemap.create(Map.of(List.of("minecraft:warm_ocean", "minecraft:lukewarm_ocean", "minecraft:deep_lukewarm_ocean"), "minecraft:ocean_ruin_warm"), "minecraft:ocean_ruin_cold")) -+ .put("village", BiomeRemap.create(Map.of(List.of("minecraft:desert"), "minecraft:village_desert", List.of("minecraft:savanna"), "minecraft:village_savanna", List.of("minecraft:snowy_plains"), "minecraft:village_snowy", List.of("minecraft:taiga"), "minecraft:village_taiga"), "minecraft:village_plains")) -+ .put("ruined_portal", BiomeRemap.create(Map.of(List.of("minecraft:desert"), "minecraft:ruined_portal_desert", List.of("minecraft:badlands", "minecraft:eroded_badlands", "minecraft:wooded_badlands", "minecraft:windswept_hills", "minecraft:windswept_forest", "minecraft:windswept_gravelly_hills", "minecraft:savanna_plateau", "minecraft:windswept_savanna", "minecraft:stony_shore", "minecraft:meadow", "minecraft:frozen_peaks", "minecraft:jagged_peaks", "minecraft:stony_peaks", "minecraft:snowy_slopes"), "minecraft:ruined_portal_mountain", List.of("minecraft:bamboo_jungle", "minecraft:jungle", "minecraft:sparse_jungle"), "minecraft:ruined_portal_jungle", List.of("minecraft:deep_frozen_ocean", "minecraft:deep_cold_ocean", "minecraft:deep_ocean", "minecraft:deep_lukewarm_ocean", "minecraft:frozen_ocean", "minecraft:ocean", "minecraft:cold_ocean", "minecraft:lukewarm_ocean", "minecraft:warm_ocean"), "minecraft:ruined_portal_ocean"), "minecraft:ruined_portal")) // Fix MC-248814, ruined_portal_standard->ruined_portal -+ .put("pillager_outpost", BiomeRemap.create("minecraft:pillager_outpost")) -+ .put("mansion", BiomeRemap.create("minecraft:mansion")) -+ .put("jungle_pyramid", BiomeRemap.create("minecraft:jungle_pyramid")) -+ .put("desert_pyramid", BiomeRemap.create("minecraft:desert_pyramid")) -+ .put("igloo", BiomeRemap.create("minecraft:igloo")) -+ .put("swamp_hut", BiomeRemap.create("minecraft:swamp_hut")) -+ .put("stronghold", BiomeRemap.create("minecraft:stronghold")) -+ .put("monument", BiomeRemap.create("minecraft:monument")) -+ .put("fortress", BiomeRemap.create("minecraft:fortress")) -+ .put("endcity", BiomeRemap.create("minecraft:end_city")) -+ .put("buried_treasure", BiomeRemap.create("minecraft:buried_treasure")) -+ .put("nether_fossil", BiomeRemap.create("minecraft:nether_fossil")) -+ .put("bastion_remnant", BiomeRemap.create("minecraft:bastion_remnant")) -+ .build() -+ ); -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ private static Object2IntOpenHashMap countBiomes(final MapType chunk) { -+ final ListType sections = chunk.getList("sections", ObjectType.MAP); -+ if (sections == null) { -+ return null; -+ } -+ -+ final Object2IntOpenHashMap ret = new Object2IntOpenHashMap<>(); -+ -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ final MapType biomes = section.getMap("biomes"); -+ -+ if (biomes == null) { -+ continue; -+ } -+ -+ final ListType palette = biomes.getList("palette", ObjectType.STRING); -+ -+ if (palette == null) { -+ continue; -+ } -+ -+ for (int k = 0, len2 = palette.size(); k < len2; ++k) { -+ ret.addTo(palette.getString(k), 1); -+ } -+ } -+ -+ return ret; -+ } -+ -+ private static String getStructureConverted(String id, final Object2IntOpenHashMap biomeCount) { -+ id = id.toLowerCase(Locale.ROOT); -+ final BiomeRemap remap = CONVERSION_MAP.get(id); -+ if (remap == null) { -+ throw new IllegalArgumentException("Unknown structure " + id); -+ } -+ if (remap.biomeToNewStructure == null || biomeCount == null) { -+ return remap.dfl; -+ } -+ -+ final Object2IntOpenHashMap remapCount = new Object2IntOpenHashMap<>(); -+ -+ for (final Iterator> iterator = biomeCount.object2IntEntrySet().fastIterator(); iterator.hasNext();) { -+ final Object2IntMap.Entry entry = iterator.next(); -+ final String remappedStructure = remap.biomeToNewStructure.get(entry.getKey()); -+ if (remappedStructure != null) { -+ remapCount.addTo(remappedStructure, entry.getIntValue()); -+ } -+ } -+ -+ String converted = remap.dfl; -+ int maxCount = 0; -+ -+ for (final Iterator> iterator = remapCount.object2IntEntrySet().fastIterator(); iterator.hasNext();) { -+ final Object2IntMap.Entry entry = iterator.next(); -+ final int count = entry.getIntValue(); -+ if (count > maxCount) { -+ maxCount = count; -+ converted = entry.getKey(); -+ } -+ } -+ -+ return converted; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType structures = data.getMap("structures"); -+ if (structures == null || structures.isEmpty()) { -+ return null; -+ } -+ -+ final Object2IntOpenHashMap biomeCounts = countBiomes(data); -+ -+ final MapType starts = structures.getMap("starts"); -+ final MapType references = structures.getMap("References"); -+ -+ if (starts != null) { -+ final MapType newStarts = Types.NBT.createEmptyMap(); -+ structures.setMap("starts", newStarts); -+ -+ for (final String key : starts.keys()) { -+ final MapType value = starts.getMap(key); -+ if ("INVALID".equals(value.getString("id", "INVALID"))) { -+ continue; -+ } -+ -+ final String remapped = getStructureConverted(key, biomeCounts); -+ value.setString("id", remapped); -+ newStarts.setMap(remapped, value); -+ } -+ } -+ -+ // This TRULY is a guess, no idea what biomes the referent has. -+ if (references != null) { -+ final MapType newReferences = Types.NBT.createEmptyMap(); -+ structures.setMap("References", newReferences); -+ for (final String key : references.keys()) { -+ final long[] value = references.getLongs(key); -+ if (value.length == 0) { -+ continue; -+ } -+ -+ newReferences.setLongs(getStructureConverted(key, biomeCounts), value); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private static final class BiomeRemap { -+ -+ public final Map biomeToNewStructure; -+ public final String dfl; -+ -+ private BiomeRemap(final Map biomeToNewStructure, final String dfl) { -+ this.biomeToNewStructure = biomeToNewStructure; -+ this.dfl = dfl; -+ } -+ -+ public static BiomeRemap create(final String newId) { -+ return new BiomeRemap(null, newId); -+ } -+ -+ public static BiomeRemap create(final Map, String> biomeMap, final String newId) { -+ final Map biomeToNewStructure = new HashMap<>(); -+ -+ for (final Map.Entry, String> entry : biomeMap.entrySet()) { -+ final List biomes = entry.getKey(); -+ final String newBiomeStructure = entry.getValue(); -+ -+ for (int i = 0, len = biomes.size(); i < len; ++i) { -+ final String biome = biomes.get(i); -+ if (biomeToNewStructure.putIfAbsent(biome, newBiomeStructure) != null) { -+ throw new IllegalStateException("Duplicate biome remap: " + biome + " -> " + newBiomeStructure + ", but already mapped to " + biomeToNewStructure.get(biome)); -+ } -+ } -+ } -+ -+ return new BiomeRemap(biomeToNewStructure, newId); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java -new file mode 100644 -index 0000000000000000000000000000000000000000..97da66165f3e3788af0dfe667509ca7edb15b0a8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java -@@ -0,0 +1,38 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V3077 { -+ -+ protected static final int VERSION = MCVersions.V1_18_2 + 102; -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final boolean isLightOn = data.getBoolean("isLightOn"); -+ if (isLightOn) { -+ return null; -+ } -+ -+ final ListType sections = data.getList("sections", ObjectType.MAP); -+ if (sections == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ section.remove("BlockLight"); -+ section.remove("SkyLight"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4271eae27756eb5cbad77679dd562e676d644748 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V3078 { -+ -+ protected static final int VERSION = MCVersions.V1_18_2 + 103; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:frog"); -+ registerMob("minecraft:tadpole"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:sculk_shrieker", new GameEventListenerWalker()); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c0cd7cd8c2656713b97f83b7e02b65008b62c297 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V3081 { -+ -+ protected static final int VERSION = MCVersions.V22W11A + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:warden"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:warden", new GameEventListenerWalker()); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ab6ebf4d10842d20c20bcbcc76483d9cfe081862 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java -@@ -0,0 +1,14 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V3082 { -+ -+ protected static final int VERSION = MCVersions.V22W11A + 2; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:chest_boat", new DataWalkerItemLists("Items")); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0b133d6a806d571b976d8e96b9ca1e3b6933af20 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V3083 { -+ -+ protected static final int VERSION = MCVersions.V22W12A + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:allay"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:allay", new GameEventListenerWalker()); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java -new file mode 100644 -index 0000000000000000000000000000000000000000..52d8510e00d2373226f35e77db6fc7a893ec0764 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java -@@ -0,0 +1,39 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.util.NamespaceUtil; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V3084 { -+ -+ protected static final int VERSION = MCVersions.V22W12A + 2; -+ -+ protected static final Map GAME_EVENT_RENAMES = new HashMap<>(ImmutableMap.builder() -+ .put("minecraft:block_press", "minecraft:block_activate") -+ .put("minecraft:block_switch", "minecraft:block_activate") -+ .put("minecraft:block_unpress", "minecraft:block_deactivate") -+ .put("minecraft:block_unswitch", "minecraft:block_deactivate") -+ .put("minecraft:drinking_finish", "minecraft:drink") -+ .put("minecraft:elytra_free_fall", "minecraft:elytra_glide") -+ .put("minecraft:entity_damaged", "minecraft:entity_damage") -+ .put("minecraft:entity_dying", "minecraft:entity_die") -+ .put("minecraft:entity_killed", "minecraft:entity_die") -+ .put("minecraft:mob_interact", "minecraft:entity_interact") -+ .put("minecraft:ravager_roar", "minecraft:entity_roar") -+ .put("minecraft:ring_bell", "minecraft:block_change") -+ .put("minecraft:shulker_close", "minecraft:container_close") -+ .put("minecraft:shulker_open", "minecraft:container_open") -+ .put("minecraft:wolf_shaking", "minecraft:entity_shake") -+ .build() -+ ); -+ -+ public static void register() { -+ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.GAME_EVENT_NAME, (final String name) -> { -+ return GAME_EVENT_RENAMES.get(NamespaceUtil.correctNamespace(name)); -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java -new file mode 100644 -index 0000000000000000000000000000000000000000..554df81bb4f1a66bce539b42493f3ea7d4dff153 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java -@@ -0,0 +1,51 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterCriteriaRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterEntityToVariant; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import com.google.common.collect.ImmutableMap; -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V3086 { -+ -+ protected static final int VERSION = MCVersions.V22W13A + 1; -+ -+ protected static final Int2ObjectOpenHashMap CAT_ID_CONVERSION = new Int2ObjectOpenHashMap<>(); -+ static { -+ CAT_ID_CONVERSION.defaultReturnValue("minecraft:tabby"); -+ CAT_ID_CONVERSION.put(0, "minecraft:tabby"); -+ CAT_ID_CONVERSION.put(1, "minecraft:black"); -+ CAT_ID_CONVERSION.put(2, "minecraft:red"); -+ CAT_ID_CONVERSION.put(3, "minecraft:siamese"); -+ CAT_ID_CONVERSION.put(4, "minecraft:british"); -+ CAT_ID_CONVERSION.put(5, "minecraft:calico"); -+ CAT_ID_CONVERSION.put(6, "minecraft:persian"); -+ CAT_ID_CONVERSION.put(7, "minecraft:ragdoll"); -+ CAT_ID_CONVERSION.put(8, "minecraft:white"); -+ CAT_ID_CONVERSION.put(9, "minecraft:jellie"); -+ CAT_ID_CONVERSION.put(10, "minecraft:all_black"); -+ } -+ -+ protected static final Map CAT_ADVANCEMENTS_CONVERSION = new HashMap<>(ImmutableMap.builder() -+ .put("textures/entity/cat/tabby.png", "minecraft:tabby") -+ .put("textures/entity/cat/black.png", "minecraft:black") -+ .put("textures/entity/cat/red.png", "minecraft:red") -+ .put("textures/entity/cat/siamese.png", "minecraft:siamese") -+ .put("textures/entity/cat/british_shorthair.png", "minecraft:british") -+ .put("textures/entity/cat/calico.png", "minecraft:calico") -+ .put("textures/entity/cat/persian.png", "minecraft:persian") -+ .put("textures/entity/cat/ragdoll.png", "minecraft:ragdoll") -+ .put("textures/entity/cat/white.png", "minecraft:white") -+ .put("textures/entity/cat/jellie.png", "minecraft:jellie") -+ .put("textures/entity/cat/all_black.png", "minecraft:all_black") -+ .build() -+ ); -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new ConverterEntityToVariant(VERSION, "CatType", CAT_ID_CONVERSION::get)); -+ MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new ConverterCriteriaRename(VERSION, "minecraft:husbandry/complete_catalogue", CAT_ADVANCEMENTS_CONVERSION::get)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8cc7cadb921d52ebb5b8ed25078145536db5e7b5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java -@@ -0,0 +1,22 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterEntityToVariant; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+ -+public final class V3087 { -+ -+ protected static final int VERSION = MCVersions.V22W13A + 2; -+ -+ protected static Int2ObjectOpenHashMap FROG_ID_CONVERSION = new Int2ObjectOpenHashMap<>(); -+ static { -+ FROG_ID_CONVERSION.put(0, "minecraft:temperate"); -+ FROG_ID_CONVERSION.put(1, "minecraft:warm"); -+ FROG_ID_CONVERSION.put(2, "minecraft:cold"); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:frog", new ConverterEntityToVariant(VERSION, "Variant", FROG_ID_CONVERSION::get)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fb1af7826dd01fd6f5cfe2ad11ba63b934675d31 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java -@@ -0,0 +1,23 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.chunk.ConverterAddBlendingData; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+ -+public final class V3088 { -+ -+ // this class originally targeted 3079 but was changed to target a later version without changing the converter, zero clue why -+ // this class then targeted 3088 but was changed to target 3441 -+ // to maintain integrity of the data version, I chose to extract the converter to a separate class and use it in both versions -+ // the reason it is important to never change old converters once released is that it creates _two_ versions under the same id. -+ // Consider the case where a user force upgrades their world, but does not load the chunk. Then, consider the case where -+ // the user does not force upgrade their world. Then, Mojang comes along and makes a decision like this and now both -+ // players load the chunk - they went through a different conversion process, which ultimately creates two versions. -+ // Unfortunately this fix doesn't exactly resolve it, as anyone running Mojang's converters will now be different -+ // from DataConverter's. It's broadly a dumb situation all around that could be avoided if Mojang wasn't being careless here. -+ protected static final int VERSION = MCVersions.V22W14A; -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new ConverterAddBlendingData(VERSION)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b3250a0b5ae2ab0aa5fffaace882052388861fd8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java -@@ -0,0 +1,23 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3090 { -+ -+ protected static final int VERSION = MCVersions.V22W15A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:painting", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ RenameHelper.renameSingle(data, "Motive", "variant"); -+ RenameHelper.renameSingle(data, "Facing", "facing"); -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8354c85fc4d92f36555c7de9dc0dffd1da05529a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java -@@ -0,0 +1,22 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3093 { -+ -+ protected static final int VERSION = MCVersions.V22W17A; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:goat", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.setBoolean("HasLeftHorn", true); -+ data.setBoolean("HasRightHorn", true); -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java -new file mode 100644 -index 0000000000000000000000000000000000000000..39540b5f76af1c7d51a51db9d711f32a3c7f624c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java -@@ -0,0 +1,42 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3094 { -+ -+ public static final int VERSION = MCVersions.V22W17A + 1; -+ -+ private static final String[] SOUND_VARIANT_TO_INSTRUMENT = new String[] { -+ "minecraft:ponder_goat_horn", -+ "minecraft:sing_goat_horn", -+ "minecraft:seek_goat_horn", -+ "minecraft:feel_goat_horn", -+ "minecraft:admire_goat_horn", -+ "minecraft:call_goat_horn", -+ "minecraft:yearn_goat_horn", -+ "minecraft:dream_goat_horn" -+ }; -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:goat_horn", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ -+ if (tag == null) { -+ return null; -+ } -+ -+ final int soundVariant = tag.getInt("SoundVariant"); -+ tag.remove("SoundVariant"); -+ -+ tag.setString("instrument", SOUND_VARIANT_TO_INSTRUMENT[soundVariant < 0 || soundVariant >= SOUND_VARIANT_TO_INSTRUMENT.length ? 0 : soundVariant]); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d5ac17b59c0dcc9baaeff022ecbf827c237cf9d6 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java -@@ -0,0 +1,61 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterCriteriaRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterEntityVariantRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.poi.ConverterPoiDelete; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+ -+public final class V3097 { -+ -+ private static final int VERSION = MCVersions.V22W19A + 1; -+ -+ public static void register() { -+ final DataConverter, MapType> removeFilteredBookText = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ tag.remove("filtered_title"); -+ tag.remove("filtered_pages"); -+ -+ return null; -+ } -+ }; -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:writable_book", removeFilteredBookText); -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:written_book", removeFilteredBookText); -+ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:sign", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.remove("FilteredText1"); -+ data.remove("FilteredText2"); -+ data.remove("FilteredText3"); -+ data.remove("FilteredText4"); -+ -+ return null; -+ } -+ }); -+ -+ final Map britishRenamer = new HashMap<>(Map.of( -+ "minecraft:british", "minecraft:british_shorthair" -+ )); -+ final Set poiRemove = new HashSet<>(Set.of( -+ "minecraft:unemployed", -+ "minecraft:nitwit" -+ )); -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new ConverterEntityVariantRename(VERSION, britishRenamer::get)); -+ MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new ConverterCriteriaRename(VERSION, "minecraft:husbandry/complete_catalogue", britishRenamer::get)); -+ MCTypeRegistry.POI_CHUNK.addStructureConverter(new ConverterPoiDelete(VERSION, poiRemove::contains)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java -new file mode 100644 -index 0000000000000000000000000000000000000000..381b49f2c50d46e52f7f9c8f6baede4e72eb343d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java -@@ -0,0 +1,27 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3108 { -+ -+ private static final int VERSION = MCVersions.V1_19_1_PRE1 + 1; -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType context = data.getMap("__context"); -+ if ("minecraft:overworld".equals(context == null ? null : context.getString("dimension"))) { -+ return null; -+ } -+ -+ data.remove("blending_data"); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java -new file mode 100644 -index 0000000000000000000000000000000000000000..24f661419cd08caa4f6d8b3ea66f0d484d07b5b9 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java -@@ -0,0 +1,33 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3201 { -+ -+ private static final int VERSION = MCVersions.V1_19_2 + 81; -+ -+ public static void register() { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { -+ private static void fixList(final MapType data, final String target) { -+ if (data == null) { -+ return; -+ } -+ final String curr = data.getString(target); -+ if (curr == null) { -+ return; -+ } -+ data.setString(target, curr.replace("\"programer_art\"", "\"programmer_art\"")); -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ fixList(data, "resourcePacks"); -+ fixList(data, "incompatibleResourcePacks"); -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java -new file mode 100644 -index 0000000000000000000000000000000000000000..84572b5fb3930e005acb4ea3bce0441267247a40 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V3203 { -+ -+ private static final int VERSION = MCVersions.V1_19_2 + 83; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:camel"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dabaf437021ce7309ca005c21afc82d88d6b04c6 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java -@@ -0,0 +1,14 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V3204 { -+ -+ private static final int VERSION = MCVersions.V1_19_2 + 84; -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:chiseled_bookshelf", new DataWalkerItemLists("Items")); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f3a109ff01a96a750967e8becdc2a3e20b71b6dd ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java -@@ -0,0 +1,16 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenSpawnEgg; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+ -+public final class V3209 { -+ -+ private static final int VERSION = MCVersions.V22W45A + 1; -+ -+ public static void register() { -+ // Note: This converter reads entity id from its sub data, but we need no breakpoint because entity ids are not -+ // remapped this version -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:pig_spawn_egg", new ConverterFlattenSpawnEgg(VERSION, 0)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b01ae34d2e238f217eb8de8b2d3502e959b5b3a7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java -@@ -0,0 +1,28 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3214 { -+ -+ private static final int VERSION = MCVersions.V1_19_3_PRE3 + 1; -+ -+ public static void register() { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String value = data.getString("ao"); -+ -+ if ("0".equals(value)) { -+ data.setString("ao", "false"); -+ } else if ("1".equals(value) || "2".equals(value)) { -+ data.setString("ao", "true"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7bda48947850559e5ccc92ea504a64996005f7d6 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3319 { -+ -+ private static final int VERSION = MCVersions.V1_19_3 + 101; -+ -+ public static void register() { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.setBoolean("onboardAccessibility", false); -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5520dffc15f76a26fca3a26568b85a577cac255c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java -@@ -0,0 +1,82 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+import java.util.HashSet; -+import java.util.Set; -+ -+public final class V3322 { -+ -+ private static final int VERSION = MCVersions.V23W04A + 1; -+ -+ private static final Set EFFECT_ITEM_TYPES = new HashSet<>( -+ Set.of( -+ "minecraft:potion", -+ "minecraft:splash_potion", -+ "minecraft:lingering_potion", -+ "minecraft:tipped_arrow" -+ ) -+ ); -+ -+ private static void updateEffectList(final MapType root, final String path) { -+ if (root == null) { -+ return; -+ } -+ -+ final ListType effects = root.getList(path, ObjectType.MAP); -+ -+ if (effects == null) { -+ return; -+ } -+ -+ for (int i = 0, len = effects.size(); i < len; ++i) { -+ final MapType data = effects.getMap(i); -+ final MapType factorData = data.getMap("FactorCalculationData"); -+ if (factorData == null) { -+ continue; -+ } -+ -+ final int timestamp = factorData.getInt("effect_changed_timestamp", -1); -+ factorData.remove("effect_changed_timestamp"); -+ -+ final int duration = data.getInt("Duration", -1); -+ -+ final int ticksActive = timestamp - duration; -+ factorData.setInt("ticks_active", ticksActive); -+ } -+ } -+ -+ public static void register() { -+ final DataConverter, MapType> entityEffectFix = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateEffectList(data, "Effects"); -+ updateEffectList(data, "ActiveEffects"); -+ updateEffectList(data, "CustomPotionEffects"); -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.PLAYER.addStructureConverter(entityEffectFix); -+ MCTypeRegistry.ENTITY.addStructureConverter(entityEffectFix); -+ -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String id = data.getString("id"); -+ if (!EFFECT_ITEM_TYPES.contains(id)) { -+ return null; -+ } -+ -+ updateEffectList(data.getMap("tag"), "CustomPotionEffects"); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4d451d496232a8f15f8daf3a2e56989155ff36a5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java -@@ -0,0 +1,17 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+ -+public final class V3325 { -+ -+ private static final int VERSION = MCVersions.V23W05A + 2; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:item_display", new DataWalkerItems("item")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:block_display", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "block_state")); -+ // text_display is a simple entity -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e9decfa3a1f819354d3b3e6a1cb09b913609fe4d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V3326 { -+ -+ private static final int VERSION = MCVersions.V23W06A; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:sniffer"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8f12da18cedc50adedf08a4e12428e7e49788886 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java -@@ -0,0 +1,17 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerListPaths; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+ -+public final class V3327 { -+ -+ private static final int VERSION = MCVersions.V23W06A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:decorated_pot", new DataWalkerListPaths<>(MCTypeRegistry.ITEM_NAME, "shards")); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:decorated_pot", new DataWalkerItems("item")); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:suspicious_sand", new DataWalkerItems("item")); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java -new file mode 100644 -index 0000000000000000000000000000000000000000..67218286c8e7896641b331118e7794bb6a6c835e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java -@@ -0,0 +1,13 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+ -+public final class V3328 { -+ -+ private static final int VERSION = MCVersions.V23W06A + 2; -+ -+ public static void register() { -+ // registers simple entity "minecraft:interaction" -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b4584cb2b99abd8739f815c741ea2424fe583ac8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java -@@ -0,0 +1,45 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.tileentity.ConverterAbstractTileEntityRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V3438 { -+ -+ public static final int VERSION = MCVersions.V1_19_4 + 101; -+ -+ public static void register() { -+ // brushable block rename -+ MCTypeRegistry.TILE_ENTITY.copyWalkers(VERSION,"minecraft:suspicious_sand", "minecraft:brushable_block"); -+ -+ ConverterAbstractTileEntityRename.register(VERSION, new HashMap<>(Map.of( -+ "minecraft:suspicious_sand", "minecraft:brushable_block" -+ ))::get); -+ -+ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:brushable_block", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ RenameHelper.renameSingle(data, "loot_table", "LootTable"); -+ RenameHelper.renameSingle(data, "loot_table_seed", "LootTableSeed"); -+ return null; -+ } -+ }); -+ -+ ConverterAbstractItemRename.register(VERSION, new HashMap<>( -+ Map.of( -+ "minecraft:pottery_shard_archer", "minecraft:archer_pottery_shard", -+ "minecraft:pottery_shard_prize", "minecraft:prize_pottery_shard", -+ "minecraft:pottery_shard_arms_up", "minecraft:arms_up_pottery_shard", -+ "minecraft:pottery_shard_skull", "minecraft:skull_pottery_shard" -+ ) -+ )::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java -new file mode 100644 -index 0000000000000000000000000000000000000000..301f8582a38fc130bf48be785b7368ac5425e510 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java -@@ -0,0 +1,94 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3439 { -+ -+ private static final int VERSION = MCVersions.V1_19_4 + 102; -+ -+ public static void register() { -+ final DataConverter, MapType> signTileUpdater = new DataConverter<>(VERSION) { -+ private static final String DEFAULT_COLOR = "black"; -+ -+ private static ListType migrateToList(final MapType root, final String prefix) { -+ if (root == null) { -+ return null; -+ } -+ -+ final ListType ret = root.getTypeUtil().createEmptyList(); -+ -+ ret.addString(root.getString(prefix.concat("1"), ComponentUtils.EMPTY)); -+ ret.addString(root.getString(prefix.concat("2"), ComponentUtils.EMPTY)); -+ ret.addString(root.getString(prefix.concat("3"), ComponentUtils.EMPTY)); -+ ret.addString(root.getString(prefix.concat("4"), ComponentUtils.EMPTY)); -+ -+ return ret; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ // front text -+ final MapType frontText = data.getTypeUtil().createEmptyMap(); -+ data.setMap("front_text", frontText); -+ -+ final ListType frontMessages = migrateToList(data, "Text"); -+ frontText.setList("messages", frontMessages); -+ -+ ListType frontFilteredMessages = null; -+ -+ for (int i = 0; i < 4; ++i) { -+ final String filtered = data.getString("FilteredText" + i); -+ if (filtered == null) { -+ if (frontFilteredMessages != null) { -+ frontFilteredMessages.addString(frontMessages.getString(i)); -+ } -+ continue; -+ } -+ -+ if (frontFilteredMessages == null) { -+ frontFilteredMessages = data.getTypeUtil().createEmptyList(); -+ for (int k = 0; k < i; ++k) { -+ frontFilteredMessages.addString(frontMessages.getString(k)); -+ } -+ } -+ -+ frontFilteredMessages.addString(filtered); -+ } -+ -+ if (frontFilteredMessages != null) { -+ frontText.setList("filtered_messages", frontFilteredMessages); -+ } -+ -+ frontText.setString("color", data.getString("Color", DEFAULT_COLOR)); -+ frontText.setBoolean("has_glowing_text", data.getBoolean("GlowingText", false)); -+ frontText.setBoolean("_filtered_correct", true); -+ -+ // back text -+ final MapType backText = data.getTypeUtil().createEmptyMap(); -+ data.setMap("back_text", backText); -+ -+ final ListType blankMessages = data.getTypeUtil().createEmptyList(); -+ backText.setList("messages", blankMessages); -+ -+ for (int i = 0; i < 4; ++i) { -+ blankMessages.addString(ComponentUtils.EMPTY); -+ } -+ -+ backText.setString("color", DEFAULT_COLOR); -+ backText.setBoolean("has_glowing_text", false); -+ -+ // misc -+ data.setBoolean("is_waxed", false); -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:sign", signTileUpdater); -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:hanging_sign", signTileUpdater); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f4209b03ec7c126aa728704d58dc0399fd89c698 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java -@@ -0,0 +1,27 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.leveldat.ConverterRemoveFeatureFlag; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.util.NamespaceUtil; -+ -+import java.util.Arrays; -+import java.util.HashSet; -+ -+public final class V3440 { -+ -+ private static final int VERSION = MCVersions.V1_19_4 + 103; -+ -+ public static void register() { -+ // Note: MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST is namespaced string -+ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST, (final String in) -> { -+ return "minecraft:overworld_update_1_20".equals(NamespaceUtil.correctNamespace(in)) ? "minecraft:overworld" : null; -+ }); -+ MCTypeRegistry.LEVEL.addStructureConverter(new ConverterRemoveFeatureFlag(VERSION, new HashSet<>( -+ Arrays.asList( -+ "minecraft:update_1_20" -+ ) -+ ))); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e37f49012c960301273412ae79ca4e69d9d1ceb9 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java -@@ -0,0 +1,15 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.chunk.ConverterAddBlendingData; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+ -+public final class V3441 { -+ -+ private static final int VERSION = MCVersions.V1_19_4 + 104; -+ -+ public static void register() { -+ // See V3088 for why this converter is duplicated here and in V3088 -+ MCTypeRegistry.CHUNK.addStructureConverter(new ConverterAddBlendingData(VERSION)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f5a7b72755b53d4e406c95f5ea5857d7f94f19ad ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java -@@ -0,0 +1,47 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V3447 { -+ -+ private static final int VERSION = MCVersions.V23W14A + 2; -+ -+ public static void register() { -+ final String[] targets = new String[] { -+ "minecraft:angler_pottery_shard", -+ "minecraft:archer_pottery_shard", -+ "minecraft:arms_up_pottery_shard", -+ "minecraft:blade_pottery_shard", -+ "minecraft:brewer_pottery_shard", -+ "minecraft:burn_pottery_shard", -+ "minecraft:danger_pottery_shard", -+ "minecraft:explorer_pottery_shard", -+ "minecraft:friend_pottery_shard", -+ "minecraft:heart_pottery_shard", -+ "minecraft:heartbreak_pottery_shard", -+ "minecraft:howl_pottery_shard", -+ "minecraft:miner_pottery_shard", -+ "minecraft:mourner_pottery_shard", -+ "minecraft:plenty_pottery_shard", -+ "minecraft:prize_pottery_shard", -+ "minecraft:sheaf_pottery_shard", -+ "minecraft:shelter_pottery_shard", -+ "minecraft:skull_pottery_shard", -+ "minecraft:snort_pottery_shard" -+ }; -+ // shard->sherd -+ final Map rename = new HashMap<>(targets.length); -+ -+ for (final String target : targets) { -+ final String replace = target.replace("_pottery_shard", "_pottery_sherd"); -+ if (rename.put(target, replace) != null) { -+ throw new IllegalArgumentException("Duplicate target " + target); -+ } -+ } -+ -+ ConverterAbstractItemRename.register(VERSION, rename::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6861a732d551b4ee0a12eb1321a12f86d352ad0a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java -@@ -0,0 +1,26 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerListPaths; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3448 { -+ -+ private static final int VERSION = MCVersions.V23W14A + 3; -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:decorated_pot", new DataWalkerListPaths<>(MCTypeRegistry.ITEM_NAME, "sherds")); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:decorated_pot", new DataWalkerItems("item")); -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:decorated_pot", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ RenameHelper.renameSingle(data, "shards", "sherds"); -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java -new file mode 100644 -index 0000000000000000000000000000000000000000..27d7214108f82baaafad6e47b2d0c19282899b4b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.chunk.ConverterRenameStatus; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V3450 { -+ -+ private static final int VERSION = MCVersions.V23W16A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new ConverterRenameStatus(VERSION, new HashMap<>( -+ Map.of( -+ "minecraft:liquid_carvers", "minecraft:carvers", -+ "minecraft:heightmaps", "minecraft:spawn" -+ ) -+ )::get)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bec25939a78141d989414a5d4f33ce134f347bb5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java -@@ -0,0 +1,36 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V3451 { -+ -+ private static final int VERSION = MCVersions.V23W16A + 2; -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.remove("isLightOn"); -+ -+ final ListType sections = data.getList("sections", ObjectType.MAP); -+ if (sections == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ section.remove("BlockLight"); -+ section.remove("SkyLight"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java -new file mode 100644 -index 0000000000000000000000000000000000000000..86509b2fa3c83dc485776d36b7bc2944b1f9a0b5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java -@@ -0,0 +1,34 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3459 { -+ -+ private static final int VERSION = MCVersions.V1_20_PRE5 + 1; -+ -+ public static void register() { -+ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.hasKey("DragonFight")) { -+ return null; -+ } -+ -+ final MapType dimensionData = data.getMap("DimensionData"); -+ if (dimensionData == null) { -+ return null; -+ } -+ -+ final MapType endData = dimensionData.getMap("1"); -+ if (endData != null) { -+ data.setMap("DragonFight", endData.getMap("DragonFight", endData.getTypeUtil().createEmptyMap()).copy()); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2421a884780d29a1f7776db8cc1f6fd7316fd0de ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java -@@ -0,0 +1,91 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V3564 { -+ -+ private static final int VERSION = MCVersions.V1_20_1 + 99; -+ -+ public static void register() { -+ final DataConverter, MapType> converter = new DataConverter<>(VERSION) { -+ -+ private static final String[] LEGACY_FIELDS = new String[] { -+ "Text1", -+ "Text2", -+ "Text3", -+ "Text4", -+ -+ "FilteredText1", -+ "FilteredText2", -+ "FilteredText3", -+ "FilteredText4", -+ -+ "Color", -+ -+ "GlowingText" -+ }; -+ -+ -+ private static void updateText(final MapType text) { -+ if (text == null) { -+ return; -+ } -+ -+ if (text.getBoolean("_filtered_correct", false)) { -+ text.remove("_filtered_correct"); -+ return; -+ } -+ -+ final ListType filteredMessages = text.getList("filtered_messages", ObjectType.STRING); -+ -+ if (filteredMessages == null || filteredMessages.size() == 0) { -+ return; -+ } -+ -+ // should treat null here as empty list -+ final ListType messages = text.getList("messages", ObjectType.STRING); -+ -+ final ListType newFilteredList = filteredMessages.getTypeUtil().createEmptyList(); -+ boolean newFilteredIsEmpty = true; -+ -+ for (int i = 0, len = filteredMessages.size(); i < len; ++i) { -+ final String filtered = filteredMessages.getString(i); -+ final String message = messages != null && i < messages.size() ? messages.getString(i) : ComponentUtils.EMPTY; -+ -+ final String newFiltered = ComponentUtils.EMPTY.equals(filtered) ? message : filtered; -+ -+ newFilteredList.addString(newFiltered); -+ -+ newFilteredIsEmpty = newFilteredIsEmpty && ComponentUtils.EMPTY.equals(newFiltered); -+ } -+ -+ if (newFilteredIsEmpty) { -+ text.remove("filtered_messages"); -+ } else { -+ text.setList("filtered_messages", newFilteredList); -+ } -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateText(data.getMap("front_text")); -+ updateText(data.getMap("back_text")); -+ -+ for (final String toRemove : LEGACY_FIELDS) { -+ data.remove(toRemove); -+ } -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:sign", converter); -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:hanging_sign", converter); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d63c4c3a53bf9cd67acc5515e9176b972cd13ce7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java -@@ -0,0 +1,30 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3565 { -+ -+ private static final int VERSION = MCVersions.V1_20_1 + 100; -+ -+ public static void register() { -+ MCTypeRegistry.SAVED_DATA_RANDOM_SEQUENCES.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { -+ final MapType oldData = root.getMap("data"); -+ if (oldData == null) { -+ return null; -+ } -+ -+ final MapType newData = root.getTypeUtil().createEmptyMap(); -+ root.setMap("data", newData); -+ -+ newData.setMap("sequences", oldData); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2501759bc1c0313c0556d4f75a0c4a3d3b6f5449 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java -@@ -0,0 +1,56 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V3566 { -+ -+ private static final int VERSION = MCVersions.V1_20_1 + 101; -+ -+ public static void register() { -+ MCTypeRegistry.SAVED_DATA_SCOREBOARD.addStructureConverter(new DataConverter<>(VERSION) { -+ -+ private static final Map SLOT_RENAMES = new HashMap<>( -+ ImmutableMap.builder() -+ .put("slot_0", "list") -+ .put("slot_1", "sidebar") -+ .put("slot_2", "below_name") -+ .put("slot_3", "sidebar.team.black") -+ .put("slot_4", "sidebar.team.dark_blue") -+ .put("slot_5", "sidebar.team.dark_green") -+ .put("slot_6", "sidebar.team.dark_aqua") -+ .put("slot_7", "sidebar.team.dark_red") -+ .put("slot_8", "sidebar.team.dark_purple") -+ .put("slot_9", "sidebar.team.gold") -+ .put("slot_10", "sidebar.team.gray") -+ .put("slot_11", "sidebar.team.dark_gray") -+ .put("slot_12", "sidebar.team.blue") -+ .put("slot_13", "sidebar.team.green") -+ .put("slot_14", "sidebar.team.aqua") -+ .put("slot_15", "sidebar.team.red") -+ .put("slot_16", "sidebar.team.light_purple") -+ .put("slot_17", "sidebar.team.yellow") -+ .put("slot_18", "sidebar.team.white") -+ .build() -+ ); -+ -+ @Override -+ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { -+ final MapType data = root.getMap("data"); -+ if (data == null) { -+ return null; -+ } -+ -+ RenameHelper.renameKeys(data.getMap("DisplaySlots"), SLOT_RENAMES::get); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java -new file mode 100644 -index 0000000000000000000000000000000000000000..311a57529c5f95ce48631b48fefb4ebae401b3f7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java -@@ -0,0 +1,243 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+ -+public final class V3568 { -+ -+ private static final int VERSION = MCVersions.V23W31A + 1; -+ -+ private static final String[] EFFECT_ID_MAP = new String[34]; -+ static { -+ EFFECT_ID_MAP[1] = "minecraft:speed"; -+ EFFECT_ID_MAP[2] = "minecraft:slowness"; -+ EFFECT_ID_MAP[3] = "minecraft:haste"; -+ EFFECT_ID_MAP[4] = "minecraft:mining_fatigue"; -+ EFFECT_ID_MAP[5] = "minecraft:strength"; -+ EFFECT_ID_MAP[6] = "minecraft:instant_health"; -+ EFFECT_ID_MAP[7] = "minecraft:instant_damage"; -+ EFFECT_ID_MAP[8] = "minecraft:jump_boost"; -+ EFFECT_ID_MAP[9] = "minecraft:nausea"; -+ EFFECT_ID_MAP[10] = "minecraft:regeneration"; -+ EFFECT_ID_MAP[11] = "minecraft:resistance"; -+ EFFECT_ID_MAP[12] = "minecraft:fire_resistance"; -+ EFFECT_ID_MAP[13] = "minecraft:water_breathing"; -+ EFFECT_ID_MAP[14] = "minecraft:invisibility"; -+ EFFECT_ID_MAP[15] = "minecraft:blindness"; -+ EFFECT_ID_MAP[16] = "minecraft:night_vision"; -+ EFFECT_ID_MAP[17] = "minecraft:hunger"; -+ EFFECT_ID_MAP[18] = "minecraft:weakness"; -+ EFFECT_ID_MAP[19] = "minecraft:poison"; -+ EFFECT_ID_MAP[20] = "minecraft:wither"; -+ EFFECT_ID_MAP[21] = "minecraft:health_boost"; -+ EFFECT_ID_MAP[22] = "minecraft:absorption"; -+ EFFECT_ID_MAP[23] = "minecraft:saturation"; -+ EFFECT_ID_MAP[24] = "minecraft:glowing"; -+ EFFECT_ID_MAP[25] = "minecraft:levitation"; -+ EFFECT_ID_MAP[26] = "minecraft:luck"; -+ EFFECT_ID_MAP[27] = "minecraft:unluck"; -+ EFFECT_ID_MAP[28] = "minecraft:slow_falling"; -+ EFFECT_ID_MAP[29] = "minecraft:conduit_power"; -+ EFFECT_ID_MAP[30] = "minecraft:dolphins_grace"; -+ EFFECT_ID_MAP[31] = "minecraft:bad_omen"; -+ EFFECT_ID_MAP[32] = "minecraft:hero_of_the_village"; -+ EFFECT_ID_MAP[33] = "minecraft:darkness"; -+ } -+ private static final Set EFFECT_ITEMS = -+ new HashSet<>( -+ Set.of( -+ "minecraft:potion", -+ "minecraft:splash_potion", -+ "minecraft:lingering_potion", -+ "minecraft:tipped_arrow" -+ ) -+ ); -+ -+ private static String readLegacyEffect(final MapType data, final String path) { -+ final Number id = data.getNumber(path); -+ if (id == null) { -+ return null; -+ } -+ -+ final int castedId = id.intValue(); -+ return castedId >= 0 && castedId < EFFECT_ID_MAP.length ? EFFECT_ID_MAP[castedId] : null; -+ } -+ -+ private static void convertLegacyEffect(final MapType data, final String legacyPath, final String newPath) { -+ final Number id = data.getNumber(legacyPath); -+ data.remove(legacyPath); -+ -+ if (id == null) { -+ return; -+ } -+ -+ final int castedId = id.intValue(); -+ final String newId = castedId >= 0 && castedId < EFFECT_ID_MAP.length ? EFFECT_ID_MAP[castedId] : null; -+ -+ if (newId == null) { -+ return; -+ } -+ -+ data.setString(newPath, newId); -+ } -+ -+ private static final Map MOB_EFFECT_RENAMES = new HashMap<>(); -+ static { -+ MOB_EFFECT_RENAMES.put("Ambient", "ambient"); -+ MOB_EFFECT_RENAMES.put("Amplifier", "amplifier"); -+ MOB_EFFECT_RENAMES.put("Duration", "duration"); -+ MOB_EFFECT_RENAMES.put("ShowParticles", "show_particles"); -+ MOB_EFFECT_RENAMES.put("ShowIcon", "show_icon"); -+ MOB_EFFECT_RENAMES.put("FactorCalculationData", "factor_calculation_data"); -+ MOB_EFFECT_RENAMES.put("HiddenEffect", "hidden_effect"); -+ } -+ -+ private static void convertMobEffect(final MapType mobEffect) { -+ if (mobEffect == null) { -+ return; -+ } -+ -+ convertLegacyEffect(mobEffect, "Id", "id"); -+ -+ for (final Map.Entry rename : MOB_EFFECT_RENAMES.entrySet()) { -+ RenameHelper.renameSingle(mobEffect, rename.getKey(), rename.getValue()); -+ } -+ -+ convertMobEffect(mobEffect.getMap("hidden_effect")); -+ } -+ -+ private static void convertMobEffectList(final MapType data, final String oldPath, final String newPath) { -+ final ListType effects = data.getList(oldPath, ObjectType.MAP); -+ if (effects == null) { -+ return; -+ } -+ -+ for (int i = 0, len = effects.size(); i < len; ++i) { -+ convertMobEffect(effects.getMap(i)); -+ } -+ -+ data.remove(oldPath); -+ data.setList(newPath, effects); -+ } -+ -+ private static void removeAndSet(final MapType data, final String toRemovePath, -+ final String toSetPath, final Object toSet) { -+ data.remove(toRemovePath); -+ if (toSet != null) { -+ data.setGeneric(toSetPath, toSet); -+ } -+ } -+ -+ private static void updateSuspiciousStew(final MapType from, final MapType into) { -+ removeAndSet(into, "EffectId", "id", readLegacyEffect(from, "EffectId")); -+ removeAndSet(into, "EffectDuration", "duration", from.getGeneric("EffectDuration")); -+ } -+ -+ public static void register() { -+ final DataConverter, MapType> beaconConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ convertLegacyEffect(data, "Primary", "primary_effect"); -+ convertLegacyEffect(data, "Secondary", "secondary_effect"); -+ -+ return null; -+ } -+ }; -+ -+ final DataConverter, MapType> mooshroomConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType newEffect = data.getTypeUtil().createEmptyMap(); -+ updateSuspiciousStew(data, newEffect); -+ -+ data.remove("EffectId"); -+ data.remove("EffectDuration"); -+ -+ if (!newEffect.isEmpty()) { -+ final ListType stewEffects = data.getTypeUtil().createEmptyList(); -+ data.setList("stew_effects", stewEffects); -+ -+ stewEffects.addMap(newEffect); -+ } -+ -+ return null; -+ } -+ }; -+ final DataConverter, MapType> arrowConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ convertMobEffectList(data, "CustomPotionEffects", "custom_potion_effects"); -+ return null; -+ } -+ }; -+ final DataConverter, MapType> areaEffectCloudConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ convertMobEffectList(data, "Effects", "effects"); -+ return null; -+ } -+ }; -+ final DataConverter, MapType> livingEntityConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ convertMobEffectList(data, "ActiveEffects", "active_effects"); -+ return null; -+ } -+ }; -+ -+ final DataConverter, MapType> itemConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { -+ final String id = root.getString("id"); -+ -+ final MapType tag = root.getMap("tag"); -+ -+ if (tag == null) { -+ return null; -+ } -+ -+ if ("minecraft:suspicious_stew".equals(id)) { -+ RenameHelper.renameSingle(tag, "Effects", "effects"); -+ -+ final ListType effects = tag.getList("effects", ObjectType.MAP); -+ -+ if (effects != null) { -+ for (int i = 0, len = effects.size(); i < len; ++i) { -+ final MapType effect = effects.getMap(i); -+ updateSuspiciousStew(effect, effect); -+ } -+ } -+ -+ return null; -+ } -+ -+ if (EFFECT_ITEMS.contains(id)) { -+ convertMobEffectList(tag, "CustomPotionEffects", "custom_potion_effects"); -+ return null; -+ } -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:beacon", beaconConverter); -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:mooshroom", mooshroomConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:arrow", arrowConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:area_effect_cloud", areaEffectCloudConverter); -+ MCTypeRegistry.ENTITY.addStructureConverter(livingEntityConverter); -+ -+ MCTypeRegistry.PLAYER.addStructureConverter(livingEntityConverter); -+ -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(itemConverter); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java -new file mode 100644 -index 0000000000000000000000000000000000000000..43cb10e3f13f9a2ffd82af70c7cae3b845cfc413 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java -@@ -0,0 +1,14 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V3682 { -+ -+ private static final int VERSION = MCVersions.V23W41A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:crafter", new DataWalkerItemLists("Items")); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d52a5a17da2c20cdc1b39f6ba6b1dbfbb9a21a0f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java -@@ -0,0 +1,31 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3683 { -+ -+ private static final int VERSION = MCVersions.V23W41A + 2; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:tnt", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ RenameHelper.renameSingle(data, "Fuse", "fuse"); -+ -+ final MapType defaultState = data.getTypeUtil().createEmptyMap(); -+ data.setMap("block_state", defaultState); -+ -+ defaultState.setString("Name", "minecraft:tnt"); -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:tnt", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "block_state")); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e0eed7f2c636e5ff65ad4c8c49c0111c6f1c04f2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java -@@ -0,0 +1,62 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+ -+public final class V3685 { -+ -+ private static final int VERSION = MCVersions.V23W42A + 1; -+ -+ private static String getType(final MapType arrow) { -+ return "minecraft:empty".equals(arrow.getString("Potion", "minecraft:empty")) ? "minecraft:arrow" : "minecraft:tipped_arrow"; -+ } -+ -+ private static MapType createItem(final TypeUtil util, final String id, final int count) { -+ final MapType ret = util.createEmptyMap(); -+ -+ ret.setString("id", id); -+ ret.setInt("Count", count); -+ -+ return ret; -+ } -+ -+ private static void registerArrowEntity(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); -+ // new: item -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItems("item")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:trident", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ RenameHelper.renameSingle(data, "Trident", "item"); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:arrow", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.setMap("item", createItem(data.getTypeUtil(), getType(data), 1)); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:spectral_arrow", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.setMap("item", createItem(data.getTypeUtil(), "minecraft:spectral_arrow", 1)); -+ return null; -+ } -+ }); -+ -+ registerArrowEntity("minecraft:trident"); -+ registerArrowEntity("minecraft:spectral_arrow"); -+ registerArrowEntity("minecraft:arrow"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java -new file mode 100644 -index 0000000000000000000000000000000000000000..427841b46b4fbb993aee6d8670d42eaf91f41793 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java -@@ -0,0 +1,37 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V3689 { -+ -+ private static final int VERSION = MCVersions.V23W44A + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:breeze"); -+ // minecraft:wind_charge is a simple entity -+ -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:trial_spawner", (final MapType data, final long fromVersion, final long toVersion) -> { -+ final ListType spawnPotentials = data.getList("spawn_potentials", ObjectType.MAP); -+ if (spawnPotentials != null) { -+ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, spawnPotentials.getMap(i).getMap("data"), "entity", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "spawn_data", fromVersion, toVersion); -+ return null; -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d49be320a8bc5f84ec1e0392257eede1a673bb27 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java -@@ -0,0 +1,23 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V3692 { -+ -+ private static final int VERSION = MCVersions.V23W46A + 1; -+ -+ private static final Map GRASS_RENAME = new HashMap<>( -+ Map.of( -+ "minecraft:grass", "minecraft:short_grass" -+ ) -+ ); -+ -+ public static void register() { -+ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, GRASS_RENAME::get); -+ ConverterAbstractItemRename.register(VERSION, GRASS_RENAME::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6ab2bf99d72983fc2742a1f6f2f7fa671611526d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V501 { -+ -+ protected static final int VERSION = MCVersions.V16W20A; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("PolarBear"); -+ } -+ -+ private V501() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java -new file mode 100644 -index 0000000000000000000000000000000000000000..febeab68a5eec229ecca4f9e7b82c9ca99b3dbe1 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java -@@ -0,0 +1,46 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.concurrent.ThreadLocalRandom; -+ -+public final class V502 { -+ -+ protected static final int VERSION = MCVersions.V16W20A + 1; -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, (final String name) -> { -+ return "minecraft:cooked_fished".equals(name) ? "minecraft:cooked_fish" : null; -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("Zombie", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!data.getBoolean("IsVillager")) { -+ return null; -+ } -+ -+ data.remove("IsVillager"); -+ -+ if (data.hasKey("ZombieType")) { -+ return null; -+ } -+ -+ int type = data.getInt("VillagerProfession", -1); -+ // Vanilla doesn't remove the profession tag, so we don't! -+ if (type < 0 || type >= 6) { -+ type = ThreadLocalRandom.current().nextInt(6); -+ } -+ -+ data.setInt("ZombieType", type); -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V502() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java -new file mode 100644 -index 0000000000000000000000000000000000000000..30769f902c7d694bce41ab319d0b9a87c6103f11 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java -@@ -0,0 +1,24 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V505 { -+ -+ protected static final int VERSION = MCVersions.V16W21B + 1; -+ -+ public static void register() { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.setString("useVbo", "true"); -+ return null; -+ } -+ }); -+ } -+ -+ private V505() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f46b1a0c4bc96d638853cc61e5703798dbf6b886 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java -@@ -0,0 +1,33 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V700 { -+ -+ protected static final int VERSION = MCVersions.V1_10_2 + 188; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("Guardian", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getBoolean("Elder")) { -+ data.setString("id", "ElderGuardian"); -+ } -+ data.remove("Elder"); -+ return null; -+ } -+ }); -+ -+ registerMob("ElderGuardian"); -+ } -+ -+ private V700() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java -new file mode 100644 -index 0000000000000000000000000000000000000000..42f173f426fb7d26e5ddb5a1c92c63b2e6a4930c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java -@@ -0,0 +1,43 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V701 { -+ -+ protected static final int VERSION = MCVersions.V1_10_2 + 189; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("Skeleton", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int type = data.getInt("SkeletonType"); -+ data.remove("SkeletonType"); -+ -+ switch (type) { -+ case 1: -+ data.setString("id", "WitherSkeleton"); -+ break; -+ case 2: -+ data.setString("id", "Stray"); -+ break; -+ } -+ -+ return null; -+ } -+ }); -+ -+ registerMob("WitherSkeleton"); -+ registerMob("Stray"); -+ } -+ -+ private V701() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5cc91edde9c8160f75165bcef554023246e0a224 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java -@@ -0,0 +1,53 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V702 { -+ -+ protected static final int VERSION = MCVersions.V1_10_2 + 190; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("Zombie", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int zombieType = data.getInt("ZombieType"); -+ data.remove("ZombieType"); -+ -+ switch (zombieType) { -+ case 0: -+ default: -+ break; -+ -+ case 1: -+ case 2: -+ case 3: -+ case 4: -+ case 5: -+ data.setString("id", "ZombieVillager"); -+ data.setInt("Profession", zombieType - 1); -+ break; -+ -+ case 6: -+ data.setString("id", "Husk"); -+ break; -+ } -+ -+ return null; -+ } -+ }); -+ -+ registerMob("ZombieVillager"); -+ registerMob( "Husk"); -+ } -+ -+ private V702() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java -new file mode 100644 -index 0000000000000000000000000000000000000000..88d9c0fcd88ccfd6d6b46ae050914079c816fa3f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java -@@ -0,0 +1,66 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V703 { -+ -+ protected static final int VERSION = MCVersions.V1_10_2 + 191; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("EntityHorse", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int type = data.getInt("Type"); -+ data.remove("Type"); -+ -+ switch (type) { -+ case 0: -+ default: -+ data.setString("id", "Horse"); -+ break; -+ -+ case 1: -+ data.setString("id", "Donkey"); -+ break; -+ -+ case 2: -+ data.setString("id", "Mule"); -+ break; -+ -+ case 3: -+ data.setString("id", "ZombieHorse"); -+ break; -+ -+ case 4: -+ data.setString("id", "SkeletonHorse"); -+ break; -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Horse", new DataWalkerItems("ArmorItem", "SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Horse", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Donkey", new DataWalkerItems("SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Donkey", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Mule", new DataWalkerItems("SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Mule", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "ZombieHorse", new DataWalkerItems("SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "ZombieHorse", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "SkeletonHorse", new DataWalkerItems("SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "SkeletonHorse", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ private V703() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ef080b7c625c977c1dd4fe179ac2ca40889720b2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java -@@ -0,0 +1,394 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.item_name.DataWalkerItemNames; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.mojang.logging.LogUtils; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.world.item.BlockItem; -+import net.minecraft.world.item.Item; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.EntityBlock; -+import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.entity.BlockEntityType; -+import org.slf4j.Logger; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V704 { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V1_10_2 + 192; -+ -+ public static final Map ITEM_ID_TO_TILE_ENTITY_ID = new HashMap<>() { -+ @Override -+ public String put(final String key, final String value) { -+ if (this.containsKey(key)) { -+ LOGGER.error("Duplicate item id to tile key: " + key); -+ throw new RuntimeException(); // only devs should see the consequence of this... at least start up the damn thing... -+ } -+ return super.put(key, value); -+ } -+ }; -+ static { -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:furnace", "minecraft:furnace"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lit_furnace", "minecraft:furnace"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chest", "minecraft:chest"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:trapped_chest", "minecraft:chest"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:ender_chest", "minecraft:ender_chest"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jukebox", "minecraft:jukebox"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dispenser", "minecraft:dispenser"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dropper", "minecraft:dropper"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mob_spawner", "minecraft:mob_spawner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:spawner", "minecraft:mob_spawner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:noteblock", "minecraft:noteblock"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brewing_stand", "minecraft:brewing_stand"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:enhanting_table", "minecraft:enchanting_table"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:command_block", "minecraft:command_block"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:beacon", "minecraft:beacon"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:skull", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector", "minecraft:daylight_detector"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:hopper", "minecraft:hopper"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:flower_pot", "minecraft:flower_pot"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:repeating_command_block", "minecraft:command_block"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chain_command_block", "minecraft:command_block"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:white_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:orange_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:magenta_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_blue_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:yellow_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lime_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:pink_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:gray_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:silver_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cyan_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:purple_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blue_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brown_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:green_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:red_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:black_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_gray_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:white_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:orange_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:magenta_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_blue_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:yellow_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lime_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:pink_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:gray_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:silver_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cyan_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:purple_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blue_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brown_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:green_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:red_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:black_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:piston_head", "minecraft:piston"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector_inverted", "minecraft:daylight_detector"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:unpowered_comparator", "minecraft:comparator"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:powered_comparator", "minecraft:comparator"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:structure_block", "minecraft:structure_block"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_portal", "minecraft:end_portal"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_gateway", "minecraft:end_gateway"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:shield", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:white_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:orange_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:magenta_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_blue_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:yellow_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lime_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:pink_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:gray_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:silver_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cyan_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:purple_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blue_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brown_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:green_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:red_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:black_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:oak_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:spruce_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:birch_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jungle_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:acacia_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dark_oak_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:crimson_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:warped_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:skeleton_skull", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wither_skeleton_skull", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:zombie_head", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:player_head", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:creeper_head", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dragon_head", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:barrel", "minecraft:barrel"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:conduit", "minecraft:conduit"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:smoker", "minecraft:smoker"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blast_furnace", "minecraft:blast_furnace"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lectern", "minecraft:lectern"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bell", "minecraft:bell"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jigsaw", "minecraft:jigsaw"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:campfire", "minecraft:campfire"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bee_nest", "minecraft:beehive"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:beehive", "minecraft:beehive"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sculk_sensor", "minecraft:sculk_sensor"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:decorated_pot", "minecraft:decorated_pot"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:crafter", "minecraft:crafter"); -+ -+ // These are missing from Vanilla (TODO check on update) -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:enchanting_table", "minecraft:enchanting_table"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:comparator", "minecraft:comparator"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_gray_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_gray_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:soul_campfire", "minecraft:campfire"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sculk_catalyst", "minecraft:sculk_catalyst"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mangrove_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sculk_shrieker", "minecraft:sculk_shrieker"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chiseled_bookshelf", "minecraft:chiseled_bookshelf"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bamboo_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:oak_hanging_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:spruce_hanging_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:birch_hanging_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jungle_hanging_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:acacia_hanging_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dark_oak_hanging_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mangrove_hanging_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bamboo_hanging_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:crimson_hanging_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:warped_hanging_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:piglin_head", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:suspicious_sand", "minecraft:brushable_block"); // note: this was renamed in the past, see special case in the itemstack walker -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cherry_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cherry_hanging_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:suspicious_gravel", "minecraft:brushable_block"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:calibrated_sculk_sensor", "minecraft:calibrated_sculk_sensor"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:trial_spawner", "minecraft:trial_spawner"); -+ } -+ -+ // This class is responsible for also integrity checking the item id to tile id map here, we just use the item registry to figure it out -+ -+ static { -+ for (final Item item : BuiltInRegistries.ITEM) { -+ if (!(item instanceof BlockItem)) { -+ continue; -+ } -+ -+ if (!(((BlockItem)item).getBlock() instanceof EntityBlock entityBlock)) { -+ continue; -+ } -+ -+ String possibleId; -+ try { -+ final BlockEntity entity = entityBlock.newBlockEntity(new BlockPos(0, 0, 0), ((Block)entityBlock).defaultBlockState()); -+ if (entity != null) { -+ possibleId = BlockEntityType.getKey(entity.getType()).toString(); -+ } else { -+ possibleId = null; -+ } -+ } catch (final Throwable th) { -+ possibleId = null; -+ } -+ -+ final String itemName = BuiltInRegistries.ITEM.getKey(item).toString(); -+ final String mappedTo = ITEM_ID_TO_TILE_ENTITY_ID.get(itemName); -+ if (mappedTo == null) { -+ LOGGER.error("Item id " + itemName + " does not contain tile mapping! (V704)"); -+ } else if (possibleId != null && !mappedTo.equals(possibleId)) { -+ final boolean chestCase = mappedTo.equals("minecraft:chest") && possibleId.equals("minecraft:trapped_chest"); -+ final boolean signCase = mappedTo.equals("minecraft:sign") && possibleId.equals("minecraft:hanging_sign"); -+ // save data is identical for the chest and sign case, so we don't care -+ // it's also important to note that there is no versioning for this map, so it is possible -+ // that mapping them correctly could cause issues converting old data -+ if (!chestCase && !signCase) { -+ LOGGER.error("Item id " + itemName + " is mapped to the wrong tile entity! Mapped to: " + mappedTo + ", expected: " + possibleId); -+ } -+ } -+ } -+ } -+ -+ protected static final Map TILE_ID_UPDATE = new HashMap<>(); -+ static { -+ TILE_ID_UPDATE.put("Airportal", "minecraft:end_portal"); -+ TILE_ID_UPDATE.put("Banner", "minecraft:banner"); -+ TILE_ID_UPDATE.put("Beacon", "minecraft:beacon"); -+ TILE_ID_UPDATE.put("Cauldron", "minecraft:brewing_stand"); -+ TILE_ID_UPDATE.put("Chest", "minecraft:chest"); -+ TILE_ID_UPDATE.put("Comparator", "minecraft:comparator"); -+ TILE_ID_UPDATE.put("Control", "minecraft:command_block"); -+ TILE_ID_UPDATE.put("DLDetector", "minecraft:daylight_detector"); -+ TILE_ID_UPDATE.put("Dropper", "minecraft:dropper"); -+ TILE_ID_UPDATE.put("EnchantTable", "minecraft:enchanting_table"); -+ TILE_ID_UPDATE.put("EndGateway", "minecraft:end_gateway"); -+ TILE_ID_UPDATE.put("EnderChest", "minecraft:ender_chest"); -+ TILE_ID_UPDATE.put("FlowerPot", "minecraft:flower_pot"); -+ TILE_ID_UPDATE.put("Furnace", "minecraft:furnace"); -+ TILE_ID_UPDATE.put("Hopper", "minecraft:hopper"); -+ TILE_ID_UPDATE.put("MobSpawner", "minecraft:mob_spawner"); -+ TILE_ID_UPDATE.put("Music", "minecraft:noteblock"); -+ TILE_ID_UPDATE.put("Piston", "minecraft:piston"); -+ TILE_ID_UPDATE.put("RecordPlayer", "minecraft:jukebox"); -+ TILE_ID_UPDATE.put("Sign", "minecraft:sign"); -+ TILE_ID_UPDATE.put("Skull", "minecraft:skull"); -+ TILE_ID_UPDATE.put("Structure", "minecraft:structure_block"); -+ TILE_ID_UPDATE.put("Trap", "minecraft:dispenser"); -+ } -+ -+ protected static void registerInventory(final String id) { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("Items")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String id = data.getString("id"); -+ if (id == null) { -+ return null; -+ } -+ -+ data.setString("id", TILE_ID_UPDATE.getOrDefault(id, id)); -+ return null; -+ } -+ }); -+ -+ -+ -+ registerInventory( "minecraft:furnace"); -+ registerInventory( "minecraft:chest"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:jukebox", new DataWalkerItems("RecordItem")); -+ registerInventory("minecraft:dispenser"); -+ registerInventory("minecraft:dropper"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:mob_spawner", (final MapType data, final long fromVersion, final long toVersion) -> { -+ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); -+ return null; -+ }); -+ registerInventory("minecraft:brewing_stand"); -+ registerInventory("minecraft:hopper"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:flower_pot", new DataWalkerItemNames("Item")); -+ -+ MCTypeRegistry.ITEM_STACK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, data, "id", fromVersion, toVersion); -+ -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ // only things here are in tag, if changed update if above -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, tag, "Items", fromVersion, toVersion); -+ -+ MapType entityTag = tag.getMap("EntityTag"); -+ if (entityTag != null) { -+ final String itemId = data.getString("id"); -+ final String entityId; -+ if ("minecraft:armor_stand".equals(itemId)) { -+ // The check for version id is changed here. For whatever reason, the legacy -+ // data converters used entity id "minecraft:armor_stand" when version was greater-than 514, -+ // but entity ids were not namespaced until V705! So somebody fucked up the legacy converters. -+ // DFU agrees with my analysis here, it will only set the entityId here to the namespaced variant -+ // with the V705 schema. -+ entityId = DataConverter.getVersion(fromVersion) < 705 ? "ArmorStand" : "minecraft:armor_stand"; -+ } else if (itemId != null && itemId.contains("_spawn_egg")) { -+ // V1451 changes spawn eggs to have the sub entity id be a part of the item id, but of course Mojang never -+ // bothered to write in logic to set the sub entity id, so we have to. -+ // format is ALWAYS :_spawn_egg post flattening -+ entityId = itemId.substring(0, itemId.indexOf("_spawn_egg")); -+ } else if ("minecraft:item_frame".equals(itemId)) { -+ // add missing item_frame entity id -+ // version check is same for armorstand, as both were namespaced at the same time -+ entityId = DataConverter.getVersion(fromVersion) < 705 ? "ItemFrame" : "minecraft:item_frame"; -+ } else if ("minecraft:glow_item_frame".equals(itemId)) { -+ // add missing glow_item_frame entity id -+ entityId = "minecraft:glow_item_frame"; -+ } else { -+ entityId = entityTag.getString("id"); -+ } -+ -+ final boolean removeId; -+ if (entityId == null) { -+ if (!"minecraft:air".equals(itemId)) { -+ LOGGER.warn("Unable to resolve Entity for ItemStack (V704): " + itemId); -+ } -+ removeId = false; -+ } else { -+ removeId = !entityTag.hasKey("id", ObjectType.STRING); -+ if (removeId) { -+ entityTag.setString("id", entityId); -+ } -+ } -+ -+ final MapType replace = MCTypeRegistry.ENTITY.convert(entityTag, fromVersion, toVersion); -+ -+ if (replace != null) { -+ entityTag = replace; -+ tag.setMap("EntityTag", entityTag); -+ } -+ if (removeId) { -+ entityTag.remove("id"); -+ } -+ } -+ -+ MapType blockEntityTag = tag.getMap("BlockEntityTag"); -+ if (blockEntityTag != null) { -+ final String itemId = data.getString("id"); -+ final String entityId; -+ if ("minecraft:suspicious_sand".equals(itemId) && fromVersion < V3438.VERSION) { -+ // renamed after this version, and since the id is a mapping to just string we need to special case this -+ entityId = "minecraft:suspicious_sand"; -+ } else { -+ entityId = ITEM_ID_TO_TILE_ENTITY_ID.get(itemId); -+ } -+ final boolean removeId; -+ if (entityId == null) { -+ if (!"minecraft:air".equals(itemId)) { -+ LOGGER.warn("Unable to resolve BlockEntity for ItemStack (V704): " + itemId); -+ } -+ removeId = false; -+ } else { -+ removeId = !blockEntityTag.hasKey("id", ObjectType.STRING); -+ if (removeId) { -+ blockEntityTag.setString("id", entityId); -+ } -+ } -+ final MapType replace = MCTypeRegistry.TILE_ENTITY.convert(blockEntityTag, fromVersion, toVersion); -+ if (replace != null) { -+ blockEntityTag = replace; -+ tag.setMap("BlockEntityTag", entityTag); -+ } -+ if (removeId) { -+ blockEntityTag.remove("id"); -+ } -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanDestroy", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanPlaceOn", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ // Enforce namespace for ids -+ MCTypeRegistry.TILE_ENTITY.addStructureHook(VERSION, new DataHookEnforceNamespacedID()); -+ } -+ -+ private V704() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e1d7013e49904dacc5e33d9c0b3f3ddb10e3d07a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java -@@ -0,0 +1,231 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID; -+import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced; -+import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity.DataWalkerTileEntities; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.mojang.logging.LogUtils; -+import org.slf4j.Logger; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V705 { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V1_10_2 + 193; -+ -+ protected static final Map ENTITY_ID_UPDATE = new HashMap<>(); -+ static { -+ ENTITY_ID_UPDATE.put("AreaEffectCloud", "minecraft:area_effect_cloud"); -+ ENTITY_ID_UPDATE.put("ArmorStand", "minecraft:armor_stand"); -+ ENTITY_ID_UPDATE.put("Arrow", "minecraft:arrow"); -+ ENTITY_ID_UPDATE.put("Bat", "minecraft:bat"); -+ ENTITY_ID_UPDATE.put("Blaze", "minecraft:blaze"); -+ ENTITY_ID_UPDATE.put("Boat", "minecraft:boat"); -+ ENTITY_ID_UPDATE.put("CaveSpider", "minecraft:cave_spider"); -+ ENTITY_ID_UPDATE.put("Chicken", "minecraft:chicken"); -+ ENTITY_ID_UPDATE.put("Cow", "minecraft:cow"); -+ ENTITY_ID_UPDATE.put("Creeper", "minecraft:creeper"); -+ ENTITY_ID_UPDATE.put("Donkey", "minecraft:donkey"); -+ ENTITY_ID_UPDATE.put("DragonFireball", "minecraft:dragon_fireball"); -+ ENTITY_ID_UPDATE.put("ElderGuardian", "minecraft:elder_guardian"); -+ ENTITY_ID_UPDATE.put("EnderCrystal", "minecraft:ender_crystal"); -+ ENTITY_ID_UPDATE.put("EnderDragon", "minecraft:ender_dragon"); -+ ENTITY_ID_UPDATE.put("Enderman", "minecraft:enderman"); -+ ENTITY_ID_UPDATE.put("Endermite", "minecraft:endermite"); -+ ENTITY_ID_UPDATE.put("EyeOfEnderSignal", "minecraft:eye_of_ender_signal"); -+ ENTITY_ID_UPDATE.put("FallingSand", "minecraft:falling_block"); -+ ENTITY_ID_UPDATE.put("Fireball", "minecraft:fireball"); -+ ENTITY_ID_UPDATE.put("FireworksRocketEntity", "minecraft:fireworks_rocket"); -+ ENTITY_ID_UPDATE.put("Ghast", "minecraft:ghast"); -+ ENTITY_ID_UPDATE.put("Giant", "minecraft:giant"); -+ ENTITY_ID_UPDATE.put("Guardian", "minecraft:guardian"); -+ ENTITY_ID_UPDATE.put("Horse", "minecraft:horse"); -+ ENTITY_ID_UPDATE.put("Husk", "minecraft:husk"); -+ ENTITY_ID_UPDATE.put("Item", "minecraft:item"); -+ ENTITY_ID_UPDATE.put("ItemFrame", "minecraft:item_frame"); -+ ENTITY_ID_UPDATE.put("LavaSlime", "minecraft:magma_cube"); -+ ENTITY_ID_UPDATE.put("LeashKnot", "minecraft:leash_knot"); -+ ENTITY_ID_UPDATE.put("MinecartChest", "minecraft:chest_minecart"); -+ ENTITY_ID_UPDATE.put("MinecartCommandBlock", "minecraft:commandblock_minecart"); -+ ENTITY_ID_UPDATE.put("MinecartFurnace", "minecraft:furnace_minecart"); -+ ENTITY_ID_UPDATE.put("MinecartHopper", "minecraft:hopper_minecart"); -+ ENTITY_ID_UPDATE.put("MinecartRideable", "minecraft:minecart"); -+ ENTITY_ID_UPDATE.put("MinecartSpawner", "minecraft:spawner_minecart"); -+ ENTITY_ID_UPDATE.put("MinecartTNT", "minecraft:tnt_minecart"); -+ ENTITY_ID_UPDATE.put("Mule", "minecraft:mule"); -+ ENTITY_ID_UPDATE.put("MushroomCow", "minecraft:mooshroom"); -+ ENTITY_ID_UPDATE.put("Ozelot", "minecraft:ocelot"); -+ ENTITY_ID_UPDATE.put("Painting", "minecraft:painting"); -+ ENTITY_ID_UPDATE.put("Pig", "minecraft:pig"); -+ ENTITY_ID_UPDATE.put("PigZombie", "minecraft:zombie_pigman"); -+ ENTITY_ID_UPDATE.put("PolarBear", "minecraft:polar_bear"); -+ ENTITY_ID_UPDATE.put("PrimedTnt", "minecraft:tnt"); -+ ENTITY_ID_UPDATE.put("Rabbit", "minecraft:rabbit"); -+ ENTITY_ID_UPDATE.put("Sheep", "minecraft:sheep"); -+ ENTITY_ID_UPDATE.put("Shulker", "minecraft:shulker"); -+ ENTITY_ID_UPDATE.put("ShulkerBullet", "minecraft:shulker_bullet"); -+ ENTITY_ID_UPDATE.put("Silverfish", "minecraft:silverfish"); -+ ENTITY_ID_UPDATE.put("Skeleton", "minecraft:skeleton"); -+ ENTITY_ID_UPDATE.put("SkeletonHorse", "minecraft:skeleton_horse"); -+ ENTITY_ID_UPDATE.put("Slime", "minecraft:slime"); -+ ENTITY_ID_UPDATE.put("SmallFireball", "minecraft:small_fireball"); -+ ENTITY_ID_UPDATE.put("SnowMan", "minecraft:snowman"); -+ ENTITY_ID_UPDATE.put("Snowball", "minecraft:snowball"); -+ ENTITY_ID_UPDATE.put("SpectralArrow", "minecraft:spectral_arrow"); -+ ENTITY_ID_UPDATE.put("Spider", "minecraft:spider"); -+ ENTITY_ID_UPDATE.put("Squid", "minecraft:squid"); -+ ENTITY_ID_UPDATE.put("Stray", "minecraft:stray"); -+ ENTITY_ID_UPDATE.put("ThrownEgg", "minecraft:egg"); -+ ENTITY_ID_UPDATE.put("ThrownEnderpearl", "minecraft:ender_pearl"); -+ ENTITY_ID_UPDATE.put("ThrownExpBottle", "minecraft:xp_bottle"); -+ ENTITY_ID_UPDATE.put("ThrownPotion", "minecraft:potion"); -+ ENTITY_ID_UPDATE.put("Villager", "minecraft:villager"); -+ ENTITY_ID_UPDATE.put("VillagerGolem", "minecraft:villager_golem"); -+ ENTITY_ID_UPDATE.put("Witch", "minecraft:witch"); -+ ENTITY_ID_UPDATE.put("WitherBoss", "minecraft:wither"); -+ ENTITY_ID_UPDATE.put("WitherSkeleton", "minecraft:wither_skeleton"); -+ ENTITY_ID_UPDATE.put("WitherSkull", "minecraft:wither_skull"); -+ ENTITY_ID_UPDATE.put("Wolf", "minecraft:wolf"); -+ ENTITY_ID_UPDATE.put("XPOrb", "minecraft:xp_orb"); -+ ENTITY_ID_UPDATE.put("Zombie", "minecraft:zombie"); -+ ENTITY_ID_UPDATE.put("ZombieHorse", "minecraft:zombie_horse"); -+ ENTITY_ID_UPDATE.put("ZombieVillager", "minecraft:zombie_villager"); -+ } -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ private static void registerThrowableProjectile(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerBlockNames("inTile")); -+ } -+ -+ public static void register() { -+ ConverterAbstractEntityRename.register(VERSION, ENTITY_ID_UPDATE::get); -+ -+ registerMob("minecraft:armor_stand"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:arrow", new DataWalkerBlockNames("inTile")); -+ registerMob("minecraft:bat"); -+ registerMob("minecraft:blaze"); -+ registerMob("minecraft:cave_spider"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:chest_minecart", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:chest_minecart", new DataWalkerItemLists("Items")); -+ registerMob("minecraft:chicken"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:commandblock_minecart", new DataWalkerBlockNames("DisplayTile")); -+ registerMob("minecraft:cow"); -+ registerMob("minecraft:creeper"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:donkey", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:donkey", new DataWalkerItems("SaddleItem")); -+ registerThrowableProjectile("minecraft:egg"); -+ registerMob("minecraft:elder_guardian"); -+ registerMob("minecraft:ender_dragon"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:enderman", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:enderman", new DataWalkerBlockNames("carried")); -+ registerMob("minecraft:endermite"); -+ registerThrowableProjectile("minecraft:ender_pearl"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:falling_block", new DataWalkerBlockNames("Block")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:falling_block", new DataWalkerTileEntities("TileEntityData")); -+ registerThrowableProjectile("minecraft:fireball"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:fireworks_rocket", new DataWalkerItems("FireworksItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:furnace_minecart", new DataWalkerBlockNames("DisplayTile")); -+ registerMob("minecraft:ghast"); -+ registerMob("minecraft:giant"); -+ registerMob("minecraft:guardian"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:hopper_minecart", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:hopper_minecart", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:horse", new DataWalkerItems("ArmorItem", "SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:horse", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ registerMob("minecraft:husk"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:item", new DataWalkerItems("Item")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:item_frame", new DataWalkerItems("Item")); -+ registerMob("minecraft:magma_cube"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:minecart", new DataWalkerBlockNames("DisplayTile")); -+ registerMob("minecraft:mooshroom"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:mule", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:mule", new DataWalkerItems("SaddleItem")); -+ registerMob("minecraft:ocelot"); -+ registerMob("minecraft:pig"); -+ registerMob("minecraft:polar_bear"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:potion", new DataWalkerItems("Potion")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:potion", new DataWalkerBlockNames("inTile")); -+ registerMob("minecraft:rabbit"); -+ registerMob("minecraft:sheep"); -+ registerMob("minecraft:shulker"); -+ registerMob("minecraft:silverfish"); -+ registerMob("minecraft:skeleton"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:skeleton_horse", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:skeleton_horse", new DataWalkerItems("SaddleItem")); -+ registerMob("minecraft:slime"); -+ registerThrowableProjectile("minecraft:small_fireball"); -+ registerThrowableProjectile("minecraft:snowball"); -+ registerMob("minecraft:snowman"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:spawner_minecart", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:spawner_minecart", (final MapType data, final long fromVersion, final long toVersion) -> { -+ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); -+ return null; -+ }); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:spectral_arrow", new DataWalkerBlockNames("inTile")); -+ registerMob("minecraft:spider"); -+ registerMob("minecraft:squid"); -+ registerMob("minecraft:stray"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:tnt_minecart", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:villager", (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); -+ -+ final MapType offers = data.getMap("Offers"); -+ if (offers != null) { -+ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); -+ if (recipes != null) { -+ for (int i = 0, len = recipes.size(); i < len; ++i) { -+ final MapType recipe = recipes.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); -+ } -+ } -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); -+ -+ return null; -+ }); -+ registerMob("minecraft:villager_golem"); -+ registerMob("minecraft:witch"); -+ registerMob("minecraft:wither"); -+ registerMob("minecraft:wither_skeleton"); -+ registerThrowableProjectile("minecraft:wither_skull"); -+ registerMob("minecraft:wolf"); -+ registerThrowableProjectile("minecraft:xp_bottle"); -+ registerMob("minecraft:zombie"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:zombie_horse", new DataWalkerItems("SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:zombie_horse", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ registerMob("minecraft:zombie_pigman"); -+ registerMob("minecraft:zombie_villager"); -+ registerMob("minecraft:evocation_illager"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:llama", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:llama", new DataWalkerItems("SaddleItem", "DecorItem")); -+ registerMob("minecraft:vex"); -+ registerMob("minecraft:vindication_illager"); -+ // Don't need to re-register itemstack walker, the V704 will correctly choose the right id for armorstand based on -+ // the source version -+ -+ // Enforce namespace for ids -+ MCTypeRegistry.ENTITY.addStructureHook(VERSION, new DataHookEnforceNamespacedID()); -+ MCTypeRegistry.ENTITY_NAME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); -+ } -+ -+ private V705() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0070a3d02a87b0f08cd5e74d4f106f3e97f6b4f8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java -@@ -0,0 +1,60 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V804 { -+ -+ protected static final int VERSION = MCVersions.V16W35A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:banner", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final MapType blockEntity = tag.getMap("BlockEntityTag"); -+ if (blockEntity == null) { -+ return null; -+ } -+ -+ if (!blockEntity.hasKey("Base", ObjectType.NUMBER)) { -+ return null; -+ } -+ -+ data.setShort("Damage", (short)(blockEntity.getShort("Base") & 15)); -+ -+ final MapType display = tag.getMap("display"); -+ if (display != null) { -+ final ListType lore = display.getList("Lore", ObjectType.STRING); -+ if (lore != null) { -+ if (lore.size() == 1 && "(+NBT)".equals(lore.getString(0))) { -+ return null; -+ } -+ } -+ } -+ -+ blockEntity.remove("Base"); -+ if (blockEntity.isEmpty()) { -+ tag.remove("BlockEntityTag"); -+ } -+ -+ if (tag.isEmpty()) { -+ data.remove("tag"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V804() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a2717b9d936872ec07141b0f3ae2a6eec81f2dbf ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java -@@ -0,0 +1,40 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V806 { -+ -+ protected static final int VERSION = MCVersions.V16W36A + 1; -+ -+ public static void register() { -+ final DataConverter, MapType> potionWaterUpdater = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ tag = Types.NBT.createEmptyMap(); -+ data.setMap("tag", tag); -+ } -+ -+ if (!tag.hasKey("Potion", ObjectType.STRING)) { -+ tag.setString("Potion", "minecraft:water"); -+ } -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:potion", potionWaterUpdater); -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:splash_potion", potionWaterUpdater); -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:lingering_potion", potionWaterUpdater); -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:tipped_arrow", potionWaterUpdater); -+ } -+ -+ private V806() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b058e5e9b34a9dd134ef93e7a397b5f1e4e11fbd ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java -@@ -0,0 +1,30 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V808 { -+ -+ protected static final int VERSION = MCVersions.V16W38A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION, 1) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!data.hasKey("Color", ObjectType.NUMBER)) { -+ data.setByte("Color", (byte)10); -+ } -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:shulker_box", new DataWalkerItemLists("Items")); -+ } -+ -+ private V808() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b6de7c32acd0adf78812edbbd184117661599c80 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java -@@ -0,0 +1,64 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V813 { -+ -+ protected static final int VERSION = MCVersions.V16W40A; -+ -+ public static final String[] SHULKER_ID_BY_COLOUR = new String[] { -+ "minecraft:white_shulker_box", -+ "minecraft:orange_shulker_box", -+ "minecraft:magenta_shulker_box", -+ "minecraft:light_blue_shulker_box", -+ "minecraft:yellow_shulker_box", -+ "minecraft:lime_shulker_box", -+ "minecraft:pink_shulker_box", -+ "minecraft:gray_shulker_box", -+ "minecraft:silver_shulker_box", -+ "minecraft:cyan_shulker_box", -+ "minecraft:purple_shulker_box", -+ "minecraft:blue_shulker_box", -+ "minecraft:brown_shulker_box", -+ "minecraft:green_shulker_box", -+ "minecraft:red_shulker_box", -+ "minecraft:black_shulker_box" -+ }; -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:shulker_box", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final MapType blockEntity = tag.getMap("BlockEntityTag"); -+ if (blockEntity == null) { -+ return null; -+ } -+ -+ final int color = blockEntity.getInt("Color"); -+ blockEntity.remove("Color"); -+ -+ data.setString("id", SHULKER_ID_BY_COLOUR[color % SHULKER_ID_BY_COLOUR.length]); -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:shulker_box", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.remove("Color"); -+ return null; -+ } -+ }); -+ } -+ -+ private V813() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4b427c128bd75d2dc8b36f0c377454385c029467 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java -@@ -0,0 +1,28 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.Locale; -+ -+public final class V816 { -+ -+ protected static final int VERSION = MCVersions.V16W43A; -+ -+ public static void register() { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String lang = data.getString("lang"); -+ if (lang != null) { -+ data.setString("lang", lang.toLowerCase(Locale.ROOT)); -+ } -+ return null; -+ } -+ }); -+ } -+ -+ private V816() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9e2ef3cea4fd382a75a4d787fe2e2ff509eb49fc ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java -@@ -0,0 +1,19 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V820 { -+ -+ protected static final int VERSION = MCVersions.V1_11 + 1; -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:totem", "minecraft:totem_of_undying" -+ )::get); -+ } -+ -+ private V820() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f81ef4f80d5f627624d535ae25d44edd523fa4bf ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java -@@ -0,0 +1,357 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID; -+import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced; -+import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.item_name.DataWalkerItemNames; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity.DataWalkerTileEntities; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.mojang.logging.LogUtils; -+import org.slf4j.Logger; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V99 { -+ -+ // Structure for all data before data upgrading was added to minecraft (pre 15w32a) -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V15W32A - 1; -+ -+ protected static final Map ITEM_ID_TO_TILE_ENTITY_ID = new HashMap<>(); -+ -+ static { -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:furnace", "Furnace"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lit_furnace", "Furnace"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chest", "Chest"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:trapped_chest", "Chest"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:ender_chest", "EnderChest"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jukebox", "RecordPlayer"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dispenser", "Trap"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dropper", "Dropper"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sign", "Sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mob_spawner", "MobSpawner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:noteblock", "Music"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brewing_stand", "Cauldron"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:enhanting_table", "EnchantTable"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:command_block", "CommandBlock"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:beacon", "Beacon"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:skull", "Skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector", "DLDetector"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:hopper", "Hopper"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:banner", "Banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:flower_pot", "FlowerPot"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:repeating_command_block", "CommandBlock"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chain_command_block", "CommandBlock"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_sign", "Sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_sign", "Sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:piston_head", "Piston"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector_inverted", "DLDetector"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:unpowered_comparator", "Comparator"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:powered_comparator", "Comparator"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_banner", "Banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_banner", "Banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:structure_block", "Structure"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_portal", "Airportal"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_gateway", "EndGateway"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:shield", "Banner"); -+ } -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("Equipment")); -+ } -+ -+ private static void registerProjectile(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerBlockNames("inTile")); -+ } -+ -+ private static void registerInventory(final String id) { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("Items")); -+ } -+ -+ public static void register() { -+ // entities -+ MCTypeRegistry.ENTITY.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "Riding", fromVersion, toVersion); -+ -+ return null; -+ }); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Item", new DataWalkerItems("Item")); -+ registerProjectile("ThrownEgg"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Arrow", new DataWalkerBlockNames("inTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "TippedArrow", new DataWalkerBlockNames("inTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "SpectralArrow", new DataWalkerBlockNames("inTile")); -+ registerProjectile("Snowball"); -+ registerProjectile("Fireball"); -+ registerProjectile("SmallFireball"); -+ registerProjectile("ThrownEnderpearl"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "ThrownPotion", new DataWalkerBlockNames("inTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "ThrownPotion", new DataWalkerItems("Potion")); -+ registerProjectile("ThrownExpBottle"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "ItemFrame", new DataWalkerItems("Item")); -+ registerProjectile("WitherSkull"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "FallingSand", new DataWalkerBlockNames("Block")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "FallingSand", new DataWalkerTileEntities("TileEntityData")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "FireworksRocketEntity", new DataWalkerItems("FireworksItem")); -+ // Note: Minecart is the generic entity. It can be subtyped via an int to become one of the specific minecarts -+ // (i.e rideable, chest, furnace, tnt, etc) -+ // Because of this, we add all walkers to the generic type, even though they might not be needed. -+ // Vanilla does not make the generic minecart convert spawners, but we do. -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Minecart", new DataWalkerBlockNames("DisplayTile")); // for all minecart types -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Minecart", new DataWalkerItemLists("Items")); // for chest types -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Minecart", (final MapType data, final long fromVersion, final long toVersion) -> { -+ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); -+ return null; -+ }); // for spawner type -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartRideable", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartChest", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartChest", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartFurnace", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartTNT", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartSpawner", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartSpawner", (final MapType data, final long fromVersion, final long toVersion) -> { -+ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); -+ return null; -+ }); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartHopper", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartHopper", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartCommandBlock", new DataWalkerBlockNames("DisplayTile")); -+ registerMob("ArmorStand"); -+ registerMob("Creeper"); -+ registerMob("Skeleton"); -+ registerMob("Spider"); -+ registerMob("Giant"); -+ registerMob("Zombie"); -+ registerMob("Slime"); -+ registerMob("Ghast"); -+ registerMob("PigZombie"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Enderman", new DataWalkerBlockNames("carried")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Enderman", new DataWalkerItemLists("Equipment")); -+ registerMob("CaveSpider"); -+ registerMob("Silverfish"); -+ registerMob("Blaze"); -+ registerMob("LavaSlime"); -+ registerMob("EnderDragon"); -+ registerMob("WitherBoss"); -+ registerMob("Bat"); -+ registerMob("Witch"); -+ registerMob("Endermite"); -+ registerMob("Guardian"); -+ registerMob("Pig"); -+ registerMob("Sheep"); -+ registerMob("Cow"); -+ registerMob("Chicken"); -+ registerMob("Squid"); -+ registerMob("Wolf"); -+ registerMob("MushroomCow"); -+ registerMob("SnowMan"); -+ registerMob("Ozelot"); -+ registerMob("VillagerGolem"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItemLists("Items", "Equipment")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItems("ArmorItem", "SaddleItem")); -+ registerMob("Rabbit"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", new DataWalkerItemLists("Inventory", "Equipment")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType offers = data.getMap("Offers"); -+ if (offers != null) { -+ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); -+ if (recipes != null) { -+ for (int i = 0; i < recipes.size(); ++i) { -+ final MapType recipe = recipes.getMap(i); -+ -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); -+ } -+ } -+ } -+ -+ return null; -+ }); -+ registerMob("Shulker"); -+ -+ // tile entities -+ -+ // Inventory -> new DataWalkerItemLists("Items") -+ registerInventory("Furnace"); -+ registerInventory("Chest"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "RecordPlayer", new DataWalkerItems("RecordItem")); -+ registerInventory("Trap"); -+ registerInventory("Dropper"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "MobSpawner", (final MapType data, final long fromVersion, final long toVersion) -> { -+ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); -+ return null; -+ }); -+ registerInventory("Cauldron"); -+ registerInventory("Hopper"); -+ // Note: Vanilla does not properly handle this case, it will not convert int ids! -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "FlowerPot", new DataWalkerItemNames("Item")); -+ -+ // rest -+ -+ MCTypeRegistry.ITEM_STACK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, data, "id", fromVersion, toVersion); -+ -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ // only things here are in tag, if changed update if above -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, tag, "Items", fromVersion, toVersion); -+ -+ MapType entityTag = tag.getMap("EntityTag"); -+ if (entityTag != null) { -+ String itemId = getStringId(data.getString("id")); -+ final String entityId; -+ if ("minecraft:armor_stand".equals(itemId)) { -+ // The check for version id is removed here. For whatever reason, the legacy -+ // data converters used entity id "minecraft:armor_stand" when version was greater-than 514, -+ // but entity ids were not namespaced until V705! So somebody fucked up the legacy converters. -+ // DFU agrees with my analysis here, it will only set the entityId here to the namespaced variant -+ // with the V705 schema. -+ entityId = "ArmorStand"; -+ } else if ("minecraft:item_frame".equals(itemId)) { -+ // add missing item_frame entity id -+ entityId = "ItemFrame"; -+ } else { -+ entityId = entityTag.getString("id"); -+ } -+ -+ final boolean removeId; -+ if (entityId == null) { -+ if (!"minecraft:air".equals(itemId)) { -+ LOGGER.warn("Unable to resolve Entity for ItemStack (V99): " + data.getGeneric("id")); -+ } -+ removeId = false; -+ } else { -+ removeId = !entityTag.hasKey("id", ObjectType.STRING); -+ if (removeId) { -+ entityTag.setString("id", entityId); -+ } -+ } -+ -+ final MapType replace = MCTypeRegistry.ENTITY.convert(entityTag, fromVersion, toVersion); -+ -+ if (replace != null) { -+ entityTag = replace; -+ tag.setMap("EntityTag", entityTag); -+ } -+ if (removeId) { -+ entityTag.remove("id"); -+ } -+ } -+ -+ MapType blockEntityTag = tag.getMap("BlockEntityTag"); -+ if (blockEntityTag != null) { -+ final String itemId = getStringId(data.getString("id")); -+ final String entityId = ITEM_ID_TO_TILE_ENTITY_ID.get(itemId); -+ final boolean removeId; -+ if (entityId == null) { -+ if (!"minecraft:air".equals(itemId)) { -+ LOGGER.warn("Unable to resolve BlockEntity for ItemStack (V99): " + data.getGeneric("id")); -+ } -+ removeId = false; -+ } else { -+ removeId = !blockEntityTag.hasKey("id", ObjectType.STRING); -+ blockEntityTag.setString("id", entityId); -+ } -+ final MapType replace = MCTypeRegistry.TILE_ENTITY.convert(blockEntityTag, fromVersion, toVersion); -+ if (replace != null) { -+ blockEntityTag = replace; -+ tag.setMap("BlockEntityTag", blockEntityTag); -+ } -+ if (removeId) { -+ blockEntityTag.remove("id"); -+ } -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanDestroy", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanPlaceOn", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, new DataWalkerItemLists("Inventory", "EnderItems")); -+ -+ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); -+ -+ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); -+ if (tileTicks != null) { -+ for (int i = 0, len = tileTicks.size(); i < len; ++i) { -+ final MapType tileTick = tileTicks.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); -+ } -+ } -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.ENTITY_CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "Entities", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.SAVED_DATA_SCOREBOARD.addStructureWalker(VERSION, (final MapType root, final long fromVersion, final long toVersion) -> { -+ final MapType data = root.getMap("data"); -+ if (data == null) { -+ return null; -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.OBJECTIVE, data, "Objectives", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TEAM, data, "Teams", fromVersion, toVersion); -+ -+ return null; -+ }); -+ MCTypeRegistry.SAVED_DATA_STRUCTURE_FEATURE_INDICES.addStructureWalker(VERSION, (final MapType root, final long fromVersion, final long toVersion) -> { -+ final MapType data = root.getMap("data"); -+ if (data == null) { -+ return null; -+ } -+ -+ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, data, "Features", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ -+ // Enforce namespacing for ids -+ MCTypeRegistry.BLOCK_NAME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); -+ MCTypeRegistry.ITEM_NAME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); -+ MCTypeRegistry.ITEM_STACK.addStructureHook(VERSION, new DataHookEnforceNamespacedID()); -+ -+ // Entity is absent; the String form is not yet namespaced, unlike the above. -+ } -+ -+ protected static String getStringId(final Object id) { -+ if (id instanceof String) { -+ return (String)id; -+ } else if (id instanceof Number) { -+ return HelperItemNameV102.getNameFromId(((Number)id).intValue()); -+ } else { -+ return null; -+ } -+ } -+ -+ private V99() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java -new file mode 100644 -index 0000000000000000000000000000000000000000..930e014858ef635ebe25f7f92dc81ba0eaac50a8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java -@@ -0,0 +1,11 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.block_name; -+ -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+ -+public final class DataWalkerBlockNames extends DataWalkerTypePaths { -+ -+ public DataWalkerBlockNames(final String... paths) { -+ super(MCTypeRegistry.BLOCK_NAME, paths); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e7655645f5d32026a609a8c7517827653c5c5e8b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java -@@ -0,0 +1,26 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.game_event; -+ -+import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class GameEventListenerWalker implements DataWalker { -+ -+ @Override -+ public MapType walk(final MapType data, final long fromVersion, final long toVersion) { -+ final MapType listener = data.getMap("listener"); -+ if (listener == null) { -+ return null; -+ } -+ -+ final MapType event = listener.getMap("event"); -+ if (event == null) { -+ return null; -+ } -+ -+ WalkerUtils.convert(MCTypeRegistry.GAME_EVENT_NAME, event, "game_event", fromVersion, toVersion); -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7c8f6a5034b48e1ec2c5925211f491115ca735aa ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java -@@ -0,0 +1,38 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.generic; -+ -+import ca.spottedleaf.dataconverter.converters.datatypes.DataType; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public class DataWalkerListPaths implements DataWalker { -+ -+ protected final DataType type; -+ protected final String[] paths; -+ -+ public DataWalkerListPaths(final DataType type, final String... paths) { -+ this.type = type; -+ this.paths = paths; -+ } -+ -+ @Override -+ public final MapType walk(final MapType data, final long fromVersion, final long toVersion) { -+ final DataType type = this.type; -+ for (final String path : this.paths) { -+ final ListType list = data.getListUnchecked(path); -+ if (list == null) { -+ continue; -+ } -+ -+ for (int i = 0, len = list.size(); i < len; ++i) { -+ final Object current = list.getGeneric(i); -+ final Object converted = type.convert((T)current, fromVersion, toVersion); -+ if (converted != null) { -+ list.setGeneric(i, converted); -+ } -+ } -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e66b4e0f7cdb032b545ace7ba852ad7979f3c96a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java -@@ -0,0 +1,34 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.generic; -+ -+import ca.spottedleaf.dataconverter.converters.datatypes.DataType; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public class DataWalkerTypePaths implements DataWalker { -+ -+ protected final DataType type; -+ protected final String[] paths; -+ -+ public DataWalkerTypePaths(final DataType type, final String... paths) { -+ this.type = type; -+ this.paths = paths; -+ } -+ -+ @Override -+ public final MapType walk(final MapType data, final long fromVersion, final long toVersion) { -+ for (final String path : this.paths) { -+ final Object current = data.getGeneric(path); -+ if (current == null) { -+ continue; -+ } -+ -+ final Object converted = this.type.convert((T)current, fromVersion, toVersion); -+ -+ if (converted != null) { -+ data.setGeneric(path, converted); -+ } -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1e81a1e46a9c0ffceb564a7b1fc4d1b51009f3f7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java -@@ -0,0 +1,127 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.generic; -+ -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCDataType; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCValueType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.ArrayList; -+ -+public final class WalkerUtils { -+ -+ public static void convert(final MCDataType type, final MapType data, final String path, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ final MapType map = data.getMap(path); -+ if (map != null) { -+ final MapType replace = type.convert(map, fromVersion, toVersion); -+ if (replace != null) { -+ data.setMap(path, replace); -+ } -+ } -+ } -+ -+ public static void convertList(final MCDataType type, final MapType data, final String path, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ final ListType list = data.getList(path, ObjectType.MAP); -+ if (list != null) { -+ for (int i = 0, len = list.size(); i < len; ++i) { -+ final MapType replace = type.convert(list.getMap(i), fromVersion, toVersion); -+ if (replace != null) { -+ list.setMap(i, replace); -+ } -+ } -+ } -+ } -+ -+ public static void convert(final MCValueType type, final MapType data, final String path, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ final Object value = data.getGeneric(path); -+ if (value != null) { -+ final Object converted = type.convert(value, fromVersion, toVersion); -+ if (converted != null) { -+ data.setGeneric(path, converted); -+ } -+ } -+ } -+ -+ public static void convert(final MCValueType type, final ListType data, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ for (int i = 0, len = data.size(); i < len; ++i) { -+ final Object value = data.getGeneric(i); -+ final Object converted = type.convert(value, fromVersion, toVersion); -+ if (converted != null) { -+ data.setGeneric(i, converted); -+ } -+ } -+ } -+ -+ public static void convertList(final MCValueType type, final MapType data, final String path, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ final ListType list = data.getListUnchecked(path); -+ if (list != null) { -+ convert(type, list, fromVersion, toVersion); -+ } -+ } -+ -+ public static void convertKeys(final MCValueType type, final MapType data, final String path, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ final MapType map = data.getMap(path); -+ if (map != null) { -+ convertKeys(type, map, fromVersion, toVersion); -+ } -+ } -+ -+ public static void convertKeys(final MCValueType type, final MapType data, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ RenameHelper.renameKeys(data, (final String input) -> { -+ return (String)type.convert(input, fromVersion, toVersion); -+ }); -+ } -+ -+ public static void convertValues(final MCDataType type, final MapType data, final String path, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ convertValues(type, data.getMap(path), fromVersion, toVersion); -+ } -+ -+ public static void convertValues(final MCDataType type, final MapType data, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ for (final String key : data.keys()) { -+ final MapType value = data.getMap(key); -+ if (value != null) { -+ final MapType replace = type.convert(value, fromVersion, toVersion); -+ if (replace != null) { -+ // no CME, key is in map already -+ data.setMap(key, replace); -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java -new file mode 100644 -index 0000000000000000000000000000000000000000..14e291efd864d97dcf83db01c09b9daaae1949bd ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java -@@ -0,0 +1,11 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.item_name; -+ -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+ -+public final class DataWalkerItemNames extends DataWalkerTypePaths { -+ -+ public DataWalkerItemNames(final String... paths) { -+ super(MCTypeRegistry.ITEM_NAME, paths); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5b4402c3cc4e68e9c591e8bbb4a2542d8e2214d4 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java -@@ -0,0 +1,12 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.itemstack; -+ -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerListPaths; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public class DataWalkerItemLists extends DataWalkerListPaths, MapType> { -+ -+ public DataWalkerItemLists(final String... paths) { -+ super(MCTypeRegistry.ITEM_STACK, paths); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java -new file mode 100644 -index 0000000000000000000000000000000000000000..04770e8378ac8784895cdfe400a47b0b601c2187 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java -@@ -0,0 +1,12 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.itemstack; -+ -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public class DataWalkerItems extends DataWalkerTypePaths, MapType> { -+ -+ public DataWalkerItems(final String... paths) { -+ super(MCTypeRegistry.ITEM_STACK, paths); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d9cc21bf41cb4b377752b684f8e59818cd620103 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java -@@ -0,0 +1,12 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity; -+ -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class DataWalkerTileEntities extends DataWalkerTypePaths, MapType> { -+ -+ public DataWalkerTileEntities(final String... paths) { -+ super(MCTypeRegistry.TILE_ENTITY, paths); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..19f7e95f754e8385bbe60fd2fb7fc95b6a4ebd7c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java -@@ -0,0 +1,272 @@ -+package ca.spottedleaf.dataconverter.types; -+ -+public interface ListType { -+ -+ public TypeUtil getTypeUtil(); -+ -+ @Override -+ public int hashCode(); -+ -+ @Override -+ public boolean equals(final Object other); -+ -+ // Provides a deep copy of this list -+ public ListType copy(); -+ -+ // returns NONE if no type has been assigned. if NONE, then this list is also empty. It is not true on the other hand that an empty list has no type. -+ public ObjectType getType(); -+ -+ public int size(); -+ -+ public void remove(final int index); -+ -+ public default Object getGeneric(final int index) { -+ switch (this.getType()) { -+ case NONE: -+ throw new IllegalStateException("List is empty and has no type"); -+ case BYTE: -+ return Byte.valueOf(this.getByte(index)); -+ case SHORT: -+ return Short.valueOf(this.getShort(index)); -+ case INT: -+ return Integer.valueOf(this.getInt(index)); -+ case LONG: -+ return Long.valueOf(this.getLong(index)); -+ case FLOAT: -+ return Float.valueOf(this.getFloat(index)); -+ case DOUBLE: -+ return Double.valueOf(this.getDouble(index)); -+ case NUMBER: -+ return this.getNumber(index); -+ case BYTE_ARRAY: -+ return this.getBytes(index); -+ case SHORT_ARRAY: -+ return this.getShorts(index); -+ case INT_ARRAY: -+ return this.getInts(index); -+ case LONG_ARRAY: -+ return this.getLongs(index); -+ case LIST: -+ return this.getList(index); -+ case MAP: -+ return this.getMap(index); -+ case STRING: -+ return this.getString(index); -+ default: -+ throw new UnsupportedOperationException(this.getType().name()); -+ } -+ } -+ -+ public default void setGeneric(final int index, final Object to) { -+ if (to instanceof Number) { -+ if (to instanceof Byte) { -+ this.setByte(index, ((Byte)to).byteValue()); -+ return; -+ } else if (to instanceof Short) { -+ this.setShort(index, ((Short)to).shortValue()); -+ return; -+ } else if (to instanceof Integer) { -+ this.setInt(index, ((Integer)to).intValue()); -+ return; -+ } else if (to instanceof Long) { -+ this.setLong(index, ((Long)to).longValue()); -+ return; -+ } else if (to instanceof Float) { -+ this.setFloat(index, ((Float)to).floatValue()); -+ return; -+ } else if (to instanceof Double) { -+ this.setDouble(index, ((Double)to).doubleValue()); -+ return; -+ } // else fall through to throw -+ } else if (to instanceof MapType) { -+ this.setMap(index, (MapType)to); -+ return; -+ } else if (to instanceof ListType) { -+ this.setList(index, (ListType)to); -+ return; -+ } else if (to instanceof String) { -+ this.setString(index, (String)to); -+ return; -+ } else if (to.getClass().isArray()) { -+ if (to instanceof byte[]) { -+ this.setBytes(index, (byte[])to); -+ return; -+ } else if (to instanceof short[]) { -+ this.setShorts(index, (short[])to); -+ return; -+ } else if (to instanceof int[]) { -+ this.setInts(index, (int[])to); -+ return; -+ } else if (to instanceof long[]) { -+ this.setLongs(index, (long[])to); -+ return; -+ } // else fall through to throw -+ } -+ -+ throw new IllegalArgumentException("Object " + to + " is not a valid type!"); -+ } -+ -+ // types here are strict. if the type on get does not match the underlying type, will throw. -+ -+ public Number getNumber(final int index); -+ -+ // if the value at index is a Number but not a byte, then returns the number casted to byte. If the value at the index is not a number, then throws -+ public byte getByte(final int index); -+ -+ public void setByte(final int index, final byte to); -+ -+ // if the value at index is a Number but not a short, then returns the number casted to short. If the value at the index is not a number, then throws -+ public short getShort(final int index); -+ -+ public void setShort(final int index, final short to); -+ -+ // if the value at index is a Number but not a int, then returns the number casted to int. If the value at the index is not a number, then throws -+ public int getInt(final int index); -+ -+ public void setInt(final int index, final int to); -+ -+ // if the value at index is a Number but not a long, then returns the number casted to long. If the value at the index is not a number, then throws -+ public long getLong(final int index); -+ -+ public void setLong(final int index, final long to); -+ -+ // if the value at index is a Number but not a float, then returns the number casted to float. If the value at the index is not a number, then throws -+ public float getFloat(final int index); -+ -+ public void setFloat(final int index, final float to); -+ -+ // if the value at index is a Number but not a double, then returns the number casted to double. If the value at the index is not a number, then throws -+ public double getDouble(final int index); -+ -+ public void setDouble(final int index, final double to); -+ -+ public byte[] getBytes(final int index); -+ -+ public void setBytes(final int index, final byte[] to); -+ -+ public short[] getShorts(final int index); -+ -+ public void setShorts(final int index, final short[] to); -+ -+ public int[] getInts(final int index); -+ -+ public void setInts(final int index, final int[] to); -+ -+ public long[] getLongs(final int index); -+ -+ public void setLongs(final int index, final long[] to); -+ -+ public ListType getList(final int index); -+ -+ public void setList(final int index, final ListType list); -+ -+ public MapType getMap(final int index); -+ -+ public void setMap(final int index, final MapType to); -+ -+ public String getString(final int index); -+ -+ public void setString(final int index, final String to); -+ -+ public default void addGeneric(final Object to) { -+ if (to instanceof Number) { -+ if (to instanceof Byte) { -+ this.addByte(((Byte)to).byteValue()); -+ return; -+ } else if (to instanceof Short) { -+ this.addShort(((Short)to).shortValue()); -+ return; -+ } else if (to instanceof Integer) { -+ this.addInt(((Integer)to).intValue()); -+ return; -+ } else if (to instanceof Long) { -+ this.addLong(((Long)to).longValue()); -+ return; -+ } else if (to instanceof Float) { -+ this.addFloat(((Float)to).floatValue()); -+ return; -+ } else if (to instanceof Double) { -+ this.addDouble(((Double)to).doubleValue()); -+ return; -+ } // else fall through to throw -+ } else if (to instanceof MapType) { -+ this.addMap((MapType)to); -+ return; -+ } else if (to instanceof ListType) { -+ this.addList((ListType)to); -+ return; -+ } else if (to instanceof String) { -+ this.addString((String)to); -+ return; -+ } else if (to.getClass().isArray()) { -+ if (to instanceof byte[]) { -+ this.addByteArray((byte[])to); -+ return; -+ } else if (to instanceof short[]) { -+ this.addShortArray((short[])to); -+ return; -+ } else if (to instanceof int[]) { -+ this.addIntArray((int[])to); -+ return; -+ } else if (to instanceof long[]) { -+ this.addLongArray((long[])to); -+ return; -+ } // else fall through to throw -+ } -+ -+ throw new IllegalArgumentException("Object " + to + " is not a valid type!"); -+ } -+ -+ public void addByte(final byte b); -+ -+ public void addByte(final int index, final byte b); -+ -+ public void addShort(final short s); -+ -+ public void addShort(final int index, final short s); -+ -+ public void addInt(final int i); -+ -+ public void addInt(final int index, final int i); -+ -+ public void addLong(final long l); -+ -+ public void addLong(final int index, final long l); -+ -+ public void addFloat(final float f); -+ -+ public void addFloat(final int index, final float f); -+ -+ public void addDouble(final double d); -+ -+ public void addDouble(final int index, final double d); -+ -+ public void addByteArray(final byte[] arr); -+ -+ public void addByteArray(final int index, final byte[] arr); -+ -+ public void addShortArray(final short[] arr); -+ -+ public void addShortArray(final int index, final short[] arr); -+ -+ public void addIntArray(final int[] arr); -+ -+ public void addIntArray(final int index, final int[] arr); -+ -+ public void addLongArray(final long[] arr); -+ -+ public void addLongArray(final int index, final long[] arr); -+ -+ public void addList(final ListType list); -+ -+ public void addList(final int index, final ListType list); -+ -+ public void addMap(final MapType map); -+ -+ public void addMap(final int index, final MapType map); -+ -+ public void addString(final String string); -+ -+ public void addString(final int index, final String string); -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1bd7809b7ee198d1ceeb2756b44105e1b0de956e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java -@@ -0,0 +1,219 @@ -+package ca.spottedleaf.dataconverter.types; -+ -+import java.util.Set; -+ -+public interface MapType { -+ -+ public TypeUtil getTypeUtil(); -+ -+ @Override -+ public int hashCode(); -+ -+ @Override -+ public boolean equals(final Object other); -+ -+ public int size(); -+ -+ public boolean isEmpty(); -+ -+ public void clear(); -+ -+ public Set keys(); -+ -+ // Provides a deep copy of this map -+ public MapType copy(); -+ -+ public boolean hasKey(final K key); -+ -+ public boolean hasKey(final K key, final ObjectType type); -+ -+ public void remove(final K key); -+ -+ public Object getGeneric(final K key); -+ -+ // types here are not strict. if the key maps to a different type, default is always returned -+ // if default is not a parameter, then default is always null -+ -+ public Number getNumber(final K key); -+ -+ public Number getNumber(final K key, final Number dfl); -+ -+ public boolean getBoolean(final K key); -+ -+ public boolean getBoolean(final K key, final boolean dfl); -+ -+ public void setBoolean(final K key, final boolean val); -+ -+ // if the mapped value is a Number but not a byte, then the number is casted to byte. If the mapped value does not exist or is not a number, returns 0 -+ public byte getByte(final K key); -+ -+ // if the mapped value is a Number but not a byte, then the number is casted to byte. If the mapped value does not exist or is not a number, returns dfl -+ public byte getByte(final K key, final byte dfl); -+ -+ public void setByte(final K key, final byte val); -+ -+ // if the mapped value is a Number but not a short, then the number is casted to short. If the mapped value does not exist or is not a number, returns 0 -+ public short getShort(final K key); -+ -+ // if the mapped value is a Number but not a short, then the number is casted to short. If the mapped value does not exist or is not a number, returns dfl -+ public short getShort(final K key, final short dfl); -+ -+ public void setShort(final K key, final short val); -+ -+ // if the mapped value is a Number but not a int, then the number is casted to int. If the mapped value does not exist or is not a number, returns 0 -+ public int getInt(final K key); -+ -+ // if the mapped value is a Number but not a int, then the number is casted to int. If the mapped value does not exist or is not a number, returns dfl -+ public int getInt(final K key, final int dfl); -+ -+ public void setInt(final K key, final int val); -+ -+ // if the mapped value is a Number but not a long, then the number is casted to long. If the mapped value does not exist or is not a number, returns 0 -+ public long getLong(final K key); -+ -+ // if the mapped value is a Number but not a long, then the number is casted to long. If the mapped value does not exist or is not a number, returns dfl -+ public long getLong(final K key, final long dfl); -+ -+ public void setLong(final K key, final long val); -+ -+ // if the mapped value is a Number but not a float, then the number is casted to float. If the mapped value does not exist or is not a number, returns 0 -+ public float getFloat(final K key); -+ -+ // if the mapped value is a Number but not a float, then the number is casted to float. If the mapped value does not exist or is not a number, returns dfl -+ public float getFloat(final K key, final float dfl); -+ -+ public void setFloat(final K key, final float val); -+ -+ // if the mapped value is a Number but not a double, then the number is casted to double. If the mapped value does not exist or is not a number, returns 0 -+ public double getDouble(final K key); -+ -+ // if the mapped value is a Number but not a double, then the number is casted to double. If the mapped value does not exist or is not a number, returns dfl -+ public double getDouble(final K key, final double dfl); -+ -+ public void setDouble(final K key, final double val); -+ -+ public byte[] getBytes(final K key); -+ -+ public byte[] getBytes(final K key, final byte[] dfl); -+ -+ public void setBytes(final K key, final byte[] val); -+ -+ public short[] getShorts(final K key); -+ -+ public short[] getShorts(final K key, final short[] dfl); -+ -+ public void setShorts(final K key, final short[] val); -+ -+ public int[] getInts(final K key); -+ -+ public int[] getInts(final K key, final int[] dfl); -+ -+ public void setInts(final K key, final int[] val); -+ -+ public long[] getLongs(final K key); -+ -+ public long[] getLongs(final K key, final long[] dfl); -+ -+ public void setLongs(final K key, final long[] val); -+ -+ public ListType getListUnchecked(final K key); -+ -+ public ListType getListUnchecked(final K key, final ListType dfl); -+ -+ public default ListType getList(final K key, final ObjectType type) { -+ return this.getList(key, type, null); -+ } -+ -+ public default ListType getOrCreateList(final K key, final ObjectType type) { -+ ListType ret = this.getList(key, type); -+ if (ret == null) { -+ this.setList(key, ret = this.getTypeUtil().createEmptyList()); -+ } -+ -+ return ret; -+ } -+ -+ public default ListType getList(final K key, final ObjectType type, final ListType dfl) { -+ final ListType ret = this.getListUnchecked(key, null); -+ final ObjectType retType; -+ if (ret != null && ((retType = ret.getType()) == type || retType == ObjectType.UNDEFINED)) { -+ return ret; -+ } else { -+ return dfl; -+ } -+ } -+ -+ public void setList(final K key, final ListType val); -+ -+ public MapType getMap(final K key); -+ -+ public default MapType getOrCreateMap(final K key) { -+ MapType ret = this.getMap(key); -+ if (ret == null) { -+ this.setMap(key, ret = this.getTypeUtil().createEmptyMap()); -+ } -+ -+ return ret; -+ } -+ -+ public MapType getMap(final K key, final MapType dfl); -+ -+ public void setMap(final K key, final MapType val); -+ -+ public String getString(final K key); -+ -+ public String getString(final K key, final String dfl); -+ -+ public void setString(final K key, final String val); -+ -+ public default void setGeneric(final K key, final Object value) { -+ if (value instanceof Boolean) { -+ this.setBoolean(key, ((Boolean)value).booleanValue()); -+ } else if (value instanceof Number) { -+ if (value instanceof Byte) { -+ this.setByte(key, ((Byte)value).byteValue()); -+ return; -+ } else if (value instanceof Short) { -+ this.setShort(key, ((Short)value).shortValue()); -+ return; -+ } else if (value instanceof Integer) { -+ this.setInt(key, ((Integer)value).intValue()); -+ return; -+ } else if (value instanceof Long) { -+ this.setLong(key, ((Long)value).longValue()); -+ return; -+ } else if (value instanceof Float) { -+ this.setFloat(key, ((Float)value).floatValue()); -+ return; -+ } else if (value instanceof Double) { -+ this.setDouble(key, ((Double)value).doubleValue()); -+ return; -+ } // else fall through to throw -+ } else if (value instanceof MapType) { -+ this.setMap(key, (MapType)value); -+ return; -+ } else if (value instanceof ListType) { -+ this.setList(key, (ListType)value); -+ return; -+ } else if (value instanceof String) { -+ this.setString(key, (String)value); -+ return; -+ } else if (value.getClass().isArray()) { -+ if (value instanceof byte[]) { -+ this.setBytes(key, (byte[])value); -+ return; -+ } else if (value instanceof short[]) { -+ this.setShorts(key, (short[])value); -+ return; -+ } else if (value instanceof int[]) { -+ this.setInts(key, (int[])value); -+ return; -+ } else if (value instanceof long[]) { -+ this.setLongs(key, (long[])value); -+ return; -+ } // else fall through to throw -+ } -+ -+ throw new IllegalArgumentException("Object " + value + " is not a valid type!"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java b/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1aab91233ddb98c3af5d424bac120891f1ee16c7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java -@@ -0,0 +1,72 @@ -+package ca.spottedleaf.dataconverter.types; -+ -+public enum ObjectType { -+ NONE(null), -+ BYTE(Byte.class), -+ SHORT(Short.class), -+ INT(Integer.class), -+ LONG(Long.class), -+ FLOAT(Float.class), -+ DOUBLE(Double.class), -+ NUMBER(Number.class), -+ BYTE_ARRAY(byte[].class), -+ SHORT_ARRAY(short[].class), -+ INT_ARRAY(int[].class), -+ LONG_ARRAY(long[].class), -+ LIST(ListType.class), -+ MAP(MapType.class), -+ STRING(String.class), -+ UNDEFINED(null); -+ -+ private final Class clazz; -+ private final boolean isNumber; -+ -+ private ObjectType(final Class clazz) { -+ this.clazz = clazz; -+ this.isNumber = clazz != null && Number.class.isAssignableFrom(clazz); -+ } -+ -+ public boolean isNumber() { -+ return this.isNumber; -+ } -+ -+ public Class getObjectClass() { -+ return this.clazz; -+ } -+ -+ public static ObjectType getType(final Object object) { -+ if (object instanceof Number) { -+ if (object instanceof Byte) { -+ return BYTE; -+ } else if (object instanceof Short) { -+ return SHORT; -+ } else if (object instanceof Integer) { -+ return INT; -+ } else if (object instanceof Long) { -+ return LONG; -+ } else if (object instanceof Float) { -+ return FLOAT; -+ } else if (object instanceof Double) { -+ return DOUBLE; -+ } // else return null -+ } else if (object instanceof MapType) { -+ return MAP; -+ } else if (object instanceof ListType) { -+ return LIST; -+ } else if (object instanceof String) { -+ return STRING; -+ } else if (object.getClass().isArray()) { -+ if (object instanceof byte[]) { -+ return BYTE_ARRAY; -+ } else if (object instanceof short[]) { -+ return SHORT_ARRAY; -+ } else if (object instanceof int[]) { -+ return INT_ARRAY; -+ } else if (object instanceof long[]) { -+ return LONG_ARRAY; -+ } // else return null -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..156a2ea46f8f88a02e88b50d7bb7be82ecd41919 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java -@@ -0,0 +1,9 @@ -+package ca.spottedleaf.dataconverter.types; -+ -+public interface TypeUtil { -+ -+ public ListType createEmptyList(); -+ -+ public MapType createEmptyMap(); -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/Types.java b/src/main/java/ca/spottedleaf/dataconverter/types/Types.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2ab9e3b579f20c9a189518496c522155630a36c4 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/Types.java -@@ -0,0 +1,15 @@ -+package ca.spottedleaf.dataconverter.types; -+ -+import ca.spottedleaf.dataconverter.types.json.JsonTypeCompressedUtil; -+import ca.spottedleaf.dataconverter.types.json.JsonTypeUtil; -+import ca.spottedleaf.dataconverter.types.nbt.NBTTypeUtil; -+ -+public interface Types { -+ -+ public static final TypeUtil NBT = new NBTTypeUtil(); -+ -+ public static final TypeUtil JSON = new JsonTypeUtil(); -+ -+ // why does this exist -+ public static final TypeUtil JSON_COMPRESSED = new JsonTypeCompressedUtil(); -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f6f57cb3a215876976b5eecae810b8b20925f2e2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java -@@ -0,0 +1,415 @@ -+package ca.spottedleaf.dataconverter.types.json; -+ -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+ -+public final class JsonListType implements ListType { -+ -+ protected final JsonArray array; -+ protected final boolean compressed; -+ -+ public JsonListType(final boolean compressed) { -+ this.array = new JsonArray(); -+ this.compressed = compressed; -+ } -+ -+ public JsonListType(final JsonArray array, final boolean compressed) { -+ this.array = array; -+ this.compressed = compressed; -+ } -+ -+ @Override -+ public TypeUtil getTypeUtil() { -+ return this.compressed ? Types.JSON_COMPRESSED : Types.JSON; -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (obj == null || obj.getClass() != JsonListType.class) { -+ return false; -+ } -+ -+ return this.array.equals(((JsonListType)obj).array); -+ } -+ -+ @Override -+ public int hashCode() { -+ return this.array.hashCode(); -+ } -+ -+ @Override -+ public String toString() { -+ return "JsonListType{" + -+ "array=" + this.array + -+ ", compressed=" + this.compressed + -+ '}'; -+ } -+ -+ public JsonArray getJson() { -+ return this.array; -+ } -+ -+ @Override -+ public ListType copy() { -+ return new JsonListType(JsonTypeUtil.copyJson(this.array), this.compressed); -+ } -+ -+ @Override -+ public ObjectType getType() { -+ return ObjectType.UNDEFINED; -+ } -+ -+ @Override -+ public int size() { -+ return this.array.size(); -+ } -+ -+ @Override -+ public void remove(final int index) { -+ this.array.remove(index); -+ } -+ -+ @Override -+ public Number getNumber(final int index) { -+ final JsonElement element = this.array.get(index); -+ if (element instanceof JsonPrimitive) { -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isNumber()) { -+ return primitive.getAsNumber(); -+ } else if (primitive.isBoolean()) { -+ return primitive.getAsBoolean() ? Byte.valueOf((byte)1) : Byte.valueOf((byte)0); -+ } else if (this.compressed && primitive.isString()) { -+ try { -+ return Integer.valueOf(Integer.parseInt(primitive.getAsString())); -+ } catch (final NumberFormatException ex) { -+ return null; -+ } -+ } -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public byte getByte(final int index) { -+ final Number number = this.getNumber(index); -+ -+ return number == null ? 0 : number.byteValue(); -+ } -+ -+ @Override -+ public void setByte(final int index, final byte to) { -+ this.array.set(index, new JsonPrimitive(Byte.valueOf(to))); -+ } -+ -+ @Override -+ public short getShort(final int index) { -+ final Number number = this.getNumber(index); -+ -+ return number == null ? 0 : number.shortValue(); -+ } -+ -+ @Override -+ public void setShort(final int index, final short to) { -+ this.array.set(index, new JsonPrimitive(Short.valueOf(to))); -+ } -+ -+ @Override -+ public int getInt(final int index) { -+ final Number number = this.getNumber(index); -+ -+ return number == null ? 0 : number.intValue(); -+ } -+ -+ @Override -+ public void setInt(final int index, final int to) { -+ this.array.set(index, new JsonPrimitive(Integer.valueOf(to))); -+ } -+ -+ @Override -+ public long getLong(final int index) { -+ final Number number = this.getNumber(index); -+ -+ return number == null ? 0 : number.longValue(); -+ } -+ -+ @Override -+ public void setLong(final int index, final long to) { -+ this.array.set(index, new JsonPrimitive(Long.valueOf(to))); -+ } -+ -+ @Override -+ public float getFloat(final int index) { -+ final Number number = this.getNumber(index); -+ -+ return number == null ? 0 : number.floatValue(); -+ } -+ -+ @Override -+ public void setFloat(final int index, final float to) { -+ this.array.set(index, new JsonPrimitive(Float.valueOf(to))); -+ } -+ -+ @Override -+ public double getDouble(final int index) { -+ final Number number = this.getNumber(index); -+ -+ return number == null ? 0 : number.doubleValue(); -+ } -+ -+ @Override -+ public void setDouble(final int index, final double to) { -+ this.array.set(index, new JsonPrimitive(Double.valueOf(to))); -+ } -+ -+ @Override -+ public byte[] getBytes(final int index) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setBytes(final int index, final byte[] to) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public short[] getShorts(final int index) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setShorts(final int index, final short[] to) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public int[] getInts(final int index) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setInts(final int index, final int[] to) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public long[] getLongs(final int index) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setLongs(final int index, final long[] to) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public ListType getList(final int index) { -+ final JsonElement element = this.array.get(index); -+ if (element instanceof JsonArray) { -+ return new JsonListType((JsonArray)element, this.compressed); -+ } -+ return null; -+ } -+ -+ @Override -+ public void setList(final int index, final ListType list) { -+ this.array.set(index, ((JsonListType)list).array); -+ } -+ -+ @Override -+ public MapType getMap(final int index) { -+ final JsonElement element = this.array.get(index); -+ if (element instanceof JsonObject) { -+ return new JsonMapType((JsonObject)element, this.compressed); -+ } -+ return null; -+ } -+ -+ @Override -+ public void setMap(final int index, final MapType to) { -+ this.array.set(index, ((JsonMapType)to).map); -+ } -+ -+ @Override -+ public String getString(final int index) { -+ final JsonElement element = this.array.get(index); -+ if (element instanceof JsonPrimitive) { -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isString() || (this.compressed && primitive.isNumber())) { -+ return primitive.getAsString(); -+ } -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public void setString(final int index, final String to) { -+ this.array.set(index, new JsonPrimitive(to)); -+ } -+ -+ @Override -+ public void addByte(final byte b) { -+ this.array.add(Byte.valueOf(b)); -+ } -+ -+ @Override -+ public void addByte(final int index, final byte b) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addShort(final short s) { -+ this.array.add(Short.valueOf(s)); -+ } -+ -+ @Override -+ public void addShort(final int index, final short s) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addInt(final int i) { -+ this.array.add(Integer.valueOf(i)); -+ } -+ -+ @Override -+ public void addInt(final int index, final int i) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addLong(final long l) { -+ this.array.add(Long.valueOf(l)); -+ } -+ -+ @Override -+ public void addLong(final int index, final long l) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addFloat(final float f) { -+ this.array.add(Float.valueOf(f)); -+ } -+ -+ @Override -+ public void addFloat(final int index, final float f) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addDouble(final double d) { -+ this.array.add(Double.valueOf(d)); -+ } -+ -+ @Override -+ public void addDouble(final int index, final double d) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addByteArray(final byte[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addByteArray(final int index, final byte[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addShortArray(final short[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addShortArray(final int index, final short[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addIntArray(final int[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addIntArray(final int index, final int[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addLongArray(final long[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addLongArray(final int index, final long[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addList(final ListType list) { -+ this.array.add(((JsonListType)list).array); -+ } -+ -+ @Override -+ public void addList(final int index, final ListType list) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addMap(final MapType map) { -+ this.array.add(((JsonMapType)map).map); -+ } -+ -+ @Override -+ public void addMap(final int index, final MapType map) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addString(final String string) { -+ this.array.add(string); -+ } -+ -+ @Override -+ public void addString(final int index, final String string) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c89bcc4b9974dd65bad9b096cccf8a4369d47f4f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java -@@ -0,0 +1,450 @@ -+package ca.spottedleaf.dataconverter.types.json; -+ -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+import java.util.LinkedHashSet; -+import java.util.Map; -+import java.util.Set; -+ -+public final class JsonMapType implements MapType { -+ -+ protected final JsonObject map; -+ protected final boolean compressed; -+ -+ public JsonMapType(final boolean compressed) { -+ this.map = new JsonObject(); -+ this.compressed = compressed; -+ } -+ -+ public JsonMapType(final JsonObject map, final boolean compressed) { -+ this.map = map; -+ this.compressed = compressed; -+ } -+ -+ @Override -+ public TypeUtil getTypeUtil() { -+ return this.compressed ? Types.JSON_COMPRESSED : Types.JSON; -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (obj == null || obj.getClass() != JsonMapType.class) { -+ return false; -+ } -+ -+ return this.map.equals(((JsonMapType)obj).map); -+ } -+ -+ @Override -+ public int hashCode() { -+ return this.map.hashCode(); -+ } -+ -+ @Override -+ public String toString() { -+ return "JsonMapType{" + -+ "map=" + this.map + -+ ", compressed=" + this.compressed + -+ '}'; -+ } -+ -+ public JsonObject getJson() { -+ return this.map; -+ } -+ -+ @Override -+ public int size() { -+ return this.map.entrySet().size(); -+ } -+ -+ @Override -+ public boolean isEmpty() { -+ return this.map.entrySet().isEmpty(); -+ } -+ -+ @Override -+ public void clear() { -+ this.map.entrySet().clear(); -+ } -+ -+ @Override -+ public Set keys() { -+ // ah shit. no keyset method -+ final Set keys = new LinkedHashSet<>(); -+ -+ for (final Map.Entry entry : this.map.entrySet()) { -+ keys.add(entry.getKey()); -+ } -+ -+ return keys; -+ } -+ -+ @Override -+ public MapType copy() { -+ return new JsonMapType(JsonTypeUtil.copyJson(this.map), this.compressed); -+ } -+ -+ @Override -+ public boolean hasKey(final String key) { -+ return this.map.has(key); -+ } -+ -+ @Override -+ public boolean hasKey(final String key, final ObjectType type) { -+ final JsonElement element = this.map.get(key); -+ if (element == null) { -+ return false; -+ } -+ -+ if (type == ObjectType.UNDEFINED) { -+ return true; -+ } -+ -+ if (element.isJsonArray()) { -+ return type == ObjectType.LIST; -+ } else if (element.isJsonObject()) { -+ return type == ObjectType.MAP; -+ } else if (element.isJsonNull()) { -+ return false; -+ } -+ -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isString()) { -+ return type == ObjectType.STRING || (this.compressed && type == ObjectType.NUMBER); -+ } else if (primitive.isBoolean()) { -+ return type.isNumber(); -+ } else { -+ // is number -+ final Number number = primitive.getAsNumber(); -+ if (number instanceof Byte) { -+ return type == ObjectType.BYTE || (this.compressed && type == ObjectType.STRING); -+ } else if (number instanceof Short) { -+ return type == ObjectType.SHORT || (this.compressed && type == ObjectType.STRING); -+ } else if (number instanceof Integer) { -+ return type == ObjectType.INT || (this.compressed && type == ObjectType.STRING); -+ } else if (number instanceof Long) { -+ return type == ObjectType.LONG || (this.compressed && type == ObjectType.STRING); -+ } else if (number instanceof Float) { -+ return type == ObjectType.FLOAT || (this.compressed && type == ObjectType.STRING); -+ } else { -+ return type == ObjectType.DOUBLE || (this.compressed && type == ObjectType.STRING); -+ } -+ } -+ } -+ -+ @Override -+ public void remove(final String key) { -+ this.map.remove(key); -+ } -+ -+ @Override -+ public Object getGeneric(final String key) { -+ final JsonElement element = this.map.get(key); -+ if (element == null || element.isJsonNull()) { -+ return null; -+ } else if (element.isJsonObject()) { -+ return new JsonMapType((JsonObject)element, this.compressed); -+ } else if (element.isJsonArray()) { -+ return new JsonListType((JsonArray)element, this.compressed); -+ } else { -+ // primitive -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isNumber()) { -+ return primitive.getAsNumber(); -+ } else if (primitive.isString()) { -+ return primitive.getAsString(); -+ } else if (primitive.isBoolean()) { -+ return Boolean.valueOf(primitive.getAsBoolean()); -+ } else { -+ throw new IllegalStateException("Unknown json object " + element); -+ } -+ } -+ } -+ -+ @Override -+ public Number getNumber(final String key) { -+ return this.getNumber(key, null); -+ } -+ -+ @Override -+ public Number getNumber(final String key, final Number dfl) { -+ final JsonElement element = this.map.get(key); -+ if (element instanceof JsonPrimitive) { -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isNumber()) { -+ return primitive.getAsNumber(); -+ } else if (primitive.isBoolean()) { -+ return primitive.getAsBoolean() ? Byte.valueOf((byte)1) : Byte.valueOf((byte)0); -+ } else if (this.compressed && primitive.isString()) { -+ try { -+ return Integer.valueOf(Integer.parseInt(primitive.getAsString())); -+ } catch (final NumberFormatException ex) { -+ return null; -+ } -+ } -+ } -+ -+ return dfl; -+ } -+ -+ @Override -+ public boolean getBoolean(final String key) { -+ return this.getBoolean(key, false); -+ } -+ -+ @Override -+ public boolean getBoolean(final String key, final boolean dfl) { -+ final JsonElement element = this.map.get(key); -+ if (element instanceof JsonPrimitive) { -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isNumber()) { -+ return primitive.getAsNumber().byteValue() != 0; -+ } else if (primitive.isBoolean()) { -+ return primitive.getAsBoolean(); -+ } -+ } -+ -+ return dfl; -+ } -+ -+ @Override -+ public void setBoolean(final String key, final boolean val) { -+ this.map.addProperty(key, Boolean.valueOf(val)); -+ } -+ -+ @Override -+ public byte getByte(final String key) { -+ return this.getByte(key, (byte)0); -+ } -+ -+ @Override -+ public byte getByte(final String key, final byte dfl) { -+ final Number ret = this.getNumber(key, null); -+ return ret == null ? dfl : ret.byteValue(); -+ } -+ -+ @Override -+ public void setByte(final String key, final byte val) { -+ this.map.addProperty(key, Byte.valueOf(val)); -+ } -+ -+ @Override -+ public short getShort(final String key) { -+ return this.getShort(key, (short)0); -+ } -+ -+ @Override -+ public short getShort(final String key, final short dfl) { -+ final Number ret = this.getNumber(key, null); -+ return ret == null ? dfl : ret.shortValue(); -+ } -+ -+ @Override -+ public void setShort(final String key, final short val) { -+ this.map.addProperty(key, Short.valueOf(val)); -+ } -+ -+ @Override -+ public int getInt(final String key) { -+ return this.getInt(key, 0); -+ } -+ -+ @Override -+ public int getInt(final String key, final int dfl) { -+ final Number ret = this.getNumber(key, null); -+ return ret == null ? dfl : ret.intValue(); -+ } -+ -+ @Override -+ public void setInt(final String key, final int val) { -+ this.map.addProperty(key, Integer.valueOf(val)); -+ } -+ -+ @Override -+ public long getLong(final String key) { -+ return this.getLong(key, 0L); -+ } -+ -+ @Override -+ public long getLong(final String key, final long dfl) { -+ final Number ret = this.getNumber(key, null); -+ return ret == null ? dfl : ret.longValue(); -+ } -+ -+ @Override -+ public void setLong(final String key, final long val) { -+ this.map.addProperty(key, Long.valueOf(val)); -+ } -+ -+ @Override -+ public float getFloat(final String key) { -+ return this.getFloat(key, 0.0F); -+ } -+ -+ @Override -+ public float getFloat(final String key, final float dfl) { -+ final Number ret = this.getNumber(key, null); -+ return ret == null ? dfl : ret.floatValue(); -+ } -+ -+ @Override -+ public void setFloat(final String key, final float val) { -+ this.map.addProperty(key, Float.valueOf(val)); -+ } -+ -+ @Override -+ public double getDouble(final String key) { -+ return this.getDouble(key, 0.0D); -+ } -+ -+ @Override -+ public double getDouble(final String key, final double dfl) { -+ final Number ret = this.getNumber(key, null); -+ return ret == null ? dfl : ret.doubleValue(); -+ } -+ -+ @Override -+ public void setDouble(final String key, final double val) { -+ this.map.addProperty(key, Double.valueOf(val)); -+ } -+ -+ @Override -+ public byte[] getBytes(final String key) { -+ return this.getBytes(key, null); -+ } -+ -+ @Override -+ public byte[] getBytes(final String key, final byte[] dfl) { -+ return dfl; -+ } -+ -+ @Override -+ public void setBytes(final String key, final byte[] val) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public short[] getShorts(final String key) { -+ return this.getShorts(key, null); -+ } -+ -+ @Override -+ public short[] getShorts(final String key, final short[] dfl) { -+ return dfl; -+ } -+ -+ @Override -+ public void setShorts(final String key, final short[] val) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public int[] getInts(final String key) { -+ return this.getInts(key, null); -+ } -+ -+ @Override -+ public int[] getInts(final String key, final int[] dfl) { -+ return dfl; -+ } -+ -+ @Override -+ public void setInts(final String key, final int[] val) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public long[] getLongs(final String key) { -+ return this.getLongs(key, null); -+ } -+ -+ @Override -+ public long[] getLongs(final String key, final long[] dfl) { -+ return dfl; -+ } -+ -+ @Override -+ public void setLongs(final String key, final long[] val) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public ListType getListUnchecked(final String key) { -+ return this.getListUnchecked(key, null); -+ } -+ -+ @Override -+ public ListType getListUnchecked(final String key, final ListType dfl) { -+ final JsonElement element = this.map.get(key); -+ if (element instanceof JsonArray) { -+ return new JsonListType((JsonArray)element, this.compressed); -+ } -+ -+ return dfl; -+ } -+ -+ @Override -+ public void setList(final String key, final ListType val) { -+ this.map.add(key, ((JsonListType)val).getJson()); -+ } -+ -+ @Override -+ public MapType getMap(final String key) { -+ return this.getMap(key, null); -+ } -+ -+ @Override -+ public MapType getMap(final String key, final MapType dfl) { -+ final JsonElement element = this.map.get(key); -+ if (element instanceof JsonObject) { -+ return new JsonMapType((JsonObject)element, this.compressed); -+ } -+ -+ return dfl; -+ } -+ -+ @Override -+ public void setMap(final String key, final MapType val) { -+ this.map.add(key, ((JsonMapType)val).map); -+ } -+ -+ @Override -+ public String getString(final String key) { -+ return this.getString(key, null); -+ } -+ -+ @Override -+ public String getString(final String key, final String dfl) { -+ final JsonElement element = this.map.get(key); -+ if (element instanceof JsonPrimitive) { -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isString()) { -+ return primitive.getAsString(); -+ } else if (this.compressed && primitive.isNumber()) { -+ return primitive.getAsString(); -+ } -+ } -+ -+ return dfl; -+ } -+ -+ @Override -+ public void setString(final String key, final String val) { -+ this.map.addProperty(key, val); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9c3093b66b847b5248bde923243fce78842bf67f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.types.json; -+ -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+ -+public final class JsonTypeCompressedUtil implements TypeUtil { -+ -+ @Override -+ public ListType createEmptyList() { -+ return new JsonListType(true); -+ } -+ -+ @Override -+ public MapType createEmptyMap() { -+ return new JsonMapType(true); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9410ae68395a09c7710bdbb2ccc6acf6633cad23 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java -@@ -0,0 +1,81 @@ -+package ca.spottedleaf.dataconverter.types.json; -+ -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+import ca.spottedleaf.dataconverter.types.nbt.NBTListType; -+import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import com.google.gson.JsonNull; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+import com.google.gson.internal.Streams; -+import com.google.gson.stream.JsonReader; -+import java.io.StringReader; -+import java.util.Map; -+ -+public final class JsonTypeUtil implements TypeUtil { -+ -+ @Override -+ public ListType createEmptyList() { -+ return new JsonListType(false); -+ } -+ -+ @Override -+ public MapType createEmptyMap() { -+ return new JsonMapType(false); -+ } -+ -+ public static T copyJson(final T from) { -+ // This is stupidly inefficient. However, deepCopy() is not exposed in this gson version. -+ final String out = from.toString(); -+ -+ return (T)Streams.parse(new JsonReader(new StringReader(out))); -+ } -+ -+ private static Object convertToGenericNBT(final JsonElement element, final boolean compressed) { -+ if (element instanceof JsonObject) { -+ return convertJsonToNBT(new JsonMapType((JsonObject)element, compressed)); -+ } else if (element instanceof JsonArray) { -+ return convertJsonToNBT(new JsonListType((JsonArray)element, compressed)); -+ } else if (element instanceof JsonNull) { -+ return null; -+ } else { -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isBoolean()) { -+ return primitive.getAsBoolean() ? Byte.valueOf((byte)1) : Byte.valueOf((byte)0); -+ } else if (primitive.isNumber()) { -+ return primitive.getAsNumber(); -+ } else if (primitive.isString()) { -+ return primitive.getAsString(); -+ } -+ } -+ -+ throw new IllegalStateException("Unrecognized type " + element); -+ } -+ -+ public static NBTMapType convertJsonToNBT(final JsonMapType json) { -+ final NBTMapType ret = new NBTMapType(); -+ for (final Map.Entry entry : json.map.entrySet()) { -+ final Object obj = convertToGenericNBT(entry.getValue(), json.compressed); -+ if (obj == null) { -+ continue; -+ } -+ -+ ret.setGeneric(entry.getKey(), obj); -+ } -+ -+ return ret; -+ } -+ -+ public static NBTListType convertJsonToNBT(final JsonListType json) { -+ final NBTListType ret = new NBTListType(); -+ -+ for (int i = 0, len = json.size(); i < len; ++i) { -+ ret.addGeneric(convertToGenericNBT(json.array.get(i), json.compressed)); -+ } -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bf4e9ea17222cfa8f7cee9e46775302c9c2e6328 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java -@@ -0,0 +1,440 @@ -+package ca.spottedleaf.dataconverter.types.nbt; -+ -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+import ca.spottedleaf.dataconverter.types.Types; -+import net.minecraft.nbt.ByteArrayTag; -+import net.minecraft.nbt.ByteTag; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.DoubleTag; -+import net.minecraft.nbt.FloatTag; -+import net.minecraft.nbt.IntArrayTag; -+import net.minecraft.nbt.IntTag; -+import net.minecraft.nbt.ListTag; -+import net.minecraft.nbt.LongArrayTag; -+import net.minecraft.nbt.LongTag; -+import net.minecraft.nbt.NumericTag; -+import net.minecraft.nbt.ShortTag; -+import net.minecraft.nbt.StringTag; -+import net.minecraft.nbt.Tag; -+ -+public final class NBTListType implements ListType { -+ -+ private final ListTag list; -+ -+ public NBTListType() { -+ this.list = new ListTag(); -+ } -+ -+ public NBTListType(final ListTag tag) { -+ this.list = tag; -+ } -+ -+ @Override -+ public TypeUtil getTypeUtil() { -+ return Types.NBT; -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ if (obj == null || obj.getClass() != NBTListType.class) { -+ return false; -+ } -+ -+ return this.list.equals(((NBTListType)obj).list); -+ } -+ -+ @Override -+ public int hashCode() { -+ return this.list.hashCode(); -+ } -+ -+ @Override -+ public String toString() { -+ return "NBTListType{" + -+ "list=" + this.list + -+ '}'; -+ } -+ -+ public ListTag getTag() { -+ return this.list; -+ } -+ -+ @Override -+ public ListType copy() { -+ return new NBTListType(this.list.copy()); -+ } -+ -+ protected static ObjectType getType(final byte id) { -+ switch (id) { -+ case 0: // END -+ return ObjectType.NONE; -+ case 1: // BYTE -+ return ObjectType.BYTE; -+ case 2: // SHORT -+ return ObjectType.SHORT; -+ case 3: // INT -+ return ObjectType.INT; -+ case 4: // LONG -+ return ObjectType.LONG; -+ case 5: // FLOAT -+ return ObjectType.FLOAT; -+ case 6: // DOUBLE -+ return ObjectType.DOUBLE; -+ case 7: // BYTE_ARRAY -+ return ObjectType.BYTE_ARRAY; -+ case 8: // STRING -+ return ObjectType.STRING; -+ case 9: // LIST -+ return ObjectType.LIST; -+ case 10: // COMPOUND -+ return ObjectType.MAP; -+ case 11: // INT_ARRAY -+ return ObjectType.INT_ARRAY; -+ case 12: // LONG_ARRAY -+ return ObjectType.LONG_ARRAY; -+ default: -+ throw new IllegalStateException("Unknown type: " + id); -+ } -+ } -+ -+ @Override -+ public ObjectType getType() { -+ return getType(this.list.getElementType()); -+ } -+ -+ @Override -+ public int size() { -+ return this.list.size(); -+ } -+ -+ @Override -+ public void remove(final int index) { -+ this.list.remove(index); -+ } -+ -+ @Override -+ public Number getNumber(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsNumber(); -+ } -+ -+ @Override -+ public byte getByte(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsByte(); -+ } -+ -+ @Override -+ public void setByte(final int index, final byte to) { -+ this.list.set(index, ByteTag.valueOf(to)); -+ } -+ -+ @Override -+ public short getShort(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsShort(); -+ } -+ -+ @Override -+ public void setShort(final int index, final short to) { -+ this.list.set(index, ShortTag.valueOf(to)); -+ } -+ -+ @Override -+ public int getInt(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsInt(); -+ } -+ -+ @Override -+ public void setInt(final int index, final int to) { -+ this.list.set(index, IntTag.valueOf(to)); -+ } -+ -+ @Override -+ public long getLong(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsLong(); -+ } -+ -+ @Override -+ public void setLong(final int index, final long to) { -+ this.list.set(index, LongTag.valueOf(to)); -+ } -+ -+ @Override -+ public float getFloat(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsFloat(); -+ } -+ -+ @Override -+ public void setFloat(final int index, final float to) { -+ this.list.set(index, FloatTag.valueOf(to)); -+ } -+ -+ @Override -+ public double getDouble(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsDouble(); -+ } -+ -+ @Override -+ public void setDouble(final int index, final double to) { -+ this.list.set(index, DoubleTag.valueOf(to)); -+ } -+ -+ @Override -+ public byte[] getBytes(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof ByteArrayTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((ByteArrayTag)tag).getAsByteArray(); -+ } -+ -+ @Override -+ public void setBytes(final int index, final byte[] to) { -+ this.list.set(index, new ByteArrayTag(to)); -+ } -+ -+ @Override -+ public short[] getShorts(final int index) { -+ // NBT does not support shorts -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setShorts(final int index, final short[] to) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public int[] getInts(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof IntArrayTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((IntArrayTag)tag).getAsIntArray(); -+ } -+ -+ @Override -+ public void setInts(final int index, final int[] to) { -+ this.list.set(index, new IntArrayTag(to)); -+ } -+ -+ @Override -+ public long[] getLongs(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof LongArrayTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((LongArrayTag)tag).getAsLongArray(); -+ } -+ -+ @Override -+ public void setLongs(final int index, final long[] to) { -+ this.list.set(index, new LongArrayTag(to)); -+ } -+ -+ @Override -+ public ListType getList(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof ListTag)) { -+ throw new IllegalStateException(); -+ } -+ return new NBTListType((ListTag)tag); -+ } -+ -+ @Override -+ public void setList(final int index, final ListType list) { -+ this.list.set(index, ((NBTListType)list).getTag()); -+ } -+ -+ @Override -+ public MapType getMap(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof CompoundTag)) { -+ throw new IllegalStateException(); -+ } -+ return new NBTMapType((CompoundTag)tag); -+ } -+ -+ @Override -+ public void setMap(final int index, final MapType to) { -+ this.list.set(index, ((NBTMapType)to).getTag()); -+ } -+ -+ @Override -+ public String getString(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof StringTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((StringTag)tag).getAsString(); -+ } -+ -+ @Override -+ public void setString(final int index, final String to) { -+ this.list.set(index, StringTag.valueOf(to)); -+ } -+ -+ @Override -+ public void addByte(final byte b) { -+ this.list.add(ByteTag.valueOf(b)); -+ } -+ -+ @Override -+ public void addByte(final int index, final byte b) { -+ this.list.add(index, ByteTag.valueOf(b)); -+ } -+ -+ @Override -+ public void addShort(final short s) { -+ this.list.add(ShortTag.valueOf(s)); -+ } -+ -+ @Override -+ public void addShort(final int index, final short s) { -+ this.list.add(index, ShortTag.valueOf(s)); -+ } -+ -+ @Override -+ public void addInt(final int i) { -+ this.list.add(IntTag.valueOf(i)); -+ } -+ -+ @Override -+ public void addInt(final int index, final int i) { -+ this.list.add(index, IntTag.valueOf(i)); -+ } -+ -+ @Override -+ public void addLong(final long l) { -+ this.list.add(LongTag.valueOf(l)); -+ } -+ -+ @Override -+ public void addLong(final int index, final long l) { -+ this.list.add(index, LongTag.valueOf(l)); -+ } -+ -+ @Override -+ public void addFloat(final float f) { -+ this.list.add(FloatTag.valueOf(f)); -+ } -+ -+ @Override -+ public void addFloat(final int index, final float f) { -+ this.list.add(index, FloatTag.valueOf(f)); -+ } -+ -+ @Override -+ public void addDouble(final double d) { -+ this.list.add(DoubleTag.valueOf(d)); -+ } -+ -+ @Override -+ public void addDouble(final int index, final double d) { -+ this.list.add(index, DoubleTag.valueOf(d)); -+ } -+ -+ @Override -+ public void addByteArray(final byte[] arr) { -+ this.list.add(new ByteArrayTag(arr)); -+ } -+ -+ @Override -+ public void addByteArray(final int index, final byte[] arr) { -+ this.list.add(index, new ByteArrayTag(arr)); -+ } -+ -+ @Override -+ public void addShortArray(final short[] arr) { -+ // NBT does not support short[] -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addShortArray(final int index, final short[] arr) { -+ // NBT does not support short[] -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addIntArray(final int[] arr) { -+ this.list.add(new IntArrayTag(arr)); -+ } -+ -+ @Override -+ public void addIntArray(final int index, final int[] arr) { -+ this.list.add(index, new IntArrayTag(arr)); -+ } -+ -+ @Override -+ public void addLongArray(final long[] arr) { -+ this.list.add(new LongArrayTag(arr)); -+ } -+ -+ @Override -+ public void addLongArray(final int index, final long[] arr) { -+ this.list.add(index, new LongArrayTag(arr)); -+ } -+ -+ @Override -+ public void addList(final ListType list) { -+ this.list.add(((NBTListType)list).getTag()); -+ } -+ -+ @Override -+ public void addList(final int index, final ListType list) { -+ this.list.add(index, ((NBTListType)list).getTag()); -+ } -+ -+ @Override -+ public void addMap(final MapType map) { -+ this.list.add(((NBTMapType)map).getTag()); -+ } -+ -+ @Override -+ public void addMap(final int index, final MapType map) { -+ this.list.add(index, ((NBTMapType)map).getTag()); -+ } -+ -+ @Override -+ public void addString(final String string) { -+ this.list.add(StringTag.valueOf(string)); -+ } -+ -+ @Override -+ public void addString(final int index, final String string) { -+ this.list.add(index, StringTag.valueOf(string)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..12880f93b53db1e60cbf13805e2eb08fee5fd203 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java -@@ -0,0 +1,440 @@ -+package ca.spottedleaf.dataconverter.types.nbt; -+ -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+import ca.spottedleaf.dataconverter.types.Types; -+import net.minecraft.nbt.ByteArrayTag; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.IntArrayTag; -+import net.minecraft.nbt.ListTag; -+import net.minecraft.nbt.LongArrayTag; -+import net.minecraft.nbt.NumericTag; -+import net.minecraft.nbt.StringTag; -+import net.minecraft.nbt.Tag; -+ -+import java.util.Set; -+ -+public final class NBTMapType implements MapType { -+ -+ private final CompoundTag map; -+ -+ public NBTMapType() { -+ this.map = new CompoundTag(); -+ } -+ -+ public NBTMapType(final CompoundTag tag) { -+ this.map = tag; -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ if (obj == null || obj.getClass() != NBTMapType.class) { -+ return false; -+ } -+ -+ return this.map.equals(((NBTMapType)obj).map); -+ } -+ -+ @Override -+ public TypeUtil getTypeUtil() { -+ return Types.NBT; -+ } -+ -+ @Override -+ public int hashCode() { -+ return this.map.hashCode(); -+ } -+ -+ @Override -+ public String toString() { -+ return "NBTMapType{" + -+ "map=" + this.map + -+ '}'; -+ } -+ -+ @Override -+ public int size() { -+ return this.map.size(); -+ } -+ -+ @Override -+ public boolean isEmpty() { -+ return this.map.isEmpty(); -+ } -+ -+ @Override -+ public void clear() { -+ this.map.getAllKeys().clear(); -+ } -+ -+ @Override -+ public Set keys() { -+ return this.map.getAllKeys(); -+ } -+ -+ public CompoundTag getTag() { -+ return this.map; -+ } -+ -+ @Override -+ public MapType copy() { -+ return new NBTMapType(this.map.copy()); -+ } -+ -+ @Override -+ public boolean hasKey(final String key) { -+ return this.map.get(key) != null; -+ } -+ -+ @Override -+ public boolean hasKey(final String key, final ObjectType type) { -+ final Tag tag = this.map.get(key); -+ if (tag == null) { -+ return false; -+ } -+ -+ final ObjectType valueType = NBTListType.getType(tag.getId()); -+ -+ return valueType == type || (type == ObjectType.NUMBER && valueType.isNumber()); -+ } -+ -+ @Override -+ public void remove(final String key) { -+ this.map.remove(key); -+ } -+ -+ @Override -+ public Object getGeneric(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag == null) { -+ return null; -+ } -+ -+ switch (NBTListType.getType(tag.getId())) { -+ case BYTE: -+ case SHORT: -+ case INT: -+ case LONG: -+ case FLOAT: -+ case DOUBLE: -+ return ((NumericTag)tag).getAsNumber(); -+ case MAP: -+ return new NBTMapType((CompoundTag)tag); -+ case LIST: -+ return new NBTListType((ListTag)tag); -+ case STRING: -+ return ((StringTag)tag).getAsString(); -+ case BYTE_ARRAY: -+ return ((ByteArrayTag)tag).getAsByteArray(); -+ // Note: No short array tag! -+ case INT_ARRAY: -+ return ((IntArrayTag)tag).getAsIntArray(); -+ case LONG_ARRAY: -+ return ((LongArrayTag)tag).getAsLongArray(); -+ } -+ -+ throw new IllegalStateException("Unrecognized type " + tag); -+ } -+ -+ @Override -+ public Number getNumber(final String key) { -+ return this.getNumber(key, null); -+ } -+ -+ @Override -+ public Number getNumber(final String key, final Number dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsNumber(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public boolean getBoolean(final String key) { -+ return this.getByte(key) != 0; -+ } -+ -+ @Override -+ public boolean getBoolean(final String key, final boolean dfl) { -+ return this.getByte(key, dfl ? (byte)1 : (byte)0) != 0; -+ } -+ -+ @Override -+ public void setBoolean(final String key, final boolean val) { -+ this.setByte(key, val ? (byte)1 : (byte)0); -+ } -+ -+ @Override -+ public byte getByte(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsByte(); -+ } -+ return 0; -+ } -+ -+ @Override -+ public byte getByte(final String key, final byte dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsByte(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setByte(final String key, final byte val) { -+ this.map.putByte(key, val); -+ } -+ -+ @Override -+ public short getShort(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsShort(); -+ } -+ return 0; -+ } -+ -+ @Override -+ public short getShort(final String key, final short dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsShort(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setShort(final String key, final short val) { -+ this.map.putShort(key, val); -+ } -+ -+ @Override -+ public int getInt(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsInt(); -+ } -+ return 0; -+ } -+ -+ @Override -+ public int getInt(final String key, final int dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsInt(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setInt(final String key, final int val) { -+ this.map.putInt(key, val); -+ } -+ -+ @Override -+ public long getLong(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsLong(); -+ } -+ return 0; -+ } -+ -+ @Override -+ public long getLong(final String key, final long dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsLong(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setLong(final String key, final long val) { -+ this.map.putLong(key, val); -+ } -+ -+ @Override -+ public float getFloat(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsFloat(); -+ } -+ return 0; -+ } -+ -+ @Override -+ public float getFloat(final String key, final float dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsFloat(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setFloat(final String key, final float val) { -+ this.map.putFloat(key, val); -+ } -+ -+ @Override -+ public double getDouble(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsDouble(); -+ } -+ return 0; -+ } -+ -+ @Override -+ public double getDouble(final String key, final double dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsDouble(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setDouble(final String key, final double val) { -+ this.map.putDouble(key, val); -+ } -+ -+ @Override -+ public byte[] getBytes(final String key) { -+ return this.getBytes(key, null); -+ } -+ -+ @Override -+ public byte[] getBytes(final String key, final byte[] dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof ByteArrayTag) { -+ return ((ByteArrayTag)tag).getAsByteArray(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setBytes(final String key, final byte[] val) { -+ this.map.putByteArray(key, val); -+ } -+ -+ @Override -+ public short[] getShorts(final String key) { -+ return this.getShorts(key, null); -+ } -+ -+ @Override -+ public short[] getShorts(final String key, final short[] dfl) { -+ // NBT does not support short array -+ return dfl; -+ } -+ -+ @Override -+ public void setShorts(final String key, final short[] val) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public int[] getInts(final String key) { -+ return this.getInts(key, null); -+ } -+ -+ @Override -+ public int[] getInts(final String key, final int[] dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof IntArrayTag) { -+ return ((IntArrayTag)tag).getAsIntArray(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setInts(final String key, final int[] val) { -+ this.map.putIntArray(key, val); -+ } -+ -+ @Override -+ public long[] getLongs(final String key) { -+ return this.getLongs(key, null); -+ } -+ -+ @Override -+ public long[] getLongs(final String key, final long[] dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof LongArrayTag) { -+ return ((LongArrayTag)tag).getAsLongArray(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setLongs(final String key, final long[] val) { -+ this.map.putLongArray(key, val); -+ } -+ -+ @Override -+ public ListType getListUnchecked(final String key) { -+ return this.getListUnchecked(key, null); -+ } -+ -+ @Override -+ public ListType getListUnchecked(final String key, final ListType dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof ListTag) { -+ return new NBTListType((ListTag)tag); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setList(final String key, final ListType val) { -+ this.map.put(key, ((NBTListType)val).getTag()); -+ } -+ -+ @Override -+ public MapType getMap(final String key) { -+ return this.getMap(key, null); -+ } -+ -+ @Override -+ public MapType getMap(final String key, final MapType dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof CompoundTag) { -+ return new NBTMapType((CompoundTag)tag); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setMap(final String key, final MapType val) { -+ this.map.put(key, ((NBTMapType)val).getTag()); -+ } -+ -+ @Override -+ public String getString(final String key) { -+ return this.getString(key, null); -+ } -+ -+ @Override -+ public String getString(final String key, final String dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof StringTag) { -+ return ((StringTag)tag).getAsString(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setString(final String key, final String val) { -+ this.map.putString(key, val); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..62c0f4073aff301bf5b3187e0d4446fd8d0ac475 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.types.nbt; -+ -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+ -+public final class NBTTypeUtil implements TypeUtil { -+ -+ @Override -+ public ListType createEmptyList() { -+ return new NBTListType(); -+ } -+ -+ @Override -+ public MapType createEmptyMap() { -+ return new NBTMapType(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6596de3d9ebae583c252aa061f0cfdf8778ea1a5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java -@@ -0,0 +1,77 @@ -+package ca.spottedleaf.dataconverter.util; -+ -+import it.unimi.dsi.fastutil.ints.Int2IntFunction; -+ -+import java.util.Arrays; -+ -+public class Int2IntArraySortedMap { -+ -+ protected int[] key; -+ protected int[] val; -+ protected int size; -+ -+ public Int2IntArraySortedMap() { -+ this.key = new int[8]; -+ this.val = new int[8]; -+ } -+ -+ public int put(final int key, final int value) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ final int current = this.val[index]; -+ this.val[index] = value; -+ return current; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ ++this.size; -+ -+ this.key[insert] = key; -+ this.val[insert] = value; -+ -+ return 0; -+ } -+ -+ public int computeIfAbsent(final int key, final Int2IntFunction producer) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ return this.val[index]; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ ++this.size; -+ -+ this.key[insert] = key; -+ -+ return this.val[insert] = producer.apply(key); -+ } -+ -+ public int get(final int key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ return 0; -+ } -+ return this.val[index]; -+ } -+ -+ public int getFloor(final int key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ final int insert = -(index + 1) - 1; -+ return insert < 0 ? 0 : this.val[insert]; -+ } -+ return this.val[index]; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..de9d632489609136c712a9adaee941fd38fad440 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java -@@ -0,0 +1,74 @@ -+package ca.spottedleaf.dataconverter.util; -+ -+import java.util.Arrays; -+import java.util.function.IntFunction; -+ -+public class Int2ObjectArraySortedMap { -+ -+ protected int[] key; -+ protected V[] val; -+ protected int size; -+ -+ public Int2ObjectArraySortedMap() { -+ this.key = new int[8]; -+ this.val = (V[])new Object[8]; -+ } -+ -+ public V put(final int key, final V value) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ final V current = this.val[index]; -+ this.val[index] = value; -+ return current; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ -+ this.key[insert] = key; -+ this.val[insert] = value; -+ -+ return null; -+ } -+ -+ public V computeIfAbsent(final int key, final IntFunction producer) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ return this.val[index]; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ -+ this.key[insert] = key; -+ -+ return this.val[insert] = producer.apply(key); -+ } -+ -+ public V get(final int key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ return null; -+ } -+ return this.val[index]; -+ } -+ -+ public V getFloor(final int key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ final int insert = -(index + 1); -+ return this.val[insert]; -+ } -+ return this.val[index]; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4bbf38c812feeb30d2aa5f3fcf482bfcbed79d05 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java -@@ -0,0 +1,239 @@ -+package ca.spottedleaf.dataconverter.util; -+ -+public final class IntegerUtil { -+ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; -+ public static final long HIGH_BIT_U64 = Long.MIN_VALUE; -+ -+ public static int ceilLog2(final int value) { -+ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ public static long ceilLog2(final long value) { -+ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int floorLog2(final int value) { -+ // xor is optimized subtract for 2^n -1 -+ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) -+ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int floorLog2(final long value) { -+ // xor is optimized subtract for 2^n -1 -+ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) -+ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int roundCeilLog2(final int value) { -+ // optimized variant of 1 << (32 - leading(val - 1)) -+ // given -+ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) -+ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) -+ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) -+ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) -+ // HIGH_BIT_32 >>> (-1 + leading(val - 1)) -+ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); -+ } -+ -+ public static long roundCeilLog2(final long value) { -+ // see logic documented above -+ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); -+ } -+ -+ public static int roundFloorLog2(final int value) { -+ // optimized variant of 1 << (31 - leading(val)) -+ // given -+ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) -+ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) -+ // HIGH_BIT_32 >> (31 - (31 - leading(val))) -+ // HIGH_BIT_32 >> (31 - 31 + leading(val)) -+ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); -+ } -+ -+ public static long roundFloorLog2(final long value) { -+ // see logic documented above -+ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); -+ } -+ -+ public static boolean isPowerOfTwo(final int n) { -+ // 2^n has one bit -+ // note: this rets true for 0 still -+ return IntegerUtil.getTrailingBit(n) == n; -+ } -+ -+ public static boolean isPowerOfTwo(final long n) { -+ // 2^n has one bit -+ // note: this rets true for 0 still -+ return IntegerUtil.getTrailingBit(n) == n; -+ } -+ -+ public static int getTrailingBit(final int n) { -+ return -n & n; -+ } -+ -+ public static long getTrailingBit(final long n) { -+ return -n & n; -+ } -+ -+ public static int trailingZeros(final int n) { -+ return Integer.numberOfTrailingZeros(n); -+ } -+ -+ public static int trailingZeros(final long n) { -+ return Long.numberOfTrailingZeros(n); -+ } -+ -+ // from hacker's delight (signed division magic value) -+ public static int getDivisorMultiple(final long numbers) { -+ return (int)(numbers >>> 32); -+ } -+ -+ // from hacker's delight (signed division magic value) -+ public static int getDivisorShift(final long numbers) { -+ return (int)numbers; -+ } -+ -+ public static long getDivisorNumbers(final int d) { -+ final int ad = branchlessAbs(d); -+ -+ if (ad < 2) { -+ throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d); -+ } -+ -+ final int two31 = 0x80000000; -+ final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour -+ -+ /* -+ Signed usage: -+ int number; -+ long magic = getDivisorNumbers(div); -+ long mul = magic >>> 32; -+ int sign = number >> 31; -+ int result = (int)(((long)number * mul) >>> magic) - sign; -+ */ -+ /* -+ Unsigned usage: -+ int number; -+ long magic = getDivisorNumbers(div); -+ long mul = magic >>> 32; -+ int result = (int)(((long)number * mul) >>> magic); -+ */ -+ -+ int p = 31; -+ -+ // all these variables are UNSIGNED! -+ int t = two31 + (d >>> 31); -+ int anc = t - 1 - (int)((t & mask)%ad); -+ int q1 = (int)((two31 & mask)/(anc & mask)); -+ int r1 = two31 - q1*anc; -+ int q2 = (int)((two31 & mask)/(ad & mask)); -+ int r2 = two31 - q2*ad; -+ int delta; -+ -+ do { -+ p = p + 1; -+ q1 = 2*q1; // Update q1 = 2**p/|nc|. -+ r1 = 2*r1; // Update r1 = rem(2**p, |nc|). -+ if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) -+ q1 = q1 + 1; -+ r1 = r1 - anc; -+ } -+ q2 = 2*q2; // Update q2 = 2**p/|d|. -+ r2 = 2*r2; // Update r2 = rem(2**p, |d|). -+ if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) -+ q2 = q2 + 1; -+ r2 = r2 - ad; -+ } -+ delta = ad - r2; -+ } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); -+ -+ int magicNum = q2 + 1; -+ if (d < 0) { -+ magicNum = -magicNum; -+ } -+ int shift = p; -+ return ((long)magicNum << 32) | shift; -+ } -+ -+ public static int branchlessAbs(final int val) { -+ // -n = -1 ^ n + 1 -+ final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0 -+ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 -+ } -+ -+ public static long branchlessAbs(final long val) { -+ // -n = -1 ^ n + 1 -+ final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0 -+ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 -+ } -+ -+ //https://github.com/skeeto/hash-prospector for hash functions -+ -+ //score = ~590.47984224483832 -+ public static int hash0(int x) { -+ x *= 0x36935555; -+ x ^= x >>> 16; -+ return x; -+ } -+ -+ //score = ~310.01596637036749 -+ public static int hash1(int x) { -+ x ^= x >>> 15; -+ x *= 0x356aaaad; -+ x ^= x >>> 17; -+ return x; -+ } -+ -+ public static int hash2(int x) { -+ x ^= x >>> 16; -+ x *= 0x7feb352d; -+ x ^= x >>> 15; -+ x *= 0x846ca68b; -+ x ^= x >>> 16; -+ return x; -+ } -+ -+ public static int hash3(int x) { -+ x ^= x >>> 17; -+ x *= 0xed5ad4bb; -+ x ^= x >>> 11; -+ x *= 0xac4c1b51; -+ x ^= x >>> 15; -+ x *= 0x31848bab; -+ x ^= x >>> 14; -+ return x; -+ } -+ -+ //score = ~365.79959673201887 -+ public static long hash1(long x) { -+ x ^= x >>> 27; -+ x *= 0xb24924b71d2d354bL; -+ x ^= x >>> 28; -+ return x; -+ } -+ -+ //h2 hash -+ public static long hash2(long x) { -+ x ^= x >>> 32; -+ x *= 0xd6e8feb86659fd93L; -+ x ^= x >>> 32; -+ x *= 0xd6e8feb86659fd93L; -+ x ^= x >>> 32; -+ return x; -+ } -+ -+ public static long hash3(long x) { -+ x ^= x >>> 45; -+ x *= 0xc161abe5704b6c79L; -+ x ^= x >>> 41; -+ x *= 0xe3e5389aedbc90f7L; -+ x ^= x >>> 56; -+ x *= 0x1f9aba75a52db073L; -+ x ^= x >>> 53; -+ return x; -+ } -+ -+ private IntegerUtil() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..94705bb141b550589faa9a0408402d8636c61907 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java -@@ -0,0 +1,76 @@ -+package ca.spottedleaf.dataconverter.util; -+ -+import it.unimi.dsi.fastutil.longs.Long2IntFunction; -+import java.util.Arrays; -+ -+public class Long2IntArraySortedMap { -+ -+ protected long[] key; -+ protected int[] val; -+ protected int size; -+ -+ public Long2IntArraySortedMap() { -+ this.key = new long[8]; -+ this.val = new int[8]; -+ } -+ -+ public int put(final long key, final int value) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ final int current = this.val[index]; -+ this.val[index] = value; -+ return current; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ ++this.size; -+ -+ this.key[insert] = key; -+ this.val[insert] = value; -+ -+ return 0; -+ } -+ -+ public int computeIfAbsent(final long key, final Long2IntFunction producer) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ return this.val[index]; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ ++this.size; -+ -+ this.key[insert] = key; -+ -+ return this.val[insert] = producer.apply(key); -+ } -+ -+ public int get(final long key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ return 0; -+ } -+ return this.val[index]; -+ } -+ -+ public int getFloor(final long key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ final int insert = -(index + 1) - 1; -+ return insert < 0 ? 0 : this.val[insert]; -+ } -+ return this.val[index]; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6f634c8825589a23f46ad7b54354475c9a95bd1b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java -@@ -0,0 +1,76 @@ -+package ca.spottedleaf.dataconverter.util; -+ -+import java.util.Arrays; -+import java.util.function.LongFunction; -+ -+public class Long2ObjectArraySortedMap { -+ -+ protected long[] key; -+ protected V[] val; -+ protected int size; -+ -+ public Long2ObjectArraySortedMap() { -+ this.key = new long[8]; -+ this.val = (V[])new Object[8]; -+ } -+ -+ public V put(final long key, final V value) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ final V current = this.val[index]; -+ this.val[index] = value; -+ return current; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ ++this.size; -+ -+ this.key[insert] = key; -+ this.val[insert] = value; -+ -+ return null; -+ } -+ -+ public V computeIfAbsent(final long key, final LongFunction producer) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ return this.val[index]; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ ++this.size; -+ -+ this.key[insert] = key; -+ -+ return this.val[insert] = producer.apply(key); -+ } -+ -+ public V get(final long key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ return null; -+ } -+ return this.val[index]; -+ } -+ -+ public V getFloor(final long key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ final int insert = -(index + 1) - 1; -+ return insert < 0 ? null : this.val[insert]; -+ } -+ return this.val[index]; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java b/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5a6536377c9c1e1753e930ff2a6bb98ea57055c7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java -@@ -0,0 +1,39 @@ -+package ca.spottedleaf.dataconverter.util; -+ -+import ca.spottedleaf.dataconverter.types.MapType; -+import net.minecraft.resources.ResourceLocation; -+ -+public final class NamespaceUtil { -+ -+ private NamespaceUtil() {} -+ -+ public static void enforceForPath(final MapType data, final String path) { -+ if (data == null) { -+ return; -+ } -+ -+ final String id = data.getString(path); -+ if (id != null) { -+ final String replace = NamespaceUtil.correctNamespaceOrNull(id); -+ if (replace != null) { -+ data.setString(path, replace); -+ } -+ } -+ } -+ -+ public static String correctNamespace(final String value) { -+ if (value == null) { -+ return null; -+ } -+ final ResourceLocation resourceLocation = ResourceLocation.tryParse(value); -+ return resourceLocation != null ? resourceLocation.toString() : value; -+ } -+ -+ public static String correctNamespaceOrNull(final String value) { -+ if (value == null) { -+ return null; -+ } -+ final String correct = correctNamespace(value); -+ return correct.equals(value) ? null : correct; -+ } -+} -diff --git a/src/main/java/net/minecraft/data/structures/StructureUpdater.java b/src/main/java/net/minecraft/data/structures/StructureUpdater.java -index 2939fad9c86f358b317f815d6efff0f41f6a3ea8..3e4cd09fc37d72d22a0f966039d1e65b1d80cc84 100644 ---- a/src/main/java/net/minecraft/data/structures/StructureUpdater.java -+++ b/src/main/java/net/minecraft/data/structures/StructureUpdater.java -@@ -25,7 +25,7 @@ public class StructureUpdater implements SnbtToNbt.Filter { - LOGGER.warn("SNBT Too old, do not forget to update: {} < {}: {}", i, 3678, name); - } - -- CompoundTag compoundTag = DataFixTypes.STRUCTURE.updateToCurrentVersion(DataFixers.getDataFixer(), nbt, i); -+ CompoundTag compoundTag = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.STRUCTURE, nbt, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - structureTemplate.load(BuiltInRegistries.BLOCK.asLookup(), compoundTag); - return structureTemplate.save(new CompoundTag()); - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -index 6743dca44e6552ad39aca757a24f3c4df400d83d..eebaf98bc0fa4696af59b2a79563beb73501a554 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -79,7 +79,7 @@ public class ChunkStorage implements AutoCloseable { - int i = ChunkStorage.getVersion(nbttagcompound); - - // CraftBukkit start -- if (i < 1466) { -+ if (false && i < 1466) { // Paper - no longer needed, data converter system handles it now - CompoundTag level = nbttagcompound.getCompound("Level"); - if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) { - ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource(); -@@ -91,7 +91,7 @@ public class ChunkStorage implements AutoCloseable { - // CraftBukkit end - - if (i < 1493) { -- nbttagcompound = DataFixTypes.CHUNK.update(this.fixerUpper, nbttagcompound, i, 1493); -+ ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, i, 1493); // Paper - replace chunk converter - if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) { - LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier); - -@@ -109,7 +109,7 @@ public class ChunkStorage implements AutoCloseable { - // Spigot end - - ChunkStorage.injectDatafixingContext(nbttagcompound, resourcekey, optional); -- nbttagcompound = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbttagcompound, Math.max(1493, i)); -+ nbttagcompound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, Math.max(1493, i), SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace chunk converter - if (i < SharedConstants.getCurrentVersion().getDataVersion().getVersion()) { - NbtUtils.addCurrentDataVersion(nbttagcompound); - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -index d80580574a9e5d1c850270d93807f3a66a9c76f8..98b3909b536f11eda9c481ffd74066ad0cdb0ebc 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -@@ -115,7 +115,7 @@ public class EntityStorage implements EntityPersistentStorage { - - private CompoundTag upgradeChunkTag(CompoundTag chunkNbt) { - int i = NbtUtils.getDataVersion(chunkNbt, -1); -- return DataFixTypes.ENTITY_CHUNK.updateToCurrentVersion(this.fixerUpper, chunkNbt, i); -+ return ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY_CHUNK, chunkNbt, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - route to new converter system - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -index 28e0f782c24afb3d8d2296bd0d3493a32ef66961..56f0e217276b01aed2f20a71f6849826285fc15b 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -@@ -142,7 +142,14 @@ public class SectionStorage implements AutoCloseable { - int j = getVersion(dynamic); - int k = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - boolean bl = j != k; -- Dynamic dynamic2 = this.type.update(this.fixerUpper, dynamic, j, k); -+ // Paper start - route to new converter system -+ Dynamic dynamic2; -+ if (this.type == net.minecraft.util.datafix.DataFixTypes.POI_CHUNK) { -+ dynamic2 = new Dynamic<>(dynamic.getOps(), (T)ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.POI_CHUNK, (CompoundTag)dynamic.getValue(), j, k)); -+ } else { -+ dynamic2 = this.type.update(this.fixerUpper, dynamic, j, k); -+ } -+ // Paper end - route to new converter system - OptionalDynamic optionalDynamic = dynamic2.get("Sections"); - - for(int l = this.levelHeightAccessor.getMinSection(); l < this.levelHeightAccessor.getMaxSection(); ++l) { -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -index 15a9736a870055d639d03063c7cf67fd769fff36..1ca00340aaa201dd34e5c350d23ef53e126a0ca6 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -@@ -115,7 +115,7 @@ public class StructureCheck { - - CompoundTag compoundTag2; - try { -- compoundTag2 = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, compoundTag, i); -+ compoundTag2 = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, compoundTag, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace chunk converter - } catch (Exception var12) { - LOGGER.warn("Failed to partially datafix chunk {}", pos, var12); - return StructureCheckResult.CHUNK_LOAD_NEEDED; -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java -index e534dac9d69147174f6b9e8ce7f27fde536351ce..270fd52ec733c89bd91155c8222936fafbcf94d6 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java -@@ -236,7 +236,7 @@ public class StructureTemplateManager { - public StructureTemplate readStructure(CompoundTag nbt) { - StructureTemplate structureTemplate = new StructureTemplate(); - int i = NbtUtils.getDataVersion(nbt, 500); -- structureTemplate.load(this.blockLookup, DataFixTypes.STRUCTURE.updateToCurrentVersion(this.fixerUpper, nbt, i)); -+ structureTemplate.load(this.blockLookup, ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.STRUCTURE, nbt, i, SharedConstants.getCurrentVersion().getDataVersion().getVersion())); // Paper - return structureTemplate; - } - -diff --git a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java -index 836bcea1c6a9de29b4a248220331f3a8c697204d..399da9d43aefbb95897df4697860d5bce5317152 100644 ---- a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java -+++ b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java -@@ -290,10 +290,10 @@ public class LevelStorageSource { - static Dynamic readLevelDataTagFixed(Path path, DataFixer dataFixer) throws IOException { - CompoundTag nbttagcompound = LevelStorageSource.readLevelDataTagRaw(path); - CompoundTag nbttagcompound1 = nbttagcompound.getCompound("Data"); -- int i = NbtUtils.getDataVersion(nbttagcompound1, -1); -+ int i = NbtUtils.getDataVersion(nbttagcompound1, -1); final int version = i; // Paper - obfuscation helpers - Dynamic dynamic = DataFixTypes.LEVEL.updateToCurrentVersion(dataFixer, new Dynamic(NbtOps.INSTANCE, nbttagcompound1), i); - Dynamic dynamic1 = dynamic.get("Player").orElseEmptyMap(); -- Dynamic dynamic2 = DataFixTypes.PLAYER.updateToCurrentVersion(dataFixer, dynamic1, i); -+ Dynamic dynamic2 = LevelStorageSource.dank(dynamic1, version); // Paper - - dynamic = dynamic.set("Player", dynamic2); - Dynamic dynamic3 = dynamic.get("WorldGenSettings").orElseEmptyMap(); -@@ -303,6 +303,12 @@ public class LevelStorageSource { - return dynamic; - } - -+ // Paper start -+ private static Dynamic dank(final Dynamic input, final int version) { -+ return new Dynamic<>(input.getOps(), (T) ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, (CompoundTag)input.getValue(), version, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion())); -+ } -+ // Paper end -+ - private LevelSummary readLevelSummary(LevelStorageSource.LevelDirectory save, boolean locked) { - Path path = save.dataFile(); - -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 49d39980054bce470ddaceeb6ab7fab83bf8dc54..63e187c65cb855031f286aad0d25ac4694f7a331 100644 ---- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -+++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -@@ -97,7 +97,7 @@ public class PlayerDataStorage { - // CraftBukkit end - int i = NbtUtils.getDataVersion(nbttagcompound, -1); - -- nbttagcompound = DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, nbttagcompound, i); -+ nbttagcompound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, nbttagcompound, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace player converter - player.load(nbttagcompound); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index b9183a9a657c2cd320fca0f15db0dae6827546f1..4e1390b9244aeb745ffd3fd1257bc74248722515 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -500,8 +500,7 @@ public final class CraftMagicNumbers implements UnsafeValues { - - CompoundTag compound = deserializeNbtFromBytes(data); - final int dataVersion = compound.getInt("DataVersion"); -- compound = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue(); -- return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of(compound)); -+ return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of(ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ITEM_STACK, compound, dataVersion, this.getDataVersion()))); // Paper - rewrite dataconverter - } - - @Override -@@ -521,7 +520,7 @@ public final class CraftMagicNumbers implements UnsafeValues { - - CompoundTag compound = deserializeNbtFromBytes(data); - int dataVersion = compound.getInt("DataVersion"); -- compound = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ENTITY, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue(); -+ compound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY, compound, dataVersion, getDataVersion()); // Paper - rewrite dataconverter - if (!preserveUUID) { - // Generate a new UUID so we don't have to worry about deserializing the same entity twice - compound.remove("UUID"); diff --git a/patches/server/0989-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch b/patches/server/0989-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch new file mode 100644 index 000000000000..477ac26b8f7d --- /dev/null +++ b/patches/server/0989-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch @@ -0,0 +1,158 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Paul Sauve +Date: Sat, 31 Oct 2020 18:43:02 -0500 +Subject: [PATCH] Strip raytracing for EntityLiving#hasLineOfSight + +The BlockGetter#clip method is very wasteful in both allocations, +and in logic. While EntityLiving#hasLineOfSight provides static +parameters for collisions with blocks and fluids, the method still does +a lot of dynamic checks for both of these, which result in extra work. +As well, since the fluid collision option is set to NONE, the entire +fluid collision system is completely unneeded, yet used anyways. + +Copyright (C) 2020 Technove LLC + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index c0bfce7266bbdfe0c5a753367032eb333f56c182..b64d6cd9899ab91895faeb090c5afadbbc90f09e 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3738,7 +3738,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + Vec3 vec3d1 = new Vec3(entity.getX(), entity.getEyeY(), entity.getZ()); + + // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists +- return vec3d1.distanceToSqr(vec3d) > 128.0D * 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; // Paper - Perf: Use distance squared ++ return vec3d1.distanceToSqr(vec3d) > 128.0D * 128.0D ? false : this.level().clipDirect(vec3d, vec3d1, net.minecraft.world.phys.shapes.CollisionContext.of(this)) == HitResult.Type.MISS; // Paper - Perf: Use distance squared & strip raytracing + } + } + +diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java +index bb8e962e63c7a2d931f9bd7f7c002aa35cfa5fd3..0fa131a6c98adb498fc8d534e0e39647e80c6923 100644 +--- a/src/main/java/net/minecraft/world/level/BlockGetter.java ++++ b/src/main/java/net/minecraft/world/level/BlockGetter.java +@@ -68,6 +68,18 @@ public interface BlockGetter extends LevelHeightAccessor { + }); + } + ++ // Paper start - Broken down variant of the method below, used by Level#clipDirect ++ @Nullable ++ default BlockHitResult.Type clipDirect(Vec3 start, Vec3 end, BlockPos pos, BlockState state, net.minecraft.world.phys.shapes.CollisionContext collisionContext) { ++ if (state.isAir()) { ++ return null; ++ } ++ ++ final VoxelShape voxelshape = ClipContext.Block.COLLIDER.get(state, this, pos, collisionContext); ++ final BlockHitResult hitResult = this.clipWithInteractionOverride(start, end, pos, voxelshape, state); ++ return hitResult == null ? null : hitResult.getType(); ++ } ++ // Paper end + // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace + default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) { + // Paper start - Add predicate for blocks when raytracing +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 5f98f3e1bc76076278cbe63d5fbb8ec75b3bf04b..29b6494d17bc8b9926244c286e05c821a6297f7b 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -329,10 +329,87 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return null; + } + +- // Paper start ++ // Paper start - Broken down method of raytracing for EntityLiving#hasLineOfSight, replaces BlockGetter#clip(CollisionContext) + public net.minecraft.world.phys.BlockHitResult.Type clipDirect(Vec3 start, Vec3 end, net.minecraft.world.phys.shapes.CollisionContext context) { +- // To be patched over +- return this.clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, context)).getType(); ++ // most of this code comes from BlockGetter#clip(CollisionContext, BiFunction, Function), but removes the needless functions ++ if (start.equals(end)) { ++ return net.minecraft.world.phys.BlockHitResult.Type.MISS; ++ } ++ ++ final double endX = Mth.lerp(-1.0E-7D, end.x, start.x); ++ final double endY = Mth.lerp(-1.0E-7D, end.y, start.y); ++ final double endZ = Mth.lerp(-1.0E-7D, end.z, start.z); ++ ++ final double startX = Mth.lerp(-1.0E-7D, start.x, end.x); ++ final double startY = Mth.lerp(-1.0E-7D, start.y, end.y); ++ final double startZ = Mth.lerp(-1.0E-7D, start.z, end.z); ++ ++ int currentX = Mth.floor(startX); ++ int currentY = Mth.floor(startY); ++ int currentZ = Mth.floor(startZ); ++ ++ final BlockPos.MutableBlockPos currentBlock = new BlockPos.MutableBlockPos(currentX, currentY, currentZ); ++ ++ LevelChunk chunk = this.getChunkIfLoaded(currentBlock); ++ if (chunk == null) { ++ return net.minecraft.world.phys.BlockHitResult.Type.MISS; ++ } ++ ++ final net.minecraft.world.phys.BlockHitResult.Type initialCheck = this.clipDirect(start, end, currentBlock, chunk.getBlockState(currentBlock), context); ++ if (initialCheck != null) { ++ return initialCheck; ++ } ++ ++ final double diffX = endX - startX; ++ final double diffY = endY - startY; ++ final double diffZ = endZ - startZ; ++ ++ final int xDirection = Mth.sign(diffX); ++ final int yDirection = Mth.sign(diffY); ++ final int zDirection = Mth.sign(diffZ); ++ ++ final double normalizedX = xDirection == 0 ? Double.MAX_VALUE : (double) xDirection / diffX; ++ final double normalizedY = yDirection == 0 ? Double.MAX_VALUE : (double) yDirection / diffY; ++ final double normalizedZ = zDirection == 0 ? Double.MAX_VALUE : (double) zDirection / diffZ; ++ ++ double normalizedXDirection = normalizedX * (xDirection > 0 ? 1.0D - Mth.frac(startX) : Mth.frac(startX)); ++ double normalizedYDirection = normalizedY * (yDirection > 0 ? 1.0D - Mth.frac(startY) : Mth.frac(startY)); ++ double normalizedZDirection = normalizedZ * (zDirection > 0 ? 1.0D - Mth.frac(startZ) : Mth.frac(startZ)); ++ ++ net.minecraft.world.phys.BlockHitResult.Type result; ++ ++ do { ++ if (normalizedXDirection > 1.0D && normalizedYDirection > 1.0D && normalizedZDirection > 1.0D) { ++ return net.minecraft.world.phys.BlockHitResult.Type.MISS; ++ } ++ ++ if (normalizedXDirection < normalizedYDirection) { ++ if (normalizedXDirection < normalizedZDirection) { ++ currentX += xDirection; ++ normalizedXDirection += normalizedX; ++ } else { ++ currentZ += zDirection; ++ normalizedZDirection += normalizedZ; ++ } ++ } else if (normalizedYDirection < normalizedZDirection) { ++ currentY += yDirection; ++ normalizedYDirection += normalizedY; ++ } else { ++ currentZ += zDirection; ++ normalizedZDirection += normalizedZ; ++ } ++ ++ currentBlock.set(currentX, currentY, currentZ); ++ if (chunk.getPos().x != currentBlock.getX() >> 4 || chunk.getPos().z != currentBlock.getZ() >> 4) { ++ chunk = this.getChunkIfLoaded(currentBlock); ++ if (chunk == null) { ++ return net.minecraft.world.phys.BlockHitResult.Type.MISS; ++ } ++ } ++ result = this.clipDirect(start, end, currentBlock, chunk.getBlockState(currentBlock), context); ++ } while (result == null); ++ ++ return result; + } + // Paper end + diff --git a/patches/server/0990-Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/patches/server/0990-Optimize-Network-Manager-and-add-advanced-packet-sup.patch new file mode 100644 index 000000000000..6ab7cbf215fb --- /dev/null +++ b/patches/server/0990-Optimize-Network-Manager-and-add-advanced-packet-sup.patch @@ -0,0 +1,394 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 6 May 2020 04:53:35 -0400 +Subject: [PATCH] Optimize Network Manager and add advanced packet support + +Adds ability for 1 packet to bundle other packets to follow it +Adds ability for a packet to delay sending more packets until a state is ready. + +Removes synchronization from sending packets +Removes processing packet queue off of main thread + - for the few cases where it is allowed, order is not necessary nor + should it even be happening concurrently in first place (handshaking/login/status) + +Ensures packets sent asynchronously are dispatched on main thread + +This helps ensure safety for ProtocolLib as packet listeners +are commonly accessing world state. This will allow you to schedule +a packet to be sent async, but itll be dispatched sync for packet +listeners to process. + +This should solve some deadlock risks + +Also adds Netty Channel Flush Consolidation to reduce the amount of flushing + +Also avoids spamming closed channel exception by rechecking closed state in dispatch +and then catch exceptions and close if they fire. + +Part of this commit was authored by: Spottedleaf, sandtechnology + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 22a7f17180b76b6c3548d3b54ae8218a469401a8..7f2aa5e17fe675f3404d67b1794d2ca68b188eb9 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -84,7 +84,7 @@ public class Connection extends SimpleChannelInboundHandler> { + return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + private final PacketFlow receiving; +- private final Queue> pendingActions = Queues.newConcurrentLinkedQueue(); ++ private final Queue pendingActions = Queues.newConcurrentLinkedQueue(); + public Channel channel; + public SocketAddress address; + // Spigot Start +@@ -116,6 +116,10 @@ public class Connection extends SimpleChannelInboundHandler> { + public java.net.InetSocketAddress virtualHost; + private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush"); // Paper - Disable explicit network manager flushing + // Paper end ++ // Paper start - Optimize network ++ public boolean isPending = true; ++ public boolean queueImmunity; ++ // Paper end - Optimize network + + // Paper start - add utility methods + public final net.minecraft.server.level.ServerPlayer getPlayer() { +@@ -375,15 +379,39 @@ public class Connection extends SimpleChannelInboundHandler> { + } + + public void send(Packet packet, @Nullable PacketSendListener callbacks, boolean flush) { +- if (this.isConnected()) { +- this.flushQueue(); ++ // Paper start - Optimize network: Handle oversized packets better ++ final boolean connected = this.isConnected(); ++ if (!connected && !this.preparing) { ++ return; ++ } ++ ++ packet.onPacketDispatch(this.getPlayer()); ++ if (connected && (InnerUtil.canSendImmediate(this, packet) ++ || (io.papermc.paper.util.MCUtil.isMainThread() && packet.isReady() && this.pendingActions.isEmpty() ++ && (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty())))) { + this.sendPacket(packet, callbacks, flush); + } else { +- this.pendingActions.add((networkmanager) -> { +- networkmanager.sendPacket(packet, callbacks, flush); +- }); +- } ++ // Write the packets to the queue, then flush - antixray hooks there already ++ final java.util.List> extraPackets = InnerUtil.buildExtraPackets(packet); ++ final boolean hasExtraPackets = extraPackets != null && !extraPackets.isEmpty(); ++ if (!hasExtraPackets) { ++ this.pendingActions.add(new PacketSendAction(packet, callbacks, flush)); ++ } else { ++ final java.util.List actions = new java.util.ArrayList<>(1 + extraPackets.size()); ++ actions.add(new PacketSendAction(packet, null, false)); // Delay the future listener until the end of the extra packets + ++ for (int i = 0, len = extraPackets.size(); i < len;) { ++ final Packet extraPacket = extraPackets.get(i); ++ final boolean end = ++i == len; ++ actions.add(new PacketSendAction(extraPacket, end ? callbacks : null, end)); // Append listener to the end ++ } ++ ++ this.pendingActions.addAll(actions); ++ } ++ ++ this.flushQueue(); ++ // Paper end - Optimize network ++ } + } + + public void runOnceConnected(Consumer task) { +@@ -391,7 +419,7 @@ public class Connection extends SimpleChannelInboundHandler> { + this.flushQueue(); + task.accept(this); + } else { +- this.pendingActions.add(task); ++ this.pendingActions.add(new WrappedConsumer(task)); // Paper - Optimize network + } + + } +@@ -409,6 +437,14 @@ public class Connection extends SimpleChannelInboundHandler> { + } + + private void doSendPacket(Packet packet, @Nullable PacketSendListener callbacks, boolean flush) { ++ // Paper start - Optimize network ++ final net.minecraft.server.level.ServerPlayer player = this.getPlayer(); ++ if (!this.isConnected()) { ++ packet.onPacketDispatchFinish(player, null); ++ return; ++ } ++ try { ++ // Paper end - Optimize network + ChannelFuture channelfuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet); + + if (callbacks != null) { +@@ -428,14 +464,24 @@ public class Connection extends SimpleChannelInboundHandler> { + }); + } + ++ // Paper start - Optimize network ++ if (packet.hasFinishListener()) { ++ channelfuture.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture)); ++ } + channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); ++ } catch (final Exception e) { ++ LOGGER.error("NetworkException: {}", player, e); ++ this.disconnect(Component.translatable("disconnect.genericReason", "Internal Exception: " + e.getMessage())); ++ packet.onPacketDispatchFinish(player, null); ++ } ++ // Paper end - Optimize network + } + + public void flushChannel() { + if (this.isConnected()) { + this.flush(); + } else { +- this.pendingActions.add(Connection::flush); ++ this.pendingActions.add(new WrappedConsumer(Connection::flush)); // Paper - Optimize network + } + + } +@@ -468,20 +514,57 @@ public class Connection extends SimpleChannelInboundHandler> { + return attributekey; + } + +- private void flushQueue() { +- if (this.channel != null && this.channel.isOpen()) { +- Queue queue = this.pendingActions; +- ++ // Paper start - Optimize network: Rewrite this to be safer if ran off main thread ++ private boolean flushQueue() { ++ if (!this.isConnected()) { ++ return true; ++ } ++ if (io.papermc.paper.util.MCUtil.isMainThread()) { ++ return this.processQueue(); ++ } else if (this.isPending) { ++ // Should only happen during login/status stages + synchronized (this.pendingActions) { +- Consumer consumer; ++ return this.processQueue(); ++ } ++ } ++ return false; ++ } ++ ++ private boolean processQueue() { ++ if (this.pendingActions.isEmpty()) { ++ return true; ++ } + +- while ((consumer = (Consumer) this.pendingActions.poll()) != null) { +- consumer.accept(this); ++ // If we are on main, we are safe here in that nothing else should be processing queue off main anymore ++ // But if we are not on main due to login/status, the parent is synchronized on packetQueue ++ final java.util.Iterator iterator = this.pendingActions.iterator(); ++ while (iterator.hasNext()) { ++ final WrappedConsumer queued = iterator.next(); // poll -> peek ++ ++ // Fix NPE (Spigot bug caused by handleDisconnection()) ++ if (queued == null) { ++ return true; ++ } ++ ++ if (queued.isConsumed()) { ++ continue; ++ } ++ ++ if (queued instanceof PacketSendAction packetSendAction) { ++ final Packet packet = packetSendAction.packet; ++ if (!packet.isReady()) { ++ return false; + } ++ } + ++ iterator.remove(); ++ if (queued.tryMarkConsumed()) { ++ queued.accept(this); + } + } ++ return true; + } ++ // Paper end - Optimize network + + private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world + private static int joinAttemptsThisTick; // Paper - Buffer joins to world +@@ -544,6 +627,7 @@ public class Connection extends SimpleChannelInboundHandler> { + public void disconnect(Component disconnectReason) { + // Spigot Start + this.preparing = false; ++ this.clearPacketQueue(); // Paper - Optimize network + // Spigot End + if (this.channel == null) { + this.delayedDisconnect = disconnectReason; +@@ -715,7 +799,7 @@ public class Connection extends SimpleChannelInboundHandler> { + public void handleDisconnection() { + if (this.channel != null && !this.channel.isOpen()) { + if (this.disconnectionHandled) { +- Connection.LOGGER.warn("handleDisconnection() called twice"); ++ // Connection.LOGGER.warn("handleDisconnection() called twice"); // Paper - Don't log useless message + } else { + this.disconnectionHandled = true; + PacketListener packetlistener = this.getPacketListener(); +@@ -728,7 +812,7 @@ public class Connection extends SimpleChannelInboundHandler> { + + packetlistener1.onDisconnect(ichatbasecomponent); + } +- this.pendingActions.clear(); // Free up packet queue. ++ this.clearPacketQueue(); // Paper - Optimize network + // Paper start - Add PlayerConnectionCloseEvent + final PacketListener packetListener = this.getPacketListener(); + if (packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) { +@@ -765,4 +849,93 @@ public class Connection extends SimpleChannelInboundHandler> { + public void setBandwidthLogger(SampleLogger log) { + this.bandwidthDebugMonitor = new BandwidthDebugMonitor(log); + } ++ ++ // Paper start - Optimize network ++ public void clearPacketQueue() { ++ final net.minecraft.server.level.ServerPlayer player = getPlayer(); ++ for (final Consumer queuedAction : this.pendingActions) { ++ if (queuedAction instanceof PacketSendAction packetSendAction) { ++ final Packet packet = packetSendAction.packet; ++ if (packet.hasFinishListener()) { ++ packet.onPacketDispatchFinish(player, null); ++ } ++ } ++ } ++ this.pendingActions.clear(); ++ } ++ ++ private static class InnerUtil { // Attempt to hide these methods from ProtocolLib, so it doesn't accidently pick them up. ++ ++ @Nullable ++ private static java.util.List> buildExtraPackets(final Packet packet) { ++ final java.util.List> extra = packet.getExtraPackets(); ++ if (extra == null || extra.isEmpty()) { ++ return null; ++ } ++ ++ final java.util.List> ret = new java.util.ArrayList<>(1 + extra.size()); ++ buildExtraPackets0(extra, ret); ++ return ret; ++ } ++ ++ private static void buildExtraPackets0(final java.util.List> extraPackets, final java.util.List> into) { ++ for (final Packet extra : extraPackets) { ++ into.add(extra); ++ final java.util.List> extraExtra = extra.getExtraPackets(); ++ if (extraExtra != null && !extraExtra.isEmpty()) { ++ buildExtraPackets0(extraExtra, into); ++ } ++ } ++ } ++ ++ private static boolean canSendImmediate(final Connection networkManager, final net.minecraft.network.protocol.Packet packet) { ++ return networkManager.isPending || networkManager.packetListener.protocol() != ConnectionProtocol.PLAY || ++ packet instanceof net.minecraft.network.protocol.common.ClientboundKeepAlivePacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundSystemChatPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundClearTitlesPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundSoundPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundSoundEntityPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundStopSoundPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket || ++ packet instanceof net.minecraft.network.protocol.game.ClientboundBossEventPacket; ++ } ++ } ++ ++ private static class WrappedConsumer implements Consumer { ++ private final Consumer delegate; ++ private final java.util.concurrent.atomic.AtomicBoolean consumed = new java.util.concurrent.atomic.AtomicBoolean(false); ++ ++ private WrappedConsumer(final Consumer delegate) { ++ this.delegate = delegate; ++ } ++ ++ @Override ++ public void accept(final Connection connection) { ++ this.delegate.accept(connection); ++ } ++ ++ public boolean tryMarkConsumed() { ++ return consumed.compareAndSet(false, true); ++ } ++ ++ public boolean isConsumed() { ++ return consumed.get(); ++ } ++ } ++ ++ private static final class PacketSendAction extends WrappedConsumer { ++ private final Packet packet; ++ ++ private PacketSendAction(final Packet packet, @Nullable final PacketSendListener packetSendListener, final boolean flush) { ++ super(connection -> connection.sendPacket(packet, packetSendListener, flush)); ++ this.packet = packet; ++ } ++ } ++ // Paper end - Optimize network + } +diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java +index cc658a61065d5c0021a4b88fa58b40211b94f8ec..da11266a0a23f446196e6facf2c358cfcc18070f 100644 +--- a/src/main/java/net/minecraft/network/protocol/Packet.java ++++ b/src/main/java/net/minecraft/network/protocol/Packet.java +@@ -11,6 +11,30 @@ public interface Packet { + void handle(T listener); + + // Paper start ++ /** ++ * @param player Null if not at PLAY stage yet ++ */ ++ default void onPacketDispatch(@Nullable net.minecraft.server.level.ServerPlayer player) { ++ } ++ ++ /** ++ * @param player Null if not at PLAY stage yet ++ * @param future Can be null if packet was cancelled ++ */ ++ default void onPacketDispatchFinish(@Nullable net.minecraft.server.level.ServerPlayer player, @Nullable io.netty.channel.ChannelFuture future) {} ++ ++ default boolean hasFinishListener() { ++ return false; ++ } ++ ++ default boolean isReady() { ++ return true; ++ } ++ ++ @Nullable ++ default java.util.List> getExtraPackets() { ++ return null; ++ } + default boolean packetTooLarge(net.minecraft.network.Connection manager) { + return false; + } +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index 4f330a44c77a7ec3237a86fda04921a8c4a1c00f..a4a29a7ea0035ecf4c61ee8547a9eb24acb667d0 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -63,10 +63,12 @@ public class ServerConnectionListener { + final List connections = Collections.synchronizedList(Lists.newArrayList()); + // Paper start - prevent blocking on adding a new connection while the server is ticking + private final java.util.Queue pending = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ private static final boolean disableFlushConsolidation = Boolean.getBoolean("Paper.disableFlushConsolidate"); // Paper - Optimize network + private final void addPending() { + Connection connection; + while ((connection = pending.poll()) != null) { + connections.add(connection); ++ connection.isPending = false; // Paper - Optimize network + } + } + // Paper end - prevent blocking on adding a new connection while the server is ticking +@@ -114,6 +116,7 @@ public class ServerConnectionListener { + ; + } + ++ if (!disableFlushConsolidation) channel.pipeline().addFirst(new io.netty.handler.flush.FlushConsolidationHandler()); // Paper - Optimize network + ChannelPipeline channelpipeline = channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30)).addLast("legacy_query", new LegacyQueryHandler(ServerConnectionListener.this.getServer())); + + Connection.configureSerialization(channelpipeline, PacketFlow.SERVERBOUND, (BandwidthDebugMonitor) null); diff --git a/patches/server/0990-Starlight.patch b/patches/server/0990-Starlight.patch deleted file mode 100644 index f3a5d03f696d..000000000000 --- a/patches/server/0990-Starlight.patch +++ /dev/null @@ -1,5422 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 28 Oct 2020 16:51:55 -0700 -Subject: [PATCH] Starlight - -See https://github.com/PaperMC/Starlight - -== AT == -public net.minecraft.server.level.ChunkHolder broadcast(Lnet/minecraft/network/protocol/Packet;Z)V - -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java -new file mode 100644 -index 0000000000000000000000000000000000000000..09ddbbfdf656aa347830941abd7c994fac05d1c5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java -@@ -0,0 +1,279 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.ImposterProtoChunk; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.level.chunk.PalettedContainer; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.ArrayList; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Set; -+import java.util.stream.Collectors; -+ -+public final class BlockStarLightEngine extends StarLightEngine { -+ -+ public BlockStarLightEngine(final Level world) { -+ super(false, world); -+ } -+ -+ @Override -+ protected boolean[] getEmptinessMap(final ChunkAccess chunk) { -+ return chunk.getBlockEmptinessMap(); -+ } -+ -+ @Override -+ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) { -+ chunk.setBlockEmptinessMap(to); -+ } -+ -+ @Override -+ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) { -+ return chunk.getBlockNibbles(); -+ } -+ -+ @Override -+ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) { -+ chunk.setBlockNibbles(to); -+ } -+ -+ @Override -+ protected boolean canUseChunk(final ChunkAccess chunk) { -+ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect()); -+ } -+ -+ @Override -+ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble != null) { -+ // de-initialisation is not as straightforward as with sky data, since deinit of block light is typically -+ // because a block was removed - which can decrease light. with sky data, block breaking can only result -+ // in increases, and thus the existing sky block check will actually correctly propagate light through -+ // a null section. so in order to propagate decreases correctly, we can do a couple of things: not remove -+ // the data section, or do edge checks on ALL axis (x, y, z). however I do not want edge checks running -+ // for clients at all, as they are expensive. so we don't remove the section, but to maintain the appearence -+ // of vanilla data management we "hide" them. -+ nibble.setHidden(); -+ } -+ } -+ -+ @Override -+ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { -+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { -+ return; -+ } -+ -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble == null) { -+ if (!initRemovedNibbles) { -+ throw new IllegalStateException(); -+ } else { -+ this.setNibbleInCache(chunkX, chunkY, chunkZ, new SWMRNibbleArray()); -+ } -+ } else { -+ nibble.setNonNull(); -+ } -+ } -+ -+ @Override -+ protected final void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) { -+ // blocks can change opacity -+ // blocks can change emitted light -+ // blocks can change direction of propagation -+ -+ final int encodeOffset = this.coordinateOffset; -+ final int emittedMask = this.emittedLightMask; -+ -+ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); -+ final BlockState blockState = this.getBlockState(worldX, worldY, worldZ); -+ final int emittedLevel = blockState.getLightEmission() & emittedMask; -+ -+ this.setLightLevel(worldX, worldY, worldZ, emittedLevel); -+ // this accounts for change in emitted light that would cause an increase -+ if (emittedLevel != 0) { -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (emittedLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) -+ ); -+ } -+ // this also accounts for a change in emitted light that would cause a decrease -+ // this also accounts for the change of direction of propagation (i.e old block was full transparent, new block is full opaque or vice versa) -+ // as it checks all neighbours (even if current level is 0) -+ this.appendToDecreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (currentLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ // always keep sided transparent false here, new block might be conditionally transparent which would -+ // prevent us from decreasing sources in the directions where the new block is opaque -+ // if it turns out we were wrong to de-propagate the source, the re-propagate logic WILL always -+ // catch that and fix it. -+ ); -+ // re-propagating neighbours (done by the decrease queue) will also account for opacity changes in this block -+ } -+ -+ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); -+ -+ @Override -+ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, -+ final int expect) { -+ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); -+ int level = centerState.getLightEmission() & 0xF; -+ -+ if (level >= (15 - 1) || level > expect) { -+ return level; -+ } -+ -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ final BlockState conditionallyOpaqueState; -+ int opacity = centerState.getOpacityIfCached(); -+ -+ if (opacity == -1) { -+ this.recalcCenterPos.set(worldX, worldY, worldZ); -+ opacity = centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos); -+ if (centerState.isConditionallyFullOpaque()) { -+ conditionallyOpaqueState = centerState; -+ } else { -+ conditionallyOpaqueState = null; -+ } -+ } else if (opacity >= 15) { -+ return level; -+ } else { -+ conditionallyOpaqueState = null; -+ } -+ opacity = Math.max(1, opacity); -+ -+ for (final AxisDirection direction : AXIS_DIRECTIONS) { -+ final int offX = worldX + direction.x; -+ final int offY = worldY + direction.y; -+ final int offZ = worldZ + direction.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ -+ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); -+ -+ if ((neighbourLevel - 1) <= level) { -+ // don't need to test transparency, we know it wont affect the result. -+ continue; -+ } -+ -+ final BlockState neighbourState = this.getBlockState(offX, offY, offZ); -+ if (neighbourState.isConditionallyFullOpaque()) { -+ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that -+ // we don't read the blockstate because most of the time this is false, so using the faster -+ // known transparency lookup results in a net win -+ this.recalcNeighbourPos.set(offX, offY, offZ); -+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); -+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); -+ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { -+ // not allowed to propagate -+ continue; -+ } -+ } -+ -+ // passed transparency, -+ -+ final int calculated = neighbourLevel - opacity; -+ level = Math.max(calculated, level); -+ if (level > expect) { -+ return level; -+ } -+ } -+ -+ return level; -+ } -+ -+ @Override -+ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions) { -+ for (final BlockPos pos : positions) { -+ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ protected List getSources(final LightChunkGetter lightAccess, final ChunkAccess chunk) { -+ final List sources = new ArrayList<>(); -+ -+ final int offX = chunk.getPos().x << 4; -+ final int offZ = chunk.getPos().z << 4; -+ -+ final LevelChunkSection[] sections = chunk.getSections(); -+ for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) { -+ final LevelChunkSection section = sections[sectionY - this.minSection]; -+ if (section == null || section.hasOnlyAir()) { -+ // no sources in empty sections -+ continue; -+ } -+ if (!section.maybeHas((final BlockState state) -> { -+ return state.getLightEmission() > 0; -+ })) { -+ // no light sources in palette -+ continue; -+ } -+ final PalettedContainer states = section.states; -+ final int offY = sectionY << 4; -+ -+ for (int index = 0; index < (16 * 16 * 16); ++index) { -+ final BlockState state = states.get(index); -+ if (state.getLightEmission() <= 0) { -+ continue; -+ } -+ -+ // index = x | (z << 4) | (y << 8) -+ sources.add(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15))); -+ } -+ } -+ -+ return sources; -+ } -+ -+ @Override -+ public void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { -+ // setup sources -+ final int emittedMask = this.emittedLightMask; -+ final List positions = this.getSources(lightAccess, chunk); -+ for (int i = 0, len = positions.size(); i < len; ++i) { -+ final BlockPos pos = positions.get(i); -+ final BlockState blockState = this.getBlockState(pos.getX(), pos.getY(), pos.getZ()); -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ -+ if (emittedLight <= this.getLightLevel(pos.getX(), pos.getY(), pos.getZ())) { -+ // some other source is brighter -+ continue; -+ } -+ -+ this.appendToIncreaseQueue( -+ ((pos.getX() + (pos.getZ() << 6) + (pos.getY() << (6 + 6)) + this.coordinateOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (emittedLight & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) -+ ); -+ -+ -+ // propagation wont set this for us -+ this.setLightLevel(pos.getX(), pos.getY(), pos.getZ(), emittedLight); -+ } -+ -+ if (needsEdgeChecks) { -+ // not required to propagate here, but this will reduce the hit of the edge checks -+ this.performLightIncrease(lightAccess); -+ -+ // verify neighbour edges -+ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); -+ } else { -+ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, this.maxLightSection); -+ -+ this.performLightIncrease(lightAccess); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4ffb4ffe01c4628d52742c5c0bbd35220eea6294 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java -@@ -0,0 +1,440 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import net.minecraft.world.level.chunk.DataLayer; -+import java.util.ArrayDeque; -+import java.util.Arrays; -+ -+// SWMR -> Single Writer Multi Reader Nibble Array -+public final class SWMRNibbleArray { -+ -+ /* -+ * Null nibble - nibble does not exist, and should not be written to. Just like vanilla - null -+ * nibbles are always 0 - and they are never written to directly. Only initialised/uninitialised -+ * nibbles can be written to. -+ * -+ * Uninitialised nibble - They are all 0, but the backing array isn't initialised. -+ * -+ * Initialised nibble - Has light data. -+ */ -+ -+ protected static final int INIT_STATE_NULL = 0; // null -+ protected static final int INIT_STATE_UNINIT = 1; // uninitialised -+ protected static final int INIT_STATE_INIT = 2; // initialised -+ protected static final int INIT_STATE_HIDDEN = 3; // initialised, but conversion to Vanilla data should be treated as if NULL -+ -+ public static final int ARRAY_SIZE = 16 * 16 * 16 / (8/4); // blocks / bytes per block -+ // this allows us to maintain only 1 byte array when we're not updating -+ static final ThreadLocal> WORKING_BYTES_POOL = ThreadLocal.withInitial(ArrayDeque::new); -+ -+ private static byte[] allocateBytes() { -+ final byte[] inPool = WORKING_BYTES_POOL.get().pollFirst(); -+ if (inPool != null) { -+ return inPool; -+ } -+ -+ return new byte[ARRAY_SIZE]; -+ } -+ -+ private static void freeBytes(final byte[] bytes) { -+ WORKING_BYTES_POOL.get().addFirst(bytes); -+ } -+ -+ public static SWMRNibbleArray fromVanilla(final DataLayer nibble) { -+ if (nibble == null) { -+ return new SWMRNibbleArray(null, true); -+ } else if (nibble.isEmpty()) { -+ return new SWMRNibbleArray(); -+ } else { -+ return new SWMRNibbleArray(nibble.getData().clone()); // make sure we don't write to the parameter later -+ } -+ } -+ -+ protected int stateUpdating; -+ protected volatile int stateVisible; -+ -+ protected byte[] storageUpdating; -+ protected boolean updatingDirty; // only returns whether storageUpdating is dirty -+ protected volatile byte[] storageVisible; -+ -+ public SWMRNibbleArray() { -+ this(null, false); // lazy init -+ } -+ -+ public SWMRNibbleArray(final byte[] bytes) { -+ this(bytes, false); -+ } -+ -+ public SWMRNibbleArray(final byte[] bytes, final boolean isNullNibble) { -+ if (bytes != null && bytes.length != ARRAY_SIZE) { -+ throw new IllegalArgumentException("Data of wrong length: " + bytes.length); -+ } -+ this.stateVisible = this.stateUpdating = bytes == null ? (isNullNibble ? INIT_STATE_NULL : INIT_STATE_UNINIT) : INIT_STATE_INIT; -+ this.storageUpdating = this.storageVisible = bytes; -+ } -+ -+ public SWMRNibbleArray(final byte[] bytes, final int state) { -+ if (bytes != null && bytes.length != ARRAY_SIZE) { -+ throw new IllegalArgumentException("Data of wrong length: " + bytes.length); -+ } -+ if (bytes == null && (state == INIT_STATE_INIT || state == INIT_STATE_HIDDEN)) { -+ throw new IllegalArgumentException("Data cannot be null and have state be initialised"); -+ } -+ this.stateUpdating = this.stateVisible = state; -+ this.storageUpdating = this.storageVisible = bytes; -+ } -+ -+ @Override -+ public String toString() { -+ StringBuilder stringBuilder = new StringBuilder(); -+ stringBuilder.append("State: "); -+ switch (this.stateVisible) { -+ case INIT_STATE_NULL: -+ stringBuilder.append("null"); -+ break; -+ case INIT_STATE_UNINIT: -+ stringBuilder.append("uninitialised"); -+ break; -+ case INIT_STATE_INIT: -+ stringBuilder.append("initialised"); -+ break; -+ case INIT_STATE_HIDDEN: -+ stringBuilder.append("hidden"); -+ break; -+ default: -+ stringBuilder.append("unknown"); -+ break; -+ } -+ stringBuilder.append("\nData:\n"); -+ -+ final byte[] data = this.storageVisible; -+ if (data != null) { -+ for (int i = 0; i < 4096; ++i) { -+ // Copied from NibbleArray#toString -+ final int level = ((data[i >>> 1] >>> ((i & 1) << 2)) & 0xF); -+ -+ stringBuilder.append(Integer.toHexString(level)); -+ if ((i & 15) == 15) { -+ stringBuilder.append("\n"); -+ } -+ -+ if ((i & 255) == 255) { -+ stringBuilder.append("\n"); -+ } -+ } -+ } else { -+ stringBuilder.append("null"); -+ } -+ -+ return stringBuilder.toString(); -+ } -+ -+ public SaveState getSaveState() { -+ synchronized (this) { -+ final int state = this.stateVisible; -+ final byte[] data = this.storageVisible; -+ if (state == INIT_STATE_NULL) { -+ return null; -+ } -+ if (state == INIT_STATE_UNINIT) { -+ return new SaveState(null, state); -+ } -+ final boolean zero = isAllZero(data); -+ if (zero) { -+ return state == INIT_STATE_INIT ? new SaveState(null, INIT_STATE_UNINIT) : null; -+ } else { -+ return new SaveState(data.clone(), state); -+ } -+ } -+ } -+ -+ protected static boolean isAllZero(final byte[] data) { -+ for (int i = 0; i < (ARRAY_SIZE >>> 4); ++i) { -+ byte whole = data[i << 4]; -+ -+ for (int k = 1; k < (1 << 4); ++k) { -+ whole |= data[(i << 4) | k]; -+ } -+ -+ if (whole != 0) { -+ return false; -+ } -+ } -+ -+ return true; -+ } -+ -+ // operation type: updating on src, updating on other -+ public void extrudeLower(final SWMRNibbleArray other) { -+ if (other.stateUpdating == INIT_STATE_NULL) { -+ throw new IllegalArgumentException(); -+ } -+ -+ if (other.storageUpdating == null) { -+ this.setUninitialised(); -+ return; -+ } -+ -+ final byte[] src = other.storageUpdating; -+ final byte[] into; -+ -+ if (!this.updatingDirty) { -+ if (this.storageUpdating != null) { -+ into = this.storageUpdating = allocateBytes(); -+ } else { -+ this.storageUpdating = into = allocateBytes(); -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ this.updatingDirty = true; -+ } else { -+ into = this.storageUpdating; -+ } -+ -+ final int start = 0; -+ final int end = (15 | (15 << 4)) >>> 1; -+ -+ /* x | (z << 4) | (y << 8) */ -+ for (int y = 0; y <= 15; ++y) { -+ System.arraycopy(src, start, into, y << (8 - 1), end - start + 1); -+ } -+ } -+ -+ // operation type: updating -+ public void setFull() { -+ if (this.stateUpdating != INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)-1); -+ this.updatingDirty = true; -+ } -+ -+ // operation type: updating -+ public void setZero() { -+ if (this.stateUpdating != INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)0); -+ this.updatingDirty = true; -+ } -+ -+ // operation type: updating -+ public void setNonNull() { -+ if (this.stateUpdating == INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ return; -+ } -+ if (this.stateUpdating != INIT_STATE_NULL) { -+ return; -+ } -+ this.stateUpdating = INIT_STATE_UNINIT; -+ } -+ -+ // operation type: updating -+ public void setNull() { -+ this.stateUpdating = INIT_STATE_NULL; -+ if (this.updatingDirty && this.storageUpdating != null) { -+ freeBytes(this.storageUpdating); -+ } -+ this.storageUpdating = null; -+ this.updatingDirty = false; -+ } -+ -+ // operation type: updating -+ public void setUninitialised() { -+ this.stateUpdating = INIT_STATE_UNINIT; -+ if (this.storageUpdating != null && this.updatingDirty) { -+ freeBytes(this.storageUpdating); -+ } -+ this.storageUpdating = null; -+ this.updatingDirty = false; -+ } -+ -+ // operation type: updating -+ public void setHidden() { -+ if (this.stateUpdating == INIT_STATE_HIDDEN) { -+ return; -+ } -+ if (this.stateUpdating != INIT_STATE_INIT) { -+ this.setNull(); -+ } else { -+ this.stateUpdating = INIT_STATE_HIDDEN; -+ } -+ } -+ -+ // operation type: updating -+ public boolean isDirty() { -+ return this.stateUpdating != this.stateVisible || this.updatingDirty; -+ } -+ -+ // operation type: updating -+ public boolean isNullNibbleUpdating() { -+ return this.stateUpdating == INIT_STATE_NULL; -+ } -+ -+ // operation type: visible -+ public boolean isNullNibbleVisible() { -+ return this.stateVisible == INIT_STATE_NULL; -+ } -+ -+ // opeartion type: updating -+ public boolean isUninitialisedUpdating() { -+ return this.stateUpdating == INIT_STATE_UNINIT; -+ } -+ -+ // operation type: visible -+ public boolean isUninitialisedVisible() { -+ return this.stateVisible == INIT_STATE_UNINIT; -+ } -+ -+ // operation type: updating -+ public boolean isInitialisedUpdating() { -+ return this.stateUpdating == INIT_STATE_INIT; -+ } -+ -+ // operation type: visible -+ public boolean isInitialisedVisible() { -+ return this.stateVisible == INIT_STATE_INIT; -+ } -+ -+ // operation type: updating -+ public boolean isHiddenUpdating() { -+ return this.stateUpdating == INIT_STATE_HIDDEN; -+ } -+ -+ // operation type: updating -+ public boolean isHiddenVisible() { -+ return this.stateVisible == INIT_STATE_HIDDEN; -+ } -+ -+ // operation type: updating -+ protected void swapUpdatingAndMarkDirty() { -+ if (this.updatingDirty) { -+ return; -+ } -+ -+ if (this.storageUpdating == null) { -+ this.storageUpdating = allocateBytes(); -+ Arrays.fill(this.storageUpdating, (byte)0); -+ } else { -+ System.arraycopy(this.storageUpdating, 0, this.storageUpdating = allocateBytes(), 0, ARRAY_SIZE); -+ } -+ -+ if (this.stateUpdating != INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ this.updatingDirty = true; -+ } -+ -+ // operation type: updating -+ public boolean updateVisible() { -+ if (!this.isDirty()) { -+ return false; -+ } -+ -+ synchronized (this) { -+ if (this.stateUpdating == INIT_STATE_NULL || this.stateUpdating == INIT_STATE_UNINIT) { -+ this.storageVisible = null; -+ } else { -+ if (this.storageVisible == null) { -+ this.storageVisible = this.storageUpdating.clone(); -+ } else { -+ if (this.storageUpdating != this.storageVisible) { -+ System.arraycopy(this.storageUpdating, 0, this.storageVisible, 0, ARRAY_SIZE); -+ } -+ } -+ -+ if (this.storageUpdating != this.storageVisible) { -+ freeBytes(this.storageUpdating); -+ } -+ this.storageUpdating = this.storageVisible; -+ } -+ this.updatingDirty = false; -+ this.stateVisible = this.stateUpdating; -+ } -+ -+ return true; -+ } -+ -+ // operation type: visible -+ public DataLayer toVanillaNibble() { -+ synchronized (this) { -+ switch (this.stateVisible) { -+ case INIT_STATE_HIDDEN: -+ case INIT_STATE_NULL: -+ return null; -+ case INIT_STATE_UNINIT: -+ return new DataLayer(); -+ case INIT_STATE_INIT: -+ return new DataLayer(this.storageVisible.clone()); -+ default: -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ -+ /* x | (z << 4) | (y << 8) */ -+ -+ // operation type: updating -+ public int getUpdating(final int x, final int y, final int z) { -+ return this.getUpdating((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); -+ } -+ -+ // operation type: updating -+ public int getUpdating(final int index) { -+ // indices range from 0 -> 4096 -+ final byte[] bytes = this.storageUpdating; -+ if (bytes == null) { -+ return 0; -+ } -+ final byte value = bytes[index >>> 1]; -+ -+ // if we are an even index, we want lower 4 bits -+ // if we are an odd index, we want upper 4 bits -+ return ((value >>> ((index & 1) << 2)) & 0xF); -+ } -+ -+ // operation type: visible -+ public int getVisible(final int x, final int y, final int z) { -+ return this.getVisible((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); -+ } -+ -+ // operation type: visible -+ public int getVisible(final int index) { -+ // indices range from 0 -> 4096 -+ final byte[] visibleBytes = this.storageVisible; -+ if (visibleBytes == null) { -+ return 0; -+ } -+ final byte value = visibleBytes[index >>> 1]; -+ -+ // if we are an even index, we want lower 4 bits -+ // if we are an odd index, we want upper 4 bits -+ return ((value >>> ((index & 1) << 2)) & 0xF); -+ } -+ -+ // operation type: updating -+ public void set(final int x, final int y, final int z, final int value) { -+ this.set((x & 15) | ((z & 15) << 4) | ((y & 15) << 8), value); -+ } -+ -+ // operation type: updating -+ public void set(final int index, final int value) { -+ if (!this.updatingDirty) { -+ this.swapUpdatingAndMarkDirty(); -+ } -+ final int shift = (index & 1) << 2; -+ final int i = index >>> 1; -+ -+ this.storageUpdating[i] = (byte)((this.storageUpdating[i] & (0xF0 >>> shift)) | (value << shift)); -+ } -+ -+ public static final class SaveState { -+ -+ public final byte[] data; -+ public final int state; -+ -+ public SaveState(final byte[] data, final int state) { -+ this.data = data; -+ this.state = state; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5f771962afb44175d446f138c8e7453230f48c6c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java -@@ -0,0 +1,709 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import ca.spottedleaf.starlight.common.util.WorldUtil; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortIterator; -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.BlockGetter; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.Arrays; -+import java.util.Set; -+ -+public final class SkyStarLightEngine extends StarLightEngine { -+ -+ /* -+ Specification for managing the initialisation and de-initialisation of skylight nibble arrays: -+ -+ Skylight nibble initialisation requires that non-empty chunk sections have 1 radius nibbles non-null. -+ -+ This presents some problems, as vanilla is only guaranteed to have 0 radius neighbours loaded when editing blocks. -+ However starlight fixes this so that it has 1 radius loaded. Still, we don't actually have guarantees -+ that we have the necessary chunks loaded to de-initialise neighbour sections (but we do have enough to de-initialise -+ our own) - we need a radius of 2 to de-initialise neighbour nibbles. -+ How do we solve this? -+ -+ Each chunk will store the last known "emptiness" of sections for each of their 1 radius neighbour chunk sections. -+ If the chunk does not have full data, then its nibbles are NOT de-initialised. This is because obviously the -+ chunk did not go through the light stage yet - or its neighbours are not lit. In either case, once the last -+ known "emptiness" of neighbouring sections is filled with data, the chunk will run a full check of the data -+ to see if any of its nibbles need to be de-initialised. -+ -+ The emptiness map allows us to de-initialise neighbour nibbles if the neighbour has it filled with data, -+ and if it doesn't have data then we know it will correctly de-initialise once it fills up. -+ -+ Unlike vanilla, we store whether nibbles are uninitialised on disk - so we don't need any dumb hacking -+ around those. -+ */ -+ -+ protected final int[] heightMapBlockChange = new int[16 * 16]; -+ { -+ Arrays.fill(this.heightMapBlockChange, Integer.MIN_VALUE); // clear heightmap -+ } -+ -+ protected final boolean[] nullPropagationCheckCache; -+ -+ public SkyStarLightEngine(final Level world) { -+ super(true, world); -+ this.nullPropagationCheckCache = new boolean[WorldUtil.getTotalLightSections(world)]; -+ } -+ -+ @Override -+ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { -+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { -+ return; -+ } -+ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble == null) { -+ if (!initRemovedNibbles) { -+ throw new IllegalStateException(); -+ } else { -+ this.setNibbleInCache(chunkX, chunkY, chunkZ, nibble = new SWMRNibbleArray(null, true)); -+ } -+ } -+ this.initNibble(nibble, chunkX, chunkY, chunkZ, extrude); -+ } -+ -+ @Override -+ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble != null) { -+ nibble.setNull(); -+ } -+ } -+ -+ protected final void initNibble(final SWMRNibbleArray currNibble, final int chunkX, final int chunkY, final int chunkZ, final boolean extrude) { -+ if (!currNibble.isNullNibbleUpdating()) { -+ // already initialised -+ return; -+ } -+ -+ final boolean[] emptinessMap = this.getEmptinessMap(chunkX, chunkZ); -+ -+ // are we above this chunk's lowest empty section? -+ int lowestY = this.minLightSection - 1; -+ for (int currY = this.maxSection; currY >= this.minSection; --currY) { -+ if (emptinessMap == null) { -+ // cannot delay nibble init for lit chunks, as we need to init to propagate into them. -+ final LevelChunkSection current = this.getChunkSection(chunkX, currY, chunkZ); -+ if (current == null || current.hasOnlyAir()) { -+ continue; -+ } -+ } else { -+ if (emptinessMap[currY - this.minSection]) { -+ continue; -+ } -+ } -+ -+ // should always be full lit here -+ lowestY = currY; -+ break; -+ } -+ -+ if (chunkY > lowestY) { -+ // we need to set this one to full -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ nibble.setNonNull(); -+ nibble.setFull(); -+ return; -+ } -+ -+ if (extrude) { -+ // this nibble is going to depend solely on the skylight data above it -+ // find first non-null data above (there does exist one, as we just found it above) -+ for (int currY = chunkY + 1; currY <= this.maxLightSection; ++currY) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, currY, chunkZ); -+ if (nibble != null && !nibble.isNullNibbleUpdating()) { -+ currNibble.setNonNull(); -+ currNibble.extrudeLower(nibble); -+ break; -+ } -+ } -+ } else { -+ currNibble.setNonNull(); -+ } -+ } -+ -+ protected final void rewriteNibbleCacheForSkylight(final ChunkAccess chunk) { -+ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { -+ final SWMRNibbleArray nibble = this.nibbleCache[index]; -+ if (nibble != null && nibble.isNullNibbleUpdating()) { -+ // stop propagation in these areas -+ this.nibbleCache[index] = null; -+ nibble.updateVisible(); -+ } -+ } -+ } -+ -+ // rets whether neighbours were init'd -+ -+ protected final boolean checkNullSection(final int chunkX, final int chunkY, final int chunkZ, -+ final boolean extrudeInitialised) { -+ // null chunk sections may have nibble neighbours in the horizontal 1 radius that are -+ // non-null. Propagation to these neighbours is necessary. -+ // What makes this easy is we know none of these neighbours are non-empty (otherwise -+ // this nibble would be initialised). So, we don't have to initialise -+ // the neighbours in the full 1 radius, because there's no worry that any "paths" -+ // to the neighbours on this horizontal plane are blocked. -+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.nullPropagationCheckCache[chunkY - this.minLightSection]) { -+ return false; -+ } -+ this.nullPropagationCheckCache[chunkY - this.minLightSection] = true; -+ -+ // check horizontal neighbours -+ boolean needInitNeighbours = false; -+ neighbour_search: -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(dx + chunkX, chunkY, dz + chunkZ); -+ if (nibble != null && !nibble.isNullNibbleUpdating()) { -+ needInitNeighbours = true; -+ break neighbour_search; -+ } -+ } -+ } -+ -+ if (needInitNeighbours) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ this.initNibble(dx + chunkX, chunkY, dz + chunkZ, (dx | dz) == 0 ? extrudeInitialised : true, true); -+ } -+ } -+ } -+ -+ return needInitNeighbours; -+ } -+ -+ protected final int getLightLevelExtruded(final int worldX, final int worldY, final int worldZ) { -+ final int chunkX = worldX >> 4; -+ int chunkY = worldY >> 4; -+ final int chunkZ = worldZ >> 4; -+ -+ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble != null) { -+ return nibble.getUpdating(worldX, worldY, worldZ); -+ } -+ -+ for (;;) { -+ if (++chunkY > this.maxLightSection) { -+ return 15; -+ } -+ -+ nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ -+ if (nibble != null) { -+ return nibble.getUpdating(worldX, 0, worldZ); -+ } -+ } -+ } -+ -+ @Override -+ protected boolean[] getEmptinessMap(final ChunkAccess chunk) { -+ return chunk.getSkyEmptinessMap(); -+ } -+ -+ @Override -+ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) { -+ chunk.setSkyEmptinessMap(to); -+ } -+ -+ @Override -+ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) { -+ return chunk.getSkyNibbles(); -+ } -+ -+ @Override -+ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) { -+ chunk.setSkyNibbles(to); -+ } -+ -+ @Override -+ protected boolean canUseChunk(final ChunkAccess chunk) { -+ // can only use chunks for sky stuff if their sections have been init'd -+ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect()); -+ } -+ -+ @Override -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, -+ final int toSection) { -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ this.rewriteNibbleCacheForSkylight(chunk); -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ for (int y = toSection; y >= fromSection; --y) { -+ this.checkNullSection(chunkX, y, chunkZ, true); -+ } -+ -+ super.checkChunkEdges(lightAccess, chunk, fromSection, toSection); -+ } -+ -+ @Override -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) { -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ this.rewriteNibbleCacheForSkylight(chunk); -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { -+ final int y = (int)iterator.nextShort(); -+ this.checkNullSection(chunkX, y, chunkZ, true); -+ } -+ -+ super.checkChunkEdges(lightAccess, chunk, sections); -+ } -+ -+ @Override -+ protected void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) { -+ // blocks can change opacity -+ // blocks can change direction of propagation -+ -+ // same logic applies from BlockStarLightEngine#checkBlock -+ -+ final int encodeOffset = this.coordinateOffset; -+ -+ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); -+ -+ if (currentLevel == 15) { -+ // must re-propagate clobbered source -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (currentLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the block is conditionally transparent -+ ); -+ } else { -+ this.setLightLevel(worldX, worldY, worldZ, 0); -+ } -+ -+ this.appendToDecreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (currentLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ ); -+ } -+ -+ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); -+ -+ @Override -+ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, -+ final int expect) { -+ if (expect == 15) { -+ return expect; -+ } -+ -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); -+ int opacity = centerState.getOpacityIfCached(); -+ -+ final BlockState conditionallyOpaqueState; -+ if (opacity < 0) { -+ this.recalcCenterPos.set(worldX, worldY, worldZ); -+ opacity = Math.max(1, centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos)); -+ if (centerState.isConditionallyFullOpaque()) { -+ conditionallyOpaqueState = centerState; -+ } else { -+ conditionallyOpaqueState = null; -+ } -+ } else { -+ conditionallyOpaqueState = null; -+ opacity = Math.max(1, opacity); -+ } -+ -+ int level = 0; -+ -+ for (final AxisDirection direction : AXIS_DIRECTIONS) { -+ final int offX = worldX + direction.x; -+ final int offY = worldY + direction.y; -+ final int offZ = worldZ + direction.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ -+ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); -+ -+ if ((neighbourLevel - 1) <= level) { -+ // don't need to test transparency, we know it wont affect the result. -+ continue; -+ } -+ -+ final BlockState neighbourState = this.getBlockState(offX, offY, offZ); -+ -+ if (neighbourState.isConditionallyFullOpaque()) { -+ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that -+ // we don't read the blockstate because most of the time this is false, so using the faster -+ // known transparency lookup results in a net win -+ this.recalcNeighbourPos.set(offX, offY, offZ); -+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); -+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); -+ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { -+ // not allowed to propagate -+ continue; -+ } -+ } -+ -+ final int calculated = neighbourLevel - opacity; -+ level = Math.max(calculated, level); -+ if (level > expect) { -+ return level; -+ } -+ } -+ -+ return level; -+ } -+ -+ @Override -+ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions) { -+ this.rewriteNibbleCacheForSkylight(atChunk); -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ -+ final BlockGetter world = lightAccess.getLevel(); -+ final int chunkX = atChunk.getPos().x; -+ final int chunkZ = atChunk.getPos().z; -+ final int heightMapOffset = chunkX * -16 + (chunkZ * (-16 * 16)); -+ -+ // setup heightmap for changes -+ for (final BlockPos pos : positions) { -+ final int index = pos.getX() + (pos.getZ() << 4) + heightMapOffset; -+ final int curr = this.heightMapBlockChange[index]; -+ if (pos.getY() > curr) { -+ this.heightMapBlockChange[index] = pos.getY(); -+ } -+ } -+ -+ // note: light sets are delayed while processing skylight source changes due to how -+ // nibbles are initialised, as we want to avoid clobbering nibble values so what when -+ // below nibbles are initialised they aren't reading from partially modified nibbles -+ -+ // now we can recalculate the sources for the changed columns -+ for (int index = 0; index < (16 * 16); ++index) { -+ final int maxY = this.heightMapBlockChange[index]; -+ if (maxY == Integer.MIN_VALUE) { -+ // not changed -+ continue; -+ } -+ this.heightMapBlockChange[index] = Integer.MIN_VALUE; // restore default for next caller -+ -+ final int columnX = (index & 15) | (chunkX << 4); -+ final int columnZ = (index >>> 4) | (chunkZ << 4); -+ -+ // try and propagate from the above y -+ // delay light set until after processing all sources to setup -+ final int maxPropagationY = this.tryPropagateSkylight(world, columnX, maxY, columnZ, true, true); -+ -+ // maxPropagationY is now the highest block that could not be propagated to -+ -+ // remove all sources below that are 15 -+ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; -+ final int encodeOffset = this.coordinateOffset; -+ -+ if (this.getLightLevelExtruded(columnX, maxPropagationY, columnZ) == 15) { -+ // ensure section is checked -+ this.checkNullSection(columnX >> 4, maxPropagationY >> 4, columnZ >> 4, true); -+ -+ for (int currY = maxPropagationY; currY >= (this.minLightSection << 4); --currY) { -+ if ((currY & 15) == 15) { -+ // ensure section is checked -+ this.checkNullSection(columnX >> 4, (currY >> 4), columnZ >> 4, true); -+ } -+ -+ // ensure section below is always checked -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(columnX >> 4, currY >> 4, columnZ >> 4); -+ if (nibble == null) { -+ // advance currY to the the top of the section below -+ currY = (currY) & (~15); -+ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually -+ // end up there -+ continue; -+ } -+ -+ if (nibble.getUpdating(columnX, currY, columnZ) != 15) { -+ break; -+ } -+ -+ // delay light set until after processing all sources to setup -+ this.appendToDecreaseQueue( -+ ((columnX + (columnZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ // do not set transparent blocks for the same reason we don't in the checkBlock method -+ ); -+ } -+ } -+ } -+ -+ // delayed light sets are processed here, and must be processed before checkBlock as checkBlock reads -+ // immediate light value -+ this.processDelayedIncreases(); -+ this.processDelayedDecreases(); -+ -+ for (final BlockPos pos : positions) { -+ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ protected final int[] heightMapGen = new int[32 * 32]; -+ -+ @Override -+ protected void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { -+ this.rewriteNibbleCacheForSkylight(chunk); -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ -+ final BlockGetter world = lightAccess.getLevel(); -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ final LevelChunkSection[] sections = chunk.getSections(); -+ -+ int highestNonEmptySection = this.maxSection; -+ while (highestNonEmptySection == (this.minSection - 1) || -+ sections[highestNonEmptySection - this.minSection] == null || sections[highestNonEmptySection - this.minSection].hasOnlyAir()) { -+ this.checkNullSection(chunkX, highestNonEmptySection, chunkZ, false); -+ // try propagate FULL to neighbours -+ -+ // check neighbours to see if we need to propagate into them -+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { -+ final int neighbourX = chunkX + direction.x; -+ final int neighbourZ = chunkZ + direction.z; -+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(neighbourX, highestNonEmptySection, neighbourZ); -+ if (neighbourNibble == null) { -+ // unloaded neighbour -+ // most of the time we fall here -+ continue; -+ } -+ -+ // it looks like we need to propagate into the neighbour -+ -+ final int incX; -+ final int incZ; -+ final int startX; -+ final int startZ; -+ -+ if (direction.x != 0) { -+ // x direction -+ incX = 0; -+ incZ = 1; -+ -+ if (direction.x < 0) { -+ // negative -+ startX = chunkX << 4; -+ } else { -+ startX = chunkX << 4 | 15; -+ } -+ startZ = chunkZ << 4; -+ } else { -+ // z direction -+ incX = 1; -+ incZ = 0; -+ -+ if (direction.z < 0) { -+ // negative -+ startZ = chunkZ << 4; -+ } else { -+ startZ = chunkZ << 4 | 15; -+ } -+ startX = chunkX << 4; -+ } -+ -+ final int encodeOffset = this.coordinateOffset; -+ final long propagateDirection = 1L << direction.ordinal(); // we only want to check in this direction -+ -+ for (int currY = highestNonEmptySection << 4, maxY = currY | 15; currY <= maxY; ++currY) { -+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { -+ this.appendToIncreaseQueue( -+ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ // no transparent flag, we know for a fact there are no blocks here that could be directionally transparent (as the section is EMPTY) -+ ); -+ } -+ } -+ } -+ -+ if (highestNonEmptySection-- == (this.minSection - 1)) { -+ break; -+ } -+ } -+ -+ if (highestNonEmptySection >= this.minSection) { -+ // fill out our other sources -+ final int minX = chunkPos.x << 4; -+ final int maxX = chunkPos.x << 4 | 15; -+ final int minZ = chunkPos.z << 4; -+ final int maxZ = chunkPos.z << 4 | 15; -+ final int startY = highestNonEmptySection << 4 | 15; -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ this.tryPropagateSkylight(world, currX, startY + 1, currZ, false, false); -+ } -+ } -+ } // else: apparently the chunk is empty -+ -+ if (needsEdgeChecks) { -+ // not required to propagate here, but this will reduce the hit of the edge checks -+ this.performLightIncrease(lightAccess); -+ -+ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { -+ this.checkNullSection(chunkX, y, chunkZ, false); -+ } -+ // no need to rewrite the nibble cache again -+ super.checkChunkEdges(lightAccess, chunk, this.minLightSection, highestNonEmptySection); -+ } else { -+ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { -+ this.checkNullSection(chunkX, y, chunkZ, false); -+ } -+ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, highestNonEmptySection); -+ -+ this.performLightIncrease(lightAccess); -+ } -+ } -+ -+ protected final void processDelayedIncreases() { -+ // copied from performLightIncrease -+ final long[] queue = this.increaseQueue; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ -+ for (int i = 0, len = this.increaseQueueInitialLength; i < len; ++i) { -+ final long queueValue = queue[i]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); -+ -+ this.setLightLevel(posX, posY, posZ, propagatedLightLevel); -+ } -+ } -+ -+ protected final void processDelayedDecreases() { -+ // copied from performLightDecrease -+ final long[] queue = this.decreaseQueue; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ -+ for (int i = 0, len = this.decreaseQueueInitialLength; i < len; ++i) { -+ final long queueValue = queue[i]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ -+ this.setLightLevel(posX, posY, posZ, 0); -+ } -+ } -+ -+ // delaying the light set is useful for block changes since they need to worry about initialising nibblearrays -+ // while also queueing light at the same time (initialising nibblearrays might depend on nibbles above, so -+ // clobbering the light values will result in broken propagation) -+ protected final int tryPropagateSkylight(final BlockGetter world, final int worldX, int startY, final int worldZ, -+ final boolean extrudeInitialised, final boolean delayLightSet) { -+ final BlockPos.MutableBlockPos mutablePos = this.mutablePos3; -+ final int encodeOffset = this.coordinateOffset; -+ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; // just don't check upwards. -+ -+ if (this.getLightLevelExtruded(worldX, startY + 1, worldZ) != 15) { -+ return startY; -+ } -+ -+ // ensure this section is always checked -+ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); -+ -+ BlockState above = this.getBlockState(worldX, startY + 1, worldZ); -+ -+ for (;startY >= (this.minLightSection << 4); --startY) { -+ if ((startY & 15) == 15) { -+ // ensure this section is always checked -+ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); -+ } -+ final BlockState current = this.getBlockState(worldX, startY, worldZ); -+ -+ final VoxelShape fromShape; -+ if (above.isConditionallyFullOpaque()) { -+ this.mutablePos2.set(worldX, startY + 1, worldZ); -+ fromShape = above.getFaceOcclusionShape(world, this.mutablePos2, AxisDirection.NEGATIVE_Y.nms); -+ if (Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { -+ // above wont let us propagate -+ break; -+ } -+ } else { -+ fromShape = Shapes.empty(); -+ } -+ -+ final int opacityIfCached = current.getOpacityIfCached(); -+ // does light propagate from the top down? -+ if (opacityIfCached != -1) { -+ if (opacityIfCached != 0) { -+ // we cannot propagate 15 through this -+ break; -+ } -+ // most of the time it falls here. -+ // add to propagate -+ // light set delayed until we determine if this nibble section is null -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ ); -+ } else { -+ mutablePos.set(worldX, startY, worldZ); -+ long flags = 0L; -+ if (current.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = current.getFaceOcclusionShape(world, mutablePos, AxisDirection.POSITIVE_Y.nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ // can't propagate here, we're done on this column. -+ break; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = current.getLightBlock(world, mutablePos); -+ if (opacity > 0) { -+ // let the queued value (if any) handle it from here. -+ break; -+ } -+ -+ // light set delayed until we determine if this nibble section is null -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ | flags -+ ); -+ } -+ -+ above = current; -+ -+ if (this.getNibbleFromCache(worldX >> 4, startY >> 4, worldZ >> 4) == null) { -+ // we skip empty sections here, as this is just an easy way of making sure the above block -+ // can propagate through air. -+ -+ // nothing can propagate in null sections, remove the queue entry for it -+ --this.increaseQueueInitialLength; -+ -+ // advance currY to the the top of the section below -+ startY = (startY) & (~15); -+ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually -+ // end up there -+ -+ // make sure this is marked as AIR -+ above = AIR_BLOCK_STATE; -+ } else if (!delayLightSet) { -+ this.setLightLevel(worldX, startY, worldZ, 15); -+ } -+ } -+ -+ return startY; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ad1eeebe6de219143492b94da309cb54ae9e0a5b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java -@@ -0,0 +1,1572 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import ca.spottedleaf.starlight.common.util.CoordinateUtils; -+import ca.spottedleaf.starlight.common.util.IntegerUtil; -+import ca.spottedleaf.starlight.common.util.WorldUtil; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortIterator; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.core.SectionPos; -+import net.minecraft.world.level.BlockGetter; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.LevelHeightAccessor; -+import net.minecraft.world.level.LightLayer; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.List; -+import java.util.Set; -+import java.util.function.Consumer; -+import java.util.function.IntConsumer; -+ -+public abstract class StarLightEngine { -+ -+ protected static final BlockState AIR_BLOCK_STATE = Blocks.AIR.defaultBlockState(); -+ -+ protected static final AxisDirection[] DIRECTIONS = AxisDirection.values(); -+ protected static final AxisDirection[] AXIS_DIRECTIONS = DIRECTIONS; -+ protected static final AxisDirection[] ONLY_HORIZONTAL_DIRECTIONS = new AxisDirection[] { -+ AxisDirection.POSITIVE_X, AxisDirection.NEGATIVE_X, -+ AxisDirection.POSITIVE_Z, AxisDirection.NEGATIVE_Z -+ }; -+ -+ protected static enum AxisDirection { -+ -+ // Declaration order is important and relied upon. Do not change without modifying propagation code. -+ POSITIVE_X(1, 0, 0), NEGATIVE_X(-1, 0, 0), -+ POSITIVE_Z(0, 0, 1), NEGATIVE_Z(0, 0, -1), -+ POSITIVE_Y(0, 1, 0), NEGATIVE_Y(0, -1, 0); -+ -+ static { -+ POSITIVE_X.opposite = NEGATIVE_X; NEGATIVE_X.opposite = POSITIVE_X; -+ POSITIVE_Z.opposite = NEGATIVE_Z; NEGATIVE_Z.opposite = POSITIVE_Z; -+ POSITIVE_Y.opposite = NEGATIVE_Y; NEGATIVE_Y.opposite = POSITIVE_Y; -+ } -+ -+ protected AxisDirection opposite; -+ -+ public final int x; -+ public final int y; -+ public final int z; -+ public final Direction nms; -+ public final long everythingButThisDirection; -+ public final long everythingButTheOppositeDirection; -+ -+ AxisDirection(final int x, final int y, final int z) { -+ this.x = x; -+ this.y = y; -+ this.z = z; -+ this.nms = Direction.fromDelta(x, y, z); -+ this.everythingButThisDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << this.ordinal())); -+ // positive is always even, negative is always odd. Flip the 1 bit to get the negative direction. -+ this.everythingButTheOppositeDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << (this.ordinal() ^ 1))); -+ } -+ -+ public AxisDirection getOpposite() { -+ return this.opposite; -+ } -+ } -+ -+ // I'd like to thank https://www.seedofandromeda.com/blogs/29-fast-flood-fill-lighting-in-a-blocky-voxel-game-pt-1 -+ // for explaining how light propagates via breadth-first search -+ -+ // While the above is a good start to understanding the general idea of what the general principles are, it's not -+ // exactly how the vanilla light engine should behave for minecraft. -+ -+ // similar to the above, except the chunk section indices vary from [-1, 1], or [0, 2] -+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] -+ // index = x + (z * 5) + (y * 25) -+ // null index indicates the chunk section doesn't exist (empty or out of bounds) -+ protected final LevelChunkSection[] sectionCache; -+ -+ // the exact same as above, except for storing fast access to SWMRNibbleArray -+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] -+ // index = x + (z * 5) + (y * 25) -+ protected final SWMRNibbleArray[] nibbleCache; -+ -+ // the exact same as above, except for storing fast access to nibbles to call change callbacks for -+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] -+ // index = x + (z * 5) + (y * 25) -+ protected final boolean[] notifyUpdateCache; -+ -+ // always initialsed during start of lighting. -+ // index = x + (z * 5) -+ protected final ChunkAccess[] chunkCache = new ChunkAccess[5 * 5]; -+ -+ // index = x + (z * 5) -+ protected final boolean[][] emptinessMapCache = new boolean[5 * 5][]; -+ -+ protected final BlockPos.MutableBlockPos mutablePos1 = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos mutablePos2 = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos mutablePos3 = new BlockPos.MutableBlockPos(); -+ -+ protected int encodeOffsetX; -+ protected int encodeOffsetY; -+ protected int encodeOffsetZ; -+ -+ protected int coordinateOffset; -+ -+ protected int chunkOffsetX; -+ protected int chunkOffsetY; -+ protected int chunkOffsetZ; -+ -+ protected int chunkIndexOffset; -+ protected int chunkSectionIndexOffset; -+ -+ protected final boolean skylightPropagator; -+ protected final int emittedLightMask; -+ protected final boolean isClientSide; -+ -+ protected final Level world; -+ protected final int minLightSection; -+ protected final int maxLightSection; -+ protected final int minSection; -+ protected final int maxSection; -+ -+ protected StarLightEngine(final boolean skylightPropagator, final Level world) { -+ this.skylightPropagator = skylightPropagator; -+ this.emittedLightMask = skylightPropagator ? 0 : 0xF; -+ this.isClientSide = world.isClientSide; -+ this.world = world; -+ this.minLightSection = WorldUtil.getMinLightSection(world); -+ this.maxLightSection = WorldUtil.getMaxLightSection(world); -+ this.minSection = WorldUtil.getMinSection(world); -+ this.maxSection = WorldUtil.getMaxSection(world); -+ -+ this.sectionCache = new LevelChunkSection[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer -+ this.nibbleCache = new SWMRNibbleArray[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer -+ this.notifyUpdateCache = new boolean[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer -+ } -+ -+ protected final void setupEncodeOffset(final int centerX, final int centerY, final int centerZ) { -+ // 31 = center + encodeOffset -+ this.encodeOffsetX = 31 - centerX; -+ this.encodeOffsetY = (-(this.minLightSection - 1) << 4); // we want 0 to be the smallest encoded value -+ this.encodeOffsetZ = 31 - centerZ; -+ -+ // coordinateIndex = x | (z << 6) | (y << 12) -+ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << 6) + (this.encodeOffsetY << 12); -+ -+ // 2 = (centerX >> 4) + chunkOffset -+ this.chunkOffsetX = 2 - (centerX >> 4); -+ this.chunkOffsetY = -(this.minLightSection - 1); // lowest should be 0 -+ this.chunkOffsetZ = 2 - (centerZ >> 4); -+ -+ // chunk index = x + (5 * z) -+ this.chunkIndexOffset = this.chunkOffsetX + (5 * this.chunkOffsetZ); -+ -+ // chunk section index = x + (5 * z) + ((5*5) * y) -+ this.chunkSectionIndexOffset = this.chunkIndexOffset + ((5 * 5) * this.chunkOffsetY); -+ } -+ -+ protected final void setupCaches(final LightChunkGetter chunkProvider, final int centerX, final int centerY, final int centerZ, -+ final boolean relaxed, final boolean tryToLoadChunksFor2Radius) { -+ final int centerChunkX = centerX >> 4; -+ final int centerChunkY = centerY >> 4; -+ final int centerChunkZ = centerZ >> 4; -+ -+ this.setupEncodeOffset(centerChunkX * 16 + 7, centerChunkY * 16 + 7, centerChunkZ * 16 + 7); -+ -+ final int radius = tryToLoadChunksFor2Radius ? 2 : 1; -+ -+ for (int dz = -radius; dz <= radius; ++dz) { -+ for (int dx = -radius; dx <= radius; ++dx) { -+ final int cx = centerChunkX + dx; -+ final int cz = centerChunkZ + dz; -+ final boolean isTwoRadius = Math.max(IntegerUtil.branchlessAbs(dx), IntegerUtil.branchlessAbs(dz)) == 2; -+ final ChunkAccess chunk = (ChunkAccess)chunkProvider.getChunkForLighting(cx, cz); -+ -+ if (chunk == null) { -+ if (relaxed | isTwoRadius) { -+ continue; -+ } -+ throw new IllegalArgumentException("Trying to propagate light update before 1 radius neighbours ready"); -+ } -+ -+ if (!this.canUseChunk(chunk)) { -+ continue; -+ } -+ -+ this.setChunkInCache(cx, cz, chunk); -+ this.setEmptinessMapCache(cx, cz, this.getEmptinessMap(chunk)); -+ if (!isTwoRadius) { -+ this.setBlocksForChunkInCache(cx, cz, chunk.getSections()); -+ this.setNibblesForChunkInCache(cx, cz, this.getNibblesOnChunk(chunk)); -+ } -+ } -+ } -+ } -+ -+ protected final ChunkAccess getChunkInCache(final int chunkX, final int chunkZ) { -+ return this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; -+ } -+ -+ protected final void setChunkInCache(final int chunkX, final int chunkZ, final ChunkAccess chunk) { -+ this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = chunk; -+ } -+ -+ protected final LevelChunkSection getChunkSection(final int chunkX, final int chunkY, final int chunkZ) { -+ return this.sectionCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; -+ } -+ -+ protected final void setChunkSectionInCache(final int chunkX, final int chunkY, final int chunkZ, final LevelChunkSection section) { -+ this.sectionCache[chunkX + 5*chunkZ + 5*5*chunkY + this.chunkSectionIndexOffset] = section; -+ } -+ -+ protected final void setBlocksForChunkInCache(final int chunkX, final int chunkZ, final LevelChunkSection[] sections) { -+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { -+ this.setChunkSectionInCache(chunkX, cy, chunkZ, -+ sections == null ? null : (cy >= this.minSection && cy <= this.maxSection ? sections[cy - this.minSection] : null)); -+ } -+ } -+ -+ protected final SWMRNibbleArray getNibbleFromCache(final int chunkX, final int chunkY, final int chunkZ) { -+ return this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; -+ } -+ -+ protected final SWMRNibbleArray[] getNibblesForChunkFromCache(final int chunkX, final int chunkZ) { -+ final SWMRNibbleArray[] ret = new SWMRNibbleArray[this.maxLightSection - this.minLightSection + 1]; -+ -+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { -+ ret[cy - this.minLightSection] = this.nibbleCache[chunkX + 5*chunkZ + (cy * (5 * 5)) + this.chunkSectionIndexOffset]; -+ } -+ -+ return ret; -+ } -+ -+ protected final void setNibbleInCache(final int chunkX, final int chunkY, final int chunkZ, final SWMRNibbleArray nibble) { -+ this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset] = nibble; -+ } -+ -+ protected final void setNibblesForChunkInCache(final int chunkX, final int chunkZ, final SWMRNibbleArray[] nibbles) { -+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { -+ this.setNibbleInCache(chunkX, cy, chunkZ, nibbles == null ? null : nibbles[cy - this.minLightSection]); -+ } -+ } -+ -+ protected final void updateVisible(final LightChunkGetter lightAccess) { -+ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { -+ final SWMRNibbleArray nibble = this.nibbleCache[index]; -+ if (!this.notifyUpdateCache[index] && (nibble == null || !nibble.isDirty())) { -+ continue; -+ } -+ -+ final int chunkX = (index % 5) - this.chunkOffsetX; -+ final int chunkZ = ((index / 5) % 5) - this.chunkOffsetZ; -+ final int ySections = (this.maxSection - this.minSection) + 1; -+ final int chunkY = ((index / (5*5)) % (ySections + 2 + 2)) - this.chunkOffsetY; -+ if ((nibble != null && nibble.updateVisible()) || this.notifyUpdateCache[index]) { -+ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, chunkY, chunkZ)); -+ } -+ } -+ } -+ -+ protected final void destroyCaches() { -+ Arrays.fill(this.sectionCache, null); -+ Arrays.fill(this.nibbleCache, null); -+ Arrays.fill(this.chunkCache, null); -+ Arrays.fill(this.emptinessMapCache, null); -+ if (this.isClientSide) { -+ Arrays.fill(this.notifyUpdateCache, false); -+ } -+ } -+ -+ protected final BlockState getBlockState(final int worldX, final int worldY, final int worldZ) { -+ final LevelChunkSection section = this.sectionCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; -+ -+ if (section != null) { -+ return section.hasOnlyAir() ? AIR_BLOCK_STATE : section.getBlockState(worldX & 15, worldY & 15, worldZ & 15); -+ } -+ -+ return AIR_BLOCK_STATE; -+ } -+ -+ protected final BlockState getBlockState(final int sectionIndex, final int localIndex) { -+ final LevelChunkSection section = this.sectionCache[sectionIndex]; -+ -+ if (section != null) { -+ return section.hasOnlyAir() ? AIR_BLOCK_STATE : section.states.get(localIndex); -+ } -+ -+ return AIR_BLOCK_STATE; -+ } -+ -+ protected final int getLightLevel(final int worldX, final int worldY, final int worldZ) { -+ final SWMRNibbleArray nibble = this.nibbleCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; -+ -+ return nibble == null ? 0 : nibble.getUpdating((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8)); -+ } -+ -+ protected final int getLightLevel(final int sectionIndex, final int localIndex) { -+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; -+ -+ return nibble == null ? 0 : nibble.getUpdating(localIndex); -+ } -+ -+ protected final void setLightLevel(final int worldX, final int worldY, final int worldZ, final int level) { -+ final int sectionIndex = (worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset; -+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; -+ -+ if (nibble != null) { -+ nibble.set((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8), level); -+ if (this.isClientSide) { -+ int cx1 = (worldX - 1) >> 4; -+ int cx2 = (worldX + 1) >> 4; -+ int cy1 = (worldY - 1) >> 4; -+ int cy2 = (worldY + 1) >> 4; -+ int cz1 = (worldZ - 1) >> 4; -+ int cz2 = (worldZ + 1) >> 4; -+ for (int x = cx1; x <= cx2; ++x) { -+ for (int y = cy1; y <= cy2; ++y) { -+ for (int z = cz1; z <= cz2; ++z) { -+ this.notifyUpdateCache[x + 5 * z + (5 * 5) * y + this.chunkSectionIndexOffset] = true; -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ protected final void postLightUpdate(final int worldX, final int worldY, final int worldZ) { -+ if (this.isClientSide) { -+ int cx1 = (worldX - 1) >> 4; -+ int cx2 = (worldX + 1) >> 4; -+ int cy1 = (worldY - 1) >> 4; -+ int cy2 = (worldY + 1) >> 4; -+ int cz1 = (worldZ - 1) >> 4; -+ int cz2 = (worldZ + 1) >> 4; -+ for (int x = cx1; x <= cx2; ++x) { -+ for (int y = cy1; y <= cy2; ++y) { -+ for (int z = cz1; z <= cz2; ++z) { -+ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; -+ } -+ } -+ } -+ } -+ } -+ -+ protected final void setLightLevel(final int sectionIndex, final int localIndex, final int worldX, final int worldY, final int worldZ, final int level) { -+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; -+ -+ if (nibble != null) { -+ nibble.set(localIndex, level); -+ if (this.isClientSide) { -+ int cx1 = (worldX - 1) >> 4; -+ int cx2 = (worldX + 1) >> 4; -+ int cy1 = (worldY - 1) >> 4; -+ int cy2 = (worldY + 1) >> 4; -+ int cz1 = (worldZ - 1) >> 4; -+ int cz2 = (worldZ + 1) >> 4; -+ for (int x = cx1; x <= cx2; ++x) { -+ for (int y = cy1; y <= cy2; ++y) { -+ for (int z = cz1; z <= cz2; ++z) { -+ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ protected final boolean[] getEmptinessMap(final int chunkX, final int chunkZ) { -+ return this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; -+ } -+ -+ protected final void setEmptinessMapCache(final int chunkX, final int chunkZ, final boolean[] emptinessMap) { -+ this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = emptinessMap; -+ } -+ -+ public static SWMRNibbleArray[] getFilledEmptyLight(final LevelHeightAccessor world) { -+ return getFilledEmptyLight(WorldUtil.getTotalLightSections(world)); -+ } -+ -+ private static SWMRNibbleArray[] getFilledEmptyLight(final int totalLightSections) { -+ final SWMRNibbleArray[] ret = new SWMRNibbleArray[totalLightSections]; -+ -+ for (int i = 0, len = ret.length; i < len; ++i) { -+ ret[i] = new SWMRNibbleArray(null, true); -+ } -+ -+ return ret; -+ } -+ -+ protected abstract boolean[] getEmptinessMap(final ChunkAccess chunk); -+ -+ protected abstract void setEmptinessMap(final ChunkAccess chunk, final boolean[] to); -+ -+ protected abstract SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk); -+ -+ protected abstract void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to); -+ -+ protected abstract boolean canUseChunk(final ChunkAccess chunk); -+ -+ public final void blocksChangedInChunk(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, -+ final Set positions, final Boolean[] changedSections) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ if (changedSections != null) { -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, changedSections, false); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ } -+ if (!positions.isEmpty()) { -+ this.propagateBlockChanges(lightAccess, chunk, positions); -+ } -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ protected abstract void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions); -+ -+ protected abstract void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ); -+ -+ // if ret > expect, then the real value is at least ret (early returns if ret > expect, rather than calculating actual) -+ // if ret == expect, then expect is the correct light value for pos -+ // if ret < expect, then ret is the real light value -+ protected abstract int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, -+ final int expect); -+ -+ protected final int[] chunkCheckDelayedUpdatesCenter = new int[16 * 16]; -+ protected final int[] chunkCheckDelayedUpdatesNeighbour = new int[16 * 16]; -+ -+ protected void checkChunkEdge(final LightChunkGetter lightAccess, final ChunkAccess chunk, -+ final int chunkX, final int chunkY, final int chunkZ) { -+ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (currNibble == null) { -+ return; -+ } -+ -+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { -+ final int neighbourOffX = direction.x; -+ final int neighbourOffZ = direction.z; -+ -+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, -+ chunkY, chunkZ + neighbourOffZ); -+ -+ if (neighbourNibble == null) { -+ continue; -+ } -+ -+ if (!currNibble.isInitialisedUpdating() && !neighbourNibble.isInitialisedUpdating()) { -+ // both are zero, nothing to check. -+ continue; -+ } -+ -+ // this chunk -+ final int incX; -+ final int incZ; -+ final int startX; -+ final int startZ; -+ -+ if (neighbourOffX != 0) { -+ // x direction -+ incX = 0; -+ incZ = 1; -+ -+ if (direction.x < 0) { -+ // negative -+ startX = chunkX << 4; -+ } else { -+ startX = chunkX << 4 | 15; -+ } -+ startZ = chunkZ << 4; -+ } else { -+ // z direction -+ incX = 1; -+ incZ = 0; -+ -+ if (neighbourOffZ < 0) { -+ // negative -+ startZ = chunkZ << 4; -+ } else { -+ startZ = chunkZ << 4 | 15; -+ } -+ startX = chunkX << 4; -+ } -+ -+ int centerDelayedChecks = 0; -+ int neighbourDelayedChecks = 0; -+ for (int currY = chunkY << 4, maxY = currY | 15; currY <= maxY; ++currY) { -+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { -+ final int neighbourX = currX + neighbourOffX; -+ final int neighbourZ = currZ + neighbourOffZ; -+ -+ final int currentIndex = (currX & 15) | -+ ((currZ & 15)) << 4 | -+ ((currY & 15) << 8); -+ final int currentLevel = currNibble.getUpdating(currentIndex); -+ -+ final int neighbourIndex = -+ (neighbourX & 15) | -+ ((neighbourZ & 15)) << 4 | -+ ((currY & 15) << 8); -+ final int neighbourLevel = neighbourNibble.getUpdating(neighbourIndex); -+ -+ // the checks are delayed because the checkBlock method clobbers light values - which then -+ // affect later calculate light value operations. While they don't affect it in a behaviourly significant -+ // way, they do have a negative performance impact due to simply queueing more values -+ -+ if (this.calculateLightValue(lightAccess, currX, currY, currZ, currentLevel) != currentLevel) { -+ this.chunkCheckDelayedUpdatesCenter[centerDelayedChecks++] = currentIndex; -+ } -+ -+ if (this.calculateLightValue(lightAccess, neighbourX, currY, neighbourZ, neighbourLevel) != neighbourLevel) { -+ this.chunkCheckDelayedUpdatesNeighbour[neighbourDelayedChecks++] = neighbourIndex; -+ } -+ } -+ } -+ -+ final int currentChunkOffX = chunkX << 4; -+ final int currentChunkOffZ = chunkZ << 4; -+ final int neighbourChunkOffX = (chunkX + direction.x) << 4; -+ final int neighbourChunkOffZ = (chunkZ + direction.z) << 4; -+ final int chunkOffY = chunkY << 4; -+ for (int i = 0, len = Math.max(centerDelayedChecks, neighbourDelayedChecks); i < len; ++i) { -+ // try to queue neighbouring data together -+ // index = x | (z << 4) | (y << 8) -+ if (i < centerDelayedChecks) { -+ final int value = this.chunkCheckDelayedUpdatesCenter[i]; -+ this.checkBlock(lightAccess, currentChunkOffX | (value & 15), -+ chunkOffY | (value >>> 8), -+ currentChunkOffZ | ((value >>> 4) & 0xF)); -+ } -+ if (i < neighbourDelayedChecks) { -+ final int value = this.chunkCheckDelayedUpdatesNeighbour[i]; -+ this.checkBlock(lightAccess, neighbourChunkOffX | (value & 15), -+ chunkOffY | (value >>> 8), -+ neighbourChunkOffZ | ((value >>> 4) & 0xF)); -+ } -+ } -+ } -+ } -+ -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) { -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { -+ this.checkChunkEdge(lightAccess, chunk, chunkX, iterator.nextShort(), chunkZ); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ // verifies that light levels on this chunks edges are consistent with this chunk's neighbours -+ // edges. if they are not, they are decreased (effectively performing the logic in checkBlock). -+ // This does not resolve skylight source problems. -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, final int toSection) { -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { -+ this.checkChunkEdge(lightAccess, chunk, chunkX, currSectionY, chunkZ); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ // pulls light from neighbours, and adds them into the increase queue. does not actually propagate. -+ protected final void propagateNeighbourLevels(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, final int toSection) { -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { -+ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ); -+ if (currNibble == null) { -+ continue; -+ } -+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { -+ final int neighbourOffX = direction.x; -+ final int neighbourOffZ = direction.z; -+ -+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, -+ currSectionY, chunkZ + neighbourOffZ); -+ -+ if (neighbourNibble == null || !neighbourNibble.isInitialisedUpdating()) { -+ // can't pull from 0 -+ continue; -+ } -+ -+ // neighbour chunk -+ final int incX; -+ final int incZ; -+ final int startX; -+ final int startZ; -+ -+ if (neighbourOffX != 0) { -+ // x direction -+ incX = 0; -+ incZ = 1; -+ -+ if (direction.x < 0) { -+ // negative -+ startX = (chunkX << 4) - 1; -+ } else { -+ startX = (chunkX << 4) + 16; -+ } -+ startZ = chunkZ << 4; -+ } else { -+ // z direction -+ incX = 1; -+ incZ = 0; -+ -+ if (neighbourOffZ < 0) { -+ // negative -+ startZ = (chunkZ << 4) - 1; -+ } else { -+ startZ = (chunkZ << 4) + 16; -+ } -+ startX = chunkX << 4; -+ } -+ -+ final long propagateDirection = 1L << direction.getOpposite().ordinal(); // we only want to check in this direction towards this chunk -+ final int encodeOffset = this.coordinateOffset; -+ -+ for (int currY = currSectionY << 4, maxY = currY | 15; currY <= maxY; ++currY) { -+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { -+ final int level = neighbourNibble.getUpdating( -+ (currX & 15) -+ | ((currZ & 15) << 4) -+ | ((currY & 15) << 8) -+ ); -+ -+ if (level <= 1) { -+ // nothing to propagate -+ continue; -+ } -+ -+ this.appendToIncreaseQueue( -+ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((level & 0xFL) << (6 + 6 + 16)) -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the current block is transparent, must check. -+ ); -+ } -+ } -+ } -+ } -+ } -+ -+ public static Boolean[] getEmptySectionsForChunk(final ChunkAccess chunk) { -+ final LevelChunkSection[] sections = chunk.getSections(); -+ final Boolean[] ret = new Boolean[sections.length]; -+ -+ for (int i = 0; i < sections.length; ++i) { -+ if (sections[i] == null || sections[i].hasOnlyAir()) { -+ ret[i] = Boolean.TRUE; -+ } else { -+ ret[i] = Boolean.FALSE; -+ } -+ } -+ -+ return ret; -+ } -+ -+ public final void forceHandleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptinessChanges) { -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ try { -+ // force current chunk into cache -+ this.setChunkInCache(chunkX, chunkZ, chunk); -+ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); -+ this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk)); -+ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); -+ -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ public final void handleEmptySectionChanges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, -+ final Boolean[] emptinessChanges) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ protected abstract void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles); -+ -+ protected abstract void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ); -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ // subclasses are guaranteed that this is always called before a changed block set -+ // newChunk specifies whether the changes describe a "first load" of a chunk or changes to existing, already loaded chunks -+ // rets non-null when the emptiness map changed and needs to be updated -+ protected final boolean[] handleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk, -+ final Boolean[] emptinessChanges, final boolean unlit) { -+ final Level world = (Level)lightAccess.getLevel(); -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ -+ boolean[] chunkEmptinessMap = this.getEmptinessMap(chunkX, chunkZ); -+ boolean[] ret = null; -+ final boolean needsInit = unlit || chunkEmptinessMap == null; -+ if (needsInit) { -+ this.setEmptinessMapCache(chunkX, chunkZ, ret = chunkEmptinessMap = new boolean[WorldUtil.getTotalSections(world)]); -+ } -+ -+ // update emptiness map -+ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { -+ Boolean valueBoxed = emptinessChanges[sectionIndex]; -+ if (valueBoxed == null) { -+ if (!needsInit) { -+ continue; -+ } -+ final LevelChunkSection section = this.getChunkSection(chunkX, sectionIndex + this.minSection, chunkZ); -+ emptinessChanges[sectionIndex] = valueBoxed = section == null || section.hasOnlyAir() ? Boolean.TRUE : Boolean.FALSE; -+ } -+ chunkEmptinessMap[sectionIndex] = valueBoxed.booleanValue(); -+ } -+ -+ // now init neighbour nibbles -+ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { -+ final Boolean valueBoxed = emptinessChanges[sectionIndex]; -+ final int sectionY = sectionIndex + this.minSection; -+ if (valueBoxed == null) { -+ continue; -+ } -+ -+ final boolean empty = valueBoxed.booleanValue(); -+ -+ if (empty) { -+ continue; -+ } -+ -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ // if we're not empty, we also need to initialise nibbles -+ // note: if we're unlit, we absolutely do not want to extrude, as light data isn't set up -+ final boolean extrude = (dx | dz) != 0 || !unlit; -+ for (int dy = 1; dy >= -1; --dy) { -+ this.initNibble(dx + chunkX, dy + sectionY, dz + chunkZ, extrude, false); -+ } -+ } -+ } -+ } -+ -+ // check for de-init and lazy-init -+ // lazy init is when chunks are being lit, so at the time they weren't loaded when their neighbours were running -+ // init checks. -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ // does this neighbour have 1 radius loaded? -+ boolean neighboursLoaded = true; -+ neighbour_loaded_search: -+ for (int dz2 = -1; dz2 <= 1; ++dz2) { -+ for (int dx2 = -1; dx2 <= 1; ++dx2) { -+ if (this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ) == null) { -+ neighboursLoaded = false; -+ break neighbour_loaded_search; -+ } -+ } -+ } -+ -+ for (int sectionY = this.maxLightSection; sectionY >= this.minLightSection; --sectionY) { -+ // check neighbours to see if we need to de-init this one -+ boolean allEmpty = true; -+ neighbour_search: -+ for (int dy2 = -1; dy2 <= 1; ++dy2) { -+ for (int dz2 = -1; dz2 <= 1; ++dz2) { -+ for (int dx2 = -1; dx2 <= 1; ++dx2) { -+ final int y = sectionY + dy2; -+ if (y < this.minSection || y > this.maxSection) { -+ // empty -+ continue; -+ } -+ final boolean[] emptinessMap = this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ); -+ if (emptinessMap != null) { -+ if (!emptinessMap[y - this.minSection]) { -+ allEmpty = false; -+ break neighbour_search; -+ } -+ } else { -+ final LevelChunkSection section = this.getChunkSection(dx + dx2 + chunkX, y, dz + dz2 + chunkZ); -+ if (section != null && !section.hasOnlyAir()) { -+ allEmpty = false; -+ break neighbour_search; -+ } -+ } -+ } -+ } -+ } -+ -+ if (allEmpty & neighboursLoaded) { -+ // can only de-init when neighbours are loaded -+ // de-init is fine to delay, as de-init is just an optimisation - it's not required for lighting -+ // to be correct -+ -+ // all were empty, so de-init -+ this.setNibbleNull(dx + chunkX, sectionY, dz + chunkZ); -+ } else if (!allEmpty) { -+ // must init -+ final boolean extrude = (dx | dz) != 0 || !unlit; -+ this.initNibble(dx + chunkX, sectionY, dz + chunkZ, extrude, false); -+ } -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, final ShortCollection sections) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ this.checkChunkEdges(lightAccess, chunk, sections); -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ // needsEdgeChecks applies when possibly loading vanilla data, which means we need to validate the current -+ // chunks light values with respect to neighbours -+ // subclasses should note that the emptiness changes are propagated BEFORE this is called, so this function -+ // does not need to detect empty chunks itself (and it should do no handling for them either!) -+ protected abstract void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks); -+ -+ public final void light(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptySections) { -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ -+ try { -+ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.maxLightSection - this.minLightSection + 1); -+ // force current chunk into cache -+ this.setChunkInCache(chunkX, chunkZ, chunk); -+ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); -+ this.setNibblesForChunkInCache(chunkX, chunkZ, nibbles); -+ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); -+ -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptySections, true); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ this.lightChunk(lightAccess, chunk, true); -+ this.setNibbles(chunk, nibbles); -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ public final void relightChunks(final LightChunkGetter lightAccess, final Set chunks, -+ final Consumer chunkLightCallback, final IntConsumer onComplete) { -+ // it's recommended for maximum performance that the set is ordered according to a BFS from the center of -+ // the region of chunks to relight -+ // it's required that tickets are added for each chunk to keep them loaded -+ final Long2ObjectOpenHashMap nibblesByChunk = new Long2ObjectOpenHashMap<>(); -+ final Long2ObjectOpenHashMap emptinessMapByChunk = new Long2ObjectOpenHashMap<>(); -+ -+ final int[] neighbourLightOrder = new int[] { -+ // d = 0 -+ 0, 0, -+ // d = 1 -+ -1, 0, -+ 0, -1, -+ 1, 0, -+ 0, 1, -+ // d = 2 -+ -1, 1, -+ 1, 1, -+ -1, -1, -+ 1, -1, -+ }; -+ -+ int lightCalls = 0; -+ -+ for (final ChunkPos chunkPos : chunks) { -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ final ChunkAccess chunk = (ChunkAccess)lightAccess.getChunkForLighting(chunkX, chunkZ); -+ if (chunk == null || !this.canUseChunk(chunk)) { -+ throw new IllegalStateException(); -+ } -+ -+ for (int i = 0, len = neighbourLightOrder.length; i < len; i += 2) { -+ final int dx = neighbourLightOrder[i]; -+ final int dz = neighbourLightOrder[i + 1]; -+ final int neighbourX = dx + chunkX; -+ final int neighbourZ = dz + chunkZ; -+ -+ final ChunkAccess neighbour = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX, neighbourZ); -+ if (neighbour == null || !this.canUseChunk(neighbour)) { -+ continue; -+ } -+ -+ if (nibblesByChunk.get(CoordinateUtils.getChunkKey(neighbourX, neighbourZ)) != null) { -+ // lit already called for neighbour, no need to light it now -+ continue; -+ } -+ -+ // light neighbour chunk -+ this.setupEncodeOffset(neighbourX * 16 + 7, 128, neighbourZ * 16 + 7); -+ try { -+ // insert all neighbouring chunks for this neighbour that we have data for -+ for (int dz2 = -1; dz2 <= 1; ++dz2) { -+ for (int dx2 = -1; dx2 <= 1; ++dx2) { -+ final int neighbourX2 = neighbourX + dx2; -+ final int neighbourZ2 = neighbourZ + dz2; -+ final long key = CoordinateUtils.getChunkKey(neighbourX2, neighbourZ2); -+ final ChunkAccess neighbour2 = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX2, neighbourZ2); -+ if (neighbour2 == null || !this.canUseChunk(neighbour2)) { -+ continue; -+ } -+ -+ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(key); -+ if (nibbles == null) { -+ // we haven't lit this chunk -+ continue; -+ } -+ -+ this.setChunkInCache(neighbourX2, neighbourZ2, neighbour2); -+ this.setBlocksForChunkInCache(neighbourX2, neighbourZ2, neighbour2.getSections()); -+ this.setNibblesForChunkInCache(neighbourX2, neighbourZ2, nibbles); -+ this.setEmptinessMapCache(neighbourX2, neighbourZ2, emptinessMapByChunk.get(key)); -+ } -+ } -+ -+ final long key = CoordinateUtils.getChunkKey(neighbourX, neighbourZ); -+ -+ // now insert the neighbour chunk and light it -+ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.world); -+ nibblesByChunk.put(key, nibbles); -+ -+ this.setChunkInCache(neighbourX, neighbourZ, neighbour); -+ this.setBlocksForChunkInCache(neighbourX, neighbourZ, neighbour.getSections()); -+ this.setNibblesForChunkInCache(neighbourX, neighbourZ, nibbles); -+ -+ final boolean[] neighbourEmptiness = this.handleEmptySectionChanges(lightAccess, neighbour, getEmptySectionsForChunk(neighbour), true); -+ emptinessMapByChunk.put(key, neighbourEmptiness); -+ if (chunks.contains(new ChunkPos(neighbourX, neighbourZ))) { -+ this.setEmptinessMap(neighbour, neighbourEmptiness); -+ } -+ -+ this.lightChunk(lightAccess, neighbour, false); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ // done lighting all neighbours, so the chunk is now fully lit -+ -+ // make sure nibbles are fully updated before calling back -+ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ for (final SWMRNibbleArray nibble : nibbles) { -+ nibble.updateVisible(); -+ } -+ -+ this.setNibbles(chunk, nibbles); -+ -+ for (int y = this.minLightSection; y <= this.maxLightSection; ++y) { -+ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, y, chunkX)); -+ } -+ -+ // now do callback -+ if (chunkLightCallback != null) { -+ chunkLightCallback.accept(chunkPos); -+ } -+ ++lightCalls; -+ } -+ -+ if (onComplete != null) { -+ onComplete.accept(lightCalls); -+ } -+ } -+ -+ // contains: -+ // lower (6 + 6 + 16) = 28 bits: encoded coordinate position (x | (z << 6) | (y << (6 + 6)))) -+ // next 4 bits: propagated light level (0, 15] -+ // next 6 bits: propagation direction bitset -+ // next 24 bits: unused -+ // last 3 bits: state flags -+ // state flags: -+ // whether the increase propagator needs to write the propagated level to the position, used to avoid cascading light -+ // updates for block sources -+ protected static final long FLAG_WRITE_LEVEL = Long.MIN_VALUE >>> 2; -+ // whether the propagation needs to check if its current level is equal to the expected level -+ // used only in increase propagation -+ protected static final long FLAG_RECHECK_LEVEL = Long.MIN_VALUE >>> 1; -+ // whether the propagation needs to consider if its block is conditionally transparent -+ protected static final long FLAG_HAS_SIDED_TRANSPARENT_BLOCKS = Long.MIN_VALUE; -+ -+ protected long[] increaseQueue = new long[16 * 16 * 16]; -+ protected int increaseQueueInitialLength; -+ protected long[] decreaseQueue = new long[16 * 16 * 16]; -+ protected int decreaseQueueInitialLength; -+ -+ protected final long[] resizeIncreaseQueue() { -+ return this.increaseQueue = Arrays.copyOf(this.increaseQueue, this.increaseQueue.length * 2); -+ } -+ -+ protected final long[] resizeDecreaseQueue() { -+ return this.decreaseQueue = Arrays.copyOf(this.decreaseQueue, this.decreaseQueue.length * 2); -+ } -+ -+ protected final void appendToIncreaseQueue(final long value) { -+ final int idx = this.increaseQueueInitialLength++; -+ long[] queue = this.increaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ queue[idx] = value; -+ } else { -+ queue[idx] = value; -+ } -+ } -+ -+ protected final void appendToDecreaseQueue(final long value) { -+ final int idx = this.decreaseQueueInitialLength++; -+ long[] queue = this.decreaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ queue[idx] = value; -+ } else { -+ queue[idx] = value; -+ } -+ } -+ -+ protected static final AxisDirection[][] OLD_CHECK_DIRECTIONS = new AxisDirection[1 << 6][]; -+ protected static final int ALL_DIRECTIONS_BITSET = (1 << 6) - 1; -+ static { -+ for (int i = 0; i < OLD_CHECK_DIRECTIONS.length; ++i) { -+ final List directions = new ArrayList<>(); -+ for (int bitset = i, len = Integer.bitCount(i), index = 0; index < len; ++index, bitset ^= IntegerUtil.getTrailingBit(bitset)) { -+ directions.add(AXIS_DIRECTIONS[IntegerUtil.trailingZeros(bitset)]); -+ } -+ OLD_CHECK_DIRECTIONS[i] = directions.toArray(new AxisDirection[0]); -+ } -+ } -+ -+ protected final void performLightIncrease(final LightChunkGetter lightAccess) { -+ final BlockGetter world = lightAccess.getLevel(); -+ long[] queue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.increaseQueueInitialLength; -+ this.increaseQueueInitialLength = 0; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xFL); -+ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63L)]; -+ -+ if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { -+ if (this.getLightLevel(posX, posY, posZ) != propagatedLightLevel) { -+ // not at the level we expect, so something changed. -+ continue; -+ } -+ } else if ((queueValue & FLAG_WRITE_LEVEL) != 0L) { -+ // these are used to restore block sources after a propagation decrease -+ this.setLightLevel(posX, posY, posZ, propagatedLightLevel); -+ } -+ -+ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { -+ // we don't need to worry about our state here. -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int currentLevel; -+ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { -+ continue; // already at the level we want or unloaded -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); -+ if (targetLevel > currentLevel) { -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -+ if (targetLevel <= currentLevel) { -+ continue; -+ } -+ -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -+ | (flags); -+ } -+ continue; -+ } -+ } -+ } else { -+ // we actually need to worry about our state here -+ final BlockState fromBlock = this.getBlockState(posX, posY, posZ); -+ this.mutablePos2.set(posX, posY, posZ); -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final VoxelShape fromShape = fromBlock.isConditionallyFullOpaque() ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); -+ -+ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { -+ continue; -+ } -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int currentLevel; -+ -+ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { -+ continue; // already at the level we want -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); -+ if (targetLevel > currentLevel) { -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -+ if (targetLevel <= currentLevel) { -+ continue; -+ } -+ -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -+ | (flags); -+ } -+ continue; -+ } -+ } -+ } -+ } -+ } -+ -+ protected final void performLightDecrease(final LightChunkGetter lightAccess) { -+ final BlockGetter world = lightAccess.getLevel(); -+ long[] queue = this.decreaseQueue; -+ long[] increaseQueue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.decreaseQueueInitialLength; -+ this.decreaseQueueInitialLength = 0; -+ int increaseQueueLength = this.increaseQueueInitialLength; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ final int emittedMask = this.emittedLightMask; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); -+ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63)]; -+ -+ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { -+ // we don't need to worry about our state here. -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int lightLevel; -+ -+ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { -+ // already at lowest (or unloaded), nothing we can do -+ continue; -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_RECHECK_LEVEL; -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (FLAG_RECHECK_LEVEL | flags); -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (flags | FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -+ | flags; -+ } -+ continue; -+ } -+ } -+ } else { -+ // we actually need to worry about our state here -+ final BlockState fromBlock = this.getBlockState(posX, posY, posZ); -+ this.mutablePos2.set(posX, posY, posZ); -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final VoxelShape fromShape = (fromBlock.isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); -+ -+ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { -+ continue; -+ } -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int lightLevel; -+ -+ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { -+ // already at lowest (or unloaded), nothing we can do -+ continue; -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_RECHECK_LEVEL; -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (FLAG_RECHECK_LEVEL | flags); -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (flags | FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -+ | flags; -+ } -+ continue; -+ } -+ } -+ } -+ } -+ -+ // propagate sources we clobbered -+ this.increaseQueueInitialLength = increaseQueueLength; -+ this.performLightIncrease(lightAccess); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java -new file mode 100644 -index 0000000000000000000000000000000000000000..499c069d64692872924963d3a7ac39664b20468d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java -@@ -0,0 +1,674 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import ca.spottedleaf.starlight.common.util.CoordinateUtils; -+import ca.spottedleaf.starlight.common.util.WorldUtil; -+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; -+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.SectionPos; -+import net.minecraft.server.level.ServerChunkCache; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.DataLayer; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.level.lighting.LayerLightEventListener; -+import net.minecraft.world.level.lighting.LevelLightEngine; -+import java.util.ArrayDeque; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.Set; -+import java.util.concurrent.CompletableFuture; -+import java.util.function.Consumer; -+import java.util.function.IntConsumer; -+ -+public final class StarLightInterface { -+ -+ public static final TicketType CHUNK_WORK_TICKET = TicketType.create("starlight_chunk_work_ticket", (p1, p2) -> Long.compare(p1.toLong(), p2.toLong())); -+ -+ /** -+ * Can be {@code null}, indicating the light is all empty. -+ */ -+ protected final Level world; -+ protected final LightChunkGetter lightAccess; -+ -+ protected final ArrayDeque cachedSkyPropagators; -+ protected final ArrayDeque cachedBlockPropagators; -+ -+ protected final LightQueue lightQueue = new LightQueue(this); -+ -+ protected final LayerLightEventListener skyReader; -+ protected final LayerLightEventListener blockReader; -+ protected final boolean isClientSide; -+ -+ protected final int minSection; -+ protected final int maxSection; -+ protected final int minLightSection; -+ protected final int maxLightSection; -+ -+ public final LevelLightEngine lightEngine; -+ -+ private final boolean hasBlockLight; -+ private final boolean hasSkyLight; -+ -+ public StarLightInterface(final LightChunkGetter lightAccess, final boolean hasSkyLight, final boolean hasBlockLight, final LevelLightEngine lightEngine) { -+ this.lightAccess = lightAccess; -+ this.world = lightAccess == null ? null : (Level)lightAccess.getLevel(); -+ this.cachedSkyPropagators = hasSkyLight && lightAccess != null ? new ArrayDeque<>() : null; -+ this.cachedBlockPropagators = hasBlockLight && lightAccess != null ? new ArrayDeque<>() : null; -+ this.isClientSide = !(this.world instanceof ServerLevel); -+ if (this.world == null) { -+ this.minSection = -4; -+ this.maxSection = 19; -+ this.minLightSection = -5; -+ this.maxLightSection = 20; -+ } else { -+ this.minSection = WorldUtil.getMinSection(this.world); -+ this.maxSection = WorldUtil.getMaxSection(this.world); -+ this.minLightSection = WorldUtil.getMinLightSection(this.world); -+ this.maxLightSection = WorldUtil.getMaxLightSection(this.world); -+ } -+ this.lightEngine = lightEngine; -+ this.hasBlockLight = hasBlockLight; -+ this.hasSkyLight = hasSkyLight; -+ this.skyReader = !hasSkyLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() { -+ @Override -+ public void checkBlock(final BlockPos blockPos) { -+ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable()); -+ } -+ -+ @Override -+ public void propagateLightSources(final ChunkPos chunkPos) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public boolean hasLightWork() { -+ // not really correct... -+ return StarLightInterface.this.hasUpdates(); -+ } -+ -+ @Override -+ public int runLightUpdates() { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setLightEnabled(final ChunkPos chunkPos, final boolean bl) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public DataLayer getDataLayerData(final SectionPos pos) { -+ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); -+ if (chunk == null || (!StarLightInterface.this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ return null; -+ } -+ -+ final int sectionY = pos.getY(); -+ -+ if (sectionY > StarLightInterface.this.maxLightSection || sectionY < StarLightInterface.this.minLightSection) { -+ return null; -+ } -+ -+ if (chunk.getSkyEmptinessMap() == null) { -+ return null; -+ } -+ -+ return chunk.getSkyNibbles()[sectionY - StarLightInterface.this.minLightSection].toVanillaNibble(); -+ } -+ -+ @Override -+ public int getLightValue(final BlockPos blockPos) { -+ return StarLightInterface.this.getSkyLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4)); -+ } -+ -+ @Override -+ public void updateSectionStatus(final SectionPos pos, final boolean notReady) { -+ StarLightInterface.this.sectionChange(pos, notReady); -+ } -+ }; -+ this.blockReader = !hasBlockLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() { -+ @Override -+ public void checkBlock(final BlockPos blockPos) { -+ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable()); -+ } -+ -+ @Override -+ public void propagateLightSources(final ChunkPos chunkPos) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public boolean hasLightWork() { -+ // not really correct... -+ return StarLightInterface.this.hasUpdates(); -+ } -+ -+ @Override -+ public int runLightUpdates() { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setLightEnabled(final ChunkPos chunkPos, final boolean bl) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public DataLayer getDataLayerData(final SectionPos pos) { -+ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); -+ -+ if (chunk == null || pos.getY() < StarLightInterface.this.minLightSection || pos.getY() > StarLightInterface.this.maxLightSection) { -+ return null; -+ } -+ -+ return chunk.getBlockNibbles()[pos.getY() - StarLightInterface.this.minLightSection].toVanillaNibble(); -+ } -+ -+ @Override -+ public int getLightValue(final BlockPos blockPos) { -+ return StarLightInterface.this.getBlockLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4)); -+ } -+ -+ @Override -+ public void updateSectionStatus(final SectionPos pos, final boolean notReady) { -+ StarLightInterface.this.sectionChange(pos, notReady); -+ } -+ }; -+ } -+ -+ public boolean hasSkyLight() { -+ return this.hasSkyLight; -+ } -+ -+ public boolean hasBlockLight() { -+ return this.hasBlockLight; -+ } -+ -+ public int getSkyLightValue(final BlockPos blockPos, final ChunkAccess chunk) { -+ if (!this.hasSkyLight) { -+ return 0; -+ } -+ final int x = blockPos.getX(); -+ int y = blockPos.getY(); -+ final int z = blockPos.getZ(); -+ -+ final int minSection = this.minSection; -+ final int maxSection = this.maxSection; -+ final int minLightSection = this.minLightSection; -+ final int maxLightSection = this.maxLightSection; -+ -+ if (chunk == null || (!this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ return 15; -+ } -+ -+ int sectionY = y >> 4; -+ -+ if (sectionY > maxLightSection) { -+ return 15; -+ } -+ -+ if (sectionY < minLightSection) { -+ sectionY = minLightSection; -+ y = sectionY << 4; -+ } -+ -+ final SWMRNibbleArray[] nibbles = chunk.getSkyNibbles(); -+ final SWMRNibbleArray immediate = nibbles[sectionY - minLightSection]; -+ -+ if (!immediate.isNullNibbleVisible()) { -+ return immediate.getVisible(x, y, z); -+ } -+ -+ final boolean[] emptinessMap = chunk.getSkyEmptinessMap(); -+ -+ if (emptinessMap == null) { -+ return 15; -+ } -+ -+ // are we above this chunk's lowest empty section? -+ int lowestY = minLightSection - 1; -+ for (int currY = maxSection; currY >= minSection; --currY) { -+ if (emptinessMap[currY - minSection]) { -+ continue; -+ } -+ -+ // should always be full lit here -+ lowestY = currY; -+ break; -+ } -+ -+ if (sectionY > lowestY) { -+ return 15; -+ } -+ -+ // this nibble is going to depend solely on the skylight data above it -+ // find first non-null data above (there does exist one, as we just found it above) -+ for (int currY = sectionY + 1; currY <= maxLightSection; ++currY) { -+ final SWMRNibbleArray nibble = nibbles[currY - minLightSection]; -+ if (!nibble.isNullNibbleVisible()) { -+ return nibble.getVisible(x, 0, z); -+ } -+ } -+ -+ // should never reach here -+ return 15; -+ } -+ -+ public int getBlockLightValue(final BlockPos blockPos, final ChunkAccess chunk) { -+ if (!this.hasBlockLight) { -+ return 0; -+ } -+ final int y = blockPos.getY(); -+ final int cy = y >> 4; -+ -+ final int minLightSection = this.minLightSection; -+ final int maxLightSection = this.maxLightSection; -+ -+ if (cy < minLightSection || cy > maxLightSection) { -+ return 0; -+ } -+ -+ if (chunk == null) { -+ return 0; -+ } -+ -+ final SWMRNibbleArray nibble = chunk.getBlockNibbles()[cy - minLightSection]; -+ return nibble.getVisible(blockPos.getX(), y, blockPos.getZ()); -+ } -+ -+ public int getRawBrightness(final BlockPos pos, final int ambientDarkness) { -+ final ChunkAccess chunk = this.getAnyChunkNow(pos.getX() >> 4, pos.getZ() >> 4); -+ -+ final int sky = this.getSkyLightValue(pos, chunk) - ambientDarkness; -+ // Don't fetch the block light level if the skylight level is 15, since the value will never be higher. -+ if (sky == 15) return 15; -+ final int block = this.getBlockLightValue(pos, chunk); -+ return Math.max(sky, block); -+ } -+ -+ public LayerLightEventListener getSkyReader() { -+ return this.skyReader; -+ } -+ -+ public LayerLightEventListener getBlockReader() { -+ return this.blockReader; -+ } -+ -+ public boolean isClientSide() { -+ return this.isClientSide; -+ } -+ -+ public ChunkAccess getAnyChunkNow(final int chunkX, final int chunkZ) { -+ if (this.world == null) { -+ // empty world -+ return null; -+ } -+ -+ final ServerChunkCache chunkProvider = ((ServerLevel)this.world).getChunkSource(); -+ final LevelChunk fullLoaded = chunkProvider.getChunkAtIfLoadedImmediately(chunkX, chunkZ); -+ if (fullLoaded != null) { -+ return fullLoaded; -+ } -+ -+ return chunkProvider.getChunkAtImmediately(chunkX, chunkZ); -+ } -+ -+ public boolean hasUpdates() { -+ return !this.lightQueue.isEmpty(); -+ } -+ -+ public Level getWorld() { -+ return this.world; -+ } -+ -+ public LightChunkGetter getLightAccess() { -+ return this.lightAccess; -+ } -+ -+ protected final SkyStarLightEngine getSkyLightEngine() { -+ if (this.cachedSkyPropagators == null) { -+ return null; -+ } -+ final SkyStarLightEngine ret; -+ synchronized (this.cachedSkyPropagators) { -+ ret = this.cachedSkyPropagators.pollFirst(); -+ } -+ -+ if (ret == null) { -+ return new SkyStarLightEngine(this.world); -+ } -+ return ret; -+ } -+ -+ protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) { -+ if (this.cachedSkyPropagators == null) { -+ return; -+ } -+ synchronized (this.cachedSkyPropagators) { -+ this.cachedSkyPropagators.addFirst(engine); -+ } -+ } -+ -+ protected final BlockStarLightEngine getBlockLightEngine() { -+ if (this.cachedBlockPropagators == null) { -+ return null; -+ } -+ final BlockStarLightEngine ret; -+ synchronized (this.cachedBlockPropagators) { -+ ret = this.cachedBlockPropagators.pollFirst(); -+ } -+ -+ if (ret == null) { -+ return new BlockStarLightEngine(this.world); -+ } -+ return ret; -+ } -+ -+ protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) { -+ if (this.cachedBlockPropagators == null) { -+ return; -+ } -+ synchronized (this.cachedBlockPropagators) { -+ this.cachedBlockPropagators.addFirst(engine); -+ } -+ } -+ -+ public LightQueue.ChunkTasks blockChange(final BlockPos pos) { -+ if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world -+ return null; -+ } -+ -+ return this.lightQueue.queueBlockChange(pos); -+ } -+ -+ public LightQueue.ChunkTasks sectionChange(final SectionPos pos, final boolean newEmptyValue) { -+ if (this.world == null) { // empty world -+ return null; -+ } -+ -+ return this.lightQueue.queueSectionChange(pos, newEmptyValue); -+ } -+ -+ public void forceLoadInChunk(final ChunkAccess chunk, final Boolean[] emptySections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); -+ } -+ if (blockEngine != null) { -+ blockEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void loadInChunk(final int chunkX, final int chunkZ, final Boolean[] emptySections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); -+ } -+ if (blockEngine != null) { -+ blockEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void lightChunk(final ChunkAccess chunk, final Boolean[] emptySections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.light(this.lightAccess, chunk, emptySections); -+ } -+ if (blockEngine != null) { -+ blockEngine.light(this.lightAccess, chunk, emptySections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void relightChunks(final Set chunks, final Consumer chunkLightCallback, -+ final IntConsumer onComplete) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.relightChunks(this.lightAccess, chunks, blockEngine == null ? chunkLightCallback : null, -+ blockEngine == null ? onComplete : null); -+ } -+ if (blockEngine != null) { -+ blockEngine.relightChunks(this.lightAccess, chunks, chunkLightCallback, onComplete); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void checkChunkEdges(final int chunkX, final int chunkZ) { -+ this.checkSkyEdges(chunkX, chunkZ); -+ this.checkBlockEdges(chunkX, chunkZ); -+ } -+ -+ public void checkSkyEdges(final int chunkX, final int chunkZ) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ } -+ } -+ -+ public void checkBlockEdges(final int chunkX, final int chunkZ) { -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ try { -+ if (blockEngine != null) { -+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); -+ } -+ } finally { -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void checkSkyEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ } -+ } -+ -+ public void checkBlockEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ try { -+ if (blockEngine != null) { -+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); -+ } -+ } finally { -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void scheduleChunkLight(final ChunkPos pos, final Runnable run) { -+ this.lightQueue.queueChunkLighting(pos, run); -+ } -+ -+ public void removeChunkTasks(final ChunkPos pos) { -+ this.lightQueue.removeChunk(pos); -+ } -+ -+ public void propagateChanges() { -+ if (this.lightQueue.isEmpty()) { -+ return; -+ } -+ -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ LightQueue.ChunkTasks task; -+ while ((task = this.lightQueue.removeFirstTask()) != null) { -+ if (task.lightTasks != null) { -+ for (final Runnable run : task.lightTasks) { -+ run.run(); -+ } -+ } -+ -+ final long coordinate = task.chunkCoordinate; -+ final int chunkX = CoordinateUtils.getChunkX(coordinate); -+ final int chunkZ = CoordinateUtils.getChunkZ(coordinate); -+ -+ final Set positions = task.changedPositions; -+ final Boolean[] sectionChanges = task.changedSectionSet; -+ -+ if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -+ skyEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); -+ } -+ if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -+ blockEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); -+ } -+ -+ if (skyEngine != null && task.queuedEdgeChecksSky != null) { -+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksSky); -+ } -+ if (blockEngine != null && task.queuedEdgeChecksBlock != null) { -+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksBlock); -+ } -+ -+ task.onComplete.complete(null); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public static final class LightQueue { -+ -+ protected final Long2ObjectLinkedOpenHashMap chunkTasks = new Long2ObjectLinkedOpenHashMap<>(); -+ protected final StarLightInterface manager; -+ -+ public LightQueue(final StarLightInterface manager) { -+ this.manager = manager; -+ } -+ -+ public synchronized boolean isEmpty() { -+ return this.chunkTasks.isEmpty(); -+ } -+ -+ public synchronized LightQueue.ChunkTasks queueBlockChange(final BlockPos pos) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ tasks.changedPositions.add(pos.immutable()); -+ return tasks; -+ } -+ -+ public synchronized LightQueue.ChunkTasks queueSectionChange(final SectionPos pos, final boolean newEmptyValue) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ -+ if (tasks.changedSectionSet == null) { -+ tasks.changedSectionSet = new Boolean[this.manager.maxSection - this.manager.minSection + 1]; -+ } -+ tasks.changedSectionSet[pos.getY() - this.manager.minSection] = Boolean.valueOf(newEmptyValue); -+ -+ return tasks; -+ } -+ -+ public synchronized LightQueue.ChunkTasks queueChunkLighting(final ChunkPos pos, final Runnable lightTask) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ if (tasks.lightTasks == null) { -+ tasks.lightTasks = new ArrayList<>(); -+ } -+ tasks.lightTasks.add(lightTask); -+ -+ return tasks; -+ } -+ -+ public synchronized LightQueue.ChunkTasks queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ -+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksSky; -+ if (queuedEdges == null) { -+ queuedEdges = tasks.queuedEdgeChecksSky = new ShortOpenHashSet(); -+ } -+ queuedEdges.addAll(sections); -+ -+ return tasks; -+ } -+ -+ public synchronized LightQueue.ChunkTasks queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ -+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksBlock; -+ if (queuedEdges == null) { -+ queuedEdges = tasks.queuedEdgeChecksBlock = new ShortOpenHashSet(); -+ } -+ queuedEdges.addAll(sections); -+ -+ return tasks; -+ } -+ -+ public void removeChunk(final ChunkPos pos) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.remove(CoordinateUtils.getChunkKey(pos)); -+ } -+ if (tasks != null) { -+ tasks.onComplete.complete(null); -+ } -+ } -+ -+ public synchronized ChunkTasks removeFirstTask() { -+ if (this.chunkTasks.isEmpty()) { -+ return null; -+ } -+ return this.chunkTasks.removeFirst(); -+ } -+ -+ public static final class ChunkTasks { -+ -+ public final Set changedPositions = new ObjectOpenHashSet<>(); -+ public Boolean[] changedSectionSet; -+ public ShortOpenHashSet queuedEdgeChecksSky; -+ public ShortOpenHashSet queuedEdgeChecksBlock; -+ public List lightTasks; -+ -+ public boolean isTicketAdded = false; -+ public final CompletableFuture onComplete = new CompletableFuture<>(); -+ -+ public final long chunkCoordinate; -+ -+ public ChunkTasks(final long chunkCoordinate) { -+ this.chunkCoordinate = chunkCoordinate; -+ } -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java b/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java -new file mode 100644 -index 0000000000000000000000000000000000000000..16a4a14e7ccf9e4d7fdf1166674fe8f529c06d39 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java -@@ -0,0 +1,128 @@ -+package ca.spottedleaf.starlight.common.util; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.SectionPos; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.ChunkPos; -+ -+public final class CoordinateUtils { -+ -+ // dx, dz are relative to the target chunk -+ // dx, dz in [-radius, radius] -+ public static int getNeighbourMappedIndex(final int dx, final int dz, final int radius) { -+ return (dx + radius) + (2 * radius + 1)*(dz + radius); -+ } -+ -+ // the chunk keys are compatible with vanilla -+ -+ public static long getChunkKey(final BlockPos pos) { -+ return ((long)(pos.getZ() >> 4) << 32) | ((pos.getX() >> 4) & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final Entity entity) { -+ return ((long)(Mth.floor(entity.getZ()) >> 4) << 32) | ((Mth.floor(entity.getX()) >> 4) & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final ChunkPos pos) { -+ return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final SectionPos pos) { -+ return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public static int getChunkX(final long chunkKey) { -+ return (int)chunkKey; -+ } -+ -+ public static int getChunkZ(final long chunkKey) { -+ return (int)(chunkKey >>> 32); -+ } -+ -+ public static int getChunkCoordinate(final double blockCoordinate) { -+ return Mth.floor(blockCoordinate) >> 4; -+ } -+ -+ // the section keys are compatible with vanilla's -+ -+ static final int SECTION_X_BITS = 22; -+ static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1; -+ static final int SECTION_Y_BITS = 20; -+ static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1; -+ static final int SECTION_Z_BITS = 22; -+ static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1; -+ // format is y,z,x (in order of LSB to MSB) -+ static final int SECTION_Y_SHIFT = 0; -+ static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS; -+ static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS; -+ static final int SECTION_TO_BLOCK_SHIFT = 4; -+ -+ public static long getChunkSectionKey(final int x, final int y, final int z) { -+ return ((x & SECTION_X_MASK) << SECTION_X_SHIFT) -+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) -+ | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT); -+ } -+ -+ public static long getChunkSectionKey(final SectionPos pos) { -+ return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT) -+ | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT) -+ | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT); -+ } -+ -+ public static long getChunkSectionKey(final ChunkPos pos, final int y) { -+ return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT) -+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) -+ | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT); -+ } -+ -+ public static long getChunkSectionKey(final BlockPos pos) { -+ return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | -+ ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | -+ (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); -+ } -+ -+ public static long getChunkSectionKey(final Entity entity) { -+ return ((Mth.lfloor(entity.getX()) << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | -+ ((Mth.lfloor(entity.getY()) >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | -+ ((Mth.lfloor(entity.getZ()) << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); -+ } -+ -+ public static int getChunkSectionX(final long key) { -+ return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS)); -+ } -+ -+ public static int getChunkSectionY(final long key) { -+ return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS)); -+ } -+ -+ public static int getChunkSectionZ(final long key) { -+ return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS)); -+ } -+ -+ // the block coordinates are not necessarily compatible with vanilla's -+ -+ public static int getBlockCoordinate(final double blockCoordinate) { -+ return Mth.floor(blockCoordinate); -+ } -+ -+ public static long getBlockKey(final int x, final int y, final int z) { -+ return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54); -+ } -+ -+ public static long getBlockKey(final BlockPos pos) { -+ return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54); -+ } -+ -+ public static long getBlockKey(final Entity entity) { -+ return ((long)entity.getX() & 0x7FFFFFF) | (((long)entity.getZ() & 0x7FFFFFF) << 27) | ((long)entity.getY() << 54); -+ } -+ -+ private CoordinateUtils() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fabf1e97c019c7365212f40018dcd08d3b828113 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java -@@ -0,0 +1,242 @@ -+package ca.spottedleaf.starlight.common.util; -+ -+public final class IntegerUtil { -+ -+ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; -+ public static final long HIGH_BIT_U64 = Long.MIN_VALUE; -+ -+ public static int ceilLog2(final int value) { -+ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ public static long ceilLog2(final long value) { -+ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int floorLog2(final int value) { -+ // xor is optimized subtract for 2^n -1 -+ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) -+ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int floorLog2(final long value) { -+ // xor is optimized subtract for 2^n -1 -+ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) -+ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int roundCeilLog2(final int value) { -+ // optimized variant of 1 << (32 - leading(val - 1)) -+ // given -+ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) -+ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) -+ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) -+ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) -+ // HIGH_BIT_32 >>> (-1 + leading(val - 1)) -+ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); -+ } -+ -+ public static long roundCeilLog2(final long value) { -+ // see logic documented above -+ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); -+ } -+ -+ public static int roundFloorLog2(final int value) { -+ // optimized variant of 1 << (31 - leading(val)) -+ // given -+ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) -+ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) -+ // HIGH_BIT_32 >> (31 - (31 - leading(val))) -+ // HIGH_BIT_32 >> (31 - 31 + leading(val)) -+ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); -+ } -+ -+ public static long roundFloorLog2(final long value) { -+ // see logic documented above -+ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); -+ } -+ -+ public static boolean isPowerOfTwo(final int n) { -+ // 2^n has one bit -+ // note: this rets true for 0 still -+ return IntegerUtil.getTrailingBit(n) == n; -+ } -+ -+ public static boolean isPowerOfTwo(final long n) { -+ // 2^n has one bit -+ // note: this rets true for 0 still -+ return IntegerUtil.getTrailingBit(n) == n; -+ } -+ -+ public static int getTrailingBit(final int n) { -+ return -n & n; -+ } -+ -+ public static long getTrailingBit(final long n) { -+ return -n & n; -+ } -+ -+ public static int trailingZeros(final int n) { -+ return Integer.numberOfTrailingZeros(n); -+ } -+ -+ public static int trailingZeros(final long n) { -+ return Long.numberOfTrailingZeros(n); -+ } -+ -+ // from hacker's delight (signed division magic value) -+ public static int getDivisorMultiple(final long numbers) { -+ return (int)(numbers >>> 32); -+ } -+ -+ // from hacker's delight (signed division magic value) -+ public static int getDivisorShift(final long numbers) { -+ return (int)numbers; -+ } -+ -+ // copied from hacker's delight (signed division magic value) -+ // http://www.hackersdelight.org/hdcodetxt/magic.c.txt -+ public static long getDivisorNumbers(final int d) { -+ final int ad = branchlessAbs(d); -+ -+ if (ad < 2) { -+ throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d); -+ } -+ -+ final int two31 = 0x80000000; -+ final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour -+ -+ /* -+ Signed usage: -+ int number; -+ long magic = getDivisorNumbers(div); -+ long mul = magic >>> 32; -+ int sign = number >> 31; -+ int result = (int)(((long)number * mul) >>> magic) - sign; -+ */ -+ /* -+ Unsigned usage: -+ int number; -+ long magic = getDivisorNumbers(div); -+ long mul = magic >>> 32; -+ int result = (int)(((long)number * mul) >>> magic); -+ */ -+ -+ int p = 31; -+ -+ // all these variables are UNSIGNED! -+ int t = two31 + (d >>> 31); -+ int anc = t - 1 - (int)((t & mask)%ad); -+ int q1 = (int)((two31 & mask)/(anc & mask)); -+ int r1 = two31 - q1*anc; -+ int q2 = (int)((two31 & mask)/(ad & mask)); -+ int r2 = two31 - q2*ad; -+ int delta; -+ -+ do { -+ p = p + 1; -+ q1 = 2*q1; // Update q1 = 2**p/|nc|. -+ r1 = 2*r1; // Update r1 = rem(2**p, |nc|). -+ if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) -+ q1 = q1 + 1; -+ r1 = r1 - anc; -+ } -+ q2 = 2*q2; // Update q2 = 2**p/|d|. -+ r2 = 2*r2; // Update r2 = rem(2**p, |d|). -+ if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) -+ q2 = q2 + 1; -+ r2 = r2 - ad; -+ } -+ delta = ad - r2; -+ } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); -+ -+ int magicNum = q2 + 1; -+ if (d < 0) { -+ magicNum = -magicNum; -+ } -+ int shift = p; -+ return ((long)magicNum << 32) | shift; -+ } -+ -+ public static int branchlessAbs(final int val) { -+ // -n = -1 ^ n + 1 -+ final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0 -+ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 -+ } -+ -+ public static long branchlessAbs(final long val) { -+ // -n = -1 ^ n + 1 -+ final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0 -+ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 -+ } -+ -+ //https://github.com/skeeto/hash-prospector for hash functions -+ -+ //score = ~590.47984224483832 -+ public static int hash0(int x) { -+ x *= 0x36935555; -+ x ^= x >>> 16; -+ return x; -+ } -+ -+ //score = ~310.01596637036749 -+ public static int hash1(int x) { -+ x ^= x >>> 15; -+ x *= 0x356aaaad; -+ x ^= x >>> 17; -+ return x; -+ } -+ -+ public static int hash2(int x) { -+ x ^= x >>> 16; -+ x *= 0x7feb352d; -+ x ^= x >>> 15; -+ x *= 0x846ca68b; -+ x ^= x >>> 16; -+ return x; -+ } -+ -+ public static int hash3(int x) { -+ x ^= x >>> 17; -+ x *= 0xed5ad4bb; -+ x ^= x >>> 11; -+ x *= 0xac4c1b51; -+ x ^= x >>> 15; -+ x *= 0x31848bab; -+ x ^= x >>> 14; -+ return x; -+ } -+ -+ //score = ~365.79959673201887 -+ public static long hash1(long x) { -+ x ^= x >>> 27; -+ x *= 0xb24924b71d2d354bL; -+ x ^= x >>> 28; -+ return x; -+ } -+ -+ //h2 hash -+ public static long hash2(long x) { -+ x ^= x >>> 32; -+ x *= 0xd6e8feb86659fd93L; -+ x ^= x >>> 32; -+ x *= 0xd6e8feb86659fd93L; -+ x ^= x >>> 32; -+ return x; -+ } -+ -+ public static long hash3(long x) { -+ x ^= x >>> 45; -+ x *= 0xc161abe5704b6c79L; -+ x ^= x >>> 41; -+ x *= 0xe3e5389aedbc90f7L; -+ x ^= x >>> 56; -+ x *= 0x1f9aba75a52db073L; -+ x ^= x >>> 53; -+ return x; -+ } -+ -+ private IntegerUtil() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8d1fd22c8b8b0ea3afce6fc3e92057194f82669f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java -@@ -0,0 +1,192 @@ -+package ca.spottedleaf.starlight.common.util; -+ -+import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; -+import ca.spottedleaf.starlight.common.light.StarLightEngine; -+import com.mojang.logging.LogUtils; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.ListTag; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import org.slf4j.Logger; -+ -+public final class SaveUtil { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ private static final int STARLIGHT_LIGHT_VERSION = 9; -+ -+ public static int getLightVersion() { -+ return STARLIGHT_LIGHT_VERSION; -+ } -+ -+ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; -+ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; -+ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; -+ -+ public static void saveLightHook(final Level world, final ChunkAccess chunk, final CompoundTag nbt) { -+ try { -+ saveLightHookReal(world, chunk, nbt); -+ } catch (final Throwable ex) { -+ // failing to inject is not fatal so we catch anything here. if it fails, it will have correctly set lit to false -+ // for Vanilla to relight on load and it will not set our lit tag so we will relight on load -+ if (ex instanceof ThreadDeath) { -+ throw (ThreadDeath)ex; -+ } -+ LOGGER.warn("Failed to inject light data into save data for chunk " + chunk.getPos() + ", chunk light will be recalculated on its next load", ex); -+ } -+ } -+ -+ private static void saveLightHookReal(final Level world, final ChunkAccess chunk, final CompoundTag tag) { -+ if (tag == null) { -+ return; -+ } -+ -+ final int minSection = WorldUtil.getMinLightSection(world); -+ final int maxSection = WorldUtil.getMaxLightSection(world); -+ -+ SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles(); -+ SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles(); -+ -+ boolean lit = chunk.isLightCorrect() || !(world instanceof ServerLevel); -+ // diff start - store our tag for whether light data is init'd -+ if (lit) { -+ tag.putBoolean("isLightOn", false); -+ } -+ // diff end - store our tag for whether light data is init'd -+ ChunkStatus status = ChunkStatus.byName(tag.getString("Status")); -+ -+ CompoundTag[] sections = new CompoundTag[maxSection - minSection + 1]; -+ -+ ListTag sectionsStored = tag.getList("sections", 10); -+ -+ for (int i = 0; i < sectionsStored.size(); ++i) { -+ CompoundTag sectionStored = sectionsStored.getCompound(i); -+ int k = sectionStored.getByte("Y"); -+ -+ // strip light data -+ sectionStored.remove("BlockLight"); -+ sectionStored.remove("SkyLight"); -+ -+ if (!sectionStored.isEmpty()) { -+ sections[k - minSection] = sectionStored; -+ } -+ } -+ -+ if (lit && status.isOrAfter(ChunkStatus.LIGHT)) { -+ for (int i = minSection; i <= maxSection; ++i) { -+ SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState(); -+ SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState(); -+ if (blockNibble != null || skyNibble != null) { -+ CompoundTag section = sections[i - minSection]; -+ if (section == null) { -+ section = new CompoundTag(); -+ section.putByte("Y", (byte)i); -+ sections[i - minSection] = section; -+ } -+ -+ // we store under the same key so mod programs editing nbt -+ // can still read the data, hopefully. -+ // however, for compatibility we store chunks as unlit so vanilla -+ // is forced to re-light them if it encounters our data. It's too much of a burden -+ // to try and maintain compatibility with a broken and inferior skylight management system. -+ -+ if (blockNibble != null) { -+ if (blockNibble.data != null) { -+ section.putByteArray("BlockLight", blockNibble.data); -+ } -+ section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state); -+ } -+ -+ if (skyNibble != null) { -+ if (skyNibble.data != null) { -+ section.putByteArray("SkyLight", skyNibble.data); -+ } -+ section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state); -+ } -+ } -+ } -+ } -+ -+ // rewrite section list -+ sectionsStored.clear(); -+ for (CompoundTag section : sections) { -+ if (section != null) { -+ sectionsStored.add(section); -+ } -+ } -+ tag.put("sections", sectionsStored); -+ if (lit) { -+ tag.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // only mark as fully lit after we have successfully injected our data -+ } -+ } -+ -+ public static void loadLightHook(final Level world, final ChunkPos pos, final CompoundTag tag, final ChunkAccess into) { -+ try { -+ loadLightHookReal(world, pos, tag, into); -+ } catch (final Throwable ex) { -+ // failing to inject is not fatal so we catch anything here. if it fails, then we simply relight. Not a problem, we get correct -+ // lighting in both cases. -+ if (ex instanceof ThreadDeath) { -+ throw (ThreadDeath)ex; -+ } -+ LOGGER.warn("Failed to load light for chunk " + pos + ", light will be recalculated", ex); -+ } -+ } -+ -+ private static void loadLightHookReal(final Level world, final ChunkPos pos, final CompoundTag tag, final ChunkAccess into) { -+ if (into == null) { -+ return; -+ } -+ final int minSection = WorldUtil.getMinLightSection(world); -+ final int maxSection = WorldUtil.getMaxLightSection(world); -+ -+ into.setLightCorrect(false); // mark as unlit in case we fail parsing -+ -+ SWMRNibbleArray[] blockNibbles = StarLightEngine.getFilledEmptyLight(world); -+ SWMRNibbleArray[] skyNibbles = StarLightEngine.getFilledEmptyLight(world); -+ -+ -+ // start copy from the original method -+ boolean lit = tag.get("isLightOn") != null && tag.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION; -+ boolean canReadSky = world.dimensionType().hasSkyLight(); -+ ChunkStatus status = ChunkStatus.byName(tag.getString("Status")); -+ if (lit && status.isOrAfter(ChunkStatus.LIGHT)) { // diff - we add the status check here -+ ListTag sections = tag.getList("sections", 10); -+ -+ for (int i = 0; i < sections.size(); ++i) { -+ CompoundTag sectionData = sections.getCompound(i); -+ int y = sectionData.getByte("Y"); -+ -+ if (sectionData.contains("BlockLight", 7)) { -+ // this is where our diff is -+ blockNibbles[y - minSection] = new SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety -+ } else { -+ blockNibbles[y - minSection] = new SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG)); -+ } -+ -+ if (canReadSky) { -+ if (sectionData.contains("SkyLight", 7)) { -+ // we store under the same key so mod programs editing nbt -+ // can still read the data, hopefully. -+ // however, for compatibility we store chunks as unlit so vanilla -+ // is forced to re-light them if it encounters our data. It's too much of a burden -+ // to try and maintain compatibility with a broken and inferior skylight management system. -+ skyNibbles[y - minSection] = new SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety -+ } else { -+ skyNibbles[y - minSection] = new SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG)); -+ } -+ } -+ } -+ } -+ // end copy from vanilla -+ -+ into.setBlockNibbles(blockNibbles); -+ into.setSkyNibbles(skyNibbles); -+ into.setLightCorrect(lit); // now we set lit here, only after we've correctly parsed data -+ } -+ -+ private SaveUtil() {} -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dd995e25ae620ae36cd5eecb2fe10ad034ba50d2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java -@@ -0,0 +1,47 @@ -+package ca.spottedleaf.starlight.common.util; -+ -+import net.minecraft.world.level.LevelHeightAccessor; -+ -+public final class WorldUtil { -+ -+ // min, max are inclusive -+ -+ public static int getMaxSection(final LevelHeightAccessor world) { -+ return world.getMaxSection() - 1; // getMaxSection() is exclusive -+ } -+ -+ public static int getMinSection(final LevelHeightAccessor world) { -+ return world.getMinSection(); -+ } -+ -+ public static int getMaxLightSection(final LevelHeightAccessor world) { -+ return getMaxSection(world) + 1; -+ } -+ -+ public static int getMinLightSection(final LevelHeightAccessor world) { -+ return getMinSection(world) - 1; -+ } -+ -+ -+ -+ public static int getTotalSections(final LevelHeightAccessor world) { -+ return getMaxSection(world) - getMinSection(world) + 1; -+ } -+ -+ public static int getTotalLightSections(final LevelHeightAccessor world) { -+ return getMaxLightSection(world) - getMinLightSection(world) + 1; -+ } -+ -+ public static int getMinBlockY(final LevelHeightAccessor world) { -+ return getMinSection(world) << 4; -+ } -+ -+ public static int getMaxBlockY(final LevelHeightAccessor world) { -+ return (getMaxSection(world) << 4) | 15; -+ } -+ -+ private WorldUtil() { -+ throw new RuntimeException(); -+ } -+ -+} -diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index 534d9c380f26d6cce3c99fa88ad2e15410535094..e47fb2aa5e885162cae5cbfc9f33ff7864bf538e 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -42,6 +42,7 @@ public final class PaperCommand extends Command { - commands.put(Set.of("dumpitem"), new DumpItemCommand()); - commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); - commands.put(Set.of("dumplisteners"), new DumpListenersCommand()); -+ commands.put(Set.of("fixlight"), new FixLightCommand()); - - return commands.entrySet().stream() - .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) -diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..463c6d8d5b114816ed9065558285945817c30385 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java -@@ -0,0 +1,115 @@ -+package io.papermc.paper.command.subcommands; -+ -+import io.papermc.paper.command.PaperSubcommand; -+import java.util.ArrayDeque; -+import java.util.Deque; -+import io.papermc.paper.util.MCUtil; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.ThreadedLevelLightEngine; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.LevelChunk; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.entity.Player; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.BLUE; -+import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; -+import static net.kyori.adventure.text.format.NamedTextColor.GREEN; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+ -+@DefaultQualifier(NonNull.class) -+public final class FixLightCommand implements PaperSubcommand { -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ this.doFixLight(sender, args); -+ return true; -+ } -+ -+ private void doFixLight(final CommandSender sender, final String[] args) { -+ if (!(sender instanceof Player)) { -+ sender.sendMessage(text("Only players can use this command", RED)); -+ return; -+ } -+ @Nullable Runnable post = null; -+ int radius = 2; -+ if (args.length > 0) { -+ try { -+ final int parsed = Integer.parseInt(args[0]); -+ if (parsed < 0) { -+ sender.sendMessage(text("Radius cannot be negative!", RED)); -+ return; -+ } -+ final int maxRadius = 32; -+ radius = Math.min(maxRadius, parsed); -+ if (radius != parsed) { -+ post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED)); -+ } -+ } catch (final Exception e) { -+ sender.sendMessage(text("'" + args[0] + "' is not a valid number.", RED)); -+ return; -+ } -+ } -+ -+ CraftPlayer player = (CraftPlayer) sender; -+ ServerPlayer handle = player.getHandle(); -+ ServerLevel world = (ServerLevel) handle.level(); -+ ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); -+ this.starlightFixLight(handle, world, lightengine, radius, post); -+ } -+ -+ private void starlightFixLight( -+ final ServerPlayer sender, -+ final ServerLevel world, -+ final ThreadedLevelLightEngine lightengine, -+ final int radius, -+ final @Nullable Runnable done -+ ) { -+ final long start = System.nanoTime(); -+ final java.util.LinkedHashSet chunks = new java.util.LinkedHashSet<>(MCUtil.getSpiralOutChunks(sender.blockPosition(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos -+ -+ final int[] pending = new int[1]; -+ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext(); ) { -+ final ChunkPos chunkPos = iterator.next(); -+ -+ final @Nullable ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); -+ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.LIGHT)) { -+ // cannot relight this chunk -+ iterator.remove(); -+ continue; -+ } -+ -+ ++pending[0]; -+ } -+ -+ final int[] relitChunks = new int[1]; -+ lightengine.relight(chunks, -+ (final ChunkPos chunkPos) -> { -+ ++relitChunks[0]; -+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( -+ text("Relit chunk ", BLUE), text(chunkPos.toString()), -+ text(", progress: ", BLUE), text((int) (Math.round(100.0 * (double) (relitChunks[0]) / (double) pending[0])) + "%") -+ )); -+ }, -+ (final int totalRelit) -> { -+ final long end = System.nanoTime(); -+ final long diff = Math.round(1.0e-6 * (end - start)); -+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( -+ text("Relit ", BLUE), text(totalRelit), -+ text(" chunks. Took ", BLUE), text(diff + "ms") -+ )); -+ if (done != null) { -+ done.run(); -+ } -+ } -+ ); -+ sender.getBukkitEntity().sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks"))); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index facfdbb87e89f4db33ce13233c2ba4366d35c15b..807a6bb1026dac2c4cd0a50afe06fd62ce23558b 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -53,7 +53,7 @@ public class ChunkHolder { - private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage - private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage - private volatile CompletableFuture> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage -- private CompletableFuture chunkToSave; -+ public CompletableFuture chunkToSave; // Paper - public - @Nullable - private final DebugBuffer chunkToSaveHistory; - public int oldTicketLevel; -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 9dab2dd7fd77fa1006c903dc5d1f4f8339e10b91..3ae47b86b80f9156e71d1da83e492153f360d1b5 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -125,7 +125,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - private final LongSet entitiesInLevel; - public final ServerLevel level; - private final ThreadedLevelLightEngine lightEngine; -- private final BlockableEventLoop mainThreadExecutor; -+ public final BlockableEventLoop mainThreadExecutor; // Paper - public - public ChunkGenerator generator; - private final RandomState randomState; - private final ChunkGeneratorStructureState chunkGeneratorState; -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index 4e1618462840a1378dbe6492696c97544815edf2..8e8e3896040241bba8fd15f4d6d046567847f741 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -380,7 +380,7 @@ public abstract class DistanceManager { - } - - public void removeTicketsOnClosing() { -- ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve -+ ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve - ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); - - while (objectiterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -index 785ca2c63fe47936ac4c0223dffd8971a295a37c..97662f8c8c125cb964d46b9095509a0da9796dba 100644 ---- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -+++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -@@ -23,6 +23,17 @@ import net.minecraft.world.level.chunk.LightChunkGetter; - import net.minecraft.world.level.lighting.LevelLightEngine; - import org.slf4j.Logger; - -+// Paper start -+import ca.spottedleaf.starlight.common.light.StarLightEngine; -+import io.papermc.paper.util.CoordinateUtils; -+import java.util.function.Supplier; -+import net.minecraft.world.level.lighting.LayerLightEventListener; -+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import net.minecraft.world.level.chunk.ChunkStatus; -+// Paper end -+ - public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable { - public static final int DEFAULT_BATCH_SIZE = 1000; - private static final Logger LOGGER = LogUtils.getLogger(); -@@ -33,13 +44,161 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - private final int taskPerBatch = 1000; - private final AtomicBoolean scheduled = new AtomicBoolean(); - -+ // Paper start - replace light engine impl -+ protected final ca.spottedleaf.starlight.common.light.StarLightInterface theLightEngine; -+ public final boolean hasBlockLight; -+ public final boolean hasSkyLight; -+ // Paper end - replace light engine impl -+ - public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox processor, ProcessorHandle> executor) { -- super(chunkProvider, true, hasBlockLight); -+ super(chunkProvider, false, false); // Paper - destroy vanilla light engine state - this.chunkMap = chunkStorage; - this.sorterMailbox = executor; - this.taskMailbox = processor; -+ // Paper start - replace light engine impl -+ this.hasBlockLight = true; -+ this.hasSkyLight = hasBlockLight; // Nice variable name. -+ this.theLightEngine = new ca.spottedleaf.starlight.common.light.StarLightInterface(chunkProvider, this.hasSkyLight, this.hasBlockLight, this); -+ // Paper end - replace light engine impl -+ } -+ -+ // Paper start - replace light engine impl -+ protected final ChunkAccess getChunk(final int chunkX, final int chunkZ) { -+ return ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkAtImmediately(chunkX, chunkZ); -+ } -+ -+ protected long relightCounter; -+ -+ public int relight(java.util.Set chunks_param, -+ java.util.function.Consumer chunkLightCallback, -+ java.util.function.IntConsumer onComplete) { -+ if (!org.bukkit.Bukkit.isPrimaryThread()) { -+ throw new IllegalStateException("Must only be called on the main thread"); -+ } -+ -+ java.util.Set chunks = new java.util.LinkedHashSet<>(chunks_param); -+ // add tickets -+ java.util.Map ticketIds = new java.util.HashMap<>(); -+ int totalChunks = 0; -+ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext();) { -+ final ChunkPos chunkPos = iterator.next(); -+ -+ final ChunkAccess chunk = (ChunkAccess)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); -+ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ // cannot relight this chunk -+ iterator.remove(); -+ continue; -+ } -+ -+ final Long id = Long.valueOf(this.relightCounter++); -+ -+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().addTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, io.papermc.paper.util.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), id); -+ ticketIds.put(chunkPos, id); -+ -+ ++totalChunks; -+ } -+ -+ this.taskMailbox.tell(() -> { -+ this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> { -+ chunkLightCallback.accept(chunkPos); -+ ((java.util.concurrent.Executor)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().mainThreadProcessor).execute(() -> { -+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()).broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunkPos, ThreadedLevelLightEngine.this, null, null), false); -+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().removeTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, io.papermc.paper.util.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), ticketIds.get(chunkPos)); -+ }); -+ }, onComplete); -+ }); -+ this.tryScheduleUpdate(); -+ -+ return totalChunks; -+ } -+ -+ private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); -+ -+ private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, -+ final Supplier runnable) { -+ final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld(); -+ -+ final ChunkAccess center = this.theLightEngine.getAnyChunkNow(chunkX, chunkZ); -+ if (center == null || !center.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ // do not accept updates in unlit chunks, unless we might be generating a chunk. thanks to the amazing -+ // chunk scheduling, we could be lighting and generating a chunk at the same time -+ return; -+ } -+ -+ if (center.getStatus() != ChunkStatus.FULL) { -+ // do not keep chunk loaded, we are probably in a gen thread -+ // if we proceed to add a ticket the chunk will be loaded, which is not what we want (avoid cascading gen) -+ runnable.get(); -+ return; -+ } -+ -+ if (!world.getChunkSource().chunkMap.mainThreadExecutor.isSameThread()) { -+ // ticket logic is not safe to run off-main, re-schedule -+ world.getChunkSource().chunkMap.mainThreadExecutor.execute(() -> { -+ this.queueTaskForSection(chunkX, chunkY, chunkZ, runnable); -+ }); -+ return; -+ } -+ -+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ final ca.spottedleaf.starlight.common.light.StarLightInterface.LightQueue.ChunkTasks updateFuture = runnable.get(); -+ -+ if (updateFuture == null) { -+ // not scheduled -+ return; -+ } -+ -+ if (updateFuture.isTicketAdded) { -+ // ticket already added -+ return; -+ } -+ updateFuture.isTicketAdded = true; -+ -+ final int references = this.chunksBeingWorkedOn.addTo(key, 1); -+ if (references == 0) { -+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -+ world.getChunkSource().addRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); -+ } -+ -+ updateFuture.onComplete.thenAcceptAsync((final Void ignore) -> { -+ final int newReferences = this.chunksBeingWorkedOn.get(key); -+ if (newReferences == 1) { -+ this.chunksBeingWorkedOn.remove(key); -+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -+ world.getChunkSource().removeRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); -+ } else { -+ this.chunksBeingWorkedOn.put(key, newReferences - 1); -+ } -+ }, world.getChunkSource().chunkMap.mainThreadExecutor).whenComplete((final Void ignore, final Throwable thr) -> { -+ if (thr != null) { -+ LOGGER.error("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr); -+ } -+ }); - } - -+ @Override -+ public boolean hasLightWork() { -+ // route to new light engine -+ return this.theLightEngine.hasUpdates(); -+ } -+ -+ @Override -+ public LayerLightEventListener getLayerListener(final LightLayer lightType) { -+ return lightType == LightLayer.BLOCK ? this.theLightEngine.getBlockReader() : this.theLightEngine.getSkyReader(); -+ } -+ -+ @Override -+ public int getRawBrightness(final BlockPos pos, final int ambientDarkness) { -+ // need to use new light hooks for this -+ final int sky = this.theLightEngine.getSkyReader().getLightValue(pos) - ambientDarkness; -+ // Don't fetch the block light level if the skylight level is 15, since the value will never be higher. -+ if (sky == 15) return 15; -+ final int block = this.theLightEngine.getBlockReader().getLightValue(pos); -+ return Math.max(sky, block); -+ } -+ // Paper end - replace light engine imp -+ - @Override - public void close() { - } -@@ -51,15 +210,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void checkBlock(BlockPos pos) { -- BlockPos blockPos = pos.immutable(); -- this.addTask(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -- super.checkBlock(blockPos); -- }, () -> { -- return "checkBlock " + blockPos; -- })); -+ // Paper start - replace light engine impl -+ final BlockPos posCopy = pos.immutable(); -+ this.queueTaskForSection(posCopy.getX() >> 4, posCopy.getY() >> 4, posCopy.getZ() >> 4, () -> { -+ return this.theLightEngine.blockChange(posCopy); -+ }); -+ // Paper end - replace light engine impl - } - - protected void updateChunkStatus(ChunkPos pos) { -+ if (true) return; // Paper - replace light engine impl - this.addTask(pos.x, pos.z, () -> { - return 0; - }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -@@ -82,17 +242,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void updateSectionStatus(SectionPos pos, boolean notReady) { -- this.addTask(pos.x(), pos.z(), () -> { -- return 0; -- }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -- super.updateSectionStatus(pos, notReady); -- }, () -> { -- return "updateSectionStatus " + pos + " " + notReady; -- })); -+ // Paper start - replace light engine impl -+ this.queueTaskForSection(pos.getX(), pos.getY(), pos.getZ(), () -> { -+ return this.theLightEngine.sectionChange(pos, notReady); -+ }); -+ // Paper end - replace light engine impl - } - - @Override - public void propagateLightSources(ChunkPos chunkPos) { -+ if (true) return; // Paper - replace light engine impl - this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { - super.propagateLightSources(chunkPos); - }, () -> { -@@ -102,6 +261,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void setLightEnabled(ChunkPos pos, boolean retainData) { -+ if (true) return; // Paper - replace light engine impl - this.addTask(pos.x, pos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { - super.setLightEnabled(pos, retainData); - }, () -> { -@@ -111,6 +271,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void queueSectionData(LightLayer lightType, SectionPos pos, @Nullable DataLayer nibbles) { -+ if (true) return; // Paper - replace light engine impl - this.addTask(pos.x(), pos.z(), () -> { - return 0; - }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -@@ -136,6 +297,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void retainData(ChunkPos pos, boolean retainData) { -+ if (true) return; // Paper - replace light engine impl - this.addTask(pos.x, pos.z, () -> { - return 0; - }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -@@ -146,6 +308,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - public CompletableFuture initializeLight(ChunkAccess chunk, boolean bl) { -+ if (true) return CompletableFuture.completedFuture(chunk); // Paper - replace light engine impl - ChunkPos chunkPos = chunk.getPos(); - this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { - LevelChunkSection[] levelChunkSections = chunk.getSections(); -@@ -171,6 +334,37 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - public CompletableFuture lightChunk(ChunkAccess chunk, boolean excludeBlocks) { -+ // Paper start - replace light engine impl -+ if (true) { -+ boolean lit = excludeBlocks; -+ final ChunkPos chunkPos = chunk.getPos(); -+ -+ return CompletableFuture.supplyAsync(() -> { -+ final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk); -+ if (!lit) { -+ chunk.setLightCorrect(false); -+ this.theLightEngine.lightChunk(chunk, emptySections); -+ chunk.setLightCorrect(true); -+ } else { -+ this.theLightEngine.forceLoadInChunk(chunk, emptySections); -+ // can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have -+ // them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should -+ // catch what we miss here. -+ this.theLightEngine.checkChunkEdges(chunkPos.x, chunkPos.z); -+ } -+ -+ this.chunkMap.releaseLightTicket(chunkPos); -+ return chunk; -+ }, (runnable) -> { -+ this.theLightEngine.scheduleChunkLight(chunkPos, runnable); -+ this.tryScheduleUpdate(); -+ }).whenComplete((final ChunkAccess c, final Throwable throwable) -> { -+ if (throwable != null) { -+ LOGGER.error("Failed to light chunk " + chunkPos, throwable); -+ } -+ }); -+ } -+ // Paper end - replace light engine impl - ChunkPos chunkPos = chunk.getPos(); - chunk.setLightCorrect(false); - this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -@@ -191,7 +385,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - public void tryScheduleUpdate() { -- if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { -+ if (this.hasLightWork() && this.scheduled.compareAndSet(false, true)) { // Paper // Paper - rewrite light engine - this.taskMailbox.tell(() -> { - this.runUpdate(); - this.scheduled.set(false); -@@ -213,7 +407,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - objectListIterator.back(j); -- super.runLightUpdates(); -+ this.theLightEngine.propagateChanges(); // Paper - rewrite light engine - - for(int var5 = 0; objectListIterator.hasNext() && var5 < i; ++var5) { - Pair pair2 = objectListIterator.next(); -diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java -index 0d536d72ac918fbd403397ff369d10143ee9c204..6051e5f272838ef23276a90e21c2fc821ca155d1 100644 ---- a/src/main/java/net/minecraft/server/level/TicketType.java -+++ b/src/main/java/net/minecraft/server/level/TicketType.java -@@ -26,6 +26,7 @@ public class TicketType { - public static final TicketType UNKNOWN = TicketType.create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); - public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit - public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit -+ public static final TicketType CHUNK_RELIGHT = create("light_update", Long::compareTo); // Paper - ensure chunks stay loaded for lighting - - public static TicketType create(String name, Comparator argumentComparator) { - return new TicketType<>(name, argumentComparator, 0L); -diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -index 209596e89307b9e1d0ff4c465876d29fef4fc290..c3e7bd8865cc8990fc59f1ff0dfc1697cbb5ca49 100644 ---- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java -+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -@@ -107,6 +107,27 @@ public class WorldGenRegion implements WorldGenLevel { - } - } - -+ // Paper start - starlight -+ @Override -+ public int getBrightness(final net.minecraft.world.level.LightLayer lightLayer, final BlockPos blockPos) { -+ final ChunkAccess chunk = this.getChunk(blockPos.getX() >> 4, blockPos.getZ() >> 4); -+ if (!chunk.isLightCorrect()) { -+ return 0; -+ } -+ return this.getLightEngine().getLayerListener(lightLayer).getLightValue(blockPos); -+ } -+ -+ -+ @Override -+ public int getRawBrightness(final BlockPos blockPos, final int subtract) { -+ final ChunkAccess chunk = this.getChunk(blockPos.getX() >> 4, blockPos.getZ() >> 4); -+ if (!chunk.isLightCorrect()) { -+ return 0; -+ } -+ return this.getLightEngine().getRawBrightness(blockPos, subtract); -+ } -+ // Paper end - starlight -+ - public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) { - return this.level.getChunkSource().chunkMap.isOldChunkAround(chunkPos, checkRadius); - } -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 64300077fce6eb28b6bddd42b3467eaa4c80c9f5..e28ac8f7960f648099e5f3607530a406c72e5056 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -881,6 +881,7 @@ public abstract class BlockBehaviour implements FeatureElement { - this.spawnTerrainParticles = blockbase_info.spawnTerrainParticles; - this.instrument = blockbase_info.instrument; - this.replaceable = blockbase_info.replaceable; -+ this.conditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion; // Paper - } - // Paper start - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time - private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; -@@ -917,6 +918,18 @@ public abstract class BlockBehaviour implements FeatureElement { - return this.shapeExceedsCube; - } - // Paper end -+ // Paper start - starlight -+ protected int opacityIfCached = -1; -+ // ret -1 if opacity is dynamic, or -1 if the block is conditionally full opaque, else return opacity in [0, 15] -+ public final int getOpacityIfCached() { -+ return this.opacityIfCached; -+ } -+ -+ protected final boolean conditionallyFullOpaque; -+ public final boolean isConditionallyFullOpaque() { -+ return this.conditionallyFullOpaque; -+ } -+ // Paper end - starlight - - public void initCache() { - this.fluidState = ((Block) this.owner).getFluidState(this.asState()); -@@ -925,6 +938,7 @@ public abstract class BlockBehaviour implements FeatureElement { - this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState()); - } - this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here -+ this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - starlight - cache opacity for light - - this.legacySolid = this.calculateSolid(); - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -index e5e562f75e7d4b6a750f192842940c5e3af81e7d..3e5addb60ae8f466dad09edb3ae1fc88fe2681e9 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -@@ -74,7 +74,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom - @Nullable - protected BlendingData blendingData; - public final Map heightmaps = Maps.newEnumMap(Heightmap.Types.class); -- protected ChunkSkyLightSources skyLightSources; -+ // Paper - starlight - remove skyLightSources - private final Map structureStarts = Maps.newHashMap(); - private final Map structuresRefences = Maps.newHashMap(); - protected final Map pendingBlockEntities = Maps.newHashMap(); -@@ -86,8 +86,55 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom - private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); - public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY); - // CraftBukkit end -+ // Paper start - rewrite light engine -+ private volatile ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles; -+ -+ private volatile ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles; -+ -+ private volatile boolean[] skyEmptinessMap; -+ -+ private volatile boolean[] blockEmptinessMap; -+ -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { -+ return this.blockNibbles; -+ } -+ -+ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { -+ this.blockNibbles = nibbles; -+ } -+ -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { -+ return this.skyNibbles; -+ } -+ -+ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { -+ this.skyNibbles = nibbles; -+ } -+ -+ public boolean[] getSkyEmptinessMap() { -+ return this.skyEmptinessMap; -+ } -+ -+ public void setSkyEmptinessMap(final boolean[] emptinessMap) { -+ this.skyEmptinessMap = emptinessMap; -+ } -+ -+ public boolean[] getBlockEmptinessMap() { -+ return this.blockEmptinessMap; -+ } -+ -+ public void setBlockEmptinessMap(final boolean[] emptinessMap) { -+ this.blockEmptinessMap = emptinessMap; -+ } -+ // Paper end - rewrite light engine - - public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) { -+ // Paper start - rewrite light engine -+ if (!(this instanceof ImposterProtoChunk)) { -+ this.setBlockNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(heightLimitView)); -+ this.setSkyNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(heightLimitView)); -+ } -+ // Paper end - rewrite light engine - this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups - this.chunkPos = pos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key - this.upgradeData = upgradeData; -@@ -96,7 +143,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom - this.inhabitedTime = inhabitedTime; - this.postProcessing = new ShortList[heightLimitView.getSectionsCount()]; - this.blendingData = blendingData; -- this.skyLightSources = new ChunkSkyLightSources(heightLimitView); -+ // Paper - starlight - remove skyLightSources - if (sectionArray != null) { - if (this.sections.length == sectionArray.length) { - System.arraycopy(sectionArray, 0, this.sections, 0, this.sections.length); -@@ -507,12 +554,12 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom - } - - public void initializeLightSources() { -- this.skyLightSources.fillFrom(this); -+ // Paper - starlight - remove skyLightSources - } - - @Override - public ChunkSkyLightSources getSkyLightSources() { -- return this.skyLightSources; -+ return null; // Paper - starlight - remove skyLightSources - } - - public static record TicksToSave(SerializableTickContainer blocks, SerializableTickContainer fluids) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java -index d5b1fd0ff3f64675f90dd9f7f328a106e0992d51..846ae3fd184a1d63b743aa25e045604576697c96 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java -@@ -260,6 +260,17 @@ public class ChunkStatus { - return this.chunkType; - } - -+ // Paper start -+ public static ChunkStatus getStatus(String name) { -+ try { -+ // We need this otherwise we return EMPTY for invalid names -+ ResourceLocation key = new ResourceLocation(name); -+ return BuiltInRegistries.CHUNK_STATUS.getOptional(key).orElse(null); -+ } catch (Exception ex) { -+ return null; // invalid name -+ } -+ } -+ // Paper end - public static ChunkStatus byName(String id) { - return (ChunkStatus) BuiltInRegistries.CHUNK_STATUS.get(ResourceLocation.tryParse(id)); - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java -index 2ee1658532cb00d7bcd1d11e03f19d21ca7f2a9e..ac754827172a4de600d0a57a7d11853481a2dbf2 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java -@@ -21,6 +21,40 @@ public class EmptyLevelChunk extends LevelChunk { - this.biome = biomeEntry; - } - -+ // Paper start - starlight -+ @Override -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { -+ return ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(this.getLevel()); -+ } -+ -+ @Override -+ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {} -+ -+ @Override -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { -+ return ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(this.getLevel()); -+ } -+ -+ @Override -+ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {} -+ -+ @Override -+ public boolean[] getSkyEmptinessMap() { -+ return null; -+ } -+ -+ @Override -+ public void setSkyEmptinessMap(final boolean[] emptinessMap) {} -+ -+ @Override -+ public boolean[] getBlockEmptinessMap() { -+ return null; -+ } -+ -+ @Override -+ public void setBlockEmptinessMap(final boolean[] emptinessMap) {} -+ // Paper end - starlight -+ - @Override - public BlockState getBlockState(BlockPos pos) { - return Blocks.VOID_AIR.defaultBlockState(); -diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java -index 6bb508105641b5729572736c5c3f9bd6711e309a..60e760b42dd6471a229dfd45490dcf8c51979d35 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java -@@ -39,6 +39,48 @@ public class ImposterProtoChunk extends ProtoChunk { - this.allowWrites = propagateToWrapped; - } - -+ // Paper start - rewrite light engine -+ @Override -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { -+ return this.wrapped.getBlockNibbles(); -+ } -+ -+ @Override -+ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { -+ this.wrapped.setBlockNibbles(nibbles); -+ } -+ -+ @Override -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { -+ return this.wrapped.getSkyNibbles(); -+ } -+ -+ @Override -+ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { -+ this.wrapped.setSkyNibbles(nibbles); -+ } -+ -+ @Override -+ public boolean[] getSkyEmptinessMap() { -+ return this.wrapped.getSkyEmptinessMap(); -+ } -+ -+ @Override -+ public void setSkyEmptinessMap(final boolean[] emptinessMap) { -+ this.wrapped.setSkyEmptinessMap(emptinessMap); -+ } -+ -+ @Override -+ public boolean[] getBlockEmptinessMap() { -+ return this.wrapped.getBlockEmptinessMap(); -+ } -+ -+ @Override -+ public void setBlockEmptinessMap(final boolean[] emptinessMap) { -+ this.wrapped.setBlockEmptinessMap(emptinessMap); -+ } -+ // Paper end - rewrite light engine -+ - @Nullable - @Override - public BlockEntity getBlockEntity(BlockPos pos) { -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 af757309cb46af6df07872f7596b66df6d6f18d7..73e682bb3ef3b2e450ec8c594b5365c7a340615e 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -220,6 +220,12 @@ public class LevelChunk extends ChunkAccess { - - public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) { - this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData()); -+ // Paper start - rewrite light engine -+ this.setBlockNibbles(protoChunk.getBlockNibbles()); -+ this.setSkyNibbles(protoChunk.getSkyNibbles()); -+ this.setSkyEmptinessMap(protoChunk.getSkyEmptinessMap()); -+ this.setBlockEmptinessMap(protoChunk.getBlockEmptinessMap()); -+ // Paper end - rewrite light engine - Iterator iterator = protoChunk.getBlockEntities().values().iterator(); - - while (iterator.hasNext()) { -@@ -246,7 +252,7 @@ public class LevelChunk extends ChunkAccess { - } - } - -- this.skyLightSources = protoChunk.skyLightSources; -+ // Paper - starlight - remove skyLightSources - this.setLightCorrect(protoChunk.isLightCorrect()); - this.unsaved = true; - this.needsDecoration = true; // CraftBukkit -@@ -437,7 +443,7 @@ public class LevelChunk extends ChunkAccess { - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); - - gameprofilerfiller.push("updateSkyLightSources"); -- this.skyLightSources.update(this, j, i, l); -+ // Paper - starlight - remove skyLightSources - gameprofilerfiller.popPush("queueCheckLight"); - this.level.getChunkSource().getLightEngine().checkBlock(blockposition); - gameprofilerfiller.pop(); -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 dd62e257e16974a6d556a7f5e2d113a2cbc08981..dfae0918079425df92d958b04275be8ae60d4b60 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -143,7 +143,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - return this.get(this.strategy.getIndex(x, y, z)); - } - -- protected T get(int index) { -+ public T get(int index) { // Paper - public - PalettedContainer.Data data = this.data; - return data.palette.valueFor(data.storage.get(index)); - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java -index 38ec21faaa16df5485a81a581506700a5ab0a440..7da1ed9640211b0e064162dcdb0000538e7b30f3 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java -@@ -130,7 +130,7 @@ public class ProtoChunk extends ChunkAccess { - } - - if (LightEngine.hasDifferentLightProperties(this, pos, blockState, state)) { -- this.skyLightSources.update(this, m, j, o); -+ // Paper - starlight - remove skyLightSources - this.lightEngine.checkBlock(pos); - } - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index f594b5c60c723ef70e51ab30b45b90f89d6972d6..7e416ad09959a08931c207f62d97af4ee868c039 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -88,6 +88,14 @@ public class ChunkSerializer { - private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); - // Paper end - Do not let the server load chunks from newer versions -+ // Paper start - replace light engine impl -+ private static final int STARLIGHT_LIGHT_VERSION = 9; -+ -+ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; -+ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; -+ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; -+ // Paper end - replace light engine impl -+ - public ChunkSerializer() {} - - // Paper start - guard against serializing mismatching coordinates -@@ -119,13 +127,20 @@ public class ChunkSerializer { - } - - UpgradeData chunkconverter = nbt.contains("UpgradeData", 10) ? new UpgradeData(nbt.getCompound("UpgradeData"), world) : UpgradeData.EMPTY; -- boolean flag = nbt.getBoolean("isLightOn"); -+ boolean flag = getStatus(nbt) != null && getStatus(nbt).isOrAfter(ChunkStatus.LIGHT) && nbt.get("isLightOn") != null && nbt.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION; // Paper - ListTag nbttaglist = nbt.getList("sections", 10); - int i = world.getSectionsCount(); - LevelChunkSection[] achunksection = new LevelChunkSection[i]; - boolean flag1 = world.dimensionType().hasSkyLight(); - ServerChunkCache chunkproviderserver = world.getChunkSource(); - LevelLightEngine levellightengine = chunkproviderserver.getLightEngine(); -+ // Paper start -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world); -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world); -+ final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world); -+ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world); -+ boolean canReadSky = world.dimensionType().hasSkyLight(); -+ // Paper end - Registry iregistry = world.registryAccess().registryOrThrow(Registries.BIOME); - Codec>> codec = ChunkSerializer.makeBiomeCodecRW(iregistry); // CraftBukkit - read/write - boolean flag2 = false; -@@ -133,7 +148,7 @@ public class ChunkSerializer { - DataResult dataresult; - - for (int j = 0; j < nbttaglist.size(); ++j) { -- CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); -+ CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); CompoundTag sectionData = nbttagcompound1; // Paper - byte b0 = nbttagcompound1.getByte("Y"); - int k = world.getSectionIndexFromSectionY(b0); - -@@ -176,19 +191,39 @@ public class ChunkSerializer { - boolean flag3 = nbttagcompound1.contains("BlockLight", 7); - boolean flag4 = flag1 && nbttagcompound1.contains("SkyLight", 7); - -- if (flag3 || flag4) { -- if (!flag2) { -- levellightengine.retainData(chunkPos, true); -- flag2 = true; -- } -- -+ // Paper start - rewrite the light engine -+ if (flag) { -+ try { -+ int y = sectionData.getByte("Y"); -+ // Paper end - rewrite the light engine - if (flag3) { -- levellightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("BlockLight"))); -+ // Paper start - rewrite the light engine -+ // this is where our diff is -+ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety -+ } else { -+ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG)); -+ // Paper end - rewrite the light engine - } - - if (flag4) { -- levellightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("SkyLight"))); -+ // Paper start - rewrite the light engine -+ // we store under the same key so mod programs editing nbt -+ // can still read the data, hopefully. -+ // however, for compatibility we store chunks as unlit so vanilla -+ // is forced to re-light them if it encounters our data. It's too much of a burden -+ // to try and maintain compatibility with a broken and inferior skylight management system. -+ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety -+ } else if (flag1) { -+ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG)); -+ // Paper end - rewrite the light engine -+ } -+ -+ // Paper start - rewrite the light engine -+ } catch (Exception ex) { -+ LOGGER.warn("Failed to load light data for chunk " + chunkPos + " in world '" + world.getWorld().getName() + "', light will be regenerated", ex); -+ flag = false; - } -+ // Paper end - rewrite light engine - } - } - -@@ -217,6 +252,8 @@ public class ChunkSerializer { - }, chunkPos); - - object1 = new LevelChunk(world.getLevel(), chunkPos, chunkconverter, levelchunkticks, levelchunkticks1, l, achunksection, ChunkSerializer.postLoadChunk(world, nbt), blendingdata); -+ ((LevelChunk)object1).setBlockNibbles(blockNibbles); // Paper - replace light impl -+ ((LevelChunk)object1).setSkyNibbles(skyNibbles); // Paper - replace light impl - } else { - ProtoChunkTicks protochunkticklist = ProtoChunkTicks.load(nbt.getList("block_ticks", 10), (s) -> { - return BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(s)); -@@ -225,6 +262,8 @@ public class ChunkSerializer { - return BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(s)); - }, chunkPos); - ProtoChunk protochunk = new ProtoChunk(chunkPos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world, iregistry, blendingdata); -+ protochunk.setBlockNibbles(blockNibbles); // Paper - replace light impl -+ protochunk.setSkyNibbles(skyNibbles); // Paper - replace light impl - - object1 = protochunk; - protochunk.setInhabitedTime(l); -@@ -346,6 +385,12 @@ public class ChunkSerializer { - // CraftBukkit end - - public static CompoundTag write(ServerLevel world, ChunkAccess chunk) { -+ // Paper start - rewrite light impl -+ final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world); -+ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world); -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles(); -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles(); -+ // Paper end - rewrite light impl - ChunkPos chunkcoordintpair = chunk.getPos(); - CompoundTag nbttagcompound = NbtUtils.addCurrentDataVersion(new CompoundTag()); - -@@ -395,11 +440,14 @@ public class ChunkSerializer { - for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) { - int j = chunk.getSectionIndexFromSectionY(i); - boolean flag1 = j >= 0 && j < achunksection.length; -- DataLayer nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); -- DataLayer nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); -+ // Paper - replace light engine - -- if (flag1 || nibblearray != null || nibblearray1 != null) { -- CompoundTag nbttagcompound1 = new CompoundTag(); -+ // Paper start - replace light engine -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState(); -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState(); -+ if (flag1 || blockNibble != null || skyNibble != null) { -+ // Paper end - replace light engine -+ CompoundTag nbttagcompound1 = new CompoundTag(); CompoundTag section = nbttagcompound1; // Paper - - if (flag1) { - LevelChunkSection chunksection = achunksection[j]; -@@ -414,13 +462,27 @@ public class ChunkSerializer { - nbttagcompound1.put("biomes", (Tag) dataresult1.getOrThrow(false, logger1::error)); - } - -- if (nibblearray != null && !nibblearray.isEmpty()) { -- nbttagcompound1.putByteArray("BlockLight", nibblearray.getData()); -+ // Paper start -+ // we store under the same key so mod programs editing nbt -+ // can still read the data, hopefully. -+ // however, for compatibility we store chunks as unlit so vanilla -+ // is forced to re-light them if it encounters our data. It's too much of a burden -+ // to try and maintain compatibility with a broken and inferior skylight management system. -+ -+ if (blockNibble != null) { -+ if (blockNibble.data != null) { -+ section.putByteArray("BlockLight", blockNibble.data); -+ } -+ section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state); - } - -- if (nibblearray1 != null && !nibblearray1.isEmpty()) { -- nbttagcompound1.putByteArray("SkyLight", nibblearray1.getData()); -+ if (skyNibble != null) { -+ if (skyNibble.data != null) { -+ section.putByteArray("SkyLight", skyNibble.data); -+ } -+ section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state); - } -+ // Paper end - - if (!nbttagcompound1.isEmpty()) { - nbttagcompound1.putByte("Y", (byte) i); -@@ -431,7 +493,8 @@ public class ChunkSerializer { - - nbttagcompound.put("sections", nbttaglist); - if (flag) { -- nbttagcompound.putBoolean("isLightOn", true); -+ nbttagcompound.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // Paper -+ nbttagcompound.putBoolean("isLightOn", false); // Paper - set to false but still store, this allows us to detect --eraseCache (as eraseCache _removes_) - } - - ListTag nbttaglist1 = new ListTag(); -@@ -505,6 +568,17 @@ public class ChunkSerializer { - })); - } - -+ // Paper start -+ public static @Nullable ChunkStatus getStatus(@Nullable CompoundTag compound) { -+ if (compound == null) { -+ return null; -+ } -+ -+ // Note: Copied from below -+ return ChunkStatus.getStatus(compound.getString("Status")); -+ } -+ // Paper end -+ - public static ChunkStatus.ChunkType getChunkTypeFromTag(@Nullable CompoundTag nbt) { - return nbt != null ? ChunkStatus.byName(nbt.getString("Status")).getChunkType() : ChunkStatus.ChunkType.PROTOCHUNK; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 4ae206af89a413edb09319fd4bce2a94c575c617..5cc2deb8f170452c7049743068bf281f67687db9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -490,12 +490,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - } - } - -- for (final ChunkPos pos : chunksToRelight) { -- final ChunkAccess chunk = serverChunkCache.getChunk(pos.x, pos.z, false); -- if (chunk != null) { -- serverChunkCache.getLightEngine().lightChunk(chunk, false); -- } -- } -+ serverChunkCache.getLightEngine().relight(chunksToRelight, pos -> {}, relit -> {}); // Paper - Starlight - - return true; - // Paper end - implement regenerate chunk method diff --git a/patches/server/0997-Allow-Saving-of-Oversized-Chunks.patch b/patches/server/0991-Allow-Saving-of-Oversized-Chunks.patch similarity index 100% rename from patches/server/0997-Allow-Saving-of-Oversized-Chunks.patch rename to patches/server/0991-Allow-Saving-of-Oversized-Chunks.patch diff --git a/patches/server/0991-Rewrite-chunk-system.patch b/patches/server/0991-Rewrite-chunk-system.patch deleted file mode 100644 index 0b54a341b427..000000000000 --- a/patches/server/0991-Rewrite-chunk-system.patch +++ /dev/null @@ -1,21682 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 11 Mar 2021 02:32:30 -0800 -Subject: [PATCH] Rewrite chunk system - -Rebased patches: - -New player chunk loader system - -Make ChunkStatus.EMPTY not rely on the main thread for completion - -In order to do this, we need to push the POI consistency checks -to a later status. Since FULL is the only other status that -uses the main thread, it can go there. - -The consistency checks are only really for when a desync occurs, -and so that delaying the check only matters when the chunk data -has desync'd. As long as the desync is sorted before the -chunk is full loaded (i.e before setBlock can occur on -a chunk), it should not matter. - -This change is primarily due to behavioural changes -in the chunk task queue brought by region threading - -which is to split the queue into separate regions. As such, -it is required that in order for the sync load to complete -that the region owning the chunk drain and execute the task -while ticking. However, that is not always possible in -region threading. Thus, removing the main thread reliance allows -the chunk to progress without requiring a tick thread. -Specifically, this allows far sync loads (outside of a specific -regions bounds) to occur without issue - namely with structure -searching. - -Increase parallelism for neighbour writing chunk statuses - -Namely, everything after FEATURES. By creating a dependency -chain indicating what chunks are in use, we can safely -schedule completely independent tasks in parallel. This -will allow the chunk system to scale beyond 10 threads -per world. - -Properly cancel chunk load tasks that were not scheduled - -Since the chunk load task was not scheduled, the entity/poi load -task fields will not be set, but the task complete counter -will not be adjusted. Thus, the chunk load task will not complete. - -To resolve this, detect when the entity/poi tasks were not scheduled -and decrement the task complete counter in such cases. - -Mark POI/Entity load tasks as completed before releasing scheduling lock - -It must be marked as completed during that lock hold since the -waiters field is set to null. Thus, any other thread attempting -a cancellation will fail to remove from waiters. Also, any -other thread attempting to cancel may set the completed field -to true which would cause accept() to fail as well. - -Completion was always designed to happen while holding the -scheduling lock to prevent these race conditions. The code -was originally set up to complete while not holding the -scheduling lock to avoid invoking callbacks while holding the -lock, however the access to the completion field was not -considered. - -Resolve this by marking the callback as completed during the -lock, but invoking the accept() function after releasing -the lock. This will prevent any cancellation attempts to be -blocked, and allow the current thread to complete the callback -without any issues. - -Cache whether region files do not exist - -The repeated I/O of creating the directory for the regionfile -or for checking if the file exists can be heavy in -when pushing chunk generation extremely hard - as each chunk gen -request may effectively go through to the I/O thread. - -Use coordinate-based locking to increase chunk system parallelism - -A significant overhead in Folia comes from the chunk system's -locks, the ticket lock and the scheduling lock. The public -test server, which had ~330 players, had signficant performance -problems with these locks: ~80% of the time spent ticking -was _waiting_ for the locks to free. Given that it used -around 15 cores total at peak, this is a complete and utter loss -of potential. - -To address this issue, I have replaced the ticket lock and scheduling -lock with two ReentrantAreaLocks. The ReentrantAreaLock takes a -shift, which is used internally to group positions into sections. -This grouping is neccessary, as the possible radius of area that -needs to be acquired for any given lock usage is up to 64. As such, -the shift is critical to reduce the number of areas required to lock -for any lock operation. Currently, it is set to a shift of 6, which -is identical to the ticket level propagation shift (and, it must be -at least the ticket level propagation shift AND the region shift). - -The chunk system locking changes required a complete rewrite of the -chunk system tick, chunk system unload, and chunk system ticket level -propagation - as all of the previous logic only works with a single -global lock. - -This does introduce two other section shifts: the lock shift, and the -ticket shift. The lock shift is simply what shift the area locks use, -and the ticket shift represents the size of the ticket sections. -Currently, these values are just set to the region shift for simplicity. -However, they are not arbitrary: the lock shift must be at least the size -of the ticket shift and must be at least the size of the region shift. -The ticket shift must also be >= the ceil(log2(max ticket level source)). - -The chunk system's ticket propagator is now global state, instead of -region state. This cleans up the logic for ticket levels significantly, -and removes usage of the region lock in this area, but it also means -that the addition of a ticket no longer creates a region. To alleviate -the side effects of this change, the global tick thread now processes -ticket level updates for each world every tick to guarantee eventual -ticket level processing. The chunk system also provides a hook to -process ticket level changes in a given _section_, so that the -region queue can guarantee that after adding its reference counter -that the region section is created/exists/wont be destroyed. - -The ticket propagator operates by updating the sources in a single ticket -section, and propagating the updates to its 1 radius neighbours. This -allows the ticket updates to occur in parallel or selectively (see above). -Currently, the process ticket level update function operates by -polling from a concurrent queue of sections to update and simply -invoking the single section update logic. This allows the function -to operate completely in parallel, provided the queue is ordered right. -Additionally, this limits the area used in the ticket/scheduling lock -when processing updates, which should massively increase parallelism compared -to before. - -The chunk system ticket addition for expirable ticket types has been modified -to no longer track exact tick deadlines, as this relies on what region the -ticket is in. Instead, the chunk system tracks a map of -lock section -> (chunk coordinate -> expire ticket count) and every ticket -has been changed to have a removeDelay count that is decremented each tick. -Each region searches its own sections to find tickets to try to expire. - -Chunk system unloading has been modified to track unloads by lock section. -The ordering is determined by which section a chunk resides in. -The unload process now removes from unload sections and processes -the full unload stages (1, 2, 3) before moving to the next section, if possible. -This allows the unload logic to only hold one lock section at a time for -each lock, which is a massive parallelism increase. - -In stress testing, these changes lowered the locking overhead to only 5% -from ~70%, which completely fix the original problem as described. - -== AT == -public net.minecraft.server.level.ChunkHolder pos -public net.minecraft.server.level.ChunkMap overworldDataStorage -public-f net.minecraft.world.level.chunk.storage.RegionFileStorage -public net.minecraft.server.level.ChunkMap getPoiManager()Lnet/minecraft/world/entity/ai/village/poi/PoiManager; - -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4fd9a0cd8f1e6ae1a97e963dc7731a80bc6fac5b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java -@@ -0,0 +1,395 @@ -+package ca.spottedleaf.concurrentutil.lock; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import it.unimi.dsi.fastutil.HashCommon; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.locks.LockSupport; -+ -+public final class ReentrantAreaLock { -+ -+ public final int coordinateShift; -+ -+ // aggressive load factor to reduce contention -+ private final ConcurrentHashMap nodes = new ConcurrentHashMap<>(128, 0.2f); -+ -+ public ReentrantAreaLock(final int coordinateShift) { -+ this.coordinateShift = coordinateShift; -+ } -+ -+ public boolean isHeldByCurrentThread(final int x, final int z) { -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int sectionX = x >> shift; -+ final int sectionZ = z >> shift; -+ -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ final Node node = this.nodes.get(coordinate); -+ -+ return node != null && node.thread == currThread; -+ } -+ -+ public boolean isHeldByCurrentThread(final int centerX, final int centerZ, final int radius) { -+ return this.isHeldByCurrentThread(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } -+ -+ public boolean isHeldByCurrentThread(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; -+ -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(currX, currZ)); -+ -+ final Node node = this.nodes.get(coordinate); -+ -+ if (node == null || node.thread != currThread) { -+ return false; -+ } -+ } -+ } -+ -+ return true; -+ } -+ -+ public Node tryLock(final int x, final int z) { -+ return this.tryLock(x, z, x, z); -+ } -+ -+ public Node tryLock(final int centerX, final int centerZ, final int radius) { -+ return this.tryLock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } -+ -+ public Node tryLock(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; -+ -+ final List areaAffected = new ArrayList<>(); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ -+ boolean failed = false; -+ -+ // try to fast acquire area -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(currX, currZ)); -+ -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ areaAffected.add(coordinate); -+ continue; -+ } -+ -+ if (prev.thread != currThread) { -+ failed = true; -+ break; -+ } -+ } -+ } -+ -+ if (!failed) { -+ return ret; -+ } -+ -+ // failed, undo logic -+ if (!areaAffected.isEmpty()) { -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final Coordinate key = areaAffected.get(i); -+ -+ if (this.nodes.remove(key) != ret) { -+ throw new IllegalStateException(); -+ } -+ } -+ -+ areaAffected.clear(); -+ -+ // since we inserted, we need to drain waiters -+ Thread unpark; -+ while ((unpark = ret.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ return null; -+ } -+ -+ public Node lock(final int x, final int z) { -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int sectionX = x >> shift; -+ final int sectionZ = z >> shift; -+ -+ final List areaAffected = new ArrayList<>(1); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ -+ for (long failures = 0L;;) { -+ final Node park; -+ -+ // try to fast acquire area -+ { -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ areaAffected.add(coordinate); -+ return ret; -+ } else if (prev.thread != currThread) { -+ park = prev; -+ } else { -+ // only one node we would want to acquire, and it's owned by this thread already -+ return ret; -+ } -+ } -+ -+ ++failures; -+ -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; -+ } -+ } -+ } -+ } -+ -+ public Node lock(final int centerX, final int centerZ, final int radius) { -+ return this.lock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } -+ -+ public Node lock(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; -+ -+ if (((fromSectionX ^ toSectionX) | (fromSectionZ ^ toSectionZ)) == 0) { -+ return this.lock(fromX, fromZ); -+ } -+ -+ final List areaAffected = new ArrayList<>(); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ -+ for (long failures = 0L;;) { -+ Node park = null; -+ boolean addedToArea = false; -+ boolean alreadyOwned = false; -+ boolean allOwned = true; -+ -+ // try to fast acquire area -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(currX, currZ)); -+ -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ addedToArea = true; -+ allOwned = false; -+ areaAffected.add(coordinate); -+ continue; -+ } -+ -+ if (prev.thread != currThread) { -+ park = prev; -+ alreadyOwned = true; -+ break; -+ } -+ } -+ } -+ -+ if (park == null) { -+ if (alreadyOwned && !allOwned) { -+ throw new IllegalStateException("Improper lock usage: Should never acquire intersecting areas"); -+ } -+ return ret; -+ } -+ -+ // failed, undo logic -+ if (addedToArea) { -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final Coordinate key = areaAffected.get(i); -+ -+ if (this.nodes.remove(key) != ret) { -+ throw new IllegalStateException(); -+ } -+ } -+ -+ areaAffected.clear(); -+ -+ // since we inserted, we need to drain waiters -+ Thread unpark; -+ while ((unpark = ret.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ ++failures; -+ -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(park); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; -+ } -+ } -+ -+ if (addedToArea) { -+ // try again, so we need to allow adds so that other threads can properly block on us -+ ret.allowAdds(); -+ } -+ } -+ } -+ -+ public void unlock(final Node node) { -+ if (node.lock != this) { -+ throw new IllegalStateException("Unlock target lock mismatch"); -+ } -+ -+ final List areaAffected = node.areaAffected; -+ -+ if (areaAffected.isEmpty()) { -+ // here we are not in the node map, and so do not need to remove from the node map or unblock any waiters -+ return; -+ } -+ -+ // remove from node map; allowing other threads to lock -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final Coordinate coordinate = areaAffected.get(i); -+ if (this.nodes.remove(coordinate) != node) { -+ throw new IllegalStateException(); -+ } -+ } -+ -+ Thread unpark; -+ while ((unpark = node.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ public static final class Node extends MultiThreadedQueue { -+ -+ private final ReentrantAreaLock lock; -+ private final List areaAffected; -+ private final Thread thread; -+ //private final Throwable WHO_CREATED_MY_ASS = new Throwable(); -+ -+ private Node(final ReentrantAreaLock lock, final List areaAffected, final Thread thread) { -+ this.lock = lock; -+ this.areaAffected = areaAffected; -+ this.thread = thread; -+ } -+ -+ @Override -+ public String toString() { -+ return "Node{" + -+ "areaAffected=" + this.areaAffected + -+ ", thread=" + this.thread + -+ '}'; -+ } -+ } -+ -+ private static final class Coordinate implements Comparable { -+ -+ public final long key; -+ -+ public Coordinate(final long key) { -+ this.key = key; -+ } -+ -+ public Coordinate(final int x, final int z) { -+ this.key = key(x, z); -+ } -+ -+ public static long key(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public static int x(final long key) { -+ return (int)key; -+ } -+ -+ public static int z(final long key) { -+ return (int)(key >>> 32); -+ } -+ -+ @Override -+ public int hashCode() { -+ return (int)HashCommon.mix(this.key); -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (!(obj instanceof Coordinate other)) { -+ return false; -+ } -+ -+ return this.key == other.key; -+ } -+ -+ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain -+ // is too large. So we should implement compareTo to help. -+ @Override -+ public int compareTo(final Coordinate other) { -+ return Long.compare(this.key, other.key); -+ } -+ -+ @Override -+ public String toString() { -+ return "[" + x(this.key) + "," + z(this.key) + "]"; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/lock/SyncReentrantAreaLock.java b/src/main/java/ca/spottedleaf/concurrentutil/lock/SyncReentrantAreaLock.java -new file mode 100644 -index 0000000000000000000000000000000000000000..64b5803d002b2968841a5ddee987f98b72964e87 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/lock/SyncReentrantAreaLock.java -@@ -0,0 +1,217 @@ -+package ca.spottedleaf.concurrentutil.lock; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import java.util.concurrent.locks.LockSupport; -+ -+// not concurrent, unlike ReentrantAreaLock -+// no incorrect lock usage detection (acquiring intersecting areas) -+// this class is nothing more than a performance reference for ReentrantAreaLock -+public final class SyncReentrantAreaLock { -+ -+ private final int coordinateShift; -+ -+ // aggressive load factor to reduce contention -+ private final Long2ReferenceOpenHashMap nodes = new Long2ReferenceOpenHashMap<>(128, 0.2f); -+ -+ public SyncReentrantAreaLock(final int coordinateShift) { -+ this.coordinateShift = coordinateShift; -+ } -+ -+ private static long key(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public Node lock(final int x, final int z) { -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int sectionX = x >> shift; -+ final int sectionZ = z >> shift; -+ -+ final LongArrayList areaAffected = new LongArrayList(); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ -+ final long coordinate = key(sectionX, sectionZ); -+ -+ for (long failures = 0L;;) { -+ final Node park; -+ -+ synchronized (this) { -+ // try to fast acquire area -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ areaAffected.add(coordinate); -+ return ret; -+ } else if (prev.thread != currThread) { -+ park = prev; -+ } else { -+ // only one node we would want to acquire, and it's owned by this thread already -+ return ret; -+ } -+ } -+ -+ ++failures; -+ -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; -+ } -+ } -+ } -+ } -+ -+ public Node lock(final int centerX, final int centerZ, final int radius) { -+ return this.lock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } -+ -+ public Node lock(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; -+ -+ final LongArrayList areaAffected = new LongArrayList(); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ -+ for (long failures = 0L;;) { -+ Node park = null; -+ boolean addedToArea = false; -+ -+ synchronized (this) { -+ // try to fast acquire area -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final long coordinate = key(currX, currZ); -+ -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ addedToArea = true; -+ areaAffected.add(coordinate); -+ continue; -+ } -+ -+ if (prev.thread != currThread) { -+ park = prev; -+ break; -+ } -+ } -+ } -+ -+ if (park == null) { -+ return ret; -+ } -+ -+ // failed, undo logic -+ if (!areaAffected.isEmpty()) { -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final long key = areaAffected.getLong(i); -+ -+ if (!this.nodes.remove(key, ret)) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ } -+ -+ if (addedToArea) { -+ areaAffected.clear(); -+ // since we inserted, we need to drain waiters -+ Thread unpark; -+ while ((unpark = ret.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ ++failures; -+ -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; -+ } -+ } -+ -+ if (addedToArea) { -+ // try again, so we need to allow adds so that other threads can properly block on us -+ ret.allowAdds(); -+ } -+ } -+ } -+ -+ public void unlock(final Node node) { -+ if (node.lock != this) { -+ throw new IllegalStateException("Unlock target lock mismatch"); -+ } -+ -+ final LongArrayList areaAffected = node.areaAffected; -+ -+ if (areaAffected.isEmpty()) { -+ // here we are not in the node map, and so do not need to remove from the node map or unblock any waiters -+ return; -+ } -+ -+ // remove from node map; allowing other threads to lock -+ synchronized (this) { -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final long coordinate = areaAffected.getLong(i); -+ if (!this.nodes.remove(coordinate, node)) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ -+ Thread unpark; -+ while ((unpark = node.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ public static final class Node extends MultiThreadedQueue { -+ -+ private final SyncReentrantAreaLock lock; -+ private final LongArrayList areaAffected; -+ private final Thread thread; -+ -+ private Node(final SyncReentrantAreaLock lock, final LongArrayList areaAffected, final Thread thread) { -+ this.lock = lock; -+ this.areaAffected = areaAffected; -+ this.thread = thread; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java -index 499c069d64692872924963d3a7ac39664b20468d..ef8ea36b2acefb935afda01396d2699e2921f396 100644 ---- a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java -@@ -41,14 +41,14 @@ public final class StarLightInterface { - protected final ArrayDeque cachedSkyPropagators; - protected final ArrayDeque cachedBlockPropagators; - -- protected final LightQueue lightQueue = new LightQueue(this); -+ public final io.papermc.paper.chunk.system.light.LightQueue lightQueue; // Paper - replace light queue - - protected final LayerLightEventListener skyReader; - protected final LayerLightEventListener blockReader; - protected final boolean isClientSide; - -- protected final int minSection; -- protected final int maxSection; -+ public final int minSection; // Paper - public -+ public final int maxSection; // Paper - public - protected final int minLightSection; - protected final int maxLightSection; - -@@ -182,6 +182,7 @@ public final class StarLightInterface { - StarLightInterface.this.sectionChange(pos, notReady); - } - }; -+ this.lightQueue = new io.papermc.paper.chunk.system.light.LightQueue(this); // Paper - replace light queue - } - - public boolean hasSkyLight() { -@@ -333,7 +334,7 @@ public final class StarLightInterface { - return this.lightAccess; - } - -- protected final SkyStarLightEngine getSkyLightEngine() { -+ public final SkyStarLightEngine getSkyLightEngine() { // Paper - public - if (this.cachedSkyPropagators == null) { - return null; - } -@@ -348,7 +349,7 @@ public final class StarLightInterface { - return ret; - } - -- protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) { -+ public final void releaseSkyLightEngine(final SkyStarLightEngine engine) { // Paper - public - if (this.cachedSkyPropagators == null) { - return; - } -@@ -357,7 +358,7 @@ public final class StarLightInterface { - } - } - -- protected final BlockStarLightEngine getBlockLightEngine() { -+ public final BlockStarLightEngine getBlockLightEngine() { // Paper - public - if (this.cachedBlockPropagators == null) { - return null; - } -@@ -372,7 +373,7 @@ public final class StarLightInterface { - return ret; - } - -- protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) { -+ public final void releaseBlockLightEngine(final BlockStarLightEngine engine) { // Paper - public - if (this.cachedBlockPropagators == null) { - return; - } -@@ -381,7 +382,7 @@ public final class StarLightInterface { - } - } - -- public LightQueue.ChunkTasks blockChange(final BlockPos pos) { -+ public io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks blockChange(final BlockPos pos) { // Paper - rewrite chunk system - if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world - return null; - } -@@ -389,7 +390,7 @@ public final class StarLightInterface { - return this.lightQueue.queueBlockChange(pos); - } - -- public LightQueue.ChunkTasks sectionChange(final SectionPos pos, final boolean newEmptyValue) { -+ public io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks sectionChange(final SectionPos pos, final boolean newEmptyValue) { // Paper - rewrite chunk system - if (this.world == null) { // empty world - return null; - } -@@ -519,57 +520,15 @@ public final class StarLightInterface { - } - - public void scheduleChunkLight(final ChunkPos pos, final Runnable run) { -- this.lightQueue.queueChunkLighting(pos, run); -+ throw new UnsupportedOperationException("No longer implemented, use the new lightQueue field to queue tasks"); // Paper - replace light queue - } - - public void removeChunkTasks(final ChunkPos pos) { -- this.lightQueue.removeChunk(pos); -+ throw new UnsupportedOperationException("No longer implemented, use the new lightQueue field to queue tasks"); // Paper - replace light queue - } - - public void propagateChanges() { -- if (this.lightQueue.isEmpty()) { -- return; -- } -- -- final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -- final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -- -- try { -- LightQueue.ChunkTasks task; -- while ((task = this.lightQueue.removeFirstTask()) != null) { -- if (task.lightTasks != null) { -- for (final Runnable run : task.lightTasks) { -- run.run(); -- } -- } -- -- final long coordinate = task.chunkCoordinate; -- final int chunkX = CoordinateUtils.getChunkX(coordinate); -- final int chunkZ = CoordinateUtils.getChunkZ(coordinate); -- -- final Set positions = task.changedPositions; -- final Boolean[] sectionChanges = task.changedSectionSet; -- -- if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -- skyEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); -- } -- if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -- blockEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); -- } -- -- if (skyEngine != null && task.queuedEdgeChecksSky != null) { -- skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksSky); -- } -- if (blockEngine != null && task.queuedEdgeChecksBlock != null) { -- blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksBlock); -- } -- -- task.onComplete.complete(null); -- } -- } finally { -- this.releaseSkyLightEngine(skyEngine); -- this.releaseBlockLightEngine(blockEngine); -- } -+ throw new UnsupportedOperationException("No longer implemented, task draining is now performed by the light thread"); // Paper - replace light queue - } - - public static final class LightQueue { -diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java -index 2f0d9b953802dee821cfde82d22b0567cce8ee91..22687667ec69a954261e55e59261286ac1b8b8cd 100644 ---- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java -+++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java -@@ -59,6 +59,16 @@ public class WorldTimingsHandler { - - public final Timing miscMobSpawning; - -+ public final Timing poiUnload; -+ public final Timing chunkUnload; -+ public final Timing poiSaveDataSerialization; -+ public final Timing chunkSave; -+ public final Timing chunkSaveDataSerialization; -+ public final Timing chunkSaveIOWait; -+ public final Timing chunkUnloadPrepareSave; -+ public final Timing chunkUnloadPOISerialization; -+ public final Timing chunkUnloadDataSave; -+ - public WorldTimingsHandler(Level server) { - String name = ((PrimaryLevelData) server.getLevelData()).getLevelName() + " - "; - -@@ -112,6 +122,16 @@ public class WorldTimingsHandler { - - - miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc"); -+ -+ poiUnload = Timings.ofSafe(name + "Chunk unload - POI"); -+ chunkUnload = Timings.ofSafe(name + "Chunk unload - Chunk"); -+ poiSaveDataSerialization = Timings.ofSafe(name + "Chunk save - POI Data serialization"); -+ chunkSave = Timings.ofSafe(name + "Chunk save - Chunk"); -+ chunkSaveDataSerialization = Timings.ofSafe(name + "Chunk save - Chunk Data serialization"); -+ chunkSaveIOWait = Timings.ofSafe(name + "Chunk save - Chunk IO Wait"); -+ chunkUnloadPrepareSave = Timings.ofSafe(name + "Chunk unload - Async Save Prepare"); -+ chunkUnloadPOISerialization = Timings.ofSafe(name + "Chunk unload - POI Data Serialization"); -+ chunkUnloadDataSave = Timings.ofSafe(name + "Chunk unload - Data Serialization"); - } - - public static Timing getTickList(ServerLevel worldserver, String timingsType) { -diff --git a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -index 05bddc0697faa8d9d9955d89d76930c84ef7df0d..cbeaadaecf816070b3a37938c8e683180939afc4 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -+++ b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -@@ -32,192 +32,41 @@ public final class ChunkSystem { - } - - public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) { -- level.chunkSource.mainThreadProcessor.execute(run); -+ level.chunkTaskScheduler.scheduleChunkTask(chunkX, chunkZ, run, priority); // Paper - rewrite chunk system - } - - public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen, - final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority, - final Consumer onComplete) { -- if (gen) { -- scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -- return; -- } -- scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> { -- if (chunk == null) { -- onComplete.accept(null); -- } else { -- if (chunk.getStatus().isOrAfter(toStatus)) { -- scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -- } else { -- onComplete.accept(null); -- } -- } -- }); -+ level.chunkTaskScheduler.scheduleChunkLoad(chunkX, chunkZ, gen, toStatus, addTicket, priority, onComplete); // Paper - rewrite chunk system - } - -- static final TicketType CHUNK_LOAD = TicketType.create("chunk_load", Long::compareTo); -- -- private static long chunkLoadCounter = 0L; -+ // Paper - rewrite chunk system - public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus, - final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -- if (!Bukkit.isPrimaryThread()) { -- scheduleChunkTask(level, chunkX, chunkZ, () -> { -- scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -- }, priority); -- return; -- } -- -- final int minLevel = 33 + ChunkStatus.getDistance(toStatus); -- final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null; -- final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); -- -- if (addTicket) { -- level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); -- } -- level.chunkSource.runDistanceManagerUpdates(); -- -- final Consumer loadCallback = (final ChunkAccess chunk) -> { -- try { -- if (onComplete != null) { -- onComplete.accept(chunk); -- } -- } catch (final ThreadDeath death) { -- throw death; -- } catch (final Throwable thr) { -- LOGGER.error("Exception handling chunk load callback", thr); -- SneakyThrow.sneaky(thr); -- } finally { -- if (addTicket) { -- level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos); -- level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); -- } -- } -- }; -- -- final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -- -- if (holder == null || holder.getTicketLevel() > minLevel) { -- loadCallback.accept(null); -- return; -- } -- -- final CompletableFuture> loadFuture = holder.getOrScheduleFuture(toStatus, level.chunkSource.chunkMap); -- -- if (loadFuture.isDone()) { -- loadCallback.accept(loadFuture.join().left().orElse(null)); -- return; -- } -- -- loadFuture.whenCompleteAsync((final Either either, final Throwable thr) -> { -- if (thr != null) { -- loadCallback.accept(null); -- return; -- } -- loadCallback.accept(either.left().orElse(null)); -- }, (final Runnable r) -> { -- scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); -- }); -+ level.chunkTaskScheduler.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); // Paper - rewrite chunk system - } - - public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ, - final FullChunkStatus toStatus, final boolean addTicket, - final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -- // This method goes unused until the chunk system rewrite -- if (toStatus == FullChunkStatus.INACCESSIBLE) { -- throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status"); -- } -- -- if (!Bukkit.isPrimaryThread()) { -- scheduleChunkTask(level, chunkX, chunkZ, () -> { -- scheduleTickingState(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -- }, priority); -- return; -- } -- -- final int minLevel = 33 - (toStatus.ordinal() - 1); -- final int radius = toStatus.ordinal() - 1; -- final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null; -- final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); -- -- if (addTicket) { -- level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); -- } -- level.chunkSource.runDistanceManagerUpdates(); -- -- final Consumer loadCallback = (final LevelChunk chunk) -> { -- try { -- if (onComplete != null) { -- onComplete.accept(chunk); -- } -- } catch (final ThreadDeath death) { -- throw death; -- } catch (final Throwable thr) { -- LOGGER.error("Exception handling chunk load callback", thr); -- SneakyThrow.sneaky(thr); -- } finally { -- if (addTicket) { -- level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos); -- level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference); -- } -- } -- }; -- -- final ChunkHolder holder = level.chunkSource.chunkMap.updatingChunkMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -- -- if (holder == null || holder.getTicketLevel() > minLevel) { -- loadCallback.accept(null); -- return; -- } -- -- final CompletableFuture> tickingState; -- switch (toStatus) { -- case FULL: { -- tickingState = holder.getFullChunkFuture(); -- break; -- } -- case BLOCK_TICKING: { -- tickingState = holder.getTickingChunkFuture(); -- break; -- } -- case ENTITY_TICKING: { -- tickingState = holder.getEntityTickingChunkFuture(); -- break; -- } -- default: { -- throw new IllegalStateException("Cannot reach here"); -- } -- } -- -- if (tickingState.isDone()) { -- loadCallback.accept(tickingState.join().left().orElse(null)); -- return; -- } -- -- tickingState.whenCompleteAsync((final Either either, final Throwable thr) -> { -- if (thr != null) { -- loadCallback.accept(null); -- return; -- } -- loadCallback.accept(either.left().orElse(null)); -- }, (final Runnable r) -> { -- scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST); -- }); -+ level.chunkTaskScheduler.scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); // Paper - rewrite chunk system - } - - public static List getVisibleChunkHolders(final ServerLevel level) { -- return new ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values()); -+ return level.chunkTaskScheduler.chunkHolderManager.getOldChunkHolders(); // Paper - rewrite chunk system - } - - public static List getUpdatingChunkHolders(final ServerLevel level) { -- return new ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values()); -+ return level.chunkTaskScheduler.chunkHolderManager.getOldChunkHolders(); // Paper - rewrite chunk system - } - - public static int getVisibleChunkHolderCount(final ServerLevel level) { -- return level.chunkSource.chunkMap.visibleChunkMap.size(); -+ return level.chunkTaskScheduler.chunkHolderManager.size(); // Paper - rewrite chunk system - } - - public static int getUpdatingChunkHolderCount(final ServerLevel level) { -- return level.chunkSource.chunkMap.updatingChunkMap.size(); -+ return level.chunkTaskScheduler.chunkHolderManager.size(); // Paper - rewrite chunk system - } - - public static boolean hasAnyChunkHolders(final ServerLevel level) { -@@ -244,26 +93,31 @@ public final class ChunkSystem { - - public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { - chunk.playerChunk = holder; -+ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.FULL; - } - - public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) { -- -+ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.INACCESSIBLE; - } - - public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) { - chunk.level.getChunkSource().tickingChunks.add(chunk); -+ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.BLOCK_TICKING; - } - - public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) { - chunk.level.getChunkSource().tickingChunks.remove(chunk); -+ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.FULL; - } - - public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { - chunk.level.getChunkSource().entityTickingChunks.add(chunk); -+ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING; - } - - public static void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { - chunk.level.getChunkSource().entityTickingChunks.remove(chunk); -+ chunk.chunkStatus = net.minecraft.server.level.FullChunkStatus.BLOCK_TICKING; - } - - public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) { -@@ -271,23 +125,15 @@ public final class ChunkSystem { - } - - public static int getSendViewDistance(final ServerPlayer player) { -- return getLoadViewDistance(player); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPISendViewDistance(player); - } - - public static int getLoadViewDistance(final ServerPlayer player) { -- final ServerLevel level = player.serverLevel(); -- if (level == null) { -- return Bukkit.getViewDistance(); -- } -- return level.chunkSource.chunkMap.getPlayerViewDistance(player); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getLoadViewDistance(player); - } - - public static int getTickViewDistance(final ServerPlayer player) { -- final ServerLevel level = player.serverLevel(); -- if (level == null) { -- return Bukkit.getSimulationDistance(); -- } -- return level.chunkSource.chunkMap.distanceManager.simulationDistance; -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPITickViewDistance(player); - } - - private ChunkSystem() { -diff --git a/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1b090f1e79b996e52097afc49c1cec85936653e6 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java -@@ -0,0 +1,1208 @@ -+package io.papermc.paper.chunk.system; -+ -+import ca.spottedleaf.concurrentutil.collection.SRSWLinkedQueue; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import io.papermc.paper.chunk.system.scheduling.ChunkHolderManager; -+import io.papermc.paper.configuration.GlobalConfiguration; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import io.papermc.paper.util.player.SingleUserAreaMap; -+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import it.unimi.dsi.fastutil.longs.LongComparator; -+import it.unimi.dsi.fastutil.longs.LongHeapPriorityQueue; -+import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -+import net.minecraft.network.protocol.Packet; -+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; -+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket; -+import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket; -+import net.minecraft.server.level.ChunkTrackingView; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.server.network.PlayerChunkSender; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.GameRules; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.levelgen.BelowZeroRetrogen; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.entity.Player; -+import java.lang.invoke.VarHandle; -+import java.util.ArrayDeque; -+import java.util.concurrent.TimeUnit; -+import java.util.concurrent.atomic.AtomicLong; -+ -+public class RegionizedPlayerChunkLoader { -+ -+ public static final TicketType REGION_PLAYER_TICKET = TicketType.create("region_player_ticket", Long::compareTo); -+ -+ public static final int MIN_VIEW_DISTANCE = 2; -+ public static final int MAX_VIEW_DISTANCE = 32; -+ -+ public static final int TICK_TICKET_LEVEL = 31; -+ public static final int GENERATED_TICKET_LEVEL = 33 + ChunkStatus.getDistance(ChunkStatus.FULL); -+ public static final int LOADED_TICKET_LEVEL = 33 + ChunkStatus.getDistance(ChunkStatus.EMPTY); -+ -+ public static final record ViewDistances( -+ int tickViewDistance, -+ int loadViewDistance, -+ int sendViewDistance -+ ) { -+ public ViewDistances setTickViewDistance(final int distance) { -+ return new ViewDistances(distance, this.loadViewDistance, this.sendViewDistance); -+ } -+ -+ public ViewDistances setLoadViewDistance(final int distance) { -+ return new ViewDistances(this.tickViewDistance, distance, this.sendViewDistance); -+ } -+ -+ -+ public ViewDistances setSendViewDistance(final int distance) { -+ return new ViewDistances(this.tickViewDistance, this.loadViewDistance, distance); -+ } -+ } -+ -+ public static int getAPITickViewDistance(final Player player) { -+ return getAPITickViewDistance(((CraftPlayer)player).getHandle()); -+ } -+ -+ public static int getAPITickViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level(); -+ final PlayerChunkLoaderData data = player.chunkLoader; -+ if (data == null) { -+ return level.playerChunkLoader.getAPITickDistance(); -+ } -+ return data.lastTickDistance; -+ } -+ -+ public static int getAPIViewDistance(final Player player) { -+ return getAPIViewDistance(((CraftPlayer)player).getHandle()); -+ } -+ -+ public static int getAPIViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level(); -+ final PlayerChunkLoaderData data = player.chunkLoader; -+ if (data == null) { -+ return level.playerChunkLoader.getAPIViewDistance(); -+ } -+ // view distance = load distance + 1 -+ return data.lastLoadDistance - 1; -+ } -+ -+ public static int getLoadViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level(); -+ final PlayerChunkLoaderData data = player.chunkLoader; -+ if (data == null) { -+ return level.playerChunkLoader.getAPIViewDistance(); -+ } -+ // view distance = load distance + 1 -+ return data.lastLoadDistance - 1; -+ } -+ -+ public static int getAPISendViewDistance(final Player player) { -+ return getAPISendViewDistance(((CraftPlayer)player).getHandle()); -+ } -+ -+ public static int getAPISendViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level(); -+ final PlayerChunkLoaderData data = player.chunkLoader; -+ if (data == null) { -+ return level.playerChunkLoader.getAPISendViewDistance(); -+ } -+ return data.lastSendDistance; -+ } -+ -+ private final ServerLevel world; -+ -+ public RegionizedPlayerChunkLoader(final ServerLevel world) { -+ this.world = world; -+ } -+ -+ public void addPlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread(player, "Cannot add player to player chunk loader async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ -+ if (player.chunkLoader != null) { -+ throw new IllegalStateException("Player is already added to player chunk loader"); -+ } -+ -+ final PlayerChunkLoaderData loader = new PlayerChunkLoaderData(this.world, player); -+ -+ player.chunkLoader = loader; -+ loader.add(); -+ } -+ -+ public void updatePlayer(final ServerPlayer player) { -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ if (loader != null) { -+ loader.update(); -+ } -+ } -+ -+ public void removePlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread(player, "Cannot remove player from player chunk loader async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ -+ if (loader == null) { -+ return; -+ } -+ -+ loader.remove(); -+ player.chunkLoader = null; -+ } -+ -+ public void setSendDistance(final int distance) { -+ this.world.setSendViewDistance(distance); -+ } -+ -+ public void setLoadDistance(final int distance) { -+ this.world.setLoadViewDistance(distance); -+ } -+ -+ public void setTickDistance(final int distance) { -+ this.world.setTickViewDistance(distance); -+ } -+ -+ // Note: follow the player chunk loader so everything stays consistent... -+ public int getAPITickDistance() { -+ final ViewDistances distances = this.world.getViewDistances(); -+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance); -+ return tickViewDistance; -+ } -+ -+ public int getAPIViewDistance() { -+ final ViewDistances distances = this.world.getViewDistances(); -+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance); -+ final int loadDistance = PlayerChunkLoaderData.getLoadViewDistance(tickViewDistance, -1, distances.loadViewDistance); -+ -+ // loadDistance = api view distance + 1 -+ return loadDistance - 1; -+ } -+ -+ public int getAPISendViewDistance() { -+ final ViewDistances distances = this.world.getViewDistances(); -+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance); -+ final int loadDistance = PlayerChunkLoaderData.getLoadViewDistance(tickViewDistance, -1, distances.loadViewDistance); -+ final int sendViewDistance = PlayerChunkLoaderData.getSendViewDistance( -+ loadDistance, -1, -1, distances.sendViewDistance -+ ); -+ -+ return sendViewDistance; -+ } -+ -+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ, final boolean borderOnly) { -+ return borderOnly ? this.isChunkSentBorderOnly(player, chunkX, chunkZ) : this.isChunkSent(player, chunkX, chunkZ); -+ } -+ -+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ if (loader == null) { -+ return false; -+ } -+ -+ return loader.sentChunks.contains(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ -+ public boolean isChunkSentBorderOnly(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ if (loader == null) { -+ return false; -+ } -+ -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ if (!loader.sentChunks.contains(CoordinateUtils.getChunkKey(dx + chunkX, dz + chunkZ))) { -+ return true; -+ } -+ } -+ } -+ -+ return false; -+ } -+ -+ public void tick() { -+ TickThread.ensureTickThread("Cannot tick player chunk loader async"); -+ long currTime = System.nanoTime(); -+ for (final ServerPlayer player : new java.util.ArrayList<>(this.world.players())) { -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ if (loader == null || loader.world != this.world) { -+ // not our problem anymore -+ continue; -+ } -+ loader.update(); // can't invoke plugin logic -+ loader.updateQueues(currTime); -+ } -+ } -+ -+ private static long[] generateBFSOrder(final int radius) { -+ final LongArrayList chunks = new LongArrayList(); -+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); -+ final LongOpenHashSet seen = new LongOpenHashSet(); -+ -+ seen.add(CoordinateUtils.getChunkKey(0, 0)); -+ queue.enqueue(CoordinateUtils.getChunkKey(0, 0)); -+ while (!queue.isEmpty()) { -+ final long chunk = queue.dequeueLong(); -+ final int chunkX = CoordinateUtils.getChunkX(chunk); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunk); -+ -+ // important that the addition to the list is here, rather than when enqueueing neighbours -+ // ensures the order is actually kept -+ chunks.add(chunk); -+ -+ // -x -+ final long n1 = CoordinateUtils.getChunkKey(chunkX - 1, chunkZ); -+ // -z -+ final long n2 = CoordinateUtils.getChunkKey(chunkX, chunkZ - 1); -+ // +x -+ final long n3 = CoordinateUtils.getChunkKey(chunkX + 1, chunkZ); -+ // +z -+ final long n4 = CoordinateUtils.getChunkKey(chunkX, chunkZ + 1); -+ -+ final long[] list = new long[] {n1, n2, n3, n4}; -+ -+ for (final long neighbour : list) { -+ final int neighbourX = CoordinateUtils.getChunkX(neighbour); -+ final int neighbourZ = CoordinateUtils.getChunkZ(neighbour); -+ if (Math.max(Math.abs(neighbourX), Math.abs(neighbourZ)) > radius) { -+ // don't enqueue out of range -+ continue; -+ } -+ if (!seen.add(neighbour)) { -+ continue; -+ } -+ queue.enqueue(neighbour); -+ } -+ } -+ -+ // to increase generation parallelism, we want to space the chunks out so that they are not nearby when generating -+ // this also means we are minimising locality -+ // but, we need to maintain sorted order by manhatten distance -+ -+ // first, build a map of manhatten distance -> chunks -+ final java.util.List byDistance = new java.util.ArrayList<>(); -+ for (final it.unimi.dsi.fastutil.longs.LongIterator iterator = chunks.iterator(); iterator.hasNext();) { -+ final long chunkKey = iterator.nextLong(); -+ -+ final int chunkX = CoordinateUtils.getChunkX(chunkKey); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunkKey); -+ -+ final int dist = Math.abs(chunkX) + Math.abs(chunkZ); -+ if (dist == byDistance.size()) { -+ final LongArrayList list = new LongArrayList(); -+ list.add(chunkKey); -+ byDistance.add(list); -+ continue; -+ } -+ -+ byDistance.get(dist).add(chunkKey); -+ } -+ -+ // per distance we transform the chunk list so that each element is maximally spaced out from each other -+ for (int i = 0, len = byDistance.size(); i < len; ++i) { -+ final LongArrayList notAdded = byDistance.get(i).clone(); -+ final LongArrayList added = new LongArrayList(); -+ -+ while (!notAdded.isEmpty()) { -+ if (added.isEmpty()) { -+ added.add(notAdded.removeLong(notAdded.size() - 1)); -+ continue; -+ } -+ -+ long maxChunk = -1L; -+ int maxDist = 0; -+ -+ // select the chunk from the not yet added set that has the largest minimum distance from -+ // the current set of added chunks -+ -+ for (final it.unimi.dsi.fastutil.longs.LongIterator iterator = notAdded.iterator(); iterator.hasNext();) { -+ final long chunkKey = iterator.nextLong(); -+ final int chunkX = CoordinateUtils.getChunkX(chunkKey); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunkKey); -+ -+ int minDist = Integer.MAX_VALUE; -+ -+ for (final it.unimi.dsi.fastutil.longs.LongIterator iterator2 = added.iterator(); iterator2.hasNext();) { -+ final long addedKey = iterator2.nextLong(); -+ final int addedX = CoordinateUtils.getChunkX(addedKey); -+ final int addedZ = CoordinateUtils.getChunkZ(addedKey); -+ -+ // here we use square distance because chunk generation uses neighbours in a square radius -+ final int dist = Math.max(Math.abs(addedX - chunkX), Math.abs(addedZ - chunkZ)); -+ -+ if (dist < minDist) { -+ minDist = dist; -+ } -+ } -+ -+ if (minDist > maxDist) { -+ maxDist = minDist; -+ maxChunk = chunkKey; -+ } -+ } -+ -+ // move the selected chunk from the not added set to the added set -+ -+ if (!notAdded.rem(maxChunk)) { -+ throw new IllegalStateException(); -+ } -+ -+ added.add(maxChunk); -+ } -+ -+ byDistance.set(i, added); -+ } -+ -+ // now, rebuild the list so that it still maintains manhatten distance order -+ final LongArrayList ret = new LongArrayList(chunks.size()); -+ -+ for (final LongArrayList dist : byDistance) { -+ ret.addAll(dist); -+ } -+ -+ return ret.toLongArray(); -+ } -+ -+ public static final class PlayerChunkLoaderData { -+ -+ private static final AtomicLong ID_GENERATOR = new AtomicLong(); -+ private final long id = ID_GENERATOR.incrementAndGet(); -+ private final Long idBoxed = Long.valueOf(this.id); -+ -+ // expected that this list returns for a given radius, the set of chunks ordered -+ // by manhattan distance -+ private static final long[][] SEARCH_RADIUS_ITERATION_LIST = new long[65][]; -+ static { -+ for (int i = 0; i < SEARCH_RADIUS_ITERATION_LIST.length; ++i) { -+ // a BFS around -x, -z, +x, +z will give increasing manhatten distance -+ SEARCH_RADIUS_ITERATION_LIST[i] = generateBFSOrder(i); -+ } -+ } -+ -+ private static final long MAX_RATE = 10_000L; -+ -+ private final ServerPlayer player; -+ private final ServerLevel world; -+ -+ private int lastChunkX = Integer.MIN_VALUE; -+ private int lastChunkZ = Integer.MIN_VALUE; -+ -+ private int lastSendDistance = Integer.MIN_VALUE; -+ private int lastLoadDistance = Integer.MIN_VALUE; -+ private int lastTickDistance = Integer.MIN_VALUE; -+ -+ private int lastSentChunkCenterX = Integer.MIN_VALUE; -+ private int lastSentChunkCenterZ = Integer.MIN_VALUE; -+ -+ private int lastSentChunkRadius = Integer.MIN_VALUE; -+ private int lastSentSimulationDistance = Integer.MIN_VALUE; -+ -+ private boolean canGenerateChunks = true; -+ -+ private final ArrayDeque> delayedTicketOps = new ArrayDeque<>(); -+ private final LongOpenHashSet sentChunks = new LongOpenHashSet(); -+ -+ private static final byte CHUNK_TICKET_STAGE_NONE = 0; -+ private static final byte CHUNK_TICKET_STAGE_LOADING = 1; -+ private static final byte CHUNK_TICKET_STAGE_LOADED = 2; -+ private static final byte CHUNK_TICKET_STAGE_GENERATING = 3; -+ private static final byte CHUNK_TICKET_STAGE_GENERATED = 4; -+ private static final byte CHUNK_TICKET_STAGE_TICK = 5; -+ private static final int[] TICKET_STAGE_TO_LEVEL = new int[] { -+ ChunkHolderManager.MAX_TICKET_LEVEL + 1, -+ LOADED_TICKET_LEVEL, -+ LOADED_TICKET_LEVEL, -+ GENERATED_TICKET_LEVEL, -+ GENERATED_TICKET_LEVEL, -+ TICK_TICKET_LEVEL -+ }; -+ private final Long2ByteOpenHashMap chunkTicketStage = new Long2ByteOpenHashMap(); -+ { -+ this.chunkTicketStage.defaultReturnValue(CHUNK_TICKET_STAGE_NONE); -+ } -+ -+ // rate limiting -+ private final AllocatingRateLimiter chunkSendLimiter = new AllocatingRateLimiter(); -+ private final AllocatingRateLimiter chunkLoadTicketLimiter = new AllocatingRateLimiter(); -+ private final AllocatingRateLimiter chunkGenerateTicketLimiter = new AllocatingRateLimiter(); -+ -+ // queues -+ private final LongComparator CLOSEST_MANHATTAN_DIST = (final long c1, final long c2) -> { -+ final int c1x = CoordinateUtils.getChunkX(c1); -+ final int c1z = CoordinateUtils.getChunkZ(c1); -+ -+ final int c2x = CoordinateUtils.getChunkX(c2); -+ final int c2z = CoordinateUtils.getChunkZ(c2); -+ -+ final int centerX = PlayerChunkLoaderData.this.lastChunkX; -+ final int centerZ = PlayerChunkLoaderData.this.lastChunkZ; -+ -+ return Integer.compare( -+ Math.abs(c1x - centerX) + Math.abs(c1z - centerZ), -+ Math.abs(c2x - centerX) + Math.abs(c2z - centerZ) -+ ); -+ }; -+ private final LongHeapPriorityQueue sendQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue tickingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue generatingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue genQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue loadingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue loadQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ -+ private volatile boolean removed; -+ -+ public PlayerChunkLoaderData(final ServerLevel world, final ServerPlayer player) { -+ this.world = world; -+ this.player = player; -+ } -+ -+ private void flushDelayedTicketOps() { -+ if (this.delayedTicketOps.isEmpty()) { -+ return; -+ } -+ this.world.chunkTaskScheduler.chunkHolderManager.performTicketUpdates(this.delayedTicketOps); -+ this.delayedTicketOps.clear(); -+ } -+ -+ private void pushDelayedTicketOp(final ChunkHolderManager.TicketOperation op) { -+ this.delayedTicketOps.addLast(op); -+ } -+ -+ private void sendChunk(final int chunkX, final int chunkZ) { -+ if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ PlayerChunkSender.sendChunk(this.player.connection, this.world, this.world.getChunkIfLoaded(chunkX, chunkZ)); -+ return; -+ } -+ throw new IllegalStateException(); -+ } -+ -+ private void sendUnloadChunk(final int chunkX, final int chunkZ) { -+ if (!this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ return; -+ } -+ this.sendUnloadChunkRaw(chunkX, chunkZ); -+ } -+ -+ private void sendUnloadChunkRaw(final int chunkX, final int chunkZ) { -+ PlayerChunkSender.dropChunkStatic(this.player, new ChunkPos(chunkX, chunkZ)); -+ } -+ -+ private final SingleUserAreaMap broadcastMap = new SingleUserAreaMap<>(this) { -+ @Override -+ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ // do nothing, we only care about remove -+ } -+ -+ @Override -+ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ parameter.sendUnloadChunk(chunkX, chunkZ); -+ } -+ }; -+ private final SingleUserAreaMap loadTicketCleanup = new SingleUserAreaMap<>(this) { -+ @Override -+ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ // do nothing, we only care about remove -+ } -+ -+ @Override -+ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final byte ticketStage = parameter.chunkTicketStage.remove(chunk); -+ final int level = TICKET_STAGE_TO_LEVEL[ticketStage]; -+ if (level > ChunkHolderManager.MAX_TICKET_LEVEL) { -+ return; -+ } -+ -+ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addAndRemove( -+ chunk, -+ TicketType.UNKNOWN, level, new ChunkPos(chunkX, chunkZ), -+ REGION_PLAYER_TICKET, level, parameter.idBoxed -+ )); -+ } -+ }; -+ private final SingleUserAreaMap tickMap = new SingleUserAreaMap<>(this) { -+ @Override -+ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ // do nothing, we will detect ticking chunks when we try to load them -+ } -+ -+ @Override -+ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ // note: by the time this is called, the tick cleanup should have ran - so, if the chunk is at -+ // the tick stage it was deemed in range for loading. Thus, we need to move it to generated -+ if (!parameter.chunkTicketStage.replace(chunk, CHUNK_TICKET_STAGE_TICK, CHUNK_TICKET_STAGE_GENERATED)) { -+ return; -+ } -+ -+ // Since we are possibly downgrading the ticket level, we add an unknown ticket so that -+ // the level is kept until tick(). -+ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addAndRemove( -+ chunk, -+ TicketType.UNKNOWN, TICK_TICKET_LEVEL, new ChunkPos(chunkX, chunkZ), -+ REGION_PLAYER_TICKET, TICK_TICKET_LEVEL, parameter.idBoxed -+ )); -+ // keep chunk at new generated level -+ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addOp( -+ chunk, -+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, parameter.idBoxed -+ )); -+ } -+ }; -+ -+ private static boolean wantChunkLoaded(final int centerX, final int centerZ, final int chunkX, final int chunkZ, -+ final int sendRadius) { -+ // expect sendRadius to be = 1 + target viewable radius -+ return ChunkTrackingView.isWithinDistance(centerX, centerZ, sendRadius, chunkX, chunkZ, true); -+ } -+ -+ private static int getClientViewDistance(final ServerPlayer player) { -+ final Integer vd = player.requestedViewDistance(); -+ return vd == null ? -1 : Math.max(0, vd.intValue()); -+ } -+ -+ private static int getTickDistance(final int playerTickViewDistance, final int worldTickViewDistance) { -+ return playerTickViewDistance < 0 ? worldTickViewDistance : playerTickViewDistance; -+ } -+ -+ private static int getLoadViewDistance(final int tickViewDistance, final int playerLoadViewDistance, -+ final int worldLoadViewDistance) { -+ return Math.max(tickViewDistance + 1, playerLoadViewDistance < 0 ? worldLoadViewDistance : playerLoadViewDistance); -+ } -+ -+ private static int getSendViewDistance(final int loadViewDistance, final int clientViewDistance, -+ final int playerSendViewDistance, final int worldSendViewDistance) { -+ return Math.min( -+ loadViewDistance - 1, -+ playerSendViewDistance < 0 ? (!GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance || clientViewDistance < 0 ? (worldSendViewDistance < 0 ? (loadViewDistance - 1) : worldSendViewDistance) : clientViewDistance + 1) : playerSendViewDistance -+ ); -+ } -+ -+ private Packet updateClientChunkRadius(final int radius) { -+ this.lastSentChunkRadius = radius; -+ return new ClientboundSetChunkCacheRadiusPacket(radius); -+ } -+ -+ private Packet updateClientSimulationDistance(final int distance) { -+ this.lastSentSimulationDistance = distance; -+ return new ClientboundSetSimulationDistancePacket(distance); -+ } -+ -+ private Packet updateClientChunkCenter(final int chunkX, final int chunkZ) { -+ this.lastSentChunkCenterX = chunkX; -+ this.lastSentChunkCenterZ = chunkZ; -+ return new ClientboundSetChunkCacheCenterPacket(chunkX, chunkZ); -+ } -+ -+ private boolean canPlayerGenerateChunks() { -+ return !this.player.isSpectator() || this.world.getGameRules().getBoolean(GameRules.RULE_SPECTATORSGENERATECHUNKS); -+ } -+ -+ private double getMaxChunkLoadRate() { -+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate; -+ -+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); -+ } -+ -+ private double getMaxChunkGenRate() { -+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate; -+ -+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); -+ } -+ -+ private double getMaxChunkSendRate() { -+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate; -+ -+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); -+ } -+ -+ private long getMaxChunkLoads() { -+ final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L); -+ long configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads; -+ if (configLimit == 0L) { -+ // by default, only allow 1/5th of the chunks in the view distance to be concurrently active -+ configLimit = Math.max(5L, radiusChunks / 5L); -+ } else if (configLimit < 0L) { -+ configLimit = Integer.MAX_VALUE; -+ } // else: use the value configured -+ configLimit = configLimit - this.loadingQueue.size(); -+ -+ return configLimit; -+ } -+ -+ private long getMaxChunkGenerates() { -+ final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L); -+ long configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates; -+ if (configLimit == 0L) { -+ // by default, only allow 1/5th of the chunks in the view distance to be concurrently active -+ configLimit = Math.max(5L, radiusChunks / 5L); -+ } else if (configLimit < 0L) { -+ configLimit = Integer.MAX_VALUE; -+ } // else: use the value configured -+ configLimit = configLimit - this.generatingQueue.size(); -+ -+ return configLimit; -+ } -+ -+ private boolean wantChunkSent(final int chunkX, final int chunkZ) { -+ final int dx = this.lastChunkX - chunkX; -+ final int dz = this.lastChunkZ - chunkZ; -+ return (Math.max(Math.abs(dx), Math.abs(dz)) <= (this.lastSendDistance + 1)) && wantChunkLoaded( -+ this.lastChunkX, this.lastChunkZ, chunkX, chunkZ, this.lastSendDistance -+ ); -+ } -+ -+ private boolean wantChunkTicked(final int chunkX, final int chunkZ) { -+ final int dx = this.lastChunkX - chunkX; -+ final int dz = this.lastChunkZ - chunkZ; -+ return Math.max(Math.abs(dx), Math.abs(dz)) <= this.lastTickDistance; -+ } -+ -+ void updateQueues(final long time) { -+ TickThread.ensureTickThread(this.player, "Cannot tick player chunk loader async"); -+ if (this.removed) { -+ throw new IllegalStateException("Ticking removed player chunk loader"); -+ } -+ // update rate limits -+ final double loadRate = this.getMaxChunkLoadRate(); -+ final double genRate = this.getMaxChunkGenRate(); -+ final double sendRate = this.getMaxChunkSendRate(); -+ -+ this.chunkLoadTicketLimiter.tickAllocation(time, loadRate, loadRate); -+ this.chunkGenerateTicketLimiter.tickAllocation(time, genRate, genRate); -+ this.chunkSendLimiter.tickAllocation(time, sendRate, sendRate); -+ -+ // try to progress chunk loads -+ while (!this.loadingQueue.isEmpty()) { -+ final long pendingLoadChunk = this.loadingQueue.firstLong(); -+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingLoadChunk); -+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingLoadChunk); -+ final ChunkAccess pending = this.world.chunkSource.getChunkAtImmediately(pendingChunkX, pendingChunkZ); -+ if (pending == null) { -+ // nothing to do here -+ break; -+ } -+ // chunk has loaded, so we can take it out of the queue -+ this.loadingQueue.dequeueLong(); -+ -+ // try to move to generate queue -+ final byte prev = this.chunkTicketStage.put(pendingLoadChunk, CHUNK_TICKET_STAGE_LOADED); -+ if (prev != CHUNK_TICKET_STAGE_LOADING) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_LOADING + ", not " + prev); -+ } -+ -+ if (this.canGenerateChunks || this.isLoadedChunkGeneratable(pending)) { -+ this.genQueue.enqueue(pendingLoadChunk); -+ } // else: don't want to generate, so just leave it loaded -+ } -+ -+ // try to push more chunk loads -+ final long maxLoads = Math.max(0L, Math.min(MAX_RATE, Math.min(this.loadQueue.size(), this.getMaxChunkLoads()))); -+ final int maxLoadsThisTick = (int)this.chunkLoadTicketLimiter.takeAllocation(time, loadRate, maxLoads); -+ if (maxLoadsThisTick > 0) { -+ final LongArrayList chunks = new LongArrayList(maxLoadsThisTick); -+ for (int i = 0; i < maxLoadsThisTick; ++i) { -+ final long chunk = this.loadQueue.dequeueLong(); -+ final byte prev = this.chunkTicketStage.put(chunk, CHUNK_TICKET_STAGE_LOADING); -+ if (prev != CHUNK_TICKET_STAGE_NONE) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_NONE + ", not " + prev); -+ } -+ this.pushDelayedTicketOp( -+ ChunkHolderManager.TicketOperation.addOp( -+ chunk, -+ REGION_PLAYER_TICKET, LOADED_TICKET_LEVEL, this.idBoxed -+ ) -+ ); -+ chunks.add(chunk); -+ this.loadingQueue.enqueue(chunk); -+ } -+ -+ // here we need to flush tickets, as scheduleChunkLoad requires tickets to be propagated with addTicket = false -+ this.flushDelayedTicketOps(); -+ // we only need to call scheduleChunkLoad because the loaded ticket level is not enough to start the chunk -+ // load - only generate ticket levels start anything, but they start generation... -+ // propagate levels -+ // Note: this CAN call plugin logic, so it is VITAL that our bookkeeping logic is completely done by the time this is invoked -+ this.world.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(); -+ -+ if (this.removed) { -+ // process ticket updates may invoke plugin logic, which may remove this player -+ return; -+ } -+ -+ for (int i = 0; i < maxLoadsThisTick; ++i) { -+ final long queuedLoadChunk = chunks.getLong(i); -+ final int queuedChunkX = CoordinateUtils.getChunkX(queuedLoadChunk); -+ final int queuedChunkZ = CoordinateUtils.getChunkZ(queuedLoadChunk); -+ this.world.chunkTaskScheduler.scheduleChunkLoad( -+ queuedChunkX, queuedChunkZ, ChunkStatus.EMPTY, false, PrioritisedExecutor.Priority.NORMAL, null -+ ); -+ if (this.removed) { -+ return; -+ } -+ } -+ } -+ -+ // try to progress chunk generations -+ while (!this.generatingQueue.isEmpty()) { -+ final long pendingGenChunk = this.generatingQueue.firstLong(); -+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingGenChunk); -+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingGenChunk); -+ final LevelChunk pending = this.world.chunkSource.getChunkAtIfLoadedMainThreadNoCache(pendingChunkX, pendingChunkZ); -+ if (pending == null) { -+ // nothing to do here -+ break; -+ } -+ -+ // chunk has generated, so we can take it out of queue -+ this.generatingQueue.dequeueLong(); -+ -+ final byte prev = this.chunkTicketStage.put(pendingGenChunk, CHUNK_TICKET_STAGE_GENERATED); -+ if (prev != CHUNK_TICKET_STAGE_GENERATING) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_GENERATING + ", not " + prev); -+ } -+ -+ // try to move to send queue -+ if (this.wantChunkSent(pendingChunkX, pendingChunkZ)) { -+ this.sendQueue.enqueue(pendingGenChunk); -+ } -+ // try to move to tick queue -+ if (this.wantChunkTicked(pendingChunkX, pendingChunkZ)) { -+ this.tickingQueue.enqueue(pendingGenChunk); -+ } -+ } -+ -+ // try to push more chunk generations -+ final long maxGens = Math.max(0L, Math.min(MAX_RATE, Math.min(this.genQueue.size(), this.getMaxChunkGenerates()))); -+ final int maxGensThisTick = (int)this.chunkGenerateTicketLimiter.takeAllocation(time, genRate, maxGens); -+ int ratedGensThisTick = 0; -+ while (!this.genQueue.isEmpty()) { -+ final long chunkKey = this.genQueue.firstLong(); -+ final int chunkX = CoordinateUtils.getChunkX(chunkKey); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunkKey); -+ final ChunkAccess chunk = this.world.chunkSource.getChunkAtImmediately(chunkX, chunkZ); -+ if (chunk.getStatus() != ChunkStatus.FULL) { -+ // only rate limit actual generations -+ if ((ratedGensThisTick + 1) > maxGensThisTick) { -+ break; -+ } -+ ++ratedGensThisTick; -+ } -+ -+ this.genQueue.dequeueLong(); -+ -+ final byte prev = this.chunkTicketStage.put(chunkKey, CHUNK_TICKET_STAGE_GENERATING); -+ if (prev != CHUNK_TICKET_STAGE_LOADED) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_LOADED + ", not " + prev); -+ } -+ this.pushDelayedTicketOp( -+ ChunkHolderManager.TicketOperation.addAndRemove( -+ chunkKey, -+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, this.idBoxed, -+ REGION_PLAYER_TICKET, LOADED_TICKET_LEVEL, this.idBoxed -+ ) -+ ); -+ this.generatingQueue.enqueue(chunkKey); -+ } -+ -+ // try to pull ticking chunks -+ tick_check_outer: -+ while (!this.tickingQueue.isEmpty()) { -+ final long pendingTicking = this.tickingQueue.firstLong(); -+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingTicking); -+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingTicking); -+ -+ final int tickingReq = 2; -+ for (int dz = -tickingReq; dz <= tickingReq; ++dz) { -+ for (int dx = -tickingReq; dx <= tickingReq; ++dx) { -+ if ((dx | dz) == 0) { -+ continue; -+ } -+ final long neighbour = CoordinateUtils.getChunkKey(dx + pendingChunkX, dz + pendingChunkZ); -+ final byte stage = this.chunkTicketStage.get(neighbour); -+ if (stage != CHUNK_TICKET_STAGE_GENERATED && stage != CHUNK_TICKET_STAGE_TICK) { -+ break tick_check_outer; -+ } -+ } -+ } -+ // only gets here if all neighbours were marked as generated or ticking themselves -+ this.tickingQueue.dequeueLong(); -+ this.pushDelayedTicketOp( -+ ChunkHolderManager.TicketOperation.addAndRemove( -+ pendingTicking, -+ REGION_PLAYER_TICKET, TICK_TICKET_LEVEL, this.idBoxed, -+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, this.idBoxed -+ ) -+ ); -+ // there is no queue to add after ticking -+ final byte prev = this.chunkTicketStage.put(pendingTicking, CHUNK_TICKET_STAGE_TICK); -+ if (prev != CHUNK_TICKET_STAGE_GENERATED) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_GENERATED + ", not " + prev); -+ } -+ } -+ -+ // try to pull sending chunks -+ final long maxSends = Math.max(0L, Math.min(MAX_RATE, Integer.MAX_VALUE)); // no logic to track concurrent sends -+ final int maxSendsThisTick = Math.min((int)this.chunkSendLimiter.takeAllocation(time, sendRate, maxSends), this.sendQueue.size()); -+ // we do not return sends that we took from the allocation back because we want to limit the max send rate, not target it -+ for (int i = 0; i < maxSendsThisTick; ++i) { -+ final long pendingSend = this.sendQueue.firstLong(); -+ final int pendingSendX = CoordinateUtils.getChunkX(pendingSend); -+ final int pendingSendZ = CoordinateUtils.getChunkZ(pendingSend); -+ final LevelChunk chunk = this.world.chunkSource.getChunkAtIfLoadedMainThreadNoCache(pendingSendX, pendingSendZ); -+ if (!chunk.areNeighboursLoaded(1) || !TickThread.isTickThreadFor(this.world, pendingSendX, pendingSendZ)) { -+ // nothing to do -+ // the target chunk may not be owned by this region, but this should be resolved in the future -+ break; -+ } -+ if (!chunk.isPostProcessingDone) { -+ // not yet post-processed, need to do this so that tile entities can properly be sent to clients -+ chunk.postProcessGeneration(); -+ // check if there was any recursive action -+ if (this.removed || this.sendQueue.isEmpty() || this.sendQueue.firstLong() != pendingSend) { -+ return; -+ } // else: good to dequeue and send, fall through -+ } -+ this.sendQueue.dequeueLong(); -+ -+ this.sendChunk(pendingSendX, pendingSendZ); -+ if (this.removed) { -+ // sendChunk may invoke plugin logic -+ return; -+ } -+ } -+ -+ this.flushDelayedTicketOps(); -+ // we assume propagate ticket levels happens after this call -+ } -+ -+ void add() { -+ TickThread.ensureTickThread(this.player, "Cannot add player asynchronously"); -+ if (this.removed) { -+ throw new IllegalStateException("Adding removed player chunk loader"); -+ } -+ final ViewDistances playerDistances = this.player.getViewDistances(); -+ final ViewDistances worldDistances = this.world.getViewDistances(); -+ final int chunkX = this.player.chunkPosition().x; -+ final int chunkZ = this.player.chunkPosition().z; -+ -+ final int tickViewDistance = getTickDistance(playerDistances.tickViewDistance, worldDistances.tickViewDistance); -+ // load view cannot be less-than tick view + 1 -+ final int loadViewDistance = getLoadViewDistance(tickViewDistance, playerDistances.loadViewDistance, worldDistances.loadViewDistance); -+ // send view cannot be greater-than load view -+ final int clientViewDistance = getClientViewDistance(this.player); -+ final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance); -+ -+ // send view distances -+ this.player.connection.send(this.updateClientChunkRadius(sendViewDistance)); -+ this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance)); -+ -+ // add to distance maps -+ this.broadcastMap.add(chunkX, chunkZ, sendViewDistance + 1); -+ this.loadTicketCleanup.add(chunkX, chunkZ, loadViewDistance + 1); -+ this.tickMap.add(chunkX, chunkZ, tickViewDistance); -+ -+ // update chunk center -+ this.player.connection.send(this.updateClientChunkCenter(chunkX, chunkZ)); -+ -+ // now we can update -+ this.update(); -+ } -+ -+ private boolean isLoadedChunkGeneratable(final int chunkX, final int chunkZ) { -+ return this.isLoadedChunkGeneratable(this.world.chunkSource.getChunkAtImmediately(chunkX, chunkZ)); -+ } -+ -+ private boolean isLoadedChunkGeneratable(final ChunkAccess chunkAccess) { -+ final BelowZeroRetrogen belowZeroRetrogen; -+ // see PortalForcer#findPortalAround -+ return chunkAccess != null && ( -+ chunkAccess.getStatus() == ChunkStatus.FULL || -+ ((belowZeroRetrogen = chunkAccess.getBelowZeroRetrogen()) != null && belowZeroRetrogen.targetStatus().isOrAfter(ChunkStatus.SPAWN)) -+ ); -+ } -+ -+ void update() { -+ TickThread.ensureTickThread(this.player, "Cannot update player asynchronously"); -+ if (this.removed) { -+ throw new IllegalStateException("Updating removed player chunk loader"); -+ } -+ final ViewDistances playerDistances = this.player.getViewDistances(); -+ final ViewDistances worldDistances = this.world.getViewDistances(); -+ -+ final int tickViewDistance = getTickDistance(playerDistances.tickViewDistance, worldDistances.tickViewDistance); -+ // load view cannot be less-than tick view + 1 -+ final int loadViewDistance = getLoadViewDistance(tickViewDistance, playerDistances.loadViewDistance, worldDistances.loadViewDistance); -+ // send view cannot be greater-than load view -+ final int clientViewDistance = getClientViewDistance(this.player); -+ final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance); -+ -+ final ChunkPos playerPos = this.player.chunkPosition(); -+ final boolean canGenerateChunks = this.canPlayerGenerateChunks(); -+ final int currentChunkX = playerPos.x; -+ final int currentChunkZ = playerPos.z; -+ -+ final int prevChunkX = this.lastChunkX; -+ final int prevChunkZ = this.lastChunkZ; -+ -+ if ( -+ // has view distance stayed the same? -+ sendViewDistance == this.lastSendDistance -+ && loadViewDistance == this.lastLoadDistance -+ && tickViewDistance == this.lastTickDistance -+ -+ // has our chunk stayed the same? -+ && prevChunkX == currentChunkX -+ && prevChunkZ == currentChunkZ -+ -+ // can we still generate chunks? -+ && this.canGenerateChunks == canGenerateChunks -+ ) { -+ // nothing we care about changed, so we're not re-calculating -+ return; -+ } -+ -+ // update distance maps -+ this.broadcastMap.update(currentChunkX, currentChunkZ, sendViewDistance + 1); -+ this.loadTicketCleanup.update(currentChunkX, currentChunkZ, loadViewDistance + 1); -+ this.tickMap.update(currentChunkX, currentChunkZ, tickViewDistance); -+ if (sendViewDistance > loadViewDistance || tickViewDistance > loadViewDistance) { -+ throw new IllegalStateException(); -+ } -+ -+ // update VDs for client -+ // this should be after the distance map updates, as they will send unload packets -+ if (this.lastSentChunkRadius != sendViewDistance) { -+ this.player.connection.send(this.updateClientChunkRadius(sendViewDistance)); -+ } -+ if (this.lastSentSimulationDistance != tickViewDistance) { -+ this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance)); -+ } -+ -+ this.sendQueue.clear(); -+ this.tickingQueue.clear(); -+ this.generatingQueue.clear(); -+ this.genQueue.clear(); -+ this.loadingQueue.clear(); -+ this.loadQueue.clear(); -+ -+ this.lastChunkX = currentChunkX; -+ this.lastChunkZ = currentChunkZ; -+ this.lastSendDistance = sendViewDistance; -+ this.lastLoadDistance = loadViewDistance; -+ this.lastTickDistance = tickViewDistance; -+ this.canGenerateChunks = canGenerateChunks; -+ -+ // +1 since we need to load chunks +1 around the load view distance... -+ final long[] toIterate = SEARCH_RADIUS_ITERATION_LIST[loadViewDistance + 1]; -+ // the iteration order is by increasing manhattan distance - so, we do NOT need to -+ // sort anything in the queue! -+ for (final long deltaChunk : toIterate) { -+ final int dx = CoordinateUtils.getChunkX(deltaChunk); -+ final int dz = CoordinateUtils.getChunkZ(deltaChunk); -+ final int chunkX = dx + currentChunkX; -+ final int chunkZ = dz + currentChunkZ; -+ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz)); -+ final int manhattanDistance = Math.abs(dx) + Math.abs(dz); -+ -+ // since chunk sending is not by radius alone, we need an extra check here to account for -+ // everything <= sendDistance -+ // Note: Vanilla may want to send chunks outside the send view distance, so we do need -+ // the dist <= view check -+ final boolean sendChunk = (squareDistance <= (sendViewDistance + 1)) -+ && wantChunkLoaded(currentChunkX, currentChunkZ, chunkX, chunkZ, sendViewDistance); -+ final boolean sentChunk = sendChunk ? this.sentChunks.contains(chunk) : this.sentChunks.remove(chunk); -+ -+ if (!sendChunk && sentChunk) { -+ // have sent the chunk, but don't want it anymore -+ // unload it now -+ this.sendUnloadChunkRaw(chunkX, chunkZ); -+ } -+ -+ final byte stage = this.chunkTicketStage.get(chunk); -+ switch (stage) { -+ case CHUNK_TICKET_STAGE_NONE: { -+ // we want the chunk to be at least loaded -+ this.loadQueue.enqueue(chunk); -+ break; -+ } -+ case CHUNK_TICKET_STAGE_LOADING: { -+ this.loadingQueue.enqueue(chunk); -+ break; -+ } -+ case CHUNK_TICKET_STAGE_LOADED: { -+ if (canGenerateChunks || this.isLoadedChunkGeneratable(chunkX, chunkZ)) { -+ this.genQueue.enqueue(chunk); -+ } -+ break; -+ } -+ case CHUNK_TICKET_STAGE_GENERATING: { -+ this.generatingQueue.enqueue(chunk); -+ break; -+ } -+ case CHUNK_TICKET_STAGE_GENERATED: { -+ if (sendChunk && !sentChunk) { -+ this.sendQueue.enqueue(chunk); -+ } -+ if (squareDistance <= tickViewDistance) { -+ this.tickingQueue.enqueue(chunk); -+ } -+ break; -+ } -+ case CHUNK_TICKET_STAGE_TICK: { -+ if (sendChunk && !sentChunk) { -+ this.sendQueue.enqueue(chunk); -+ } -+ break; -+ } -+ default: { -+ throw new IllegalStateException("Unknown stage: " + stage); -+ } -+ } -+ } -+ -+ // update the chunk center -+ // this must be done last so that the client does not ignore any of our unload chunk packets above -+ if (this.lastSentChunkCenterX != currentChunkX || this.lastSentChunkCenterZ != currentChunkZ) { -+ this.player.connection.send(this.updateClientChunkCenter(currentChunkX, currentChunkZ)); -+ } -+ -+ this.flushDelayedTicketOps(); -+ } -+ -+ void remove() { -+ TickThread.ensureTickThread(this.player, "Cannot add player asynchronously"); -+ if (this.removed) { -+ throw new IllegalStateException("Removing removed player chunk loader"); -+ } -+ this.removed = true; -+ // sends the chunk unload packets -+ this.broadcastMap.remove(); -+ // cleans up loading/generating tickets -+ this.loadTicketCleanup.remove(); -+ // cleans up ticking tickets -+ this.tickMap.remove(); -+ -+ // purge queues -+ this.sendQueue.clear(); -+ this.tickingQueue.clear(); -+ this.generatingQueue.clear(); -+ this.genQueue.clear(); -+ this.loadingQueue.clear(); -+ this.loadQueue.clear(); -+ -+ // flush ticket changes -+ this.flushDelayedTicketOps(); -+ -+ // now all tickets should be removed, which is all of our external state -+ } -+ } -+ -+ // TODO rebase into util patch -+ private static final class AllocatingRateLimiter { -+ -+ // max difference granularity in ns -+ private static final long MAX_GRANULARITY = TimeUnit.SECONDS.toNanos(1L); -+ -+ private double allocation; -+ private long lastAllocationUpdate; -+ private double takeCarry; -+ private long lastTakeUpdate; -+ -+ // rate in units/s, and time in ns -+ public void tickAllocation(final long time, final double rate, final double maxAllocation) { -+ final long diff = Math.min(MAX_GRANULARITY, time - this.lastAllocationUpdate); -+ this.lastAllocationUpdate = time; -+ -+ this.allocation = Math.min(maxAllocation - this.takeCarry, this.allocation + rate * (diff*1.0E-9D)); -+ } -+ -+ // rate in units/s, and time in ns -+ public long takeAllocation(final long time, final double rate, final long maxTake) { -+ if (maxTake < 1L) { -+ return 0L; -+ } -+ -+ double ret = this.takeCarry; -+ final long diff = Math.min(MAX_GRANULARITY, time - this.lastTakeUpdate); -+ this.lastTakeUpdate = time; -+ -+ // note: abs(takeCarry) <= 1.0 -+ final double take = Math.min(Math.min((double)maxTake - this.takeCarry, this.allocation), rate * (diff*1.0E-9)); -+ -+ ret += take; -+ this.allocation -= take; -+ -+ final long retInteger = (long)Math.floor(ret); -+ this.takeCarry = ret - (double)retInteger; -+ -+ return retInteger; -+ } -+ } -+ -+ static final class CountedSRSWLinkedQueue { -+ -+ private final SRSWLinkedQueue queue = new SRSWLinkedQueue<>(); -+ private volatile long countAdded; -+ private volatile long countRemoved; -+ -+ private static final VarHandle COUNT_ADDED_HANDLE = ConcurrentUtil.getVarHandle(CountedSRSWLinkedQueue.class, "countAdded", long.class); -+ private static final VarHandle COUNT_REMOVED_HANDLE = ConcurrentUtil.getVarHandle(CountedSRSWLinkedQueue.class, "countRemoved", long.class); -+ -+ private long getCountAddedPlain() { -+ return (long)COUNT_ADDED_HANDLE.get(this); -+ } -+ -+ private long getCountAddedAcquire() { -+ return (long)COUNT_ADDED_HANDLE.getAcquire(this); -+ } -+ -+ private void setCountAddedRelease(final long to) { -+ COUNT_ADDED_HANDLE.setRelease(this, to); -+ } -+ -+ private long getCountRemovedPlain() { -+ return (long)COUNT_REMOVED_HANDLE.get(this); -+ } -+ -+ private long getCountRemovedAcquire() { -+ return (long)COUNT_REMOVED_HANDLE.getAcquire(this); -+ } -+ -+ private void setCountRemovedRelease(final long to) { -+ COUNT_REMOVED_HANDLE.setRelease(this, to); -+ } -+ -+ public void add(final E element) { -+ this.setCountAddedRelease(this.getCountAddedPlain() + 1L); -+ this.queue.addLast(element); -+ } -+ -+ public E poll() { -+ final E ret = this.queue.poll(); -+ if (ret != null) { -+ this.setCountRemovedRelease(this.getCountRemovedPlain() + 1L); -+ } -+ -+ return ret; -+ } -+ -+ public long size() { -+ final long removed = this.getCountRemovedAcquire(); -+ final long added = this.getCountAddedAcquire(); -+ -+ return added - removed; -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java -new file mode 100644 -index 0000000000000000000000000000000000000000..15ee41452992714108efe53b708b5a4e1da7c1ff ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java -@@ -0,0 +1,902 @@ -+package io.papermc.paper.chunk.system.entity; -+ -+import com.destroystokyo.paper.util.maplist.EntityList; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import io.papermc.paper.util.WorldUtil; -+import io.papermc.paper.world.ChunkEntitySlices; -+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; -+import net.minecraft.core.BlockPos; -+import io.papermc.paper.chunk.system.ChunkSystem; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.AbortableIterationConsumer; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.entity.EntityInLevelCallback; -+import net.minecraft.world.level.entity.EntityTypeTest; -+import net.minecraft.world.level.entity.LevelCallback; -+import net.minecraft.server.level.FullChunkStatus; -+import net.minecraft.world.level.entity.LevelEntityGetter; -+import net.minecraft.world.level.entity.Visibility; -+import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.Vec3; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+import org.slf4j.Logger; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Iterator; -+import java.util.List; -+import java.util.NoSuchElementException; -+import java.util.UUID; -+import java.util.concurrent.locks.StampedLock; -+import java.util.function.Consumer; -+import java.util.function.Predicate; -+ -+public final class EntityLookup implements LevelEntityGetter { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ protected static final int REGION_SHIFT = 5; -+ protected static final int REGION_MASK = (1 << REGION_SHIFT) - 1; -+ protected static final int REGION_SIZE = 1 << REGION_SHIFT; -+ -+ public final ServerLevel world; -+ -+ private final StampedLock stateLock = new StampedLock(); -+ protected final Long2ObjectOpenHashMap regions = new Long2ObjectOpenHashMap<>(128, 0.5f); -+ -+ private final int minSection; // inclusive -+ private final int maxSection; // inclusive -+ private final LevelCallback worldCallback; -+ -+ private final StampedLock entityByLock = new StampedLock(); -+ private final Int2ReferenceOpenHashMap entityById = new Int2ReferenceOpenHashMap<>(); -+ private final Object2ReferenceOpenHashMap entityByUUID = new Object2ReferenceOpenHashMap<>(); -+ private final EntityList accessibleEntities = new EntityList(); -+ -+ public EntityLookup(final ServerLevel world, final LevelCallback worldCallback) { -+ this.world = world; -+ this.minSection = WorldUtil.getMinSection(world); -+ this.maxSection = WorldUtil.getMaxSection(world); -+ this.worldCallback = worldCallback; -+ } -+ -+ private static Entity maskNonAccessible(final Entity entity) { -+ if (entity == null) { -+ return null; -+ } -+ final Visibility visibility = EntityLookup.getEntityStatus(entity); -+ return visibility.isAccessible() ? entity : null; -+ } -+ -+ @Nullable -+ @Override -+ public Entity get(final int id) { -+ final long attempt = this.entityByLock.tryOptimisticRead(); -+ if (attempt != 0L) { -+ try { -+ final Entity ret = this.entityById.get(id); -+ -+ if (this.entityByLock.validate(attempt)) { -+ return maskNonAccessible(ret); -+ } -+ } catch (final Error error) { -+ throw error; -+ } catch (final Throwable thr) { -+ // ignore -+ } -+ } -+ -+ this.entityByLock.readLock(); -+ try { -+ return maskNonAccessible(this.entityById.get(id)); -+ } finally { -+ this.entityByLock.tryUnlockRead(); -+ } -+ } -+ -+ @Nullable -+ @Override -+ public Entity get(final UUID id) { -+ final long attempt = this.entityByLock.tryOptimisticRead(); -+ if (attempt != 0L) { -+ try { -+ final Entity ret = this.entityByUUID.get(id); -+ -+ if (this.entityByLock.validate(attempt)) { -+ return maskNonAccessible(ret); -+ } -+ } catch (final Error error) { -+ throw error; -+ } catch (final Throwable thr) { -+ // ignore -+ } -+ } -+ -+ this.entityByLock.readLock(); -+ try { -+ return maskNonAccessible(this.entityByUUID.get(id)); -+ } finally { -+ this.entityByLock.tryUnlockRead(); -+ } -+ } -+ -+ public boolean hasEntity(final UUID uuid) { -+ return this.get(uuid) != null; -+ } -+ -+ public String getDebugInfo() { -+ return "count_id:" + this.entityById.size() + ",count_uuid:" + this.entityByUUID.size() + ",region_count:" + this.regions.size(); -+ } -+ -+ static final class ArrayIterable implements Iterable { -+ -+ private final T[] array; -+ private final int off; -+ private final int length; -+ -+ public ArrayIterable(final T[] array, final int off, final int length) { -+ this.array = array; -+ this.off = off; -+ this.length = length; -+ if (length > array.length) { -+ throw new IllegalArgumentException("Length must be no greater-than the array length"); -+ } -+ } -+ -+ @NotNull -+ @Override -+ public Iterator iterator() { -+ return new ArrayIterator<>(this.array, this.off, this.length); -+ } -+ -+ static final class ArrayIterator implements Iterator { -+ -+ private final T[] array; -+ private int off; -+ private final int length; -+ -+ public ArrayIterator(final T[] array, final int off, final int length) { -+ this.array = array; -+ this.off = off; -+ this.length = length; -+ } -+ -+ @Override -+ public boolean hasNext() { -+ return this.off < this.length; -+ } -+ -+ @Override -+ public T next() { -+ if (this.off >= this.length) { -+ throw new NoSuchElementException(); -+ } -+ return this.array[this.off++]; -+ } -+ -+ @Override -+ public void remove() { -+ throw new UnsupportedOperationException(); -+ } -+ } -+ } -+ -+ @Override -+ public Iterable getAll() { -+ return new ArrayIterable<>(this.accessibleEntities.getRawData(), 0, this.accessibleEntities.size()); -+ } -+ -+ public Entity[] getAllCopy() { -+ return Arrays.copyOf(this.accessibleEntities.getRawData(), this.accessibleEntities.size(), Entity[].class); -+ } -+ -+ @Override -+ public void get(final EntityTypeTest filter, final AbortableIterationConsumer action) { -+ final Int2ReferenceOpenHashMap entityCopy; -+ -+ this.entityByLock.readLock(); -+ try { -+ entityCopy = this.entityById.clone(); -+ } finally { -+ this.entityByLock.tryUnlockRead(); -+ } -+ for (final Entity entity : entityCopy.values()) { -+ final Visibility visibility = EntityLookup.getEntityStatus(entity); -+ if (!visibility.isAccessible()) { -+ continue; -+ } -+ final U casted = filter.tryCast(entity); -+ if (casted != null && action.accept(casted).shouldAbort()) { -+ break; -+ } -+ } -+ } -+ -+ @Override -+ public void get(final AABB box, final Consumer action) { -+ List entities = new ArrayList<>(); -+ this.getEntitiesWithoutDragonParts(null, box, entities, null); -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ action.accept(entities.get(i)); -+ } -+ } -+ -+ @Override -+ public void get(final EntityTypeTest filter, final AABB box, final AbortableIterationConsumer action) { -+ List entities = new ArrayList<>(); -+ this.getEntitiesWithoutDragonParts(null, box, entities, null); -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ final U casted = filter.tryCast(entities.get(i)); -+ if (casted != null && action.accept(casted).shouldAbort()) { -+ break; -+ } -+ } -+ } -+ -+ public void entityStatusChange(final Entity entity, final ChunkEntitySlices slices, final Visibility oldVisibility, final Visibility newVisibility, final boolean moved, -+ final boolean created, final boolean destroyed) { -+ TickThread.ensureTickThread(entity, "Entity status change must only happen on the main thread"); -+ -+ if (entity.updatingSectionStatus) { -+ // recursive status update -+ LOGGER.error("Cannot recursively update entity chunk status for entity " + entity, new Throwable()); -+ return; -+ } -+ -+ final boolean entityStatusUpdateBefore = slices == null ? false : slices.startPreventingStatusUpdates(); -+ -+ if (entityStatusUpdateBefore) { -+ LOGGER.error("Cannot update chunk status for entity " + entity + " since entity chunk (" + slices.chunkX + "," + slices.chunkZ + ") is receiving update", new Throwable()); -+ return; -+ } -+ -+ try { -+ final Boolean ticketBlockBefore = this.world.chunkTaskScheduler.chunkHolderManager.blockTicketUpdates(); -+ try { -+ entity.updatingSectionStatus = true; -+ try { -+ if (created) { -+ EntityLookup.this.worldCallback.onCreated(entity); -+ } -+ -+ if (oldVisibility == newVisibility) { -+ if (moved && newVisibility.isAccessible()) { -+ EntityLookup.this.worldCallback.onSectionChange(entity); -+ } -+ return; -+ } -+ -+ if (newVisibility.ordinal() > oldVisibility.ordinal()) { -+ // status upgrade -+ if (!oldVisibility.isAccessible() && newVisibility.isAccessible()) { -+ this.accessibleEntities.add(entity); -+ EntityLookup.this.worldCallback.onTrackingStart(entity); -+ } -+ -+ if (!oldVisibility.isTicking() && newVisibility.isTicking()) { -+ EntityLookup.this.worldCallback.onTickingStart(entity); -+ } -+ } else { -+ // status downgrade -+ if (oldVisibility.isTicking() && !newVisibility.isTicking()) { -+ EntityLookup.this.worldCallback.onTickingEnd(entity); -+ } -+ -+ if (oldVisibility.isAccessible() && !newVisibility.isAccessible()) { -+ this.accessibleEntities.remove(entity); -+ EntityLookup.this.worldCallback.onTrackingEnd(entity); -+ } -+ } -+ -+ if (moved && newVisibility.isAccessible()) { -+ EntityLookup.this.worldCallback.onSectionChange(entity); -+ } -+ -+ if (destroyed) { -+ EntityLookup.this.worldCallback.onDestroyed(entity); -+ } -+ } finally { -+ entity.updatingSectionStatus = false; -+ } -+ } finally { -+ this.world.chunkTaskScheduler.chunkHolderManager.unblockTicketUpdates(ticketBlockBefore); -+ } -+ } finally { -+ if (slices != null) { -+ slices.stopPreventingStatusUpdates(false); -+ } -+ } -+ } -+ -+ public void chunkStatusChange(final int x, final int z, final FullChunkStatus newStatus) { -+ this.getChunk(x, z).updateStatus(newStatus, this); -+ } -+ -+ public void addLegacyChunkEntities(final List entities, final ChunkPos forChunk) { -+ this.addEntityChunk(entities, forChunk, true); -+ } -+ -+ public void addEntityChunkEntities(final List entities, final ChunkPos forChunk) { -+ this.addEntityChunk(entities, forChunk, true); -+ } -+ -+ public void addWorldGenChunkEntities(final List entities, final ChunkPos forChunk) { -+ this.addEntityChunk(entities, forChunk, false); -+ } -+ -+ private void addRecursivelySafe(final Entity root, final boolean fromDisk) { -+ if (!this.addEntity(root, fromDisk)) { -+ // possible we are a passenger, and so should dismount from any valid entity in the world -+ root.stopRiding(true); -+ return; -+ } -+ for (final Entity passenger : root.getPassengers()) { -+ this.addRecursivelySafe(passenger, fromDisk); -+ } -+ } -+ -+ private void addEntityChunk(final List entities, final ChunkPos forChunk, final boolean fromDisk) { -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ final Entity entity = entities.get(i); -+ if (entity.isPassenger()) { -+ continue; -+ } -+ -+ if (!entity.chunkPosition().equals(forChunk)) { -+ LOGGER.warn("Root entity " + entity + " is outside of serialized chunk " + forChunk); -+ // can't set removed here, as we may not own the chunk position -+ // skip the entity -+ continue; -+ } -+ -+ final Vec3 rootPosition = entity.position(); -+ -+ // always adjust positions before adding passengers in case plugins access the entity, and so that -+ // they are added to the right entity chunk -+ for (final Entity passenger : entity.getIndirectPassengers()) { -+ if (!passenger.chunkPosition().equals(forChunk)) { -+ passenger.setPosRaw(rootPosition.x, rootPosition.y, rootPosition.z, true); -+ } -+ } -+ -+ this.addRecursivelySafe(entity, fromDisk); -+ } -+ } -+ -+ public boolean addNewEntity(final Entity entity) { -+ return this.addEntity(entity, false); -+ } -+ -+ public static Visibility getEntityStatus(final Entity entity) { -+ if (entity.isAlwaysTicking()) { -+ return Visibility.TICKING; -+ } -+ final FullChunkStatus entityStatus = entity.chunkStatus; -+ return Visibility.fromFullChunkStatus(entityStatus == null ? FullChunkStatus.INACCESSIBLE : entityStatus); -+ } -+ -+ private boolean addEntity(final Entity entity, final boolean fromDisk) { -+ final BlockPos pos = entity.blockPosition(); -+ final int sectionX = pos.getX() >> 4; -+ final int sectionY = Mth.clamp(pos.getY() >> 4, this.minSection, this.maxSection); -+ final int sectionZ = pos.getZ() >> 4; -+ TickThread.ensureTickThread(this.world, sectionX, sectionZ, "Cannot add entity off-main thread"); -+ -+ if (entity.isRemoved()) { -+ LOGGER.warn("Refusing to add removed entity: " + entity); -+ return false; -+ } -+ -+ if (entity.updatingSectionStatus) { -+ LOGGER.warn("Entity " + entity + " is currently prevented from being added/removed to world since it is processing section status updates", new Throwable()); -+ return false; -+ } -+ -+ if (fromDisk) { -+ ChunkSystem.onEntityPreAdd(this.world, entity); -+ if (entity.isRemoved()) { -+ // removed from checkDupeUUID call -+ return false; -+ } -+ } -+ -+ this.entityByLock.writeLock(); -+ try { -+ if (this.entityById.containsKey(entity.getId())) { -+ LOGGER.warn("Entity id already exists: " + entity.getId() + ", mapped to " + this.entityById.get(entity.getId()) + ", can't add " + entity); -+ return false; -+ } -+ if (this.entityByUUID.containsKey(entity.getUUID())) { -+ LOGGER.warn("Entity uuid already exists: " + entity.getUUID() + ", mapped to " + this.entityByUUID.get(entity.getUUID()) + ", can't add " + entity); -+ return false; -+ } -+ this.entityById.put(entity.getId(), entity); -+ this.entityByUUID.put(entity.getUUID(), entity); -+ } finally { -+ this.entityByLock.tryUnlockWrite(); -+ } -+ -+ entity.sectionX = sectionX; -+ entity.sectionY = sectionY; -+ entity.sectionZ = sectionZ; -+ final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ); -+ if (!slices.addEntity(entity, sectionY)) { -+ LOGGER.warn("Entity " + entity + " added to world '" + this.world.getWorld().getName() + "', but was already contained in entity chunk (" + sectionX + "," + sectionZ + ")"); -+ } -+ -+ entity.setLevelCallback(new EntityCallback(entity)); -+ -+ this.entityStatusChange(entity, slices, Visibility.HIDDEN, getEntityStatus(entity), false, !fromDisk, false); -+ -+ return true; -+ } -+ -+ public boolean canRemoveEntity(final Entity entity) { -+ if (entity.updatingSectionStatus) { -+ return false; -+ } -+ -+ final int sectionX = entity.sectionX; -+ final int sectionZ = entity.sectionZ; -+ final ChunkEntitySlices slices = this.getChunk(sectionX, sectionZ); -+ return slices == null || !slices.isPreventingStatusUpdates(); -+ } -+ -+ private void removeEntity(final Entity entity) { -+ final int sectionX = entity.sectionX; -+ final int sectionY = entity.sectionY; -+ final int sectionZ = entity.sectionZ; -+ TickThread.ensureTickThread(this.world, sectionX, sectionZ, "Cannot remove entity off-main"); -+ if (!entity.isRemoved()) { -+ throw new IllegalStateException("Only call Entity#setRemoved to remove an entity"); -+ } -+ final ChunkEntitySlices slices = this.getChunk(sectionX, sectionZ); -+ // all entities should be in a chunk -+ if (slices == null) { -+ LOGGER.warn("Cannot remove entity " + entity + " from null entity slices (" + sectionX + "," + sectionZ + ")"); -+ } else { -+ if (slices.isPreventingStatusUpdates()) { -+ throw new IllegalStateException("Attempting to remove entity " + entity + " from entity slices (" + sectionX + "," + sectionZ + ") that is receiving status updates"); -+ } -+ if (!slices.removeEntity(entity, sectionY)) { -+ LOGGER.warn("Failed to remove entity " + entity + " from entity slices (" + sectionX + "," + sectionZ + ")"); -+ } -+ } -+ entity.sectionX = entity.sectionY = entity.sectionZ = Integer.MIN_VALUE; -+ -+ this.entityByLock.writeLock(); -+ try { -+ if (!this.entityById.remove(entity.getId(), entity)) { -+ LOGGER.warn("Failed to remove entity " + entity + " by id, current entity mapped: " + this.entityById.get(entity.getId())); -+ } -+ if (!this.entityByUUID.remove(entity.getUUID(), entity)) { -+ LOGGER.warn("Failed to remove entity " + entity + " by uuid, current entity mapped: " + this.entityByUUID.get(entity.getUUID())); -+ } -+ } finally { -+ this.entityByLock.tryUnlockWrite(); -+ } -+ } -+ -+ private ChunkEntitySlices moveEntity(final Entity entity) { -+ // ensure we own the entity -+ TickThread.ensureTickThread(entity, "Cannot move entity off-main"); -+ -+ final BlockPos newPos = entity.blockPosition(); -+ final int newSectionX = newPos.getX() >> 4; -+ final int newSectionY = Mth.clamp(newPos.getY() >> 4, this.minSection, this.maxSection); -+ final int newSectionZ = newPos.getZ() >> 4; -+ -+ if (newSectionX == entity.sectionX && newSectionY == entity.sectionY && newSectionZ == entity.sectionZ) { -+ return null; -+ } -+ -+ // ensure the new section is owned by this tick thread -+ TickThread.ensureTickThread(this.world, newSectionX, newSectionZ, "Cannot move entity off-main"); -+ -+ // ensure the old section is owned by this tick thread -+ TickThread.ensureTickThread(this.world, entity.sectionX, entity.sectionZ, "Cannot move entity off-main"); -+ -+ final ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ); -+ final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ); -+ -+ if (!old.removeEntity(entity, entity.sectionY)) { -+ LOGGER.warn("Could not remove entity " + entity + " from its old chunk section (" + entity.sectionX + "," + entity.sectionY + "," + entity.sectionZ + ") since it was not contained in the section"); -+ } -+ -+ if (!slices.addEntity(entity, newSectionY)) { -+ LOGGER.warn("Could not add entity " + entity + " to its new chunk section (" + newSectionX + "," + newSectionY + "," + newSectionZ + ") as it is already contained in the section"); -+ } -+ -+ entity.sectionX = newSectionX; -+ entity.sectionY = newSectionY; -+ entity.sectionZ = newSectionZ; -+ -+ return slices; -+ } -+ -+ public void getEntitiesWithoutDragonParts(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { -+ continue; -+ } -+ -+ chunk.getEntitiesWithoutDragonParts(except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { -+ continue; -+ } -+ -+ chunk.getEntities(except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { -+ continue; -+ } -+ -+ chunk.getHardCollidingEntities(except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final EntityType type, final AABB box, final List into, -+ final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { -+ continue; -+ } -+ -+ chunk.getEntities(type, box, (List)into, (Predicate)predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final Class clazz, final Entity except, final AABB box, final List into, -+ final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(FullChunkStatus.FULL)) { -+ continue; -+ } -+ -+ chunk.getEntities(clazz, except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void entitySectionLoad(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { -+ TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot load in entity section off-main"); -+ synchronized (this) { -+ final ChunkEntitySlices curr = this.getChunk(chunkX, chunkZ); -+ if (curr != null) { -+ this.removeChunk(chunkX, chunkZ); -+ -+ curr.mergeInto(slices); -+ -+ this.addChunk(chunkX, chunkZ, slices); -+ } else { -+ this.addChunk(chunkX, chunkZ, slices); -+ } -+ } -+ } -+ -+ public void entitySectionUnload(final int chunkX, final int chunkZ) { -+ TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot unload entity section off-main"); -+ this.removeChunk(chunkX, chunkZ); -+ } -+ -+ public ChunkEntitySlices getChunk(final int chunkX, final int chunkZ) { -+ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ if (region == null) { -+ return null; -+ } -+ -+ return region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT)); -+ } -+ -+ public ChunkEntitySlices getOrCreateChunk(final int chunkX, final int chunkZ) { -+ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ ChunkEntitySlices ret; -+ if (region == null || (ret = region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT))) == null) { -+ // loadInEntityChunk will call addChunk for us -+ return this.world.chunkTaskScheduler.chunkHolderManager.getOrCreateEntityChunk(chunkX, chunkZ, true); -+ } -+ -+ return ret; -+ } -+ -+ public ChunkSlicesRegion getRegion(final int regionX, final int regionZ) { -+ final long key = CoordinateUtils.getChunkKey(regionX, regionZ); -+ final long attempt = this.stateLock.tryOptimisticRead(); -+ if (attempt != 0L) { -+ try { -+ final ChunkSlicesRegion ret = this.regions.get(key); -+ -+ if (this.stateLock.validate(attempt)) { -+ return ret; -+ } -+ } catch (final Error error) { -+ throw error; -+ } catch (final Throwable thr) { -+ // ignore -+ } -+ } -+ -+ this.stateLock.readLock(); -+ try { -+ return this.regions.get(key); -+ } finally { -+ this.stateLock.tryUnlockRead(); -+ } -+ } -+ -+ private synchronized void removeChunk(final int chunkX, final int chunkZ) { -+ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); -+ -+ final ChunkSlicesRegion region = this.regions.get(key); -+ final int remaining = region.remove(relIndex); -+ -+ if (remaining == 0) { -+ this.stateLock.writeLock(); -+ try { -+ this.regions.remove(key); -+ } finally { -+ this.stateLock.tryUnlockWrite(); -+ } -+ } -+ } -+ -+ public synchronized void addChunk(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { -+ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); -+ -+ ChunkSlicesRegion region = this.regions.get(key); -+ if (region != null) { -+ region.add(relIndex, slices); -+ } else { -+ region = new ChunkSlicesRegion(); -+ region.add(relIndex, slices); -+ this.stateLock.writeLock(); -+ try { -+ this.regions.put(key, region); -+ } finally { -+ this.stateLock.tryUnlockWrite(); -+ } -+ } -+ } -+ -+ public static final class ChunkSlicesRegion { -+ -+ protected final ChunkEntitySlices[] slices = new ChunkEntitySlices[REGION_SIZE * REGION_SIZE]; -+ protected int sliceCount; -+ -+ public ChunkEntitySlices get(final int index) { -+ return this.slices[index]; -+ } -+ -+ public int remove(final int index) { -+ final ChunkEntitySlices slices = this.slices[index]; -+ if (slices == null) { -+ throw new IllegalStateException(); -+ } -+ -+ this.slices[index] = null; -+ -+ return --this.sliceCount; -+ } -+ -+ public void add(final int index, final ChunkEntitySlices slices) { -+ final ChunkEntitySlices curr = this.slices[index]; -+ if (curr != null) { -+ throw new IllegalStateException(); -+ } -+ -+ this.slices[index] = slices; -+ -+ ++this.sliceCount; -+ } -+ } -+ -+ private final class EntityCallback implements EntityInLevelCallback { -+ -+ public final Entity entity; -+ -+ public EntityCallback(final Entity entity) { -+ this.entity = entity; -+ } -+ -+ @Override -+ public void onMove() { -+ final Entity entity = this.entity; -+ final Visibility oldVisibility = getEntityStatus(entity); -+ final ChunkEntitySlices newSlices = EntityLookup.this.moveEntity(this.entity); -+ if (newSlices == null) { -+ // no new section, so didn't change sections -+ return; -+ } -+ final Visibility newVisibility = getEntityStatus(entity); -+ -+ EntityLookup.this.entityStatusChange(entity, newSlices, oldVisibility, newVisibility, true, false, false); -+ } -+ -+ @Override -+ public void onRemove(final Entity.RemovalReason reason) { -+ final Entity entity = this.entity; -+ TickThread.ensureTickThread(entity, "Cannot remove entity off-main"); // Paper - rewrite chunk system -+ final Visibility tickingState = EntityLookup.getEntityStatus(entity); -+ -+ EntityLookup.this.removeEntity(entity); -+ -+ EntityLookup.this.entityStatusChange(entity, null, tickingState, Visibility.HIDDEN, false, false, reason.shouldDestroy()); -+ -+ this.entity.setLevelCallback(NoOpCallback.INSTANCE); -+ } -+ } -+ -+ private static final class NoOpCallback implements EntityInLevelCallback { -+ -+ public static final NoOpCallback INSTANCE = new NoOpCallback(); -+ -+ @Override -+ public void onMove() {} -+ -+ @Override -+ public void onRemove(final Entity.RemovalReason reason) {} -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2934f0cf0ef09c84739312b00186c2ef0019a165 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java -@@ -0,0 +1,1343 @@ -+package io.papermc.paper.chunk.system.io; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.executor.Cancellable; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedQueueExecutorThread; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import it.unimi.dsi.fastutil.HashCommon; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.storage.RegionFile; -+import net.minecraft.world.level.chunk.storage.RegionFileStorage; -+import org.slf4j.Logger; -+import java.io.IOException; -+import java.lang.invoke.VarHandle; -+import java.util.concurrent.CompletableFuture; -+import java.util.concurrent.CompletionException; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.function.BiConsumer; -+import java.util.function.BiFunction; -+import java.util.function.Consumer; -+import java.util.function.Function; -+ -+/** -+ * Prioritised RegionFile I/O executor, responsible for all RegionFile access. -+ *

    -+ * All functions provided are MT-Safe, however certain ordering constraints are recommended: -+ *

      -+ *
    • -+ * Chunk saves may not occur for unloaded chunks. -+ *
    • -+ *
    • -+ * Tasks must be scheduled on the chunk scheduler thread. -+ *
    • -+ *
    -+ * By following these constraints, no chunk data loss should occur with the exception of underlying I/O problems. -+ */ -+public final class RegionFileIOThread extends PrioritisedQueueExecutorThread { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ /** -+ * The kinds of region files controlled by the region file thread. Add more when needed, and ensure -+ * getControllerFor is updated. -+ */ -+ public static enum RegionFileType { -+ CHUNK_DATA, -+ POI_DATA, -+ ENTITY_DATA; -+ } -+ -+ protected static final RegionFileType[] CACHED_REGIONFILE_TYPES = RegionFileType.values(); -+ -+ private ChunkDataController getControllerFor(final ServerLevel world, final RegionFileType type) { -+ return switch (type) { -+ case CHUNK_DATA -> world.chunkDataControllerNew; -+ case POI_DATA -> world.poiDataControllerNew; -+ case ENTITY_DATA -> world.entityDataControllerNew; -+ default -> throw new IllegalStateException("Unknown controller type " + type); -+ }; -+ } -+ -+ /** -+ * Collects regionfile data for a certain chunk. -+ */ -+ public static final class RegionFileData { -+ -+ private final boolean[] hasResult = new boolean[CACHED_REGIONFILE_TYPES.length]; -+ private final CompoundTag[] data = new CompoundTag[CACHED_REGIONFILE_TYPES.length]; -+ private final Throwable[] throwables = new Throwable[CACHED_REGIONFILE_TYPES.length]; -+ -+ /** -+ * Sets the result associated with the specified regionfile type. Note that -+ * results can only be set once per regionfile type. -+ * -+ * @param type The regionfile type. -+ * @param data The result to set. -+ */ -+ public void setData(final RegionFileType type, final CompoundTag data) { -+ final int index = type.ordinal(); -+ -+ if (this.hasResult[index]) { -+ throw new IllegalArgumentException("Result already exists for type " + type); -+ } -+ this.hasResult[index] = true; -+ this.data[index] = data; -+ } -+ -+ /** -+ * Sets the result associated with the specified regionfile type. Note that -+ * results can only be set once per regionfile type. -+ * -+ * @param type The regionfile type. -+ * @param throwable The result to set. -+ */ -+ public void setThrowable(final RegionFileType type, final Throwable throwable) { -+ final int index = type.ordinal(); -+ -+ if (this.hasResult[index]) { -+ throw new IllegalArgumentException("Result already exists for type " + type); -+ } -+ this.hasResult[index] = true; -+ this.throwables[index] = throwable; -+ } -+ -+ /** -+ * Returns whether there is a result for the specified regionfile type. -+ * -+ * @param type Specified regionfile type. -+ * -+ * @return Whether a result exists for {@code type}. -+ */ -+ public boolean hasResult(final RegionFileType type) { -+ return this.hasResult[type.ordinal()]; -+ } -+ -+ /** -+ * Returns the data result for the regionfile type. -+ * -+ * @param type Specified regionfile type. -+ * -+ * @throws IllegalArgumentException If the result has not been set for {@code type}. -+ * @return The data result for the specified type. If the result is a {@code Throwable}, -+ * then returns {@code null}. -+ */ -+ public CompoundTag getData(final RegionFileType type) { -+ final int index = type.ordinal(); -+ -+ if (!this.hasResult[index]) { -+ throw new IllegalArgumentException("Result does not exist for type " + type); -+ } -+ -+ return this.data[index]; -+ } -+ -+ /** -+ * Returns the throwable result for the regionfile type. -+ * -+ * @param type Specified regionfile type. -+ * -+ * @throws IllegalArgumentException If the result has not been set for {@code type}. -+ * @return The throwable result for the specified type. If the result is an {@code CompoundTag}, -+ * then returns {@code null}. -+ */ -+ public Throwable getThrowable(final RegionFileType type) { -+ final int index = type.ordinal(); -+ -+ if (!this.hasResult[index]) { -+ throw new IllegalArgumentException("Result does not exist for type " + type); -+ } -+ -+ return this.throwables[index]; -+ } -+ } -+ -+ private static final Object INIT_LOCK = new Object(); -+ -+ static RegionFileIOThread[] threads; -+ -+ /* needs to be consistent given a set of parameters */ -+ static RegionFileIOThread selectThread(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ if (threads == null) { -+ throw new IllegalStateException("Threads not initialised"); -+ } -+ -+ final int regionX = chunkX >> 5; -+ final int regionZ = chunkZ >> 5; -+ final int typeOffset = type.ordinal(); -+ -+ return threads[(System.identityHashCode(world) + regionX + regionZ + typeOffset) % threads.length]; -+ } -+ -+ /** -+ * Shuts down the I/O executor(s). Watis for all tasks to complete if specified. -+ * Tasks queued during this call might not be accepted, and tasks queued after will not be accepted. -+ * -+ * @param wait Whether to wait until all tasks have completed. -+ */ -+ public static void close(final boolean wait) { -+ for (int i = 0, len = threads.length; i < len; ++i) { -+ threads[i].close(false, true); -+ } -+ if (wait) { -+ RegionFileIOThread.flush(); -+ } -+ } -+ -+ public static long[] getExecutedTasks() { -+ final long[] ret = new long[threads.length]; -+ for (int i = 0, len = threads.length; i < len; ++i) { -+ ret[i] = threads[i].getTotalTasksExecuted(); -+ } -+ -+ return ret; -+ } -+ -+ public static long[] getTasksScheduled() { -+ final long[] ret = new long[threads.length]; -+ for (int i = 0, len = threads.length; i < len; ++i) { -+ ret[i] = threads[i].getTotalTasksScheduled(); -+ } -+ return ret; -+ } -+ -+ public static void flush() { -+ for (int i = 0, len = threads.length; i < len; ++i) { -+ threads[i].waitUntilAllExecuted(); -+ } -+ } -+ -+ public static void partialFlush(final int totalTasksRemaining) { -+ long failures = 1L; // start out at 0.25ms -+ -+ for (;;) { -+ final long[] executed = getExecutedTasks(); -+ final long[] scheduled = getTasksScheduled(); -+ -+ long sum = 0; -+ for (int i = 0; i < executed.length; ++i) { -+ sum += scheduled[i] - executed[i]; -+ } -+ -+ if (sum <= totalTasksRemaining) { -+ break; -+ } -+ -+ failures = ConcurrentUtil.linearLongBackoff(failures, 250_000L, 5_000_000L); // 500us, 5ms -+ } -+ } -+ -+ /** -+ * Inits the executor with the specified number of threads. -+ * -+ * @param threads Specified number of threads. -+ */ -+ public static void init(final int threads) { -+ synchronized (INIT_LOCK) { -+ if (RegionFileIOThread.threads != null) { -+ throw new IllegalStateException("Already initialised threads"); -+ } -+ -+ RegionFileIOThread.threads = new RegionFileIOThread[threads]; -+ -+ for (int i = 0; i < threads; ++i) { -+ RegionFileIOThread.threads[i] = new RegionFileIOThread(i); -+ RegionFileIOThread.threads[i].start(); -+ } -+ } -+ } -+ -+ private RegionFileIOThread(final int threadNumber) { -+ super(new PrioritisedThreadedTaskQueue(), (int)(1.0e6)); // 1.0ms spinwait time -+ this.setName("RegionFile I/O Thread #" + threadNumber); -+ this.setPriority(Thread.NORM_PRIORITY - 2); // we keep priority close to normal because threads can wait on us -+ this.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { -+ LOGGER.error("Uncaught exception thrown from I/O thread, report this! Thread: " + thread.getName(), thr); -+ }); -+ } -+ -+ /** -+ * Returns whether the current thread is a regionfile I/O executor. -+ * @return Whether the current thread is a regionfile I/O executor. -+ */ -+ public static boolean isRegionFileThread() { -+ return Thread.currentThread() instanceof RegionFileIOThread; -+ } -+ -+ /** -+ * Returns the priority associated with blocking I/O based on the current thread. The goal is to avoid -+ * dumb plugins from taking away priority from threads we consider crucial. -+ * @return The priroity to use with blocking I/O on the current thread. -+ */ -+ public static PrioritisedExecutor.Priority getIOBlockingPriorityForCurrentThread() { -+ if (TickThread.isTickThread()) { -+ return PrioritisedExecutor.Priority.BLOCKING; -+ } -+ return PrioritisedExecutor.Priority.HIGHEST; -+ } -+ -+ /** -+ * Returns the current {@code CompoundTag} pending for write for the specified chunk and regionfile type. -+ * Note that this does not copy the result, so do not modify the result returned. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * -+ * @return The compound tag associated for the specified chunk. {@code null} if no write was pending, or if {@code null} is the write pending. -+ */ -+ public static CompoundTag getPendingWrite(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ return thread.getPendingWriteInternal(world, chunkX, chunkZ, type); -+ } -+ -+ CompoundTag getPendingWriteInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); -+ -+ if (task == null) { -+ return null; -+ } -+ -+ final CompoundTag ret = task.inProgressWrite; -+ -+ return ret == ChunkDataTask.NOTHING_TO_WRITE ? null : ret; -+ } -+ -+ /** -+ * Returns the priority for the specified regionfile type for the specified chunk. -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * @return The priority for the chunk -+ */ -+ public static PrioritisedExecutor.Priority getPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ return thread.getPriorityInternal(world, chunkX, chunkZ, type); -+ } -+ -+ PrioritisedExecutor.Priority getPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); -+ -+ if (task == null) { -+ return PrioritisedExecutor.Priority.COMPLETING; -+ } -+ -+ return task.prioritisedTask.getPriority(); -+ } -+ -+ /** -+ * Sets the priority for all regionfile types for the specified chunk. Note that great care should -+ * be taken using this method, as there can be multiple tasks tied to the same chunk that want different -+ * priorities. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param priority New priority. -+ * -+ * @see #raisePriority(ServerLevel, int, int, Priority) -+ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, -+ final PrioritisedExecutor.Priority priority) { -+ for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ RegionFileIOThread.setPriority(world, chunkX, chunkZ, type, priority); -+ } -+ } -+ -+ /** -+ * Sets the priority for the specified regionfile type for the specified chunk. Note that great care should -+ * be taken using this method, as there can be multiple tasks tied to the same chunk that want different -+ * priorities. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * @param priority New priority. -+ * -+ * @see #raisePriority(ServerLevel, int, int, Priority) -+ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ thread.setPriorityInternal(world, chunkX, chunkZ, type, priority); -+ } -+ -+ void setPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); -+ -+ if (task != null) { -+ task.prioritisedTask.setPriority(priority); -+ } -+ } -+ -+ /** -+ * Raises the priority for all regionfile types for the specified chunk. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param priority New priority. -+ * -+ * @see #setPriority(ServerLevel, int, int, Priority) -+ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void raisePriority(final ServerLevel world, final int chunkX, final int chunkZ, -+ final PrioritisedExecutor.Priority priority) { -+ for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ RegionFileIOThread.raisePriority(world, chunkX, chunkZ, type, priority); -+ } -+ } -+ -+ /** -+ * Raises the priority for the specified regionfile type for the specified chunk. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * @param priority New priority. -+ * -+ * @see #setPriority(ServerLevel, int, int, Priority) -+ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, Priority) -+ * @see #lowerPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void raisePriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ thread.raisePriorityInternal(world, chunkX, chunkZ, type, priority); -+ } -+ -+ void raisePriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); -+ -+ if (task != null) { -+ task.prioritisedTask.raisePriority(priority); -+ } -+ } -+ -+ /** -+ * Lowers the priority for all regionfile types for the specified chunk. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param priority New priority. -+ * -+ * @see #raisePriority(ServerLevel, int, int, Priority) -+ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #setPriority(ServerLevel, int, int, Priority) -+ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void lowerPriority(final ServerLevel world, final int chunkX, final int chunkZ, -+ final PrioritisedExecutor.Priority priority) { -+ for (final RegionFileType type : CACHED_REGIONFILE_TYPES) { -+ RegionFileIOThread.lowerPriority(world, chunkX, chunkZ, type, priority); -+ } -+ } -+ -+ /** -+ * Lowers the priority for the specified regionfile type for the specified chunk. -+ * -+ * @param world Specified world. -+ * @param chunkX Specified chunk x. -+ * @param chunkZ Specified chunk z. -+ * @param type Specified regionfile type. -+ * @param priority New priority. -+ * -+ * @see #raisePriority(ServerLevel, int, int, Priority) -+ * @see #raisePriority(ServerLevel, int, int, RegionFileType, Priority) -+ * @see #setPriority(ServerLevel, int, int, Priority) -+ * @see #setPriority(ServerLevel, int, int, RegionFileType, Priority) -+ */ -+ public static void lowerPriority(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ thread.lowerPriorityInternal(world, chunkX, chunkZ, type, priority); -+ } -+ -+ void lowerPriorityInternal(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ final ChunkDataTask task = taskController.tasks.get(Long.valueOf(CoordinateUtils.getChunkKey(chunkX, chunkZ))); -+ -+ if (task != null) { -+ task.prioritisedTask.lowerPriority(priority); -+ } -+ } -+ -+ /** -+ * Schedules the chunk data to be written asynchronously. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means -+ * saves must be scheduled before a chunk is unloaded. -+ *
    • -+ *
    • -+ * Writes may be called concurrently, although only the "later" write will go through. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param data Chunk's data -+ * @param type The regionfile type to write to. -+ * -+ * @throws IllegalStateException If the file io thread has shutdown. -+ */ -+ public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, -+ final RegionFileType type) { -+ RegionFileIOThread.scheduleSave(world, chunkX, chunkZ, data, type, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ /** -+ * Schedules the chunk data to be written asynchronously. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means -+ * saves must be scheduled before a chunk is unloaded. -+ *
    • -+ *
    • -+ * Writes may be called concurrently, although only the "later" write will go through. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param data Chunk's data -+ * @param type The regionfile type to write to. -+ * @param priority The minimum priority to schedule at. -+ * -+ * @throws IllegalStateException If the file io thread has shutdown. -+ */ -+ public static void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, -+ final RegionFileType type, final PrioritisedExecutor.Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ thread.scheduleSaveInternal(world, chunkX, chunkZ, data, type, priority); -+ } -+ -+ void scheduleSaveInternal(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data, -+ final RegionFileType type, final PrioritisedExecutor.Priority priority) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ -+ final boolean[] created = new boolean[1]; -+ final ChunkCoordinate key = new ChunkCoordinate(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ final ChunkDataTask task = taskController.tasks.compute(key, (final ChunkCoordinate keyInMap, final ChunkDataTask taskRunning) -> { -+ if (taskRunning == null || taskRunning.failedWrite) { -+ // no task is scheduled or the previous write failed - meaning we need to overwrite it -+ -+ // create task -+ final ChunkDataTask newTask = new ChunkDataTask(world, chunkX, chunkZ, taskController, RegionFileIOThread.this, priority); -+ newTask.inProgressWrite = data; -+ created[0] = true; -+ -+ return newTask; -+ } -+ -+ taskRunning.inProgressWrite = data; -+ -+ return taskRunning; -+ }); -+ -+ if (created[0]) { -+ task.prioritisedTask.queue(); -+ } else { -+ task.prioritisedTask.raisePriority(priority); -+ } -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load all regionfile types, and then call -+ * {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean)} -+ * for single load. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -+ */ -+ public static Cancellable loadAllChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Consumer onComplete, final boolean intendingToBlock) { -+ return RegionFileIOThread.loadAllChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load all regionfile types, and then call -+ * {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority)} -+ * for single load. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * @param priority The minimum priority to load the data at. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -+ */ -+ public static Cancellable loadAllChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Consumer onComplete, final boolean intendingToBlock, -+ final PrioritisedExecutor.Priority priority) { -+ return RegionFileIOThread.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, priority, CACHED_REGIONFILE_TYPES); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load data for the specified regionfile type(s), and -+ * then call {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean)} -+ * for single load. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * @param types The regionfile type(s) to load. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -+ */ -+ public static Cancellable loadChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Consumer onComplete, final boolean intendingToBlock, -+ final RegionFileType... types) { -+ return RegionFileIOThread.loadChunkData(world, chunkX, chunkZ, onComplete, intendingToBlock, PrioritisedExecutor.Priority.NORMAL, types); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load data for the specified regionfile type(s), and -+ * then call {@code onComplete}. This is a bulk load operation, see {@link #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority)} -+ * for single load. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * @param types The regionfile type(s) to load. -+ * @param priority The minimum priority to load the data at. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean) -+ * @see #loadDataAsync(ServerLevel, int, int, RegionFileType, BiConsumer, boolean, Priority) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -+ */ -+ public static Cancellable loadChunkData(final ServerLevel world, final int chunkX, final int chunkZ, -+ final Consumer onComplete, final boolean intendingToBlock, -+ final PrioritisedExecutor.Priority priority, final RegionFileType... types) { -+ if (types == null) { -+ throw new NullPointerException("Types cannot be null"); -+ } -+ if (types.length == 0) { -+ throw new IllegalArgumentException("Types cannot be empty"); -+ } -+ -+ final RegionFileData ret = new RegionFileData(); -+ -+ final Cancellable[] reads = new CancellableRead[types.length]; -+ final AtomicInteger completions = new AtomicInteger(); -+ final int expectedCompletions = types.length; -+ -+ for (int i = 0; i < expectedCompletions; ++i) { -+ final RegionFileType type = types[i]; -+ reads[i] = RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, -+ (final CompoundTag data, final Throwable throwable) -> { -+ if (throwable != null) { -+ ret.setThrowable(type, throwable); -+ } else { -+ ret.setData(type, data); -+ } -+ -+ if (completions.incrementAndGet() == expectedCompletions) { -+ onComplete.accept(ret); -+ } -+ }, intendingToBlock, priority); -+ } -+ -+ return new CancellableReads(reads); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load the specified regionfile type, and then call -+ * {@code onComplete}. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -+ */ -+ public static Cancellable loadDataAsync(final ServerLevel world, final int chunkX, final int chunkZ, -+ final RegionFileType type, final BiConsumer onComplete, -+ final boolean intendingToBlock) { -+ return RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, onComplete, intendingToBlock, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ /** -+ * Schedules a load to be executed asynchronously. This task will load the specified regionfile type, and then call -+ * {@code onComplete}. -+ *

    -+ * Impl notes: -+ *

      -+ *
    • -+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may -+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of -+ * data is undefined behaviour, and can cause deadlock. -+ *
    • -+ *
    -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param onComplete Consumer to execute once this task has completed -+ * @param intendingToBlock Whether the caller is intending to block on completion. This only affects the cost -+ * of this call. -+ * @param priority Minimum priority to load the data at. -+ * -+ * @return The {@link Cancellable} for this chunk load. Cancelling it will not affect other loads for the same chunk data. -+ * -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, RegionFileType...) -+ * @see #loadChunkData(ServerLevel, int, int, Consumer, boolean, Priority, RegionFileType...) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean) -+ * @see #loadAllChunkData(ServerLevel, int, int, Consumer, boolean, Priority) -+ */ -+ public static Cancellable loadDataAsync(final ServerLevel world, final int chunkX, final int chunkZ, -+ final RegionFileType type, final BiConsumer onComplete, -+ final boolean intendingToBlock, final PrioritisedExecutor.Priority priority) { -+ final RegionFileIOThread thread = RegionFileIOThread.selectThread(world, chunkX, chunkZ, type); -+ return thread.loadDataAsyncInternal(world, chunkX, chunkZ, type, onComplete, intendingToBlock, priority); -+ } -+ -+ private static Boolean doesRegionFileExist(final int chunkX, final int chunkZ, final boolean intendingToBlock, -+ final ChunkDataController taskController) { -+ final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); -+ if (intendingToBlock) { -+ return taskController.computeForRegionFile(chunkX, chunkZ, true, (final RegionFile file) -> { -+ if (file == null) { // null if no regionfile exists -+ return Boolean.FALSE; -+ } -+ -+ return file.hasChunk(chunkPos) ? Boolean.TRUE : Boolean.FALSE; -+ }); -+ } else { -+ // first check if the region file for sure does not exist -+ if (taskController.doesRegionFileNotExist(chunkX, chunkZ)) { -+ return Boolean.FALSE; -+ } // else: it either exists or is not known, fall back to checking the loaded region file -+ -+ return taskController.computeForRegionFileIfLoaded(chunkX, chunkZ, (final RegionFile file) -> { -+ if (file == null) { // null if not loaded -+ // not sure at this point, let the I/O thread figure it out -+ return Boolean.TRUE; -+ } -+ -+ return file.hasChunk(chunkPos) ? Boolean.TRUE : Boolean.FALSE; -+ }); -+ } -+ } -+ -+ Cancellable loadDataAsyncInternal(final ServerLevel world, final int chunkX, final int chunkZ, -+ final RegionFileType type, final BiConsumer onComplete, -+ final boolean intendingToBlock, final PrioritisedExecutor.Priority priority) { -+ final ChunkDataController taskController = this.getControllerFor(world, type); -+ -+ final ImmediateCallbackCompletion callbackInfo = new ImmediateCallbackCompletion(); -+ -+ final ChunkCoordinate key = new ChunkCoordinate(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ final BiFunction compute = (final ChunkCoordinate keyInMap, final ChunkDataTask running) -> { -+ if (running == null) { -+ // not scheduled -+ -+ if (callbackInfo.regionFileCalculation == null) { -+ // caller will compute this outside of compute(), to avoid holding the bin lock -+ callbackInfo.needsRegionFileTest = true; -+ return null; -+ } -+ -+ if (callbackInfo.regionFileCalculation == Boolean.FALSE) { -+ // not on disk -+ callbackInfo.data = null; -+ callbackInfo.throwable = null; -+ callbackInfo.completeNow = true; -+ return null; -+ } -+ -+ // set up task -+ final ChunkDataTask newTask = new ChunkDataTask( -+ world, chunkX, chunkZ, taskController, RegionFileIOThread.this, priority -+ ); -+ newTask.inProgressRead = new RegionFileIOThread.InProgressRead(); -+ newTask.inProgressRead.waiters.add(onComplete); -+ -+ callbackInfo.tasksNeedsScheduling = true; -+ return newTask; -+ } -+ -+ final CompoundTag pendingWrite = running.inProgressWrite; -+ -+ if (pendingWrite == ChunkDataTask.NOTHING_TO_WRITE) { -+ // need to add to waiters here, because the regionfile thread will use compute() to lock and check for cancellations -+ if (!running.inProgressRead.addToWaiters(onComplete)) { -+ callbackInfo.data = running.inProgressRead.value; -+ callbackInfo.throwable = running.inProgressRead.throwable; -+ callbackInfo.completeNow = true; -+ } -+ return running; -+ } -+ // using the result sync here - don't bump priority -+ -+ // at this stage we have to use the in progress write's data to avoid an order issue -+ callbackInfo.data = pendingWrite; -+ callbackInfo.throwable = null; -+ callbackInfo.completeNow = true; -+ return running; -+ }; -+ -+ ChunkDataTask curr = taskController.tasks.get(key); -+ if (curr == null) { -+ callbackInfo.regionFileCalculation = doesRegionFileExist(chunkX, chunkZ, intendingToBlock, taskController); -+ } -+ ChunkDataTask ret = taskController.tasks.compute(key, compute); -+ if (callbackInfo.needsRegionFileTest) { -+ // curr isn't null but when we went into compute() it was -+ callbackInfo.regionFileCalculation = doesRegionFileExist(chunkX, chunkZ, intendingToBlock, taskController); -+ // now it should be fine -+ ret = taskController.tasks.compute(key, compute); -+ } -+ -+ // needs to be scheduled -+ if (callbackInfo.tasksNeedsScheduling) { -+ ret.prioritisedTask.queue(); -+ } else if (callbackInfo.completeNow) { -+ try { -+ onComplete.accept(callbackInfo.data, callbackInfo.throwable); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Callback " + ConcurrentUtil.genericToString(onComplete) + " synchronously failed to handle chunk data for task " + ret.toString(), thr); -+ } -+ } else { -+ // we're waiting on a task we didn't schedule, so raise its priority to what we want -+ ret.prioritisedTask.raisePriority(priority); -+ } -+ -+ return new CancellableRead(onComplete, ret); -+ } -+ -+ /** -+ * Schedules a load task to be executed asynchronously, and blocks on that task. -+ * -+ * @param world Chunk's world -+ * @param chunkX Chunk's x coordinate -+ * @param chunkZ Chunk's z coordinate -+ * @param type Regionfile type -+ * @param priority Minimum priority to load the data at. -+ * -+ * @return The chunk data for the chunk. Note that a {@code null} result means the chunk or regionfile does not exist on disk. -+ * -+ * @throws IOException If the load fails for any reason -+ */ -+ public static CompoundTag loadData(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileType type, -+ final PrioritisedExecutor.Priority priority) throws IOException { -+ final CompletableFuture ret = new CompletableFuture<>(); -+ -+ RegionFileIOThread.loadDataAsync(world, chunkX, chunkZ, type, (final CompoundTag compound, final Throwable thr) -> { -+ if (thr != null) { -+ ret.completeExceptionally(thr); -+ } else { -+ ret.complete(compound); -+ } -+ }, true, priority); -+ -+ try { -+ return ret.join(); -+ } catch (final CompletionException ex) { -+ throw new IOException(ex); -+ } -+ } -+ -+ private static final class ImmediateCallbackCompletion { -+ -+ public CompoundTag data; -+ public Throwable throwable; -+ public boolean completeNow; -+ public boolean tasksNeedsScheduling; -+ public boolean needsRegionFileTest; -+ public Boolean regionFileCalculation; -+ -+ } -+ -+ static final class CancellableRead implements Cancellable { -+ -+ private BiConsumer callback; -+ private RegionFileIOThread.ChunkDataTask task; -+ -+ CancellableRead(final BiConsumer callback, final RegionFileIOThread.ChunkDataTask task) { -+ this.callback = callback; -+ this.task = task; -+ } -+ -+ @Override -+ public boolean cancel() { -+ final BiConsumer callback = this.callback; -+ final RegionFileIOThread.ChunkDataTask task = this.task; -+ -+ if (callback == null || task == null) { -+ return false; -+ } -+ -+ this.callback = null; -+ this.task = null; -+ -+ final RegionFileIOThread.InProgressRead read = task.inProgressRead; -+ -+ // read can be null if no read was scheduled (i.e no regionfile existed or chunk in regionfile didn't) -+ return (read != null && read.waiters.remove(callback)); -+ } -+ } -+ -+ static final class CancellableReads implements Cancellable { -+ -+ private Cancellable[] reads; -+ -+ protected static final VarHandle READS_HANDLE = ConcurrentUtil.getVarHandle(CancellableReads.class, "reads", Cancellable[].class); -+ -+ CancellableReads(final Cancellable[] reads) { -+ this.reads = reads; -+ } -+ -+ @Override -+ public boolean cancel() { -+ final Cancellable[] reads = (Cancellable[])READS_HANDLE.getAndSet((CancellableReads)this, (Cancellable[])null); -+ -+ if (reads == null) { -+ return false; -+ } -+ -+ boolean ret = false; -+ -+ for (final Cancellable read : reads) { -+ ret |= read.cancel(); -+ } -+ -+ return ret; -+ } -+ } -+ -+ static final class InProgressRead { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ CompoundTag value; -+ Throwable throwable; -+ final MultiThreadedQueue> waiters = new MultiThreadedQueue<>(); -+ -+ // rets false if already completed (callback not invoked), true if callback was added -+ boolean addToWaiters(final BiConsumer callback) { -+ return this.waiters.add(callback); -+ } -+ -+ void complete(final RegionFileIOThread.ChunkDataTask task, final CompoundTag value, final Throwable throwable) { -+ this.value = value; -+ this.throwable = throwable; -+ -+ BiConsumer consumer; -+ while ((consumer = this.waiters.pollOrBlockAdds()) != null) { -+ try { -+ consumer.accept(value, throwable); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Callback " + ConcurrentUtil.genericToString(consumer) + " failed to handle chunk data for task " + task.toString(), thr); -+ } -+ } -+ } -+ } -+ -+ /** -+ * Class exists to replace {@link Long} usages as keys inside non-fastutil hashtables. The hash for some Long {@code x} -+ * is defined as {@code (x >>> 32) ^ x}. Chunk keys as long values are defined as {@code ((chunkX & 0xFFFFFFFFL) | (chunkZ << 32))}, -+ * which means the hashcode as a Long value will be {@code chunkX ^ chunkZ}. Given that most chunks are created within a radius arounds players, -+ * this will lead to many hash collisions. So, this class uses a better hashing algorithm so that usage of -+ * non-fastutil collections is not degraded. -+ */ -+ public static final class ChunkCoordinate implements Comparable { -+ -+ public final long key; -+ -+ public ChunkCoordinate(final long key) { -+ this.key = key; -+ } -+ -+ @Override -+ public int hashCode() { -+ return (int)HashCommon.mix(this.key); -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (!(obj instanceof ChunkCoordinate)) { -+ return false; -+ } -+ -+ final ChunkCoordinate other = (ChunkCoordinate)obj; -+ -+ return this.key == other.key; -+ } -+ -+ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain -+ // is too large. So we should implement compareTo to help. -+ @Override -+ public int compareTo(final RegionFileIOThread.ChunkCoordinate other) { -+ return Long.compare(this.key, other.key); -+ } -+ -+ @Override -+ public String toString() { -+ return new ChunkPos(this.key).toString(); -+ } -+ } -+ -+ public static abstract class ChunkDataController { -+ -+ // ConcurrentHashMap synchronizes per chain, so reduce the chance of task's hashes colliding. -+ protected final ConcurrentHashMap tasks = new ConcurrentHashMap<>(8192, 0.10f); -+ -+ public final RegionFileType type; -+ -+ public ChunkDataController(final RegionFileType type) { -+ this.type = type; -+ } -+ -+ public abstract RegionFileStorage getCache(); -+ -+ public abstract void writeData(final int chunkX, final int chunkZ, final CompoundTag compound) throws IOException; -+ -+ public abstract CompoundTag readData(final int chunkX, final int chunkZ) throws IOException; -+ -+ public boolean hasTasks() { -+ return !this.tasks.isEmpty(); -+ } -+ -+ public boolean doesRegionFileNotExist(final int chunkX, final int chunkZ) { -+ return this.getCache().doesRegionFileNotExistNoIO(new ChunkPos(chunkX, chunkZ)); -+ } -+ -+ public T computeForRegionFile(final int chunkX, final int chunkZ, final boolean existingOnly, final Function function) { -+ final RegionFileStorage cache = this.getCache(); -+ final RegionFile regionFile; -+ synchronized (cache) { -+ try { -+ regionFile = cache.getRegionFile(new ChunkPos(chunkX, chunkZ), existingOnly, true); -+ } catch (final IOException ex) { -+ throw new RuntimeException(ex); -+ } -+ } -+ -+ try { -+ return function.apply(regionFile); -+ } finally { -+ if (regionFile != null) { -+ regionFile.fileLock.unlock(); -+ } -+ } -+ } -+ -+ public T computeForRegionFileIfLoaded(final int chunkX, final int chunkZ, final Function function) { -+ final RegionFileStorage cache = this.getCache(); -+ final RegionFile regionFile; -+ -+ synchronized (cache) { -+ regionFile = cache.getRegionFileIfLoaded(new ChunkPos(chunkX, chunkZ)); -+ if (regionFile != null) { -+ regionFile.fileLock.lock(); -+ } -+ } -+ -+ try { -+ return function.apply(regionFile); -+ } finally { -+ if (regionFile != null) { -+ regionFile.fileLock.unlock(); -+ } -+ } -+ } -+ } -+ -+ static final class ChunkDataTask implements Runnable { -+ -+ protected static final CompoundTag NOTHING_TO_WRITE = new CompoundTag(); -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ RegionFileIOThread.InProgressRead inProgressRead; -+ volatile CompoundTag inProgressWrite = NOTHING_TO_WRITE; // only needs to be acquire/release -+ -+ boolean failedWrite; -+ -+ final ServerLevel world; -+ final int chunkX; -+ final int chunkZ; -+ final RegionFileIOThread.ChunkDataController taskController; -+ -+ final PrioritisedExecutor.PrioritisedTask prioritisedTask; -+ -+ /* -+ * IO thread will perform reads before writes for a given chunk x and z -+ * -+ * How reads/writes are scheduled: -+ * -+ * If read is scheduled while scheduling write, take no special action and just schedule write -+ * If read is scheduled while scheduling read and no write is scheduled, chain the read task -+ * -+ * -+ * If write is scheduled while scheduling read, use the pending write data and ret immediately (so no read is scheduled) -+ * If write is scheduled while scheduling write (ignore read in progress), overwrite the write in progress data -+ * -+ * This allows the reads and writes to act as if they occur synchronously to the thread scheduling them, however -+ * it fails to properly propagate write failures thanks to writes overwriting each other -+ */ -+ -+ public ChunkDataTask(final ServerLevel world, final int chunkX, final int chunkZ, final RegionFileIOThread.ChunkDataController taskController, -+ final PrioritisedExecutor executor, final PrioritisedExecutor.Priority priority) { -+ this.world = world; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.taskController = taskController; -+ this.prioritisedTask = executor.createTask(this, priority); -+ } -+ -+ @Override -+ public String toString() { -+ return "Task for world: '" + this.world.getWorld().getName() + "' at (" + this.chunkX + "," + this.chunkZ + -+ ") type: " + this.taskController.type.name() + ", hash: " + this.hashCode(); -+ } -+ -+ @Override -+ public void run() { -+ final RegionFileIOThread.InProgressRead read = this.inProgressRead; -+ final ChunkCoordinate chunkKey = new ChunkCoordinate(CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ)); -+ -+ if (read != null) { -+ final boolean[] canRead = new boolean[] { true }; -+ -+ if (read.waiters.isEmpty()) { -+ // cancelled read? go to task controller to confirm -+ final ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final ChunkCoordinate keyInMap, final ChunkDataTask valueInMap) -> { -+ if (valueInMap == null) { -+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); -+ } -+ if (valueInMap != ChunkDataTask.this) { -+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -+ } -+ -+ if (!read.waiters.isEmpty()) { // as per usual IntelliJ is unable to figure out that there are concurrent accesses. -+ return valueInMap; -+ } else { -+ canRead[0] = false; -+ } -+ -+ return valueInMap.inProgressWrite == NOTHING_TO_WRITE ? null : valueInMap; -+ }); -+ -+ if (inMap == null) { -+ // read is cancelled - and no write pending, so we're done -+ return; -+ } -+ // if there is a write in progress, we don't actually have to worry about waiters gaining new entries - -+ // the readers will just use the in progress write, so the value in canRead is good to use without -+ // further synchronisation. -+ } -+ -+ if (canRead[0]) { -+ CompoundTag compound = null; -+ Throwable throwable = null; -+ -+ try { -+ compound = this.taskController.readData(this.chunkX, this.chunkZ); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ throwable = thr; -+ LOGGER.error("Failed to read chunk data for task: " + this.toString(), thr); -+ } -+ read.complete(this, compound, throwable); -+ } -+ } -+ -+ CompoundTag write = this.inProgressWrite; -+ -+ if (write == NOTHING_TO_WRITE) { -+ final ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final ChunkCoordinate keyInMap, final ChunkDataTask valueInMap) -> { -+ if (valueInMap == null) { -+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); -+ } -+ if (valueInMap != ChunkDataTask.this) { -+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -+ } -+ return valueInMap.inProgressWrite == NOTHING_TO_WRITE ? null : valueInMap; -+ }); -+ -+ if (inMap == null) { -+ return; // set the task value to null, indicating we're done -+ } // else: inProgressWrite changed, so now we have something to write -+ } -+ -+ for (;;) { -+ write = this.inProgressWrite; -+ final CompoundTag dataWritten = write; -+ -+ boolean failedWrite = false; -+ -+ try { -+ this.taskController.writeData(this.chunkX, this.chunkZ, write); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ if (thr instanceof RegionFileStorage.RegionFileSizeException) { -+ final int maxSize = RegionFile.MAX_CHUNK_SIZE / (1024 * 1024); -+ LOGGER.error("Chunk at (" + this.chunkX + "," + this.chunkZ + ") in '" + this.world.getWorld().getName() + "' exceeds max size of " + maxSize + "MiB, it has been deleted from disk."); -+ } else { -+ failedWrite = thr instanceof IOException; -+ LOGGER.error("Failed to write chunk data for task: " + this.toString(), thr); -+ } -+ } -+ -+ final boolean finalFailWrite = failedWrite; -+ final boolean[] done = new boolean[] { false }; -+ -+ this.taskController.tasks.compute(chunkKey, (final ChunkCoordinate keyInMap, final ChunkDataTask valueInMap) -> { -+ if (valueInMap == null) { -+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!"); -+ } -+ if (valueInMap != ChunkDataTask.this) { -+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!"); -+ } -+ if (valueInMap.inProgressWrite == dataWritten) { -+ valueInMap.failedWrite = finalFailWrite; -+ done[0] = true; -+ // keep the data in map if we failed the write so we can try to prevent data loss -+ return finalFailWrite ? valueInMap : null; -+ } -+ // different data than expected, means we need to retry write -+ return valueInMap; -+ }); -+ -+ if (done[0]) { -+ return; -+ } -+ -+ // fetch & write new data -+ continue; -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/light/LightQueue.java b/src/main/java/io/papermc/paper/chunk/system/light/LightQueue.java -new file mode 100644 -index 0000000000000000000000000000000000000000..de28d6ee71990da74d9deb360fac8bde5adbc918 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/light/LightQueue.java -@@ -0,0 +1,283 @@ -+package io.papermc.paper.chunk.system.light; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.starlight.common.light.BlockStarLightEngine; -+import ca.spottedleaf.starlight.common.light.SkyStarLightEngine; -+import ca.spottedleaf.starlight.common.light.StarLightInterface; -+import io.papermc.paper.util.CoordinateUtils; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.SectionPos; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import java.util.ArrayList; -+import java.util.HashSet; -+import java.util.List; -+import java.util.Set; -+import java.util.concurrent.CompletableFuture; -+import java.util.function.BooleanSupplier; -+ -+public final class LightQueue { -+ -+ protected final Long2ObjectOpenHashMap chunkTasks = new Long2ObjectOpenHashMap<>(); -+ protected final StarLightInterface manager; -+ protected final ServerLevel world; -+ -+ public LightQueue(final StarLightInterface manager) { -+ this.manager = manager; -+ this.world = ((ServerLevel)manager.getWorld()); -+ } -+ -+ public void lowerPriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ final ChunkTasks task; -+ synchronized (this) { -+ task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ if (task != null) { -+ task.lowerPriority(priority); -+ } -+ } -+ -+ public void setPriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ final ChunkTasks task; -+ synchronized (this) { -+ task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ if (task != null) { -+ task.setPriority(priority); -+ } -+ } -+ -+ public void raisePriority(final int chunkX, final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ final ChunkTasks task; -+ synchronized (this) { -+ task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ if (task != null) { -+ task.raisePriority(priority); -+ } -+ } -+ -+ public PrioritisedExecutor.Priority getPriority(final int chunkX, final int chunkZ) { -+ final ChunkTasks task; -+ synchronized (this) { -+ task = this.chunkTasks.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ if (task != null) { -+ return task.getPriority(); -+ } -+ -+ return PrioritisedExecutor.Priority.COMPLETING; -+ } -+ -+ public boolean isEmpty() { -+ synchronized (this) { -+ return this.chunkTasks.isEmpty(); -+ } -+ } -+ -+ public ChunkTasks queueBlockChange(final BlockPos pos) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { -+ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this); -+ }); -+ tasks.changedPositions.add(pos.immutable()); -+ } -+ -+ tasks.schedule(); -+ -+ return tasks; -+ } -+ -+ public ChunkTasks queueSectionChange(final SectionPos pos, final boolean newEmptyValue) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { -+ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this); -+ }); -+ -+ if (tasks.changedSectionSet == null) { -+ tasks.changedSectionSet = new Boolean[this.manager.maxSection - this.manager.minSection + 1]; -+ } -+ tasks.changedSectionSet[pos.getY() - this.manager.minSection] = Boolean.valueOf(newEmptyValue); -+ } -+ -+ tasks.schedule(); -+ -+ return tasks; -+ } -+ -+ public ChunkTasks queueChunkLightTask(final ChunkPos pos, final BooleanSupplier lightTask, final PrioritisedExecutor.Priority priority) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { -+ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this, priority); -+ }); -+ if (tasks.lightTasks == null) { -+ tasks.lightTasks = new ArrayList<>(); -+ } -+ tasks.lightTasks.add(lightTask); -+ } -+ -+ tasks.schedule(); -+ -+ return tasks; -+ } -+ -+ public ChunkTasks queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { -+ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this); -+ }); -+ -+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksSky; -+ if (queuedEdges == null) { -+ queuedEdges = tasks.queuedEdgeChecksSky = new ShortOpenHashSet(); -+ } -+ queuedEdges.addAll(sections); -+ } -+ -+ tasks.schedule(); -+ -+ return tasks; -+ } -+ -+ public ChunkTasks queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) { -+ final ChunkTasks tasks; -+ -+ synchronized (this) { -+ tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), (final long keyInMap) -> { -+ return new ChunkTasks(keyInMap, LightQueue.this.manager, LightQueue.this); -+ }); -+ -+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksBlock; -+ if (queuedEdges == null) { -+ queuedEdges = tasks.queuedEdgeChecksBlock = new ShortOpenHashSet(); -+ } -+ queuedEdges.addAll(sections); -+ } -+ -+ tasks.schedule(); -+ -+ return tasks; -+ } -+ -+ public void removeChunk(final ChunkPos pos) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.remove(CoordinateUtils.getChunkKey(pos)); -+ } -+ if (tasks != null && tasks.cancel()) { -+ tasks.onComplete.complete(null); -+ } -+ } -+ -+ public static final class ChunkTasks implements Runnable { -+ -+ public final CompletableFuture onComplete = new CompletableFuture<>(); -+ public boolean isTicketAdded; -+ public final long chunkCoordinate; -+ -+ private final StarLightInterface lightEngine; -+ private final LightQueue queue; -+ private final PrioritisedExecutor.PrioritisedTask task; -+ private final Set changedPositions = new HashSet<>(); -+ private Boolean[] changedSectionSet; -+ private ShortOpenHashSet queuedEdgeChecksSky; -+ private ShortOpenHashSet queuedEdgeChecksBlock; -+ private List lightTasks; -+ -+ public ChunkTasks(final long chunkCoordinate, final StarLightInterface lightEngine, final LightQueue queue) { -+ this(chunkCoordinate, lightEngine, queue, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ public ChunkTasks(final long chunkCoordinate, final StarLightInterface lightEngine, final LightQueue queue, -+ final PrioritisedExecutor.Priority priority) { -+ this.chunkCoordinate = chunkCoordinate; -+ this.lightEngine = lightEngine; -+ this.queue = queue; -+ this.task = queue.world.chunkTaskScheduler.radiusAwareScheduler.createTask( -+ CoordinateUtils.getChunkX(chunkCoordinate), CoordinateUtils.getChunkZ(chunkCoordinate), -+ ChunkStatus.LIGHT.writeRadius, this, priority -+ ); -+ } -+ -+ public void schedule() { -+ this.task.queue(); -+ } -+ -+ public boolean cancel() { -+ return this.task.cancel(); -+ } -+ -+ public PrioritisedExecutor.Priority getPriority() { -+ return this.task.getPriority(); -+ } -+ -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ this.task.lowerPriority(priority); -+ } -+ -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ this.task.setPriority(priority); -+ } -+ -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ this.task.raisePriority(priority); -+ } -+ -+ @Override -+ public void run() { -+ synchronized (this.queue) { -+ this.queue.chunkTasks.remove(this.chunkCoordinate); -+ } -+ -+ boolean litChunk = false; -+ if (this.lightTasks != null) { -+ for (final BooleanSupplier run : this.lightTasks) { -+ if (run.getAsBoolean()) { -+ litChunk = true; -+ break; -+ } -+ } -+ } -+ -+ final SkyStarLightEngine skyEngine = this.lightEngine.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.lightEngine.getBlockLightEngine(); -+ try { -+ final long coordinate = this.chunkCoordinate; -+ final int chunkX = CoordinateUtils.getChunkX(coordinate); -+ final int chunkZ = CoordinateUtils.getChunkZ(coordinate); -+ -+ final Set positions = this.changedPositions; -+ final Boolean[] sectionChanges = this.changedSectionSet; -+ -+ if (!litChunk) { -+ if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -+ skyEngine.blocksChangedInChunk(this.lightEngine.getLightAccess(), chunkX, chunkZ, positions, sectionChanges); -+ } -+ if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -+ blockEngine.blocksChangedInChunk(this.lightEngine.getLightAccess(), chunkX, chunkZ, positions, sectionChanges); -+ } -+ -+ if (skyEngine != null && this.queuedEdgeChecksSky != null) { -+ skyEngine.checkChunkEdges(this.lightEngine.getLightAccess(), chunkX, chunkZ, this.queuedEdgeChecksSky); -+ } -+ if (blockEngine != null && this.queuedEdgeChecksBlock != null) { -+ blockEngine.checkChunkEdges(this.lightEngine.getLightAccess(), chunkX, chunkZ, this.queuedEdgeChecksBlock); -+ } -+ } -+ -+ this.onComplete.complete(null); -+ } finally { -+ this.lightEngine.releaseSkyLightEngine(skyEngine); -+ this.lightEngine.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/poi/PoiChunk.java b/src/main/java/io/papermc/paper/chunk/system/poi/PoiChunk.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d72041aa814ff179e6e29a45dcd359a91d426d47 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/poi/PoiChunk.java -@@ -0,0 +1,213 @@ -+package io.papermc.paper.chunk.system.poi; -+ -+import com.mojang.logging.LogUtils; -+import com.mojang.serialization.Codec; -+import com.mojang.serialization.DataResult; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import io.papermc.paper.util.WorldUtil; -+import net.minecraft.SharedConstants; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.NbtOps; -+import net.minecraft.nbt.Tag; -+import net.minecraft.resources.RegistryOps; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.entity.ai.village.poi.PoiManager; -+import net.minecraft.world.entity.ai.village.poi.PoiSection; -+import org.slf4j.Logger; -+ -+import java.util.Optional; -+ -+public final class PoiChunk { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ public final ServerLevel world; -+ public final int chunkX; -+ public final int chunkZ; -+ public final int minSection; -+ public final int maxSection; -+ -+ protected final PoiSection[] sections; -+ -+ private boolean isDirty; -+ private boolean loaded; -+ -+ public PoiChunk(final ServerLevel world, final int chunkX, final int chunkZ, final int minSection, final int maxSection) { -+ this(world, chunkX, chunkZ, minSection, maxSection, new PoiSection[maxSection - minSection + 1]); -+ } -+ -+ public PoiChunk(final ServerLevel world, final int chunkX, final int chunkZ, final int minSection, final int maxSection, final PoiSection[] sections) { -+ this.world = world; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.minSection = minSection; -+ this.maxSection = maxSection; -+ this.sections = sections; -+ if (this.sections.length != (maxSection - minSection + 1)) { -+ throw new IllegalStateException("Incorrect length used, expected " + (maxSection - minSection + 1) + ", got " + this.sections.length); -+ } -+ } -+ -+ public void load() { -+ TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Loading in poi chunk off-main"); -+ if (this.loaded) { -+ return; -+ } -+ this.loaded = true; -+ this.world.chunkSource.getPoiManager().loadInPoiChunk(this); -+ } -+ -+ public boolean isLoaded() { -+ return this.loaded; -+ } -+ -+ public boolean isEmpty() { -+ for (final PoiSection section : this.sections) { -+ if (section != null && !section.isEmpty()) { -+ return false; -+ } -+ } -+ -+ return true; -+ } -+ -+ public PoiSection getOrCreateSection(final int chunkY) { -+ if (chunkY >= this.minSection && chunkY <= this.maxSection) { -+ final int idx = chunkY - this.minSection; -+ final PoiSection ret = this.sections[idx]; -+ if (ret != null) { -+ return ret; -+ } -+ -+ final PoiManager poiManager = this.world.getPoiManager(); -+ final long key = CoordinateUtils.getChunkSectionKey(this.chunkX, chunkY, this.chunkZ); -+ -+ return this.sections[idx] = new PoiSection(() -> { -+ poiManager.setDirty(key); -+ }); -+ } -+ throw new IllegalArgumentException("chunkY is out of bounds, chunkY: " + chunkY + " outside [" + this.minSection + "," + this.maxSection + "]"); -+ } -+ -+ public PoiSection getSection(final int chunkY) { -+ if (chunkY >= this.minSection && chunkY <= this.maxSection) { -+ return this.sections[chunkY - this.minSection]; -+ } -+ return null; -+ } -+ -+ public Optional getSectionForVanilla(final int chunkY) { -+ if (chunkY >= this.minSection && chunkY <= this.maxSection) { -+ final PoiSection ret = this.sections[chunkY - this.minSection]; -+ return ret == null ? Optional.empty() : ret.noAllocateOptional; -+ } -+ return Optional.empty(); -+ } -+ -+ public boolean isDirty() { -+ return this.isDirty; -+ } -+ -+ public void setDirty(final boolean dirty) { -+ this.isDirty = dirty; -+ } -+ -+ // returns null if empty -+ public CompoundTag save() { -+ final RegistryOps registryOps = RegistryOps.create(NbtOps.INSTANCE, world.getPoiManager().registryAccess); -+ -+ final CompoundTag ret = new CompoundTag(); -+ final CompoundTag sections = new CompoundTag(); -+ ret.put("Sections", sections); -+ -+ ret.putInt("DataVersion", SharedConstants.getCurrentVersion().getDataVersion().getVersion()); -+ -+ final ServerLevel world = this.world; -+ final PoiManager poiManager = world.getPoiManager(); -+ final int chunkX = this.chunkX; -+ final int chunkZ = this.chunkZ; -+ -+ for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) { -+ final PoiSection chunk = this.sections[sectionY - this.minSection]; -+ if (chunk == null || chunk.isEmpty()) { -+ continue; -+ } -+ -+ final long key = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ); -+ // codecs are honestly such a fucking disaster. What the fuck is this trash? -+ final Codec codec = PoiSection.codec(() -> { -+ poiManager.setDirty(key); -+ }); -+ -+ final DataResult serializedResult = codec.encodeStart(registryOps, chunk); -+ final int finalSectionY = sectionY; -+ final Tag serialized = serializedResult.resultOrPartial((final String description) -> { -+ LOGGER.error("Failed to serialize poi chunk for world: " + world.getWorld().getName() + ", chunk: (" + chunkX + "," + finalSectionY + "," + chunkZ + "); description: " + description); -+ }).orElse(null); -+ if (serialized == null) { -+ // failed, should be logged from the resultOrPartial -+ continue; -+ } -+ -+ sections.put(Integer.toString(sectionY), serialized); -+ } -+ -+ return sections.isEmpty() ? null : ret; -+ } -+ -+ public static PoiChunk empty(final ServerLevel world, final int chunkX, final int chunkZ) { -+ final PoiChunk ret = new PoiChunk(world, chunkX, chunkZ, WorldUtil.getMinSection(world), WorldUtil.getMaxSection(world)); -+ ret.loaded = true; -+ return ret; -+ } -+ -+ public static PoiChunk parse(final ServerLevel world, final int chunkX, final int chunkZ, final CompoundTag data) { -+ final PoiChunk ret = empty(world, chunkX, chunkZ); -+ -+ final RegistryOps registryOps = RegistryOps.create(NbtOps.INSTANCE, world.getPoiManager().registryAccess); -+ -+ final CompoundTag sections = data.getCompound("Sections"); -+ -+ if (sections.isEmpty()) { -+ // nothing to parse -+ return ret; -+ } -+ -+ final PoiManager poiManager = world.getPoiManager(); -+ -+ boolean readAnything = false; -+ -+ for (int sectionY = ret.minSection; sectionY <= ret.maxSection; ++sectionY) { -+ final String key = Integer.toString(sectionY); -+ if (!sections.contains(key)) { -+ continue; -+ } -+ -+ final long coordinateKey = CoordinateUtils.getChunkSectionKey(chunkX, sectionY, chunkZ); -+ // codecs are honestly such a fucking disaster. What the fuck is this trash? -+ final Codec codec = PoiSection.codec(() -> { -+ poiManager.setDirty(coordinateKey); -+ }); -+ -+ final CompoundTag section = sections.getCompound(key); -+ final DataResult deserializeResult = codec.parse(registryOps, section); -+ final int finalSectionY = sectionY; -+ final PoiSection deserialized = deserializeResult.resultOrPartial((final String description) -> { -+ LOGGER.error("Failed to deserialize poi chunk for world: " + world.getWorld().getName() + ", chunk: (" + chunkX + "," + finalSectionY + "," + chunkZ + "); description: " + description); -+ }).orElse(null); -+ -+ if (deserialized == null || deserialized.isEmpty()) { -+ // completely empty, no point in storing this -+ continue; -+ } -+ -+ readAnything = true; -+ ret.sections[sectionY - ret.minSection] = deserialized; -+ } -+ -+ ret.loaded = !readAnything; // Set loaded to false if we read anything to ensure proper callbacks to PoiManager are made on #load -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkFullTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkFullTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..679ed4d53269e1113035b462cf74ab16a231e22e ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkFullTask.java -@@ -0,0 +1,135 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.poi.PoiChunk; -+import net.minecraft.server.level.ChunkMap; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.ImposterProtoChunk; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import org.slf4j.Logger; -+import java.lang.invoke.VarHandle; -+ -+public final class ChunkFullTask extends ChunkProgressionTask implements Runnable { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ protected final NewChunkHolder chunkHolder; -+ protected final ChunkAccess fromChunk; -+ protected final PrioritisedExecutor.PrioritisedTask convertToFullTask; -+ -+ public ChunkFullTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, -+ final NewChunkHolder chunkHolder, final ChunkAccess fromChunk, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ); -+ this.chunkHolder = chunkHolder; -+ this.fromChunk = fromChunk; -+ this.convertToFullTask = scheduler.createChunkTask(chunkX, chunkZ, this, priority); -+ } -+ -+ @Override -+ public ChunkStatus getTargetStatus() { -+ return ChunkStatus.FULL; -+ } -+ -+ @Override -+ public void run() { -+ // See Vanilla protoChunkToFullChunk for what this function should be doing -+ final LevelChunk chunk; -+ try { -+ // moved from the load from nbt stage into here -+ final PoiChunk poiChunk = this.chunkHolder.getPoiChunk(); -+ if (poiChunk == null) { -+ LOGGER.error("Expected poi chunk to be loaded with chunk for task " + this.toString()); -+ } else { -+ poiChunk.load(); -+ this.world.getPoiManager().checkConsistency(this.fromChunk); -+ } -+ -+ if (this.fromChunk instanceof ImposterProtoChunk wrappedFull) { -+ chunk = wrappedFull.getWrapped(); -+ } else { -+ final ServerLevel world = this.world; -+ final ProtoChunk protoChunk = (ProtoChunk)this.fromChunk; -+ chunk = new LevelChunk(this.world, protoChunk, (final LevelChunk unused) -> { -+ ChunkMap.postLoadProtoChunk(world, protoChunk.getEntities(), protoChunk.getPos()); // Paper - rewrite chunk system -+ }); -+ } -+ -+ chunk.setChunkHolder(this.scheduler.chunkHolderManager.getChunkHolder(this.chunkX, this.chunkZ)); // replaces setFullStatus -+ chunk.runPostLoad(); -+ // Unlike Vanilla, we load the entity chunk here, as we load the NBT in empty status (unlike Vanilla) -+ // This brings entity addition back in line with older versions of the game -+ // Since we load the NBT in the empty status, this will never block for I/O -+ this.world.chunkTaskScheduler.chunkHolderManager.getOrCreateEntityChunk(this.chunkX, this.chunkZ, false); -+ -+ // we don't need the entitiesInLevel trash, this system doesn't double run callbacks -+ chunk.setLoaded(true); -+ chunk.registerAllBlockEntitiesAfterLevelLoad(); -+ chunk.registerTickContainerInLevel(this.world); -+ } catch (final Throwable throwable) { -+ this.complete(null, throwable); -+ -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ return; -+ } -+ this.complete(chunk, null); -+ } -+ -+ protected volatile boolean scheduled; -+ protected static final VarHandle SCHEDULED_HANDLE = ConcurrentUtil.getVarHandle(ChunkFullTask.class, "scheduled", boolean.class); -+ -+ @Override -+ public boolean isScheduled() { -+ return this.scheduled; -+ } -+ -+ @Override -+ public void schedule() { -+ if ((boolean)SCHEDULED_HANDLE.getAndSet((ChunkFullTask)this, true)) { -+ throw new IllegalStateException("Cannot double call schedule()"); -+ } -+ this.convertToFullTask.queue(); -+ } -+ -+ @Override -+ public void cancel() { -+ if (this.convertToFullTask.cancel()) { -+ this.complete(null, null); -+ } -+ } -+ -+ @Override -+ public PrioritisedExecutor.Priority getPriority() { -+ return this.convertToFullTask.getPriority(); -+ } -+ -+ @Override -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.convertToFullTask.lowerPriority(priority); -+ } -+ -+ @Override -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.convertToFullTask.setPriority(priority); -+ } -+ -+ @Override -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.convertToFullTask.raisePriority(priority); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5b446e6ac151f99f64f0c442d0b40b5e251bc4c4 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java -@@ -0,0 +1,1500 @@ -+package io.papermc.paper.chunk.system.scheduling; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; -+import ca.spottedleaf.concurrentutil.map.SWMRLong2ObjectHashTable; -+import com.google.common.collect.ImmutableList; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonObject; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader; -+import io.papermc.paper.chunk.system.io.RegionFileIOThread; -+import io.papermc.paper.chunk.system.poi.PoiChunk; -+import io.papermc.paper.threadedregions.TickRegions; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import io.papermc.paper.world.ChunkEntitySlices; -+import it.unimi.dsi.fastutil.longs.Long2ByteLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.longs.Long2ByteMap; -+import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.longs.Long2IntMap; -+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -+import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; -+import net.minecraft.nbt.CompoundTag; -+import io.papermc.paper.chunk.system.ChunkSystem; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ChunkLevel; -+import net.minecraft.server.level.FullChunkStatus; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.Ticket; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.util.SortedArraySet; -+import net.minecraft.util.Unit; -+import net.minecraft.world.level.ChunkPos; -+import org.bukkit.plugin.Plugin; -+import org.slf4j.Logger; -+import java.io.IOException; -+import java.text.DecimalFormat; -+import java.util.ArrayDeque; -+import java.util.ArrayList; -+import java.util.Collection; -+import java.util.Collections; -+import java.util.Iterator; -+import java.util.List; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.TimeUnit; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.concurrent.atomic.AtomicReference; -+import java.util.concurrent.locks.LockSupport; -+import java.util.function.Predicate; -+ -+public final class ChunkHolderManager { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ public static final int FULL_LOADED_TICKET_LEVEL = 33; -+ public static final int BLOCK_TICKING_TICKET_LEVEL = 32; -+ public static final int ENTITY_TICKING_TICKET_LEVEL = 31; -+ public static final int MAX_TICKET_LEVEL = ChunkLevel.MAX_LEVEL; // inclusive -+ -+ private static final long NO_TIMEOUT_MARKER = Long.MIN_VALUE; -+ private static final long PROBE_MARKER = Long.MIN_VALUE + 1; -+ public final ReentrantAreaLock ticketLockArea; -+ -+ private final ConcurrentHashMap>> tickets = new java.util.concurrent.ConcurrentHashMap<>(); -+ private final ConcurrentHashMap sectionToChunkToExpireCount = new java.util.concurrent.ConcurrentHashMap<>(); -+ final ChunkQueue unloadQueue; -+ -+ public boolean processTicketUpdates(final int posX, final int posZ) { -+ final int ticketShift = ThreadedTicketLevelPropagator.SECTION_SHIFT; -+ final int ticketMask = (1 << ticketShift) - 1; -+ final List scheduledTasks = new ArrayList<>(); -+ final List changedFullStatus = new ArrayList<>(); -+ final boolean ret; -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( -+ ((posX >> ticketShift) - 1) << ticketShift, -+ ((posZ >> ticketShift) - 1) << ticketShift, -+ (((posX >> ticketShift) + 1) << ticketShift) | ticketMask, -+ (((posZ >> ticketShift) + 1) << ticketShift) | ticketMask -+ ); -+ try { -+ ret = this.processTicketUpdatesNoLock(posX >> ticketShift, posZ >> ticketShift, scheduledTasks, changedFullStatus); -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ -+ this.addChangedStatuses(changedFullStatus); -+ -+ for (int i = 0, len = scheduledTasks.size(); i < len; ++i) { -+ scheduledTasks.get(i).schedule(); -+ } -+ -+ return ret; -+ } -+ -+ private boolean processTicketUpdatesNoLock(final int sectionX, final int sectionZ, final List scheduledTasks, -+ final List changedFullStatus) { -+ return this.ticketLevelPropagator.performUpdate( -+ sectionX, sectionZ, this.taskScheduler.schedulingLockArea, scheduledTasks, changedFullStatus -+ ); -+ } -+ -+ private final SWMRLong2ObjectHashTable chunkHolders = new SWMRLong2ObjectHashTable<>(16384, 0.25f); -+ // what a disaster of a name -+ // this is a map of removal tick to a map of chunks and the number of tickets a chunk has that are to expire that tick -+ private final Long2ObjectOpenHashMap removeTickToChunkExpireTicketCount = new Long2ObjectOpenHashMap<>(); -+ private final ServerLevel world; -+ private final ChunkTaskScheduler taskScheduler; -+ private long currentTick; -+ -+ private final ArrayDeque pendingFullLoadUpdate = new ArrayDeque<>(); -+ private final ObjectRBTreeSet autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> { -+ if (c1 == c2) { -+ return 0; -+ } -+ -+ final int saveTickCompare = Long.compare(c1.lastAutoSave, c2.lastAutoSave); -+ -+ if (saveTickCompare != 0) { -+ return saveTickCompare; -+ } -+ -+ final long coord1 = CoordinateUtils.getChunkKey(c1.chunkX, c1.chunkZ); -+ final long coord2 = CoordinateUtils.getChunkKey(c2.chunkX, c2.chunkZ); -+ -+ if (coord1 == coord2) { -+ throw new IllegalStateException("Duplicate chunkholder in auto save queue"); -+ } -+ -+ return Long.compare(coord1, coord2); -+ }); -+ -+ public ChunkHolderManager(final ServerLevel world, final ChunkTaskScheduler taskScheduler) { -+ this.world = world; -+ this.taskScheduler = taskScheduler; -+ this.ticketLockArea = new ReentrantAreaLock(taskScheduler.getChunkSystemLockShift()); -+ this.unloadQueue = new ChunkQueue(world.getRegionChunkShift()); -+ } -+ -+ private final AtomicLong statusUpgradeId = new AtomicLong(); -+ -+ long getNextStatusUpgradeId() { -+ return this.statusUpgradeId.incrementAndGet(); -+ } -+ -+ public List getOldChunkHolders() { -+ final List holders = this.getChunkHolders(); -+ final List ret = new ArrayList<>(holders.size()); -+ for (final NewChunkHolder holder : holders) { -+ ret.add(holder.vanillaChunkHolder); -+ } -+ return ret; -+ } -+ -+ public List getChunkHolders() { -+ final List ret = new ArrayList<>(this.chunkHolders.size()); -+ this.chunkHolders.forEachValue(ret::add); -+ return ret; -+ } -+ -+ public int size() { -+ return this.chunkHolders.size(); -+ } -+ -+ public void close(final boolean save, final boolean halt) { -+ TickThread.ensureTickThread("Closing world off-main"); -+ if (halt) { -+ LOGGER.info("Waiting 60s for chunk system to halt for world '" + this.world.getWorld().getName() + "'"); -+ if (!this.taskScheduler.halt(true, TimeUnit.SECONDS.toNanos(60L))) { -+ LOGGER.warn("Failed to halt world generation/loading tasks for world '" + this.world.getWorld().getName() + "'"); -+ } else { -+ LOGGER.info("Halted chunk system for world '" + this.world.getWorld().getName() + "'"); -+ } -+ } -+ -+ if (save) { -+ this.saveAllChunks(true, true, true); -+ } -+ -+ if (this.world.chunkDataControllerNew.hasTasks() || this.world.entityDataControllerNew.hasTasks() || this.world.poiDataControllerNew.hasTasks()) { -+ RegionFileIOThread.flush(); -+ } -+ -+ // kill regionfile cache -+ try { -+ this.world.chunkDataControllerNew.getCache().close(); -+ } catch (final IOException ex) { -+ LOGGER.error("Failed to close chunk regionfile cache for world '" + this.world.getWorld().getName() + "'", ex); -+ } -+ try { -+ this.world.entityDataControllerNew.getCache().close(); -+ } catch (final IOException ex) { -+ LOGGER.error("Failed to close entity regionfile cache for world '" + this.world.getWorld().getName() + "'", ex); -+ } -+ try { -+ this.world.poiDataControllerNew.getCache().close(); -+ } catch (final IOException ex) { -+ LOGGER.error("Failed to close poi regionfile cache for world '" + this.world.getWorld().getName() + "'", ex); -+ } -+ } -+ -+ void ensureInAutosave(final NewChunkHolder holder) { -+ if (!this.autoSaveQueue.contains(holder)) { -+ holder.lastAutoSave = MinecraftServer.currentTick; -+ this.autoSaveQueue.add(holder); -+ } -+ } -+ -+ public void autoSave() { -+ final List reschedule = new ArrayList<>(); -+ final long currentTick = MinecraftServer.currentTickLong; -+ final long maxSaveTime = currentTick - this.world.paperConfig().chunks.autoSaveInterval.value(); -+ for (int autoSaved = 0; autoSaved < this.world.paperConfig().chunks.maxAutoSaveChunksPerTick && !this.autoSaveQueue.isEmpty();) { -+ final NewChunkHolder holder = this.autoSaveQueue.first(); -+ -+ if (holder.lastAutoSave > maxSaveTime) { -+ break; -+ } -+ -+ this.autoSaveQueue.remove(holder); -+ -+ holder.lastAutoSave = currentTick; -+ if (holder.save(false, false) != null) { -+ ++autoSaved; -+ } -+ -+ if (holder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)) { -+ reschedule.add(holder); -+ } -+ } -+ -+ for (final NewChunkHolder holder : reschedule) { -+ if (holder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)) { -+ this.autoSaveQueue.add(holder); -+ } -+ } -+ } -+ -+ public void saveAllChunks(final boolean flush, final boolean shutdown, final boolean logProgress) { -+ final List holders = this.getChunkHolders(); -+ -+ if (logProgress) { -+ LOGGER.info("Saving all chunkholders for world '" + this.world.getWorld().getName() + "'"); -+ } -+ -+ final DecimalFormat format = new DecimalFormat("#0.00"); -+ -+ int saved = 0; -+ -+ long start = System.nanoTime(); -+ long lastLog = start; -+ boolean needsFlush = false; -+ final int flushInterval = 50; -+ -+ int savedChunk = 0; -+ int savedEntity = 0; -+ int savedPoi = 0; -+ -+ for (int i = 0, len = holders.size(); i < len; ++i) { -+ final NewChunkHolder holder = holders.get(i); -+ try { -+ final NewChunkHolder.SaveStat saveStat = holder.save(shutdown, false); -+ if (saveStat != null) { -+ ++saved; -+ needsFlush = flush; -+ if (saveStat.savedChunk()) { -+ ++savedChunk; -+ } -+ if (saveStat.savedEntityChunk()) { -+ ++savedEntity; -+ } -+ if (saveStat.savedPoiChunk()) { -+ ++savedPoi; -+ } -+ } -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to save chunk (" + holder.chunkX + "," + holder.chunkZ + ") in world '" + this.world.getWorld().getName() + "'", thr); -+ } -+ if (needsFlush && (saved % flushInterval) == 0) { -+ needsFlush = false; -+ RegionFileIOThread.partialFlush(flushInterval / 2); -+ } -+ if (logProgress) { -+ final long currTime = System.nanoTime(); -+ if ((currTime - lastLog) > TimeUnit.SECONDS.toNanos(10L)) { -+ lastLog = currTime; -+ LOGGER.info("Saved " + saved + " chunks (" + format.format((double)(i+1)/(double)len * 100.0) + "%) in world '" + this.world.getWorld().getName() + "'"); -+ } -+ } -+ } -+ if (flush) { -+ RegionFileIOThread.flush(); -+ if (this.world.paperConfig().chunks.flushRegionsOnSave) { -+ try { -+ this.world.chunkSource.chunkMap.regionFileCache.flush(); -+ } catch (IOException ex) { -+ LOGGER.error("Exception when flushing regions in world {}", this.world.getWorld().getName(), ex); -+ } -+ } -+ } -+ if (logProgress) { -+ LOGGER.info("Saved " + savedChunk + " block chunks, " + savedEntity + " entity chunks, " + savedPoi + " poi chunks in world '" + this.world.getWorld().getName() + "' in " + format.format(1.0E-9 * (System.nanoTime() - start)) + "s"); -+ } -+ } -+ -+ protected final ThreadedTicketLevelPropagator ticketLevelPropagator = new ThreadedTicketLevelPropagator() { -+ @Override -+ protected void processLevelUpdates(final Long2ByteLinkedOpenHashMap updates) { -+ // first the necessary chunkholders must be created, so just update the ticket levels -+ for (final Iterator iterator = updates.long2ByteEntrySet().fastIterator(); iterator.hasNext();) { -+ final Long2ByteMap.Entry entry = iterator.next(); -+ final long key = entry.getLongKey(); -+ final int newLevel = convertBetweenTicketLevels((int)entry.getByteValue()); -+ -+ NewChunkHolder current = ChunkHolderManager.this.chunkHolders.get(key); -+ if (current == null && newLevel > MAX_TICKET_LEVEL) { -+ // not loaded and it shouldn't be loaded! -+ iterator.remove(); -+ continue; -+ } -+ -+ final int currentLevel = current == null ? MAX_TICKET_LEVEL + 1 : current.getCurrentTicketLevel(); -+ if (currentLevel == newLevel) { -+ // nothing to do -+ iterator.remove(); -+ continue; -+ } -+ -+ if (current == null) { -+ // must create -+ current = ChunkHolderManager.this.createChunkHolder(key); -+ synchronized (ChunkHolderManager.this.chunkHolders) { -+ ChunkHolderManager.this.chunkHolders.put(key, current); -+ } -+ current.updateTicketLevel(newLevel); -+ } else { -+ current.updateTicketLevel(newLevel); -+ } -+ } -+ } -+ -+ @Override -+ protected void processSchedulingUpdates(final Long2ByteLinkedOpenHashMap updates, final List scheduledTasks, -+ final List changedFullStatus) { -+ final List prev = CURRENT_TICKET_UPDATE_SCHEDULING.get(); -+ CURRENT_TICKET_UPDATE_SCHEDULING.set(scheduledTasks); -+ try { -+ for (final LongIterator iterator = updates.keySet().iterator(); iterator.hasNext();) { -+ final long key = iterator.nextLong(); -+ final NewChunkHolder current = ChunkHolderManager.this.chunkHolders.get(key); -+ -+ if (current == null) { -+ throw new IllegalStateException("Expected chunk holder to be created"); -+ } -+ -+ current.processTicketLevelUpdate(scheduledTasks, changedFullStatus); -+ } -+ } finally { -+ CURRENT_TICKET_UPDATE_SCHEDULING.set(prev); -+ } -+ } -+ }; -+ // function for converting between ticket levels and propagator levels and vice versa -+ // the problem is the ticket level propagator will propagate from a set source down to zero, whereas mojang expects -+ // levels to propagate from a set value up to a maximum value. so we need to convert the levels we put into the propagator -+ // and the levels we get out of the propagator -+ -+ public static int convertBetweenTicketLevels(final int level) { -+ return ChunkLevel.MAX_LEVEL - level + 1; -+ } -+ -+ public String getTicketDebugString(final long coordinate) { -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate)); -+ try { -+ final SortedArraySet> tickets = this.tickets.get(new RegionFileIOThread.ChunkCoordinate(coordinate)); -+ -+ return tickets != null ? tickets.first().toString() : "no_ticket"; -+ } finally { -+ if (ticketLock != null) { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ } -+ -+ public Long2ObjectOpenHashMap>> getTicketsCopy() { -+ final Long2ObjectOpenHashMap>> ret = new Long2ObjectOpenHashMap<>(); -+ final Long2ObjectOpenHashMap> sections = new Long2ObjectOpenHashMap(); -+ final int sectionShift = this.taskScheduler.getChunkSystemLockShift(); -+ for (final RegionFileIOThread.ChunkCoordinate coord : this.tickets.keySet()) { -+ sections.computeIfAbsent( -+ CoordinateUtils.getChunkKey( -+ CoordinateUtils.getChunkX(coord.key) >> sectionShift, -+ CoordinateUtils.getChunkZ(coord.key) >> sectionShift -+ ), -+ (final long keyInMap) -> { -+ return new ArrayList<>(); -+ } -+ ).add(coord); -+ } -+ -+ for (final Iterator>> iterator = sections.long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2ObjectMap.Entry> entry = iterator.next(); -+ final long sectionKey = entry.getLongKey(); -+ final List coordinates = entry.getValue(); -+ -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( -+ CoordinateUtils.getChunkX(sectionKey) << sectionShift, -+ CoordinateUtils.getChunkZ(sectionKey) << sectionShift -+ ); -+ try { -+ for (final RegionFileIOThread.ChunkCoordinate coord : coordinates) { -+ final SortedArraySet> tickets = this.tickets.get(coord); -+ if (tickets == null) { -+ // removed before we acquired lock -+ continue; -+ } -+ ret.put(coord.key, new SortedArraySet<>(tickets)); -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ -+ return ret; -+ } -+ -+ public Collection getPluginChunkTickets(int x, int z) { -+ ImmutableList.Builder ret; -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(x, z); -+ try { -+ final long coordinate = CoordinateUtils.getChunkKey(x, z); -+ final SortedArraySet> tickets = this.tickets.get(new RegionFileIOThread.ChunkCoordinate(coordinate)); -+ -+ if (tickets == null) { -+ return Collections.emptyList(); -+ } -+ -+ ret = ImmutableList.builder(); -+ for (Ticket ticket : tickets) { -+ if (ticket.getType() == TicketType.PLUGIN_TICKET) { -+ ret.add((Plugin)ticket.key); -+ } -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ -+ return ret.build(); -+ } -+ -+ protected final void updateTicketLevel(final long coordinate, final int ticketLevel) { -+ if (ticketLevel > ChunkLevel.MAX_LEVEL) { -+ this.ticketLevelPropagator.removeSource(CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate)); -+ } else { -+ this.ticketLevelPropagator.setSource(CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate), convertBetweenTicketLevels(ticketLevel)); -+ } -+ } -+ -+ private static int getTicketLevelAt(SortedArraySet> tickets) { -+ return !tickets.isEmpty() ? tickets.first().getTicketLevel() : MAX_TICKET_LEVEL + 1; -+ } -+ -+ public boolean addTicketAtLevel(final TicketType type, final ChunkPos chunkPos, final int level, -+ final T identifier) { -+ return this.addTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkPos), level, identifier); -+ } -+ -+ public boolean addTicketAtLevel(final TicketType type, final int chunkX, final int chunkZ, final int level, -+ final T identifier) { -+ return this.addTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkX, chunkZ), level, identifier); -+ } -+ -+ private void addExpireCount(final int chunkX, final int chunkZ) { -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ final int sectionShift = this.world.getRegionChunkShift(); -+ final RegionFileIOThread.ChunkCoordinate sectionKey = new RegionFileIOThread.ChunkCoordinate(CoordinateUtils.getChunkKey( -+ chunkX >> sectionShift, -+ chunkZ >> sectionShift -+ )); -+ -+ this.sectionToChunkToExpireCount.computeIfAbsent(sectionKey, (final RegionFileIOThread.ChunkCoordinate keyInMap) -> { -+ return new Long2IntOpenHashMap(); -+ }).addTo(chunkKey, 1); -+ } -+ -+ private void removeExpireCount(final int chunkX, final int chunkZ) { -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ final int sectionShift = this.world.getRegionChunkShift(); -+ final RegionFileIOThread.ChunkCoordinate sectionKey = new RegionFileIOThread.ChunkCoordinate(CoordinateUtils.getChunkKey( -+ chunkX >> sectionShift, -+ chunkZ >> sectionShift -+ )); -+ -+ final Long2IntOpenHashMap removeCounts = this.sectionToChunkToExpireCount.get(sectionKey); -+ final int prevCount = removeCounts.addTo(chunkKey, -1); -+ -+ if (prevCount == 1) { -+ removeCounts.remove(chunkKey); -+ if (removeCounts.isEmpty()) { -+ this.sectionToChunkToExpireCount.remove(sectionKey); -+ } -+ } -+ } -+ -+ // supposed to return true if the ticket was added and did not replace another -+ // but, we always return false if the ticket cannot be added -+ public boolean addTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier) { -+ return this.addTicketAtLevel(type, chunk, level, identifier, true); -+ } -+ -+ boolean addTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier, final boolean lock) { -+ final long removeDelay = type.timeout <= 0 ? NO_TIMEOUT_MARKER : type.timeout; -+ if (level > MAX_TICKET_LEVEL) { -+ return false; -+ } -+ -+ final int chunkX = CoordinateUtils.getChunkX(chunk); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunk); -+ final RegionFileIOThread.ChunkCoordinate chunkCoord = new RegionFileIOThread.ChunkCoordinate(chunk); -+ final Ticket ticket = new Ticket<>(type, level, identifier, removeDelay); -+ -+ final ReentrantAreaLock.Node ticketLock = lock ? this.ticketLockArea.lock(chunkX, chunkZ) : null; -+ try { -+ final SortedArraySet> ticketsAtChunk = this.tickets.computeIfAbsent(chunkCoord, (final RegionFileIOThread.ChunkCoordinate keyInMap) -> { -+ return SortedArraySet.create(4); -+ }); -+ -+ final int levelBefore = getTicketLevelAt(ticketsAtChunk); -+ final Ticket current = (Ticket)ticketsAtChunk.replace(ticket); -+ final int levelAfter = getTicketLevelAt(ticketsAtChunk); -+ -+ if (current != ticket) { -+ final long oldRemoveDelay = current.removeDelay; -+ if (removeDelay != oldRemoveDelay) { -+ if (oldRemoveDelay != NO_TIMEOUT_MARKER && removeDelay == NO_TIMEOUT_MARKER) { -+ this.removeExpireCount(chunkX, chunkZ); -+ } else if (oldRemoveDelay == NO_TIMEOUT_MARKER) { -+ // since old != new, we have that NO_TIMEOUT_MARKER != new -+ this.addExpireCount(chunkX, chunkZ); -+ } -+ } -+ } else { -+ if (removeDelay != NO_TIMEOUT_MARKER) { -+ this.addExpireCount(chunkX, chunkZ); -+ } -+ } -+ -+ if (levelBefore != levelAfter) { -+ this.updateTicketLevel(chunk, levelAfter); -+ } -+ -+ return current == ticket; -+ } finally { -+ if (ticketLock != null) { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ } -+ -+ public boolean removeTicketAtLevel(final TicketType type, final ChunkPos chunkPos, final int level, final T identifier) { -+ return this.removeTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkPos), level, identifier); -+ } -+ -+ public boolean removeTicketAtLevel(final TicketType type, final int chunkX, final int chunkZ, final int level, final T identifier) { -+ return this.removeTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkX, chunkZ), level, identifier); -+ } -+ -+ public boolean removeTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier) { -+ return this.removeTicketAtLevel(type, chunk, level, identifier, true); -+ } -+ -+ boolean removeTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier, final boolean lock) { -+ if (level > MAX_TICKET_LEVEL) { -+ return false; -+ } -+ -+ final int chunkX = CoordinateUtils.getChunkX(chunk); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunk); -+ final RegionFileIOThread.ChunkCoordinate chunkCoord = new RegionFileIOThread.ChunkCoordinate(chunk); -+ final Ticket probe = new Ticket<>(type, level, identifier, PROBE_MARKER); -+ -+ final ReentrantAreaLock.Node ticketLock = lock ? this.ticketLockArea.lock(chunkX, chunkZ) : null; -+ try { -+ final SortedArraySet> ticketsAtChunk = this.tickets.get(chunkCoord); -+ if (ticketsAtChunk == null) { -+ return false; -+ } -+ -+ final int oldLevel = getTicketLevelAt(ticketsAtChunk); -+ final Ticket ticket = (Ticket)ticketsAtChunk.removeAndGet(probe); -+ -+ if (ticket == null) { -+ return false; -+ } -+ -+ final int newLevel = getTicketLevelAt(ticketsAtChunk); -+ // we should not change the ticket levels while the target region may be ticking -+ if (oldLevel != newLevel) { -+ // Delay unload chunk patch originally by Aikar, updated to 1.20 by jpenilla -+ // these days, the patch is mostly useful to keep chunks ticking when players teleport -+ // so that their pets can teleport with them as well. -+ final long delayTimeout = this.world.paperConfig().chunks.delayChunkUnloadsBy.ticks(); -+ final TicketType toAdd; -+ final long timeout; -+ if (type == RegionizedPlayerChunkLoader.REGION_PLAYER_TICKET && delayTimeout > 0) { -+ toAdd = TicketType.DELAY_UNLOAD; -+ timeout = delayTimeout; -+ } else { -+ toAdd = TicketType.UNKNOWN; -+ // always expect UNKNOWN to be > 1, but just in case -+ timeout = Math.max(1, toAdd.timeout); -+ } -+ final Ticket unknownTicket = new Ticket<>(toAdd, level, new ChunkPos(chunk), timeout); -+ if (ticketsAtChunk.add(unknownTicket)) { -+ this.addExpireCount(chunkX, chunkZ); -+ } else { -+ throw new IllegalStateException("Should have been able to add " + unknownTicket + " to " + ticketsAtChunk); -+ } -+ } -+ -+ final long removeDelay = ticket.removeDelay; -+ if (removeDelay != NO_TIMEOUT_MARKER) { -+ this.removeExpireCount(chunkX, chunkZ); -+ } -+ -+ return true; -+ } finally { -+ if (ticketLock != null) { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ } -+ -+ // atomic with respect to all add/remove/addandremove ticket calls for the given chunk -+ public void addAndRemoveTickets(final long chunk, final TicketType addType, final int addLevel, final T addIdentifier, -+ final TicketType removeType, final int removeLevel, final V removeIdentifier) { -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk)); -+ try { -+ this.addTicketAtLevel(addType, chunk, addLevel, addIdentifier, false); -+ this.removeTicketAtLevel(removeType, chunk, removeLevel, removeIdentifier, false); -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ -+ // atomic with respect to all add/remove/addandremove ticket calls for the given chunk -+ public boolean addIfRemovedTicket(final long chunk, final TicketType addType, final int addLevel, final T addIdentifier, -+ final TicketType removeType, final int removeLevel, final V removeIdentifier) { -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk)); -+ try { -+ if (this.removeTicketAtLevel(removeType, chunk, removeLevel, removeIdentifier, false)) { -+ this.addTicketAtLevel(addType, chunk, addLevel, addIdentifier, false); -+ return true; -+ } -+ return false; -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ -+ public void removeAllTicketsFor(final TicketType ticketType, final int ticketLevel, final T ticketIdentifier) { -+ if (ticketLevel > MAX_TICKET_LEVEL) { -+ return; -+ } -+ -+ final Long2ObjectOpenHashMap> sections = new Long2ObjectOpenHashMap(); -+ final int sectionShift = this.taskScheduler.getChunkSystemLockShift(); -+ for (final RegionFileIOThread.ChunkCoordinate coord : this.tickets.keySet()) { -+ sections.computeIfAbsent( -+ CoordinateUtils.getChunkKey( -+ CoordinateUtils.getChunkX(coord.key) >> sectionShift, -+ CoordinateUtils.getChunkZ(coord.key) >> sectionShift -+ ), -+ (final long keyInMap) -> { -+ return new ArrayList<>(); -+ } -+ ).add(coord); -+ } -+ -+ for (final Iterator>> iterator = sections.long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2ObjectMap.Entry> entry = iterator.next(); -+ final long sectionKey = entry.getLongKey(); -+ final List coordinates = entry.getValue(); -+ -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( -+ CoordinateUtils.getChunkX(sectionKey) << sectionShift, -+ CoordinateUtils.getChunkZ(sectionKey) << sectionShift -+ ); -+ try { -+ for (final RegionFileIOThread.ChunkCoordinate coord : coordinates) { -+ this.removeTicketAtLevel(ticketType, coord.key, ticketLevel, ticketIdentifier, false); -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ } -+ -+ public void tick() { -+ final int sectionShift = this.world.getRegionChunkShift(); -+ -+ final Predicate> expireNow = (final Ticket ticket) -> { -+ if (ticket.removeDelay == NO_TIMEOUT_MARKER) { -+ return false; -+ } -+ return --ticket.removeDelay <= 0L; -+ }; -+ -+ for (final Iterator iterator = this.sectionToChunkToExpireCount.keySet().iterator(); iterator.hasNext();) { -+ final RegionFileIOThread.ChunkCoordinate section = iterator.next(); -+ final long sectionKey = section.key; -+ -+ if (!this.sectionToChunkToExpireCount.containsKey(section)) { -+ // removed concurrently -+ continue; -+ } -+ -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( -+ CoordinateUtils.getChunkX(sectionKey) << sectionShift, -+ CoordinateUtils.getChunkZ(sectionKey) << sectionShift -+ ); -+ -+ try { -+ final Long2IntOpenHashMap chunkToExpireCount = this.sectionToChunkToExpireCount.get(section); -+ if (chunkToExpireCount == null) { -+ // lost to some race -+ continue; -+ } -+ -+ for (final Iterator iterator1 = chunkToExpireCount.long2IntEntrySet().fastIterator(); iterator1.hasNext();) { -+ final Long2IntMap.Entry entry = iterator1.next(); -+ -+ final long chunkKey = entry.getLongKey(); -+ final int expireCount = entry.getIntValue(); -+ -+ final RegionFileIOThread.ChunkCoordinate chunk = new RegionFileIOThread.ChunkCoordinate(chunkKey); -+ -+ final SortedArraySet> tickets = this.tickets.get(chunk); -+ final int levelBefore = getTicketLevelAt(tickets); -+ -+ final int sizeBefore = tickets.size(); -+ tickets.removeIf(expireNow); -+ final int sizeAfter = tickets.size(); -+ final int levelAfter = getTicketLevelAt(tickets); -+ -+ if (tickets.isEmpty()) { -+ this.tickets.remove(chunk); -+ } -+ if (levelBefore != levelAfter) { -+ this.updateTicketLevel(chunkKey, levelAfter); -+ } -+ -+ final int newExpireCount = expireCount - (sizeBefore - sizeAfter); -+ -+ if (newExpireCount == expireCount) { -+ continue; -+ } -+ -+ if (newExpireCount != 0) { -+ entry.setValue(newExpireCount); -+ } else { -+ iterator1.remove(); -+ } -+ } -+ -+ if (chunkToExpireCount.isEmpty()) { -+ this.sectionToChunkToExpireCount.remove(section); -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ } -+ -+ this.processTicketUpdates(); -+ } -+ -+ public NewChunkHolder getChunkHolder(final int chunkX, final int chunkZ) { -+ return this.chunkHolders.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ -+ public NewChunkHolder getChunkHolder(final long position) { -+ return this.chunkHolders.get(position); -+ } -+ -+ public void raisePriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); -+ if (chunkHolder != null) { -+ chunkHolder.raisePriority(priority); -+ } -+ } -+ -+ public void setPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); -+ if (chunkHolder != null) { -+ chunkHolder.setPriority(priority); -+ } -+ } -+ -+ public void lowerPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ final NewChunkHolder chunkHolder = this.getChunkHolder(x, z); -+ if (chunkHolder != null) { -+ chunkHolder.lowerPriority(priority); -+ } -+ } -+ -+ private NewChunkHolder createChunkHolder(final long position) { -+ final NewChunkHolder ret = new NewChunkHolder(this.world, CoordinateUtils.getChunkX(position), CoordinateUtils.getChunkZ(position), this.taskScheduler); -+ -+ ChunkSystem.onChunkHolderCreate(this.world, ret.vanillaChunkHolder); -+ ret.vanillaChunkHolder.onChunkAdd(); -+ -+ return ret; -+ } -+ -+ // because this function creates the chunk holder without a ticket, it is the caller's responsibility to ensure -+ // the chunk holder eventually unloads. this should only be used to avoid using processTicketUpdates to create chunkholders, -+ // as processTicketUpdates may call plugin logic; in every other case a ticket is appropriate -+ private NewChunkHolder getOrCreateChunkHolder(final int chunkX, final int chunkZ) { -+ return this.getOrCreateChunkHolder(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ -+ private NewChunkHolder getOrCreateChunkHolder(final long position) { -+ final int chunkX = CoordinateUtils.getChunkX(position); -+ final int chunkZ = CoordinateUtils.getChunkZ(position); -+ -+ if (!this.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ)) { -+ throw new IllegalStateException("Must hold ticket level update lock!"); -+ } -+ if (!this.taskScheduler.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ)) { -+ throw new IllegalStateException("Must hold scheduler lock!!"); -+ } -+ -+ // we could just acquire these locks, but... -+ // must own the locks because the caller needs to ensure that no unload can occur AFTER this function returns -+ -+ NewChunkHolder current = this.chunkHolders.get(position); -+ if (current != null) { -+ return current; -+ } -+ -+ current = this.createChunkHolder(position); -+ synchronized (this.chunkHolders) { -+ this.chunkHolders.put(position, current); -+ } -+ -+ return current; -+ } -+ -+ private final AtomicLong entityLoadCounter = new AtomicLong(); -+ -+ public ChunkEntitySlices getOrCreateEntityChunk(final int chunkX, final int chunkZ, final boolean transientChunk) { -+ TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot create entity chunk off-main"); -+ ChunkEntitySlices ret; -+ -+ NewChunkHolder current = this.getChunkHolder(chunkX, chunkZ); -+ if (current != null && (ret = current.getEntityChunk()) != null && (transientChunk || !ret.isTransient())) { -+ return ret; -+ } -+ -+ final AtomicBoolean isCompleted = new AtomicBoolean(); -+ final Thread waiter = Thread.currentThread(); -+ final Long entityLoadId = Long.valueOf(this.entityLoadCounter.getAndIncrement()); -+ NewChunkHolder.GenericDataLoadTaskCallback loadTask = null; -+ final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(chunkX, chunkZ); -+ try { -+ this.addTicketAtLevel(TicketType.ENTITY_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, entityLoadId); -+ final ReentrantAreaLock.Node schedulingLock = this.taskScheduler.schedulingLockArea.lock(chunkX, chunkZ); -+ try { -+ current = this.getOrCreateChunkHolder(chunkX, chunkZ); -+ if ((ret = current.getEntityChunk()) != null && (transientChunk || !ret.isTransient())) { -+ this.removeTicketAtLevel(TicketType.ENTITY_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, entityLoadId); -+ return ret; -+ } -+ -+ if (current.isEntityChunkNBTLoaded()) { -+ isCompleted.setPlain(true); -+ } else { -+ loadTask = current.getOrLoadEntityData((final GenericDataLoadTask.TaskResult result) -> { -+ if (!transientChunk) { -+ isCompleted.set(true); -+ LockSupport.unpark(waiter); -+ } -+ }); -+ final ChunkLoadTask.EntityDataLoadTask entityLoad = current.getEntityDataLoadTask(); -+ -+ if (entityLoad != null && !transientChunk) { -+ entityLoad.raisePriority(PrioritisedExecutor.Priority.BLOCKING); -+ } -+ } -+ } finally { -+ this.taskScheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ -+ if (loadTask != null) { -+ loadTask.schedule(); -+ } -+ -+ if (!transientChunk) { -+ // Note: no need to busy wait on the chunk queue, entity load will complete off-main -+ boolean interrupted = false; -+ while (!isCompleted.get()) { -+ interrupted |= Thread.interrupted(); -+ LockSupport.park(); -+ } -+ -+ if (interrupted) { -+ Thread.currentThread().interrupt(); -+ } -+ } -+ -+ // now that the entity data is loaded, we can load it into the world -+ -+ ret = current.loadInEntityChunk(transientChunk); -+ -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ this.addAndRemoveTickets(chunkKey, -+ TicketType.UNKNOWN, MAX_TICKET_LEVEL, new ChunkPos(chunkX, chunkZ), -+ TicketType.ENTITY_LOAD, MAX_TICKET_LEVEL, entityLoadId -+ ); -+ -+ return ret; -+ } -+ -+ public PoiChunk getPoiChunkIfLoaded(final int chunkX, final int chunkZ, final boolean checkLoadInCallback) { -+ final NewChunkHolder holder = this.getChunkHolder(chunkX, chunkZ); -+ if (holder != null) { -+ final PoiChunk ret = holder.getPoiChunk(); -+ return ret == null || (checkLoadInCallback && !ret.isLoaded()) ? null : ret; -+ } -+ return null; -+ } -+ -+ private final AtomicLong poiLoadCounter = new AtomicLong(); -+ -+ public PoiChunk loadPoiChunk(final int chunkX, final int chunkZ) { -+ TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot create poi chunk off-main"); -+ PoiChunk ret; -+ -+ NewChunkHolder current = this.getChunkHolder(chunkX, chunkZ); -+ if (current != null && (ret = current.getPoiChunk()) != null) { -+ if (!ret.isLoaded()) { -+ ret.load(); -+ } -+ return ret; -+ } -+ -+ final AtomicReference completed = new AtomicReference<>(); -+ final AtomicBoolean isCompleted = new AtomicBoolean(); -+ final Thread waiter = Thread.currentThread(); -+ final Long poiLoadId = Long.valueOf(this.poiLoadCounter.getAndIncrement()); -+ NewChunkHolder.GenericDataLoadTaskCallback loadTask = null; -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention -+ try { -+ // Folia - use area based lock to reduce contention -+ this.addTicketAtLevel(TicketType.POI_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, poiLoadId); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.taskScheduler.schedulingLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention -+ try { -+ current = this.getOrCreateChunkHolder(chunkX, chunkZ); -+ if (current.isPoiChunkLoaded()) { -+ this.removeTicketAtLevel(TicketType.POI_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, poiLoadId); -+ return current.getPoiChunk(); -+ } -+ -+ loadTask = current.getOrLoadPoiData((final GenericDataLoadTask.TaskResult result) -> { -+ completed.setPlain(result.left()); -+ isCompleted.set(true); -+ LockSupport.unpark(waiter); -+ }); -+ final ChunkLoadTask.PoiDataLoadTask poiLoad = current.getPoiDataLoadTask(); -+ -+ if (poiLoad != null) { -+ poiLoad.raisePriority(PrioritisedExecutor.Priority.BLOCKING); -+ } -+ } finally { -+ this.taskScheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention -+ } -+ -+ if (loadTask != null) { -+ loadTask.schedule(); -+ } -+ -+ // Note: no need to busy wait on the chunk queue, poi load will complete off-main -+ -+ boolean interrupted = false; -+ while (!isCompleted.get()) { -+ interrupted |= Thread.interrupted(); -+ LockSupport.park(); -+ } -+ -+ if (interrupted) { -+ Thread.currentThread().interrupt(); -+ } -+ -+ ret = completed.getPlain(); -+ -+ ret.load(); -+ -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ this.addAndRemoveTickets(chunkKey, -+ TicketType.UNKNOWN, MAX_TICKET_LEVEL, new ChunkPos(chunkX, chunkZ), -+ TicketType.POI_LOAD, MAX_TICKET_LEVEL, poiLoadId -+ ); -+ -+ return ret; -+ } -+ -+ void addChangedStatuses(final List changedFullStatus) { -+ if (changedFullStatus.isEmpty()) { -+ return; -+ } -+ if (!TickThread.isTickThread()) { -+ this.taskScheduler.scheduleChunkTask(() -> { -+ final ArrayDeque pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate; -+ for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { -+ pendingFullLoadUpdate.add(changedFullStatus.get(i)); -+ } -+ -+ ChunkHolderManager.this.processPendingFullUpdate(); -+ }, PrioritisedExecutor.Priority.HIGHEST); -+ } else { -+ final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; -+ for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { -+ pendingFullLoadUpdate.add(changedFullStatus.get(i)); -+ } -+ } -+ } -+ -+ private void removeChunkHolder(final NewChunkHolder holder) { -+ holder.killed = true; -+ holder.vanillaChunkHolder.onChunkRemove(); -+ this.autoSaveQueue.remove(holder); -+ ChunkSystem.onChunkHolderDelete(this.world, holder.vanillaChunkHolder); -+ synchronized (this.chunkHolders) { -+ this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ)); -+ } -+ } -+ -+ // note: never call while inside the chunk system, this will absolutely break everything -+ public void processUnloads() { -+ TickThread.ensureTickThread("Cannot unload chunks off-main"); -+ -+ if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { -+ throw new IllegalStateException("Cannot unload chunks recursively"); -+ } -+ final int sectionShift = this.unloadQueue.coordinateShift; // sectionShift <= lock shift -+ final List unloadSectionsForRegion = this.unloadQueue.retrieveForAllRegions(); -+ int unloadCountTentative = 0; -+ for (final ChunkQueue.SectionToUnload sectionRef : unloadSectionsForRegion) { -+ final ChunkQueue.UnloadSection section -+ = this.unloadQueue.getSectionUnsynchronized(sectionRef.sectionX(), sectionRef.sectionZ()); -+ -+ if (section == null) { -+ // removed concurrently -+ continue; -+ } -+ -+ // technically reading the size field is unsafe, and it may be incorrect. -+ // We assume that the error here cumulatively goes away over many ticks. If it did not, then it is possible -+ // for chunks to never unload or not unload fast enough. -+ unloadCountTentative += section.chunks.size(); -+ } -+ -+ if (unloadCountTentative <= 0) { -+ // no work to do -+ return; -+ } -+ -+ // Note: The behaviour that we process ticket updates while holding the lock has been dropped here, as it is racey behavior. -+ // But, we do need to process updates here so that any add ticket that is synchronised before this call does not go missed. -+ this.processTicketUpdates(); -+ -+ final int toUnloadCount = Math.max(50, (int)(unloadCountTentative * 0.05)); -+ int processedCount = 0; -+ -+ for (final ChunkQueue.SectionToUnload sectionRef : unloadSectionsForRegion) { -+ final List stage1 = new ArrayList<>(); -+ final List stage2 = new ArrayList<>(); -+ -+ final int sectionLowerX = sectionRef.sectionX() << sectionShift; -+ final int sectionLowerZ = sectionRef.sectionZ() << sectionShift; -+ -+ // stage 1: set up for stage 2 while holding critical locks -+ ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(sectionLowerX, sectionLowerZ); -+ try { -+ final ReentrantAreaLock.Node scheduleLock = this.taskScheduler.schedulingLockArea.lock(sectionLowerX, sectionLowerZ); -+ try { -+ final ChunkQueue.UnloadSection section -+ = this.unloadQueue.getSectionUnsynchronized(sectionRef.sectionX(), sectionRef.sectionZ()); -+ -+ if (section == null) { -+ // removed concurrently -+ continue; -+ } -+ -+ // collect the holders to run stage 1 on -+ final int sectionCount = section.chunks.size(); -+ -+ if ((sectionCount + processedCount) <= toUnloadCount) { -+ // we can just drain the entire section -+ -+ for (final LongIterator iterator = section.chunks.iterator(); iterator.hasNext();) { -+ final NewChunkHolder holder = this.chunkHolders.get(iterator.nextLong()); -+ if (holder == null) { -+ throw new IllegalStateException(); -+ } -+ stage1.add(holder); -+ } -+ -+ // remove section -+ this.unloadQueue.removeSection(sectionRef.sectionX(), sectionRef.sectionZ()); -+ } else { -+ // processedCount + len = toUnloadCount -+ // we cannot drain the entire section -+ for (int i = 0, len = toUnloadCount - processedCount; i < len; ++i) { -+ final NewChunkHolder holder = this.chunkHolders.get(section.chunks.removeFirstLong()); -+ if (holder == null) { -+ throw new IllegalStateException(); -+ } -+ stage1.add(holder); -+ } -+ } -+ -+ // run stage 1 -+ for (int i = 0, len = stage1.size(); i < len; ++i) { -+ final NewChunkHolder chunkHolder = stage1.get(i); -+ if (chunkHolder.isSafeToUnload() != null) { -+ LOGGER.error("Chunkholder " + chunkHolder + " is not safe to unload but is inside the unload queue?"); -+ continue; -+ } -+ final NewChunkHolder.UnloadState state = chunkHolder.unloadStage1(); -+ if (state == null) { -+ // can unload immediately -+ this.removeChunkHolder(chunkHolder); -+ continue; -+ } -+ stage2.add(state); -+ } -+ } finally { -+ this.taskScheduler.schedulingLockArea.unlock(scheduleLock); -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ -+ // stage 2: invoke expensive unload logic, designed to run without locks thanks to stage 1 -+ final List stage3 = new ArrayList<>(stage2.size()); -+ -+ final Boolean before = this.blockTicketUpdates(); -+ try { -+ for (int i = 0, len = stage2.size(); i < len; ++i) { -+ final NewChunkHolder.UnloadState state = stage2.get(i); -+ final NewChunkHolder holder = state.holder(); -+ -+ holder.unloadStage2(state); -+ stage3.add(holder); -+ } -+ } finally { -+ this.unblockTicketUpdates(before); -+ } -+ -+ // stage 3: actually attempt to remove the chunk holders -+ ticketLock = this.ticketLockArea.lock(sectionLowerX, sectionLowerZ); -+ try { -+ final ReentrantAreaLock.Node scheduleLock = this.taskScheduler.schedulingLockArea.lock(sectionLowerX, sectionLowerZ); -+ try { -+ for (int i = 0, len = stage3.size(); i < len; ++i) { -+ final NewChunkHolder holder = stage3.get(i); -+ -+ if (holder.unloadStage3()) { -+ this.removeChunkHolder(holder); -+ } else { -+ // add cooldown so the next unload check is not immediately next tick -+ this.addTicketAtLevel(TicketType.UNLOAD_COOLDOWN, CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ), MAX_TICKET_LEVEL, Unit.INSTANCE, false); -+ } -+ } -+ } finally { -+ this.taskScheduler.schedulingLockArea.unlock(scheduleLock); -+ } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ -+ processedCount += stage1.size(); -+ -+ if (processedCount >= toUnloadCount) { -+ break; -+ } -+ } -+ } -+ -+ public enum TicketOperationType { -+ ADD, REMOVE, ADD_IF_REMOVED, ADD_AND_REMOVE -+ } -+ -+ public static record TicketOperation ( -+ TicketOperationType op, long chunkCoord, -+ TicketType ticketType, int ticketLevel, T identifier, -+ TicketType ticketType2, int ticketLevel2, V identifier2 -+ ) { -+ -+ private TicketOperation(TicketOperationType op, long chunkCoord, -+ TicketType ticketType, int ticketLevel, T identifier) { -+ this(op, chunkCoord, ticketType, ticketLevel, identifier, null, 0, null); -+ } -+ -+ public static TicketOperation addOp(final ChunkPos chunk, final TicketType type, final int ticketLevel, final T identifier) { -+ return addOp(CoordinateUtils.getChunkKey(chunk), type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation addOp(final int chunkX, final int chunkZ, final TicketType type, final int ticketLevel, final T identifier) { -+ return addOp(CoordinateUtils.getChunkKey(chunkX, chunkZ), type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation addOp(final long chunk, final TicketType type, final int ticketLevel, final T identifier) { -+ return new TicketOperation<>(TicketOperationType.ADD, chunk, type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation removeOp(final ChunkPos chunk, final TicketType type, final int ticketLevel, final T identifier) { -+ return removeOp(CoordinateUtils.getChunkKey(chunk), type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation removeOp(final int chunkX, final int chunkZ, final TicketType type, final int ticketLevel, final T identifier) { -+ return removeOp(CoordinateUtils.getChunkKey(chunkX, chunkZ), type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation removeOp(final long chunk, final TicketType type, final int ticketLevel, final T identifier) { -+ return new TicketOperation<>(TicketOperationType.REMOVE, chunk, type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation addIfRemovedOp(final long chunk, -+ final TicketType addType, final int addLevel, final T addIdentifier, -+ final TicketType removeType, final int removeLevel, final V removeIdentifier) { -+ return new TicketOperation<>( -+ TicketOperationType.ADD_IF_REMOVED, chunk, addType, addLevel, addIdentifier, -+ removeType, removeLevel, removeIdentifier -+ ); -+ } -+ -+ public static TicketOperation addAndRemove(final long chunk, -+ final TicketType addType, final int addLevel, final T addIdentifier, -+ final TicketType removeType, final int removeLevel, final V removeIdentifier) { -+ return new TicketOperation<>( -+ TicketOperationType.ADD_AND_REMOVE, chunk, addType, addLevel, addIdentifier, -+ removeType, removeLevel, removeIdentifier -+ ); -+ } -+ } -+ -+ private boolean processTicketOp(TicketOperation operation) { -+ boolean ret = false; -+ switch (operation.op) { -+ case ADD: { -+ ret |= this.addTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier); -+ break; -+ } -+ case REMOVE: { -+ ret |= this.removeTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier); -+ break; -+ } -+ case ADD_IF_REMOVED: { -+ ret |= this.addIfRemovedTicket( -+ operation.chunkCoord, -+ operation.ticketType, operation.ticketLevel, operation.identifier, -+ operation.ticketType2, operation.ticketLevel2, operation.identifier2 -+ ); -+ break; -+ } -+ case ADD_AND_REMOVE: { -+ ret = true; -+ this.addAndRemoveTickets( -+ operation.chunkCoord, -+ operation.ticketType, operation.ticketLevel, operation.identifier, -+ operation.ticketType2, operation.ticketLevel2, operation.identifier2 -+ ); -+ break; -+ } -+ } -+ -+ return ret; -+ } -+ -+ public void performTicketUpdates(final Collection> operations) { -+ for (final TicketOperation operation : operations) { -+ this.processTicketOp(operation); -+ } -+ } -+ -+ private final ThreadLocal BLOCK_TICKET_UPDATES = ThreadLocal.withInitial(() -> { -+ return Boolean.FALSE; -+ }); -+ -+ public Boolean blockTicketUpdates() { -+ final Boolean ret = BLOCK_TICKET_UPDATES.get(); -+ BLOCK_TICKET_UPDATES.set(Boolean.TRUE); -+ return ret; -+ } -+ -+ public void unblockTicketUpdates(final Boolean before) { -+ BLOCK_TICKET_UPDATES.set(before); -+ } -+ -+ public boolean processTicketUpdates() { -+ return this.processTicketUpdates(true, true, null); -+ } -+ -+ private static final ThreadLocal> CURRENT_TICKET_UPDATE_SCHEDULING = new ThreadLocal<>(); -+ -+ static List getCurrentTicketUpdateScheduling() { -+ return CURRENT_TICKET_UPDATE_SCHEDULING.get(); -+ } -+ -+ private boolean processTicketUpdates(final boolean checkLocks, final boolean processFullUpdates, List scheduledTasks) { -+ TickThread.ensureTickThread("Cannot process ticket levels off-main"); -+ if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { -+ throw new IllegalStateException("Cannot update ticket level while unloading chunks or updating entity manager"); -+ } -+ -+ List changedFullStatus = null; -+ -+ final boolean isTickThread = TickThread.isTickThread(); -+ -+ boolean ret = false; -+ final boolean canProcessFullUpdates = processFullUpdates & isTickThread; -+ final boolean canProcessScheduling = scheduledTasks == null; -+ -+ if (this.ticketLevelPropagator.hasPendingUpdates()) { -+ if (scheduledTasks == null) { -+ scheduledTasks = new ArrayList<>(); -+ } -+ changedFullStatus = new ArrayList<>(); -+ -+ ret |= this.ticketLevelPropagator.performUpdates( -+ this.ticketLockArea, this.taskScheduler.schedulingLockArea, -+ scheduledTasks, changedFullStatus -+ ); -+ } -+ -+ if (changedFullStatus != null) { -+ this.addChangedStatuses(changedFullStatus); -+ } -+ -+ if (canProcessScheduling && scheduledTasks != null) { -+ for (int i = 0, len = scheduledTasks.size(); i < len; ++i) { -+ scheduledTasks.get(i).schedule(); -+ } -+ } -+ -+ if (canProcessFullUpdates) { -+ ret |= this.processPendingFullUpdate(); -+ } -+ -+ return ret; -+ } -+ -+ // only call on tick thread -+ protected final boolean processPendingFullUpdate() { -+ final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; -+ -+ boolean ret = false; -+ -+ List changedFullStatus = new ArrayList<>(); -+ -+ NewChunkHolder holder; -+ while ((holder = pendingFullLoadUpdate.poll()) != null) { -+ ret |= holder.handleFullStatusChange(changedFullStatus); -+ -+ if (!changedFullStatus.isEmpty()) { -+ for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { -+ pendingFullLoadUpdate.add(changedFullStatus.get(i)); -+ } -+ changedFullStatus.clear(); -+ } -+ } -+ -+ return ret; -+ } -+ -+ public JsonObject getDebugJsonForWatchdog() { -+ return this.getDebugJsonNoLock(); -+ } -+ -+ private JsonObject getDebugJsonNoLock() { -+ final JsonObject ret = new JsonObject(); -+ ret.addProperty("current_tick", Long.valueOf(this.currentTick)); -+ -+ final JsonArray unloadQueue = new JsonArray(); -+ ret.add("unload_queue", unloadQueue); -+ ret.addProperty("lock_shift", Integer.valueOf(this.taskScheduler.getChunkSystemLockShift())); -+ ret.addProperty("ticket_shift", Integer.valueOf(ThreadedTicketLevelPropagator.SECTION_SHIFT)); -+ ret.addProperty("region_shift", Integer.valueOf(this.world.getRegionChunkShift())); -+ for (final ChunkQueue.SectionToUnload section : this.unloadQueue.retrieveForAllRegions()) { -+ final JsonObject sectionJson = new JsonObject(); -+ unloadQueue.add(sectionJson); -+ sectionJson.addProperty("sectionX", section.sectionX()); -+ sectionJson.addProperty("sectionZ", section.sectionX()); -+ sectionJson.addProperty("order", section.order()); -+ -+ final JsonArray coordinates = new JsonArray(); -+ sectionJson.add("coordinates", coordinates); -+ -+ final ChunkQueue.UnloadSection actualSection = this.unloadQueue.getSectionUnsynchronized(section.sectionX(), section.sectionZ()); -+ for (final LongIterator iterator = actualSection.chunks.iterator(); iterator.hasNext();) { -+ final long coordinate = iterator.nextLong(); -+ -+ final JsonObject coordinateJson = new JsonObject(); -+ coordinates.add(coordinateJson); -+ -+ coordinateJson.addProperty("chunkX", Integer.valueOf(CoordinateUtils.getChunkX(coordinate))); -+ coordinateJson.addProperty("chunkZ", Integer.valueOf(CoordinateUtils.getChunkZ(coordinate))); -+ } -+ } -+ -+ final JsonArray holders = new JsonArray(); -+ ret.add("chunkholders", holders); -+ -+ for (final NewChunkHolder holder : this.getChunkHolders()) { -+ holders.add(holder.getDebugJson()); -+ } -+ -+ // TODO -+ /* -+ final JsonArray removeTickToChunkExpireTicketCount = new JsonArray(); -+ ret.add("remove_tick_to_chunk_expire_ticket_count", removeTickToChunkExpireTicketCount); -+ -+ for (final Long2ObjectMap.Entry tickEntry : this.removeTickToChunkExpireTicketCount.long2ObjectEntrySet()) { -+ final long tick = tickEntry.getLongKey(); -+ final Long2IntOpenHashMap coordinateToCount = tickEntry.getValue(); -+ -+ final JsonObject tickJson = new JsonObject(); -+ removeTickToChunkExpireTicketCount.add(tickJson); -+ -+ tickJson.addProperty("tick", Long.valueOf(tick)); -+ -+ final JsonArray tickEntries = new JsonArray(); -+ tickJson.add("entries", tickEntries); -+ -+ for (final Long2IntMap.Entry entry : coordinateToCount.long2IntEntrySet()) { -+ final long coordinate = entry.getLongKey(); -+ final int count = entry.getIntValue(); -+ -+ final JsonObject entryJson = new JsonObject(); -+ tickEntries.add(entryJson); -+ -+ entryJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); -+ entryJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); -+ entryJson.addProperty("count", Integer.valueOf(count)); -+ } -+ } -+ -+ final JsonArray allTicketsJson = new JsonArray(); -+ ret.add("tickets", allTicketsJson); -+ -+ for (final Long2ObjectMap.Entry>> coordinateTickets : this.tickets.long2ObjectEntrySet()) { -+ final long coordinate = coordinateTickets.getLongKey(); -+ final SortedArraySet> tickets = coordinateTickets.getValue(); -+ -+ final JsonObject coordinateJson = new JsonObject(); -+ allTicketsJson.add(coordinateJson); -+ -+ coordinateJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); -+ coordinateJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); -+ -+ final JsonArray ticketsSerialized = new JsonArray(); -+ coordinateJson.add("tickets", ticketsSerialized); -+ -+ for (final Ticket ticket : tickets) { -+ final JsonObject ticketSerialized = new JsonObject(); -+ ticketsSerialized.add(ticketSerialized); -+ -+ ticketSerialized.addProperty("type", ticket.getType().toString()); -+ ticketSerialized.addProperty("level", Integer.valueOf(ticket.getTicketLevel())); -+ ticketSerialized.addProperty("identifier", Objects.toString(ticket.key)); -+ ticketSerialized.addProperty("remove_tick", Long.valueOf(ticket.removalTick)); -+ } -+ } -+ */ -+ -+ return ret; -+ } -+ -+ public JsonObject getDebugJson() { -+ return this.getDebugJsonNoLock(); // Folia - use area based lock to reduce contention -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLightTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLightTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..53ddd7e9ac05e6a9eb809f329796e6d4f6bb2ab1 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLightTask.java -@@ -0,0 +1,181 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.starlight.common.light.StarLightEngine; -+import ca.spottedleaf.starlight.common.light.StarLightInterface; -+import io.papermc.paper.chunk.system.light.LightQueue; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+import java.util.function.BooleanSupplier; -+ -+public final class ChunkLightTask extends ChunkProgressionTask { -+ -+ private static final Logger LOGGER = LogManager.getLogger(); -+ -+ protected final ChunkAccess fromChunk; -+ -+ private final LightTaskPriorityHolder priorityHolder; -+ -+ public ChunkLightTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, -+ final ChunkAccess chunk, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ); -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.priorityHolder = new LightTaskPriorityHolder(priority, this); -+ this.fromChunk = chunk; -+ } -+ -+ @Override -+ public boolean isScheduled() { -+ return this.priorityHolder.isScheduled(); -+ } -+ -+ @Override -+ public ChunkStatus getTargetStatus() { -+ return ChunkStatus.LIGHT; -+ } -+ -+ @Override -+ public void schedule() { -+ this.priorityHolder.schedule(); -+ } -+ -+ @Override -+ public void cancel() { -+ this.priorityHolder.cancel(); -+ } -+ -+ @Override -+ public PrioritisedExecutor.Priority getPriority() { -+ return this.priorityHolder.getPriority(); -+ } -+ -+ @Override -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ this.priorityHolder.raisePriority(priority); -+ } -+ -+ @Override -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ this.priorityHolder.setPriority(priority); -+ } -+ -+ @Override -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ this.priorityHolder.raisePriority(priority); -+ } -+ -+ private static final class LightTaskPriorityHolder extends PriorityHolder { -+ -+ protected final ChunkLightTask task; -+ -+ protected LightTaskPriorityHolder(final PrioritisedExecutor.Priority priority, final ChunkLightTask task) { -+ super(priority); -+ this.task = task; -+ } -+ -+ @Override -+ protected void cancelScheduled() { -+ final ChunkLightTask task = this.task; -+ task.complete(null, null); -+ } -+ -+ @Override -+ protected PrioritisedExecutor.Priority getScheduledPriority() { -+ final ChunkLightTask task = this.task; -+ return task.world.getChunkSource().getLightEngine().theLightEngine.lightQueue.getPriority(task.chunkX, task.chunkZ); -+ } -+ -+ @Override -+ protected void scheduleTask(final PrioritisedExecutor.Priority priority) { -+ final ChunkLightTask task = this.task; -+ final StarLightInterface starLightInterface = task.world.getChunkSource().getLightEngine().theLightEngine; -+ final LightQueue lightQueue = starLightInterface.lightQueue; -+ lightQueue.queueChunkLightTask(new ChunkPos(task.chunkX, task.chunkZ), new LightTask(starLightInterface, task), priority); -+ lightQueue.setPriority(task.chunkX, task.chunkZ, priority); -+ } -+ -+ @Override -+ protected void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ final ChunkLightTask task = this.task; -+ final StarLightInterface starLightInterface = task.world.getChunkSource().getLightEngine().theLightEngine; -+ final LightQueue lightQueue = starLightInterface.lightQueue; -+ lightQueue.lowerPriority(task.chunkX, task.chunkZ, priority); -+ } -+ -+ @Override -+ protected void setPriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ final ChunkLightTask task = this.task; -+ final StarLightInterface starLightInterface = task.world.getChunkSource().getLightEngine().theLightEngine; -+ final LightQueue lightQueue = starLightInterface.lightQueue; -+ lightQueue.setPriority(task.chunkX, task.chunkZ, priority); -+ } -+ -+ @Override -+ protected void raisePriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ final ChunkLightTask task = this.task; -+ final StarLightInterface starLightInterface = task.world.getChunkSource().getLightEngine().theLightEngine; -+ final LightQueue lightQueue = starLightInterface.lightQueue; -+ lightQueue.raisePriority(task.chunkX, task.chunkZ, priority); -+ } -+ } -+ -+ private static final class LightTask implements BooleanSupplier { -+ -+ protected final StarLightInterface lightEngine; -+ protected final ChunkLightTask task; -+ -+ public LightTask(final StarLightInterface lightEngine, final ChunkLightTask task) { -+ this.lightEngine = lightEngine; -+ this.task = task; -+ } -+ -+ @Override -+ public boolean getAsBoolean() { -+ final ChunkLightTask task = this.task; -+ // executed on light thread -+ if (!task.priorityHolder.markExecuting()) { -+ // cancelled -+ return false; -+ } -+ -+ try { -+ final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(task.fromChunk); -+ -+ if (task.fromChunk.isLightCorrect() && task.fromChunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ this.lightEngine.forceLoadInChunk(task.fromChunk, emptySections); -+ this.lightEngine.checkChunkEdges(task.chunkX, task.chunkZ); -+ } else { -+ task.fromChunk.setLightCorrect(false); -+ this.lightEngine.lightChunk(task.fromChunk, emptySections); -+ task.fromChunk.setLightCorrect(true); -+ } -+ // we need to advance status -+ if (task.fromChunk instanceof ProtoChunk chunk && chunk.getStatus() == ChunkStatus.LIGHT.getParent()) { -+ chunk.setStatus(ChunkStatus.LIGHT); -+ } -+ } catch (final Throwable thr) { -+ if (!(thr instanceof ThreadDeath)) { -+ LOGGER.fatal("Failed to light chunk " + task.fromChunk.getPos().toString() + " in world '" + this.lightEngine.getWorld().getWorld().getName() + "'", thr); -+ } -+ -+ task.complete(null, thr); -+ -+ if (thr instanceof ThreadDeath) { -+ throw (ThreadDeath)thr; -+ } -+ -+ return true; -+ } -+ -+ task.complete(task.fromChunk, null); -+ return true; -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f0b03a05387e38ca6933b1ea6fe59b537d5535b9 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java -@@ -0,0 +1,484 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.dataconverter.minecraft.MCDataConverter; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.io.RegionFileIOThread; -+import io.papermc.paper.chunk.system.poi.PoiChunk; -+import net.minecraft.SharedConstants; -+import net.minecraft.core.registries.Registries; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ChunkMap; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import net.minecraft.world.level.chunk.UpgradeData; -+import net.minecraft.world.level.chunk.storage.ChunkSerializer; -+import net.minecraft.world.level.chunk.storage.EntityStorage; -+import net.minecraft.world.level.levelgen.blending.BlendingData; -+import org.slf4j.Logger; -+import java.lang.invoke.VarHandle; -+import java.util.Map; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.function.Consumer; -+ -+public final class ChunkLoadTask extends ChunkProgressionTask { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ private final NewChunkHolder chunkHolder; -+ private final ChunkDataLoadTask loadTask; -+ -+ private volatile boolean cancelled; -+ private NewChunkHolder.GenericDataLoadTaskCallback entityLoadTask; -+ private NewChunkHolder.GenericDataLoadTaskCallback poiLoadTask; -+ private GenericDataLoadTask.TaskResult loadResult; -+ private final AtomicInteger taskCountToComplete = new AtomicInteger(3); // one for poi, one for entity, and one for chunk data -+ -+ protected ChunkLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, -+ final NewChunkHolder chunkHolder, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ); -+ this.chunkHolder = chunkHolder; -+ this.loadTask = new ChunkDataLoadTask(scheduler, world, chunkX, chunkZ, priority); -+ this.loadTask.addCallback((final GenericDataLoadTask.TaskResult result) -> { -+ ChunkLoadTask.this.loadResult = result; // must be before getAndDecrement -+ ChunkLoadTask.this.tryCompleteLoad(); -+ }); -+ } -+ -+ private void tryCompleteLoad() { -+ if (this.taskCountToComplete.decrementAndGet() == 0) { -+ final GenericDataLoadTask.TaskResult result = this.cancelled ? null : this.loadResult; // only after the getAndDecrement -+ ChunkLoadTask.this.complete(result == null ? null : result.left(), result == null ? null : result.right()); -+ } -+ } -+ -+ @Override -+ public ChunkStatus getTargetStatus() { -+ return ChunkStatus.EMPTY; -+ } -+ -+ private boolean scheduled; -+ -+ @Override -+ public boolean isScheduled() { -+ return this.scheduled; -+ } -+ -+ @Override -+ public void schedule() { -+ final NewChunkHolder.GenericDataLoadTaskCallback entityLoadTask; -+ final NewChunkHolder.GenericDataLoadTaskCallback poiLoadTask; -+ -+ final Consumer> scheduleLoadTask = (final GenericDataLoadTask.TaskResult result) -> { -+ ChunkLoadTask.this.tryCompleteLoad(); -+ }; -+ -+ // NOTE: it is IMPOSSIBLE for getOrLoadEntityData/getOrLoadPoiData to complete synchronously, because -+ // they must schedule a task to off main or to on main to complete -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ if (this.scheduled) { -+ throw new IllegalStateException("schedule() called twice"); -+ } -+ this.scheduled = true; -+ if (this.cancelled) { -+ return; -+ } -+ if (!this.chunkHolder.isEntityChunkNBTLoaded()) { -+ entityLoadTask = this.chunkHolder.getOrLoadEntityData((Consumer)scheduleLoadTask); -+ } else { -+ entityLoadTask = null; -+ this.taskCountToComplete.getAndDecrement(); // we know the chunk load is not done here, as it is not scheduled -+ } -+ -+ if (!this.chunkHolder.isPoiChunkLoaded()) { -+ poiLoadTask = this.chunkHolder.getOrLoadPoiData((Consumer)scheduleLoadTask); -+ } else { -+ poiLoadTask = null; -+ this.taskCountToComplete.getAndDecrement(); // we know the chunk load is not done here, as it is not scheduled -+ } -+ -+ this.entityLoadTask = entityLoadTask; -+ this.poiLoadTask = poiLoadTask; -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ -+ if (entityLoadTask != null) { -+ entityLoadTask.schedule(); -+ } -+ -+ if (poiLoadTask != null) { -+ poiLoadTask.schedule(); -+ } -+ -+ this.loadTask.schedule(false); -+ } -+ -+ @Override -+ public void cancel() { -+ // must be before load task access, so we can synchronise with the writes to the fields -+ final boolean scheduled; -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ // must read field here, as it may be written later conucrrently - -+ // we need to know if we scheduled _before_ cancellation -+ scheduled = this.scheduled; -+ this.cancelled = true; -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ -+ /* -+ Note: The entityLoadTask/poiLoadTask do not complete when cancelled, -+ so we need to manually try to complete in those cases -+ It is also important to note that we set the cancelled field first, just in case -+ the chunk load task attempts to complete with a non-null value -+ */ -+ -+ if (scheduled) { -+ // since we scheduled, we need to cancel the tasks -+ if (this.entityLoadTask != null) { -+ if (this.entityLoadTask.cancel()) { -+ this.tryCompleteLoad(); -+ } -+ } -+ if (this.poiLoadTask != null) { -+ if (this.poiLoadTask.cancel()) { -+ this.tryCompleteLoad(); -+ } -+ } -+ } else { -+ // since nothing was scheduled, we need to decrement the task count here ourselves -+ -+ // for entity load task -+ this.tryCompleteLoad(); -+ -+ // for poi load task -+ this.tryCompleteLoad(); -+ } -+ this.loadTask.cancel(); -+ } -+ -+ @Override -+ public PrioritisedExecutor.Priority getPriority() { -+ return this.loadTask.getPriority(); -+ } -+ -+ @Override -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); -+ if (entityLoad != null) { -+ entityLoad.lowerPriority(priority); -+ } -+ -+ final PoiDataLoadTask poiLoad = this.chunkHolder.getPoiDataLoadTask(); -+ -+ if (poiLoad != null) { -+ poiLoad.lowerPriority(priority); -+ } -+ -+ this.loadTask.lowerPriority(priority); -+ } -+ -+ @Override -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); -+ if (entityLoad != null) { -+ entityLoad.setPriority(priority); -+ } -+ -+ final PoiDataLoadTask poiLoad = this.chunkHolder.getPoiDataLoadTask(); -+ -+ if (poiLoad != null) { -+ poiLoad.setPriority(priority); -+ } -+ -+ this.loadTask.setPriority(priority); -+ } -+ -+ @Override -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ final EntityDataLoadTask entityLoad = this.chunkHolder.getEntityDataLoadTask(); -+ if (entityLoad != null) { -+ entityLoad.raisePriority(priority); -+ } -+ -+ final PoiDataLoadTask poiLoad = this.chunkHolder.getPoiDataLoadTask(); -+ -+ if (poiLoad != null) { -+ poiLoad.raisePriority(priority); -+ } -+ -+ this.loadTask.raisePriority(priority); -+ } -+ -+ protected static abstract class CallbackDataLoadTask extends GenericDataLoadTask { -+ -+ private TaskResult result; -+ private final MultiThreadedQueue>> waiters = new MultiThreadedQueue<>(); -+ -+ protected volatile boolean completed; -+ protected static final VarHandle COMPLETED_HANDLE = ConcurrentUtil.getVarHandle(CallbackDataLoadTask.class, "completed", boolean.class); -+ -+ protected CallbackDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final RegionFileIOThread.RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, type, priority); -+ } -+ -+ public void addCallback(final Consumer> consumer) { -+ if (!this.waiters.add(consumer)) { -+ try { -+ consumer.accept(this.result); -+ } catch (final Throwable throwable) { -+ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Consumer", ChunkTaskScheduler.stringIfNull(consumer), -+ "Completed throwable", ChunkTaskScheduler.stringIfNull(this.result.right()) -+ ), throwable); -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ } -+ } -+ } -+ -+ @Override -+ protected void onComplete(final TaskResult result) { -+ if ((boolean)COMPLETED_HANDLE.getAndSet((CallbackDataLoadTask)this, (boolean)true)) { -+ throw new IllegalStateException("Already completed"); -+ } -+ this.result = result; -+ Consumer> consumer; -+ while ((consumer = this.waiters.pollOrBlockAdds()) != null) { -+ try { -+ consumer.accept(result); -+ } catch (final Throwable throwable) { -+ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Consumer", ChunkTaskScheduler.stringIfNull(consumer), -+ "Completed throwable", ChunkTaskScheduler.stringIfNull(result.right()) -+ ), throwable); -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ return; -+ } -+ } -+ } -+ } -+ -+ public static final class ChunkDataLoadTask extends CallbackDataLoadTask { -+ protected ChunkDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.CHUNK_DATA, priority); -+ } -+ -+ @Override -+ protected boolean hasOffMain() { -+ return true; -+ } -+ -+ @Override -+ protected boolean hasOnMain() { -+ return false; -+ } -+ -+ @Override -+ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ return this.scheduler.loadExecutor.createTask(run, priority); -+ } -+ -+ @Override -+ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ protected TaskResult completeOnMainOffMain(final ChunkAccess data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ private ProtoChunk getEmptyChunk() { -+ return new ProtoChunk( -+ new ChunkPos(this.chunkX, this.chunkZ), UpgradeData.EMPTY, this.world, -+ this.world.registryAccess().registryOrThrow(Registries.BIOME), (BlendingData)null -+ ); -+ } -+ -+ @Override -+ protected TaskResult runOffMain(final CompoundTag data, final Throwable throwable) { -+ if (throwable != null) { -+ LOGGER.error("Failed to load chunk data for task: " + this.toString() + ", chunk data will be lost", throwable); -+ return new TaskResult<>(this.getEmptyChunk(), null); -+ } -+ -+ if (data == null) { -+ return new TaskResult<>(this.getEmptyChunk(), null); -+ } -+ -+ // need to convert data, and then deserialize it -+ -+ try { -+ final ChunkPos chunkPos = new ChunkPos(this.chunkX, this.chunkZ); -+ final ChunkMap chunkMap = this.world.getChunkSource().chunkMap; -+ // run converters -+ // note: upgradeChunkTag copies the data already -+ final CompoundTag converted = chunkMap.upgradeChunkTag( -+ this.world.getTypeKey(), chunkMap.overworldDataStorage, data, chunkMap.generator.getTypeNameForDataFixer(), -+ chunkPos, this.world -+ ); -+ // deserialize -+ final ChunkSerializer.InProgressChunkHolder chunkHolder = ChunkSerializer.readInProgressChunkHolder( -+ this.world, chunkMap.getPoiManager(), chunkPos, converted -+ ); -+ -+ return new TaskResult<>(chunkHolder.protoChunk, null); -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr2) { -+ LOGGER.error("Failed to parse chunk data for task: " + this.toString() + ", chunk data will be lost", thr2); -+ return new TaskResult<>(this.getEmptyChunk(), null); -+ } -+ } -+ -+ @Override -+ protected TaskResult runOnMain(final ChunkAccess data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); -+ } -+ } -+ -+ public static final class PoiDataLoadTask extends CallbackDataLoadTask { -+ public PoiDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.POI_DATA, priority); -+ } -+ -+ @Override -+ protected boolean hasOffMain() { -+ return true; -+ } -+ -+ @Override -+ protected boolean hasOnMain() { -+ return false; -+ } -+ -+ @Override -+ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ return this.scheduler.loadExecutor.createTask(run, priority); -+ } -+ -+ @Override -+ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ protected TaskResult completeOnMainOffMain(final PoiChunk data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ protected TaskResult runOffMain(CompoundTag data, final Throwable throwable) { -+ if (throwable != null) { -+ LOGGER.error("Failed to load poi data for task: " + this.toString() + ", poi data will be lost", throwable); -+ return new TaskResult<>(PoiChunk.empty(this.world, this.chunkX, this.chunkZ), null); -+ } -+ -+ if (data == null || data.isEmpty()) { -+ // nothing to do -+ return new TaskResult<>(PoiChunk.empty(this.world, this.chunkX, this.chunkZ), null); -+ } -+ -+ try { -+ data = data.copy(); // coming from the I/O thread, so we need to copy -+ // run converters -+ final int dataVersion = !data.contains(SharedConstants.DATA_VERSION_TAG, net.minecraft.nbt.Tag.TAG_ANY_NUMERIC) ? 1945 : data.getInt(SharedConstants.DATA_VERSION_TAG); -+ final CompoundTag converted = MCDataConverter.convertTag( -+ MCTypeRegistry.POI_CHUNK, data, dataVersion, SharedConstants.getCurrentVersion().getDataVersion().getVersion() -+ ); -+ -+ // now we need to parse it -+ return new TaskResult<>(PoiChunk.parse(this.world, this.chunkX, this.chunkZ, converted), null); -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr2) { -+ LOGGER.error("Failed to run parse poi data for task: " + this.toString() + ", poi data will be lost", thr2); -+ return new TaskResult<>(PoiChunk.empty(this.world, this.chunkX, this.chunkZ), null); -+ } -+ } -+ -+ @Override -+ protected TaskResult runOnMain(final PoiChunk data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); -+ } -+ } -+ -+ public static final class EntityDataLoadTask extends CallbackDataLoadTask { -+ -+ public EntityDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.ENTITY_DATA, priority); -+ } -+ -+ @Override -+ protected boolean hasOffMain() { -+ return true; -+ } -+ -+ @Override -+ protected boolean hasOnMain() { -+ return false; -+ } -+ -+ @Override -+ protected PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ return this.scheduler.loadExecutor.createTask(run, priority); -+ } -+ -+ @Override -+ protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ protected TaskResult completeOnMainOffMain(final CompoundTag data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ protected TaskResult runOffMain(final CompoundTag data, final Throwable throwable) { -+ if (throwable != null) { -+ LOGGER.error("Failed to load entity data for task: " + this.toString() + ", entity data will be lost", throwable); -+ return new TaskResult<>(null, null); -+ } -+ -+ if (data == null || data.isEmpty()) { -+ // nothing to do -+ return new TaskResult<>(null, null); -+ } -+ -+ try { -+ // note: data comes from the I/O thread, so we need to copy it -+ return new TaskResult<>(EntityStorage.upgradeChunkTag(data.copy()), null); -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr2) { -+ LOGGER.error("Failed to run converters for entity data for task: " + this.toString() + ", entity data will be lost", thr2); -+ return new TaskResult<>(null, thr2); -+ } -+ } -+ -+ @Override -+ protected TaskResult runOnMain(final CompoundTag data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkProgressionTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkProgressionTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..322675a470eacbf0e5452f4009c643f2d0b4ce24 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkProgressionTask.java -@@ -0,0 +1,105 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import java.lang.invoke.VarHandle; -+import java.util.Map; -+import java.util.function.BiConsumer; -+ -+public abstract class ChunkProgressionTask { -+ -+ private final MultiThreadedQueue> waiters = new MultiThreadedQueue<>(); -+ private ChunkAccess completedChunk; -+ private Throwable completedThrowable; -+ -+ protected final ChunkTaskScheduler scheduler; -+ protected final ServerLevel world; -+ protected final int chunkX; -+ protected final int chunkZ; -+ -+ protected volatile boolean completed; -+ protected static final VarHandle COMPLETED_HANDLE = ConcurrentUtil.getVarHandle(ChunkProgressionTask.class, "completed", boolean.class); -+ -+ protected ChunkProgressionTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ) { -+ this.scheduler = scheduler; -+ this.world = world; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ } -+ -+ // Used only for debug json -+ public abstract boolean isScheduled(); -+ -+ // Note: It is the responsibility of the task to set the chunk's status once it has completed -+ public abstract ChunkStatus getTargetStatus(); -+ -+ /* Only executed once */ -+ /* Implementations must be prepared to handle cases where cancel() is called before schedule() */ -+ public abstract void schedule(); -+ -+ /* May be called multiple times */ -+ public abstract void cancel(); -+ -+ public abstract PrioritisedExecutor.Priority getPriority(); -+ -+ /* Schedule lock is always held for the priority update calls */ -+ -+ public abstract void lowerPriority(final PrioritisedExecutor.Priority priority); -+ -+ public abstract void setPriority(final PrioritisedExecutor.Priority priority); -+ -+ public abstract void raisePriority(final PrioritisedExecutor.Priority priority); -+ -+ public final void onComplete(final BiConsumer onComplete) { -+ if (!this.waiters.add(onComplete)) { -+ try { -+ onComplete.accept(this.completedChunk, this.completedThrowable); -+ } catch (final Throwable throwable) { -+ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Consumer", ChunkTaskScheduler.stringIfNull(onComplete), -+ "Completed throwable", ChunkTaskScheduler.stringIfNull(this.completedThrowable) -+ ), throwable); -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ } -+ } -+ } -+ -+ protected final void complete(final ChunkAccess chunk, final Throwable throwable) { -+ try { -+ this.complete0(chunk, throwable); -+ } catch (final Throwable thr2) { -+ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Completed throwable", ChunkTaskScheduler.stringIfNull(throwable) -+ ), thr2); -+ if (thr2 instanceof ThreadDeath) { -+ throw (ThreadDeath)thr2; -+ } -+ } -+ } -+ -+ private void complete0(final ChunkAccess chunk, final Throwable throwable) { -+ if ((boolean)COMPLETED_HANDLE.getAndSet((ChunkProgressionTask)this, (boolean)true)) { -+ throw new IllegalStateException("Already completed"); -+ } -+ this.completedChunk = chunk; -+ this.completedThrowable = throwable; -+ -+ BiConsumer consumer; -+ while ((consumer = this.waiters.pollOrBlockAdds()) != null) { -+ consumer.accept(chunk, throwable); -+ } -+ } -+ -+ @Override -+ public String toString() { -+ return "ChunkProgressionTask{class: " + this.getClass().getName() + ", for world: " + this.world.getWorld().getName() + -+ ", chunk: (" + this.chunkX + "," + this.chunkZ + "), hashcode: " + System.identityHashCode(this) + ", priority: " + this.getPriority() + -+ ", status: " + this.getTargetStatus().toString() + ", scheduled: " + this.isScheduled() + "}"; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkQueue.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkQueue.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4cc1b3ba6d093a9683dbd8b7fe76106ae391e019 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkQueue.java -@@ -0,0 +1,160 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import it.unimi.dsi.fastutil.HashCommon; -+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.Map; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.atomic.AtomicLong; -+ -+public final class ChunkQueue { -+ -+ public final int coordinateShift; -+ private final AtomicLong orderGenerator = new AtomicLong(); -+ private final ConcurrentHashMap unloadSections = new ConcurrentHashMap<>(); -+ -+ /* -+ * Note: write operations do not occur in parallel for any given section. -+ * Note: coordinateShift <= region shift in order for retrieveForCurrentRegion() to function correctly -+ */ -+ -+ public ChunkQueue(final int coordinateShift) { -+ this.coordinateShift = coordinateShift; -+ } -+ -+ public static record SectionToUnload(int sectionX, int sectionZ, Coordinate coord, long order, int count) {} -+ -+ public List retrieveForAllRegions() { -+ final List ret = new ArrayList<>(); -+ -+ for (final Map.Entry entry : this.unloadSections.entrySet()) { -+ final Coordinate coord = entry.getKey(); -+ final long key = coord.key; -+ final UnloadSection section = entry.getValue(); -+ final int sectionX = Coordinate.x(key); -+ final int sectionZ = Coordinate.z(key); -+ -+ ret.add(new SectionToUnload(sectionX, sectionZ, coord, section.order, section.chunks.size())); -+ } -+ -+ ret.sort((final SectionToUnload s1, final SectionToUnload s2) -> { -+ return Long.compare(s1.order, s2.order); -+ }); -+ -+ return ret; -+ } -+ -+ public UnloadSection getSectionUnsynchronized(final int sectionX, final int sectionZ) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ return this.unloadSections.get(coordinate); -+ } -+ -+ public UnloadSection removeSection(final int sectionX, final int sectionZ) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ return this.unloadSections.remove(coordinate); -+ } -+ -+ // write operation -+ public boolean addChunk(final int chunkX, final int chunkZ) { -+ final int shift = this.coordinateShift; -+ final int sectionX = chunkX >> shift; -+ final int sectionZ = chunkZ >> shift; -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ final long chunkKey = Coordinate.key(chunkX, chunkZ); -+ -+ UnloadSection section = this.unloadSections.get(coordinate); -+ if (section == null) { -+ section = new UnloadSection(this.orderGenerator.getAndIncrement()); -+ // write operations do not occur in parallel for a given section -+ this.unloadSections.put(coordinate, section); -+ } -+ -+ return section.chunks.add(chunkKey); -+ } -+ -+ // write operation -+ public boolean removeChunk(final int chunkX, final int chunkZ) { -+ final int shift = this.coordinateShift; -+ final int sectionX = chunkX >> shift; -+ final int sectionZ = chunkZ >> shift; -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ final long chunkKey = Coordinate.key(chunkX, chunkZ); -+ -+ final UnloadSection section = this.unloadSections.get(coordinate); -+ -+ if (section == null) { -+ return false; -+ } -+ -+ if (!section.chunks.remove(chunkKey)) { -+ return false; -+ } -+ -+ if (section.chunks.isEmpty()) { -+ this.unloadSections.remove(coordinate); -+ } -+ -+ return true; -+ } -+ -+ public static final class UnloadSection { -+ -+ public final long order; -+ public final LongLinkedOpenHashSet chunks = new LongLinkedOpenHashSet(); -+ -+ public UnloadSection(final long order) { -+ this.order = order; -+ } -+ } -+ -+ private static final class Coordinate implements Comparable { -+ -+ public final long key; -+ -+ public Coordinate(final long key) { -+ this.key = key; -+ } -+ -+ public Coordinate(final int x, final int z) { -+ this.key = key(x, z); -+ } -+ -+ public static long key(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public static int x(final long key) { -+ return (int)key; -+ } -+ -+ public static int z(final long key) { -+ return (int)(key >>> 32); -+ } -+ -+ @Override -+ public int hashCode() { -+ return (int)HashCommon.mix(this.key); -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (!(obj instanceof Coordinate other)) { -+ return false; -+ } -+ -+ return this.key == other.key; -+ } -+ -+ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain -+ // is too large. So we should implement compareTo to help. -+ @Override -+ public int compareTo(final Coordinate other) { -+ return Long.compare(this.key, other.key); -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..17ce14f2dcbf900890efbc2351782bc6f8867068 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -@@ -0,0 +1,883 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue; -+import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.io.RegionFileIOThread; -+import io.papermc.paper.chunk.system.scheduling.queue.RadiusAwarePrioritisedExecutor; -+import io.papermc.paper.configuration.GlobalConfiguration; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import java.util.function.BooleanSupplier; -+import net.minecraft.CrashReport; -+import net.minecraft.CrashReportCategory; -+import net.minecraft.ReportedException; -+import io.papermc.paper.util.MCUtil; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkMap; -+import net.minecraft.server.level.FullChunkStatus; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.LevelChunk; -+import org.bukkit.Bukkit; -+import org.slf4j.Logger; -+import java.io.File; -+import java.util.ArrayDeque; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Collections; -+import java.util.List; -+import java.util.Map; -+import java.util.Objects; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.function.Consumer; -+ -+public final class ChunkTaskScheduler { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ static int newChunkSystemIOThreads; -+ static int newChunkSystemWorkerThreads; -+ static int newChunkSystemGenParallelism; -+ static int newChunkSystemLoadParallelism; -+ -+ public static ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool workerThreads; -+ -+ private static boolean initialised = false; -+ -+ public static void init(final GlobalConfiguration.ChunkSystem config) { -+ if (initialised) { -+ return; -+ } -+ initialised = true; -+ newChunkSystemIOThreads = config.ioThreads; -+ newChunkSystemWorkerThreads = config.workerThreads; -+ if (newChunkSystemIOThreads < 0) { -+ newChunkSystemIOThreads = 1; -+ } else { -+ newChunkSystemIOThreads = Math.max(1, newChunkSystemIOThreads); -+ } -+ int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2; -+ if (defaultWorkerThreads <= 4) { -+ defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2; -+ } else { -+ defaultWorkerThreads = defaultWorkerThreads / 2; -+ } -+ defaultWorkerThreads = Integer.getInteger("Paper.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); -+ -+ if (newChunkSystemWorkerThreads < 0) { -+ newChunkSystemWorkerThreads = defaultWorkerThreads; -+ } else { -+ newChunkSystemWorkerThreads = Math.max(1, newChunkSystemWorkerThreads); -+ } -+ -+ String newChunkSystemGenParallelism = config.genParallelism; -+ if (newChunkSystemGenParallelism.equalsIgnoreCase("default")) { -+ newChunkSystemGenParallelism = "true"; -+ } -+ boolean useParallelGen; -+ if (newChunkSystemGenParallelism.equalsIgnoreCase("on") || newChunkSystemGenParallelism.equalsIgnoreCase("enabled") -+ || newChunkSystemGenParallelism.equalsIgnoreCase("true")) { -+ useParallelGen = true; -+ } else if (newChunkSystemGenParallelism.equalsIgnoreCase("off") || newChunkSystemGenParallelism.equalsIgnoreCase("disabled") -+ || newChunkSystemGenParallelism.equalsIgnoreCase("false")) { -+ useParallelGen = false; -+ } else { -+ throw new IllegalStateException("Invalid option for gen-parallelism: must be one of [on, off, enabled, disabled, true, false, default]"); -+ } -+ -+ ChunkTaskScheduler.newChunkSystemGenParallelism = useParallelGen ? newChunkSystemWorkerThreads : 1; -+ ChunkTaskScheduler.newChunkSystemLoadParallelism = newChunkSystemWorkerThreads; -+ -+ RegionFileIOThread.init(newChunkSystemIOThreads); -+ workerThreads = new ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool( -+ "Paper Chunk System Worker Pool", newChunkSystemWorkerThreads, -+ (final Thread thread, final Integer id) -> { -+ thread.setPriority(Thread.NORM_PRIORITY - 2); -+ thread.setName("Tuinity Chunk System Worker #" + id.intValue()); -+ thread.setUncaughtExceptionHandler(io.papermc.paper.chunk.system.scheduling.NewChunkHolder.CHUNKSYSTEM_UNCAUGHT_EXCEPTION_HANDLER); -+ }, (long)(20.0e6)); // 20ms -+ -+ LOGGER.info("Chunk system is using " + newChunkSystemIOThreads + " I/O threads, " + newChunkSystemWorkerThreads + " worker threads, and gen parallelism of " + ChunkTaskScheduler.newChunkSystemGenParallelism + " threads"); -+ } -+ -+ public final ServerLevel world; -+ public final PrioritisedThreadPool workers; -+ public final RadiusAwarePrioritisedExecutor radiusAwareScheduler; -+ public final PrioritisedThreadPool.PrioritisedPoolExecutor parallelGenExecutor; -+ private final PrioritisedThreadPool.PrioritisedPoolExecutor radiusAwareGenExecutor; -+ public final PrioritisedThreadPool.PrioritisedPoolExecutor loadExecutor; -+ -+ private final PrioritisedThreadedTaskQueue mainThreadExecutor = new PrioritisedThreadedTaskQueue(); -+ -+ public final ChunkHolderManager chunkHolderManager; -+ -+ static { -+ ChunkStatus.EMPTY.writeRadius = 0; -+ ChunkStatus.STRUCTURE_STARTS.writeRadius = 0; -+ ChunkStatus.STRUCTURE_REFERENCES.writeRadius = 0; -+ ChunkStatus.BIOMES.writeRadius = 0; -+ ChunkStatus.NOISE.writeRadius = 0; -+ ChunkStatus.SURFACE.writeRadius = 0; -+ ChunkStatus.CARVERS.writeRadius = 0; -+ ChunkStatus.FEATURES.writeRadius = 1; -+ ChunkStatus.INITIALIZE_LIGHT.writeRadius = 0; -+ ChunkStatus.LIGHT.writeRadius = 2; -+ ChunkStatus.SPAWN.writeRadius = 0; -+ ChunkStatus.FULL.writeRadius = 0; -+ -+ /* -+ It's important that the neighbour read radius is taken into account. If _any_ later status is using some chunk as -+ a neighbour, it must be also safe if that neighbour is being generated. i.e for any status later than FEATURES, -+ for a status to be parallel safe it must not read the block data from its neighbours. -+ */ -+ final List parallelCapableStatus = Arrays.asList( -+ // No-op executor. -+ ChunkStatus.EMPTY, -+ -+ // This is parallel capable, as CB has fixed the concurrency issue with stronghold generations. -+ // Does not touch neighbour chunks. -+ ChunkStatus.STRUCTURE_STARTS, -+ -+ // Surprisingly this is parallel capable. It is simply reading the already-created structure starts -+ // into the structure references for the chunk. So while it reads from it neighbours, its neighbours -+ // will not change, even if executed in parallel. -+ ChunkStatus.STRUCTURE_REFERENCES, -+ -+ // Safe. Mojang runs it in parallel as well. -+ ChunkStatus.BIOMES, -+ -+ // Safe. Mojang runs it in parallel as well. -+ ChunkStatus.NOISE, -+ -+ // Parallel safe. Only touches the target chunk. Biome retrieval is now noise based, which is -+ // completely thread-safe. -+ ChunkStatus.SURFACE, -+ -+ // No global state is modified in the carvers. It only touches the specified chunk. So it is parallel safe. -+ ChunkStatus.CARVERS, -+ -+ // FEATURES is not parallel safe. It writes to neighbours. -+ -+ // no-op executor -+ ChunkStatus.INITIALIZE_LIGHT -+ -+ // LIGHT is not parallel safe. It also doesn't run on the generation executor, so no point. -+ -+ // Only writes to the specified chunk. State is not read by later statuses. Parallel safe. -+ // Note: it may look unsafe because it writes to a worldgenregion, but the region size is always 0 - -+ // see the task margin. -+ // However, if the neighbouring FEATURES chunk is unloaded, but then fails to load in again (for whatever -+ // reason), then it would write to this chunk - and since this status reads blocks from itself, it's not -+ // safe to execute this in parallel. -+ // SPAWN -+ -+ // FULL is executed on main. -+ ); -+ -+ for (final ChunkStatus status : parallelCapableStatus) { -+ status.isParallelCapable = true; -+ } -+ } -+ -+ private static final int[] ACCESS_RADIUS_TABLE = new int[ChunkStatus.getStatusList().size()]; -+ private static final int[] MAX_ACCESS_RADIUS_TABLE = new int[ACCESS_RADIUS_TABLE.length]; -+ static { -+ Arrays.fill(ACCESS_RADIUS_TABLE, -1); -+ } -+ -+ private static int getAccessRadius0(final ChunkStatus genStatus) { -+ if (genStatus == ChunkStatus.EMPTY) { -+ return 0; -+ } -+ -+ final int radius = Math.max(genStatus.loadRange, genStatus.getRange()); -+ int maxRange = radius; -+ -+ for (int dist = 1; dist <= radius; ++dist) { -+ final ChunkStatus requiredNeighbourStatus = ChunkMap.getDependencyStatus(genStatus, radius); -+ final int rad = ACCESS_RADIUS_TABLE[requiredNeighbourStatus.getIndex()]; -+ if (rad == -1) { -+ throw new IllegalStateException(); -+ } -+ -+ maxRange = Math.max(maxRange, dist + rad); -+ } -+ -+ return maxRange; -+ } -+ -+ private static int maxAccessRadius; -+ -+ static { -+ final List statuses = ChunkStatus.getStatusList(); -+ for (int i = 0, len = statuses.size(); i < len; ++i) { -+ ACCESS_RADIUS_TABLE[i] = getAccessRadius0(statuses.get(i)); -+ } -+ int max = 0; -+ for (int i = 0, len = statuses.size(); i < len; ++i) { -+ MAX_ACCESS_RADIUS_TABLE[i] = max = Math.max(ACCESS_RADIUS_TABLE[i], max); -+ } -+ maxAccessRadius = max; -+ } -+ -+ public static int getMaxAccessRadius() { -+ return maxAccessRadius; -+ } -+ -+ public static int getAccessRadius(final ChunkStatus genStatus) { -+ return ACCESS_RADIUS_TABLE[genStatus.getIndex()]; -+ } -+ -+ public static int getAccessRadius(final FullChunkStatus status) { -+ return (status.ordinal() - 1) + getAccessRadius(ChunkStatus.FULL); -+ } -+ -+ final ReentrantAreaLock schedulingLockArea; -+ private final int lockShift; -+ -+ public final int getChunkSystemLockShift() { -+ return this.lockShift; -+ } -+ // Folia end - use area based lock to reduce contention -+ -+ public ChunkTaskScheduler(final ServerLevel world, final PrioritisedThreadPool workers) { -+ this.world = world; -+ this.workers = workers; -+ // must be >= region shift (in paper, doesn't exist) and must be >= ticket propagator section shift -+ // it must be >= region shift since the regioniser assumes ticket updates do not occur in parallel for the region sections -+ // it must be >= ticket propagator section shift so that the ticket propagator can assume that owning a position implies owning -+ // the entire section -+ // we just take the max, as we want the smallest shift that satisfies these properties -+ this.lockShift = Math.max(world.getRegionChunkShift(), ThreadedTicketLevelPropagator.SECTION_SHIFT); -+ this.schedulingLockArea = new ReentrantAreaLock(this.getChunkSystemLockShift()); -+ -+ final String worldName = world.getWorld().getName(); -+ this.parallelGenExecutor = workers.createExecutor("Chunk parallel generation executor for world '" + worldName + "'", Math.max(1, newChunkSystemGenParallelism)); -+ this.radiusAwareGenExecutor = -+ newChunkSystemGenParallelism <= 1 ? this.parallelGenExecutor : workers.createExecutor("Chunk radius aware generator for world '" + worldName + "'", newChunkSystemGenParallelism); -+ this.loadExecutor = workers.createExecutor("Chunk load executor for world '" + worldName + "'", newChunkSystemLoadParallelism); -+ this.radiusAwareScheduler = new RadiusAwarePrioritisedExecutor(this.radiusAwareGenExecutor, Math.max(1, newChunkSystemGenParallelism)); -+ this.chunkHolderManager = new ChunkHolderManager(world, this); -+ } -+ -+ private final AtomicBoolean failedChunkSystem = new AtomicBoolean(); -+ -+ public static Object stringIfNull(final Object obj) { -+ return obj == null ? "null" : obj; -+ } -+ -+ public void unrecoverableChunkSystemFailure(final int chunkX, final int chunkZ, final Map objectsOfInterest, final Throwable thr) { -+ final NewChunkHolder holder = this.chunkHolderManager.getChunkHolder(chunkX, chunkZ); -+ LOGGER.error("Chunk system error at chunk (" + chunkX + "," + chunkZ + "), holder: " + holder + ", exception:", new Throwable(thr)); -+ -+ if (this.failedChunkSystem.getAndSet(true)) { -+ return; -+ } -+ -+ final ReportedException reportedException = thr instanceof ReportedException ? (ReportedException)thr : new ReportedException(new CrashReport("Chunk system error", thr)); -+ -+ CrashReportCategory crashReportCategory = reportedException.getReport().addCategory("Chunk system details"); -+ crashReportCategory.setDetail("Chunk coordinate", new ChunkPos(chunkX, chunkZ).toString()); -+ crashReportCategory.setDetail("ChunkHolder", Objects.toString(holder)); -+ crashReportCategory.setDetail("unrecoverableChunkSystemFailure caller thread", Thread.currentThread().getName()); -+ -+ crashReportCategory = reportedException.getReport().addCategory("Chunk System Objects of Interest"); -+ for (final Map.Entry entry : objectsOfInterest.entrySet()) { -+ if (entry.getValue() instanceof Throwable thrObject) { -+ crashReportCategory.setDetailError(Objects.toString(entry.getKey()), thrObject); -+ } else { -+ crashReportCategory.setDetail(Objects.toString(entry.getKey()), Objects.toString(entry.getValue())); -+ } -+ } -+ -+ final Runnable crash = () -> { -+ throw new RuntimeException("Chunk system crash propagated from unrecoverableChunkSystemFailure", reportedException); -+ }; -+ -+ // this may not be good enough, specifically thanks to stupid ass plugins swallowing exceptions -+ this.scheduleChunkTask(chunkX, chunkZ, crash, PrioritisedExecutor.Priority.BLOCKING); -+ // so, make the main thread pick it up -+ MinecraftServer.chunkSystemCrash = new RuntimeException("Chunk system crash propagated from unrecoverableChunkSystemFailure", reportedException); -+ } -+ -+ public boolean executeMainThreadTask() { -+ TickThread.ensureTickThread("Cannot execute main thread task off-main"); -+ return this.mainThreadExecutor.executeTask(); -+ } -+ -+ public void raisePriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ this.chunkHolderManager.raisePriority(x, z, priority); -+ } -+ -+ public void setPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ this.chunkHolderManager.setPriority(x, z, priority); -+ } -+ -+ public void lowerPriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -+ this.chunkHolderManager.lowerPriority(x, z, priority); -+ } -+ -+ private final AtomicLong chunkLoadCounter = new AtomicLong(); -+ -+ public void scheduleTickingState(final int chunkX, final int chunkZ, final FullChunkStatus toStatus, -+ final boolean addTicket, final PrioritisedExecutor.Priority priority, -+ final Consumer onComplete) { -+ if (!TickThread.isTickThread()) { -+ this.scheduleChunkTask(chunkX, chunkZ, () -> { -+ ChunkTaskScheduler.this.scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -+ }, priority); -+ return; -+ } -+ final int accessRadius = getAccessRadius(toStatus); -+ if (this.chunkHolderManager.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { -+ throw new IllegalStateException("Cannot schedule chunk load during ticket level update"); -+ } -+ if (this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { -+ throw new IllegalStateException("Cannot schedule chunk loading recursively"); -+ } -+ -+ if (toStatus == FullChunkStatus.INACCESSIBLE) { -+ throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status"); -+ } -+ -+ final int minLevel = 33 - (toStatus.ordinal() - 1); -+ final Long chunkReference = addTicket ? Long.valueOf(this.chunkLoadCounter.getAndIncrement()) : null; -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ if (addTicket) { -+ this.chunkHolderManager.addTicketAtLevel(TicketType.CHUNK_LOAD, chunkKey, minLevel, chunkReference); -+ this.chunkHolderManager.processTicketUpdates(); -+ } -+ -+ final Consumer loadCallback = (final LevelChunk chunk) -> { -+ try { -+ if (onComplete != null) { -+ onComplete.accept(chunk); -+ } -+ } finally { -+ if (addTicket) { -+ ChunkTaskScheduler.this.chunkHolderManager.addAndRemoveTickets(chunkKey, -+ TicketType.UNKNOWN, minLevel, new ChunkPos(chunkKey), -+ TicketType.CHUNK_LOAD, minLevel, chunkReference -+ ); -+ } -+ } -+ }; -+ -+ final boolean scheduled; -+ final LevelChunk chunk; -+ final ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius); -+ try { -+ final ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius); -+ try { -+ final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey); -+ if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) { -+ scheduled = false; -+ chunk = null; -+ } else { -+ final FullChunkStatus currStatus = chunkHolder.getChunkStatus(); -+ if (currStatus.isOrAfter(toStatus)) { -+ scheduled = false; -+ chunk = (LevelChunk)chunkHolder.getCurrentChunk(); -+ } else { -+ scheduled = true; -+ chunk = null; -+ -+ final int radius = toStatus.ordinal() - 1; // 0 -> BORDER, 1 -> TICKING, 2 -> ENTITY_TICKING -+ for (int dz = -radius; dz <= radius; ++dz) { -+ for (int dx = -radius; dx <= radius; ++dx) { -+ final NewChunkHolder neighbour = -+ (dx | dz) == 0 ? chunkHolder : this.chunkHolderManager.getChunkHolder(dx + chunkX, dz + chunkZ); -+ if (neighbour != null) { -+ neighbour.raisePriority(priority); -+ } -+ } -+ } -+ -+ // ticket level should schedule for us -+ chunkHolder.addFullStatusConsumer(toStatus, loadCallback); -+ } -+ } -+ } finally { -+ this.schedulingLockArea.unlock(schedulingLock); -+ } -+ } finally { -+ this.chunkHolderManager.ticketLockArea.unlock(ticketLock); -+ } -+ -+ if (!scheduled) { -+ // couldn't schedule -+ try { -+ loadCallback.accept(chunk); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to process chunk full status callback", thr); -+ } -+ } -+ } -+ -+ public void scheduleChunkLoad(final int chunkX, final int chunkZ, final boolean gen, final ChunkStatus toStatus, final boolean addTicket, -+ final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -+ if (gen) { -+ this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -+ return; -+ } -+ this.scheduleChunkLoad(chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> { -+ if (chunk == null) { -+ onComplete.accept(null); -+ } else { -+ if (chunk.getStatus().isOrAfter(toStatus)) { -+ this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -+ } else { -+ onComplete.accept(null); -+ } -+ } -+ }); -+ } -+ -+ // only appropriate to use with ServerLevel#syncLoadNonFull -+ public boolean beginChunkLoadForNonFullSync(final int chunkX, final int chunkZ, final ChunkStatus toStatus, -+ final PrioritisedExecutor.Priority priority) { -+ final int accessRadius = getAccessRadius(toStatus); -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final int minLevel = 33 + ChunkStatus.getDistance(toStatus); -+ final List tasks = new ArrayList<>(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention -+ try { -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention -+ try { -+ final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey); -+ if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) { -+ return false; -+ } else { -+ final ChunkStatus genStatus = chunkHolder.getCurrentGenStatus(); -+ if (genStatus != null && genStatus.isOrAfter(toStatus)) { -+ return true; -+ } else { -+ chunkHolder.raisePriority(priority); -+ -+ if (!chunkHolder.upgradeGenTarget(toStatus)) { -+ this.schedule(chunkX, chunkZ, toStatus, chunkHolder, tasks); -+ } -+ } -+ } -+ } finally { -+ this.schedulingLockArea.unlock(schedulingLock); -+ } -+ } finally { -+ this.chunkHolderManager.ticketLockArea.unlock(ticketLock); -+ } -+ -+ for (int i = 0, len = tasks.size(); i < len; ++i) { -+ tasks.get(i).schedule(); -+ } -+ -+ return true; -+ } -+ -+ public void scheduleChunkLoad(final int chunkX, final int chunkZ, final ChunkStatus toStatus, final boolean addTicket, -+ final PrioritisedExecutor.Priority priority, final Consumer onComplete) { -+ if (!TickThread.isTickThread()) { -+ this.scheduleChunkTask(chunkX, chunkZ, () -> { -+ ChunkTaskScheduler.this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); -+ }, priority); -+ return; -+ } -+ final int accessRadius = getAccessRadius(toStatus); -+ if (this.chunkHolderManager.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { -+ throw new IllegalStateException("Cannot schedule chunk load during ticket level update"); -+ } -+ if (this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { -+ throw new IllegalStateException("Cannot schedule chunk loading recursively"); -+ } -+ -+ if (toStatus == ChunkStatus.FULL) { -+ this.scheduleTickingState(chunkX, chunkZ, FullChunkStatus.FULL, addTicket, priority, (Consumer)onComplete); -+ return; -+ } -+ -+ final int minLevel = 33 + ChunkStatus.getDistance(toStatus); -+ final Long chunkReference = addTicket ? Long.valueOf(this.chunkLoadCounter.getAndIncrement()) : null; -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ if (addTicket) { -+ this.chunkHolderManager.addTicketAtLevel(TicketType.CHUNK_LOAD, chunkKey, minLevel, chunkReference); -+ this.chunkHolderManager.processTicketUpdates(); -+ } -+ -+ final Consumer loadCallback = (final ChunkAccess chunk) -> { -+ try { -+ if (onComplete != null) { -+ onComplete.accept(chunk); -+ } -+ } finally { -+ if (addTicket) { -+ ChunkTaskScheduler.this.chunkHolderManager.addAndRemoveTickets(chunkKey, -+ TicketType.UNKNOWN, minLevel, new ChunkPos(chunkKey), -+ TicketType.CHUNK_LOAD, minLevel, chunkReference -+ ); -+ } -+ } -+ }; -+ -+ final List tasks = new ArrayList<>(); -+ -+ final boolean scheduled; -+ final ChunkAccess chunk; -+ final ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius); -+ try { -+ final ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius); -+ try { -+ final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey); -+ if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) { -+ scheduled = false; -+ chunk = null; -+ } else { -+ final ChunkStatus genStatus = chunkHolder.getCurrentGenStatus(); -+ if (genStatus != null && genStatus.isOrAfter(toStatus)) { -+ scheduled = false; -+ chunk = chunkHolder.getCurrentChunk(); -+ } else { -+ scheduled = true; -+ chunk = null; -+ chunkHolder.raisePriority(priority); -+ -+ if (!chunkHolder.upgradeGenTarget(toStatus)) { -+ this.schedule(chunkX, chunkZ, toStatus, chunkHolder, tasks); -+ } -+ chunkHolder.addStatusConsumer(toStatus, loadCallback); -+ } -+ } -+ } finally { -+ this.schedulingLockArea.unlock(schedulingLock); -+ } -+ } finally { -+ this.chunkHolderManager.ticketLockArea.unlock(ticketLock); -+ } -+ -+ for (int i = 0, len = tasks.size(); i < len; ++i) { -+ tasks.get(i).schedule(); -+ } -+ -+ if (!scheduled) { -+ // couldn't schedule -+ try { -+ loadCallback.accept(chunk); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to process chunk status callback", thr); -+ } -+ } -+ } -+ -+ private ChunkProgressionTask createTask(final int chunkX, final int chunkZ, final ChunkAccess chunk, -+ final NewChunkHolder chunkHolder, final List neighbours, -+ final ChunkStatus toStatus, final PrioritisedExecutor.Priority initialPriority) { -+ if (toStatus == ChunkStatus.EMPTY) { -+ return new ChunkLoadTask(this, this.world, chunkX, chunkZ, chunkHolder, initialPriority); -+ } -+ if (toStatus == ChunkStatus.LIGHT) { -+ return new ChunkLightTask(this, this.world, chunkX, chunkZ, chunk, initialPriority); -+ } -+ if (toStatus == ChunkStatus.FULL) { -+ return new ChunkFullTask(this, this.world, chunkX, chunkZ, chunkHolder, chunk, initialPriority); -+ } -+ -+ return new ChunkUpgradeGenericStatusTask(this, this.world, chunkX, chunkZ, chunk, neighbours, toStatus, initialPriority); -+ } -+ -+ ChunkProgressionTask schedule(final int chunkX, final int chunkZ, final ChunkStatus targetStatus, final NewChunkHolder chunkHolder, -+ final List allTasks) { -+ return this.schedule(chunkX, chunkZ, targetStatus, chunkHolder, allTasks, chunkHolder.getEffectivePriority()); -+ } -+ -+ // rets new task scheduled for the _specified_ chunk -+ // note: this must hold the scheduling lock -+ // minPriority is only used to pass the priority through to neighbours, as priority calculation has not yet been done -+ // schedule will ignore the generation target, so it should be checked by the caller to ensure the target is not regressed! -+ private ChunkProgressionTask schedule(final int chunkX, final int chunkZ, final ChunkStatus targetStatus, -+ final NewChunkHolder chunkHolder, final List allTasks, -+ final PrioritisedExecutor.Priority minPriority) { -+ if (!this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, getAccessRadius(targetStatus))) { -+ throw new IllegalStateException("Not holding scheduling lock"); -+ } -+ -+ if (chunkHolder.hasGenerationTask()) { -+ chunkHolder.upgradeGenTarget(targetStatus); -+ return null; -+ } -+ -+ final PrioritisedExecutor.Priority requestedPriority = PrioritisedExecutor.Priority.max(minPriority, chunkHolder.getEffectivePriority()); -+ final ChunkStatus currentGenStatus = chunkHolder.getCurrentGenStatus(); -+ final ChunkAccess chunk = chunkHolder.getCurrentChunk(); -+ -+ if (currentGenStatus == null) { -+ // not yet loaded -+ final ChunkProgressionTask task = this.createTask( -+ chunkX, chunkZ, chunk, chunkHolder, Collections.emptyList(), ChunkStatus.EMPTY, requestedPriority -+ ); -+ -+ allTasks.add(task); -+ -+ final List chunkHolderNeighbours = new ArrayList<>(1); -+ chunkHolderNeighbours.add(chunkHolder); -+ -+ chunkHolder.setGenerationTarget(targetStatus); -+ chunkHolder.setGenerationTask(task, ChunkStatus.EMPTY, chunkHolderNeighbours); -+ -+ return task; -+ } -+ -+ if (currentGenStatus.isOrAfter(targetStatus)) { -+ // nothing to do -+ return null; -+ } -+ -+ // we know for sure now that we want to schedule _something_, so set the target -+ chunkHolder.setGenerationTarget(targetStatus); -+ -+ final ChunkStatus chunkRealStatus = chunk.getStatus(); -+ final ChunkStatus toStatus = currentGenStatus.getNextStatus(); -+ -+ // if this chunk has already generated up to or past the specified status, then we don't -+ // need the neighbours AT ALL. -+ final int neighbourReadRadius = chunkRealStatus.isOrAfter(toStatus) ? toStatus.loadRange : toStatus.getRange(); -+ -+ boolean unGeneratedNeighbours = false; -+ -+ // copied from MCUtil.getSpiralOutChunks -+ for (int r = 1; r <= neighbourReadRadius; r++) { -+ int x = -r; -+ int z = r; -+ -+ // Iterates the edge of half of the box; then negates for other half. -+ while (x <= r && z > -r) { -+ final int radius = Math.max(Math.abs(x), Math.abs(z)); -+ final ChunkStatus requiredNeighbourStatus = ChunkMap.getDependencyStatus(toStatus, radius); -+ -+ unGeneratedNeighbours |= this.checkNeighbour( -+ chunkX + x, chunkZ + z, requiredNeighbourStatus, chunkHolder, allTasks, requestedPriority -+ ); -+ unGeneratedNeighbours |= this.checkNeighbour( -+ chunkX - x, chunkZ - z, requiredNeighbourStatus, chunkHolder, allTasks, requestedPriority -+ ); -+ -+ if (x < r) { -+ x++; -+ } else { -+ z--; -+ } -+ } -+ } -+ -+ if (unGeneratedNeighbours) { -+ // can't schedule, but neighbour completion will schedule for us when they're ALL done -+ -+ // propagate our priority to neighbours -+ chunkHolder.recalculateNeighbourPriorities(); -+ return null; -+ } -+ -+ // need to gather neighbours -+ -+ final List neighbours; -+ final List chunkHolderNeighbours; -+ if (neighbourReadRadius <= 0) { -+ neighbours = new ArrayList<>(1); -+ chunkHolderNeighbours = new ArrayList<>(1); -+ neighbours.add(chunk); -+ chunkHolderNeighbours.add(chunkHolder); -+ } else { -+ // the iteration order is _very_ important, as all generation statuses expect a certain order such that: -+ // chunkAtRelative = neighbours.get(relX + relZ * (2 * radius + 1)) -+ neighbours = new ArrayList<>((2 * neighbourReadRadius + 1) * (2 * neighbourReadRadius + 1)); -+ chunkHolderNeighbours = new ArrayList<>((2 * neighbourReadRadius + 1) * (2 * neighbourReadRadius + 1)); -+ for (int dz = -neighbourReadRadius; dz <= neighbourReadRadius; ++dz) { -+ for (int dx = -neighbourReadRadius; dx <= neighbourReadRadius; ++dx) { -+ final NewChunkHolder holder = (dx | dz) == 0 ? chunkHolder : this.chunkHolderManager.getChunkHolder(dx + chunkX, dz + chunkZ); -+ neighbours.add(holder.getChunkForNeighbourAccess()); -+ chunkHolderNeighbours.add(holder); -+ } -+ } -+ } -+ -+ final ChunkProgressionTask task = this.createTask(chunkX, chunkZ, chunk, chunkHolder, neighbours, toStatus, chunkHolder.getEffectivePriority()); -+ allTasks.add(task); -+ -+ chunkHolder.setGenerationTask(task, toStatus, chunkHolderNeighbours); -+ -+ return task; -+ } -+ -+ // rets true if the neighbour is not at the required status, false otherwise -+ private boolean checkNeighbour(final int chunkX, final int chunkZ, final ChunkStatus requiredStatus, final NewChunkHolder center, -+ final List tasks, final PrioritisedExecutor.Priority minPriority) { -+ final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkX, chunkZ); -+ -+ if (chunkHolder == null) { -+ throw new IllegalStateException("Missing chunkholder when required"); -+ } -+ -+ final ChunkStatus holderStatus = chunkHolder.getCurrentGenStatus(); -+ if (holderStatus != null && holderStatus.isOrAfter(requiredStatus)) { -+ return false; -+ } -+ -+ if (chunkHolder.hasFailedGeneration()) { -+ return true; -+ } -+ -+ center.addGenerationBlockingNeighbour(chunkHolder); -+ chunkHolder.addWaitingNeighbour(center, requiredStatus); -+ -+ if (chunkHolder.upgradeGenTarget(requiredStatus)) { -+ return true; -+ } -+ -+ // not at status required, so we need to schedule its generation -+ this.schedule( -+ chunkX, chunkZ, requiredStatus, chunkHolder, tasks, minPriority -+ ); -+ -+ return true; -+ } -+ -+ /** -+ * @deprecated Chunk tasks must be tied to coordinates in the future -+ */ -+ @Deprecated -+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run) { -+ return this.scheduleChunkTask(run, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ /** -+ * @deprecated Chunk tasks must be tied to coordinates in the future -+ */ -+ @Deprecated -+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ return this.mainThreadExecutor.queueRunnable(run, priority); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run) { -+ return this.createChunkTask(chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run, -+ final PrioritisedExecutor.Priority priority) { -+ return this.mainThreadExecutor.createTask(run, priority); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run) { -+ return this.mainThreadExecutor.queueRunnable(run); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run, -+ final PrioritisedExecutor.Priority priority) { -+ return this.mainThreadExecutor.queueRunnable(run, priority); -+ } -+ -+ public void executeTasksUntil(final BooleanSupplier exit) { -+ if (Bukkit.isPrimaryThread()) { -+ this.mainThreadExecutor.executeConditionally(exit); -+ } else { -+ long counter = 1L; -+ while (!exit.getAsBoolean()) { -+ counter = ConcurrentUtil.linearLongBackoff(counter, 100_000L, 5_000_000L); // 100us, 5ms -+ } -+ } -+ } -+ -+ public boolean halt(final boolean sync, final long maxWaitNS) { -+ this.radiusAwareGenExecutor.halt(); -+ this.parallelGenExecutor.halt(); -+ this.loadExecutor.halt(); -+ final long time = System.nanoTime(); -+ if (sync) { -+ for (long failures = 9L;; failures = ConcurrentUtil.linearLongBackoff(failures, 500_000L, 50_000_000L)) { -+ if ( -+ !this.radiusAwareGenExecutor.isActive() && -+ !this.parallelGenExecutor.isActive() && -+ !this.loadExecutor.isActive() -+ ) { -+ return true; -+ } -+ if ((System.nanoTime() - time) >= maxWaitNS) { -+ return false; -+ } -+ } -+ } -+ -+ return true; -+ } -+ -+ public static final ArrayDeque WAITING_CHUNKS = new ArrayDeque<>(); // stack -+ -+ public static final class ChunkInfo { -+ -+ public final int chunkX; -+ public final int chunkZ; -+ public final ServerLevel world; -+ -+ public ChunkInfo(final int chunkX, final int chunkZ, final ServerLevel world) { -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.world = world; -+ } -+ -+ @Override -+ public String toString() { -+ return "[( " + this.chunkX + "," + this.chunkZ + ") in '" + this.world.getWorld().getName() + "']"; -+ } -+ } -+ -+ public static void pushChunkWait(final ServerLevel world, final int chunkX, final int chunkZ) { -+ synchronized (WAITING_CHUNKS) { -+ WAITING_CHUNKS.push(new ChunkInfo(chunkX, chunkZ, world)); -+ } -+ } -+ -+ public static void popChunkWait() { -+ synchronized (WAITING_CHUNKS) { -+ WAITING_CHUNKS.pop(); -+ } -+ } -+ -+ public static ChunkInfo[] getChunkInfos() { -+ synchronized (WAITING_CHUNKS) { -+ return WAITING_CHUNKS.toArray(new ChunkInfo[0]); -+ } -+ } -+ -+ public static void dumpAllChunkLoadInfo(final boolean longPrint) { -+ final ChunkInfo[] chunkInfos = getChunkInfos(); -+ if (chunkInfos.length > 0) { -+ LOGGER.error("Chunk wait task info below: "); -+ for (final ChunkInfo chunkInfo : chunkInfos) { -+ final NewChunkHolder holder = chunkInfo.world.chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkInfo.chunkX, chunkInfo.chunkZ); -+ LOGGER.error("Chunk wait: " + chunkInfo); -+ LOGGER.error("Chunk holder: " + holder); -+ } -+ -+ if (longPrint) { -+ final File file = new File(new File(new File("."), "debug"), "chunks-watchdog.txt"); -+ LOGGER.error("Writing chunk information dump to " + file); -+ try { -+ MCUtil.dumpChunks(file, true); -+ LOGGER.error("Successfully written chunk information!"); -+ } catch (final Throwable thr) { -+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkUpgradeGenericStatusTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkUpgradeGenericStatusTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e929d55fdb6fad6587af058dff6cadb6bc2a156b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkUpgradeGenericStatusTask.java -@@ -0,0 +1,212 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import com.mojang.datafixers.util.Either; -+import com.mojang.logging.LogUtils; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ChunkMap; -+import net.minecraft.server.level.ServerChunkCache; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import org.slf4j.Logger; -+import java.lang.invoke.VarHandle; -+import java.util.List; -+import java.util.Map; -+import java.util.concurrent.CompletableFuture; -+ -+public final class ChunkUpgradeGenericStatusTask extends ChunkProgressionTask implements Runnable { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ protected final ChunkAccess fromChunk; -+ protected final ChunkStatus fromStatus; -+ protected final ChunkStatus toStatus; -+ protected final List neighbours; -+ -+ protected final PrioritisedExecutor.PrioritisedTask generateTask; -+ -+ public ChunkUpgradeGenericStatusTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final ChunkAccess chunk, final List neighbours, -+ final ChunkStatus toStatus, final PrioritisedExecutor.Priority priority) { -+ super(scheduler, world, chunkX, chunkZ); -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.fromChunk = chunk; -+ this.fromStatus = chunk.getStatus(); -+ this.toStatus = toStatus; -+ this.neighbours = neighbours; -+ if (this.toStatus.isParallelCapable) { -+ this.generateTask = this.scheduler.parallelGenExecutor.createTask(this, priority); -+ } else { -+ this.generateTask = this.scheduler.radiusAwareScheduler.createTask(chunkX, chunkZ, this.toStatus.writeRadius, this, priority); -+ } -+ } -+ -+ @Override -+ public ChunkStatus getTargetStatus() { -+ return this.toStatus; -+ } -+ -+ private boolean isEmptyTask() { -+ // must use fromStatus here to avoid any race condition with run() overwriting the status -+ final boolean generation = !this.fromStatus.isOrAfter(this.toStatus); -+ return (generation && this.toStatus.isEmptyGenStatus()) || (!generation && this.toStatus.isEmptyLoadStatus()); -+ } -+ -+ @Override -+ public void run() { -+ final ChunkAccess chunk = this.fromChunk; -+ -+ final ServerChunkCache serverChunkCache = this.world.chunkSource; -+ final ChunkMap chunkMap = serverChunkCache.chunkMap; -+ -+ final CompletableFuture> completeFuture; -+ -+ final boolean generation; -+ boolean completing = false; -+ -+ // note: should optimise the case where the chunk does not need to execute the status, because -+ // schedule() calls this synchronously if it will run through that path -+ -+ try { -+ generation = !chunk.getStatus().isOrAfter(this.toStatus); -+ if (generation) { -+ if (this.toStatus.isEmptyGenStatus()) { -+ if (chunk instanceof ProtoChunk) { -+ ((ProtoChunk)chunk).setStatus(this.toStatus); -+ } -+ completing = true; -+ this.complete(chunk, null); -+ return; -+ } -+ completeFuture = this.toStatus.generate(Runnable::run, this.world, chunkMap.generator, chunkMap.structureTemplateManager, -+ serverChunkCache.getLightEngine(), null, this.neighbours) -+ .whenComplete((final Either either, final Throwable throwable) -> { -+ final ChunkAccess newChunk = (either == null) ? null : either.left().orElse(null); -+ if (newChunk instanceof ProtoChunk) { -+ ((ProtoChunk)newChunk).setStatus(ChunkUpgradeGenericStatusTask.this.toStatus); -+ } -+ } -+ ); -+ } else { -+ if (this.toStatus.isEmptyLoadStatus()) { -+ completing = true; -+ this.complete(chunk, null); -+ return; -+ } -+ completeFuture = this.toStatus.load(this.world, chunkMap.structureTemplateManager, serverChunkCache.getLightEngine(), null, chunk); -+ } -+ } catch (final Throwable throwable) { -+ if (!completing) { -+ this.complete(null, throwable); -+ -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ return; -+ } -+ -+ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Target status", ChunkTaskScheduler.stringIfNull(this.toStatus), -+ "From status", ChunkTaskScheduler.stringIfNull(this.fromStatus), -+ "Generation task", this -+ ), throwable); -+ -+ if (!(throwable instanceof ThreadDeath)) { -+ LOGGER.error("Failed to complete status for chunk: status:" + this.toStatus + ", chunk: (" + this.chunkX + "," + this.chunkZ + "), world: " + this.world.getWorld().getName(), throwable); -+ } else { -+ // ensure the chunk system can respond, then die -+ throw (ThreadDeath)throwable; -+ } -+ return; -+ } -+ -+ if (!completeFuture.isDone() && !this.toStatus.warnedAboutNoImmediateComplete.getAndSet(true)) { -+ LOGGER.warn("Future status not complete after scheduling: " + this.toStatus.toString() + ", generate: " + generation); -+ } -+ -+ final Either either; -+ final ChunkAccess newChunk; -+ -+ try { -+ either = completeFuture.join(); -+ newChunk = (either == null) ? null : either.left().orElse(null); -+ } catch (final Throwable throwable) { -+ this.complete(null, throwable); -+ // ensure the chunk system can respond, then die -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ return; -+ } -+ -+ if (newChunk == null) { -+ this.complete(null, new IllegalStateException("Chunk for status: " + ChunkUpgradeGenericStatusTask.this.toStatus.toString() + ", generation: " + generation + " should not be null! Either: " + either).fillInStackTrace()); -+ return; -+ } -+ -+ this.complete(newChunk, null); -+ } -+ -+ protected volatile boolean scheduled; -+ protected static final VarHandle SCHEDULED_HANDLE = ConcurrentUtil.getVarHandle(ChunkUpgradeGenericStatusTask.class, "scheduled", boolean.class); -+ -+ @Override -+ public boolean isScheduled() { -+ return this.scheduled; -+ } -+ -+ @Override -+ public void schedule() { -+ if ((boolean)SCHEDULED_HANDLE.getAndSet((ChunkUpgradeGenericStatusTask)this, true)) { -+ throw new IllegalStateException("Cannot double call schedule()"); -+ } -+ if (this.isEmptyTask()) { -+ if (this.generateTask.cancel()) { -+ this.run(); -+ } -+ } else { -+ this.generateTask.queue(); -+ } -+ } -+ -+ @Override -+ public void cancel() { -+ if (this.generateTask.cancel()) { -+ this.complete(null, null); -+ } -+ } -+ -+ @Override -+ public PrioritisedExecutor.Priority getPriority() { -+ return this.generateTask.getPriority(); -+ } -+ -+ @Override -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.generateTask.lowerPriority(priority); -+ } -+ -+ @Override -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.generateTask.setPriority(priority); -+ } -+ -+ @Override -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.generateTask.raisePriority(priority); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/GenericDataLoadTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/GenericDataLoadTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..396d72c00e47cf1669ae20dc839c1c961b1f262a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/GenericDataLoadTask.java -@@ -0,0 +1,746 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.completable.Completable; -+import ca.spottedleaf.concurrentutil.executor.Cancellable; -+import ca.spottedleaf.concurrentutil.executor.standard.DelayedPrioritisedTask; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.io.RegionFileIOThread; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ServerLevel; -+import org.slf4j.Logger; -+import java.lang.invoke.VarHandle; -+import java.util.Map; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.function.BiConsumer; -+ -+public abstract class GenericDataLoadTask { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ protected static final CompoundTag CANCELLED_DATA = new CompoundTag(); -+ -+ // reference count is the upper 32 bits -+ protected final AtomicLong stageAndReferenceCount = new AtomicLong(STAGE_NOT_STARTED); -+ -+ protected static final long STAGE_MASK = 0xFFFFFFFFL; -+ protected static final long STAGE_CANCELLED = 0xFFFFFFFFL; -+ protected static final long STAGE_NOT_STARTED = 0L; -+ protected static final long STAGE_LOADING = 1L; -+ protected static final long STAGE_PROCESSING = 2L; -+ protected static final long STAGE_COMPLETED = 3L; -+ -+ // for loading data off disk -+ protected final LoadDataFromDiskTask loadDataFromDiskTask; -+ // processing off-main -+ protected final PrioritisedExecutor.PrioritisedTask processOffMain; -+ // processing on-main -+ protected final PrioritisedExecutor.PrioritisedTask processOnMain; -+ -+ protected final ChunkTaskScheduler scheduler; -+ protected final ServerLevel world; -+ protected final int chunkX; -+ protected final int chunkZ; -+ protected final RegionFileIOThread.RegionFileType type; -+ -+ public GenericDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, -+ final int chunkZ, final RegionFileIOThread.RegionFileType type, -+ final PrioritisedExecutor.Priority priority) { -+ this.scheduler = scheduler; -+ this.world = world; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.type = type; -+ -+ final ProcessOnMainTask mainTask; -+ if (this.hasOnMain()) { -+ mainTask = new ProcessOnMainTask(); -+ this.processOnMain = this.createOnMain(mainTask, priority); -+ } else { -+ mainTask = null; -+ this.processOnMain = null; -+ } -+ -+ final ProcessOffMainTask offMainTask; -+ if (this.hasOffMain()) { -+ offMainTask = new ProcessOffMainTask(mainTask); -+ this.processOffMain = this.createOffMain(offMainTask, priority); -+ } else { -+ offMainTask = null; -+ this.processOffMain = null; -+ } -+ -+ if (this.processOffMain == null && this.processOnMain == null) { -+ throw new IllegalStateException("Illegal class implementation: " + this.getClass().getName() + ", should be able to schedule at least one task!"); -+ } -+ -+ this.loadDataFromDiskTask = new LoadDataFromDiskTask(world, chunkX, chunkZ, type, new DataLoadCallback(offMainTask, mainTask), priority); -+ } -+ -+ public static final record TaskResult(L left, R right) {} -+ -+ protected abstract boolean hasOffMain(); -+ -+ protected abstract boolean hasOnMain(); -+ -+ protected abstract PrioritisedExecutor.PrioritisedTask createOffMain(final Runnable run, final PrioritisedExecutor.Priority priority); -+ -+ protected abstract PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority); -+ -+ protected abstract TaskResult runOffMain(final CompoundTag data, final Throwable throwable); -+ -+ protected abstract TaskResult runOnMain(final OnMain data, final Throwable throwable); -+ -+ protected abstract void onComplete(final TaskResult result); -+ -+ protected abstract TaskResult completeOnMainOffMain(final OnMain data, final Throwable throwable); -+ -+ @Override -+ public String toString() { -+ return "GenericDataLoadTask{class: " + this.getClass().getName() + ", world: " + this.world.getWorld().getName() + -+ ", chunk: (" + this.chunkX + "," + this.chunkZ + "), hashcode: " + System.identityHashCode(this) + ", priority: " + this.getPriority() + -+ ", type: " + this.type.toString() + "}"; -+ } -+ -+ public PrioritisedExecutor.Priority getPriority() { -+ if (this.processOnMain != null) { -+ return this.processOnMain.getPriority(); -+ } else { -+ return this.processOffMain.getPriority(); -+ } -+ } -+ -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ // can't lower I/O tasks, we don't know what they affect -+ if (this.processOffMain != null) { -+ this.processOffMain.lowerPriority(priority); -+ } -+ if (this.processOnMain != null) { -+ this.processOnMain.lowerPriority(priority); -+ } -+ } -+ -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ // can't lower I/O tasks, we don't know what they affect -+ this.loadDataFromDiskTask.raisePriority(priority); -+ if (this.processOffMain != null) { -+ this.processOffMain.setPriority(priority); -+ } -+ if (this.processOnMain != null) { -+ this.processOnMain.setPriority(priority); -+ } -+ } -+ -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ // can't lower I/O tasks, we don't know what they affect -+ this.loadDataFromDiskTask.raisePriority(priority); -+ if (this.processOffMain != null) { -+ this.processOffMain.raisePriority(priority); -+ } -+ if (this.processOnMain != null) { -+ this.processOnMain.raisePriority(priority); -+ } -+ } -+ -+ // returns whether scheduleNow() needs to be called -+ public boolean schedule(final boolean delay) { -+ if (this.stageAndReferenceCount.get() != STAGE_NOT_STARTED || -+ !this.stageAndReferenceCount.compareAndSet(STAGE_NOT_STARTED, (1L << 32) | STAGE_LOADING)) { -+ // try and increment reference count -+ int failures = 0; -+ for (long curr = this.stageAndReferenceCount.get();;) { -+ if ((curr & STAGE_MASK) == STAGE_CANCELLED || (curr & STAGE_MASK) == STAGE_COMPLETED) { -+ // cancelled or completed, nothing to do here -+ return false; -+ } -+ -+ if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, curr + (1L << 32)))) { -+ // successful -+ return false; -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ if (!delay) { -+ this.scheduleNow(); -+ return false; -+ } -+ return true; -+ } -+ -+ public void scheduleNow() { -+ this.loadDataFromDiskTask.schedule(); // will schedule the rest -+ } -+ -+ // assumes the current stage cannot be completed -+ // returns false if cancelled, returns true if can proceed -+ private boolean advanceStage(final long expect, final long to) { -+ int failures = 0; -+ for (long curr = this.stageAndReferenceCount.get();;) { -+ if ((curr & STAGE_MASK) != expect) { -+ // must be cancelled -+ return false; -+ } -+ -+ final long newVal = (curr & ~STAGE_MASK) | to; -+ if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, newVal))) { -+ return true; -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public boolean cancel() { -+ int failures = 0; -+ for (long curr = this.stageAndReferenceCount.get();;) { -+ if ((curr & STAGE_MASK) == STAGE_COMPLETED || (curr & STAGE_MASK) == STAGE_CANCELLED) { -+ return false; -+ } -+ -+ if ((curr & STAGE_MASK) == STAGE_NOT_STARTED || (curr & ~STAGE_MASK) == (1L << 32)) { -+ // no other references, so we can cancel -+ final long newVal = STAGE_CANCELLED; -+ if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, newVal))) { -+ this.loadDataFromDiskTask.cancel(); -+ if (this.processOffMain != null) { -+ this.processOffMain.cancel(); -+ } -+ if (this.processOnMain != null) { -+ this.processOnMain.cancel(); -+ } -+ this.onComplete(null); -+ return true; -+ } -+ } else { -+ if ((curr & ~STAGE_MASK) == (0L << 32)) { -+ throw new IllegalStateException("Reference count cannot be zero here"); -+ } -+ // just decrease the reference count -+ final long newVal = curr - (1L << 32); -+ if (curr == (curr = this.stageAndReferenceCount.compareAndExchange(curr, newVal))) { -+ return false; -+ } -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ protected final class DataLoadCallback implements BiConsumer { -+ -+ protected final ProcessOffMainTask offMainTask; -+ protected final ProcessOnMainTask onMainTask; -+ -+ public DataLoadCallback(final ProcessOffMainTask offMainTask, final ProcessOnMainTask onMainTask) { -+ this.offMainTask = offMainTask; -+ this.onMainTask = onMainTask; -+ } -+ -+ @Override -+ public void accept(final CompoundTag compoundTag, final Throwable throwable) { -+ if (GenericDataLoadTask.this.stageAndReferenceCount.get() == STAGE_CANCELLED) { -+ // don't try to schedule further -+ return; -+ } -+ -+ try { -+ if (compoundTag == CANCELLED_DATA) { -+ // cancelled, except this isn't possible -+ LOGGER.error("Data callback says cancelled, but stage does not?"); -+ return; -+ } -+ -+ // get off of the regionfile callback ASAP, no clue what locks are held right now... -+ if (GenericDataLoadTask.this.processOffMain != null) { -+ this.offMainTask.data = compoundTag; -+ this.offMainTask.throwable = throwable; -+ GenericDataLoadTask.this.processOffMain.queue(); -+ return; -+ } else { -+ // no off-main task, so go straight to main -+ this.onMainTask.data = (OnMain)compoundTag; -+ this.onMainTask.throwable = throwable; -+ GenericDataLoadTask.this.processOnMain.queue(); -+ } -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr2) { -+ LOGGER.error("Failed I/O callback for task: " + GenericDataLoadTask.this.toString(), thr2); -+ GenericDataLoadTask.this.scheduler.unrecoverableChunkSystemFailure( -+ GenericDataLoadTask.this.chunkX, GenericDataLoadTask.this.chunkZ, Map.of( -+ "Callback throwable", ChunkTaskScheduler.stringIfNull(throwable) -+ ), thr2); -+ } -+ } -+ } -+ -+ protected final class ProcessOffMainTask implements Runnable { -+ -+ protected CompoundTag data; -+ protected Throwable throwable; -+ protected final ProcessOnMainTask schedule; -+ -+ public ProcessOffMainTask(final ProcessOnMainTask schedule) { -+ this.schedule = schedule; -+ } -+ -+ @Override -+ public void run() { -+ if (!GenericDataLoadTask.this.advanceStage(STAGE_LOADING, this.schedule == null ? STAGE_COMPLETED : STAGE_PROCESSING)) { -+ // cancelled -+ return; -+ } -+ final TaskResult newData = GenericDataLoadTask.this.runOffMain(this.data, this.throwable); -+ -+ if (GenericDataLoadTask.this.stageAndReferenceCount.get() == STAGE_CANCELLED) { -+ // don't try to schedule further -+ return; -+ } -+ -+ if (this.schedule != null) { -+ final TaskResult syncComplete = GenericDataLoadTask.this.completeOnMainOffMain(newData.left, newData.right); -+ -+ if (syncComplete != null) { -+ if (GenericDataLoadTask.this.advanceStage(STAGE_PROCESSING, STAGE_COMPLETED)) { -+ GenericDataLoadTask.this.onComplete(syncComplete); -+ } // else: cancelled -+ return; -+ } -+ -+ this.schedule.data = newData.left; -+ this.schedule.throwable = newData.right; -+ -+ GenericDataLoadTask.this.processOnMain.queue(); -+ } else { -+ GenericDataLoadTask.this.onComplete((TaskResult)newData); -+ } -+ } -+ } -+ -+ protected final class ProcessOnMainTask implements Runnable { -+ -+ protected OnMain data; -+ protected Throwable throwable; -+ -+ @Override -+ public void run() { -+ if (!GenericDataLoadTask.this.advanceStage(STAGE_PROCESSING, STAGE_COMPLETED)) { -+ // cancelled -+ return; -+ } -+ final TaskResult result = GenericDataLoadTask.this.runOnMain(this.data, this.throwable); -+ -+ GenericDataLoadTask.this.onComplete(result); -+ } -+ } -+ -+ public static final class LoadDataFromDiskTask { -+ -+ protected volatile int priority; -+ protected static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(LoadDataFromDiskTask.class, "priority", int.class); -+ -+ protected static final int PRIORITY_EXECUTED = Integer.MIN_VALUE >>> 0; -+ protected static final int PRIORITY_LOAD_SCHEDULED = Integer.MIN_VALUE >>> 1; -+ protected static final int PRIORITY_UNLOAD_SCHEDULED = Integer.MIN_VALUE >>> 2; -+ -+ protected static final int PRIORITY_FLAGS = ~Character.MAX_VALUE; -+ -+ protected final int getPriorityVolatile() { -+ return (int)PRIORITY_HANDLE.getVolatile((LoadDataFromDiskTask)this); -+ } -+ -+ protected final int compareAndExchangePriorityVolatile(final int expect, final int update) { -+ return (int)PRIORITY_HANDLE.compareAndExchange((LoadDataFromDiskTask)this, (int)expect, (int)update); -+ } -+ -+ protected final int getAndOrPriorityVolatile(final int val) { -+ return (int)PRIORITY_HANDLE.getAndBitwiseOr((LoadDataFromDiskTask)this, (int)val); -+ } -+ -+ protected final void setPriorityPlain(final int val) { -+ PRIORITY_HANDLE.set((LoadDataFromDiskTask)this, (int)val); -+ } -+ -+ private final ServerLevel world; -+ private final int chunkX; -+ private final int chunkZ; -+ -+ private final RegionFileIOThread.RegionFileType type; -+ private Cancellable dataLoadTask; -+ private Cancellable dataUnloadCancellable; -+ private DelayedPrioritisedTask dataUnloadTask; -+ -+ private final BiConsumer onComplete; -+ -+ // onComplete should be caller sensitive, it may complete synchronously with schedule() - which does -+ // hold a priority lock. -+ public LoadDataFromDiskTask(final ServerLevel world, final int chunkX, final int chunkZ, -+ final RegionFileIOThread.RegionFileType type, -+ final BiConsumer onComplete, -+ final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.world = world; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.type = type; -+ this.onComplete = onComplete; -+ this.setPriorityPlain(priority.priority); -+ } -+ -+ private void complete(final CompoundTag data, final Throwable throwable) { -+ try { -+ this.onComplete.accept(data, throwable); -+ } catch (final Throwable thr2) { -+ this.world.chunkTaskScheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Completed throwable", ChunkTaskScheduler.stringIfNull(throwable), -+ "Regionfile type", ChunkTaskScheduler.stringIfNull(this.type) -+ ), thr2); -+ if (thr2 instanceof ThreadDeath) { -+ throw (ThreadDeath)thr2; -+ } -+ } -+ } -+ -+ protected boolean markExecuting() { -+ return (this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) == 0; -+ } -+ -+ protected boolean isMarkedExecuted() { -+ return (this.getPriorityVolatile() & PRIORITY_EXECUTED) != 0; -+ } -+ -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_EXECUTED) != 0) { -+ // cancelled or executed -+ return; -+ } -+ -+ if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { -+ RegionFileIOThread.lowerPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); -+ return; -+ } -+ -+ if ((curr & PRIORITY_UNLOAD_SCHEDULED) != 0) { -+ if (this.dataUnloadTask != null) { -+ this.dataUnloadTask.lowerPriority(priority); -+ } -+ // no return - we need to propagate priority -+ } -+ -+ if (!priority.isHigherPriority(curr & ~PRIORITY_FLAGS)) { -+ return; -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority | (curr & PRIORITY_FLAGS)))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_EXECUTED) != 0) { -+ // cancelled or executed -+ return; -+ } -+ -+ if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { -+ RegionFileIOThread.setPriority(this.world, this.chunkX, this.chunkZ, this.type, priority); -+ return; -+ } -+ -+ if ((curr & PRIORITY_UNLOAD_SCHEDULED) != 0) { -+ if (this.dataUnloadTask != null) { -+ this.dataUnloadTask.setPriority(priority); -+ } -+ // no return - we need to propagate priority -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority | (curr & PRIORITY_FLAGS)))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_EXECUTED) != 0) { -+ // cancelled or executed -+ return; -+ } -+ -+ if ((curr & PRIORITY_LOAD_SCHEDULED) != 0) { -+ RegionFileIOThread.raisePriority(this.world, this.chunkX, this.chunkZ, this.type, priority); -+ return; -+ } -+ -+ if ((curr & PRIORITY_UNLOAD_SCHEDULED) != 0) { -+ if (this.dataUnloadTask != null) { -+ this.dataUnloadTask.raisePriority(priority); -+ } -+ // no return - we need to propagate priority -+ } -+ -+ if (!priority.isLowerPriority(curr & ~PRIORITY_FLAGS)) { -+ return; -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority | (curr & PRIORITY_FLAGS)))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public void cancel() { -+ if ((this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) != 0) { -+ // cancelled or executed already -+ return; -+ } -+ -+ // OK if we miss the field read, the task cannot complete if the cancelled bit is set and -+ // the write to dataLoadTask will check for the cancelled bit -+ if (this.dataUnloadCancellable != null) { -+ this.dataUnloadCancellable.cancel(); -+ } -+ -+ if (this.dataLoadTask != null) { -+ this.dataLoadTask.cancel(); -+ } -+ -+ this.complete(CANCELLED_DATA, null); -+ } -+ -+ private final AtomicBoolean scheduled = new AtomicBoolean(); -+ -+ public void schedule() { -+ if (this.scheduled.getAndSet(true)) { -+ throw new IllegalStateException("schedule() called twice"); -+ } -+ int priority = this.getPriorityVolatile(); -+ -+ if ((priority & PRIORITY_EXECUTED) != 0) { -+ // cancelled -+ return; -+ } -+ -+ final BiConsumer consumer = (final CompoundTag data, final Throwable thr) -> { -+ // because cancelScheduled() cannot actually stop this task from executing in every case, we need -+ // to mark complete here to ensure we do not double complete -+ if (LoadDataFromDiskTask.this.markExecuting()) { -+ LoadDataFromDiskTask.this.complete(data, thr); -+ } // else: cancelled -+ }; -+ -+ final PrioritisedExecutor.Priority initialPriority = PrioritisedExecutor.Priority.getPriority(priority); -+ boolean scheduledUnload = false; -+ -+ final NewChunkHolder holder = this.world.chunkTaskScheduler.chunkHolderManager.getChunkHolder(this.chunkX, this.chunkZ); -+ if (holder != null) { -+ final BiConsumer unloadConsumer = (final CompoundTag data, final Throwable thr) -> { -+ if (data != null) { -+ consumer.accept(data, null); -+ } else { -+ // need to schedule task -+ LoadDataFromDiskTask.this.schedule(false, consumer, PrioritisedExecutor.Priority.getPriority(LoadDataFromDiskTask.this.getPriorityVolatile() & ~PRIORITY_FLAGS)); -+ } -+ }; -+ Cancellable unloadCancellable = null; -+ CompoundTag syncComplete = null; -+ final NewChunkHolder.UnloadTask unloadTask = holder.getUnloadTask(this.type); // can be null if no task exists -+ final Completable unloadCompletable = unloadTask == null ? null : unloadTask.completable(); -+ if (unloadCompletable != null) { -+ unloadCancellable = unloadCompletable.addAsynchronousWaiter(unloadConsumer); -+ if (unloadCancellable == null) { -+ syncComplete = unloadCompletable.getResult(); -+ } -+ } -+ -+ if (syncComplete != null) { -+ consumer.accept(syncComplete, null); -+ return; -+ } -+ -+ if (unloadCancellable != null) { -+ scheduledUnload = true; -+ this.dataUnloadCancellable = unloadCancellable; -+ this.dataUnloadTask = unloadTask.task(); -+ } -+ } -+ -+ this.schedule(scheduledUnload, consumer, initialPriority); -+ } -+ -+ private void schedule(final boolean scheduledUnload, final BiConsumer consumer, final PrioritisedExecutor.Priority initialPriority) { -+ int priority = this.getPriorityVolatile(); -+ -+ if ((priority & PRIORITY_EXECUTED) != 0) { -+ // cancelled -+ return; -+ } -+ -+ if (!scheduledUnload) { -+ this.dataLoadTask = RegionFileIOThread.loadDataAsync( -+ this.world, this.chunkX, this.chunkZ, this.type, consumer, -+ initialPriority.isHigherPriority(PrioritisedExecutor.Priority.NORMAL), initialPriority -+ ); -+ } -+ -+ int failures = 0; -+ for (;;) { -+ if (priority == (priority = this.compareAndExchangePriorityVolatile(priority, priority | (scheduledUnload ? PRIORITY_UNLOAD_SCHEDULED : PRIORITY_LOAD_SCHEDULED)))) { -+ return; -+ } -+ -+ if ((priority & PRIORITY_EXECUTED) != 0) { -+ // cancelled or executed -+ if (this.dataUnloadCancellable != null) { -+ this.dataUnloadCancellable.cancel(); -+ } -+ -+ if (this.dataLoadTask != null) { -+ this.dataLoadTask.cancel(); -+ } -+ return; -+ } -+ -+ if (scheduledUnload) { -+ if (this.dataUnloadTask != null) { -+ this.dataUnloadTask.setPriority(PrioritisedExecutor.Priority.getPriority(priority & ~PRIORITY_FLAGS)); -+ } -+ } else { -+ RegionFileIOThread.setPriority(this.world, this.chunkX, this.chunkZ, this.type, PrioritisedExecutor.Priority.getPriority(priority & ~PRIORITY_FLAGS)); -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ /* -+ private static final class LoadDataPriorityHolder extends PriorityHolder { -+ -+ protected final LoadDataFromDiskTask task; -+ -+ protected LoadDataPriorityHolder(final PrioritisedExecutor.Priority priority, final LoadDataFromDiskTask task) { -+ super(priority); -+ this.task = task; -+ } -+ -+ @Override -+ protected void cancelScheduled() { -+ final Cancellable dataLoadTask = this.task.dataLoadTask; -+ if (dataLoadTask != null) { -+ // OK if we miss the field read, the task cannot complete if the cancelled bit is set and -+ // the write to dataLoadTask will check for the cancelled bit -+ this.task.dataLoadTask.cancel(); -+ } -+ this.task.complete(CANCELLED_DATA, null); -+ } -+ -+ @Override -+ protected PrioritisedExecutor.Priority getScheduledPriority() { -+ final LoadDataFromDiskTask task = this.task; -+ return RegionFileIOThread.getPriority(task.world, task.chunkX, task.chunkZ, task.type); -+ } -+ -+ @Override -+ protected void scheduleTask(final PrioritisedExecutor.Priority priority) { -+ final LoadDataFromDiskTask task = this.task; -+ final BiConsumer consumer = (final CompoundTag data, final Throwable thr) -> { -+ // because cancelScheduled() cannot actually stop this task from executing in every case, we need -+ // to mark complete here to ensure we do not double complete -+ if (LoadDataPriorityHolder.this.markExecuting()) { -+ LoadDataPriorityHolder.this.task.complete(data, thr); -+ } // else: cancelled -+ }; -+ task.dataLoadTask = RegionFileIOThread.loadDataAsync( -+ task.world, task.chunkX, task.chunkZ, task.type, consumer, -+ priority.isHigherPriority(PrioritisedExecutor.Priority.NORMAL), priority -+ ); -+ if (this.isMarkedExecuted()) { -+ // if we are marked as completed, it could be: -+ // 1. we were cancelled -+ // 2. the consumer was completed -+ // in the 2nd case, cancel() does nothing -+ // in the 1st case, we ensure cancel() is called as it is possible for the cancelling thread -+ // to miss the field write here -+ task.dataLoadTask.cancel(); -+ } -+ } -+ -+ @Override -+ protected void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ final LoadDataFromDiskTask task = this.task; -+ RegionFileIOThread.lowerPriority(task.world, task.chunkX, task.chunkZ, task.type, priority); -+ } -+ -+ @Override -+ protected void setPriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ final LoadDataFromDiskTask task = this.task; -+ RegionFileIOThread.setPriority(task.world, task.chunkX, task.chunkZ, task.type, priority); -+ } -+ -+ @Override -+ protected void raisePriorityScheduled(final PrioritisedExecutor.Priority priority) { -+ final LoadDataFromDiskTask task = this.task; -+ RegionFileIOThread.raisePriority(task.world, task.chunkX, task.chunkZ, task.type, priority); -+ } -+ } -+ */ -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b66a7d4aab887309579154815a0d4abf9de506b0 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -@@ -0,0 +1,2106 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.completable.Completable; -+import ca.spottedleaf.concurrentutil.executor.Cancellable; -+import ca.spottedleaf.concurrentutil.executor.standard.DelayedPrioritisedTask; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.io.RegionFileIOThread; -+import io.papermc.paper.chunk.system.poi.PoiChunk; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.TickThread; -+import io.papermc.paper.util.WorldUtil; -+import io.papermc.paper.world.ChunkEntitySlices; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ChunkLevel; -+import net.minecraft.server.level.FullChunkStatus; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.ImposterProtoChunk; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.storage.ChunkSerializer; -+import net.minecraft.world.level.chunk.storage.EntityStorage; -+import org.slf4j.Logger; -+import java.lang.invoke.VarHandle; -+import java.util.ArrayList; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Map; -+import java.util.Objects; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.function.Consumer; -+ -+public final class NewChunkHolder { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ public static final Thread.UncaughtExceptionHandler CHUNKSYSTEM_UNCAUGHT_EXCEPTION_HANDLER = new Thread.UncaughtExceptionHandler() { -+ @Override -+ public void uncaughtException(final Thread thread, final Throwable throwable) { -+ if (!(throwable instanceof ThreadDeath)) { -+ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); -+ } -+ } -+ }; -+ -+ public final ServerLevel world; -+ public final int chunkX; -+ public final int chunkZ; -+ -+ public final ChunkTaskScheduler scheduler; -+ -+ // load/unload state -+ -+ // chunk data state -+ -+ private ChunkEntitySlices entityChunk; -+ // entity chunk that is loaded, but not yet deserialized -+ private CompoundTag pendingEntityChunk; -+ -+ ChunkEntitySlices loadInEntityChunk(final boolean transientChunk) { -+ TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Cannot sync load entity data off-main"); -+ final CompoundTag entityChunk; -+ final ChunkEntitySlices ret; -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ if (this.entityChunk != null && (transientChunk || !this.entityChunk.isTransient())) { -+ return this.entityChunk; -+ } -+ final CompoundTag pendingEntityChunk = this.pendingEntityChunk; -+ if (!transientChunk && pendingEntityChunk == null) { -+ throw new IllegalStateException("Must load entity data from disk before loading in the entity chunk!"); -+ } -+ -+ if (this.entityChunk == null) { -+ ret = this.entityChunk = new ChunkEntitySlices( -+ this.world, this.chunkX, this.chunkZ, this.getChunkStatus(), -+ WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world) -+ ); -+ -+ ret.setTransient(transientChunk); -+ -+ this.world.getEntityLookup().entitySectionLoad(this.chunkX, this.chunkZ, ret); -+ } else { -+ // transientChunk = false here -+ ret = this.entityChunk; -+ this.entityChunk.setTransient(false); -+ } -+ -+ if (!transientChunk) { -+ this.pendingEntityChunk = null; -+ entityChunk = pendingEntityChunk == EMPTY_ENTITY_CHUNK ? null : pendingEntityChunk; -+ } else { -+ entityChunk = null; -+ } -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ -+ if (!transientChunk) { -+ if (entityChunk != null) { -+ final List entities = EntityStorage.readEntities(this.world, entityChunk); -+ -+ this.world.getEntityLookup().addEntityChunkEntities(entities, new ChunkPos(this.chunkX, this.chunkZ)); -+ } -+ } -+ -+ return ret; -+ } -+ -+ // needed to distinguish whether the entity chunk has been read from disk but is empty or whether it has _not_ -+ // been read from disk -+ private static final CompoundTag EMPTY_ENTITY_CHUNK = new CompoundTag(); -+ -+ private ChunkLoadTask.EntityDataLoadTask entityDataLoadTask; -+ // note: if entityDataLoadTask is cancelled, but on its completion entityDataLoadTaskWaiters.size() != 0, -+ // then the task is rescheduled -+ private List entityDataLoadTaskWaiters; -+ -+ public ChunkLoadTask.EntityDataLoadTask getEntityDataLoadTask() { -+ return this.entityDataLoadTask; -+ } -+ -+ // must hold schedule lock for the two below functions -+ -+ // returns only if the data has been loaded from disk, DOES NOT relate to whether it has been deserialized -+ // or added into the world (or even into entityChunk) -+ public boolean isEntityChunkNBTLoaded() { -+ return (this.entityChunk != null && !this.entityChunk.isTransient()) || this.pendingEntityChunk != null; -+ } -+ -+ private void completeEntityLoad(final GenericDataLoadTask.TaskResult result) { -+ final List completeWaiters; -+ ChunkLoadTask.EntityDataLoadTask entityDataLoadTask = null; -+ boolean scheduleEntityTask = false; -+ ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ final List waiters = this.entityDataLoadTaskWaiters; -+ this.entityDataLoadTask = null; -+ if (result != null) { -+ this.entityDataLoadTaskWaiters = null; -+ this.pendingEntityChunk = result.left() == null ? EMPTY_ENTITY_CHUNK : result.left(); -+ if (result.right() != null) { -+ LOGGER.error("Unhandled entity data load exception, data data will be lost: ", result.right()); -+ } -+ -+ for (final GenericDataLoadTaskCallback callback : waiters) { -+ callback.markCompleted(); -+ } -+ -+ completeWaiters = waiters; -+ } else { -+ // cancelled -+ completeWaiters = null; -+ -+ // need to re-schedule? -+ if (waiters.isEmpty()) { -+ this.entityDataLoadTaskWaiters = null; -+ // no tasks to schedule _for_ -+ } else { -+ entityDataLoadTask = this.entityDataLoadTask = new ChunkLoadTask.EntityDataLoadTask( -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority() -+ ); -+ entityDataLoadTask.addCallback(this::completeEntityLoad); -+ // need one schedule() per waiter -+ for (final GenericDataLoadTaskCallback callback : waiters) { -+ scheduleEntityTask |= entityDataLoadTask.schedule(true); -+ } -+ } -+ } -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ -+ if (scheduleEntityTask) { -+ entityDataLoadTask.scheduleNow(); -+ } -+ -+ // avoid holding the scheduling lock while completing -+ if (completeWaiters != null) { -+ for (final GenericDataLoadTaskCallback callback : completeWaiters) { -+ callback.acceptCompleted(result); -+ } -+ } -+ -+ schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ this.checkUnload(); -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } -+ -+ // note: it is guaranteed that the consumer cannot be called for the entirety that the schedule lock is held -+ // however, when the consumer is invoked, it will hold the schedule lock -+ public GenericDataLoadTaskCallback getOrLoadEntityData(final Consumer> consumer) { -+ if (this.isEntityChunkNBTLoaded()) { -+ throw new IllegalStateException("Cannot load entity data, it is already loaded"); -+ } -+ // why not just acquire the lock? because the caller NEEDS to call isEntityChunkNBTLoaded before this! -+ if (!this.scheduler.schedulingLockArea.isHeldByCurrentThread(this.chunkX, this.chunkZ)) { -+ throw new IllegalStateException("Must hold scheduling lock"); -+ } -+ -+ final GenericDataLoadTaskCallback ret = new EntityDataLoadTaskCallback((Consumer)consumer, this); -+ -+ if (this.entityDataLoadTask == null) { -+ this.entityDataLoadTask = new ChunkLoadTask.EntityDataLoadTask( -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority() -+ ); -+ this.entityDataLoadTask.addCallback(this::completeEntityLoad); -+ this.entityDataLoadTaskWaiters = new ArrayList<>(); -+ } -+ this.entityDataLoadTaskWaiters.add(ret); -+ if (this.entityDataLoadTask.schedule(true)) { -+ ret.schedule = this.entityDataLoadTask; -+ } -+ this.checkUnload(); -+ -+ return ret; -+ } -+ -+ private static final class EntityDataLoadTaskCallback extends GenericDataLoadTaskCallback { -+ -+ public EntityDataLoadTaskCallback(final Consumer> consumer, final NewChunkHolder chunkHolder) { -+ super(consumer, chunkHolder); -+ } -+ -+ @Override -+ void internalCancel() { -+ this.chunkHolder.entityDataLoadTaskWaiters.remove(this); -+ this.chunkHolder.entityDataLoadTask.cancel(); -+ } -+ } -+ -+ private PoiChunk poiChunk; -+ -+ private ChunkLoadTask.PoiDataLoadTask poiDataLoadTask; -+ // note: if entityDataLoadTask is cancelled, but on its completion entityDataLoadTaskWaiters.size() != 0, -+ // then the task is rescheduled -+ private List poiDataLoadTaskWaiters; -+ -+ public ChunkLoadTask.PoiDataLoadTask getPoiDataLoadTask() { -+ return this.poiDataLoadTask; -+ } -+ -+ // must hold schedule lock for the two below functions -+ -+ public boolean isPoiChunkLoaded() { -+ return this.poiChunk != null; -+ } -+ -+ private void completePoiLoad(final GenericDataLoadTask.TaskResult result) { -+ final List completeWaiters; -+ ChunkLoadTask.PoiDataLoadTask poiDataLoadTask = null; -+ boolean schedulePoiTask = false; -+ ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ final List waiters = this.poiDataLoadTaskWaiters; -+ this.poiDataLoadTask = null; -+ if (result != null) { -+ this.poiDataLoadTaskWaiters = null; -+ this.poiChunk = result.left(); -+ if (result.right() != null) { -+ LOGGER.error("Unhandled poi load exception, poi data will be lost: ", result.right()); -+ } -+ -+ for (final GenericDataLoadTaskCallback callback : waiters) { -+ callback.markCompleted(); -+ } -+ -+ completeWaiters = waiters; -+ } else { -+ // cancelled -+ completeWaiters = null; -+ -+ // need to re-schedule? -+ if (waiters.isEmpty()) { -+ this.poiDataLoadTaskWaiters = null; -+ // no tasks to schedule _for_ -+ } else { -+ poiDataLoadTask = this.poiDataLoadTask = new ChunkLoadTask.PoiDataLoadTask( -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority() -+ ); -+ poiDataLoadTask.addCallback(this::completePoiLoad); -+ // need one schedule() per waiter -+ for (final GenericDataLoadTaskCallback callback : waiters) { -+ schedulePoiTask |= poiDataLoadTask.schedule(true); -+ } -+ } -+ } -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ -+ if (schedulePoiTask) { -+ poiDataLoadTask.scheduleNow(); -+ } -+ -+ // avoid holding the scheduling lock while completing -+ if (completeWaiters != null) { -+ for (final GenericDataLoadTaskCallback callback : completeWaiters) { -+ callback.acceptCompleted(result); -+ } -+ } -+ schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ this.checkUnload(); -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } -+ -+ // note: it is guaranteed that the consumer cannot be called for the entirety that the schedule lock is held -+ // however, when the consumer is invoked, it will hold the schedule lock -+ public GenericDataLoadTaskCallback getOrLoadPoiData(final Consumer> consumer) { -+ if (this.isPoiChunkLoaded()) { -+ throw new IllegalStateException("Cannot load poi data, it is already loaded"); -+ } -+ // why not just acquire the lock? because the caller NEEDS to call isPoiChunkLoaded before this! -+ if (!this.scheduler.schedulingLockArea.isHeldByCurrentThread(this.chunkX, this.chunkZ)) { -+ throw new IllegalStateException("Must hold scheduling lock"); -+ } -+ -+ final GenericDataLoadTaskCallback ret = new PoiDataLoadTaskCallback((Consumer)consumer, this); -+ -+ if (this.poiDataLoadTask == null) { -+ this.poiDataLoadTask = new ChunkLoadTask.PoiDataLoadTask( -+ this.scheduler, this.world, this.chunkX, this.chunkZ, this.getEffectivePriority() -+ ); -+ this.poiDataLoadTask.addCallback(this::completePoiLoad); -+ this.poiDataLoadTaskWaiters = new ArrayList<>(); -+ } -+ this.poiDataLoadTaskWaiters.add(ret); -+ if (this.poiDataLoadTask.schedule(true)) { -+ ret.schedule = this.poiDataLoadTask; -+ } -+ this.checkUnload(); -+ -+ return ret; -+ } -+ -+ private static final class PoiDataLoadTaskCallback extends GenericDataLoadTaskCallback { -+ -+ public PoiDataLoadTaskCallback(final Consumer> consumer, final NewChunkHolder chunkHolder) { -+ super(consumer, chunkHolder); -+ } -+ -+ @Override -+ void internalCancel() { -+ this.chunkHolder.poiDataLoadTaskWaiters.remove(this); -+ this.chunkHolder.poiDataLoadTask.cancel(); -+ } -+ } -+ -+ public static abstract class GenericDataLoadTaskCallback implements Cancellable { -+ -+ protected final Consumer> consumer; -+ protected final NewChunkHolder chunkHolder; -+ protected boolean completed; -+ protected GenericDataLoadTask schedule; -+ protected final AtomicBoolean scheduled = new AtomicBoolean(); -+ -+ public GenericDataLoadTaskCallback(final Consumer> consumer, -+ final NewChunkHolder chunkHolder) { -+ this.consumer = consumer; -+ this.chunkHolder = chunkHolder; -+ } -+ -+ public void schedule() { -+ if (this.scheduled.getAndSet(true)) { -+ throw new IllegalStateException("Double calling schedule()"); -+ } -+ if (this.schedule != null) { -+ this.schedule.scheduleNow(); -+ this.schedule = null; -+ } -+ } -+ -+ boolean isCompleted() { -+ return this.completed; -+ } -+ -+ // must hold scheduling lock -+ private boolean setCompleted() { -+ if (this.completed) { -+ return false; -+ } -+ return this.completed = true; -+ } -+ -+ // must hold scheduling lock -+ void markCompleted() { -+ if (this.completed) { -+ throw new IllegalStateException("May not be completed here"); -+ } -+ this.completed = true; -+ } -+ -+ void acceptCompleted(final GenericDataLoadTask.TaskResult result) { -+ if (result != null) { -+ if (this.completed) { -+ this.consumer.accept(result); -+ } else { -+ throw new IllegalStateException("Cannot be uncompleted at this point"); -+ } -+ } else { -+ throw new NullPointerException("Result cannot be null (cancelled)"); -+ } -+ } -+ -+ // holds scheduling lock -+ abstract void internalCancel(); -+ -+ @Override -+ public boolean cancel() { -+ final NewChunkHolder holder = this.chunkHolder; // Folia - use area based lock to reduce contention -+ final ReentrantAreaLock.Node schedulingLock = holder.scheduler.schedulingLockArea.lock(holder.chunkX, holder.chunkZ); -+ try { -+ if (!this.completed) { -+ this.completed = true; -+ this.internalCancel(); -+ return true; -+ } -+ return false; -+ } finally { -+ holder.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } -+ } -+ -+ private ChunkAccess currentChunk; -+ -+ // generation status state -+ -+ /** -+ * Current status the chunk has been brought up to by the chunk system. null indicates no work at all -+ */ -+ private ChunkStatus currentGenStatus; -+ -+ // This allows unsynchronised access to the chunk and last gen status -+ private volatile ChunkCompletion lastChunkCompletion; -+ -+ public ChunkCompletion getLastChunkCompletion() { -+ return this.lastChunkCompletion; -+ } -+ -+ public static final record ChunkCompletion(ChunkAccess chunk, ChunkStatus genStatus) {}; -+ -+ /** -+ * The target final chunk status the chunk system will bring the chunk to. -+ */ -+ private ChunkStatus requestedGenStatus; -+ -+ private ChunkProgressionTask generationTask; -+ private ChunkStatus generationTaskStatus; -+ -+ /** -+ * contains the neighbours that this chunk generation is blocking on -+ */ -+ protected final ReferenceLinkedOpenHashSet neighboursBlockingGenTask = new ReferenceLinkedOpenHashSet<>(4); -+ -+ /** -+ * map of ChunkHolder -> Required Status for this chunk -+ */ -+ protected final Reference2ObjectLinkedOpenHashMap neighboursWaitingForUs = new Reference2ObjectLinkedOpenHashMap<>(); -+ -+ public void addGenerationBlockingNeighbour(final NewChunkHolder neighbour) { -+ this.neighboursBlockingGenTask.add(neighbour); -+ } -+ -+ public void addWaitingNeighbour(final NewChunkHolder neighbour, final ChunkStatus requiredStatus) { -+ final boolean wasEmpty = this.neighboursWaitingForUs.isEmpty(); -+ this.neighboursWaitingForUs.put(neighbour, requiredStatus); -+ if (wasEmpty) { -+ this.checkUnload(); -+ } -+ } -+ -+ // priority state -+ -+ // the target priority for this chunk to generate at -+ // TODO this will screw over scheduling at lower priorities to neighbours, fix -+ private PrioritisedExecutor.Priority priority = PrioritisedExecutor.Priority.NORMAL; -+ private boolean priorityLocked; -+ -+ // the priority neighbouring chunks have requested this chunk generate at -+ private PrioritisedExecutor.Priority neighbourRequestedPriority = PrioritisedExecutor.Priority.IDLE; -+ -+ public PrioritisedExecutor.Priority getEffectivePriority() { -+ return PrioritisedExecutor.Priority.max(this.priority, this.neighbourRequestedPriority); -+ } -+ -+ protected void recalculateNeighbourRequestedPriority() { -+ if (this.neighboursWaitingForUs.isEmpty()) { -+ this.neighbourRequestedPriority = PrioritisedExecutor.Priority.IDLE; -+ return; -+ } -+ -+ PrioritisedExecutor.Priority max = PrioritisedExecutor.Priority.IDLE; -+ -+ for (final NewChunkHolder holder : this.neighboursWaitingForUs.keySet()) { -+ final PrioritisedExecutor.Priority neighbourPriority = holder.getEffectivePriority(); -+ if (neighbourPriority.isHigherPriority(max)) { -+ max = neighbourPriority; -+ } -+ } -+ -+ final PrioritisedExecutor.Priority current = this.getEffectivePriority(); -+ this.neighbourRequestedPriority = max; -+ final PrioritisedExecutor.Priority next = this.getEffectivePriority(); -+ -+ if (current == next) { -+ return; -+ } -+ -+ // our effective priority has changed, so change our task -+ if (this.generationTask != null) { -+ this.generationTask.setPriority(next); -+ } -+ -+ // now propagate this to our neighbours -+ this.recalculateNeighbourPriorities(); -+ } -+ -+ public void recalculateNeighbourPriorities() { -+ for (final NewChunkHolder holder : this.neighboursBlockingGenTask) { -+ holder.recalculateNeighbourRequestedPriority(); -+ } -+ } -+ -+ // must hold scheduling lock -+ public void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (this.priority != null && this.priority.isHigherOrEqualPriority(priority)) { -+ return; -+ } -+ this.setPriority(priority); -+ } -+ -+ private void lockPriority() { -+ this.priority = PrioritisedExecutor.Priority.NORMAL; -+ this.priorityLocked = true; -+ } -+ -+ // must hold scheduling lock -+ public void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (this.priorityLocked) { -+ return; -+ } -+ final PrioritisedExecutor.Priority old = this.getEffectivePriority(); -+ this.priority = priority; -+ final PrioritisedExecutor.Priority newPriority = this.getEffectivePriority(); -+ -+ if (old != newPriority) { -+ if (this.generationTask != null) { -+ this.generationTask.setPriority(newPriority); -+ } -+ } -+ -+ this.recalculateNeighbourPriorities(); -+ } -+ -+ // must hold scheduling lock -+ public void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (this.priority != null && this.priority.isLowerOrEqualPriority(priority)) { -+ return; -+ } -+ this.setPriority(priority); -+ } -+ -+ // error handling state -+ private ChunkStatus failedGenStatus; -+ private Throwable genTaskException; -+ private Thread genTaskFailedThread; -+ -+ private boolean failedLightUpdate; -+ -+ public void failedLightUpdate() { -+ this.failedLightUpdate = true; -+ } -+ -+ public boolean hasFailedGeneration() { -+ return this.genTaskException != null; -+ } -+ -+ // ticket level state -+ private int oldTicketLevel = ChunkLevel.MAX_LEVEL + 1; -+ private int currentTicketLevel = ChunkLevel.MAX_LEVEL + 1; -+ -+ public int getTicketLevel() { -+ return this.currentTicketLevel; -+ } -+ -+ public final ChunkHolder vanillaChunkHolder; -+ -+ public NewChunkHolder(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkTaskScheduler scheduler) { -+ this.world = world; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.scheduler = scheduler; -+ this.vanillaChunkHolder = new ChunkHolder(new ChunkPos(chunkX, chunkZ), world, world.getLightEngine(), world.chunkSource.chunkMap, this); -+ } -+ -+ protected ImposterProtoChunk wrappedChunkForNeighbour; -+ -+ // holds scheduling lock -+ public ChunkAccess getChunkForNeighbourAccess() { -+ // Vanilla overrides the status futures with an imposter chunk to prevent writes to full chunks -+ // But we don't store per-status futures, so we need this hack -+ if (this.wrappedChunkForNeighbour != null) { -+ return this.wrappedChunkForNeighbour; -+ } -+ final ChunkAccess ret = this.currentChunk; -+ return ret instanceof LevelChunk fullChunk ? this.wrappedChunkForNeighbour = new ImposterProtoChunk(fullChunk, false) : ret; -+ } -+ -+ public ChunkAccess getCurrentChunk() { -+ return this.currentChunk; -+ } -+ -+ int getCurrentTicketLevel() { -+ return this.currentTicketLevel; -+ } -+ -+ void updateTicketLevel(final int toLevel) { -+ this.currentTicketLevel = toLevel; -+ } -+ -+ private int totalNeighboursUsingThisChunk = 0; -+ -+ // holds schedule lock -+ public void addNeighbourUsingChunk() { -+ final int now = ++this.totalNeighboursUsingThisChunk; -+ -+ if (now == 1) { -+ this.checkUnload(); -+ } -+ } -+ -+ // holds schedule lock -+ public void removeNeighbourUsingChunk() { -+ final int now = --this.totalNeighboursUsingThisChunk; -+ -+ if (now == 0) { -+ this.checkUnload(); -+ } -+ -+ if (now < 0) { -+ throw new IllegalStateException("Neighbours using this chunk cannot be negative"); -+ } -+ } -+ -+ // must hold scheduling lock -+ // returns string reason for why chunk should remain loaded, null otherwise -+ public final String isSafeToUnload() { -+ // is ticket level below threshold? -+ if (this.oldTicketLevel <= ChunkHolderManager.MAX_TICKET_LEVEL) { -+ return "ticket_level"; -+ } -+ -+ // are we being used by another chunk for generation? -+ if (this.totalNeighboursUsingThisChunk != 0) { -+ return "neighbours_generating"; -+ } -+ -+ // are we going to be used by another chunk for generation? -+ if (!this.neighboursWaitingForUs.isEmpty()) { -+ return "neighbours_waiting"; -+ } -+ -+ // chunk must be marked inaccessible (i.e unloaded to plugins) -+ if (this.getChunkStatus() != FullChunkStatus.INACCESSIBLE) { -+ return "fullchunkstatus"; -+ } -+ -+ // are we currently generating anything, or have requested generation? -+ if (this.generationTask != null) { -+ return "generating"; -+ } -+ if (this.requestedGenStatus != null) { -+ return "requested_generation"; -+ } -+ -+ // entity data requested? -+ if (this.entityDataLoadTask != null) { -+ return "entity_data_requested"; -+ } -+ -+ // poi data requested? -+ if (this.poiDataLoadTask != null) { -+ return "poi_data_requested"; -+ } -+ -+ // are we pending serialization? -+ if (this.entityDataUnload != null) { -+ return "entity_serialization"; -+ } -+ if (this.poiDataUnload != null) { -+ return "poi_serialization"; -+ } -+ if (this.chunkDataUnload != null) { -+ return "chunk_serialization"; -+ } -+ -+ // Note: light tasks do not need a check, as they add a ticket. -+ -+ // nothing is using this chunk, so it should be unloaded -+ return null; -+ } -+ -+ /** Unloaded from chunk map */ -+ boolean killed; -+ -+ // must hold scheduling lock -+ private void checkUnload() { -+ if (this.killed) { -+ return; -+ } -+ if (this.isSafeToUnload() == null) { -+ // ensure in unload queue -+ this.scheduler.chunkHolderManager.unloadQueue.addChunk(this.chunkX, this.chunkZ); -+ } else { -+ // ensure not in unload queue -+ this.scheduler.chunkHolderManager.unloadQueue.removeChunk(this.chunkX, this.chunkZ); -+ } -+ } -+ -+ static final record UnloadState(NewChunkHolder holder, ChunkAccess chunk, ChunkEntitySlices entityChunk, PoiChunk poiChunk) {}; -+ -+ // note: these are completed with null to indicate that no write occurred -+ // they are also completed with null to indicate a null write occurred -+ private UnloadTask chunkDataUnload; -+ private UnloadTask entityDataUnload; -+ private UnloadTask poiDataUnload; -+ -+ public static final record UnloadTask(Completable completable, DelayedPrioritisedTask task) {} -+ -+ public UnloadTask getUnloadTask(final RegionFileIOThread.RegionFileType type) { -+ switch (type) { -+ case CHUNK_DATA: -+ return this.chunkDataUnload; -+ case ENTITY_DATA: -+ return this.entityDataUnload; -+ case POI_DATA: -+ return this.poiDataUnload; -+ default: -+ throw new IllegalStateException("Unknown regionfile type " + type); -+ } -+ } -+ -+ private UnloadState unloadState; -+ -+ // holds schedule lock -+ UnloadState unloadStage1() { -+ // because we hold the scheduling lock, we cannot actually unload anything -+ // so we need to null this chunk's state -+ ChunkAccess chunk = this.currentChunk; -+ ChunkEntitySlices entityChunk = this.entityChunk; -+ PoiChunk poiChunk = this.poiChunk; -+ // chunk state -+ this.currentChunk = null; -+ this.currentGenStatus = null; -+ this.wrappedChunkForNeighbour = null; -+ this.lastChunkCompletion = null; -+ // entity chunk state -+ this.entityChunk = null; -+ this.pendingEntityChunk = null; -+ -+ // poi chunk state -+ this.poiChunk = null; -+ -+ // priority state -+ this.priorityLocked = false; -+ -+ if (chunk != null) { -+ this.chunkDataUnload = new UnloadTask(new Completable<>(), new DelayedPrioritisedTask(PrioritisedExecutor.Priority.NORMAL)); -+ } -+ if (poiChunk != null) { -+ this.poiDataUnload = new UnloadTask(new Completable<>(), null); -+ } -+ if (entityChunk != null) { -+ this.entityDataUnload = new UnloadTask(new Completable<>(), null); -+ } -+ -+ return this.unloadState = (chunk != null || entityChunk != null || poiChunk != null) ? new UnloadState(this, chunk, entityChunk, poiChunk) : null; -+ } -+ -+ // data is null if failed or does not need to be saved -+ void completeAsyncChunkDataSave(final CompoundTag data) { -+ if (data != null) { -+ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, data, RegionFileIOThread.RegionFileType.CHUNK_DATA); -+ } -+ this.chunkDataUnload.completable().complete(data); -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ // can only write to these fields while holding the schedule lock -+ this.chunkDataUnload = null; -+ this.checkUnload(); -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } -+ -+ void unloadStage2(final UnloadState state) { -+ this.unloadState = null; -+ final ChunkAccess chunk = state.chunk(); -+ final ChunkEntitySlices entityChunk = state.entityChunk(); -+ final PoiChunk poiChunk = state.poiChunk(); -+ -+ final boolean shouldLevelChunkNotSave = (chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave); -+ -+ // unload chunk data -+ if (chunk != null) { -+ if (chunk instanceof LevelChunk levelChunk) { -+ levelChunk.setLoaded(false); -+ } -+ -+ if (!shouldLevelChunkNotSave) { -+ this.saveChunk(chunk, true); -+ } else { -+ this.completeAsyncChunkDataSave(null); -+ } -+ -+ if (chunk instanceof LevelChunk levelChunk) { -+ this.world.unload(levelChunk); -+ } -+ } -+ -+ // unload entity data -+ if (entityChunk != null) { -+ this.saveEntities(entityChunk, true); -+ // yes this is a hack to pass the compound tag through... -+ final CompoundTag lastEntityUnload = this.lastEntityUnload; -+ this.lastEntityUnload = null; -+ -+ if (entityChunk.unload()) { -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ entityChunk.setTransient(true); -+ this.entityChunk = entityChunk; -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } else { -+ this.world.getEntityLookup().entitySectionUnload(this.chunkX, this.chunkZ); -+ } -+ // we need to delay the callback until after determining transience, otherwise a potential loader could -+ // set entityChunk before we do -+ this.entityDataUnload.completable().complete(lastEntityUnload); -+ } -+ -+ // unload poi data -+ if (poiChunk != null) { -+ if (poiChunk.isDirty() && !shouldLevelChunkNotSave) { -+ this.savePOI(poiChunk, true); -+ } else { -+ this.poiDataUnload.completable().complete(null); -+ } -+ -+ if (poiChunk.isLoaded()) { -+ this.world.getPoiManager().onUnload(CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ)); -+ } -+ } -+ } -+ -+ boolean unloadStage3() { -+ // can only write to these while holding the schedule lock, and we instantly complete them in stage2 -+ this.poiDataUnload = null; -+ this.entityDataUnload = null; -+ -+ // we need to check if anything has been loaded in the meantime (or if we have transient entities) -+ if (this.entityChunk != null || this.poiChunk != null || this.currentChunk != null) { -+ return false; -+ } -+ -+ return this.isSafeToUnload() == null; -+ } -+ -+ private void cancelGenTask() { -+ if (this.generationTask != null) { -+ this.generationTask.cancel(); -+ } else { -+ // otherwise, we are blocking on neighbours, so remove them -+ if (!this.neighboursBlockingGenTask.isEmpty()) { -+ for (final NewChunkHolder neighbour : this.neighboursBlockingGenTask) { -+ if (neighbour.neighboursWaitingForUs.remove(this) == null) { -+ throw new IllegalStateException("Corrupt state"); -+ } -+ if (neighbour.neighboursWaitingForUs.isEmpty()) { -+ neighbour.checkUnload(); -+ } -+ } -+ this.neighboursBlockingGenTask.clear(); -+ this.checkUnload(); -+ } -+ } -+ } -+ -+ // holds: ticket level update lock -+ // holds: schedule lock -+ public void processTicketLevelUpdate(final List scheduledTasks, final List changedLoadStatus) { -+ final int oldLevel = this.oldTicketLevel; -+ final int newLevel = this.currentTicketLevel; -+ -+ if (oldLevel == newLevel) { -+ return; -+ } -+ -+ this.oldTicketLevel = newLevel; -+ -+ final FullChunkStatus oldState = ChunkLevel.fullStatus(oldLevel); -+ final FullChunkStatus newState = ChunkLevel.fullStatus(newLevel); -+ final boolean oldUnloaded = oldLevel > ChunkHolderManager.MAX_TICKET_LEVEL; -+ final boolean newUnloaded = newLevel > ChunkHolderManager.MAX_TICKET_LEVEL; -+ -+ final ChunkStatus maxGenerationStatusOld = ChunkLevel.generationStatus(oldLevel); -+ final ChunkStatus maxGenerationStatusNew = ChunkLevel.generationStatus(newLevel); -+ -+ // check for cancellations from downgrading ticket level -+ if (this.requestedGenStatus != null && !newState.isOrAfter(FullChunkStatus.FULL) && newLevel > oldLevel) { -+ // note: cancel() may invoke onChunkGenComplete synchronously here -+ if (newUnloaded) { -+ // need to cancel all tasks -+ // note: requested status must be set to null here before cancellation, to indicate to the -+ // completion logic that we do not want rescheduling to occur -+ this.requestedGenStatus = null; -+ this.cancelGenTask(); -+ } else { -+ final ChunkStatus toCancel = maxGenerationStatusNew.getNextStatus(); -+ final ChunkStatus currentRequestedStatus = this.requestedGenStatus; -+ -+ if (currentRequestedStatus.isOrAfter(toCancel)) { -+ // we do have to cancel something here -+ // clamp requested status to the maximum -+ if (this.currentGenStatus != null && this.currentGenStatus.isOrAfter(maxGenerationStatusNew)) { -+ // already generated to status, so we must cancel -+ this.requestedGenStatus = null; -+ this.cancelGenTask(); -+ } else { -+ // not generated to status, so we may have to cancel -+ // note: gen task is always 1 status above current gen status if not null -+ this.requestedGenStatus = maxGenerationStatusNew; -+ if (this.generationTaskStatus != null && this.generationTaskStatus.isOrAfter(toCancel)) { -+ // TOOD is this even possible? i don't think so -+ throw new IllegalStateException("?????"); -+ } -+ } -+ } -+ } -+ } -+ -+ if (newState != oldState) { -+ if (newState.isOrAfter(oldState)) { -+ // status upgrade -+ if (!oldState.isOrAfter(FullChunkStatus.FULL) && newState.isOrAfter(FullChunkStatus.FULL)) { -+ // may need to schedule full load -+ if (this.currentGenStatus != ChunkStatus.FULL) { -+ if (this.requestedGenStatus != null) { -+ this.requestedGenStatus = ChunkStatus.FULL; -+ } else { -+ this.scheduler.schedule( -+ this.chunkX, this.chunkZ, ChunkStatus.FULL, this, scheduledTasks -+ ); -+ } -+ } else { -+ // now we are fully loaded -+ this.queueBorderFullStatus(true, changedLoadStatus); -+ } -+ } -+ } else { -+ // status downgrade -+ if (!newState.isOrAfter(FullChunkStatus.ENTITY_TICKING) && oldState.isOrAfter(FullChunkStatus.ENTITY_TICKING)) { -+ this.completeFullStatusConsumers(FullChunkStatus.ENTITY_TICKING, null); -+ } -+ -+ if (!newState.isOrAfter(FullChunkStatus.BLOCK_TICKING) && oldState.isOrAfter(FullChunkStatus.BLOCK_TICKING)) { -+ this.completeFullStatusConsumers(FullChunkStatus.BLOCK_TICKING, null); -+ } -+ -+ if (!newState.isOrAfter(FullChunkStatus.FULL) && oldState.isOrAfter(FullChunkStatus.FULL)) { -+ this.completeFullStatusConsumers(FullChunkStatus.FULL, null); -+ } -+ } -+ } -+ -+ if (oldState != newState) { -+ if (this.onTicketUpdate(oldState, newState)) { -+ changedLoadStatus.add(this); -+ } -+ } -+ -+ if (oldUnloaded != newUnloaded) { -+ this.checkUnload(); -+ } -+ } -+ -+ /* -+ For full chunks, vanilla just loads chunks around it up to FEATURES, 1 radius -+ -+ For ticking chunks, it updates the persistent entity manager (soon to be completely nuked by EntitySliceManager, which -+ will also need to be updated but with far less implications) -+ It also shoves the scheduled block ticks into the tick scheduler -+ -+ For entity ticking chunks, updates the entity manager (see above) -+ */ -+ -+ static final int NEIGHBOUR_RADIUS = 2; -+ private long fullNeighbourChunksLoadedBitset; -+ -+ private static int getFullNeighbourIndex(final int relativeX, final int relativeZ) { -+ // index = (relativeX + NEIGHBOUR_CACHE_RADIUS) + (relativeZ + NEIGHBOUR_CACHE_RADIUS) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1) -+ // optimised variant of the above by moving some of the ops to compile time -+ return relativeX + (relativeZ * (NEIGHBOUR_RADIUS * 2 + 1)) + (NEIGHBOUR_RADIUS + NEIGHBOUR_RADIUS * ((NEIGHBOUR_RADIUS * 2 + 1))); -+ } -+ public final boolean isNeighbourFullLoaded(final int relativeX, final int relativeZ) { -+ return (this.fullNeighbourChunksLoadedBitset & (1L << getFullNeighbourIndex(relativeX, relativeZ))) != 0; -+ } -+ -+ // returns true if this chunk changed full status -+ public final boolean setNeighbourFullLoaded(final int relativeX, final int relativeZ) { -+ final long before = this.fullNeighbourChunksLoadedBitset; -+ final int index = getFullNeighbourIndex(relativeX, relativeZ); -+ this.fullNeighbourChunksLoadedBitset |= (1L << index); -+ return this.onNeighbourChange(before, this.fullNeighbourChunksLoadedBitset); -+ } -+ -+ // returns true if this chunk changed full status -+ public final boolean setNeighbourFullUnloaded(final int relativeX, final int relativeZ) { -+ final long before = this.fullNeighbourChunksLoadedBitset; -+ final int index = getFullNeighbourIndex(relativeX, relativeZ); -+ this.fullNeighbourChunksLoadedBitset &= ~(1L << index); -+ return this.onNeighbourChange(before, this.fullNeighbourChunksLoadedBitset); -+ } -+ -+ public static boolean areNeighboursFullLoaded(final long bitset, final int radius) { -+ // index = relativeX + (relativeZ * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)) + (NEIGHBOUR_CACHE_RADIUS + NEIGHBOUR_CACHE_RADIUS * ((NEIGHBOUR_CACHE_RADIUS * 2 + 1))) -+ switch (radius) { -+ case 0: { -+ return (bitset & (1L << getFullNeighbourIndex(0, 0))) != 0L; -+ } -+ case 1: { -+ long mask = 0L; -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ mask |= (1L << getFullNeighbourIndex(dx, dz)); -+ } -+ } -+ return (bitset & mask) == mask; -+ } -+ case 2: { -+ long mask = 0L; -+ for (int dx = -2; dx <= 2; ++dx) { -+ for (int dz = -2; dz <= 2; ++dz) { -+ mask |= (1L << getFullNeighbourIndex(dx, dz)); -+ } -+ } -+ return (bitset & mask) == mask; -+ } -+ -+ default: { -+ throw new IllegalArgumentException("Radius not recognized: " + radius); -+ } -+ } -+ } -+ -+ // upper 16 bits are pending status, lower 16 bits are current status -+ private volatile long chunkStatus; -+ private static final long PENDING_STATUS_MASK = Long.MIN_VALUE >> 31; -+ private static final FullChunkStatus[] CHUNK_STATUS_BY_ID = FullChunkStatus.values(); -+ private static final VarHandle CHUNK_STATUS_HANDLE = ConcurrentUtil.getVarHandle(NewChunkHolder.class, "chunkStatus", long.class); -+ -+ public static FullChunkStatus getCurrentChunkStatus(final long encoded) { -+ return CHUNK_STATUS_BY_ID[(int)encoded]; -+ } -+ -+ public static FullChunkStatus getPendingChunkStatus(final long encoded) { -+ return CHUNK_STATUS_BY_ID[(int)(encoded >>> 32)]; -+ } -+ -+ public FullChunkStatus getChunkStatus() { -+ return getCurrentChunkStatus(((long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this))); -+ } -+ -+ public boolean isEntityTickingReady() { -+ return this.getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING); -+ } -+ -+ public boolean isTickingReady() { -+ return this.getChunkStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); -+ } -+ -+ public boolean isFullChunkReady() { -+ return this.getChunkStatus().isOrAfter(FullChunkStatus.FULL); -+ } -+ -+ private static FullChunkStatus getStatusForBitset(final long bitset) { -+ if (areNeighboursFullLoaded(bitset, 2)) { -+ return FullChunkStatus.ENTITY_TICKING; -+ } else if (areNeighboursFullLoaded(bitset, 1)) { -+ return FullChunkStatus.BLOCK_TICKING; -+ } else if (areNeighboursFullLoaded(bitset, 0)) { -+ return FullChunkStatus.FULL; -+ } else { -+ return FullChunkStatus.INACCESSIBLE; -+ } -+ } -+ -+ // note: only while updating ticket level, so holds ticket update lock + scheduling lock -+ protected final boolean onTicketUpdate(final FullChunkStatus oldState, final FullChunkStatus newState) { -+ if (oldState == newState) { -+ return false; -+ } -+ -+ // preserve border request after full status complete, as it does not set anything in the bitset -+ FullChunkStatus byNeighbours = getStatusForBitset(this.fullNeighbourChunksLoadedBitset); -+ if (byNeighbours == FullChunkStatus.INACCESSIBLE && newState.isOrAfter(FullChunkStatus.FULL) && this.currentGenStatus == ChunkStatus.FULL) { -+ byNeighbours = FullChunkStatus.FULL; -+ } -+ -+ final FullChunkStatus toSet; -+ -+ if (newState.isOrAfter(byNeighbours)) { -+ // must clamp to neighbours level, even though we have the ticket level -+ toSet = byNeighbours; -+ } else { -+ // must clamp to ticket level, even though we have the neighbours -+ toSet = newState; -+ } -+ -+ long curr = (long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this); -+ -+ if (curr == ((long)toSet.ordinal() | ((long)toSet.ordinal() << 32))) { -+ // nothing to do -+ return false; -+ } -+ -+ int failures = 0; -+ for (;;) { -+ final long update = (curr & ~PENDING_STATUS_MASK) | ((long)toSet.ordinal() << 32); -+ if (curr == (curr = (long)CHUNK_STATUS_HANDLE.compareAndExchange((NewChunkHolder)this, curr, update))) { -+ return true; -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ protected final boolean onNeighbourChange(final long bitsetBefore, final long bitsetAfter) { -+ FullChunkStatus oldState = getStatusForBitset(bitsetBefore); -+ FullChunkStatus newState = getStatusForBitset(bitsetAfter); -+ final FullChunkStatus currStateTicketLevel = ChunkLevel.fullStatus(this.oldTicketLevel); -+ if (oldState.isOrAfter(currStateTicketLevel)) { -+ oldState = currStateTicketLevel; -+ } -+ if (newState.isOrAfter(currStateTicketLevel)) { -+ newState = currStateTicketLevel; -+ } -+ // preserve border request after full status complete, as it does not set anything in the bitset -+ if (newState == FullChunkStatus.INACCESSIBLE && currStateTicketLevel.isOrAfter(FullChunkStatus.FULL) && this.currentGenStatus == ChunkStatus.FULL) { -+ newState = FullChunkStatus.FULL; -+ } -+ -+ if (oldState == newState) { -+ return false; -+ } -+ -+ int failures = 0; -+ for (long curr = (long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this);;) { -+ final long update = (curr & ~PENDING_STATUS_MASK) | ((long)newState.ordinal() << 32); -+ if (curr == (curr = (long)CHUNK_STATUS_HANDLE.compareAndExchange((NewChunkHolder)this, curr, update))) { -+ return true; -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ private boolean queueBorderFullStatus(final boolean loaded, final List changedFullStatus) { -+ final FullChunkStatus toStatus = loaded ? FullChunkStatus.FULL : FullChunkStatus.INACCESSIBLE; -+ -+ int failures = 0; -+ for (long curr = (long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this);;) { -+ final FullChunkStatus currPending = getPendingChunkStatus(curr); -+ if (loaded && currPending != FullChunkStatus.INACCESSIBLE) { -+ throw new IllegalStateException("Expected " + FullChunkStatus.INACCESSIBLE + " for pending, but got " + currPending); -+ } -+ -+ final long update = (curr & ~PENDING_STATUS_MASK) | ((long)toStatus.ordinal() << 32); -+ if (curr == (curr = (long)CHUNK_STATUS_HANDLE.compareAndExchange((NewChunkHolder)this, curr, update))) { -+ if ((int)(update) != (int)(update >>> 32)) { -+ changedFullStatus.add(this); -+ return true; -+ } -+ return false; -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ // only call on main thread, must hold ticket level and scheduling lock -+ private void onFullChunkLoadChange(final boolean loaded, final List changedFullStatus) { -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ, NEIGHBOUR_RADIUS); -+ try { -+ for (int dz = -NEIGHBOUR_RADIUS; dz <= NEIGHBOUR_RADIUS; ++dz) { -+ for (int dx = -NEIGHBOUR_RADIUS; dx <= NEIGHBOUR_RADIUS; ++dx) { -+ final NewChunkHolder holder = (dx | dz) == 0 ? this : this.scheduler.chunkHolderManager.getChunkHolder(dx + this.chunkX, dz + this.chunkZ); -+ if (loaded) { -+ if (holder.setNeighbourFullLoaded(-dx, -dz)) { -+ changedFullStatus.add(holder); -+ } -+ } else { -+ if (holder != null && holder.setNeighbourFullUnloaded(-dx, -dz)) { -+ changedFullStatus.add(holder); -+ } -+ } -+ } -+ } -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } -+ -+ private FullChunkStatus updateCurrentState(final FullChunkStatus to) { -+ int failures = 0; -+ for (long curr = (long)CHUNK_STATUS_HANDLE.getVolatile((NewChunkHolder)this);;) { -+ final long update = (curr & PENDING_STATUS_MASK) | (long)to.ordinal(); -+ if (curr == (curr = (long)CHUNK_STATUS_HANDLE.compareAndExchange((NewChunkHolder)this, curr, update))) { -+ return getPendingChunkStatus(curr); -+ } -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ private void changeEntityChunkStatus(final FullChunkStatus toStatus) { -+ this.world.getEntityLookup().chunkStatusChange(this.chunkX, this.chunkZ, toStatus); -+ } -+ -+ private boolean processingFullStatus = false; -+ -+ // only to be called on the main thread, no locks need to be held -+ public boolean handleFullStatusChange(final List changedFullStatus) { -+ TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Cannot update full status thread off-main"); -+ -+ boolean ret = false; -+ -+ if (this.processingFullStatus) { -+ // we cannot process updates recursively -+ return ret; -+ } -+ -+ // note: use opaque reads for chunk status read since we need it to be atomic -+ -+ // test if anything changed -+ long statusCheck = (long)CHUNK_STATUS_HANDLE.getOpaque((NewChunkHolder)this); -+ if ((int)statusCheck == (int)(statusCheck >>> 32)) { -+ // nothing changed -+ return ret; -+ } -+ -+ final ChunkTaskScheduler scheduler = this.scheduler; -+ final ChunkHolderManager holderManager = scheduler.chunkHolderManager; -+ final int ticketKeep; -+ final Long ticketId = Long.valueOf(holderManager.getNextStatusUpgradeId()); -+ final ReentrantAreaLock.Node ticketLock = holderManager.ticketLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ ticketKeep = this.currentTicketLevel; -+ statusCheck = (long)CHUNK_STATUS_HANDLE.getOpaque((NewChunkHolder)this); -+ // handle race condition where ticket level and target status is updated concurrently -+ if ((int)statusCheck == (int)(statusCheck >>> 32)) { -+ // nothing changed -+ return ret; -+ } -+ holderManager.addTicketAtLevel(TicketType.STATUS_UPGRADE, CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ), ticketKeep, ticketId, false); -+ } finally { -+ holderManager.ticketLockArea.unlock(ticketLock); -+ } -+ -+ this.processingFullStatus = true; -+ try { -+ for (;;) { -+ final long currStateEncoded = (long)CHUNK_STATUS_HANDLE.getOpaque((NewChunkHolder)this); -+ final FullChunkStatus currState = getCurrentChunkStatus(currStateEncoded); -+ FullChunkStatus nextState = getPendingChunkStatus(currStateEncoded); -+ if (currState == nextState) { -+ if (nextState == FullChunkStatus.INACCESSIBLE) { -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); -+ try { -+ this.checkUnload(); -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ } -+ break; -+ } -+ -+ // chunks cannot downgrade state while status is pending a change -+ final LevelChunk chunk = (LevelChunk)this.currentChunk; -+ -+ // Note: we assume that only load/unload contain plugin logic -+ // plugin logic is anything stupid enough to possibly change the chunk status while it is already -+ // being changed (i.e during load it is possible it will try to set to full ticking) -+ // in order to allow this change, we also need this plugin logic to be contained strictly after all -+ // of the chunk system load callbacks are invoked -+ if (nextState.isOrAfter(currState)) { -+ // state upgrade -+ if (!currState.isOrAfter(FullChunkStatus.FULL) && nextState.isOrAfter(FullChunkStatus.FULL)) { -+ nextState = this.updateCurrentState(FullChunkStatus.FULL); -+ holderManager.ensureInAutosave(this); -+ chunk.pushChunkIntoLoadedMap(); -+ this.changeEntityChunkStatus(FullChunkStatus.FULL); -+ chunk.onChunkLoad(this); -+ this.onFullChunkLoadChange(true, changedFullStatus); -+ this.completeFullStatusConsumers(FullChunkStatus.FULL, chunk); -+ } -+ -+ if (!currState.isOrAfter(FullChunkStatus.BLOCK_TICKING) && nextState.isOrAfter(FullChunkStatus.BLOCK_TICKING)) { -+ nextState = this.updateCurrentState(FullChunkStatus.BLOCK_TICKING); -+ this.changeEntityChunkStatus(FullChunkStatus.BLOCK_TICKING); -+ chunk.onChunkTicking(this); -+ this.completeFullStatusConsumers(FullChunkStatus.BLOCK_TICKING, chunk); -+ } -+ -+ if (!currState.isOrAfter(FullChunkStatus.ENTITY_TICKING) && nextState.isOrAfter(FullChunkStatus.ENTITY_TICKING)) { -+ nextState = this.updateCurrentState(FullChunkStatus.ENTITY_TICKING); -+ this.changeEntityChunkStatus(FullChunkStatus.ENTITY_TICKING); -+ chunk.onChunkEntityTicking(this); -+ this.completeFullStatusConsumers(FullChunkStatus.ENTITY_TICKING, chunk); -+ } -+ } else { -+ if (currState.isOrAfter(FullChunkStatus.ENTITY_TICKING) && !nextState.isOrAfter(FullChunkStatus.ENTITY_TICKING)) { -+ this.changeEntityChunkStatus(FullChunkStatus.BLOCK_TICKING); -+ chunk.onChunkNotEntityTicking(this); -+ nextState = this.updateCurrentState(FullChunkStatus.BLOCK_TICKING); -+ } -+ -+ if (currState.isOrAfter(FullChunkStatus.BLOCK_TICKING) && !nextState.isOrAfter(FullChunkStatus.BLOCK_TICKING)) { -+ this.changeEntityChunkStatus(FullChunkStatus.FULL); -+ chunk.onChunkNotTicking(this); -+ nextState = this.updateCurrentState(FullChunkStatus.FULL); -+ } -+ -+ if (currState.isOrAfter(FullChunkStatus.FULL) && !nextState.isOrAfter(FullChunkStatus.FULL)) { -+ this.onFullChunkLoadChange(false, changedFullStatus); -+ this.changeEntityChunkStatus(FullChunkStatus.INACCESSIBLE); -+ chunk.onChunkUnload(this); -+ nextState = this.updateCurrentState(FullChunkStatus.INACCESSIBLE); -+ } -+ } -+ -+ ret = true; -+ } -+ } finally { -+ this.processingFullStatus = false; -+ holderManager.removeTicketAtLevel(TicketType.STATUS_UPGRADE, this.chunkX, this.chunkZ, ticketKeep, ticketId); -+ } -+ -+ return ret; -+ } -+ -+ // note: must hold scheduling lock -+ // rets true if the current requested gen status is not null (effectively, whether further scheduling is not needed) -+ boolean upgradeGenTarget(final ChunkStatus toStatus) { -+ if (toStatus == null) { -+ throw new NullPointerException("toStatus cannot be null"); -+ } -+ if (this.requestedGenStatus == null && this.generationTask == null) { -+ return false; -+ } -+ if (this.requestedGenStatus == null || !this.requestedGenStatus.isOrAfter(toStatus)) { -+ this.requestedGenStatus = toStatus; -+ } -+ return true; -+ } -+ -+ public void setGenerationTarget(final ChunkStatus toStatus) { -+ this.requestedGenStatus = toStatus; -+ } -+ -+ public boolean hasGenerationTask() { -+ return this.generationTask != null; -+ } -+ -+ public ChunkStatus getCurrentGenStatus() { -+ return this.currentGenStatus; -+ } -+ -+ public ChunkStatus getRequestedGenStatus() { -+ return this.requestedGenStatus; -+ } -+ -+ private final Reference2ObjectOpenHashMap>> statusWaiters = new Reference2ObjectOpenHashMap<>(); -+ -+ void addStatusConsumer(final ChunkStatus status, final Consumer consumer) { -+ this.statusWaiters.computeIfAbsent(status, (final ChunkStatus keyInMap) -> { -+ return new ArrayList<>(4); -+ }).add(consumer); -+ } -+ -+ private void completeStatusConsumers(ChunkStatus status, final ChunkAccess chunk) { -+ // need to tell future statuses to complete if cancelled -+ do { -+ this.completeStatusConsumers0(status, chunk); -+ } while (chunk == null && status != (status = status.getNextStatus())); -+ } -+ -+ private void completeStatusConsumers0(final ChunkStatus status, final ChunkAccess chunk) { -+ final List> consumers; -+ consumers = this.statusWaiters.remove(status); -+ -+ if (consumers == null) { -+ return; -+ } -+ -+ // must be scheduled to main, we do not trust the callback to not do anything stupid -+ this.scheduler.scheduleChunkTask(this.chunkX, this.chunkZ, () -> { -+ for (final Consumer consumer : consumers) { -+ try { -+ consumer.accept(chunk); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to process chunk status callback", thr); -+ } -+ } -+ }, PrioritisedExecutor.Priority.HIGHEST); -+ } -+ -+ private final Reference2ObjectOpenHashMap>> fullStatusWaiters = new Reference2ObjectOpenHashMap<>(); -+ -+ void addFullStatusConsumer(final FullChunkStatus status, final Consumer consumer) { -+ this.fullStatusWaiters.computeIfAbsent(status, (final FullChunkStatus keyInMap) -> { -+ return new ArrayList<>(4); -+ }).add(consumer); -+ } -+ -+ private void completeFullStatusConsumers(FullChunkStatus status, final LevelChunk chunk) { -+ // need to tell future statuses to complete if cancelled -+ final FullChunkStatus max = CHUNK_STATUS_BY_ID[CHUNK_STATUS_BY_ID.length - 1]; -+ -+ for (;;) { -+ this.completeFullStatusConsumers0(status, chunk); -+ if (chunk != null || status == max) { -+ break; -+ } -+ status = CHUNK_STATUS_BY_ID[status.ordinal() + 1]; -+ } -+ } -+ -+ private void completeFullStatusConsumers0(final FullChunkStatus status, final LevelChunk chunk) { -+ final List> consumers; -+ consumers = this.fullStatusWaiters.remove(status); -+ -+ if (consumers == null) { -+ return; -+ } -+ -+ // must be scheduled to main, we do not trust the callback to not do anything stupid -+ this.scheduler.scheduleChunkTask(this.chunkX, this.chunkZ, () -> { -+ for (final Consumer consumer : consumers) { -+ try { -+ consumer.accept(chunk); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to process chunk status callback", thr); -+ } -+ } -+ }, PrioritisedExecutor.Priority.HIGHEST); -+ } -+ -+ // note: must hold scheduling lock -+ private void onChunkGenComplete(final ChunkAccess newChunk, final ChunkStatus newStatus, -+ final List scheduleList, final List changedLoadStatus) { -+ if (!this.neighboursBlockingGenTask.isEmpty()) { -+ throw new IllegalStateException("Cannot have neighbours blocking this gen task"); -+ } -+ if (newChunk != null || (this.requestedGenStatus == null || !this.requestedGenStatus.isOrAfter(newStatus))) { -+ this.completeStatusConsumers(newStatus, newChunk); -+ } -+ // done now, clear state (must be done before scheduling new tasks) -+ this.generationTask = null; -+ this.generationTaskStatus = null; -+ if (newChunk == null) { -+ // task was cancelled -+ // should be careful as this could be called while holding the schedule lock and/or inside the -+ // ticket level update -+ // while a task may be cancelled, it is possible for it to be later re-scheduled -+ // however, because generationTask is only set to null on _completion_, the scheduler leaves -+ // the rescheduling logic to us here -+ final ChunkStatus requestedGenStatus = this.requestedGenStatus; -+ this.requestedGenStatus = null; -+ if (requestedGenStatus != null) { -+ // it looks like it has been requested, so we must reschedule -+ if (!this.neighboursWaitingForUs.isEmpty()) { -+ for (final Iterator> iterator = this.neighboursWaitingForUs.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Reference2ObjectMap.Entry entry = iterator.next(); -+ -+ final NewChunkHolder chunkHolder = entry.getKey(); -+ final ChunkStatus toStatus = entry.getValue(); -+ -+ if (!requestedGenStatus.isOrAfter(toStatus)) { -+ // if we were cancelled, we are responsible for removing the waiter -+ if (!chunkHolder.neighboursBlockingGenTask.remove(this)) { -+ throw new IllegalStateException("Corrupt state"); -+ } -+ if (chunkHolder.neighboursBlockingGenTask.isEmpty()) { -+ chunkHolder.checkUnload(); -+ } -+ iterator.remove(); -+ continue; -+ } -+ } -+ } -+ -+ // note: only after generationTask -> null, generationTaskStatus -> null, and requestedGenStatus -> null -+ this.scheduler.schedule( -+ this.chunkX, this.chunkZ, requestedGenStatus, this, scheduleList -+ ); -+ -+ // return, can't do anything further -+ return; -+ } -+ -+ if (!this.neighboursWaitingForUs.isEmpty()) { -+ for (final NewChunkHolder chunkHolder : this.neighboursWaitingForUs.keySet()) { -+ if (!chunkHolder.neighboursBlockingGenTask.remove(this)) { -+ throw new IllegalStateException("Corrupt state"); -+ } -+ if (chunkHolder.neighboursBlockingGenTask.isEmpty()) { -+ chunkHolder.checkUnload(); -+ } -+ } -+ this.neighboursWaitingForUs.clear(); -+ } -+ // reset priority, we have nothing left to generate to -+ this.setPriority(PrioritisedExecutor.Priority.NORMAL); -+ this.checkUnload(); -+ return; -+ } -+ -+ this.currentChunk = newChunk; -+ this.currentGenStatus = newStatus; -+ this.lastChunkCompletion = new ChunkCompletion(newChunk, newStatus); -+ -+ final ChunkStatus requestedGenStatus = this.requestedGenStatus; -+ -+ List needsScheduling = null; -+ boolean recalculatePriority = false; -+ for (final Iterator> iterator -+ = this.neighboursWaitingForUs.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Reference2ObjectMap.Entry entry = iterator.next(); -+ final NewChunkHolder neighbour = entry.getKey(); -+ final ChunkStatus requiredStatus = entry.getValue(); -+ -+ if (!newStatus.isOrAfter(requiredStatus)) { -+ if (requestedGenStatus == null || !requestedGenStatus.isOrAfter(requiredStatus)) { -+ // if we're cancelled, still need to clear this map -+ if (!neighbour.neighboursBlockingGenTask.remove(this)) { -+ throw new IllegalStateException("Neighbour is not waiting for us?"); -+ } -+ if (neighbour.neighboursBlockingGenTask.isEmpty()) { -+ neighbour.checkUnload(); -+ } -+ -+ iterator.remove(); -+ } -+ continue; -+ } -+ -+ // doesn't matter what isCancelled is here, we need to schedule if we can -+ -+ recalculatePriority = true; -+ if (!neighbour.neighboursBlockingGenTask.remove(this)) { -+ throw new IllegalStateException("Neighbour is not waiting for us?"); -+ } -+ -+ if (neighbour.neighboursBlockingGenTask.isEmpty()) { -+ if (neighbour.requestedGenStatus != null) { -+ if (needsScheduling == null) { -+ needsScheduling = new ArrayList<>(); -+ } -+ needsScheduling.add(neighbour); -+ } else { -+ neighbour.checkUnload(); -+ } -+ } -+ -+ // remove last; access to entry will throw if removed -+ iterator.remove(); -+ } -+ -+ if (newStatus == ChunkStatus.FULL) { -+ this.lockPriority(); -+ // must use oldTicketLevel, we hold the schedule lock but not the ticket level lock -+ // however, schedule lock needs to be held for ticket level callback, so we're fine here -+ if (ChunkLevel.fullStatus(this.oldTicketLevel).isOrAfter(FullChunkStatus.FULL)) { -+ this.queueBorderFullStatus(true, changedLoadStatus); -+ } -+ } -+ -+ if (recalculatePriority) { -+ this.recalculateNeighbourRequestedPriority(); -+ } -+ -+ if (requestedGenStatus != null && !newStatus.isOrAfter(requestedGenStatus)) { -+ this.scheduleNeighbours(needsScheduling, scheduleList); -+ -+ // we need to schedule more tasks now -+ this.scheduler.schedule( -+ this.chunkX, this.chunkZ, requestedGenStatus, this, scheduleList -+ ); -+ } else { -+ // we're done now -+ if (requestedGenStatus != null) { -+ this.requestedGenStatus = null; -+ } -+ // reached final stage, so stop scheduling now -+ this.setPriority(PrioritisedExecutor.Priority.NORMAL); -+ this.checkUnload(); -+ -+ this.scheduleNeighbours(needsScheduling, scheduleList); -+ } -+ } -+ -+ private void scheduleNeighbours(final List needsScheduling, final List scheduleList) { -+ if (needsScheduling != null) { -+ for (int i = 0, len = needsScheduling.size(); i < len; ++i) { -+ final NewChunkHolder neighbour = needsScheduling.get(i); -+ -+ this.scheduler.schedule( -+ neighbour.chunkX, neighbour.chunkZ, neighbour.requestedGenStatus, neighbour, scheduleList -+ ); -+ } -+ } -+ } -+ -+ public void setGenerationTask(final ChunkProgressionTask generationTask, final ChunkStatus taskStatus, -+ final List neighbours) { -+ if (this.generationTask != null || (this.currentGenStatus != null && this.currentGenStatus.isOrAfter(taskStatus))) { -+ throw new IllegalStateException("Currently generating or provided task is trying to generate to a level we are already at!"); -+ } -+ if (this.requestedGenStatus == null || !this.requestedGenStatus.isOrAfter(taskStatus)) { -+ throw new IllegalStateException("Cannot schedule generation task when not requested"); -+ } -+ this.generationTask = generationTask; -+ this.generationTaskStatus = taskStatus; -+ -+ for (int i = 0, len = neighbours.size(); i < len; ++i) { -+ neighbours.get(i).addNeighbourUsingChunk(); -+ } -+ -+ this.checkUnload(); -+ -+ generationTask.onComplete((final ChunkAccess access, final Throwable thr) -> { -+ if (generationTask != this.generationTask) { -+ throw new IllegalStateException( -+ "Cannot complete generation task '" + generationTask + "' because we are waiting on '" + this.generationTask + "' instead!" -+ ); -+ } -+ if (thr != null) { -+ if (this.genTaskException != null) { -+ // first one is probably the TRUE problem -+ return; -+ } -+ // don't set generation task to null, so that scheduling will not attempt to create another task and it -+ // will automatically block any further scheduling usage of this chunk as it will wait forever for a failed -+ // task to complete -+ this.genTaskException = thr; -+ this.failedGenStatus = taskStatus; -+ this.genTaskFailedThread = Thread.currentThread(); -+ -+ this.scheduler.unrecoverableChunkSystemFailure(this.chunkX, this.chunkZ, Map.of( -+ "Generation task", ChunkTaskScheduler.stringIfNull(generationTask), -+ "Task to status", ChunkTaskScheduler.stringIfNull(taskStatus) -+ ), thr); -+ return; -+ } -+ -+ final boolean scheduleTasks; -+ List tasks = ChunkHolderManager.getCurrentTicketUpdateScheduling(); -+ if (tasks == null) { -+ scheduleTasks = true; -+ tasks = new ArrayList<>(); -+ } else { -+ scheduleTasks = false; -+ // we are currently updating ticket levels, so we already hold the schedule lock -+ // this means we have to leave the ticket level update to handle the scheduling -+ } -+ final List changedLoadStatus = new ArrayList<>(); -+ // theoretically, we could schedule a chunk at the max radius which performs another max radius access. So we need to double the radius. -+ final ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ, 2 * ChunkTaskScheduler.getMaxAccessRadius()); -+ try { -+ for (int i = 0, len = neighbours.size(); i < len; ++i) { -+ neighbours.get(i).removeNeighbourUsingChunk(); -+ } -+ this.onChunkGenComplete(access, taskStatus, tasks, changedLoadStatus); -+ } finally { -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); -+ } -+ this.scheduler.chunkHolderManager.addChangedStatuses(changedLoadStatus); -+ -+ if (scheduleTasks) { -+ // can't hold the lock while scheduling, so we have to build the tasks and then schedule after -+ for (int i = 0, len = tasks.size(); i < len; ++i) { -+ tasks.get(i).schedule(); -+ } -+ } -+ }); -+ } -+ -+ public PoiChunk getPoiChunk() { -+ return this.poiChunk; -+ } -+ -+ public ChunkEntitySlices getEntityChunk() { -+ return this.entityChunk; -+ } -+ -+ public long lastAutoSave; -+ -+ public static final record SaveStat(boolean savedChunk, boolean savedEntityChunk, boolean savedPoiChunk) {} -+ -+ public SaveStat save(final boolean shutdown, final boolean unloading) { -+ TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Cannot save data off-main"); -+ -+ ChunkAccess chunk = this.getCurrentChunk(); -+ PoiChunk poi = this.getPoiChunk(); -+ ChunkEntitySlices entities = this.getEntityChunk(); -+ boolean executedUnloadTask = false; -+ -+ if (shutdown) { -+ // make sure that the async unloads complete -+ if (this.unloadState != null) { -+ // must have errored during unload -+ chunk = this.unloadState.chunk(); -+ poi = this.unloadState.poiChunk(); -+ entities = this.unloadState.entityChunk(); -+ } -+ final UnloadTask chunkUnloadTask = this.chunkDataUnload; -+ final DelayedPrioritisedTask chunkDataUnloadTask = chunkUnloadTask == null ? null : chunkUnloadTask.task(); -+ if (chunkDataUnloadTask != null) { -+ final PrioritisedExecutor.PrioritisedTask unloadTask = chunkDataUnloadTask.getTask(); -+ if (unloadTask != null) { -+ executedUnloadTask = unloadTask.execute(); -+ } -+ } -+ } -+ -+ boolean canSaveChunk = !(chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave) && -+ (chunk != null && ((shutdown || chunk instanceof LevelChunk) && chunk.isUnsaved())); -+ boolean canSavePOI = !(chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave) && (poi != null && poi.isDirty()); -+ boolean canSaveEntities = entities != null; -+ -+ try (co.aikar.timings.Timing ignored = this.world.timings.chunkSave.startTiming()) { // Paper -+ if (canSaveChunk) { -+ canSaveChunk = this.saveChunk(chunk, unloading); -+ } -+ if (canSavePOI) { -+ canSavePOI = this.savePOI(poi, unloading); -+ } -+ if (canSaveEntities) { -+ // on shutdown, we need to force transient entity chunks to save -+ canSaveEntities = this.saveEntities(entities, unloading || shutdown); -+ if (unloading || shutdown) { -+ this.lastEntityUnload = null; -+ } -+ } -+ } -+ -+ return executedUnloadTask | canSaveChunk | canSaveEntities | canSavePOI ? new SaveStat(executedUnloadTask || canSaveChunk, canSaveEntities, canSavePOI): null; -+ } -+ -+ static final class AsyncChunkSerializeTask implements Runnable { -+ -+ private final ServerLevel world; -+ private final ChunkAccess chunk; -+ private final ChunkSerializer.AsyncSaveData asyncSaveData; -+ private final NewChunkHolder toComplete; -+ -+ public AsyncChunkSerializeTask(final ServerLevel world, final ChunkAccess chunk, final ChunkSerializer.AsyncSaveData asyncSaveData, -+ final NewChunkHolder toComplete) { -+ this.world = world; -+ this.chunk = chunk; -+ this.asyncSaveData = asyncSaveData; -+ this.toComplete = toComplete; -+ } -+ -+ @Override -+ public void run() { -+ final CompoundTag toSerialize; -+ try { -+ toSerialize = ChunkSerializer.saveChunk(this.world, this.chunk, this.asyncSaveData); -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable throwable) { -+ LOGGER.error("Failed to asynchronously save chunk " + this.chunk.getPos() + " for world '" + this.world.getWorld().getName() + "', falling back to synchronous save", throwable); -+ this.world.chunkTaskScheduler.scheduleChunkTask(this.chunk.locX, this.chunk.locZ, () -> { -+ final CompoundTag synchronousSave; -+ try { -+ synchronousSave = ChunkSerializer.saveChunk(AsyncChunkSerializeTask.this.world, AsyncChunkSerializeTask.this.chunk, AsyncChunkSerializeTask.this.asyncSaveData); -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable throwable2) { -+ LOGGER.error("Failed to synchronously save chunk " + AsyncChunkSerializeTask.this.chunk.getPos() + " for world '" + AsyncChunkSerializeTask.this.world.getWorld().getName() + "', chunk data will be lost", throwable2); -+ AsyncChunkSerializeTask.this.toComplete.completeAsyncChunkDataSave(null); -+ return; -+ } -+ -+ AsyncChunkSerializeTask.this.toComplete.completeAsyncChunkDataSave(synchronousSave); -+ LOGGER.info("Successfully serialized chunk " + AsyncChunkSerializeTask.this.chunk.getPos() + " for world '" + AsyncChunkSerializeTask.this.world.getWorld().getName() + "' synchronously"); -+ -+ }, PrioritisedExecutor.Priority.HIGHEST); -+ return; -+ } -+ this.toComplete.completeAsyncChunkDataSave(toSerialize); -+ } -+ -+ @Override -+ public String toString() { -+ return "AsyncChunkSerializeTask{" + -+ "chunk={pos=" + this.chunk.getPos() + ",world=\"" + this.world.getWorld().getName() + "\"}" + -+ "}"; -+ } -+ } -+ -+ private boolean saveChunk(final ChunkAccess chunk, final boolean unloading) { -+ if (!chunk.isUnsaved()) { -+ if (unloading) { -+ this.completeAsyncChunkDataSave(null); -+ } -+ return false; -+ } -+ boolean completing = false; -+ try { -+ if (unloading) { -+ try { -+ final ChunkSerializer.AsyncSaveData asyncSaveData = ChunkSerializer.getAsyncSaveData(this.world, chunk); -+ -+ final PrioritisedExecutor.PrioritisedTask task = this.scheduler.loadExecutor.createTask(new AsyncChunkSerializeTask(this.world, chunk, asyncSaveData, this)); -+ -+ this.chunkDataUnload.task().setTask(task); -+ -+ task.queue(); -+ -+ chunk.setUnsaved(false); -+ -+ return true; -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to prepare async chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "', falling back to synchronous save", thr); -+ // fall through to synchronous save -+ } -+ } -+ -+ final CompoundTag save = ChunkSerializer.saveChunk(this.world, chunk, null); -+ -+ if (unloading) { -+ completing = true; -+ this.completeAsyncChunkDataSave(save); -+ LOGGER.info("Successfully serialized chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "' synchronously"); -+ } else { -+ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.CHUNK_DATA); -+ } -+ chunk.setUnsaved(false); -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to save chunk data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "'"); -+ if (unloading && !completing) { -+ this.completeAsyncChunkDataSave(null); -+ } -+ } -+ -+ return true; -+ } -+ -+ private boolean lastEntitySaveNull; -+ private CompoundTag lastEntityUnload; -+ private boolean saveEntities(final ChunkEntitySlices entities, final boolean unloading) { -+ try { -+ CompoundTag mergeFrom = null; -+ if (entities.isTransient()) { -+ if (!unloading) { -+ // if we're a transient chunk, we cannot save until unloading because otherwise a double save will -+ // result in double adding the entities -+ return false; -+ } -+ try { -+ mergeFrom = RegionFileIOThread.loadData(this.world, this.chunkX, this.chunkZ, RegionFileIOThread.RegionFileType.ENTITY_DATA, PrioritisedExecutor.Priority.BLOCKING); -+ } catch (final Exception ex) { -+ LOGGER.error("Cannot merge transient entities for chunk (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "', data on disk will be replaced", ex); -+ } -+ } -+ -+ final CompoundTag save = entities.save(); -+ if (mergeFrom != null) { -+ if (save == null) { -+ // don't override the data on disk with nothing -+ return false; -+ } else { -+ EntityStorage.copyEntities(mergeFrom, save); -+ } -+ } -+ if (save == null && this.lastEntitySaveNull) { -+ return false; -+ } -+ -+ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.ENTITY_DATA); -+ this.lastEntitySaveNull = save == null; -+ if (unloading) { -+ this.lastEntityUnload = save; -+ } -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to save entity data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "'"); -+ } -+ -+ return true; -+ } -+ -+ private boolean lastPoiSaveNull; -+ private boolean savePOI(final PoiChunk poi, final boolean unloading) { -+ try { -+ final CompoundTag save = poi.save(); -+ poi.setDirty(false); -+ if (save == null && this.lastPoiSaveNull) { -+ if (unloading) { -+ this.poiDataUnload.completable().complete(null); -+ } -+ return false; -+ } -+ -+ RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, save, RegionFileIOThread.RegionFileType.POI_DATA); -+ this.lastPoiSaveNull = save == null; -+ if (unloading) { -+ this.poiDataUnload.completable().complete(save); -+ } -+ } catch (final ThreadDeath death) { -+ throw death; -+ } catch (final Throwable thr) { -+ LOGGER.error("Failed to save poi data (" + this.chunkX + "," + this.chunkZ + ") in world '" + this.world.getWorld().getName() + "'"); -+ } -+ -+ return true; -+ } -+ -+ @Override -+ public String toString() { -+ final ChunkCompletion lastCompletion = this.lastChunkCompletion; -+ final ChunkEntitySlices entityChunk = this.entityChunk; -+ final long chunkStatus = this.chunkStatus; -+ final int fullChunkStatus = (int)chunkStatus; -+ final int pendingChunkStatus = (int)(chunkStatus >>> 32); -+ final FullChunkStatus currentFullStatus = fullChunkStatus < 0 || fullChunkStatus >= CHUNK_STATUS_BY_ID.length ? null : CHUNK_STATUS_BY_ID[fullChunkStatus]; -+ final FullChunkStatus pendingFullStatus = pendingChunkStatus < 0 || pendingChunkStatus >= CHUNK_STATUS_BY_ID.length ? null : CHUNK_STATUS_BY_ID[pendingChunkStatus]; -+ return "NewChunkHolder{" + -+ "world=" + this.world.getWorld().getName() + -+ ", chunkX=" + this.chunkX + -+ ", chunkZ=" + this.chunkZ + -+ ", entityChunkFromDisk=" + (entityChunk != null && !entityChunk.isTransient()) + -+ ", lastChunkCompletion={chunk_class=" + (lastCompletion == null || lastCompletion.chunk() == null ? "null" : lastCompletion.chunk().getClass().getName()) + ",status=" + (lastCompletion == null ? "null" : lastCompletion.genStatus()) + "}" + -+ ", currentGenStatus=" + this.currentGenStatus + -+ ", requestedGenStatus=" + this.requestedGenStatus + -+ ", generationTask=" + this.generationTask + -+ ", generationTaskStatus=" + this.generationTaskStatus + -+ ", priority=" + this.priority + -+ ", priorityLocked=" + this.priorityLocked + -+ ", neighbourRequestedPriority=" + this.neighbourRequestedPriority + -+ ", effective_priority=" + this.getEffectivePriority() + -+ ", oldTicketLevel=" + this.oldTicketLevel + -+ ", currentTicketLevel=" + this.currentTicketLevel + -+ ", totalNeighboursUsingThisChunk=" + this.totalNeighboursUsingThisChunk + -+ ", fullNeighbourChunksLoadedBitset=" + this.fullNeighbourChunksLoadedBitset + -+ ", chunkStatusRaw=" + chunkStatus + -+ ", currentChunkStatus=" + currentFullStatus + -+ ", pendingChunkStatus=" + pendingFullStatus + -+ ", is_unload_safe=" + this.isSafeToUnload() + -+ ", killed=" + this.killed + -+ '}'; -+ } -+ -+ private static JsonElement serializeCompletable(final Completable completable) { -+ if (completable == null) { -+ return new JsonPrimitive("null"); -+ } -+ -+ final JsonObject ret = new JsonObject(); -+ final boolean isCompleted = completable.isCompleted(); -+ ret.addProperty("completed", Boolean.valueOf(isCompleted)); -+ -+ if (isCompleted) { -+ ret.addProperty("completed_exceptionally", Boolean.valueOf(completable.getThrowable() != null)); -+ } -+ -+ return ret; -+ } -+ -+ // holds ticket and scheduling lock -+ public JsonObject getDebugJson() { -+ final JsonObject ret = new JsonObject(); -+ -+ final ChunkCompletion lastCompletion = this.lastChunkCompletion; -+ final ChunkEntitySlices slices = this.entityChunk; -+ final PoiChunk poiChunk = this.poiChunk; -+ -+ ret.addProperty("chunkX", Integer.valueOf(this.chunkX)); -+ ret.addProperty("chunkZ", Integer.valueOf(this.chunkZ)); -+ ret.addProperty("entity_chunk", slices == null ? "null" : "transient=" + slices.isTransient()); -+ ret.addProperty("poi_chunk", "null=" + (poiChunk == null)); -+ ret.addProperty("completed_chunk_class", lastCompletion == null ? "null" : lastCompletion.chunk().getClass().getName()); -+ ret.addProperty("completed_gen_status", lastCompletion == null ? "null" : lastCompletion.genStatus().toString()); -+ ret.addProperty("priority", Objects.toString(this.priority)); -+ ret.addProperty("neighbour_requested_priority", Objects.toString(this.neighbourRequestedPriority)); -+ ret.addProperty("generation_task", Objects.toString(this.generationTask)); -+ ret.addProperty("is_safe_unload", Objects.toString(this.isSafeToUnload())); -+ ret.addProperty("old_ticket_level", Integer.valueOf(this.oldTicketLevel)); -+ ret.addProperty("current_ticket_level", Integer.valueOf(this.currentTicketLevel)); -+ ret.addProperty("neighbours_using_chunk", Integer.valueOf(this.totalNeighboursUsingThisChunk)); -+ -+ final JsonObject neighbourWaitState = new JsonObject(); -+ ret.add("neighbour_state", neighbourWaitState); -+ -+ final JsonArray blockingGenNeighbours = new JsonArray(); -+ neighbourWaitState.add("blocking_gen_task", blockingGenNeighbours); -+ for (final NewChunkHolder blockingGenNeighbour : this.neighboursBlockingGenTask) { -+ final JsonObject neighbour = new JsonObject(); -+ blockingGenNeighbours.add(neighbour); -+ -+ neighbour.addProperty("chunkX", Integer.valueOf(blockingGenNeighbour.chunkX)); -+ neighbour.addProperty("chunkZ", Integer.valueOf(blockingGenNeighbour.chunkZ)); -+ } -+ -+ final JsonArray neighboursWaitingForUs = new JsonArray(); -+ neighbourWaitState.add("neighbours_waiting_on_us", neighboursWaitingForUs); -+ for (final Reference2ObjectMap.Entry entry : this.neighboursWaitingForUs.reference2ObjectEntrySet()) { -+ final NewChunkHolder holder = entry.getKey(); -+ final ChunkStatus status = entry.getValue(); -+ -+ final JsonObject neighbour = new JsonObject(); -+ neighboursWaitingForUs.add(neighbour); -+ -+ -+ neighbour.addProperty("chunkX", Integer.valueOf(holder.chunkX)); -+ neighbour.addProperty("chunkZ", Integer.valueOf(holder.chunkZ)); -+ neighbour.addProperty("waiting_for", Objects.toString(status)); -+ } -+ -+ ret.addProperty("fullchunkstatus", Objects.toString(this.getChunkStatus())); -+ ret.addProperty("fullchunkstatus_raw", Long.valueOf(this.chunkStatus)); -+ ret.addProperty("generation_task", Objects.toString(this.generationTask)); -+ ret.addProperty("requested_generation", Objects.toString(this.requestedGenStatus)); -+ ret.addProperty("has_entity_load_task", Boolean.valueOf(this.entityDataLoadTask != null)); -+ ret.addProperty("has_poi_load_task", Boolean.valueOf(this.poiDataLoadTask != null)); -+ -+ final UnloadTask entityDataUnload = this.entityDataUnload; -+ final UnloadTask poiDataUnload = this.poiDataUnload; -+ final UnloadTask chunkDataUnload = this.chunkDataUnload; -+ -+ ret.add("entity_unload_completable", serializeCompletable(entityDataUnload == null ? null : entityDataUnload.completable())); -+ ret.add("poi_unload_completable", serializeCompletable(poiDataUnload == null ? null : poiDataUnload.completable())); -+ ret.add("chunk_unload_completable", serializeCompletable(chunkDataUnload == null ? null : chunkDataUnload.completable())); -+ -+ final DelayedPrioritisedTask unloadTask = chunkDataUnload == null ? null : chunkDataUnload.task(); -+ if (unloadTask == null) { -+ ret.addProperty("unload_task_priority", "null"); -+ ret.addProperty("unload_task_priority_raw", "null"); -+ } else { -+ ret.addProperty("unload_task_priority", Objects.toString(unloadTask.getPriority())); -+ ret.addProperty("unload_task_priority_raw", Integer.valueOf(unloadTask.getPriorityInternal())); -+ } -+ -+ ret.addProperty("killed", Boolean.valueOf(this.killed)); -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/PriorityHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/PriorityHolder.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b4c56bf12dc8dd17452210ece4fd67411cc6b2fd ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/PriorityHolder.java -@@ -0,0 +1,215 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import java.lang.invoke.VarHandle; -+ -+public abstract class PriorityHolder { -+ -+ protected volatile int priority; -+ protected static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(PriorityHolder.class, "priority", int.class); -+ -+ protected static final int PRIORITY_SCHEDULED = Integer.MIN_VALUE >>> 0; -+ protected static final int PRIORITY_EXECUTED = Integer.MIN_VALUE >>> 1; -+ -+ protected final int getPriorityVolatile() { -+ return (int)PRIORITY_HANDLE.getVolatile((PriorityHolder)this); -+ } -+ -+ protected final int compareAndExchangePriorityVolatile(final int expect, final int update) { -+ return (int)PRIORITY_HANDLE.compareAndExchange((PriorityHolder)this, (int)expect, (int)update); -+ } -+ -+ protected final int getAndOrPriorityVolatile(final int val) { -+ return (int)PRIORITY_HANDLE.getAndBitwiseOr((PriorityHolder)this, (int)val); -+ } -+ -+ protected final void setPriorityPlain(final int val) { -+ PRIORITY_HANDLE.set((PriorityHolder)this, (int)val); -+ } -+ -+ protected PriorityHolder(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ this.setPriorityPlain(priority.priority); -+ } -+ -+ // used only for debug json -+ public boolean isScheduled() { -+ return (this.getPriorityVolatile() & PRIORITY_SCHEDULED) != 0; -+ } -+ -+ // returns false if cancelled -+ protected boolean markExecuting() { -+ return (this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) == 0; -+ } -+ -+ protected boolean isMarkedExecuted() { -+ return (this.getPriorityVolatile() & PRIORITY_EXECUTED) != 0; -+ } -+ -+ public void cancel() { -+ if ((this.getAndOrPriorityVolatile(PRIORITY_EXECUTED) & PRIORITY_EXECUTED) != 0) { -+ // cancelled already -+ return; -+ } -+ this.cancelScheduled(); -+ } -+ -+ public void schedule() { -+ int priority = this.getPriorityVolatile(); -+ -+ if ((priority & PRIORITY_SCHEDULED) != 0) { -+ throw new IllegalStateException("schedule() called twice"); -+ } -+ -+ if ((priority & PRIORITY_EXECUTED) != 0) { -+ // cancelled -+ return; -+ } -+ -+ this.scheduleTask(PrioritisedExecutor.Priority.getPriority(priority)); -+ -+ int failures = 0; -+ for (;;) { -+ if (priority == (priority = this.compareAndExchangePriorityVolatile(priority, priority | PRIORITY_SCHEDULED))) { -+ return; -+ } -+ -+ if ((priority & PRIORITY_SCHEDULED) != 0) { -+ throw new IllegalStateException("schedule() called twice"); -+ } -+ -+ if ((priority & PRIORITY_EXECUTED) != 0) { -+ // cancelled or executed -+ return; -+ } -+ -+ this.setPriorityScheduled(PrioritisedExecutor.Priority.getPriority(priority)); -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public final PrioritisedExecutor.Priority getPriority() { -+ final int ret = this.getPriorityVolatile(); -+ if ((ret & PRIORITY_EXECUTED) != 0) { -+ return PrioritisedExecutor.Priority.COMPLETING; -+ } -+ if ((ret & PRIORITY_SCHEDULED) != 0) { -+ return this.getScheduledPriority(); -+ } -+ return PrioritisedExecutor.Priority.getPriority(ret); -+ } -+ -+ public final void lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_EXECUTED) != 0) { -+ return; -+ } -+ -+ if ((curr & PRIORITY_SCHEDULED) != 0) { -+ this.lowerPriorityScheduled(priority); -+ return; -+ } -+ -+ if (!priority.isLowerPriority(curr)) { -+ return; -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public final void setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_EXECUTED) != 0) { -+ return; -+ } -+ -+ if ((curr & PRIORITY_SCHEDULED) != 0) { -+ this.setPriorityScheduled(priority); -+ return; -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ public final void raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ int failures = 0; -+ for (int curr = this.getPriorityVolatile();;) { -+ if ((curr & PRIORITY_EXECUTED) != 0) { -+ return; -+ } -+ -+ if ((curr & PRIORITY_SCHEDULED) != 0) { -+ this.raisePriorityScheduled(priority); -+ return; -+ } -+ -+ if (!priority.isHigherPriority(curr)) { -+ return; -+ } -+ -+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) { -+ return; -+ } -+ -+ // failed, retry -+ -+ ++failures; -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ } -+ } -+ -+ protected abstract void cancelScheduled(); -+ -+ protected abstract PrioritisedExecutor.Priority getScheduledPriority(); -+ -+ protected abstract void scheduleTask(final PrioritisedExecutor.Priority priority); -+ -+ protected abstract void lowerPriorityScheduled(final PrioritisedExecutor.Priority priority); -+ -+ protected abstract void setPriorityScheduled(final PrioritisedExecutor.Priority priority); -+ -+ protected abstract void raisePriorityScheduled(final PrioritisedExecutor.Priority priority); -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ThreadedTicketLevelPropagator.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ThreadedTicketLevelPropagator.java -new file mode 100644 -index 0000000000000000000000000000000000000000..287240ed3b440f2f5733c368416e4276f626405d ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ThreadedTicketLevelPropagator.java -@@ -0,0 +1,1477 @@ -+package io.papermc.paper.chunk.system.scheduling; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import it.unimi.dsi.fastutil.HashCommon; -+import it.unimi.dsi.fastutil.longs.Long2ByteLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.Short2ByteLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.Short2ByteMap; -+import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; -+import java.lang.invoke.VarHandle; -+import java.util.ArrayDeque; -+import java.util.Arrays; -+import java.util.Iterator; -+import java.util.List; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.locks.LockSupport; -+ -+public abstract class ThreadedTicketLevelPropagator { -+ -+ // sections are 64 in length -+ public static final int SECTION_SHIFT = 6; -+ public static final int SECTION_SIZE = 1 << SECTION_SHIFT; -+ private static final int LEVEL_BITS = SECTION_SHIFT; -+ private static final int LEVEL_COUNT = 1 << LEVEL_BITS; -+ private static final int MIN_SOURCE_LEVEL = 1; -+ // we limit the max source to 62 because the depropagation code _must_ attempt to depropagate -+ // a 1 level to 0; and if a source was 63 then it may cross more than 2 sections in depropagation -+ private static final int MAX_SOURCE_LEVEL = 62; -+ -+ private final UpdateQueue updateQueue; -+ private final ConcurrentHashMap sections = new ConcurrentHashMap<>(); -+ -+ public ThreadedTicketLevelPropagator() { -+ this.updateQueue = new UpdateQueue(); -+ } -+ -+ // must hold ticket lock for: -+ // (posX & ~(SECTION_SIZE - 1), posZ & ~(SECTION_SIZE - 1)) to (posX | (SECTION_SIZE - 1), posZ | (SECTION_SIZE - 1)) -+ public void setSource(final int posX, final int posZ, final int to) { -+ if (to < 1 || to > MAX_SOURCE_LEVEL) { -+ throw new IllegalArgumentException("Source: " + to); -+ } -+ -+ final int sectionX = posX >> SECTION_SHIFT; -+ final int sectionZ = posZ >> SECTION_SHIFT; -+ -+ final Coordinate coordinate = new Coordinate(sectionX, sectionZ); -+ Section section = this.sections.get(coordinate); -+ if (section == null) { -+ if (null != this.sections.putIfAbsent(coordinate, section = new Section(sectionX, sectionZ))) { -+ throw new IllegalStateException("Race condition while creating new section"); -+ } -+ } -+ -+ final int localIdx = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ final short sLocalIdx = (short)localIdx; -+ -+ final short sourceAndLevel = section.levels[localIdx]; -+ final int currentSource = (sourceAndLevel >>> 8) & 0xFF; -+ -+ if (currentSource == to) { -+ // nothing to do -+ // make sure to kill the current update, if any -+ section.queuedSources.replace(sLocalIdx, (byte)to); -+ return; -+ } -+ -+ if (section.queuedSources.put(sLocalIdx, (byte)to) == Section.NO_QUEUED_UPDATE && section.queuedSources.size() == 1) { -+ this.queueSectionUpdate(section); -+ } -+ } -+ -+ // must hold ticket lock for: -+ // (posX & ~(SECTION_SIZE - 1), posZ & ~(SECTION_SIZE - 1)) to (posX | (SECTION_SIZE - 1), posZ | (SECTION_SIZE - 1)) -+ public void removeSource(final int posX, final int posZ) { -+ final int sectionX = posX >> SECTION_SHIFT; -+ final int sectionZ = posZ >> SECTION_SHIFT; -+ -+ final Coordinate coordinate = new Coordinate(sectionX, sectionZ); -+ final Section section = this.sections.get(coordinate); -+ -+ if (section == null) { -+ return; -+ } -+ -+ final int localIdx = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ final short sLocalIdx = (short)localIdx; -+ -+ final int currentSource = (section.levels[localIdx] >>> 8) & 0xFF; -+ -+ if (currentSource == 0) { -+ // we use replace here so that we do not possibly multi-queue a section for an update -+ section.queuedSources.replace(sLocalIdx, (byte)0); -+ return; -+ } -+ -+ if (section.queuedSources.put(sLocalIdx, (byte)0) == Section.NO_QUEUED_UPDATE && section.queuedSources.size() == 1) { -+ this.queueSectionUpdate(section); -+ } -+ } -+ -+ private void queueSectionUpdate(final Section section) { -+ this.updateQueue.append(new UpdateQueue.UpdateQueueNode(section, null)); -+ } -+ -+ public boolean hasPendingUpdates() { -+ return !this.updateQueue.isEmpty(); -+ } -+ -+ // holds ticket lock for every chunk section represented by any position in the key set -+ // updates is modifiable and passed to processSchedulingUpdates after this call -+ protected abstract void processLevelUpdates(final Long2ByteLinkedOpenHashMap updates); -+ -+ // holds ticket lock for every chunk section represented by any position in the key set -+ // holds scheduling lock in max access radius for every position held by the ticket lock -+ // updates is cleared after this call -+ protected abstract void processSchedulingUpdates(final Long2ByteLinkedOpenHashMap updates, final List scheduledTasks, -+ final List changedFullStatus); -+ -+ // must hold ticket lock for every position in the sections in one radius around sectionX,sectionZ -+ public boolean performUpdate(final int sectionX, final int sectionZ, final ReentrantAreaLock schedulingLock, -+ final List scheduledTasks, final List changedFullStatus) { -+ if (!this.hasPendingUpdates()) { -+ return false; -+ } -+ -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ final Section section = this.sections.get(coordinate); -+ -+ if (section == null || section.queuedSources.isEmpty()) { -+ // no section or no updates -+ return false; -+ } -+ -+ final Propagator propagator = Propagator.acquirePropagator(); -+ final boolean ret = this.performUpdate(section, null, propagator, -+ null, schedulingLock, scheduledTasks, changedFullStatus -+ ); -+ Propagator.returnPropagator(propagator); -+ return ret; -+ } -+ -+ private boolean performUpdate(final Section section, final UpdateQueue.UpdateQueueNode node, final Propagator propagator, -+ final ReentrantAreaLock ticketLock, final ReentrantAreaLock schedulingLock, -+ final List scheduledTasks, final List changedFullStatus) { -+ final int sectionX = section.sectionX; -+ final int sectionZ = section.sectionZ; -+ -+ final int rad1MinX = (sectionX - 1) << SECTION_SHIFT; -+ final int rad1MinZ = (sectionZ - 1) << SECTION_SHIFT; -+ final int rad1MaxX = ((sectionX + 1) << SECTION_SHIFT) | (SECTION_SIZE - 1); -+ final int rad1MaxZ = ((sectionZ + 1) << SECTION_SHIFT) | (SECTION_SIZE - 1); -+ -+ // set up encode offset first as we need to queue level changes _before_ -+ propagator.setupEncodeOffset(sectionX, sectionZ); -+ -+ final int coordinateOffset = propagator.coordinateOffset; -+ -+ final ReentrantAreaLock.Node ticketNode = ticketLock == null ? null : ticketLock.lock(rad1MinX, rad1MinZ, rad1MaxX, rad1MaxZ); -+ final boolean ret; -+ try { -+ // first, check if this update was stolen -+ if (section != this.sections.get(new Coordinate(sectionX, sectionZ))) { -+ // occurs when a stolen update deletes this section -+ // it is possible that another update is scheduled, but that one will have the correct section -+ if (node != null) { -+ this.updateQueue.remove(node); -+ } -+ return false; -+ } -+ -+ final int oldSourceSize = section.sources.size(); -+ -+ // process pending sources -+ for (final Iterator iterator = section.queuedSources.short2ByteEntrySet().fastIterator(); iterator.hasNext();) { -+ final Short2ByteMap.Entry entry = iterator.next(); -+ final int pos = (int)entry.getShortKey(); -+ final int posX = (pos & (SECTION_SIZE - 1)) | (sectionX << SECTION_SHIFT); -+ final int posZ = ((pos >> SECTION_SHIFT) & (SECTION_SIZE - 1)) | (sectionZ << SECTION_SHIFT); -+ final int newSource = (int)entry.getByteValue(); -+ -+ final short currentEncoded = section.levels[pos]; -+ final int currLevel = currentEncoded & 0xFF; -+ final int prevSource = (currentEncoded >>> 8) & 0xFF; -+ -+ if (prevSource == newSource) { -+ // nothing changed -+ continue; -+ } -+ -+ if ((prevSource < currLevel && newSource <= currLevel) || newSource == currLevel) { -+ // just update the source, don't need to propagate change -+ section.levels[pos] = (short)(currLevel | (newSource << 8)); -+ // level is unchanged, don't add to changed positions -+ } else { -+ // set current level and current source to new source -+ section.levels[pos] = (short)(newSource | (newSource << 8)); -+ // must add to updated positions in case this is final -+ propagator.updatedPositions.put(Coordinate.key(posX, posZ), (byte)newSource); -+ if (newSource != 0) { -+ // queue increase with new source level -+ propagator.appendToIncreaseQueue( -+ ((long)(posX + (posZ << Propagator.COORDINATE_BITS) + coordinateOffset) & ((1L << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) - 1)) | -+ ((newSource & (LEVEL_COUNT - 1L)) << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) | -+ (Propagator.ALL_DIRECTIONS_BITSET << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS + LEVEL_BITS)) -+ ); -+ } -+ // queue decrease with previous level -+ if (newSource < currLevel) { -+ propagator.appendToDecreaseQueue( -+ ((long)(posX + (posZ << Propagator.COORDINATE_BITS) + coordinateOffset) & ((1L << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) - 1)) | -+ ((currLevel & (LEVEL_COUNT - 1L)) << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) | -+ (Propagator.ALL_DIRECTIONS_BITSET << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS + LEVEL_BITS)) -+ ); -+ } -+ } -+ -+ if (newSource == 0) { -+ // prevSource != newSource, so we are removing this source -+ section.sources.remove((short)pos); -+ } else if (prevSource == 0) { -+ // prevSource != newSource, so we are adding this source -+ section.sources.add((short)pos); -+ } -+ } -+ -+ section.queuedSources.clear(); -+ -+ final int newSourceSize = section.sources.size(); -+ -+ if (oldSourceSize == 0 && newSourceSize != 0) { -+ // need to make sure the sections in 1 radius are initialised -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ if ((dx | dz) == 0) { -+ continue; -+ } -+ final int offX = dx + sectionX; -+ final int offZ = dz + sectionZ; -+ final Coordinate coordinate = new Coordinate(offX, offZ); -+ final Section neighbour = this.sections.computeIfAbsent(coordinate, (final Coordinate keyInMap) -> { -+ return new Section(Coordinate.x(keyInMap.key), Coordinate.z(keyInMap.key)); -+ }); -+ -+ // increase ref count -+ ++neighbour.oneRadNeighboursWithSources; -+ if (neighbour.oneRadNeighboursWithSources <= 0 || neighbour.oneRadNeighboursWithSources > 8) { -+ throw new IllegalStateException(Integer.toString(neighbour.oneRadNeighboursWithSources)); -+ } -+ } -+ } -+ } -+ -+ if (propagator.hasUpdates()) { -+ propagator.setupCaches(this, sectionX, sectionZ, 1); -+ propagator.performDecrease(); -+ // don't need try-finally, as any exception will cause the propagator to not be returned -+ propagator.destroyCaches(); -+ } -+ -+ if (newSourceSize == 0) { -+ final boolean decrementRef = oldSourceSize != 0; -+ // check for section de-init -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ final int offX = dx + sectionX; -+ final int offZ = dz + sectionZ; -+ final Coordinate coordinate = new Coordinate(offX, offZ); -+ final Section neighbour = this.sections.get(coordinate); -+ -+ if (neighbour == null) { -+ if (oldSourceSize == 0 && (dx | dz) != 0) { -+ // since we don't have sources, this section is allowed to null -+ continue; -+ } -+ throw new IllegalStateException("??"); -+ } -+ -+ if (decrementRef && (dx | dz) != 0) { -+ // decrease ref count, but only for neighbours -+ --neighbour.oneRadNeighboursWithSources; -+ } -+ -+ // we need to check the current section for de-init as well -+ if (neighbour.oneRadNeighboursWithSources == 0) { -+ if (neighbour.queuedSources.isEmpty() && neighbour.sources.isEmpty()) { -+ // need to de-init -+ this.sections.remove(coordinate); -+ } // else: neighbour is queued for an update, and it will de-init itself -+ } else if (neighbour.oneRadNeighboursWithSources < 0 || neighbour.oneRadNeighboursWithSources > 8) { -+ throw new IllegalStateException(Integer.toString(neighbour.oneRadNeighboursWithSources)); -+ } -+ } -+ } -+ } -+ -+ -+ ret = !propagator.updatedPositions.isEmpty(); -+ -+ if (ret) { -+ this.processLevelUpdates(propagator.updatedPositions); -+ -+ if (!propagator.updatedPositions.isEmpty()) { -+ // now we can actually update the ticket levels in the chunk holders -+ final int maxScheduleRadius = 2 * ChunkTaskScheduler.getMaxAccessRadius(); -+ -+ // allow the chunkholders to process ticket level updates without needing to acquire the schedule lock every time -+ final ReentrantAreaLock.Node schedulingNode = schedulingLock.lock( -+ rad1MinX - maxScheduleRadius, rad1MinZ - maxScheduleRadius, -+ rad1MaxX + maxScheduleRadius, rad1MaxZ + maxScheduleRadius -+ ); -+ try { -+ this.processSchedulingUpdates(propagator.updatedPositions, scheduledTasks, changedFullStatus); -+ } finally { -+ schedulingLock.unlock(schedulingNode); -+ } -+ } -+ -+ propagator.updatedPositions.clear(); -+ } -+ } finally { -+ if (ticketLock != null) { -+ ticketLock.unlock(ticketNode); -+ } -+ } -+ -+ // finished -+ if (node != null) { -+ this.updateQueue.remove(node); -+ } -+ -+ return ret; -+ } -+ -+ public boolean performUpdates(final ReentrantAreaLock ticketLock, final ReentrantAreaLock schedulingLock, -+ final List scheduledTasks, final List changedFullStatus) { -+ if (this.updateQueue.isEmpty()) { -+ return false; -+ } -+ -+ final long maxOrder = this.updateQueue.getLastOrder(); -+ -+ boolean updated = false; -+ Propagator propagator = null; -+ -+ for (;;) { -+ final UpdateQueue.UpdateQueueNode toUpdate = this.updateQueue.acquireNextToUpdate(maxOrder); -+ if (toUpdate == null) { -+ this.updateQueue.awaitFirst(maxOrder); -+ -+ if (!this.updateQueue.hasRemainingUpdates(maxOrder)) { -+ if (propagator != null) { -+ Propagator.returnPropagator(propagator); -+ } -+ return updated; -+ } -+ -+ continue; -+ } -+ -+ if (propagator == null) { -+ propagator = Propagator.acquirePropagator(); -+ } -+ -+ updated |= this.performUpdate(toUpdate.section, toUpdate, propagator, ticketLock, schedulingLock, scheduledTasks, changedFullStatus); -+ } -+ } -+ -+ private static final class UpdateQueue { -+ -+ private volatile UpdateQueueNode head; -+ private volatile UpdateQueueNode tail; -+ private volatile UpdateQueueNode lastUpdating; -+ -+ protected static final VarHandle HEAD_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueue.class, "head", UpdateQueueNode.class); -+ protected static final VarHandle TAIL_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueue.class, "tail", UpdateQueueNode.class); -+ protected static final VarHandle LAST_UPDATING = ConcurrentUtil.getVarHandle(UpdateQueue.class, "lastUpdating", UpdateQueueNode.class); -+ -+ /* head */ -+ -+ protected final void setHeadPlain(final UpdateQueueNode newHead) { -+ HEAD_HANDLE.set(this, newHead); -+ } -+ -+ protected final void setHeadOpaque(final UpdateQueueNode newHead) { -+ HEAD_HANDLE.setOpaque(this, newHead); -+ } -+ -+ protected final UpdateQueueNode getHeadPlain() { -+ return (UpdateQueueNode)HEAD_HANDLE.get(this); -+ } -+ -+ protected final UpdateQueueNode getHeadOpaque() { -+ return (UpdateQueueNode)HEAD_HANDLE.getOpaque(this); -+ } -+ -+ protected final UpdateQueueNode getHeadAcquire() { -+ return (UpdateQueueNode)HEAD_HANDLE.getAcquire(this); -+ } -+ -+ /* tail */ -+ -+ protected final void setTailPlain(final UpdateQueueNode newTail) { -+ TAIL_HANDLE.set(this, newTail); -+ } -+ -+ protected final void setTailOpaque(final UpdateQueueNode newTail) { -+ TAIL_HANDLE.setOpaque(this, newTail); -+ } -+ -+ protected final UpdateQueueNode getTailPlain() { -+ return (UpdateQueueNode)TAIL_HANDLE.get(this); -+ } -+ -+ protected final UpdateQueueNode getTailOpaque() { -+ return (UpdateQueueNode)TAIL_HANDLE.getOpaque(this); -+ } -+ -+ /* lastUpdating */ -+ -+ protected final UpdateQueueNode getLastUpdatingVolatile() { -+ return (UpdateQueueNode)LAST_UPDATING.getVolatile(this); -+ } -+ -+ protected final UpdateQueueNode compareAndExchangeLastUpdatingVolatile(final UpdateQueueNode expect, final UpdateQueueNode update) { -+ return (UpdateQueueNode)LAST_UPDATING.compareAndExchange(this, expect, update); -+ } -+ -+ public UpdateQueue() { -+ final UpdateQueueNode dummy = new UpdateQueueNode(null, null); -+ dummy.order = -1L; -+ dummy.preventAdds(); -+ -+ this.setHeadPlain(dummy); -+ this.setTailPlain(dummy); -+ } -+ -+ public boolean isEmpty() { -+ return this.peek() == null; -+ } -+ -+ public boolean hasRemainingUpdates(final long maxUpdate) { -+ final UpdateQueueNode node = this.peek(); -+ return node != null && node.order <= maxUpdate; -+ } -+ -+ public long getLastOrder() { -+ for (UpdateQueueNode tail = this.getTailOpaque(), curr = tail;;) { -+ final UpdateQueueNode next = curr.getNextVolatile(); -+ if (next == null) { -+ // try to update stale tail -+ if (this.getTailOpaque() == tail && curr != tail) { -+ this.setTailOpaque(curr); -+ } -+ return curr.order; -+ } -+ curr = next; -+ } -+ } -+ -+ public UpdateQueueNode acquireNextToUpdate(final long maxOrder) { -+ int failures = 0; -+ for (UpdateQueueNode prev = this.getLastUpdatingVolatile();;) { -+ UpdateQueueNode next = prev == null ? this.peek() : prev.next; -+ -+ if (next == null || next.order > maxOrder) { -+ return null; -+ } -+ -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (prev == (prev = this.compareAndExchangeLastUpdatingVolatile(prev, next))) { -+ return next; -+ } -+ -+ ++failures; -+ } -+ } -+ -+ public void awaitFirst(final long maxOrder) { -+ final UpdateQueueNode earliest = this.peek(); -+ if (earliest == null || earliest.order > maxOrder) { -+ return; -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ // we do not use add-blocking because we use the nullability of the section to block -+ // remove() does not begin to poll from the wait queue until the section is null'd, -+ // and so provided we check the nullability before parking there is no ordering of these operations -+ // such that remove() finishes polling from the wait queue while section is not null -+ earliest.add(currThread); -+ -+ // wait until completed -+ while (earliest.getSectionVolatile() != null) { -+ LockSupport.park(); -+ } -+ } -+ -+ public UpdateQueueNode peek() { -+ for (UpdateQueueNode head = this.getHeadOpaque(), curr = head;;) { -+ final UpdateQueueNode next = curr.getNextVolatile(); -+ final Section element = curr.getSectionVolatile(); /* Likely in sync */ -+ -+ if (element != null) { -+ if (this.getHeadOpaque() == head && curr != head) { -+ this.setHeadOpaque(curr); -+ } -+ return curr; -+ } -+ -+ if (next == null) { -+ if (this.getHeadOpaque() == head && curr != head) { -+ this.setHeadOpaque(curr); -+ } -+ return null; -+ } -+ curr = next; -+ } -+ } -+ -+ public void remove(final UpdateQueueNode node) { -+ // mark as removed -+ node.setSectionVolatile(null); -+ -+ // use peek to advance head -+ this.peek(); -+ -+ // unpark any waiters / block the wait queue -+ Thread unpark; -+ while ((unpark = node.poll()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ public void append(final UpdateQueueNode node) { -+ int failures = 0; -+ -+ for (UpdateQueueNode currTail = this.getTailOpaque(), curr = currTail;;) { -+ /* It has been experimentally shown that placing the read before the backoff results in significantly greater performance */ -+ /* It is likely due to a cache miss caused by another write to the next field */ -+ final UpdateQueueNode next = curr.getNextVolatile(); -+ -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (next == null) { -+ node.order = curr.order + 1L; -+ final UpdateQueueNode compared = curr.compareExchangeNextVolatile(null, node); -+ -+ if (compared == null) { -+ /* Added */ -+ /* Avoid CASing on tail more than we need to */ -+ /* CAS to avoid setting an out-of-date tail */ -+ if (this.getTailOpaque() == currTail) { -+ this.setTailOpaque(node); -+ } -+ return; -+ } -+ -+ ++failures; -+ curr = compared; -+ continue; -+ } -+ -+ if (curr == currTail) { -+ /* Tail is likely not up-to-date */ -+ curr = next; -+ } else { -+ /* Try to update to tail */ -+ if (currTail == (currTail = this.getTailOpaque())) { -+ curr = next; -+ } else { -+ curr = currTail; -+ } -+ } -+ } -+ } -+ -+ // each node also represents a set of waiters, represented by the MTQ -+ // if the queue is add-blocked, then the update is complete -+ private static final class UpdateQueueNode extends MultiThreadedQueue { -+ private long order; -+ private Section section; -+ private volatile UpdateQueueNode next; -+ -+ protected static final VarHandle SECTION_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueueNode.class, "section", Section.class); -+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueueNode.class, "next", UpdateQueueNode.class); -+ -+ public UpdateQueueNode(final Section section, final UpdateQueueNode next) { -+ SECTION_HANDLE.set(this, section); -+ NEXT_HANDLE.set(this, next); -+ } -+ -+ /* section */ -+ -+ protected final Section getSectionPlain() { -+ return (Section)SECTION_HANDLE.get(this); -+ } -+ -+ protected final Section getSectionVolatile() { -+ return (Section)SECTION_HANDLE.getVolatile(this); -+ } -+ -+ protected final void setSectionPlain(final Section update) { -+ SECTION_HANDLE.set(this, update); -+ } -+ -+ protected final void setSectionOpaque(final Section update) { -+ SECTION_HANDLE.setOpaque(this, update); -+ } -+ -+ protected final void setSectionVolatile(final Section update) { -+ SECTION_HANDLE.setVolatile(this, update); -+ } -+ -+ protected final Section getAndSetSectionVolatile(final Section update) { -+ return (Section)SECTION_HANDLE.getAndSet(this, update); -+ } -+ -+ protected final Section compareExchangeSectionVolatile(final Section expect, final Section update) { -+ return (Section)SECTION_HANDLE.compareAndExchange(this, expect, update); -+ } -+ -+ /* next */ -+ -+ protected final UpdateQueueNode getNextPlain() { -+ return (UpdateQueueNode)NEXT_HANDLE.get(this); -+ } -+ -+ protected final UpdateQueueNode getNextOpaque() { -+ return (UpdateQueueNode)NEXT_HANDLE.getOpaque(this); -+ } -+ -+ protected final UpdateQueueNode getNextAcquire() { -+ return (UpdateQueueNode)NEXT_HANDLE.getAcquire(this); -+ } -+ -+ protected final UpdateQueueNode getNextVolatile() { -+ return (UpdateQueueNode)NEXT_HANDLE.getVolatile(this); -+ } -+ -+ protected final void setNextPlain(final UpdateQueueNode next) { -+ NEXT_HANDLE.set(this, next); -+ } -+ -+ protected final void setNextVolatile(final UpdateQueueNode next) { -+ NEXT_HANDLE.setVolatile(this, next); -+ } -+ -+ protected final UpdateQueueNode compareExchangeNextVolatile(final UpdateQueueNode expect, final UpdateQueueNode set) { -+ return (UpdateQueueNode)NEXT_HANDLE.compareAndExchange(this, expect, set); -+ } -+ } -+ } -+ -+ private static final class Section { -+ -+ // upper 8 bits: sources, lower 8 bits: level -+ // if we REALLY wanted to get crazy, we could make the increase propagator use MethodHandles#byteArrayViewVarHandle -+ // to read and write the lower 8 bits of this array directly rather than reading, updating the bits, then writing back. -+ private final short[] levels = new short[SECTION_SIZE * SECTION_SIZE]; -+ // set of local positions that represent sources -+ private final ShortOpenHashSet sources = new ShortOpenHashSet(); -+ // map of local index to new source level -+ // the source level _cannot_ be updated in the backing storage immediately since the update -+ private static final byte NO_QUEUED_UPDATE = (byte)-1; -+ private final Short2ByteLinkedOpenHashMap queuedSources = new Short2ByteLinkedOpenHashMap(); -+ { -+ this.queuedSources.defaultReturnValue(NO_QUEUED_UPDATE); -+ } -+ private int oneRadNeighboursWithSources = 0; -+ -+ public final int sectionX; -+ public final int sectionZ; -+ -+ public Section(final int sectionX, final int sectionZ) { -+ this.sectionX = sectionX; -+ this.sectionZ = sectionZ; -+ } -+ -+ public boolean isZero() { -+ for (final short val : this.levels) { -+ if (val != 0) { -+ return false; -+ } -+ } -+ return true; -+ } -+ -+ @Override -+ public String toString() { -+ final StringBuilder ret = new StringBuilder(); -+ -+ for (int x = 0; x < SECTION_SIZE; ++x) { -+ ret.append("levels x=").append(x).append("\n"); -+ for (int z = 0; z < SECTION_SIZE; ++z) { -+ final short v = this.levels[x | (z << SECTION_SHIFT)]; -+ ret.append(v & 0xFF).append("."); -+ } -+ ret.append("\n"); -+ ret.append("sources x=").append(x).append("\n"); -+ for (int z = 0; z < SECTION_SIZE; ++z) { -+ final short v = this.levels[x | (z << SECTION_SHIFT)]; -+ ret.append((v >>> 8) & 0xFF).append("."); -+ } -+ ret.append("\n\n"); -+ } -+ -+ return ret.toString(); -+ } -+ } -+ -+ -+ private static final class Propagator { -+ -+ private static final ArrayDeque CACHED_PROPAGATORS = new ArrayDeque<>(); -+ private static final int MAX_PROPAGATORS = Runtime.getRuntime().availableProcessors() * 2; -+ -+ private static Propagator acquirePropagator() { -+ synchronized (CACHED_PROPAGATORS) { -+ final Propagator ret = CACHED_PROPAGATORS.pollFirst(); -+ if (ret != null) { -+ return ret; -+ } -+ } -+ return new Propagator(); -+ } -+ -+ private static void returnPropagator(final Propagator propagator) { -+ synchronized (CACHED_PROPAGATORS) { -+ if (CACHED_PROPAGATORS.size() < MAX_PROPAGATORS) { -+ CACHED_PROPAGATORS.add(propagator); -+ } -+ } -+ } -+ -+ private static final int SECTION_RADIUS = 2; -+ private static final int SECTION_CACHE_WIDTH = 2 * SECTION_RADIUS + 1; -+ // minimum number of bits to represent [0, SECTION_SIZE * SECTION_CACHE_WIDTH) -+ private static final int COORDINATE_BITS = 9; -+ private static final int COORDINATE_SIZE = 1 << COORDINATE_BITS; -+ static { -+ if ((SECTION_SIZE * SECTION_CACHE_WIDTH) > (1 << COORDINATE_BITS)) { -+ throw new IllegalStateException("Adjust COORDINATE_BITS"); -+ } -+ } -+ // index = x + (z * SECTION_CACHE_WIDTH) -+ // (this requires x >= 0 and z >= 0) -+ private final Section[] sections = new Section[SECTION_CACHE_WIDTH * SECTION_CACHE_WIDTH]; -+ -+ private int encodeOffsetX; -+ private int encodeOffsetZ; -+ -+ private int coordinateOffset; -+ -+ private int encodeSectionOffsetX; -+ private int encodeSectionOffsetZ; -+ -+ private int sectionIndexOffset; -+ -+ public final boolean hasUpdates() { -+ return this.decreaseQueueInitialLength != 0 || this.increaseQueueInitialLength != 0; -+ } -+ -+ protected final void setupEncodeOffset(final int centerSectionX, final int centerSectionZ) { -+ final int maxCoordinate = (SECTION_RADIUS * SECTION_SIZE - 1); -+ // must have that encoded >= 0 -+ // coordinates can range from [-maxCoordinate + centerSection*SECTION_SIZE, maxCoordinate + centerSection*SECTION_SIZE] -+ // we want a range of [0, maxCoordinate*2] -+ // so, 0 = -maxCoordinate + centerSection*SECTION_SIZE + offset -+ this.encodeOffsetX = maxCoordinate - (centerSectionX << SECTION_SHIFT); -+ this.encodeOffsetZ = maxCoordinate - (centerSectionZ << SECTION_SHIFT); -+ -+ // encoded coordinates range from [0, SECTION_SIZE * SECTION_CACHE_WIDTH) -+ // coordinate index = (x + encodeOffsetX) + ((z + encodeOffsetZ) << COORDINATE_BITS) -+ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << COORDINATE_BITS); -+ -+ // need encoded values to be >= 0 -+ // so, 0 = (-SECTION_RADIUS + centerSectionX) + encodeOffset -+ this.encodeSectionOffsetX = SECTION_RADIUS - centerSectionX; -+ this.encodeSectionOffsetZ = SECTION_RADIUS - centerSectionZ; -+ -+ // section index = (secX + encodeSectionOffsetX) + ((secZ + encodeSectionOffsetZ) * SECTION_CACHE_WIDTH) -+ this.sectionIndexOffset = this.encodeSectionOffsetX + (this.encodeSectionOffsetZ * SECTION_CACHE_WIDTH); -+ } -+ -+ // must hold ticket lock for (centerSectionX,centerSectionZ) in radius rad -+ // must call setupEncodeOffset -+ protected final void setupCaches(final ThreadedTicketLevelPropagator propagator, -+ final int centerSectionX, final int centerSectionZ, -+ final int rad) { -+ for (int dz = -rad; dz <= rad; ++dz) { -+ for (int dx = -rad; dx <= rad; ++dx) { -+ final int sectionX = centerSectionX + dx; -+ final int sectionZ = centerSectionZ + dz; -+ final Coordinate coordinate = new Coordinate(sectionX, sectionZ); -+ final Section section = propagator.sections.get(coordinate); -+ -+ if (section == null) { -+ throw new IllegalStateException("Section at " + coordinate + " should not be null"); -+ } -+ -+ this.setSectionInCache(sectionX, sectionZ, section); -+ } -+ } -+ } -+ -+ protected final void setSectionInCache(final int sectionX, final int sectionZ, final Section section) { -+ this.sections[sectionX + SECTION_CACHE_WIDTH*sectionZ + this.sectionIndexOffset] = section; -+ } -+ -+ protected final Section getSection(final int sectionX, final int sectionZ) { -+ return this.sections[sectionX + SECTION_CACHE_WIDTH*sectionZ + this.sectionIndexOffset]; -+ } -+ -+ protected final int getLevel(final int posX, final int posZ) { -+ final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH*(posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; -+ if (section != null) { -+ return (int)section.levels[(posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT)] & 0xFF; -+ } -+ -+ return 0; -+ } -+ -+ protected final void setLevel(final int posX, final int posZ, final int to) { -+ final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH*(posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; -+ if (section != null) { -+ final int index = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ final short level = section.levels[index]; -+ section.levels[index] = (short)((level & ~0xFF) | (to & 0xFF)); -+ this.updatedPositions.put(Coordinate.key(posX, posZ), (byte)to); -+ } -+ } -+ -+ protected final void destroyCaches() { -+ Arrays.fill(this.sections, null); -+ } -+ -+ // contains: -+ // lower (COORDINATE_BITS(9) + COORDINATE_BITS(9) = 18) bits encoded position: (x | (z << COORDINATE_BITS)) -+ // next LEVEL_BITS (6) bits: propagated level [0, 63] -+ // propagation directions bitset (16 bits): -+ protected static final long ALL_DIRECTIONS_BITSET = ( -+ // z = -1 -+ (1L << ((1 - 1) | ((1 - 1) << 2))) | -+ (1L << ((1 + 0) | ((1 - 1) << 2))) | -+ (1L << ((1 + 1) | ((1 - 1) << 2))) | -+ -+ // z = 0 -+ (1L << ((1 - 1) | ((1 + 0) << 2))) | -+ //(1L << ((1 + 0) | ((1 + 0) << 2))) | // exclude (0,0) -+ (1L << ((1 + 1) | ((1 + 0) << 2))) | -+ -+ // z = 1 -+ (1L << ((1 - 1) | ((1 + 1) << 2))) | -+ (1L << ((1 + 0) | ((1 + 1) << 2))) | -+ (1L << ((1 + 1) | ((1 + 1) << 2))) -+ ); -+ -+ private void ex(int bitset) { -+ for (int i = 0, len = Integer.bitCount(bitset); i < len; ++i) { -+ final int set = Integer.numberOfTrailingZeros(bitset); -+ final int tailingBit = (-bitset) & bitset; -+ // XOR to remove the trailing bit -+ bitset ^= tailingBit; -+ -+ // the encoded value set is (x_val) | (z_val << 2), totaling 4 bits -+ // thus, the bitset is 16 bits wide where each one represents a direction to propagate and the -+ // index of the set bit is the encoded value -+ // the encoded coordinate has 3 valid states: -+ // 0b00 (0) -> -1 -+ // 0b01 (1) -> 0 -+ // 0b10 (2) -> 1 -+ // the decode operation then is val - 1, and the encode operation is val + 1 -+ final int xOff = (set & 3) - 1; -+ final int zOff = ((set >>> 2) & 3) - 1; -+ System.out.println("Encoded: (" + xOff + "," + zOff + ")"); -+ } -+ } -+ -+ private void ch(long bs, int shift) { -+ int bitset = (int)(bs >>> shift); -+ for (int i = 0, len = Integer.bitCount(bitset); i < len; ++i) { -+ final int set = Integer.numberOfTrailingZeros(bitset); -+ final int tailingBit = (-bitset) & bitset; -+ // XOR to remove the trailing bit -+ bitset ^= tailingBit; -+ -+ // the encoded value set is (x_val) | (z_val << 2), totaling 4 bits -+ // thus, the bitset is 16 bits wide where each one represents a direction to propagate and the -+ // index of the set bit is the encoded value -+ // the encoded coordinate has 3 valid states: -+ // 0b00 (0) -> -1 -+ // 0b01 (1) -> 0 -+ // 0b10 (2) -> 1 -+ // the decode operation then is val - 1, and the encode operation is val + 1 -+ final int xOff = (set & 3) - 1; -+ final int zOff = ((set >>> 2) & 3) - 1; -+ if (Math.abs(xOff) > 1 || Math.abs(zOff) > 1 || (xOff | zOff) == 0) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ -+ // whether the increase propagator needs to write the propagated level to the position, used to avoid cascading -+ // updates for sources -+ protected static final long FLAG_WRITE_LEVEL = Long.MIN_VALUE >>> 1; -+ // whether the propagation needs to check if its current level is equal to the expected level -+ // used only in increase propagation -+ protected static final long FLAG_RECHECK_LEVEL = Long.MIN_VALUE >>> 0; -+ -+ protected long[] increaseQueue = new long[SECTION_SIZE * SECTION_SIZE * 2]; -+ protected int increaseQueueInitialLength; -+ protected long[] decreaseQueue = new long[SECTION_SIZE * SECTION_SIZE * 2]; -+ protected int decreaseQueueInitialLength; -+ -+ protected final Long2ByteLinkedOpenHashMap updatedPositions = new Long2ByteLinkedOpenHashMap(); -+ -+ protected final long[] resizeIncreaseQueue() { -+ return this.increaseQueue = Arrays.copyOf(this.increaseQueue, this.increaseQueue.length * 2); -+ } -+ -+ protected final long[] resizeDecreaseQueue() { -+ return this.decreaseQueue = Arrays.copyOf(this.decreaseQueue, this.decreaseQueue.length * 2); -+ } -+ -+ protected final void appendToIncreaseQueue(final long value) { -+ final int idx = this.increaseQueueInitialLength++; -+ long[] queue = this.increaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ queue[idx] = value; -+ return; -+ } else { -+ queue[idx] = value; -+ return; -+ } -+ } -+ -+ protected final void appendToDecreaseQueue(final long value) { -+ final int idx = this.decreaseQueueInitialLength++; -+ long[] queue = this.decreaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ queue[idx] = value; -+ return; -+ } else { -+ queue[idx] = value; -+ return; -+ } -+ } -+ -+ protected final void performIncrease() { -+ long[] queue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.increaseQueueInitialLength; -+ this.increaseQueueInitialLength = 0; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.sectionIndexOffset; -+ -+ final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; -+ final int propagatedLevel = ((int)queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); -+ // note: the above code requires coordinate bits * 2 < 32 -+ // bitset is 16 bits -+ int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); -+ -+ if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { -+ if (this.getLevel(posX, posZ) != propagatedLevel) { -+ // not at the level we expect, so something changed. -+ continue; -+ } -+ } else if ((queueValue & FLAG_WRITE_LEVEL) != 0L) { -+ // these are used to restore sources after a propagation decrease -+ this.setLevel(posX, posZ, propagatedLevel); -+ } -+ -+ // this bitset represents the values that we have not propagated to -+ // this bitset lets us determine what directions the neighbours we set should propagate to, in most cases -+ // significantly reducing the total number of ops -+ // since we propagate in a 1 radius, we need a 2 radius bitset to hold all possible values we would possibly need -+ // but if we use only 5x5 bits, then we need to use div/mod to retrieve coordinates from the bitset, so instead -+ // we use an 8x8 bitset and luckily that can be fit into only one long value (64 bits) -+ // to make things easy, we use positions [0, 4] in the bitset, with current position being 2 -+ // index = x | (z << 3) -+ -+ // to start, we eliminate everything 1 radius from the current position as the previous propagator -+ // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius -+ // but the rest not propagated are already handled -+ long currentPropagation = ~( -+ // z = -1 -+ (1L << ((2 - 1) | ((2 - 1) << 3))) | -+ (1L << ((2 + 0) | ((2 - 1) << 3))) | -+ (1L << ((2 + 1) | ((2 - 1) << 3))) | -+ -+ // z = 0 -+ (1L << ((2 - 1) | ((2 + 0) << 3))) | -+ (1L << ((2 + 0) | ((2 + 0) << 3))) | -+ (1L << ((2 + 1) | ((2 + 0) << 3))) | -+ -+ // z = 1 -+ (1L << ((2 - 1) | ((2 + 1) << 3))) | -+ (1L << ((2 + 0) | ((2 + 1) << 3))) | -+ (1L << ((2 + 1) | ((2 + 1) << 3))) -+ ); -+ -+ final int toPropagate = propagatedLevel - 1; -+ -+ // we could use while (propagateDirectionBitset != 0), but it's not a predictable branch. By counting -+ // the bits, the cpu loop predictor should perfectly predict the loop. -+ for (int l = 0, len = Integer.bitCount(propagateDirectionBitset); l < len; ++l) { -+ final int set = Integer.numberOfTrailingZeros(propagateDirectionBitset); -+ final int tailingBit = (-propagateDirectionBitset) & propagateDirectionBitset; -+ propagateDirectionBitset ^= tailingBit; -+ -+ // pDecode is from [0, 2], and 1 must be subtracted to fully decode the offset -+ // it has been split to save some cycles via parallelism -+ final int pDecodeX = (set & 3); -+ final int pDecodeZ = ((set >>> 2) & 3); -+ -+ // re-ordered -1 on the position decode into pos - 1 to occur in parallel with determining pDecodeX -+ final int offX = (posX - 1) + pDecodeX; -+ final int offZ = (posZ - 1) + pDecodeZ; -+ -+ final int sectionIndex = (offX >> SECTION_SHIFT) + ((offZ >> SECTION_SHIFT) * SECTION_CACHE_WIDTH) + sectionOffset; -+ final int localIndex = (offX & (SECTION_SIZE - 1)) | ((offZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ -+ // to retrieve a set of bits from a long value: (n_bitmask << (nstartidx)) & bitset -+ // bitset idx = x | (z << 3) -+ -+ // read three bits, so we need 7L -+ // note that generally: off - pos = (pos - 1) + pDecode - pos = pDecode - 1 -+ // nstartidx1 = x rel -1 for z rel -1 -+ // = (offX - posX - 1 + 2) | ((offZ - posZ - 1 + 2) << 3) -+ // = (pDecodeX - 1 - 1 + 2) | ((pDecodeZ - 1 - 1 + 2) << 3) -+ // = pDecodeX | (pDecodeZ << 3) = start -+ final int start = pDecodeX | (pDecodeZ << 3); -+ final long bitsetLine1 = currentPropagation & (7L << (start)); -+ -+ // nstartidx2 = x rel -1 for z rel 0 = line after line1, so we can just add 8 (row length of bitset) -+ final long bitsetLine2 = currentPropagation & (7L << (start + 8)); -+ -+ // nstartidx2 = x rel -1 for z rel 0 = line after line2, so we can just add 8 (row length of bitset) -+ final long bitsetLine3 = currentPropagation & (7L << (start + (8 + 8))); -+ -+ // remove ("take") lines from bitset -+ currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); -+ -+ // now try to propagate -+ final Section section = this.sections[sectionIndex]; -+ -+ // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag -+ final short currentStoredLevel = section.levels[localIndex]; -+ final int currentLevel = currentStoredLevel & 0xFF; -+ -+ if (currentLevel >= toPropagate) { -+ continue; // already at the level we want -+ } -+ -+ // update level -+ section.levels[localIndex] = (short)((currentStoredLevel & ~0xFF) | (toPropagate & 0xFF)); -+ updatedPositions.putAndMoveToLast(Coordinate.key(offX, offZ), (byte)toPropagate); -+ -+ // queue next -+ if (toPropagate > 1) { -+ // now combine into one bitset to pass to child -+ // the child bitset is 4x4, so we just shift each line by 4 -+ // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value -+ final long childPropagation = -+ ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 -+ ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 -+ ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 -+ -+ // don't queue update if toPropagate cannot propagate anything to neighbours -+ // (for increase, propagating 0 to neighbours is useless) -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((toPropagate & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | -+ childPropagation; //(ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); -+ continue; -+ } -+ continue; -+ } -+ } -+ } -+ -+ protected final void performDecrease() { -+ long[] queue = this.decreaseQueue; -+ long[] increaseQueue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.decreaseQueueInitialLength; -+ this.decreaseQueueInitialLength = 0; -+ int increaseQueueLength = this.increaseQueueInitialLength; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.sectionIndexOffset; -+ -+ final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; -+ final int propagatedLevel = ((int)queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); -+ // note: the above code requires coordinate bits * 2 < 32 -+ // bitset is 16 bits -+ int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); -+ -+ // this bitset represents the values that we have not propagated to -+ // this bitset lets us determine what directions the neighbours we set should propagate to, in most cases -+ // significantly reducing the total number of ops -+ // since we propagate in a 1 radius, we need a 2 radius bitset to hold all possible values we would possibly need -+ // but if we use only 5x5 bits, then we need to use div/mod to retrieve coordinates from the bitset, so instead -+ // we use an 8x8 bitset and luckily that can be fit into only one long value (64 bits) -+ // to make things easy, we use positions [0, 4] in the bitset, with current position being 2 -+ // index = x | (z << 3) -+ -+ // to start, we eliminate everything 1 radius from the current position as the previous propagator -+ // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius -+ // but the rest not propagated are already handled -+ long currentPropagation = ~( -+ // z = -1 -+ (1L << ((2 - 1) | ((2 - 1) << 3))) | -+ (1L << ((2 + 0) | ((2 - 1) << 3))) | -+ (1L << ((2 + 1) | ((2 - 1) << 3))) | -+ -+ // z = 0 -+ (1L << ((2 - 1) | ((2 + 0) << 3))) | -+ (1L << ((2 + 0) | ((2 + 0) << 3))) | -+ (1L << ((2 + 1) | ((2 + 0) << 3))) | -+ -+ // z = 1 -+ (1L << ((2 - 1) | ((2 + 1) << 3))) | -+ (1L << ((2 + 0) | ((2 + 1) << 3))) | -+ (1L << ((2 + 1) | ((2 + 1) << 3))) -+ ); -+ -+ final int toPropagate = propagatedLevel - 1; -+ -+ // we could use while (propagateDirectionBitset != 0), but it's not a predictable branch. By counting -+ // the bits, the cpu loop predictor should perfectly predict the loop. -+ for (int l = 0, len = Integer.bitCount(propagateDirectionBitset); l < len; ++l) { -+ final int set = Integer.numberOfTrailingZeros(propagateDirectionBitset); -+ final int tailingBit = (-propagateDirectionBitset) & propagateDirectionBitset; -+ propagateDirectionBitset ^= tailingBit; -+ -+ -+ // pDecode is from [0, 2], and 1 must be subtracted to fully decode the offset -+ // it has been split to save some cycles via parallelism -+ final int pDecodeX = (set & 3); -+ final int pDecodeZ = ((set >>> 2) & 3); -+ -+ // re-ordered -1 on the position decode into pos - 1 to occur in parallel with determining pDecodeX -+ final int offX = (posX - 1) + pDecodeX; -+ final int offZ = (posZ - 1) + pDecodeZ; -+ -+ final int sectionIndex = (offX >> SECTION_SHIFT) + ((offZ >> SECTION_SHIFT) * SECTION_CACHE_WIDTH) + sectionOffset; -+ final int localIndex = (offX & (SECTION_SIZE - 1)) | ((offZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ -+ // to retrieve a set of bits from a long value: (n_bitmask << (nstartidx)) & bitset -+ // bitset idx = x | (z << 3) -+ -+ // read three bits, so we need 7L -+ // note that generally: off - pos = (pos - 1) + pDecode - pos = pDecode - 1 -+ // nstartidx1 = x rel -1 for z rel -1 -+ // = (offX - posX - 1 + 2) | ((offZ - posZ - 1 + 2) << 3) -+ // = (pDecodeX - 1 - 1 + 2) | ((pDecodeZ - 1 - 1 + 2) << 3) -+ // = pDecodeX | (pDecodeZ << 3) = start -+ final int start = pDecodeX | (pDecodeZ << 3); -+ final long bitsetLine1 = currentPropagation & (7L << (start)); -+ -+ // nstartidx2 = x rel -1 for z rel 0 = line after line1, so we can just add 8 (row length of bitset) -+ final long bitsetLine2 = currentPropagation & (7L << (start + 8)); -+ -+ // nstartidx2 = x rel -1 for z rel 0 = line after line2, so we can just add 8 (row length of bitset) -+ final long bitsetLine3 = currentPropagation & (7L << (start + (8 + 8))); -+ -+ // now try to propagate -+ final Section section = this.sections[sectionIndex]; -+ -+ // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag -+ final short currentStoredLevel = section.levels[localIndex]; -+ final int currentLevel = currentStoredLevel & 0xFF; -+ final int sourceLevel = (currentStoredLevel >>> 8) & 0xFF; -+ -+ if (currentLevel == 0) { -+ continue; // already at the level we want -+ } -+ -+ if (currentLevel > toPropagate) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((currentLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | -+ (FLAG_RECHECK_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); -+ continue; -+ } -+ -+ // remove ("take") lines from bitset -+ // can't do this during decrease, TODO WHY? -+ //currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); -+ -+ // update level -+ section.levels[localIndex] = (short)((currentStoredLevel & ~0xFF)); -+ updatedPositions.putAndMoveToLast(Coordinate.key(offX, offZ), (byte)0); -+ -+ if (sourceLevel != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((sourceLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | -+ (FLAG_WRITE_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); -+ } -+ -+ // queue next -+ // note: targetLevel > 0 here, since toPropagate >= currentLevel and currentLevel > 0 -+ // now combine into one bitset to pass to child -+ // the child bitset is 4x4, so we just shift each line by 4 -+ // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value -+ final long childPropagation = -+ ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 -+ ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 -+ ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 -+ -+ // don't queue update if toPropagate cannot propagate anything to neighbours -+ // (for increase, propagating 0 to neighbours is useless) -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((toPropagate & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | -+ (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); //childPropagation; -+ continue; -+ } -+ } -+ -+ // propagate sources we clobbered -+ this.increaseQueueInitialLength = increaseQueueLength; -+ this.performIncrease(); -+ } -+ } -+ -+ private static final class Coordinate implements Comparable { -+ -+ public final long key; -+ -+ public Coordinate(final long key) { -+ this.key = key; -+ } -+ -+ public Coordinate(final int x, final int z) { -+ this.key = key(x, z); -+ } -+ -+ public static long key(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public static int x(final long key) { -+ return (int)key; -+ } -+ -+ public static int z(final long key) { -+ return (int)(key >>> 32); -+ } -+ -+ @Override -+ public int hashCode() { -+ return (int)HashCommon.mix(this.key); -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (!(obj instanceof Coordinate other)) { -+ return false; -+ } -+ -+ return this.key == other.key; -+ } -+ -+ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain -+ // is too large. So we should implement compareTo to help. -+ @Override -+ public int compareTo(final Coordinate other) { -+ return Long.compare(this.key, other.key); -+ } -+ -+ @Override -+ public String toString() { -+ return "[" + x(this.key) + "," + z(this.key) + "]"; -+ } -+ } -+ -+ /* -+ private static final java.util.Random random = new java.util.Random(4L); -+ private static final List> walkers = -+ new java.util.ArrayList<>(); -+ static final int PLAYERS = 0; -+ static final int RAD_BLOCKS = 10000; -+ static final int RAD = RAD_BLOCKS >> 4; -+ static final int RAD_BIG_BLOCKS = 100_000; -+ static final int RAD_BIG = RAD_BIG_BLOCKS >> 4; -+ static final int VD = 4; -+ static final int BIG_PLAYERS = 50; -+ static final double WALK_CHANCE = 0.10; -+ static final double TP_CHANCE = 0.01; -+ static final int TP_BACK_PLAYERS = 200; -+ static final double TP_BACK_CHANCE = 0.25; -+ static final double TP_STEAL_CHANCE = 0.25; -+ private static final List> tpBack = -+ new java.util.ArrayList<>(); -+ -+ public static void main(final String[] args) { -+ final ReentrantAreaLock ticketLock = new ReentrantAreaLock(SECTION_SHIFT); -+ final ReentrantAreaLock schedulingLock = new ReentrantAreaLock(SECTION_SHIFT); -+ final Long2ByteLinkedOpenHashMap levelMap = new Long2ByteLinkedOpenHashMap(); -+ final Long2ByteLinkedOpenHashMap refMap = new Long2ByteLinkedOpenHashMap(); -+ final io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D ref = new io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D((final long coordinate, final byte oldLevel, final byte newLevel) -> { -+ if (newLevel == 0) { -+ refMap.remove(coordinate); -+ } else { -+ refMap.put(coordinate, newLevel); -+ } -+ }); -+ final ThreadedTicketLevelPropagator propagator = new ThreadedTicketLevelPropagator() { -+ @Override -+ protected void processLevelUpdates(Long2ByteLinkedOpenHashMap updates) { -+ for (final long key : updates.keySet()) { -+ final byte val = updates.get(key); -+ if (val == 0) { -+ levelMap.remove(key); -+ } else { -+ levelMap.put(key, val); -+ } -+ } -+ } -+ -+ @Override -+ protected void processSchedulingUpdates(Long2ByteLinkedOpenHashMap updates, List scheduledTasks, List changedFullStatus) {} -+ }; -+ -+ for (;;) { -+ if (walkers.isEmpty() && tpBack.isEmpty()) { -+ for (int i = 0; i < PLAYERS; ++i) { -+ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD; -+ int posX = random.nextInt(-rad, rad + 1); -+ int posZ = random.nextInt(-rad, rad + 1); -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<>(null) { -+ @Override -+ protected void addCallback(Void parameter, int chunkX, int chunkZ) { -+ int src = 45 - 31 + 1; -+ ref.setSource(chunkX, chunkZ, src); -+ propagator.setSource(chunkX, chunkZ, src); -+ } -+ -+ @Override -+ protected void removeCallback(Void parameter, int chunkX, int chunkZ) { -+ ref.removeSource(chunkX, chunkZ); -+ propagator.removeSource(chunkX, chunkZ); -+ } -+ }; -+ -+ map.add(posX, posZ, VD); -+ -+ walkers.add(map); -+ } -+ for (int i = 0; i < TP_BACK_PLAYERS; ++i) { -+ int rad = RAD_BIG; -+ int posX = random.nextInt(-rad, rad + 1); -+ int posZ = random.nextInt(-rad, rad + 1); -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<>(null) { -+ @Override -+ protected void addCallback(Void parameter, int chunkX, int chunkZ) { -+ int src = 45 - 31 + 1; -+ ref.setSource(chunkX, chunkZ, src); -+ propagator.setSource(chunkX, chunkZ, src); -+ } -+ -+ @Override -+ protected void removeCallback(Void parameter, int chunkX, int chunkZ) { -+ ref.removeSource(chunkX, chunkZ); -+ propagator.removeSource(chunkX, chunkZ); -+ } -+ }; -+ -+ map.add(posX, posZ, random.nextInt(1, 63)); -+ -+ tpBack.add(map); -+ } -+ } else { -+ for (int i = 0; i < PLAYERS; ++i) { -+ if (random.nextDouble() > WALK_CHANCE) { -+ continue; -+ } -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = walkers.get(i); -+ -+ int updateX = random.nextInt(-1, 2); -+ int updateZ = random.nextInt(-1, 2); -+ -+ map.update(map.lastChunkX + updateX, map.lastChunkZ + updateZ, VD); -+ } -+ -+ for (int i = 0; i < PLAYERS; ++i) { -+ if (random.nextDouble() > TP_CHANCE) { -+ continue; -+ } -+ -+ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD; -+ int posX = random.nextInt(-rad, rad + 1); -+ int posZ = random.nextInt(-rad, rad + 1); -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = walkers.get(i); -+ -+ map.update(posX, posZ, VD); -+ } -+ -+ for (int i = 0; i < TP_BACK_PLAYERS; ++i) { -+ if (random.nextDouble() > TP_BACK_CHANCE) { -+ continue; -+ } -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = tpBack.get(i); -+ -+ map.update(-map.lastChunkX, -map.lastChunkZ, random.nextInt(1, 63)); -+ -+ if (random.nextDouble() > TP_STEAL_CHANCE) { -+ propagator.performUpdate( -+ map.lastChunkX >> SECTION_SHIFT, map.lastChunkZ >> SECTION_SHIFT, schedulingLock, null, null -+ ); -+ propagator.performUpdate( -+ (-map.lastChunkX >> SECTION_SHIFT), (-map.lastChunkZ >> SECTION_SHIFT), schedulingLock, null, null -+ ); -+ } -+ } -+ } -+ -+ ref.propagateUpdates(); -+ propagator.performUpdates(ticketLock, schedulingLock, null, null); -+ -+ if (!refMap.equals(levelMap)) { -+ throw new IllegalStateException("Error!"); -+ } -+ } -+ } -+ */ -+} -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/queue/RadiusAwarePrioritisedExecutor.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/queue/RadiusAwarePrioritisedExecutor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f7b0e2564ac4bd2db1d2b2bdc230c9f52f8a21b7 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/queue/RadiusAwarePrioritisedExecutor.java -@@ -0,0 +1,667 @@ -+package io.papermc.paper.chunk.system.scheduling.queue; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import io.papermc.paper.util.CoordinateUtils; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import java.util.ArrayList; -+import java.util.Comparator; -+import java.util.List; -+import java.util.PriorityQueue; -+ -+public class RadiusAwarePrioritisedExecutor { -+ -+ private static final Comparator DEPENDENCY_NODE_COMPARATOR = (final DependencyNode t1, final DependencyNode t2) -> { -+ return Long.compare(t1.id, t2.id); -+ }; -+ -+ private final DependencyTree[] queues = new DependencyTree[PrioritisedExecutor.Priority.TOTAL_SCHEDULABLE_PRIORITIES]; -+ private static final int NO_TASKS_QUEUED = -1; -+ private int selectedQueue = NO_TASKS_QUEUED; -+ private boolean canQueueTasks = true; -+ -+ public RadiusAwarePrioritisedExecutor(final PrioritisedExecutor executor, final int maxToSchedule) { -+ for (int i = 0; i < this.queues.length; ++i) { -+ this.queues[i] = new DependencyTree(this, executor, maxToSchedule, i); -+ } -+ } -+ -+ private boolean canQueueTasks() { -+ return this.canQueueTasks; -+ } -+ -+ private List treeFinished() { -+ this.canQueueTasks = true; -+ for (int priority = 0; priority < this.queues.length; ++priority) { -+ final DependencyTree queue = this.queues[priority]; -+ if (queue.hasWaitingTasks()) { -+ final List ret = queue.tryPushTasks(); -+ -+ if (ret == null || ret.isEmpty()) { -+ // this happens when the tasks in the wait queue were purged -+ // in this case, the queue was actually empty, we just had to purge it -+ // if we set the selected queue without scheduling any tasks, the queue will never be unselected -+ // as that requires a scheduled task completing... -+ continue; -+ } -+ -+ this.selectedQueue = priority; -+ return ret; -+ } -+ } -+ -+ this.selectedQueue = NO_TASKS_QUEUED; -+ -+ return null; -+ } -+ -+ private List queue(final Task task, final PrioritisedExecutor.Priority priority) { -+ final int priorityId = priority.priority; -+ final DependencyTree queue = this.queues[priorityId]; -+ -+ final DependencyNode node = new DependencyNode(task, queue); -+ -+ if (task.dependencyNode != null) { -+ throw new IllegalStateException(); -+ } -+ task.dependencyNode = node; -+ -+ queue.pushNode(node); -+ -+ if (this.selectedQueue == NO_TASKS_QUEUED) { -+ this.canQueueTasks = true; -+ this.selectedQueue = priorityId; -+ return queue.tryPushTasks(); -+ } -+ -+ if (!this.canQueueTasks) { -+ return null; -+ } -+ -+ if (PrioritisedExecutor.Priority.isHigherPriority(priorityId, this.selectedQueue)) { -+ // prevent the lower priority tree from queueing more tasks -+ this.canQueueTasks = false; -+ return null; -+ } -+ -+ // priorityId != selectedQueue: lower priority, don't care - treeFinished will pick it up -+ return priorityId == this.selectedQueue ? queue.tryPushTasks() : null; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run, final PrioritisedExecutor.Priority priority) { -+ if (radius < 0) { -+ throw new IllegalArgumentException("Radius must be > 0: " + radius); -+ } -+ return new Task(this, chunkX, chunkZ, radius, run, priority); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run) { -+ return this.createTask(chunkX, chunkZ, radius, run, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run, final PrioritisedExecutor.Priority priority) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run, priority); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ return new Task(this, 0, 0, -1, run, priority); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run) { -+ return this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, priority); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ // all accesses must be synchronised by the radius aware object -+ private static final class DependencyTree { -+ -+ private final RadiusAwarePrioritisedExecutor scheduler; -+ private final PrioritisedExecutor executor; -+ private final int maxToSchedule; -+ private final int treeIndex; -+ -+ private int currentlyExecuting; -+ private long idGenerator; -+ -+ private final PriorityQueue awaiting = new PriorityQueue<>(DEPENDENCY_NODE_COMPARATOR); -+ -+ private final PriorityQueue infiniteRadius = new PriorityQueue<>(DEPENDENCY_NODE_COMPARATOR); -+ private boolean isInfiniteRadiusScheduled; -+ -+ private final Long2ReferenceOpenHashMap nodeByPosition = new Long2ReferenceOpenHashMap<>(); -+ -+ public DependencyTree(final RadiusAwarePrioritisedExecutor scheduler, final PrioritisedExecutor executor, -+ final int maxToSchedule, final int treeIndex) { -+ this.scheduler = scheduler; -+ this.executor = executor; -+ this.maxToSchedule = maxToSchedule; -+ this.treeIndex = treeIndex; -+ } -+ -+ public boolean hasWaitingTasks() { -+ return !this.awaiting.isEmpty() || !this.infiniteRadius.isEmpty(); -+ } -+ -+ private long nextId() { -+ return this.idGenerator++; -+ } -+ -+ private boolean isExecutingAnyTasks() { -+ return this.currentlyExecuting != 0; -+ } -+ -+ private void pushNode(final DependencyNode node) { -+ if (!node.task.isFiniteRadius()) { -+ this.infiniteRadius.add(node); -+ return; -+ } -+ -+ // set up dependency for node -+ final Task task = node.task; -+ -+ final int centerX = task.chunkX; -+ final int centerZ = task.chunkZ; -+ final int radius = task.radius; -+ -+ final int minX = centerX - radius; -+ final int maxX = centerX + radius; -+ -+ final int minZ = centerZ - radius; -+ final int maxZ = centerZ + radius; -+ -+ ReferenceOpenHashSet parents = null; -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final DependencyNode dependency = this.nodeByPosition.put(CoordinateUtils.getChunkKey(currX, currZ), node); -+ if (dependency != null) { -+ if (parents == null) { -+ parents = new ReferenceOpenHashSet<>(); -+ } -+ if (parents.add(dependency)) { -+ // added a dependency, so we need to add as a child to the dependency -+ if (dependency.children == null) { -+ dependency.children = new ArrayList<>(); -+ } -+ dependency.children.add(node); -+ } -+ } -+ } -+ } -+ -+ if (parents == null) { -+ // no dependencies, add straight to awaiting -+ this.awaiting.add(node); -+ } else { -+ node.parents = parents.size(); -+ // we will be added to awaiting once we have no parents -+ } -+ } -+ -+ // called only when a node is returned after being executed -+ private List returnNode(final DependencyNode node) { -+ final Task task = node.task; -+ -+ // now that the task is completed, we can push its children to the awaiting queue -+ this.pushChildren(node); -+ -+ if (task.isFiniteRadius()) { -+ // remove from dependency map -+ this.removeNodeFromMap(node); -+ } else { -+ // mark as no longer executing infinite radius -+ if (!this.isInfiniteRadiusScheduled) { -+ throw new IllegalStateException(); -+ } -+ this.isInfiniteRadiusScheduled = false; -+ } -+ -+ // decrement executing count, we are done executing this task -+ --this.currentlyExecuting; -+ -+ if (this.currentlyExecuting == 0) { -+ return this.scheduler.treeFinished(); -+ } -+ -+ return this.scheduler.canQueueTasks() ? this.tryPushTasks() : null; -+ } -+ -+ private List tryPushTasks() { -+ // tasks are not queued, but only created here - we do hold the lock for the map -+ List ret = null; -+ PrioritisedExecutor.PrioritisedTask pushedTask; -+ while ((pushedTask = this.tryPushTask()) != null) { -+ if (ret == null) { -+ ret = new ArrayList<>(); -+ } -+ ret.add(pushedTask); -+ } -+ -+ return ret; -+ } -+ -+ private void removeNodeFromMap(final DependencyNode node) { -+ final Task task = node.task; -+ -+ final int centerX = task.chunkX; -+ final int centerZ = task.chunkZ; -+ final int radius = task.radius; -+ -+ final int minX = centerX - radius; -+ final int maxX = centerX + radius; -+ -+ final int minZ = centerZ - radius; -+ final int maxZ = centerZ + radius; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ this.nodeByPosition.remove(CoordinateUtils.getChunkKey(currX, currZ), node); -+ } -+ } -+ } -+ -+ private void pushChildren(final DependencyNode node) { -+ // add all the children that we can into awaiting -+ final List children = node.children; -+ if (children != null) { -+ for (int i = 0, len = children.size(); i < len; ++i) { -+ final DependencyNode child = children.get(i); -+ int newParents = --child.parents; -+ if (newParents == 0) { -+ // no more dependents, we can push to awaiting -+ // even if the child is purged, we need to push it so that its children will be pushed -+ this.awaiting.add(child); -+ } else if (newParents < 0) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ } -+ -+ private DependencyNode pollAwaiting() { -+ final DependencyNode ret = this.awaiting.poll(); -+ if (ret == null) { -+ return ret; -+ } -+ -+ if (ret.parents != 0) { -+ throw new IllegalStateException(); -+ } -+ -+ if (ret.purged) { -+ // need to manually remove from state here -+ this.pushChildren(ret); -+ this.removeNodeFromMap(ret); -+ } // else: delay children push until the task has finished -+ -+ return ret; -+ } -+ -+ private DependencyNode pollInfinite() { -+ return this.infiniteRadius.poll(); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask tryPushTask() { -+ if (this.currentlyExecuting >= this.maxToSchedule || this.isInfiniteRadiusScheduled) { -+ return null; -+ } -+ -+ DependencyNode firstInfinite; -+ while ((firstInfinite = this.infiniteRadius.peek()) != null && firstInfinite.purged) { -+ this.pollInfinite(); -+ } -+ -+ DependencyNode firstAwaiting; -+ while ((firstAwaiting = this.awaiting.peek()) != null && firstAwaiting.purged) { -+ this.pollAwaiting(); -+ } -+ -+ if (firstInfinite == null && firstAwaiting == null) { -+ return null; -+ } -+ -+ // firstAwaiting compared to firstInfinite -+ final int compare; -+ -+ if (firstAwaiting == null) { -+ // we choose first infinite, or infinite < awaiting -+ compare = 1; -+ } else if (firstInfinite == null) { -+ // we choose first awaiting, or awaiting < infinite -+ compare = -1; -+ } else { -+ compare = DEPENDENCY_NODE_COMPARATOR.compare(firstAwaiting, firstInfinite); -+ } -+ -+ if (compare >= 0) { -+ if (this.currentlyExecuting != 0) { -+ // don't queue infinite task while other tasks are executing in parallel -+ return null; -+ } -+ ++this.currentlyExecuting; -+ this.pollInfinite(); -+ this.isInfiniteRadiusScheduled = true; -+ return firstInfinite.task.pushTask(this.executor); -+ } else { -+ ++this.currentlyExecuting; -+ this.pollAwaiting(); -+ return firstAwaiting.task.pushTask(this.executor); -+ } -+ } -+ } -+ -+ private static final class DependencyNode { -+ -+ private final Task task; -+ private final DependencyTree tree; -+ -+ // dependency tree fields -+ // (must hold lock on the scheduler to use) -+ // null is the same as empty, we just use it so that we don't allocate the set unless we need to -+ private List children; -+ // 0 indicates that this task is considered "awaiting" -+ private int parents; -+ // false -> scheduled and not cancelled -+ // true -> scheduled but cancelled -+ private boolean purged; -+ private final long id; -+ -+ public DependencyNode(final Task task, final DependencyTree tree) { -+ this.task = task; -+ this.id = tree.nextId(); -+ this.tree = tree; -+ } -+ } -+ -+ private static final class Task implements PrioritisedExecutor.PrioritisedTask, Runnable { -+ -+ // task specific fields -+ private final RadiusAwarePrioritisedExecutor scheduler; -+ private final int chunkX; -+ private final int chunkZ; -+ private final int radius; -+ private Runnable run; -+ private PrioritisedExecutor.Priority priority; -+ -+ private DependencyNode dependencyNode; -+ private PrioritisedExecutor.PrioritisedTask queuedTask; -+ -+ private Task(final RadiusAwarePrioritisedExecutor scheduler, final int chunkX, final int chunkZ, final int radius, -+ final Runnable run, final PrioritisedExecutor.Priority priority) { -+ this.scheduler = scheduler; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.radius = radius; -+ this.run = run; -+ this.priority = priority; -+ } -+ -+ private boolean isFiniteRadius() { -+ return this.radius >= 0; -+ } -+ -+ private PrioritisedExecutor.PrioritisedTask pushTask(final PrioritisedExecutor executor) { -+ return this.queuedTask = executor.createTask(this, this.priority); -+ } -+ -+ private void executeTask() { -+ final Runnable run = this.run; -+ this.run = null; -+ run.run(); -+ } -+ -+ private static void scheduleTasks(final List toSchedule) { -+ if (toSchedule != null) { -+ for (int i = 0, len = toSchedule.size(); i < len; ++i) { -+ toSchedule.get(i).queue(); -+ } -+ } -+ } -+ -+ private void returnNode() { -+ final List toSchedule; -+ synchronized (this.scheduler) { -+ final DependencyNode node = this.dependencyNode; -+ this.dependencyNode = null; -+ toSchedule = node.tree.returnNode(node); -+ } -+ -+ scheduleTasks(toSchedule); -+ } -+ -+ @Override -+ public void run() { -+ final Runnable run = this.run; -+ this.run = null; -+ try { -+ run.run(); -+ } finally { -+ this.returnNode(); -+ } -+ } -+ -+ @Override -+ public boolean queue() { -+ final List toSchedule; -+ synchronized (this.scheduler) { -+ if (this.queuedTask != null || this.dependencyNode != null || this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ toSchedule = this.scheduler.queue(this, this.priority); -+ } -+ -+ scheduleTasks(toSchedule); -+ return true; -+ } -+ -+ @Override -+ public boolean cancel() { -+ final PrioritisedExecutor.PrioritisedTask task; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ this.priority = PrioritisedExecutor.Priority.COMPLETING; -+ if (this.dependencyNode != null) { -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ } -+ -+ return true; -+ } -+ } -+ -+ if (task.cancel()) { -+ // must manually return the node -+ this.run = null; -+ this.returnNode(); -+ return true; -+ } -+ return false; -+ } -+ -+ @Override -+ public boolean execute() { -+ final PrioritisedExecutor.PrioritisedTask task; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ this.priority = PrioritisedExecutor.Priority.COMPLETING; -+ if (this.dependencyNode != null) { -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ } -+ // fall through to execution logic -+ } -+ } -+ -+ if (task != null) { -+ // will run the return node logic automatically -+ return task.execute(); -+ } else { -+ // don't run node removal/insertion logic, we aren't actually removed from the dependency tree -+ this.executeTask(); -+ return true; -+ } -+ } -+ -+ @Override -+ public PrioritisedExecutor.Priority getPriority() { -+ final PrioritisedExecutor.PrioritisedTask task; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ return this.priority; -+ } -+ } -+ -+ return task.getPriority(); -+ } -+ -+ @Override -+ public boolean setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ final PrioritisedExecutor.PrioritisedTask task; -+ List toSchedule = null; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (this.priority == priority) { -+ return true; -+ } -+ -+ this.priority = priority; -+ if (this.dependencyNode != null) { -+ // need to re-insert node -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ toSchedule = this.scheduler.queue(this, priority); -+ } -+ } -+ } -+ -+ if (task != null) { -+ return task.setPriority(priority); -+ } -+ -+ scheduleTasks(toSchedule); -+ -+ return true; -+ } -+ -+ @Override -+ public boolean raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ final PrioritisedExecutor.PrioritisedTask task; -+ List toSchedule = null; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (this.priority.isHigherOrEqualPriority(priority)) { -+ return true; -+ } -+ -+ this.priority = priority; -+ if (this.dependencyNode != null) { -+ // need to re-insert node -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ toSchedule = this.scheduler.queue(this, priority); -+ } -+ } -+ } -+ -+ if (task != null) { -+ return task.raisePriority(priority); -+ } -+ -+ scheduleTasks(toSchedule); -+ -+ return true; -+ } -+ -+ @Override -+ public boolean lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ final PrioritisedExecutor.PrioritisedTask task; -+ List toSchedule = null; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (this.priority.isLowerOrEqualPriority(priority)) { -+ return true; -+ } -+ -+ this.priority = priority; -+ if (this.dependencyNode != null) { -+ // need to re-insert node -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ toSchedule = this.scheduler.queue(this, priority); -+ } -+ } -+ } -+ -+ if (task != null) { -+ return task.lowerPriority(priority); -+ } -+ -+ scheduleTasks(toSchedule); -+ -+ return true; -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index e47fb2aa5e885162cae5cbfc9f33ff7864bf538e..b68b37274f22c2a89d723aec4d1c6be813eef73c 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -43,6 +43,7 @@ public final class PaperCommand extends Command { - commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); - commands.put(Set.of("dumplisteners"), new DumpListenersCommand()); - commands.put(Set.of("fixlight"), new FixLightCommand()); -+ commands.put(Set.of("debug", "chunkinfo", "holderinfo"), new ChunkDebugCommand()); - - return commands.entrySet().stream() - .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) -diff --git a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..962d3cae6340fc11607b59355e291629618f289c ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java -@@ -0,0 +1,265 @@ -+package io.papermc.paper.command.subcommands; -+ -+import io.papermc.paper.command.CommandUtil; -+import io.papermc.paper.command.PaperSubcommand; -+import java.io.File; -+import java.time.LocalDateTime; -+import java.time.format.DateTimeFormatter; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.List; -+import java.util.Locale; -+import io.papermc.paper.util.MCUtil; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.FullChunkStatus; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ImposterProtoChunk; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.ProtoChunk; -+import org.bukkit.Bukkit; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.BLUE; -+import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; -+import static net.kyori.adventure.text.format.NamedTextColor.GREEN; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+ -+@DefaultQualifier(NonNull.class) -+public final class ChunkDebugCommand implements PaperSubcommand { -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ switch (subCommand) { -+ case "debug" -> this.doDebug(sender, args); -+ case "chunkinfo" -> this.doChunkInfo(sender, args); -+ case "holderinfo" -> this.doHolderInfo(sender, args); -+ } -+ return true; -+ } -+ -+ @Override -+ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { -+ switch (subCommand) { -+ case "debug" -> { -+ if (args.length == 1) { -+ return CommandUtil.getListMatchingLast(sender, args, "help", "chunks"); -+ } -+ } -+ case "holderinfo" -> { -+ List worldNames = new ArrayList<>(); -+ worldNames.add("*"); -+ for (org.bukkit.World world : Bukkit.getWorlds()) { -+ worldNames.add(world.getName()); -+ } -+ if (args.length == 1) { -+ return CommandUtil.getListMatchingLast(sender, args, worldNames); -+ } -+ } -+ case "chunkinfo" -> { -+ List worldNames = new ArrayList<>(); -+ worldNames.add("*"); -+ for (org.bukkit.World world : Bukkit.getWorlds()) { -+ worldNames.add(world.getName()); -+ } -+ if (args.length == 1) { -+ return CommandUtil.getListMatchingLast(sender, args, worldNames); -+ } -+ } -+ } -+ return Collections.emptyList(); -+ } -+ -+ private void doChunkInfo(final CommandSender sender, final String[] args) { -+ List worlds; -+ if (args.length < 1 || args[0].equals("*")) { -+ worlds = Bukkit.getWorlds(); -+ } else { -+ worlds = new ArrayList<>(args.length); -+ for (final String arg : args) { -+ org.bukkit.@Nullable World world = Bukkit.getWorld(arg); -+ if (world == null) { -+ sender.sendMessage(text("World '" + arg + "' is invalid", RED)); -+ return; -+ } -+ worlds.add(world); -+ } -+ } -+ -+ int accumulatedTotal = 0; -+ int accumulatedInactive = 0; -+ int accumulatedBorder = 0; -+ int accumulatedTicking = 0; -+ int accumulatedEntityTicking = 0; -+ -+ for (final org.bukkit.World bukkitWorld : worlds) { -+ final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); -+ -+ int total = 0; -+ int inactive = 0; -+ int full = 0; -+ int blockTicking = 0; -+ int entityTicking = 0; -+ -+ for (final ChunkHolder chunk : io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(world)) { -+ if (chunk.getFullChunkNowUnchecked() == null) { -+ continue; -+ } -+ -+ ++total; -+ -+ FullChunkStatus state = chunk.getFullStatus(); -+ -+ switch (state) { -+ case INACCESSIBLE -> ++inactive; -+ case FULL -> ++full; -+ case BLOCK_TICKING -> ++blockTicking; -+ case ENTITY_TICKING -> ++entityTicking; -+ } -+ } -+ -+ accumulatedTotal += total; -+ accumulatedInactive += inactive; -+ accumulatedBorder += full; -+ accumulatedTicking += blockTicking; -+ accumulatedEntityTicking += entityTicking; -+ -+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.getName(), GREEN), text(":"))); -+ sender.sendMessage(text().color(DARK_AQUA).append( -+ text("Total: ", BLUE), text(total), -+ text(" Inactive: ", BLUE), text(inactive), -+ text(" Full: ", BLUE), text(full), -+ text(" Block Ticking: ", BLUE), text(blockTicking), -+ text(" Entity Ticking: ", BLUE), text(entityTicking) -+ )); -+ } -+ if (worlds.size() > 1) { -+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text("all listed worlds", GREEN), text(":", DARK_AQUA))); -+ sender.sendMessage(text().color(DARK_AQUA).append( -+ text("Total: ", BLUE), text(accumulatedTotal), -+ text(" Inactive: ", BLUE), text(accumulatedInactive), -+ text(" Full: ", BLUE), text(accumulatedBorder), -+ text(" Block Ticking: ", BLUE), text(accumulatedTicking), -+ text(" Entity Ticking: ", BLUE), text(accumulatedEntityTicking) -+ )); -+ } -+ } -+ -+ private void doHolderInfo(final CommandSender sender, final String[] args) { -+ List worlds; -+ if (args.length < 1 || args[0].equals("*")) { -+ worlds = Bukkit.getWorlds(); -+ } else { -+ worlds = new ArrayList<>(args.length); -+ for (final String arg : args) { -+ org.bukkit.@Nullable World world = Bukkit.getWorld(arg); -+ if (world == null) { -+ sender.sendMessage(text("World '" + arg + "' is invalid", RED)); -+ return; -+ } -+ worlds.add(world); -+ } -+ } -+ -+ int accumulatedTotal = 0; -+ int accumulatedCanUnload = 0; -+ int accumulatedNull = 0; -+ int accumulatedReadOnly = 0; -+ int accumulatedProtoChunk = 0; -+ int accumulatedFullChunk = 0; -+ -+ for (final org.bukkit.World bukkitWorld : worlds) { -+ final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); -+ -+ int total = 0; -+ int canUnload = 0; -+ int nullChunks = 0; -+ int readOnly = 0; -+ int protoChunk = 0; -+ int fullChunk = 0; -+ -+ for (final ChunkHolder chunk : world.chunkTaskScheduler.chunkHolderManager.getOldChunkHolders()) { // Paper - change updating chunks map -+ final ChunkAccess lastChunk = chunk.getAvailableChunkNow(); -+ -+ ++total; -+ -+ if (lastChunk == null) { -+ ++nullChunks; -+ } else if (lastChunk instanceof ImposterProtoChunk) { -+ ++readOnly; -+ } else if (lastChunk instanceof ProtoChunk) { -+ ++protoChunk; -+ } else if (lastChunk instanceof LevelChunk) { -+ ++fullChunk; -+ } -+ -+ if (chunk.newChunkHolder.isSafeToUnload() == null) { -+ ++canUnload; -+ } -+ } -+ -+ accumulatedTotal += total; -+ accumulatedCanUnload += canUnload; -+ accumulatedNull += nullChunks; -+ accumulatedReadOnly += readOnly; -+ accumulatedProtoChunk += protoChunk; -+ accumulatedFullChunk += fullChunk; -+ -+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.getName(), GREEN), text(":"))); -+ sender.sendMessage(text().color(DARK_AQUA).append( -+ text("Total: ", BLUE), text(total), -+ text(" Unloadable: ", BLUE), text(canUnload), -+ text(" Null: ", BLUE), text(nullChunks), -+ text(" ReadOnly: ", BLUE), text(readOnly), -+ text(" Proto: ", BLUE), text(protoChunk), -+ text(" Full: ", BLUE), text(fullChunk) -+ )); -+ } -+ if (worlds.size() > 1) { -+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text("all listed worlds", GREEN), text(":", DARK_AQUA))); -+ sender.sendMessage(text().color(DARK_AQUA).append( -+ text("Total: ", BLUE), text(accumulatedTotal), -+ text(" Unloadable: ", BLUE), text(accumulatedCanUnload), -+ text(" Null: ", BLUE), text(accumulatedNull), -+ text(" ReadOnly: ", BLUE), text(accumulatedReadOnly), -+ text(" Proto: ", BLUE), text(accumulatedProtoChunk), -+ text(" Full: ", BLUE), text(accumulatedFullChunk) -+ )); -+ } -+ } -+ -+ private void doDebug(final CommandSender sender, final String[] args) { -+ if (args.length < 1) { -+ sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); -+ return; -+ } -+ -+ final String debugType = args[0].toLowerCase(Locale.ENGLISH); -+ switch (debugType) { -+ case "chunks" -> { -+ if (args.length >= 2 && args[1].toLowerCase(Locale.ENGLISH).equals("help")) { -+ sender.sendMessage(text("Use /paper debug chunks [world] to dump loaded chunk information to a file", RED)); -+ break; -+ } -+ File file = new File(new File(new File("."), "debug"), -+ "chunks-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); -+ sender.sendMessage(text("Writing chunk information dump to " + file, GREEN)); -+ try { -+ MCUtil.dumpChunks(file, false); -+ sender.sendMessage(text("Successfully written chunk information!", GREEN)); -+ } catch (Throwable thr) { -+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); -+ sender.sendMessage(text("Failed to dump chunk information, see console", RED)); -+ } -+ } -+ // "help" & default -+ default -> sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED)); -+ } -+ } -+ -+} -diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index ffd52f6871161cd1f2d23040ed4493434a29b834..a6f58b3457b7477015c5c6d969e7d83017dd3fa1 100644 ---- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -28,6 +28,45 @@ public class GlobalConfiguration extends ConfigurationPart { - public static GlobalConfiguration get() { - return instance; - } -+ -+ public ChunkLoadingBasic chunkLoadingBasic; -+ -+ public class ChunkLoadingBasic extends ConfigurationPart { -+ @Comment("The maximum rate in chunks per second that the server will send to any individual player. Set to -1 to disable this limit.") -+ public double playerMaxChunkSendRate = 75.0; -+ -+ @Comment( -+ "The maximum rate at which chunks will load for any individual player. " + -+ "Note that this setting also affects chunk generations, since a chunk load is always first issued to test if a" + -+ "chunk is already generated. Set to -1 to disable this limit." -+ ) -+ public double playerMaxChunkLoadRate = 100.0; -+ -+ @Comment("The maximum rate at which chunks will generate for any individual player. Set to -1 to disable this limit.") -+ public double playerMaxChunkGenerateRate = -1.0; -+ } -+ -+ public ChunkLoadingAdvanced chunkLoadingAdvanced; -+ -+ public class ChunkLoadingAdvanced extends ConfigurationPart { -+ @Comment( -+ "Set to true if the server will match the chunk send radius that clients have configured" + -+ "in their view distance settings if the client is less-than the server's send distance." -+ ) -+ public boolean autoConfigSendDistance = true; -+ -+ @Comment( -+ "Specifies the maximum amount of concurrent chunk loads that an individual player can have." + -+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." -+ ) -+ public int playerMaxConcurrentChunkLoads = 0; -+ -+ @Comment( -+ "Specifies the maximum amount of concurrent chunk generations that an individual player can have." + -+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." -+ ) -+ public int playerMaxConcurrentChunkGenerates = 0; -+ } - static void set(GlobalConfiguration instance) { - GlobalConfiguration.instance = instance; - } -@@ -129,21 +168,6 @@ public class GlobalConfiguration extends ConfigurationPart { - public int incomingPacketThreshold = 300; - } - -- public ChunkLoading chunkLoading; -- -- public class ChunkLoading extends ConfigurationPart { -- public int minLoadRadius = 2; -- public int maxConcurrentSends = 2; -- public boolean autoconfigSendDistance = true; -- public double targetPlayerChunkSendRate = 100.0; -- public double globalMaxChunkSendRate = -1.0; -- public boolean enableFrustumPriority = false; -- public double globalMaxChunkLoadRate = -1.0; -- public double playerMaxConcurrentLoads = 20.0; -- public double globalMaxConcurrentLoads = 500.0; -- public double playerMaxChunkLoadRate = -1.0; -- } -- - public UnsupportedSettings unsupportedSettings; - - public class UnsupportedSettings extends ConfigurationPart { -@@ -198,7 +222,7 @@ public class GlobalConfiguration extends ConfigurationPart { - - @PostProcess - private void postProcess() { -- //io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.init(this); -+ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.init(this); - } - } - -diff --git a/src/main/java/io/papermc/paper/threadedregions/TickRegions.java b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d5d39e9c1f326e91010237b0db80d527ac52f4d6 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java -@@ -0,0 +1,9 @@ -+package io.papermc.paper.threadedregions; -+ -+// placeholder class for Folia -+public class TickRegions { -+ -+ public static int getRegionChunkShift() { -+ return 4; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/util/IntervalledCounter.java b/src/main/java/io/papermc/paper/util/IntervalledCounter.java -index cea9c098ade00ee87b8efc8164ab72f5279758f0..197224e31175252d8438a8df585bbb65f2288d7f 100644 ---- a/src/main/java/io/papermc/paper/util/IntervalledCounter.java -+++ b/src/main/java/io/papermc/paper/util/IntervalledCounter.java -@@ -2,6 +2,8 @@ package io.papermc.paper.util; - - public final class IntervalledCounter { - -+ private static final int INITIAL_SIZE = 8; -+ - protected long[] times; - protected long[] counts; - protected final long interval; -@@ -11,8 +13,8 @@ public final class IntervalledCounter { - protected int tail; // exclusive - - public IntervalledCounter(final long interval) { -- this.times = new long[8]; -- this.counts = new long[8]; -+ this.times = new long[INITIAL_SIZE]; -+ this.counts = new long[INITIAL_SIZE]; - this.interval = interval; - } - -@@ -67,13 +69,13 @@ public final class IntervalledCounter { - this.tail = nextTail; - } - -- public void updateAndAdd(final int count) { -+ public void updateAndAdd(final long count) { - final long currTime = System.nanoTime(); - this.updateCurrentTime(currTime); - this.addTime(currTime, count); - } - -- public void updateAndAdd(final int count, final long currTime) { -+ public void updateAndAdd(final long count, final long currTime) { - this.updateCurrentTime(currTime); - this.addTime(currTime, count); - } -@@ -93,9 +95,13 @@ public final class IntervalledCounter { - this.tail = size; - - if (tail >= head) { -+ // sequentially ordered from [head, tail) - System.arraycopy(oldElements, head, newElements, 0, size); - System.arraycopy(oldCounts, head, newCounts, 0, size); - } else { -+ // ordered from [head, length) -+ // then followed by [0, tail) -+ - System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head); - System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail); - -@@ -106,10 +112,18 @@ public final class IntervalledCounter { - - // returns in units per second - public double getRate() { -- return this.size() / (this.interval * 1.0e-9); -+ return (double)this.sum / ((double)this.interval * 1.0E-9); -+ } -+ -+ public long getInterval() { -+ return this.interval; - } - -- public long size() { -+ public long getSum() { - return this.sum; - } -+ -+ public int totalDataPoints() { -+ return this.tail >= this.head ? (this.tail - this.head) : (this.tail + (this.counts.length - this.head)); -+ } - } -diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java -index 0d3ccf3ae219a3b24d17be03de8fd4906cb7235d..850f75172e9efa72cabb8e5bd124b96a0b1a945f 100644 ---- a/src/main/java/io/papermc/paper/util/MCUtil.java -+++ b/src/main/java/io/papermc/paper/util/MCUtil.java -@@ -6,17 +6,30 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; - import io.papermc.paper.math.BlockPosition; - import io.papermc.paper.math.FinePosition; - import io.papermc.paper.math.Position; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonObject; -+import com.google.gson.internal.Streams; -+import com.google.gson.stream.JsonWriter; -+import com.mojang.datafixers.util.Either; - import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; - import java.lang.ref.Cleaner; -+import it.unimi.dsi.fastutil.objects.ReferenceArrayList; - import net.minecraft.core.BlockPos; - import net.minecraft.core.Direction; - import net.minecraft.core.Vec3i; - import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ChunkMap; -+import net.minecraft.server.level.DistanceManager; - import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.Ticket; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.level.ChunkPos; - import net.minecraft.world.level.ClipContext; - import net.minecraft.world.level.Level; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; - import net.minecraft.world.phys.Vec3; - import org.apache.commons.lang.exception.ExceptionUtils; - import com.mojang.authlib.GameProfile; -@@ -28,8 +41,11 @@ import org.spigotmc.AsyncCatcher; - - import javax.annotation.Nonnull; - import javax.annotation.Nullable; -+import java.io.*; -+import java.nio.charset.StandardCharsets; - import java.util.List; - import java.util.Queue; -+import java.util.Set; - import java.util.concurrent.CompletableFuture; - import java.util.concurrent.ExecutionException; - import java.util.concurrent.LinkedBlockingQueue; -@@ -529,6 +545,100 @@ public final class MCUtil { - } - } - -+ public static ChunkStatus getChunkStatus(ChunkHolder chunk) { -+ return chunk.getChunkHolderStatus(); -+ } -+ -+ public static void dumpChunks(File file, boolean watchdog) throws IOException { -+ file.getParentFile().mkdirs(); -+ file.createNewFile(); -+ ReferenceArrayList worlds = new ReferenceArrayList<>(org.bukkit.Bukkit.getWorlds()); -+ ReferenceArrayList loadedWorlds = new ReferenceArrayList<>(worlds); -+ JsonObject data = new JsonObject(); -+ -+ data.addProperty("server-version", org.bukkit.Bukkit.getVersion()); -+ data.addProperty("data-version", 1); -+ -+ { -+ JsonArray players = new JsonArray(); -+ data.add("all-players", players); -+ List playerList = MinecraftServer.getServer().getPlayerList().players; -+ for (ServerPlayer player : playerList) { -+ JsonObject playerData = new JsonObject(); -+ players.add(playerData); -+ -+ Level playerWorld = player.level(); -+ org.bukkit.World craftWorld = playerWorld.getWorld(); -+ Entity.RemovalReason removalReason = player.getRemovalReason(); -+ -+ playerData.addProperty("name", player.getScoreboardName()); -+ playerData.addProperty("x", player.getX()); -+ playerData.addProperty("y", player.getY()); -+ playerData.addProperty("z", player.getZ()); -+ playerData.addProperty("world", playerWorld == null ? "null world" : craftWorld.getName()); -+ playerData.addProperty("removalReason", removalReason == null ? "null" : removalReason.name()); -+ -+ if (!worlds.contains(craftWorld)) { -+ worlds.add(craftWorld); -+ } -+ } -+ } -+ -+ JsonArray chunkWaitInformation = new JsonArray(); -+ data.add("chunk-wait-infos", chunkWaitInformation); -+ -+ for (io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.ChunkInfo chunkInfo : io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.getChunkInfos()) { -+ chunkWaitInformation.add(chunkInfo.toString()); -+ } -+ -+ JsonArray worldsData = new JsonArray(); -+ -+ for (org.bukkit.World bukkitWorld : worlds) { -+ JsonObject worldData = new JsonObject(); -+ -+ ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle(); -+ List players = world.players(); -+ -+ worldData.addProperty("is-loaded", loadedWorlds.contains(bukkitWorld)); -+ worldData.addProperty("name", world.getWorld().getName()); -+ worldData.addProperty("view-distance", world.getWorld().getViewDistance()); // Paper - replace chunk loader system -+ worldData.addProperty("tick-view-distance", world.getWorld().getSimulationDistance()); // Paper - replace chunk loader system -+ worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory); -+ worldData.addProperty("keep-spawn-loaded-range", world.paperConfig().spawn.keepSpawnLoadedRange * 16); -+ -+ JsonArray playersData = new JsonArray(); -+ -+ for (ServerPlayer player : players) { -+ JsonObject playerData = new JsonObject(); -+ -+ playerData.addProperty("name", player.getScoreboardName()); -+ playerData.addProperty("x", player.getX()); -+ playerData.addProperty("y", player.getY()); -+ playerData.addProperty("z", player.getZ()); -+ -+ playersData.add(playerData); -+ } -+ -+ worldData.add("players", playersData); -+ worldData.add("chunk-data", watchdog ? world.chunkTaskScheduler.chunkHolderManager.getDebugJsonForWatchdog() : world.chunkTaskScheduler.chunkHolderManager.getDebugJson()); -+ worldsData.add(worldData); -+ } -+ -+ data.add("worlds", worldsData); -+ -+ StringWriter stringWriter = new StringWriter(); -+ JsonWriter jsonWriter = new JsonWriter(stringWriter); -+ jsonWriter.setIndent(" "); -+ jsonWriter.setLenient(false); -+ Streams.write(data, jsonWriter); -+ -+ String fileData = stringWriter.toString(); -+ -+ try (PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8)) { -+ out.print(fileData); -+ } -+ } -+ - public static int getTicketLevelFor(net.minecraft.world.level.chunk.ChunkStatus status) { - return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status); - } -diff --git a/src/main/java/io/papermc/paper/util/TickThread.java b/src/main/java/io/papermc/paper/util/TickThread.java -index 73e83d56a340f0c7dcb8ff737d621003e72c6de4..bdaf062f9b66ceab303a0807eca301342886a8ea 100644 ---- a/src/main/java/io/papermc/paper/util/TickThread.java -+++ b/src/main/java/io/papermc/paper/util/TickThread.java -@@ -1,12 +1,20 @@ - package io.papermc.paper.util; - -+import net.minecraft.core.BlockPos; - import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.network.ServerGamePacketListenerImpl; -+import net.minecraft.util.Mth; - import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.Vec3; - import org.bukkit.Bukkit; - import java.util.concurrent.atomic.AtomicInteger; - --public final class TickThread extends Thread { -+public class TickThread extends Thread { - - public static final boolean STRICT_THREAD_CHECKS = Boolean.getBoolean("paper.strict-thread-checks"); - -@@ -16,6 +24,10 @@ public final class TickThread extends Thread { - } - } - -+ /** -+ * @deprecated -+ */ -+ @Deprecated - public static void softEnsureTickThread(final String reason) { - if (!STRICT_THREAD_CHECKS) { - return; -@@ -23,6 +35,10 @@ public final class TickThread extends Thread { - ensureTickThread(reason); - } - -+ /** -+ * @deprecated -+ */ -+ @Deprecated - public static void ensureTickThread(final String reason) { - if (!isTickThread()) { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -@@ -30,6 +46,20 @@ public final class TickThread extends Thread { - } - } - -+ public static void ensureTickThread(final ServerLevel world, final BlockPos pos, final String reason) { -+ if (!isTickThreadFor(world, pos)) { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -+ throw new IllegalStateException(reason); -+ } -+ } -+ -+ public static void ensureTickThread(final ServerLevel world, final ChunkPos pos, final String reason) { -+ if (!isTickThreadFor(world, pos)) { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -+ throw new IllegalStateException(reason); -+ } -+ } -+ - public static void ensureTickThread(final ServerLevel world, final int chunkX, final int chunkZ, final String reason) { - if (!isTickThreadFor(world, chunkX, chunkZ)) { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -@@ -44,6 +74,20 @@ public final class TickThread extends Thread { - } - } - -+ public static void ensureTickThread(final ServerLevel world, final AABB aabb, final String reason) { -+ if (!isTickThreadFor(world, aabb)) { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -+ throw new IllegalStateException(reason); -+ } -+ } -+ -+ public static void ensureTickThread(final ServerLevel world, final double blockX, final double blockZ, final String reason) { -+ if (!isTickThreadFor(world, blockX, blockZ)) { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -+ throw new IllegalStateException(reason); -+ } -+ } -+ - public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ - - private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); -@@ -66,13 +110,45 @@ public final class TickThread extends Thread { - } - - public static boolean isTickThread() { -- return Bukkit.isPrimaryThread(); -+ return Thread.currentThread() instanceof TickThread; -+ } -+ -+ public static boolean isShutdownThread() { -+ return false; -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final BlockPos pos) { -+ return isTickThread(); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final ChunkPos pos) { -+ return isTickThread(); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final Vec3 pos) { -+ return isTickThread(); - } - - public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ) { - return isTickThread(); - } - -+ public static boolean isTickThreadFor(final ServerLevel world, final AABB aabb) { -+ return isTickThread(); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final double blockX, final double blockZ) { -+ return isTickThread(); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { -+ return isTickThread(); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { -+ return isTickThread(); -+ } -+ - public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ, final int radius) { - return isTickThread(); - } -diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7e8dc9e8f381abfdcce2746edc93122d623622d1 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java -@@ -0,0 +1,606 @@ -+package io.papermc.paper.world; -+ -+import com.destroystokyo.paper.util.maplist.EntityList; -+import io.papermc.paper.chunk.system.entity.EntityLookup; -+import io.papermc.paper.util.TickThread; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.FullChunkStatus; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.boss.EnderDragonPart; -+import net.minecraft.world.entity.boss.enderdragon.EnderDragon; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.storage.EntityStorage; -+import net.minecraft.world.level.entity.Visibility; -+import net.minecraft.world.phys.AABB; -+import org.bukkit.craftbukkit.event.CraftEventFactory; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Iterator; -+import java.util.List; -+import java.util.function.Predicate; -+ -+public final class ChunkEntitySlices { -+ -+ protected final int minSection; -+ protected final int maxSection; -+ public final int chunkX; -+ public final int chunkZ; -+ protected final ServerLevel world; -+ -+ protected final EntityCollectionBySection allEntities; -+ protected final EntityCollectionBySection hardCollidingEntities; -+ protected final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByClass; -+ protected final EntityList entities = new EntityList(); -+ -+ public FullChunkStatus status; -+ -+ protected boolean isTransient; -+ -+ public boolean isTransient() { -+ return this.isTransient; -+ } -+ -+ public void setTransient(final boolean value) { -+ this.isTransient = value; -+ } -+ -+ // TODO implement container search optimisations -+ -+ public ChunkEntitySlices(final ServerLevel world, final int chunkX, final int chunkZ, final FullChunkStatus status, -+ final int minSection, final int maxSection) { // inclusive, inclusive -+ this.minSection = minSection; -+ this.maxSection = maxSection; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.world = world; -+ -+ this.allEntities = new EntityCollectionBySection(this); -+ this.hardCollidingEntities = new EntityCollectionBySection(this); -+ this.entitiesByClass = new Reference2ObjectOpenHashMap<>(); -+ -+ this.status = status; -+ } -+ -+ // Paper start - optimise CraftChunk#getEntities -+ public org.bukkit.entity.Entity[] getChunkEntities() { -+ List ret = new java.util.ArrayList<>(); -+ final Entity[] entities = this.entities.getRawData(); -+ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { -+ final Entity entity = entities[i]; -+ if (entity == null) { -+ continue; -+ } -+ final org.bukkit.entity.Entity bukkit = entity.getBukkitEntity(); -+ if (bukkit != null && bukkit.isValid()) { -+ ret.add(bukkit); -+ } -+ } -+ -+ return ret.toArray(new org.bukkit.entity.Entity[0]); -+ } -+ -+ public CompoundTag save() { -+ final int len = this.entities.size(); -+ if (len == 0) { -+ return null; -+ } -+ -+ final Entity[] rawData = this.entities.getRawData(); -+ final List collectedEntities = new ArrayList<>(len); -+ for (int i = 0; i < len; ++i) { -+ final Entity entity = rawData[i]; -+ if (entity.shouldBeSaved()) { -+ collectedEntities.add(entity); -+ } -+ } -+ -+ if (collectedEntities.isEmpty()) { -+ return null; -+ } -+ -+ return EntityStorage.saveEntityChunk(collectedEntities, new ChunkPos(this.chunkX, this.chunkZ), this.world); -+ } -+ -+ // returns true if this chunk has transient entities remaining -+ public boolean unload() { -+ final int len = this.entities.size(); -+ final Entity[] collectedEntities = Arrays.copyOf(this.entities.getRawData(), len); -+ -+ for (int i = 0; i < len; ++i) { -+ final Entity entity = collectedEntities[i]; -+ if (entity.isRemoved()) { -+ // removed by us below -+ continue; -+ } -+ if (entity.shouldBeSaved()) { -+ entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK); -+ if (entity.isVehicle()) { -+ // we cannot assume that these entities are contained within this chunk, because entities can -+ // desync - so we need to remove them all -+ for (final Entity passenger : entity.getIndirectPassengers()) { -+ passenger.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK); -+ } -+ } -+ } -+ } -+ -+ return this.entities.size() != 0; -+ } -+ -+ private List getAllEntities() { -+ final int len = this.entities.size(); -+ if (len == 0) { -+ return new ArrayList<>(); -+ } -+ -+ final Entity[] rawData = this.entities.getRawData(); -+ final List collectedEntities = new ArrayList<>(len); -+ for (int i = 0; i < len; ++i) { -+ collectedEntities.add(rawData[i]); -+ } -+ -+ return collectedEntities; -+ } -+ -+ public void callEntitiesLoadEvent() { -+ CraftEventFactory.callEntitiesLoadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); -+ } -+ -+ public void callEntitiesUnloadEvent() { -+ CraftEventFactory.callEntitiesUnloadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); -+ } -+ // Paper end - optimise CraftChunk#getEntities -+ -+ public boolean isEmpty() { -+ return this.entities.size() == 0; -+ } -+ -+ public void mergeInto(final ChunkEntitySlices slices) { -+ final Entity[] entities = this.entities.getRawData(); -+ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { -+ final Entity entity = entities[i]; -+ slices.addEntity(entity, entity.sectionY); -+ } -+ } -+ -+ private boolean preventStatusUpdates; -+ public boolean startPreventingStatusUpdates() { -+ final boolean ret = this.preventStatusUpdates; -+ this.preventStatusUpdates = true; -+ return ret; -+ } -+ -+ public boolean isPreventingStatusUpdates() { -+ return this.preventStatusUpdates; -+ } -+ -+ public void stopPreventingStatusUpdates(final boolean prev) { -+ this.preventStatusUpdates = prev; -+ } -+ -+ public void updateStatus(final FullChunkStatus status, final EntityLookup lookup) { -+ this.status = status; -+ -+ final Entity[] entities = this.entities.getRawData(); -+ -+ for (int i = 0, size = this.entities.size(); i < size; ++i) { -+ final Entity entity = entities[i]; -+ -+ final Visibility oldVisibility = EntityLookup.getEntityStatus(entity); -+ entity.chunkStatus = status; -+ final Visibility newVisibility = EntityLookup.getEntityStatus(entity); -+ -+ lookup.entityStatusChange(entity, this, oldVisibility, newVisibility, false, false, false); -+ } -+ } -+ -+ public boolean addEntity(final Entity entity, final int chunkSection) { -+ if (!this.entities.add(entity)) { -+ return false; -+ } -+ entity.chunkStatus = this.status; -+ final int sectionIndex = chunkSection - this.minSection; -+ -+ this.allEntities.addEntity(entity, sectionIndex); -+ -+ if (entity.hardCollides()) { -+ this.hardCollidingEntities.addEntity(entity, sectionIndex); -+ } -+ -+ for (final Iterator, EntityCollectionBySection>> iterator = -+ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); -+ -+ if (entry.getKey().isInstance(entity)) { -+ entry.getValue().addEntity(entity, sectionIndex); -+ } -+ } -+ -+ return true; -+ } -+ -+ public boolean removeEntity(final Entity entity, final int chunkSection) { -+ if (!this.entities.remove(entity)) { -+ return false; -+ } -+ entity.chunkStatus = null; -+ final int sectionIndex = chunkSection - this.minSection; -+ -+ this.allEntities.removeEntity(entity, sectionIndex); -+ -+ if (entity.hardCollides()) { -+ this.hardCollidingEntities.removeEntity(entity, sectionIndex); -+ } -+ -+ for (final Iterator, EntityCollectionBySection>> iterator = -+ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); -+ -+ if (entry.getKey().isInstance(entity)) { -+ entry.getValue().removeEntity(entity, sectionIndex); -+ } -+ } -+ -+ return true; -+ } -+ -+ public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ this.hardCollidingEntities.getEntities(except, box, into, predicate); -+ } -+ -+ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ this.allEntities.getEntitiesWithEnderDragonParts(except, box, into, predicate); -+ } -+ -+ public void getEntitiesWithoutDragonParts(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ this.allEntities.getEntities(except, box, into, predicate); -+ } -+ -+ public void getEntities(final EntityType type, final AABB box, final List into, -+ final Predicate predicate) { -+ this.allEntities.getEntities(type, box, (List)into, (Predicate)predicate); -+ } -+ -+ protected EntityCollectionBySection initClass(final Class clazz) { -+ final EntityCollectionBySection ret = new EntityCollectionBySection(this); -+ -+ for (int sectionIndex = 0; sectionIndex < this.allEntities.entitiesBySection.length; ++sectionIndex) { -+ final BasicEntityList sectionEntities = this.allEntities.entitiesBySection[sectionIndex]; -+ if (sectionEntities == null) { -+ continue; -+ } -+ -+ final Entity[] storage = sectionEntities.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, sectionEntities.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (clazz.isInstance(entity)) { -+ ret.addEntity(entity, sectionIndex); -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public void getEntities(final Class clazz, final Entity except, final AABB box, final List into, -+ final Predicate predicate) { -+ EntityCollectionBySection collection = this.entitiesByClass.get(clazz); -+ if (collection != null) { -+ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); -+ } else { -+ this.entitiesByClass.putIfAbsent(clazz, collection = this.initClass(clazz)); -+ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); -+ } -+ } -+ -+ protected static final class BasicEntityList { -+ -+ protected static final Entity[] EMPTY = new Entity[0]; -+ protected static final int DEFAULT_CAPACITY = 4; -+ -+ protected E[] storage; -+ protected int size; -+ -+ public BasicEntityList() { -+ this(0); -+ } -+ -+ public BasicEntityList(final int cap) { -+ this.storage = (E[])(cap <= 0 ? EMPTY : new Entity[cap]); -+ } -+ -+ public boolean isEmpty() { -+ return this.size == 0; -+ } -+ -+ public int size() { -+ return this.size; -+ } -+ -+ private void resize() { -+ if (this.storage == EMPTY) { -+ this.storage = (E[])new Entity[DEFAULT_CAPACITY]; -+ } else { -+ this.storage = Arrays.copyOf(this.storage, this.storage.length * 2); -+ } -+ } -+ -+ public void add(final E entity) { -+ final int idx = this.size++; -+ if (idx >= this.storage.length) { -+ this.resize(); -+ this.storage[idx] = entity; -+ } else { -+ this.storage[idx] = entity; -+ } -+ } -+ -+ public int indexOf(final E entity) { -+ final E[] storage = this.storage; -+ -+ for (int i = 0, len = Math.min(this.storage.length, this.size); i < len; ++i) { -+ if (storage[i] == entity) { -+ return i; -+ } -+ } -+ -+ return -1; -+ } -+ -+ public boolean remove(final E entity) { -+ final int idx = this.indexOf(entity); -+ if (idx == -1) { -+ return false; -+ } -+ -+ final int size = --this.size; -+ final E[] storage = this.storage; -+ if (idx != size) { -+ System.arraycopy(storage, idx + 1, storage, idx, size - idx); -+ } -+ -+ storage[size] = null; -+ -+ return true; -+ } -+ -+ public boolean has(final E entity) { -+ return this.indexOf(entity) != -1; -+ } -+ } -+ -+ protected static final class EntityCollectionBySection { -+ -+ protected final ChunkEntitySlices manager; -+ protected final long[] nonEmptyBitset; -+ protected final BasicEntityList[] entitiesBySection; -+ protected int count; -+ -+ public EntityCollectionBySection(final ChunkEntitySlices manager) { -+ this.manager = manager; -+ -+ final int sectionCount = manager.maxSection - manager.minSection + 1; -+ -+ this.nonEmptyBitset = new long[(sectionCount + (Long.SIZE - 1)) >>> 6]; // (sectionCount + (Long.SIZE - 1)) / Long.SIZE -+ this.entitiesBySection = new BasicEntityList[sectionCount]; -+ } -+ -+ public void addEntity(final Entity entity, final int sectionIndex) { -+ BasicEntityList list = this.entitiesBySection[sectionIndex]; -+ -+ if (list != null && list.has(entity)) { -+ return; -+ } -+ -+ if (list == null) { -+ this.entitiesBySection[sectionIndex] = list = new BasicEntityList<>(); -+ this.nonEmptyBitset[sectionIndex >>> 6] |= (1L << (sectionIndex & (Long.SIZE - 1))); -+ } -+ -+ list.add(entity); -+ ++this.count; -+ } -+ -+ public void removeEntity(final Entity entity, final int sectionIndex) { -+ final BasicEntityList list = this.entitiesBySection[sectionIndex]; -+ -+ if (list == null || !list.remove(entity)) { -+ return; -+ } -+ -+ --this.count; -+ -+ if (list.isEmpty()) { -+ this.entitiesBySection[sectionIndex] = null; -+ this.nonEmptyBitset[sectionIndex >>> 6] ^= (1L << (sectionIndex & (Long.SIZE - 1))); -+ } -+ } -+ -+ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test(entity)) { -+ continue; -+ } -+ -+ into.add(entity); -+ } -+ } -+ } -+ -+ public void getEntitiesWithEnderDragonParts(final Entity except, final AABB box, final List into, -+ final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate == null || predicate.test(entity)) { -+ into.add(entity); -+ } // else: continue to test the ender dragon parts -+ -+ if (entity instanceof EnderDragon) { -+ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { -+ if (part == except || !part.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test(part)) { -+ continue; -+ } -+ -+ into.add(part); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntitiesWithEnderDragonParts(final Entity except, final Class clazz, final AABB box, final List into, -+ final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate == null || predicate.test(entity)) { -+ into.add(entity); -+ } // else: continue to test the ender dragon parts -+ -+ if (entity instanceof EnderDragon) { -+ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { -+ if (part == except || !part.getBoundingBox().intersects(box) || !clazz.isInstance(part)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test(part)) { -+ continue; -+ } -+ -+ into.add(part); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final EntityType type, final AABB box, final List into, -+ final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || (type != null && entity.getType() != type) || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test((T)entity)) { -+ continue; -+ } -+ -+ into.add((T)entity); -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index deb2d8c22a1c5724d0ac8571f4ea54711988dc4b..72d013d06705b08ed696e3d3b6d631d65800c2c9 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -327,6 +327,7 @@ public class Main { - - convertable_conversionsession.saveDataTag(iregistrycustom_dimension, savedata); - */ -+ Class.forName(net.minecraft.world.entity.npc.VillagerTrades.class.getName()); // Paper - load this sync so it won't fail later async - final DedicatedServer dedicatedserver = (DedicatedServer) MinecraftServer.spin((thread) -> { - DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, worldLoader.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::new); - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 48da5bdabcf38afbbd1509eca56d5c761622409f..48e3b0f065b370f780f8a6145fba9f3f7976badb 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -311,7 +311,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { - AtomicReference atomicreference = new AtomicReference(); -- Thread thread = new Thread(() -> { -+ Thread thread = new io.papermc.paper.util.TickThread(() -> { // Paper - rewrite chunk system - ((MinecraftServer) atomicreference.get()).runServer(); - }, "Server thread"); - -@@ -643,7 +643,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -- return worldserver1.getChunkSource().chunkMap.hasWork(); -- })) { -- this.nextTickTimeNanos = Util.getNanos() + TimeUtil.NANOSECONDS_PER_MILLISECOND; -- iterator = this.getAllLevels().iterator(); -- -- while (iterator.hasNext()) { -- worldserver = (ServerLevel) iterator.next(); -- worldserver.getChunkSource().removeTicketsOnClosing(); -- worldserver.getChunkSource().tick(() -> { -- return true; -- }, false); -- } -- -- this.waitUntilNextTick(); -- } -- -- this.saveAllChunks(false, true, false); -- iterator = this.getAllLevels().iterator(); -- -- while (iterator.hasNext()) { -- worldserver = (ServerLevel) iterator.next(); -- if (worldserver != null) { -- try { -- worldserver.close(); -- } catch (IOException ioexception) { -- MinecraftServer.LOGGER.error("Exception closing the level", ioexception); -- } -- } -- } -+ this.saveAllChunks(false, true, false, true); // Paper - rewrite chunk system - move closing into here - - this.isSaving = false; - this.resources.close(); -@@ -1014,6 +995,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -- for (final Entity entity : level.getEntities().getAll()) { -+ for (final Entity entity : level.getEntityLookup().getAllCopy()) { // Paper - rewrite chunk system - if (entity.isRemoved()) { - continue; - } -@@ -2520,7 +2515,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -+ LOGGER.info("Async debug chunks executing"); -+ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(false); -+ CommandSender sender = MinecraftServer.getServer().console; -+ java.io.File file = new java.io.File(new java.io.File(new java.io.File("."), "debug"), -+ "chunks-" + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(java.time.LocalDateTime.now()) + ".txt"); -+ sender.sendMessage(net.kyori.adventure.text.Component.text("Writing chunk information dump to " + file, net.kyori.adventure.text.format.NamedTextColor.GREEN)); -+ try { -+ io.papermc.paper.util.MCUtil.dumpChunks(file, true); -+ sender.sendMessage(net.kyori.adventure.text.Component.text("Successfully written chunk information!", net.kyori.adventure.text.format.NamedTextColor.GREEN)); -+ } catch (Throwable thr) { -+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr); -+ sender.sendMessage(net.kyori.adventure.text.Component.text("Failed to dump chunk information, see console", net.kyori.adventure.text.format.NamedTextColor.RED)); -+ } -+ }; -+ Thread t = new Thread(run); -+ t.setName("Async debug thread #" + ASYNC_DEBUG_CHUNKS_COUNT.getAndIncrement()); -+ t.setDaemon(true); -+ t.start(); -+ return; -+ } -+ // Paper end - rewrite chunk system - this.serverCommandQueue.add(new ConsoleInput(command, commandSource)); // Paper - Perf: use proper queue - } - -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 807a6bb1026dac2c4cd0a50afe06fd62ce23558b..2b998bdbe49bf8211b755e0eb7c1bf13ac280eab 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -48,17 +48,12 @@ public class ChunkHolder { - private static final Either NOT_DONE_YET = Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED); - private static final CompletableFuture> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK); - private static final List CHUNK_STATUSES = ChunkStatus.getStatusList(); -- private final AtomicReferenceArray>> futures; -+ // Paper - rewrite chunk system - private final LevelHeightAccessor levelHeightAccessor; -- private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage -- private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage -- private volatile CompletableFuture> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage -- public CompletableFuture chunkToSave; // Paper - public -+ // Paper - rewrite chunk system - @Nullable - private final DebugBuffer chunkToSaveHistory; -- public int oldTicketLevel; -- private int ticketLevel; -- private int queueLevel; -+ // Paper - rewrite chunk system - public final ChunkPos pos; - private boolean hasChangedSections; - private final ShortSet[] changedBlocksPerSection; -@@ -67,11 +62,20 @@ public class ChunkHolder { - private final LevelLightEngine lightEngine; - private final ChunkHolder.LevelChangeListener onLevelChange; - public final ChunkHolder.PlayerProvider playerProvider; -- private boolean wasAccessibleSinceLastSave; -- private CompletableFuture pendingFullStateConfirmation; -- private CompletableFuture sendSync; -+ // Paper - rewrite chunk system - - private final ChunkMap chunkMap; // Paper -+ // Paper start - no-tick view distance -+ public final LevelChunk getSendingChunk() { -+ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used -+ // in Chunk's neighbour callback -+ LevelChunk ret = this.chunkMap.level.getChunkSource().getChunkAtIfLoadedImmediately(this.pos.x, this.pos.z); -+ if (ret != null && ret.areNeighboursLoaded(1)) { -+ return ret; -+ } -+ return null; -+ } -+ // Paper end - no-tick view distance - - // Paper start - public void onChunkAdd() { -@@ -83,158 +87,143 @@ public class ChunkHolder { - } - // Paper end - -- public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { -- this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); -- this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -- this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -- this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -- this.chunkToSave = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error -+ public final io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder; // Paper - rewrite chunk system -+ -+ // Paper start - replace player chunk loader -+ private final com.destroystokyo.paper.util.maplist.ReferenceList playersSentChunkTo = new com.destroystokyo.paper.util.maplist.ReferenceList<>(); -+ -+ public void addPlayer(ServerPlayer player) { -+ if (!this.playersSentChunkTo.add(player)) { -+ throw new IllegalStateException("Already sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player); -+ } -+ } -+ -+ public void removePlayer(ServerPlayer player) { -+ if (!this.playersSentChunkTo.remove(player)) { -+ throw new IllegalStateException("Have not sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player); -+ } -+ } -+ -+ public boolean hasChunkBeenSent() { -+ return this.playersSentChunkTo.size() != 0; -+ } -+ -+ public boolean hasBeenSent(ServerPlayer to) { -+ return this.playersSentChunkTo.contains(to); -+ } -+ // Paper end - replace player chunk loader -+ public ChunkHolder(ChunkPos pos, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.PlayerProvider playersWatchingChunkProvider, io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder) { // Paper - rewrite chunk system -+ this.newChunkHolder = newChunkHolder; // Paper - rewrite chunk system - this.chunkToSaveHistory = null; - this.blockChangedLightSectionFilter = new BitSet(); - this.skyChangedLightSectionFilter = new BitSet(); -- this.pendingFullStateConfirmation = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error -- this.sendSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error -+ // Paper - rewrite chunk system - this.pos = pos; - this.levelHeightAccessor = world; - this.lightEngine = lightingProvider; -- this.onLevelChange = levelUpdateListener; -+ this.onLevelChange = null; // Paper - rewrite chunk system - this.playerProvider = playersWatchingChunkProvider; -- this.oldTicketLevel = ChunkLevel.MAX_LEVEL + 1; -- this.ticketLevel = this.oldTicketLevel; -- this.queueLevel = this.oldTicketLevel; -- this.setTicketLevel(level); -+ // Paper - rewrite chunk system - this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()]; - this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper - } - - // Paper start - public @Nullable ChunkAccess getAvailableChunkNow() { -- // TODO can we just getStatusFuture(EMPTY)? -- for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) { -- CompletableFuture> future = this.getFutureIfPresentUnchecked(curr); -- Either either = future.getNow(null); -- if (either == null || either.left().isEmpty()) { -- continue; -- } -- return either.left().get(); -- } -- return null; -+ return this.newChunkHolder.getCurrentChunk(); // Paper - rewrite chunk system - } - // Paper end - // CraftBukkit start - public LevelChunk getFullChunkNow() { -- // Note: We use the oldTicketLevel for isLoaded checks. -- if (!ChunkLevel.fullStatus(this.oldTicketLevel).isOrAfter(FullChunkStatus.FULL)) return null; -- return this.getFullChunkNowUnchecked(); -+ // Paper start - rewrite chunk system -+ ChunkAccess chunk = this.getAvailableChunkNow(); -+ if (!this.isFullChunkReady() || !(chunk instanceof LevelChunk)) return null; // instanceof to avoid a race condition on off-main threads -+ return (LevelChunk)chunk; -+ // Paper end - rewrite chunk system - } - - public LevelChunk getFullChunkNowUnchecked() { -- CompletableFuture> statusFuture = this.getFutureIfPresentUnchecked(ChunkStatus.FULL); -- Either either = (Either) statusFuture.getNow(null); -- return (either == null) ? null : (LevelChunk) either.left().orElse(null); -+ // Paper start - rewrite chunk system -+ ChunkAccess chunk = this.getAvailableChunkNow(); -+ return chunk instanceof LevelChunk ? (LevelChunk)chunk : null; -+ // Paper end - rewrite chunk system - } - // CraftBukkit end - - public CompletableFuture> getFutureIfPresentUnchecked(ChunkStatus leastStatus) { -- CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(leastStatus.getIndex()); -- -- return completablefuture == null ? ChunkHolder.UNLOADED_CHUNK_FUTURE : completablefuture; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public CompletableFuture> getFutureIfPresent(ChunkStatus leastStatus) { -- return ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(leastStatus) ? this.getFutureIfPresentUnchecked(leastStatus) : ChunkHolder.UNLOADED_CHUNK_FUTURE; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public final CompletableFuture> getTickingChunkFuture() { // Paper - final for inline -- return this.tickingChunkFuture; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public final CompletableFuture> getEntityTickingChunkFuture() { // Paper - final for inline -- return this.entityTickingChunkFuture; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public final CompletableFuture> getFullChunkFuture() { // Paper - final for inline -- return this.fullChunkFuture; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @Nullable - public final LevelChunk getTickingChunk() { // Paper - final for inline -- CompletableFuture> completablefuture = this.getTickingChunkFuture(); -- Either either = (Either) completablefuture.getNow(null); // CraftBukkit - decompile error -- -- return either == null ? null : (LevelChunk) either.left().orElse(null); // CraftBukkit - decompile error -+ // Paper start - rewrite chunk system -+ if (!this.isTickingReady()) { -+ return null; -+ } -+ return (LevelChunk)this.getAvailableChunkNow(); -+ // Paper end - rewrite chunk system - } - - public CompletableFuture getChunkSendSyncFuture() { -- return this.sendSync; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @Nullable - public LevelChunk getChunkToSend() { -- return !this.sendSync.isDone() ? null : this.getTickingChunk(); -+ return this.getSendingChunk(); // Paper - rewrite chunk system - } - - @Nullable - public final LevelChunk getFullChunk() { // Paper - final for inline -- CompletableFuture> completablefuture = this.getFullChunkFuture(); -- Either either = (Either) completablefuture.getNow(null); // CraftBukkit - decompile error -- -- return either == null ? null : (LevelChunk) either.left().orElse(null); // CraftBukkit - decompile error -+ // Paper start - rewrite chunk system -+ if (!this.isFullChunkReady()) { -+ return null; -+ } -+ return (LevelChunk)this.getAvailableChunkNow(); -+ // Paper end - rewrite chunk system - } - - @Nullable - public ChunkStatus getLastAvailableStatus() { -- for (int i = ChunkHolder.CHUNK_STATUSES.size() - 1; i >= 0; --i) { -- ChunkStatus chunkstatus = (ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i); -- CompletableFuture> completablefuture = this.getFutureIfPresentUnchecked(chunkstatus); -- -- if (((Either) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).left().isPresent()) { -- return chunkstatus; -- } -- } -- -- return null; -+ return this.newChunkHolder.getCurrentGenStatus(); // Paper - rewrite chunk system - } - - // Paper start - public ChunkStatus getChunkHolderStatus() { -- for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) { -- CompletableFuture> future = this.getFutureIfPresentUnchecked(curr); -- Either either = future.getNow(null); -- if (either == null || !either.left().isPresent()) { -- continue; -- } -- return curr; -- } -- -- return null; -+ return this.newChunkHolder.getCurrentGenStatus(); // Paper - rewrite chunk system - } - // Paper end - - @Nullable - public ChunkAccess getLastAvailable() { -- for (int i = ChunkHolder.CHUNK_STATUSES.size() - 1; i >= 0; --i) { -- ChunkStatus chunkstatus = (ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i); -- CompletableFuture> completablefuture = this.getFutureIfPresentUnchecked(chunkstatus); -- -- if (!completablefuture.isCompletedExceptionally()) { -- Optional optional = ((Either) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).left(); -- -- if (optional.isPresent()) { -- return (ChunkAccess) optional.get(); -- } -- } -- } -- -- return null; -+ return this.newChunkHolder.getCurrentChunk(); // Paper - rewrite chunk system - } - -- public final CompletableFuture getChunkToSave() { // Paper - final for inline -- return this.chunkToSave; -- } -+ // Paper - rewrite chunk system - - public void blockChanged(BlockPos pos) { -- LevelChunk chunk = this.getTickingChunk(); -+ // Paper start - replace player chunk loader -+ if (this.playersSentChunkTo.size() == 0) { -+ return; -+ } -+ // Paper end - replace player chunk loader -+ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance - - if (chunk != null) { - int i = this.levelHeightAccessor.getSectionIndex(pos.getY()); -@@ -250,16 +239,17 @@ public class ChunkHolder { - } - - public void sectionLightChanged(LightLayer lightType, int y) { -- Either either = (Either) this.getFutureIfPresent(ChunkStatus.INITIALIZE_LIGHT).getNow(null); // CraftBukkit - decompile error -+ // Paper start - no-tick view distance - -- if (either != null) { -- ChunkAccess ichunkaccess = (ChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error -+ if (true) { -+ ChunkAccess ichunkaccess = this.getAvailableChunkNow(); - - if (ichunkaccess != null) { - ichunkaccess.setUnsaved(true); -- LevelChunk chunk = this.getTickingChunk(); -+ LevelChunk chunk = this.getSendingChunk(); -+ // Paper end - no-tick view distance - -- if (chunk != null) { -+ if (this.playersSentChunkTo.size() != 0 && chunk != null) { // Paper - replace player chunk loader - int j = this.lightEngine.getMinLightSection(); - int k = this.lightEngine.getMaxLightSection(); - -@@ -284,7 +274,7 @@ public class ChunkHolder { - List list; - - if (!this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) { -- list = this.playerProvider.getPlayers(this.pos, true); -+ list = this.getPlayers(true); // Paper - rewrite chunk system - if (!list.isEmpty()) { - ClientboundLightUpdatePacket packetplayoutlightupdate = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, this.skyChangedLightSectionFilter, this.blockChangedLightSectionFilter); - -@@ -296,7 +286,7 @@ public class ChunkHolder { - } - - if (this.hasChangedSections) { -- list = this.playerProvider.getPlayers(this.pos, false); -+ list = this.getPlayers(false); // Paper - rewrite chunk system - - for (int i = 0; i < this.changedBlocksPerSection.length; ++i) { - ShortSet shortset = this.changedBlocksPerSection[i]; -@@ -354,78 +344,35 @@ public class ChunkHolder { - - } - -- private void broadcast(List players, Packet packet) { -- players.forEach((entityplayer) -> { -- entityplayer.connection.send(packet); -- }); -- } -- -- public CompletableFuture> getOrScheduleFuture(ChunkStatus targetStatus, ChunkMap chunkStorage) { -- int i = targetStatus.getIndex(); -- CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(i); -- -- if (completablefuture != null) { -- Either either = (Either) completablefuture.getNow(ChunkHolder.NOT_DONE_YET); -- -- if (either == null) { -- String s = "value in future for status: " + targetStatus + " was incorrectly set to null at chunk: " + this.pos; -- -- throw chunkStorage.debugFuturesAndCreateReportedException(new IllegalStateException("null value previously set for chunk status"), s); -- } -- -- if (either == ChunkHolder.NOT_DONE_YET || either.right().isEmpty()) { -- return completablefuture; -+ // Paper start - rewrite chunk system -+ public List getPlayers(boolean onlyOnWatchDistanceEdge){ -+ List ret = new java.util.ArrayList<>(); -+ for (int i = 0, len = this.playersSentChunkTo.size(); i < len; ++i) { -+ ServerPlayer player = this.playersSentChunkTo.getUnchecked(i); -+ if (onlyOnWatchDistanceEdge && !this.chunkMap.level.playerChunkLoader.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) { -+ continue; - } -+ ret.add(player); - } - -- if (ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(targetStatus)) { -- CompletableFuture> completablefuture1 = chunkStorage.schedule(this, targetStatus); -- -- this.updateChunkToSave(completablefuture1, "schedule " + targetStatus); -- this.futures.set(i, completablefuture1); -- return completablefuture1; -- } else { -- return completablefuture == null ? ChunkHolder.UNLOADED_CHUNK_FUTURE : completablefuture; -- } -+ return ret; - } - -- protected void addSaveDependency(String thenDesc, CompletableFuture then) { -- if (this.chunkToSaveHistory != null) { -- this.chunkToSaveHistory.push(new ChunkHolder.ChunkSaveDebug(Thread.currentThread(), then, thenDesc)); -- } -- -- this.chunkToSave = this.chunkToSave.thenCombine(then, (ichunkaccess, object) -> { -- return ichunkaccess; -- }); -+ public void broadcast(Packet packet, boolean onlyOnWatchDistanceEdge) { -+ this.broadcast(this.getPlayers(onlyOnWatchDistanceEdge), packet); - } -+ // Paper end - rewrite chunk system - -- private void updateChunkToSave(CompletableFuture> then, String thenDesc) { -- if (this.chunkToSaveHistory != null) { -- this.chunkToSaveHistory.push(new ChunkHolder.ChunkSaveDebug(Thread.currentThread(), then, thenDesc)); -- } -- -- this.chunkToSave = this.chunkToSave.thenCombine(then, (ichunkaccess, either) -> { -- return (ChunkAccess) either.map((ichunkaccess1) -> { -- return ichunkaccess1; -- }, (playerchunk_failure) -> { -- return ichunkaccess; -- }); -+ private void broadcast(List players, Packet packet) { -+ players.forEach((entityplayer) -> { -+ entityplayer.connection.send(packet); - }); - } - -- public void addSendDependency(CompletableFuture postProcessingFuture) { -- if (this.sendSync.isDone()) { -- this.sendSync = postProcessingFuture; -- } else { -- this.sendSync = this.sendSync.thenCombine(postProcessingFuture, (object, object1) -> { -- return null; -- }); -- } -- -- } -+ // Paper - rewrite chunk system - - public FullChunkStatus getFullStatus() { -- return ChunkLevel.fullStatus(this.ticketLevel); -+ return this.newChunkHolder.getChunkStatus(); // Paper - rewrite chunk system) { - } - - public final ChunkPos getPos() { // Paper - final for inline -@@ -433,240 +380,17 @@ public class ChunkHolder { - } - - public final int getTicketLevel() { // Paper - final for inline -- return this.ticketLevel; -- } -- -- public int getQueueLevel() { -- return this.queueLevel; -- } -- -- private void setQueueLevel(int level) { -- this.queueLevel = level; -- } -- -- public void setTicketLevel(int level) { -- this.ticketLevel = level; -- } -- -- private void scheduleFullChunkPromotion(ChunkMap playerchunkmap, CompletableFuture> completablefuture, Executor executor, FullChunkStatus fullchunkstatus) { -- this.pendingFullStateConfirmation.cancel(false); -- CompletableFuture completablefuture1 = new CompletableFuture(); -- -- completablefuture1.thenRunAsync(() -> { -- playerchunkmap.onFullChunkStatusChange(this.pos, fullchunkstatus); -- }, executor); -- this.pendingFullStateConfirmation = completablefuture1; -- completablefuture.thenAccept((either) -> { -- either.ifLeft((chunk) -> { -- completablefuture1.complete(null); // CraftBukkit - decompile error -- }); -- }); -- } -- -- private void demoteFullChunk(ChunkMap playerchunkmap, FullChunkStatus fullchunkstatus) { -- this.pendingFullStateConfirmation.cancel(false); -- playerchunkmap.onFullChunkStatusChange(this.pos, fullchunkstatus); -- } -- -- protected void updateFutures(ChunkMap chunkStorage, Executor executor) { -- ChunkStatus chunkstatus = ChunkLevel.generationStatus(this.oldTicketLevel); -- ChunkStatus chunkstatus1 = ChunkLevel.generationStatus(this.ticketLevel); -- boolean flag = ChunkLevel.isLoaded(this.oldTicketLevel); -- boolean flag1 = ChunkLevel.isLoaded(this.ticketLevel); -- FullChunkStatus fullchunkstatus = ChunkLevel.fullStatus(this.oldTicketLevel); -- FullChunkStatus fullchunkstatus1 = ChunkLevel.fullStatus(this.ticketLevel); -- // CraftBukkit start -- // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. -- if (fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && !fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) { -- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { -- LevelChunk chunk = (LevelChunk)either.left().orElse(null); -- if (chunk != null) { -- chunkStorage.callbackExecutor.execute(() -> { -- // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick -- // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag. -- // These actions may however happen deferred, so we manually set the needsSaving flag already here. -- chunk.setUnsaved(true); -- chunk.unloadCallback(); -- }); -- } -- }).exceptionally((throwable) -> { -- // ensure exceptions are printed, by default this is not the case -- MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable); -- return null; -- }); -- -- // Run callback right away if the future was already done -- chunkStorage.callbackExecutor.run(); -- } -- // CraftBukkit end -- -- if (flag) { -- Either either = Either.right(new ChunkHolder.ChunkLoadingFailure() { -- public String toString() { -- return "Unloaded ticket level " + ChunkHolder.this.pos; -- } -- }); -- -- for (int i = flag1 ? chunkstatus1.getIndex() + 1 : 0; i <= chunkstatus.getIndex(); ++i) { -- CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(i); -- -- if (completablefuture == null) { -- this.futures.set(i, CompletableFuture.completedFuture(either)); -- } -- } -- } -- -- boolean flag2 = fullchunkstatus.isOrAfter(FullChunkStatus.FULL); -- boolean flag3 = fullchunkstatus1.isOrAfter(FullChunkStatus.FULL); -- -- this.wasAccessibleSinceLastSave |= flag3; -- if (!flag2 && flag3) { -- int expectCreateCount = ++this.fullChunkCreateCount; // Paper -- this.fullChunkFuture = chunkStorage.prepareAccessibleChunk(this); -- this.scheduleFullChunkPromotion(chunkStorage, this.fullChunkFuture, executor, FullChunkStatus.FULL); -- // Paper start - cache ticking ready status -- this.fullChunkFuture.thenAccept(either -> { -- final Optional left = either.left(); -- if (left.isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) { -- LevelChunk fullChunk = either.left().get(); -- ChunkHolder.this.isFullChunkReady = true; -- io.papermc.paper.chunk.system.ChunkSystem.onChunkBorder(fullChunk, this); -- } -- }); -- this.updateChunkToSave(this.fullChunkFuture, "full"); -- } -- -- if (flag2 && !flag3) { -- // Paper start -- if (this.isFullChunkReady) { -- io.papermc.paper.chunk.system.ChunkSystem.onChunkNotBorder(this.fullChunkFuture.join().left().get(), this); // Paper -- } -- // Paper end -- this.fullChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); -- this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -- ++this.fullChunkCreateCount; // Paper - cache ticking ready status -- this.isFullChunkReady = false; // Paper - cache ticking ready status -- } -- -- boolean flag4 = fullchunkstatus.isOrAfter(FullChunkStatus.BLOCK_TICKING); -- boolean flag5 = fullchunkstatus1.isOrAfter(FullChunkStatus.BLOCK_TICKING); -- -- if (!flag4 && flag5) { -- this.tickingChunkFuture = chunkStorage.prepareTickingChunk(this); -- this.scheduleFullChunkPromotion(chunkStorage, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING); -- // Paper start - cache ticking ready status -- this.tickingChunkFuture.thenAccept(either -> { -- either.ifLeft(chunk -> { -- // note: Here is a very good place to add callbacks to logic waiting on this. -- ChunkHolder.this.isTickingReady = true; -- io.papermc.paper.chunk.system.ChunkSystem.onChunkTicking(chunk, this); -- }); -- }); -- // Paper end -- this.updateChunkToSave(this.tickingChunkFuture, "ticking"); -- } -- -- if (flag4 && !flag5) { -- // Paper start -- if (this.isTickingReady) { -- io.papermc.paper.chunk.system.ChunkSystem.onChunkNotTicking(this.tickingChunkFuture.join().left().get(), this); // Paper -- } -- // Paper end -- this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage -- this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -- } -- -- boolean flag6 = fullchunkstatus.isOrAfter(FullChunkStatus.ENTITY_TICKING); -- boolean flag7 = fullchunkstatus1.isOrAfter(FullChunkStatus.ENTITY_TICKING); -- -- if (!flag6 && flag7) { -- if (this.entityTickingChunkFuture != ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE) { -- throw (IllegalStateException) Util.pauseInIde(new IllegalStateException()); -- } -- -- this.entityTickingChunkFuture = chunkStorage.prepareEntityTickingChunk(this); -- this.scheduleFullChunkPromotion(chunkStorage, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING); -- // Paper start - cache ticking ready status -- this.entityTickingChunkFuture.thenAccept(either -> { -- either.ifLeft(chunk -> { -- ChunkHolder.this.isEntityTickingReady = true; -- io.papermc.paper.chunk.system.ChunkSystem.onChunkEntityTicking(chunk, this); -- }); -- }); -- // Paper end -- this.updateChunkToSave(this.entityTickingChunkFuture, "entity ticking"); -- } -- -- if (flag6 && !flag7) { -- // Paper start -- if (this.isEntityTickingReady) { -- io.papermc.paper.chunk.system.ChunkSystem.onChunkNotEntityTicking(this.entityTickingChunkFuture.join().left().get(), this); -- } -- // Paper end -- this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage -- this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -- } -- -- if (!fullchunkstatus1.isOrAfter(fullchunkstatus)) { -- this.demoteFullChunk(chunkStorage, fullchunkstatus1); -- } -- -- this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel); -- this.oldTicketLevel = this.ticketLevel; -- // CraftBukkit start -- // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. -- if (!fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) { -- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { -- LevelChunk chunk = (LevelChunk)either.left().orElse(null); -- if (chunk != null) { -- chunkStorage.callbackExecutor.execute(() -> { -- chunk.loadCallback(); -- }); -- } -- }).exceptionally((throwable) -> { -- // ensure exceptions are printed, by default this is not the case -- MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable); -- return null; -- }); -- -- // Run callback right away if the future was already done -- chunkStorage.callbackExecutor.run(); -- } -- // CraftBukkit end -- } -- -- public boolean wasAccessibleSinceLastSave() { -- return this.wasAccessibleSinceLastSave; -+ return this.newChunkHolder.getTicketLevel(); // Paper - rewrite chunk system - } - -- public void refreshAccessibility() { -- this.wasAccessibleSinceLastSave = ChunkLevel.fullStatus(this.ticketLevel).isOrAfter(FullChunkStatus.FULL); -- } -+ // Paper - rewrite chunk system - - public void replaceProtoChunk(ImposterProtoChunk chunk) { -- for (int i = 0; i < this.futures.length(); ++i) { -- CompletableFuture> completablefuture = (CompletableFuture) this.futures.get(i); -- -- if (completablefuture != null) { -- Optional optional = ((Either) completablefuture.getNow(ChunkHolder.UNLOADED_CHUNK)).left(); -- -- if (!optional.isEmpty() && optional.get() instanceof ProtoChunk) { -- this.futures.set(i, CompletableFuture.completedFuture(Either.left(chunk))); -- } -- } -- } -- -- this.updateChunkToSave(CompletableFuture.completedFuture(Either.left(chunk.getWrapped())), "replaceProto"); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public List>>> getAllFutures() { -- List>>> list = new ArrayList(); -- -- for (int i = 0; i < ChunkHolder.CHUNK_STATUSES.size(); ++i) { -- list.add(Pair.of((ChunkStatus) ChunkHolder.CHUNK_STATUSES.get(i), (CompletableFuture) this.futures.get(i))); -- } -- -- return list; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @FunctionalInterface -@@ -704,15 +428,15 @@ public class ChunkHolder { - - // Paper start - public final boolean isEntityTickingReady() { -- return this.isEntityTickingReady; -+ return this.newChunkHolder.isEntityTickingReady(); // Paper - rewrite chunk system - } - - public final boolean isTickingReady() { -- return this.isTickingReady; -+ return this.newChunkHolder.isTickingReady(); // Paper - rewrite chunk system - } - - public final boolean isFullChunkReady() { -- return this.isFullChunkReady; -+ return this.newChunkHolder.isFullChunkReady(); // Paper - rewrite chunk system - } - // Paper end - } -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 3ae47b86b80f9156e71d1da83e492153f360d1b5..5c1accb75655eadd4858ee24cdcdf9b200fbbcb2 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -119,10 +119,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public static final int MIN_VIEW_DISTANCE = 2; - public static final int MAX_VIEW_DISTANCE = 32; - public static final int FORCED_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING); -- public final Long2ObjectLinkedOpenHashMap updatingChunkMap = new Long2ObjectLinkedOpenHashMap(); -- public volatile Long2ObjectLinkedOpenHashMap visibleChunkMap; -- private final Long2ObjectLinkedOpenHashMap pendingUnloads; -- private final LongSet entitiesInLevel; -+ // Paper - rewrite chunk system - public final ServerLevel level; - private final ThreadedLevelLightEngine lightEngine; - public final BlockableEventLoop mainThreadExecutor; // Paper - public -@@ -131,16 +128,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - private final ChunkGeneratorStructureState chunkGeneratorState; - public final Supplier overworldDataStorage; - private final PoiManager poiManager; -- public final LongSet toDrop; -+ // Paper - rewrite chunk system - private boolean modified; -- private final ChunkTaskPriorityQueueSorter queueSorter; -- private final ProcessorHandle> worldgenMailbox; -- private final ProcessorHandle> mainThreadMailbox; -+ // Paper - rewrite chunk system - public final ChunkProgressListener progressListener; - private final ChunkStatusUpdateListener chunkStatusListener; - public final ChunkMap.ChunkDistanceManager distanceManager; - private final AtomicInteger tickingGenerated; -- private final StructureTemplateManager structureTemplateManager; -+ public final StructureTemplateManager structureTemplateManager; // Paper - rewrite chunk system - private final String storageName; - private final PlayerMap playerMap; - public final Int2ObjectMap entityMap; -@@ -149,27 +144,6 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - private final Queue unloadQueue; - public int serverViewDistance; - -- // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() -- public final CallbackExecutor callbackExecutor = new CallbackExecutor(); -- public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { -- -- private final java.util.Queue queue = new java.util.ArrayDeque<>(); -- -- @Override -- public void execute(Runnable runnable) { -- this.queue.add(runnable); -- } -- -- @Override -- public void run() { -- Runnable task; -- while ((task = this.queue.poll()) != null) { -- task.run(); -- } -- } -- }; -- // CraftBukkit end -- - // Paper start - distance maps - private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); - -@@ -178,6 +152,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated - this.nearbyPlayers.addPlayer(player); -+ this.level.playerChunkLoader.addPlayer(player); // Paper - replace chunk loader - } - - void removePlayerFromDistanceMaps(ServerPlayer player) { -@@ -185,6 +160,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated - this.nearbyPlayers.removePlayer(player); -+ this.level.playerChunkLoader.removePlayer(player); // Paper - replace chunk loader - } - - void updateMaps(ServerPlayer player) { -@@ -192,6 +168,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated - this.nearbyPlayers.tickPlayer(player); -+ this.level.playerChunkLoader.updatePlayer(player); // Paper - replace chunk loader - } - // Paper end - // Paper start -@@ -221,17 +198,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) { -- return this.pendingUnloads.get(io.papermc.paper.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ return null; // Paper - rewrite chunk system - } - public final io.papermc.paper.util.player.NearbyPlayers nearbyPlayers; - // Paper end - - public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { - super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); -- this.visibleChunkMap = this.updatingChunkMap.clone(); -- this.pendingUnloads = new Long2ObjectLinkedOpenHashMap(); -- this.entitiesInLevel = new LongOpenHashSet(); -- this.toDrop = new LongOpenHashSet(); -+ // Paper - rewrite chunk system - this.tickingGenerated = new AtomicInteger(); - this.playerMap = new PlayerMap(); - this.entityMap = new Int2ObjectOpenHashMap(); -@@ -262,19 +236,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j, world.spigotConfig); // Spigot - this.mainThreadExecutor = mainThreadExecutor; -- ProcessorMailbox threadedmailbox = ProcessorMailbox.create(executor, "worldgen"); -+ // Paper - rewrite chunk system - - Objects.requireNonNull(mainThreadExecutor); -- ProcessorHandle mailbox = ProcessorHandle.of("main", mainThreadExecutor::tell); -+ // Paper - rewrite chunk system - - this.progressListener = worldGenerationProgressListener; - this.chunkStatusListener = chunkStatusChangeListener; -- ProcessorMailbox threadedmailbox1 = ProcessorMailbox.create(executor, "light"); -+ // Paper - rewrite chunk system - -- this.queueSorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE); -- this.worldgenMailbox = this.queueSorter.getProcessor(threadedmailbox, false); -- this.mainThreadMailbox = this.queueSorter.getProcessor(mailbox, false); -- this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), threadedmailbox1, this.queueSorter.getProcessor(threadedmailbox1, false)); -+ // Paper - rewrite chunk system -+ this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), null, null); // Paper - rewrite chunk system - this.distanceManager = new ChunkMap.ChunkDistanceManager(executor, mainThreadExecutor); - this.overworldDataStorage = persistentStateManagerFactory; - this.poiManager = new PoiManager(path.resolve("poi"), dataFixer, dsync, iregistrycustom, world); -@@ -330,23 +302,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - boolean isChunkTracked(ServerPlayer player, int chunkX, int chunkZ) { -- return player.getChunkTrackingView().contains(chunkX, chunkZ) && !player.connection.chunkSender.isPending(ChunkPos.asLong(chunkX, chunkZ)); -+ // Paper start - rewrite player chunk loader -+ return this.level.playerChunkLoader.isChunkSent(player, chunkX, chunkZ); -+ // Paper end - rewrite player chunk loader - } - - private boolean isChunkOnTrackedBorder(ServerPlayer player, int chunkX, int chunkZ) { -- if (!this.isChunkTracked(player, chunkX, chunkZ)) { -- return false; -- } else { -- for (int k = -1; k <= 1; ++k) { -- for (int l = -1; l <= 1; ++l) { -- if ((k != 0 || l != 0) && !this.isChunkTracked(player, chunkX + k, chunkZ + l)) { -- return true; -- } -- } -- } -- -- return false; -- } -+ // Paper start - rewrite player chunk loader -+ return this.level.playerChunkLoader.isChunkSent(player, chunkX, chunkZ, true); -+ // Paper end - rewrite player chunk loader - } - - protected ThreadedLevelLightEngine getLightEngine() { -@@ -355,20 +319,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - @Nullable - protected ChunkHolder getUpdatingChunkIfPresent(long pos) { -- return (ChunkHolder) this.updatingChunkMap.get(pos); -+ // Paper start - rewrite chunk system -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder holder = this.level.chunkTaskScheduler.chunkHolderManager.getChunkHolder(pos); -+ return holder == null ? null : holder.vanillaChunkHolder; -+ // Paper end - rewrite chunk system - } - - @Nullable - public ChunkHolder getVisibleChunkIfPresent(long pos) { -- return (ChunkHolder) this.visibleChunkMap.get(pos); -+ // Paper start - rewrite chunk system -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder holder = this.level.chunkTaskScheduler.chunkHolderManager.getChunkHolder(pos); -+ return holder == null ? null : holder.vanillaChunkHolder; -+ // Paper end - rewrite chunk system - } - - protected IntSupplier getChunkQueueLevel(long pos) { -- return () -> { -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); -- -- return playerchunk == null ? ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1 : Math.min(playerchunk.getQueueLevel(), ChunkTaskPriorityQueue.PRIORITY_LEVEL_COUNT - 1); -- }; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public String getChunkDebugData(ChunkPos chunkPos) { -@@ -397,84 +363,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - private CompletableFuture, ChunkHolder.ChunkLoadingFailure>> getChunkRangeFuture(ChunkHolder centerChunk, int margin, IntFunction distanceToStatus) { -- if (margin == 0) { -- ChunkStatus chunkstatus = (ChunkStatus) distanceToStatus.apply(0); -- -- return centerChunk.getOrScheduleFuture(chunkstatus, this).thenApply((either) -> { -- return either.mapLeft(List::of); -- }); -- } else { -- List>> list = new ArrayList(); -- List list1 = new ArrayList(); -- ChunkPos chunkcoordintpair = centerChunk.getPos(); -- int j = chunkcoordintpair.x; -- int k = chunkcoordintpair.z; -- -- for (int l = -margin; l <= margin; ++l) { -- for (int i1 = -margin; i1 <= margin; ++i1) { -- int j1 = Math.max(Math.abs(i1), Math.abs(l)); -- final ChunkPos chunkcoordintpair1 = new ChunkPos(j + i1, k + l); -- long k1 = chunkcoordintpair1.toLong(); -- ChunkHolder playerchunk1 = this.getUpdatingChunkIfPresent(k1); -- -- if (playerchunk1 == null) { -- return CompletableFuture.completedFuture(Either.right(new ChunkHolder.ChunkLoadingFailure() { -- public String toString() { -- return "Unloaded " + chunkcoordintpair1; -- } -- })); -- } -- -- ChunkStatus chunkstatus1 = (ChunkStatus) distanceToStatus.apply(j1); -- CompletableFuture> completablefuture = playerchunk1.getOrScheduleFuture(chunkstatus1, this); -- -- list1.add(playerchunk1); -- list.add(completablefuture); -- } -- } -- -- CompletableFuture>> completablefuture1 = Util.sequence(list); -- CompletableFuture, ChunkHolder.ChunkLoadingFailure>> completablefuture2 = completablefuture1.thenApply((list2) -> { -- List list3 = Lists.newArrayList(); -- // CraftBukkit start - decompile error -- int cnt = 0; -- -- for (Iterator iterator = list2.iterator(); iterator.hasNext(); ++cnt) { -- final int l1 = cnt; -- // CraftBukkit end -- final Either either = (Either) iterator.next(); -- -- if (either == null) { -- throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a"); -- } -- -- Optional optional = either.left(); -- -- if (optional.isEmpty()) { -- return Either.right(new ChunkHolder.ChunkLoadingFailure() { -- public String toString() { -- ChunkPos chunkcoordintpair2 = new ChunkPos(j + l1 % (margin * 2 + 1), k + l1 / (margin * 2 + 1)); -- -- return "Unloaded " + chunkcoordintpair2 + " " + either.right().get(); -- } -- }); -- } -- -- list3.add((ChunkAccess) optional.get()); -- } -- -- return Either.left(list3); -- }); -- Iterator iterator = list1.iterator(); -- -- while (iterator.hasNext()) { -- ChunkHolder playerchunk2 = (ChunkHolder) iterator.next(); -- -- playerchunk2.addSaveDependency("getChunkRangeFuture " + chunkcoordintpair + " " + margin, completablefuture2); -- } -- -- return completablefuture2; -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public ReportedException debugFuturesAndCreateReportedException(IllegalStateException exception, String details) { -@@ -504,263 +393,72 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - public CompletableFuture> prepareEntityTickingChunk(ChunkHolder chunk) { -- return this.getChunkRangeFuture(chunk, 2, (i) -> { -- return ChunkStatus.FULL; -- }).thenApplyAsync((either) -> { -- return either.mapLeft((list) -> { -- return (LevelChunk) list.get(list.size() / 2); -- }); -- }, this.mainThreadExecutor); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @Nullable - ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k) { -- if (!ChunkLevel.isLoaded(k) && !ChunkLevel.isLoaded(level)) { -- return holder; -- } else { -- if (holder != null) { -- holder.setTicketLevel(level); -- } -- -- if (holder != null) { -- if (!ChunkLevel.isLoaded(level)) { -- this.toDrop.add(pos); -- } else { -- this.toDrop.remove(pos); -- } -- } -- -- if (ChunkLevel.isLoaded(level) && holder == null) { -- holder = (ChunkHolder) this.pendingUnloads.remove(pos); -- if (holder != null) { -- holder.setTicketLevel(level); -- } else { -- holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this); -- // Paper start -- io.papermc.paper.chunk.system.ChunkSystem.onChunkHolderCreate(this.level, holder); -- // Paper end -- } -- -- // Paper start -- holder.onChunkAdd(); -- // Paper end -- this.updatingChunkMap.put(pos, holder); -- this.modified = true; -- } -- -- return holder; -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @Override - public void close() throws IOException { -- try { -- this.queueSorter.close(); -- this.poiManager.close(); -- } finally { -- super.close(); -- } -+ throw new UnsupportedOperationException("Use ServerChunkCache#close"); // Paper - rewrite chunk system -+ } - -+ // Paper start - rewrite chunk system -+ protected void saveIncrementally() { -+ this.level.chunkTaskScheduler.chunkHolderManager.autoSave(); // Paper - rewrite chunk system - } -+ // Paper end - - rewrite chunk system - - protected void saveAllChunks(boolean flush) { -- if (flush) { -- List list = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); // Paper -- MutableBoolean mutableboolean = new MutableBoolean(); -- -- do { -- mutableboolean.setFalse(); -- list.stream().map((playerchunk) -> { -- CompletableFuture completablefuture; -- -- do { -- completablefuture = playerchunk.getChunkToSave(); -- BlockableEventLoop iasynctaskhandler = this.mainThreadExecutor; -- -- Objects.requireNonNull(completablefuture); -- iasynctaskhandler.managedBlock(completablefuture::isDone); -- } while (completablefuture != playerchunk.getChunkToSave()); -- -- return (ChunkAccess) completablefuture.join(); -- }).filter((ichunkaccess) -> { -- return ichunkaccess instanceof ImposterProtoChunk || ichunkaccess instanceof LevelChunk; -- }).filter(this::save).forEach((ichunkaccess) -> { -- mutableboolean.setTrue(); -- }); -- } while (mutableboolean.isTrue()); -- -- this.processUnloads(() -> { -- return true; -- }); -- this.flushWorker(); -- } else { -- io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).forEach(this::saveChunkIfNeeded); -- } -- -+ this.level.chunkTaskScheduler.chunkHolderManager.saveAllChunks(flush, false, false); // Paper - rewrite chunk system - } - - protected void tick(BooleanSupplier shouldKeepTicking) { - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); - -+ try (Timing ignored = this.level.timings.poiUnload.startTiming()) { // Paper - gameprofilerfiller.push("poi"); - this.poiManager.tick(shouldKeepTicking); -+ } // Paper - gameprofilerfiller.popPush("chunk_unload"); - if (!this.level.noSave()) { -+ try (Timing ignored = this.level.timings.chunkUnload.startTiming()) { // Paper - this.processUnloads(shouldKeepTicking); -+ } // Paper - } - - gameprofilerfiller.pop(); - } - - public boolean hasWork() { -- return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || io.papermc.paper.chunk.system.ChunkSystem.hasAnyChunkHolders(this.level) || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets(); // Paper -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - private void processUnloads(BooleanSupplier shouldKeepTicking) { -- LongIterator longiterator = this.toDrop.iterator(); -- -- for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) { -- long j = longiterator.nextLong(); -- ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j); -- -- if (playerchunk != null) { -- playerchunk.onChunkRemove(); // Paper -- this.pendingUnloads.put(j, playerchunk); -- this.modified = true; -- ++i; -- this.scheduleUnload(j, playerchunk); -- } -- } -- -- int k = Math.max(0, this.unloadQueue.size() - 2000); -- -- Runnable runnable; -- -- while ((shouldKeepTicking.getAsBoolean() || k > 0) && (runnable = (Runnable) this.unloadQueue.poll()) != null) { -- --k; -- runnable.run(); -- } -- -- int l = 0; -- Iterator objectiterator = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper -- -- while (l < 20 && shouldKeepTicking.getAsBoolean() && objectiterator.hasNext()) { -- if (this.saveChunkIfNeeded((ChunkHolder) objectiterator.next())) { -- ++l; -- } -- } -+ this.level.chunkTaskScheduler.chunkHolderManager.processUnloads(); // Paper - rewrite chunk system - - } - - private void scheduleUnload(long pos, ChunkHolder holder) { -- CompletableFuture completablefuture = holder.getChunkToSave(); -- Consumer consumer = (ichunkaccess) -> { // CraftBukkit - decompile error -- CompletableFuture completablefuture1 = holder.getChunkToSave(); -- -- if (completablefuture1 != completablefuture) { -- this.scheduleUnload(pos, holder); -- } else { -- // Paper start -- boolean removed; -- if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) { -- io.papermc.paper.chunk.system.ChunkSystem.onChunkHolderDelete(this.level, holder); -- // Paper end -- if (ichunkaccess instanceof LevelChunk) { -- ((LevelChunk) ichunkaccess).setLoaded(false); -- } -- -- this.save(ichunkaccess); -- if (this.entitiesInLevel.remove(pos) && ichunkaccess instanceof LevelChunk) { -- LevelChunk chunk = (LevelChunk) ichunkaccess; -- -- this.level.unload(chunk); -- } -- -- this.lightEngine.updateChunkStatus(ichunkaccess.getPos()); -- this.lightEngine.tryScheduleUpdate(); -- this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null); -- this.chunkSaveCooldowns.remove(ichunkaccess.getPos().toLong()); -- } else if (removed) { // Paper start -- io.papermc.paper.chunk.system.ChunkSystem.onChunkHolderDelete(this.level, holder); -- } // Paper end -- -- } -- }; -- Queue queue = this.unloadQueue; -- -- Objects.requireNonNull(this.unloadQueue); -- completablefuture.thenAcceptAsync(consumer, queue::add).whenComplete((ovoid, throwable) -> { -- if (throwable != null) { -- ChunkMap.LOGGER.error("Failed to save chunk {}", holder.getPos(), throwable); -- } -- -- }); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - protected boolean promoteChunkMap() { -- if (!this.modified) { -- return false; -- } else { -- this.visibleChunkMap = this.updatingChunkMap.clone(); -- this.modified = false; -- return true; -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public CompletableFuture> schedule(ChunkHolder holder, ChunkStatus requiredStatus) { -- ChunkPos chunkcoordintpair = holder.getPos(); -- -- if (requiredStatus == ChunkStatus.EMPTY) { -- return this.scheduleChunkLoad(chunkcoordintpair); -- } else { -- if (requiredStatus == ChunkStatus.LIGHT) { -- this.distanceManager.addTicket(TicketType.LIGHT, chunkcoordintpair, ChunkLevel.byStatus(ChunkStatus.LIGHT), chunkcoordintpair); -- } -- -- if (!requiredStatus.hasLoadDependencies()) { -- Optional optional = ((Either) holder.getOrScheduleFuture(requiredStatus.getParent(), this).getNow(ChunkHolder.UNLOADED_CHUNK)).left(); -- -- if (optional.isPresent() && ((ChunkAccess) optional.get()).getStatus().isOrAfter(requiredStatus)) { -- CompletableFuture> completablefuture = requiredStatus.load(this.level, this.structureTemplateManager, this.lightEngine, (ichunkaccess) -> { -- return this.protoChunkToFullChunk(holder); -- }, (ChunkAccess) optional.get()); -- -- this.progressListener.onStatusChange(chunkcoordintpair, requiredStatus); -- return completablefuture; -- } -- } -- -- return this.scheduleChunkGeneration(holder, requiredStatus); -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - private CompletableFuture> scheduleChunkLoad(ChunkPos pos) { -- return this.readChunk(pos).thenApply((optional) -> { -- return optional.filter((nbttagcompound) -> { -- boolean flag = ChunkMap.isChunkDataValid(nbttagcompound); -- -- if (!flag) { -- ChunkMap.LOGGER.error("Chunk file at {} is missing level data, skipping", pos); -- } -- -- return flag; -- }); -- }).thenApplyAsync((optional) -> { -- this.level.getProfiler().incrementCounter("chunkLoad"); -- if (optional.isPresent()) { -- ProtoChunk protochunk = ChunkSerializer.read(this.level, this.poiManager, pos, (CompoundTag) optional.get()); -- -- this.markPosition(pos, protochunk.getStatus().getChunkType()); -- return Either.left(protochunk); // CraftBukkit - decompile error -- } else { -- return Either.left(this.createEmptyChunk(pos)); // CraftBukkit - decompile error -- } -- }, this.mainThreadExecutor).exceptionallyAsync((throwable) -> { -- return this.handleChunkLoadFailure(throwable, pos); -- }, this.mainThreadExecutor); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - -- private static boolean isChunkDataValid(CompoundTag nbt) { -+ public static boolean isChunkDataValid(CompoundTag nbt) { // Paper - async chunk loading - return nbt.contains("Status", 8); - } - -@@ -796,54 +494,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - private CompletableFuture> scheduleChunkGeneration(ChunkHolder holder, ChunkStatus requiredStatus) { -- ChunkPos chunkcoordintpair = holder.getPos(); -- CompletableFuture, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkRangeFuture(holder, requiredStatus.getRange(), (i) -> { -- return this.getDependencyStatus(requiredStatus, i); -- }); -- -- this.level.getProfiler().incrementCounter(() -> { -- return "chunkGenerate " + requiredStatus; -- }); -- Executor executor = (runnable) -> { -- this.worldgenMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); -- }; -- -- return completablefuture.thenComposeAsync((either) -> { -- return (CompletionStage) either.map((list) -> { -- try { -- ChunkAccess ichunkaccess = (ChunkAccess) list.get(list.size() / 2); -- CompletableFuture completablefuture1; -- -- if (ichunkaccess.getStatus().isOrAfter(requiredStatus)) { -- completablefuture1 = requiredStatus.load(this.level, this.structureTemplateManager, this.lightEngine, (ichunkaccess1) -> { -- return this.protoChunkToFullChunk(holder); -- }, ichunkaccess); -- } else { -- completablefuture1 = requiredStatus.generate(executor, this.level, this.generator, this.structureTemplateManager, this.lightEngine, (ichunkaccess1) -> { -- return this.protoChunkToFullChunk(holder); -- }, list); -- } -- -- this.progressListener.onStatusChange(chunkcoordintpair, requiredStatus); -- return completablefuture1; -- } catch (Exception exception) { -- exception.getStackTrace(); -- CrashReport crashreport = CrashReport.forThrowable(exception, "Exception generating new chunk"); -- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk to be generated"); -- -- crashreportsystemdetails.setDetail("Location", (Object) String.format(Locale.ROOT, "%d,%d", chunkcoordintpair.x, chunkcoordintpair.z)); -- crashreportsystemdetails.setDetail("Position hash", (Object) ChunkPos.asLong(chunkcoordintpair.x, chunkcoordintpair.z)); -- crashreportsystemdetails.setDetail("Generator", (Object) this.generator); -- this.mainThreadExecutor.execute(() -> { -- throw new ReportedException(crashreport); -- }); -- throw new ReportedException(crashreport); -- } -- }, (playerchunk_failure) -> { -- this.releaseLightTicket(chunkcoordintpair); -- return CompletableFuture.completedFuture(Either.right(playerchunk_failure)); -- }); -- }, executor); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - protected void releaseLightTicket(ChunkPos pos) { -@@ -854,7 +505,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - })); - } - -- private ChunkStatus getDependencyStatus(ChunkStatus centerChunkTargetStatus, int distance) { -+ public static ChunkStatus getDependencyStatus(ChunkStatus centerChunkTargetStatus, int distance) { // Paper -> public, static - ChunkStatus chunkstatus1; - - if (distance == 0) { -@@ -866,7 +517,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - return chunkstatus1; - } - -- private static void postLoadProtoChunk(ServerLevel world, List nbt) { -+ public static void postLoadProtoChunk(ServerLevel world, List nbt, ChunkPos position) { // Paper - public and add chunk position parameter - if (!nbt.isEmpty()) { - // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities - world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(nbt, world).filter((entity) -> { -@@ -882,53 +533,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - checkDupeUUID(world, entity); // Paper - duplicate uuid resolving - return !needsRemoval; -- })); -+ }), position); // Paper - rewrite chunk system - // CraftBukkit end - } - - } - - private CompletableFuture> protoChunkToFullChunk(ChunkHolder chunkHolder) { -- CompletableFuture> completablefuture = chunkHolder.getFutureIfPresentUnchecked(ChunkStatus.FULL.getParent()); -- -- return completablefuture.thenApplyAsync((either) -> { -- ChunkStatus chunkstatus = ChunkLevel.generationStatus(chunkHolder.getTicketLevel()); -- -- return !chunkstatus.isOrAfter(ChunkStatus.FULL) ? ChunkHolder.UNLOADED_CHUNK : either.mapLeft((ichunkaccess) -> { -- try (Timing ignored = level.timings.chunkPostLoad.startTimingIfSync()) { // Paper -- ChunkPos chunkcoordintpair = chunkHolder.getPos(); -- ProtoChunk protochunk = (ProtoChunk) ichunkaccess; -- LevelChunk chunk; -- -- if (protochunk instanceof ImposterProtoChunk) { -- chunk = ((ImposterProtoChunk) protochunk).getWrapped(); -- } else { -- chunk = new LevelChunk(this.level, protochunk, (chunk1) -> { -- ChunkMap.postLoadProtoChunk(this.level, protochunk.getEntities()); -- }); -- chunkHolder.replaceProtoChunk(new ImposterProtoChunk(chunk, false)); -- } -- -- chunk.setFullStatus(() -> { -- return ChunkLevel.fullStatus(chunkHolder.getTicketLevel()); -- }); -- chunk.runPostLoad(); -- if (this.entitiesInLevel.add(chunkcoordintpair.toLong())) { -- chunk.setLoaded(true); -- chunk.registerAllBlockEntitiesAfterLevelLoad(); -- chunk.registerTickContainerInLevel(this.level); -- } -- -- return chunk; -- } // Paper -- }); -- }, (runnable) -> { -- ProcessorHandle mailbox = this.mainThreadMailbox; -- long i = chunkHolder.getPos().toLong(); -- -- Objects.requireNonNull(chunkHolder); -- mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, chunkHolder::getTicketLevel)); -- }); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - // Paper start - duplicate uuid resolving -@@ -971,61 +583,16 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - // Paper end - duplicate uuid resolving - public CompletableFuture> prepareTickingChunk(ChunkHolder holder) { -- CompletableFuture, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkRangeFuture(holder, 1, (i) -> { -- return ChunkStatus.FULL; -- }); -- CompletableFuture> completablefuture1 = completablefuture.thenApplyAsync((either) -> { -- return either.mapLeft((list) -> { -- return (LevelChunk) list.get(list.size() / 2); -- }); -- }, (runnable) -> { -- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); -- }).thenApplyAsync((either) -> { -- return either.ifLeft((chunk) -> { -- chunk.postProcessGeneration(); -- this.level.startTickingChunk(chunk); -- CompletableFuture completablefuture2 = holder.getChunkSendSyncFuture(); -- -- if (completablefuture2.isDone()) { -- this.onChunkReadyToSend(chunk); -- } else { -- completablefuture2.thenAcceptAsync((object) -> { -- this.onChunkReadyToSend(chunk); -- }, this.mainThreadExecutor); -- } -- -- }); -- }, this.mainThreadExecutor); -- -- completablefuture1.handle((either, throwable) -> { -- this.tickingGenerated.getAndIncrement(); -- return null; -- }); -- return completablefuture1; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - private void onChunkReadyToSend(LevelChunk chunk) { -- ChunkPos chunkcoordintpair = chunk.getPos(); -- Iterator iterator = this.playerMap.getAllPlayers().iterator(); -- -- while (iterator.hasNext()) { -- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -- -- if (entityplayer.getChunkTrackingView().contains(chunkcoordintpair)) { -- ChunkMap.markChunkPendingToSend(entityplayer, chunk); -- } -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite player chunk loader - - } - - public CompletableFuture> prepareAccessibleChunk(ChunkHolder holder) { -- return this.getChunkRangeFuture(holder, 1, ChunkStatus::getStatusAroundFullChunk).thenApplyAsync((either) -> { -- return either.mapLeft((list) -> { -- return (LevelChunk) list.get(list.size() / 2); -- }); -- }, (runnable) -> { -- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); -- }); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public int getTickingGenerated() { -@@ -1033,130 +600,52 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - private boolean saveChunkIfNeeded(ChunkHolder chunkHolder) { -- if (!chunkHolder.wasAccessibleSinceLastSave()) { -- return false; -- } else { -- ChunkAccess ichunkaccess = (ChunkAccess) chunkHolder.getChunkToSave().getNow(null); // CraftBukkit - decompile error -- -- if (!(ichunkaccess instanceof ImposterProtoChunk) && !(ichunkaccess instanceof LevelChunk)) { -- return false; -- } else { -- long i = ichunkaccess.getPos().toLong(); -- long j = this.chunkSaveCooldowns.getOrDefault(i, -1L); -- long k = System.currentTimeMillis(); -- -- if (k < j) { -- return false; -- } else { -- boolean flag = this.save(ichunkaccess); -- -- chunkHolder.refreshAccessibility(); -- if (flag) { -- this.chunkSaveCooldowns.put(i, k + 10000L); -- } -- -- return flag; -- } -- } -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public boolean save(ChunkAccess chunk) { -- this.poiManager.flush(chunk.getPos()); -- if (!chunk.isUnsaved()) { -- return false; -- } else { -- chunk.setUnsaved(false); -- ChunkPos chunkcoordintpair = chunk.getPos(); -- -- try { -- ChunkStatus chunkstatus = chunk.getStatus(); -- -- if (chunkstatus.getChunkType() != ChunkStatus.ChunkType.LEVELCHUNK) { -- if (this.isExistingChunkFull(chunkcoordintpair)) { -- return false; -- } -- -- if (chunkstatus == ChunkStatus.EMPTY && chunk.getAllStarts().values().stream().noneMatch(StructureStart::isValid)) { -- return false; -- } -- } -- -- this.level.getProfiler().incrementCounter("chunkSave"); -- CompoundTag nbttagcompound = ChunkSerializer.write(this.level, chunk); -- -- this.write(chunkcoordintpair, nbttagcompound); -- this.markPosition(chunkcoordintpair, chunkstatus.getChunkType()); -- return true; -- } catch (Exception exception) { -- ChunkMap.LOGGER.error("Failed to save chunk {},{}", new Object[]{chunkcoordintpair.x, chunkcoordintpair.z, exception}); -- return false; -- } -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - private boolean isExistingChunkFull(ChunkPos pos) { -- byte b0 = this.chunkTypeCache.get(pos.toLong()); -- -- if (b0 != 0) { -- return b0 == 1; -- } else { -- CompoundTag nbttagcompound; -- -- try { -- nbttagcompound = (CompoundTag) ((Optional) this.readChunk(pos).join()).orElse((Object) null); -- if (nbttagcompound == null) { -- this.markPositionReplaceable(pos); -- return false; -- } -- } catch (Exception exception) { -- ChunkMap.LOGGER.error("Failed to read chunk {}", pos, exception); -- this.markPositionReplaceable(pos); -- return false; -- } -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system -+ } - -- ChunkStatus.ChunkType chunkstatus_type = ChunkSerializer.getChunkTypeFromTag(nbttagcompound); -+ // Paper start - replace player loader system -+ public void setTickViewDistance(int distance) { -+ this.level.playerChunkLoader.setTickDistance(distance); -+ } - -- return this.markPosition(pos, chunkstatus_type) == 1; -- } -+ public void setSendViewDistance(int distance) { -+ this.level.playerChunkLoader.setSendDistance(distance); - } -+ // Paper end - replace player loader system - - public void setServerViewDistance(int watchDistance) { // Paper - public - int j = Mth.clamp(watchDistance, 2, 32); - - if (j != this.serverViewDistance) { - this.serverViewDistance = j; -- this.distanceManager.updatePlayerTickets(this.serverViewDistance); -- Iterator iterator = this.playerMap.getAllPlayers().iterator(); -- -- while (iterator.hasNext()) { -- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -- -- this.updateChunkTracking(entityplayer); -- } -+ this.level.playerChunkLoader.setLoadDistance(this.serverViewDistance + 1); // Paper - replace player loader system - } - - } - - public int getPlayerViewDistance(ServerPlayer player) { // Paper - public -- return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance); -+ return io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player); // Paper - per player view distance - } - - private void markChunkPendingToSend(ServerPlayer player, ChunkPos pos) { -- LevelChunk chunk = this.getChunkToSend(pos.toLong()); -- -- if (chunk != null) { -- ChunkMap.markChunkPendingToSend(player, chunk); -- } -+ throw new UnsupportedOperationException(); // Paper - per player view distance - - } - - private static void markChunkPendingToSend(ServerPlayer player, LevelChunk chunk) { -- player.connection.chunkSender.markChunkPendingToSend(chunk); -+ throw new UnsupportedOperationException(); // Paper - rewrite player chunk loader - } - - private static void dropChunk(ServerPlayer player, ChunkPos pos) { -- player.connection.chunkSender.dropChunk(player, pos); -+ // Paper - rewrite player chunk loader - } - - @Nullable -@@ -1179,30 +668,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - void dumpChunks(Writer writer) throws IOException { -- CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer); -- TickingTracker tickingtracker = this.distanceManager.tickingTracker(); -- Iterator objectbidirectionaliterator = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper -- -- while (objectbidirectionaliterator.hasNext()) { -- ChunkHolder playerchunk = objectbidirectionaliterator.next(); // Paper -- long i = playerchunk.pos.toLong(); // Paper -- ChunkPos chunkcoordintpair = new ChunkPos(i); -- // Paper -- Optional optional = Optional.ofNullable(playerchunk.getLastAvailable()); -- Optional optional1 = optional.flatMap((ichunkaccess) -> { -- return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty(); -- }); -- -- // CraftBukkit - decompile error -- csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> { -- return chunk.getBlockEntities().size(); -- }).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> { -- return chunk.getBlockTicks().count(); -- }).orElse(0), optional1.map((chunk) -> { -- return chunk.getFluidTicks().count(); -- }).orElse(0)); -- } -- -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - private static String printFuture(CompletableFuture> future) { -@@ -1221,6 +687,35 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - } - -+ // Paper start - Asynchronous chunk io -+ @Nullable -+ @Override -+ public CompoundTag readSync(ChunkPos chunkcoordintpair) throws IOException { -+ // Paper start - rewrite chunk system -+ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { -+ return io.papermc.paper.chunk.system.io.RegionFileIOThread.loadData( -+ this.level, chunkcoordintpair.x, chunkcoordintpair.z, io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.CHUNK_DATA, -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.getIOBlockingPriorityForCurrentThread() -+ ); -+ } -+ // Paper end - rewrite chunk system -+ return super.readSync(chunkcoordintpair); -+ } -+ -+ @Override -+ public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws IOException { -+ // Paper start - rewrite chunk system -+ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.scheduleSave( -+ this.level, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.CHUNK_DATA); -+ return; -+ } -+ // Paper end - rewrite chunk system -+ super.write(chunkcoordintpair, nbttagcompound); -+ } -+ // Paper end -+ - private CompletableFuture> readChunk(ChunkPos chunkPos) { - return this.read(chunkPos).thenApplyAsync((optional) -> { - return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit -@@ -1321,8 +816,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.distanceManager.addPlayer(SectionPos.of((EntityAccess) player), player); - } - -- player.setChunkTrackingView(ChunkTrackingView.EMPTY); -- this.updateChunkTracking(player); -+ // Paper - handled by player chunk loader - this.addPlayerToDistanceMaps(player); // Paper - distance maps - } else { - SectionPos sectionposition = player.getLastSectionPos(); -@@ -1333,7 +827,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - this.removePlayerFromDistanceMaps(player); // Paper - distance maps -- this.applyChunkTrackingView(player, ChunkTrackingView.EMPTY); -+ // Paper - handled by player chunk loader - } - - } -@@ -1381,73 +875,30 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerMap.unIgnorePlayer(player); - } - -- this.updateChunkTracking(player); -+ // Paper - replaced by PlayerChunkLoader - } - - this.updateMaps(player); // Paper - distance maps - } - - private void updateChunkTracking(ServerPlayer player) { -- ChunkPos chunkcoordintpair = player.chunkPosition(); -- int i = this.getPlayerViewDistance(player); -- ChunkTrackingView chunktrackingview = player.getChunkTrackingView(); -- -- if (chunktrackingview instanceof ChunkTrackingView.Positioned) { -- ChunkTrackingView.Positioned chunktrackingview_a = (ChunkTrackingView.Positioned) chunktrackingview; -- -- if (chunktrackingview_a.center().equals(chunkcoordintpair) && chunktrackingview_a.viewDistance() == i) { -- return; -- } -- } -- -- this.applyChunkTrackingView(player, ChunkTrackingView.of(chunkcoordintpair, i)); -+ throw new UnsupportedOperationException(); // Paper - replaced by PlayerChunkLoader - } - - private void applyChunkTrackingView(ServerPlayer player, ChunkTrackingView chunkFilter) { -- if (player.level() == this.level) { -- ChunkTrackingView chunktrackingview1 = player.getChunkTrackingView(); -- -- if (chunkFilter instanceof ChunkTrackingView.Positioned) { -- label15: -- { -- ChunkTrackingView.Positioned chunktrackingview_a = (ChunkTrackingView.Positioned) chunkFilter; -- -- if (chunktrackingview1 instanceof ChunkTrackingView.Positioned) { -- ChunkTrackingView.Positioned chunktrackingview_a1 = (ChunkTrackingView.Positioned) chunktrackingview1; -- -- if (chunktrackingview_a1.center().equals(chunktrackingview_a.center())) { -- break label15; -- } -- } -- -- player.connection.send(new ClientboundSetChunkCacheCenterPacket(chunktrackingview_a.center().x, chunktrackingview_a.center().z)); -- } -- } -- -- ChunkTrackingView.difference(chunktrackingview1, chunkFilter, (chunkcoordintpair) -> { -- this.markChunkPendingToSend(player, chunkcoordintpair); -- }, (chunkcoordintpair) -> { -- ChunkMap.dropChunk(player, chunkcoordintpair); -- }); -- player.setChunkTrackingView(chunkFilter); -- } -+ throw new UnsupportedOperationException(); // Paper - replaced by PlayerChunkLoader - } - - @Override - public List getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) { -- Set set = this.playerMap.getAllPlayers(); -- Builder builder = ImmutableList.builder(); -- Iterator iterator = set.iterator(); -- -- while (iterator.hasNext()) { -- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -- -- if (onlyOnWatchDistanceEdge && this.isChunkOnTrackedBorder(entityplayer, chunkPos.x, chunkPos.z) || !onlyOnWatchDistanceEdge && this.isChunkTracked(entityplayer, chunkPos.x, chunkPos.z)) { -- builder.add(entityplayer); -- } -+ // Paper start - per player view distance -+ ChunkHolder holder = this.getVisibleChunkIfPresent(chunkPos.toLong()); -+ if (holder == null) { -+ return new java.util.ArrayList<>(); -+ } else { -+ return holder.getPlayers(onlyOnWatchDistanceEdge); - } -- -- return builder.build(); -+ // Paper end - per player view distance - } - - public void addEntity(Entity entity) { -@@ -1520,13 +971,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - protected void tick() { -- Iterator iterator = this.playerMap.getAllPlayers().iterator(); -- -- while (iterator.hasNext()) { -- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -- -- this.updateChunkTracking(entityplayer); -- } -+ // Paper - replaced by PlayerChunkLoader - - List list = Lists.newArrayList(); - List list1 = this.level.players(); -@@ -1635,16 +1080,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - public void waitForLightBeforeSending(ChunkPos centerPos, int radius) { -- int j = radius + 1; -- -- ChunkPos.rangeClosed(centerPos, j).forEach((chunkcoordintpair1) -> { -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(chunkcoordintpair1.toLong()); -- -- if (playerchunk != null) { -- playerchunk.addSendDependency(this.lightEngine.waitForPendingTasks(chunkcoordintpair1.x, chunkcoordintpair1.z)); -- } -- -- }); -+ // Paper - rewrite player chunk loader - } - - public class ChunkDistanceManager extends DistanceManager { // Paper - public -@@ -1655,7 +1091,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - @Override - protected boolean isChunkToRemove(long pos) { -- return ChunkMap.this.toDrop.contains(pos); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @Nullable -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index 8e8e3896040241bba8fd15f4d6d046567847f741..c80a625f7289e3bb33c6851d2072957e153ca1fb 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -39,67 +39,29 @@ import org.slf4j.Logger; - - public abstract class DistanceManager { - -+ // Paper start - rewrite chunk system -+ public io.papermc.paper.chunk.system.scheduling.ChunkHolderManager getChunkHolderManager() { -+ return this.chunkMap.level.chunkTaskScheduler.chunkHolderManager; -+ } -+ // Paper end - rewrite chunk system -+ - static final Logger LOGGER = LogUtils.getLogger(); - static final int PLAYER_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING); - private static final int INITIAL_TICKET_LIST_CAPACITY = 4; - final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); -- public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); -- private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); -+ // Paper - rewrite chunk system - private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); -- private final TickingTracker tickingTicketsTracker = new TickingTracker(); -- private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(32); -- final Set chunksToUpdateFutures = Sets.newHashSet(); -- final ChunkTaskPriorityQueueSorter ticketThrottler; -- final ProcessorHandle> ticketThrottlerInput; -- final ProcessorHandle ticketThrottlerReleaser; -- final LongSet ticketsToRelease = new LongOpenHashSet(); -- final Executor mainThreadExecutor; -- private long ticketTickCounter; -- public int simulationDistance = 10; -+ // Paper - rewrite chunk system - private final ChunkMap chunkMap; // Paper - - protected DistanceManager(Executor workerExecutor, Executor mainThreadExecutor, ChunkMap chunkMap) { -- Objects.requireNonNull(mainThreadExecutor); -- ProcessorHandle mailbox = ProcessorHandle.of("player ticket throttler", mainThreadExecutor::execute); -- ChunkTaskPriorityQueueSorter chunktaskqueuesorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(mailbox), workerExecutor, 4); -- -- this.ticketThrottler = chunktaskqueuesorter; -- this.ticketThrottlerInput = chunktaskqueuesorter.getProcessor(mailbox, true); -- this.ticketThrottlerReleaser = chunktaskqueuesorter.getReleaseProcessor(mailbox); -- this.mainThreadExecutor = mainThreadExecutor; -+ // Paper - rewrite chunk system - this.chunkMap = chunkMap; // Paper - } - - protected void purgeStaleTickets() { -- ++this.ticketTickCounter; -- ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); -- -- while (objectiterator.hasNext()) { -- Entry>> entry = (Entry) objectiterator.next(); -- Iterator> iterator = ((SortedArraySet) entry.getValue()).iterator(); -- boolean flag = false; -- -- while (iterator.hasNext()) { -- Ticket ticket = (Ticket) iterator.next(); -- -- if (ticket.timedOut(this.ticketTickCounter)) { -- iterator.remove(); -- flag = true; -- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); -- } -- } -- -- if (flag) { -- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); -- } -- -- if (((SortedArraySet) entry.getValue()).isEmpty()) { -- objectiterator.remove(); -- } -- } -- -+ this.getChunkHolderManager().tick(); // Paper - rewrite chunk system - } -- - private static int getTicketLevelAt(SortedArraySet> tickets) { - return !tickets.isEmpty() ? ((Ticket) tickets.first()).getTicketLevel() : ChunkLevel.MAX_LEVEL + 1; - } -@@ -113,108 +75,25 @@ public abstract class DistanceManager { - protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k); - - public boolean runAllUpdates(ChunkMap chunkStorage) { -- this.naturalSpawnChunkCounter.runAllUpdates(); -- this.tickingTicketsTracker.runAllUpdates(); -- this.playerTicketManager.runAllUpdates(); -- int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); -- boolean flag = i != 0; -- -- if (flag) { -- ; -- } -- -- if (!this.chunksToUpdateFutures.isEmpty()) { -- // CraftBukkit start -- // Iterate pending chunk updates with protection against concurrent modification exceptions -- java.util.Iterator iter = this.chunksToUpdateFutures.iterator(); -- int expectedSize = this.chunksToUpdateFutures.size(); -- do { -- ChunkHolder playerchunk = iter.next(); -- iter.remove(); -- expectedSize--; -- -- playerchunk.updateFutures(chunkStorage, this.mainThreadExecutor); -- -- // Reset iterator if set was modified using add() -- if (this.chunksToUpdateFutures.size() != expectedSize) { -- expectedSize = this.chunksToUpdateFutures.size(); -- iter = this.chunksToUpdateFutures.iterator(); -- } -- } while (iter.hasNext()); -- // CraftBukkit end -- -- return true; -- } else { -- if (!this.ticketsToRelease.isEmpty()) { -- LongIterator longiterator = this.ticketsToRelease.iterator(); -- -- while (longiterator.hasNext()) { -- long j = longiterator.nextLong(); -- -- if (this.getTickets(j).stream().anyMatch((ticket) -> { -- return ticket.getType() == TicketType.PLAYER; -- })) { -- ChunkHolder playerchunk = chunkStorage.getUpdatingChunkIfPresent(j); -- -- if (playerchunk == null) { -- throw new IllegalStateException(); -- } -- -- CompletableFuture> completablefuture = playerchunk.getEntityTickingChunkFuture(); -- -- completablefuture.thenAccept((either) -> { -- this.mainThreadExecutor.execute(() -> { -- this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { -- }, j, false)); -- }); -- }); -- } -- } -- -- this.ticketsToRelease.clear(); -- } -- -- return flag; -- } -+ return this.getChunkHolderManager().processTicketUpdates(); // Paper - rewrite chunk system - } - - boolean addTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean -- SortedArraySet> arraysetsorted = this.getTickets(i); -- int j = DistanceManager.getTicketLevelAt(arraysetsorted); -- Ticket ticket1 = (Ticket) arraysetsorted.addOrGet(ticket); -- -- ticket1.setCreatedTick(this.ticketTickCounter); -- if (ticket.getTicketLevel() < j) { -- this.ticketTracker.update(i, ticket.getTicketLevel(), true); -- } -- -- return ticket == ticket1; // CraftBukkit -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::addTicket"); // Paper -+ return this.getChunkHolderManager().addTicketAtLevel((TicketType)ticket.getType(), i, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system - } - - boolean removeTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean -- SortedArraySet> arraysetsorted = this.getTickets(i); -- -- boolean removed = false; // CraftBukkit -- if (arraysetsorted.remove(ticket)) { -- removed = true; // CraftBukkit -- } -- -- if (arraysetsorted.isEmpty()) { -- this.tickets.remove(i); -- } -- -- this.ticketTracker.update(i, DistanceManager.getTicketLevelAt(arraysetsorted), false); -- return removed; // CraftBukkit -+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::removeTicket"); // Paper -+ return this.getChunkHolderManager().removeTicketAtLevel((TicketType)ticket.getType(), i, ticket.getTicketLevel(), ticket.key); // Paper - rewrite chunk system - } - - public void addTicket(TicketType type, ChunkPos pos, int level, T argument) { -- this.addTicket(pos.toLong(), new Ticket<>(type, level, argument)); -+ this.getChunkHolderManager().addTicketAtLevel(type, pos, level, argument); // Paper - rewrite chunk system - } - - public void removeTicket(TicketType type, ChunkPos pos, int level, T argument) { -- Ticket ticket = new Ticket<>(type, level, argument); -- -- this.removeTicket(pos.toLong(), ticket); -+ this.getChunkHolderManager().removeTicketAtLevel(type, pos, level, argument); // Paper - rewrite chunk system - } - - public void addRegionTicket(TicketType type, ChunkPos pos, int radius, T argument) { -@@ -223,13 +102,7 @@ public abstract class DistanceManager { - } - - public boolean addRegionTicketAtDistance(TicketType tickettype, ChunkPos chunkcoordintpair, int i, T t0) { -- // CraftBukkit end -- Ticket ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); -- long j = chunkcoordintpair.toLong(); -- -- boolean added = this.addTicket(j, ticket); // CraftBukkit -- this.tickingTicketsTracker.addTicket(j, ticket); -- return added; // CraftBukkit -+ return this.getChunkHolderManager().addTicketAtLevel(tickettype, chunkcoordintpair, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); // Paper - rewrite chunk system - } - - public void removeRegionTicket(TicketType type, ChunkPos pos, int radius, T argument) { -@@ -238,31 +111,21 @@ public abstract class DistanceManager { - } - - public boolean removeRegionTicketAtDistance(TicketType tickettype, ChunkPos chunkcoordintpair, int i, T t0) { -- // CraftBukkit end -- Ticket ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); -- long j = chunkcoordintpair.toLong(); -- -- boolean removed = this.removeTicket(j, ticket); // CraftBukkit -- this.tickingTicketsTracker.removeTicket(j, ticket); -- return removed; // CraftBukkit -+ return this.getChunkHolderManager().removeTicketAtLevel(tickettype, chunkcoordintpair, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); // Paper - rewrite chunk system - } - -- private SortedArraySet> getTickets(long position) { -- return (SortedArraySet) this.tickets.computeIfAbsent(position, (j) -> { -- return SortedArraySet.create(4); -- }); -- } -+ // Paper - rewrite chunk system - - protected void updateChunkForced(ChunkPos pos, boolean forced) { -- Ticket ticket = new Ticket<>(TicketType.FORCED, ChunkMap.FORCED_TICKET_LEVEL, pos); -+ Ticket ticket = new Ticket<>(TicketType.FORCED, ChunkMap.FORCED_TICKET_LEVEL, pos, 0L); // Paper - rewrite chunk system - long i = pos.toLong(); - - if (forced) { - this.addTicket(i, ticket); -- this.tickingTicketsTracker.addTicket(i, ticket); -+ //this.tickingTicketsTracker.addTicket(i, ticket); // Paper - no longer used - } else { - this.removeTicket(i, ticket); -- this.tickingTicketsTracker.removeTicket(i, ticket); -+ //this.tickingTicketsTracker.removeTicket(i, ticket); // Paper - no longer used - } - - } -@@ -271,12 +134,10 @@ public abstract class DistanceManager { - ChunkPos chunkcoordintpair = pos.chunk(); - long i = chunkcoordintpair.toLong(); - -- ((ObjectSet) this.playersPerChunk.computeIfAbsent(i, (j) -> { -- return new ObjectOpenHashSet(); -- })).add(player); -+ // Paper - no longer used - this.naturalSpawnChunkCounter.update(i, 0, true); -- this.playerTicketManager.update(i, 0, true); -- this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); -+ //this.playerTicketManager.update(i, 0, true); // Paper - no longer used -+ //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used - } - - public void removePlayer(SectionPos pos, ServerPlayer player) { -@@ -289,40 +150,44 @@ public abstract class DistanceManager { - if (objectset == null || objectset.isEmpty()) { // Paper - this.playersPerChunk.remove(i); - this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); -- this.playerTicketManager.update(i, Integer.MAX_VALUE, false); -- this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); -+ //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used -+ //this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used - } - - } - -- private int getPlayerTicketLevel() { -- return Math.max(0, ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING) - this.simulationDistance); -- } -+ // Paper - rewrite chunk system - - public boolean inEntityTickingRange(long chunkPos) { -- return ChunkLevel.isEntityTicking(this.tickingTicketsTracker.getLevel(chunkPos)); -+ // Paper start - replace player chunk loader system -+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos); -+ return holder != null && holder.isEntityTickingReady(); -+ // Paper end - replace player chunk loader system - } - - public boolean inBlockTickingRange(long chunkPos) { -- return ChunkLevel.isBlockTicking(this.tickingTicketsTracker.getLevel(chunkPos)); -+ // Paper start - replace player chunk loader system -+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos); -+ return holder != null && holder.isTickingReady(); -+ // Paper end - replace player chunk loader system - } - - protected String getTicketDebugString(long pos) { -- SortedArraySet> arraysetsorted = (SortedArraySet) this.tickets.get(pos); -- -- return arraysetsorted != null && !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.first()).toString() : "no_ticket"; -+ return this.getChunkHolderManager().getTicketDebugString(pos); // Paper - rewrite chunk system - } - - protected void updatePlayerTickets(int viewDistance) { -- this.playerTicketManager.updateViewDistance(viewDistance); -+ this.chunkMap.setServerViewDistance(viewDistance); // Paper - route to player chunk manager - } - -- public void updateSimulationDistance(int simulationDistance) { -- if (simulationDistance != this.simulationDistance) { -- this.simulationDistance = simulationDistance; -- this.tickingTicketsTracker.replacePlayerTicketsLevel(this.getPlayerTicketLevel()); -- } -+ // Paper start -+ public int getSimulationDistance() { -+ return this.chunkMap.level.playerChunkLoader.getAPITickDistance(); -+ } -+ // Paper end - -+ public void updateSimulationDistance(int simulationDistance) { -+ this.chunkMap.level.playerChunkLoader.setTickDistance(simulationDistance); // Paper - route to player chunk manager - } - - public int getNaturalSpawnChunkCount() { -@@ -336,103 +201,28 @@ public abstract class DistanceManager { - } - - public String getDebugStatus() { -- return this.ticketThrottler.getDebugStatus(); -+ return "No DistanceManager stats available"; // Paper - rewrite chunk system - } - -- private void dumpTickets(String path) { -- try { -- FileOutputStream fileoutputstream = new FileOutputStream(new File(path)); -+ // Paper - rewrite chunk system - -- try { -- ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().iterator(); -- -- while (objectiterator.hasNext()) { -- Entry>> entry = (Entry) objectiterator.next(); -- ChunkPos chunkcoordintpair = new ChunkPos(entry.getLongKey()); -- Iterator iterator = ((SortedArraySet) entry.getValue()).iterator(); -- -- while (iterator.hasNext()) { -- Ticket ticket = (Ticket) iterator.next(); -- -- fileoutputstream.write((chunkcoordintpair.x + "\t" + chunkcoordintpair.z + "\t" + ticket.getType() + "\t" + ticket.getTicketLevel() + "\t\n").getBytes(StandardCharsets.UTF_8)); -- } -- } -- } catch (Throwable throwable) { -- try { -- fileoutputstream.close(); -- } catch (Throwable throwable1) { -- throwable.addSuppressed(throwable1); -- } -- -- throw throwable; -- } -- -- fileoutputstream.close(); -- } catch (IOException ioexception) { -- DistanceManager.LOGGER.error("Failed to dump tickets to {}", path, ioexception); -- } -- -- } -- -- @VisibleForTesting -- TickingTracker tickingTracker() { -- return this.tickingTicketsTracker; -- } -+ // Paper - replace player chunk loader - - public void removeTicketsOnClosing() { -- ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve -- ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); -- -- while (objectiterator.hasNext()) { -- Entry>> entry = (Entry) objectiterator.next(); -- Iterator> iterator = ((SortedArraySet) entry.getValue()).iterator(); -- boolean flag = false; -- -- while (iterator.hasNext()) { -- Ticket ticket = (Ticket) iterator.next(); -- -- if (!immutableset.contains(ticket.getType())) { -- iterator.remove(); -- flag = true; -- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); -- } -- } -- -- if (flag) { -- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); -- } -- -- if (((SortedArraySet) entry.getValue()).isEmpty()) { -- objectiterator.remove(); -- } -- } -- -+ // Paper - rewrite chunk system - this stupid hack ain't needed anymore - } - - public boolean hasTickets() { -- return !this.tickets.isEmpty(); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - // CraftBukkit start - public void removeAllTicketsFor(TicketType ticketType, int ticketLevel, T ticketIdentifier) { -- Ticket target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier); -- -- for (java.util.Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -- Entry>> entry = iterator.next(); -- SortedArraySet> tickets = entry.getValue(); -- if (tickets.remove(target)) { -- // copied from removeTicket -- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false); -- -- // can't use entry after it's removed -- if (tickets.isEmpty()) { -- iterator.remove(); -- } -- } -- } -+ this.getChunkHolderManager().removeAllTicketsFor(ticketType, ticketLevel, ticketIdentifier); // Paper - rewrite chunk system - } - // CraftBukkit end - -+ /* Paper - rewrite chunk system - private class ChunkTicketTracker extends ChunkTracker { - - private static final int MAX_LEVEL = ChunkLevel.MAX_LEVEL + 1; -@@ -479,6 +269,7 @@ public abstract class DistanceManager { - return this.runUpdates(distance); - } - } -+ */ // Paper - rewrite chunk system - - private class FixedPlayerDistanceChunkTracker extends ChunkTracker { - -@@ -558,6 +349,7 @@ public abstract class DistanceManager { - } - } - -+ /* Paper - rewrite chunk system - private class PlayerTicketTracker extends DistanceManager.FixedPlayerDistanceChunkTracker { - - private int viewDistance = 0; -@@ -653,4 +445,5 @@ public abstract class DistanceManager { - return distance <= this.viewDistance; - } - } -+ */ // Paper - rewrite chunk system - } -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 8a118a7b2878d3c99dadfa97e2ae58fda2b3f93b..9bb4223fbb665211df11dc89fcd13cb7a92cd5dd 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -73,7 +73,7 @@ public class ServerChunkCache extends ChunkSource { - public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet entityTickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true); - final com.destroystokyo.paper.util.concurrent.WeakSeqLock loadedChunkMapSeqLock = new com.destroystokyo.paper.util.concurrent.WeakSeqLock(); - final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap loadedChunkMap = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(8192, 0.5f); -- long chunkFutureAwaitCounter; -+ final java.util.concurrent.atomic.AtomicLong chunkFutureAwaitCounter = new java.util.concurrent.atomic.AtomicLong(); // Paper - chunk system rewrite - private final LevelChunk[] lastLoadedChunks = new LevelChunk[4 * 4]; - // Paper end - -@@ -197,7 +197,7 @@ public class ServerChunkCache extends ChunkSource { - public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) { - long k = ChunkPos.asLong(x, z); - -- if (Thread.currentThread() == this.mainThread) { -+ if (io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system - return this.getChunkAtIfLoadedMainThread(x, z); - } - -@@ -249,7 +249,8 @@ public class ServerChunkCache extends ChunkSource { - @Nullable - @Override - public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) { -- if (Thread.currentThread() != this.mainThread) { -+ final int x1 = x; final int z1 = z; // Paper - conflict on variable change -+ if (!io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system - return (ChunkAccess) CompletableFuture.supplyAsync(() -> { - return this.getChunk(x, z, leastStatus, create); - }, this.mainThreadProcessor).join(); -@@ -267,24 +268,19 @@ public class ServerChunkCache extends ChunkSource { - - ChunkAccess ichunkaccess; - -- for (int l = 0; l < 4; ++l) { -- if (k == this.lastChunkPos[l] && leastStatus == this.lastChunkStatus[l]) { -- ichunkaccess = this.lastChunk[l]; -- if (ichunkaccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime -- return ichunkaccess; -- } -- } -- } -+ // Paper - rewrite chunk system - there are no correct callbacks to remove items from cache in the new chunk system - - gameprofilerfiller.incrementCounter("getChunkCacheMiss"); -- CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create); -+ CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create, true); // Paper - ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor; - - Objects.requireNonNull(completablefuture); - if (!completablefuture.isDone()) { // Paper -+ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.pushChunkWait(this.level, x1, z1); // Paper - rewrite chunk system - com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads - this.level.timings.syncChunkLoad.startTiming(); // Paper - chunkproviderserver_b.managedBlock(completablefuture::isDone); -+ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.popChunkWait(); // Paper - rewrite chunk system - this.level.timings.syncChunkLoad.stopTiming(); // Paper - } // Paper - ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { -@@ -304,7 +300,7 @@ public class ServerChunkCache extends ChunkSource { - @Nullable - @Override - public LevelChunk getChunkNow(int chunkX, int chunkZ) { -- if (Thread.currentThread() != this.mainThread) { -+ if (!io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system - return null; - } else { - return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - Perf: Optimise getChunkAt calls for loaded chunks -@@ -318,7 +314,7 @@ public class ServerChunkCache extends ChunkSource { - } - - public CompletableFuture> getChunkFuture(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { -- boolean flag1 = Thread.currentThread() == this.mainThread; -+ boolean flag1 = io.papermc.paper.util.TickThread.isTickThread(); // Paper - rewrite chunk system - CompletableFuture completablefuture; - - if (flag1) { -@@ -339,47 +335,52 @@ public class ServerChunkCache extends ChunkSource { - } - - private CompletableFuture> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { -- ChunkPos chunkcoordintpair = new ChunkPos(chunkX, chunkZ); -- long k = chunkcoordintpair.toLong(); -- int l = ChunkLevel.byStatus(leastStatus); -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k); -+ // Paper start - add isUrgent - old sig left in place for dirty nms plugins -+ return getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create, false); -+ } -+ private CompletableFuture> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create, boolean isUrgent) { -+ // Paper start - rewrite chunk system -+ io.papermc.paper.util.TickThread.ensureTickThread(this.level, chunkX, chunkZ, "Scheduling chunk load off-main"); -+ int minLevel = ChunkLevel.byStatus(leastStatus); -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.level.chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkX, chunkZ); - -- // CraftBukkit start - don't add new ticket for currently unloading chunk -- boolean currentlyUnloading = false; -- if (playerchunk != null) { -- FullChunkStatus oldChunkState = ChunkLevel.fullStatus(playerchunk.oldTicketLevel); -- FullChunkStatus currentChunkState = ChunkLevel.fullStatus(playerchunk.getTicketLevel()); -- currentlyUnloading = (oldChunkState.isOrAfter(FullChunkStatus.FULL) && !currentChunkState.isOrAfter(FullChunkStatus.FULL)); -+ boolean needsFullScheduling = leastStatus == ChunkStatus.FULL && (chunkHolder == null || !chunkHolder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)); -+ -+ if ((chunkHolder == null || chunkHolder.getTicketLevel() > minLevel || needsFullScheduling) && !create) { -+ return ChunkHolder.UNLOADED_CHUNK_FUTURE; - } -- if (create && !currentlyUnloading) { -- // CraftBukkit end -- this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); -- if (this.chunkAbsent(playerchunk, l)) { -- ProfilerFiller gameprofilerfiller = this.level.getProfiler(); -- -- gameprofilerfiller.push("chunkLoad"); -- this.runDistanceManagerUpdates(); -- playerchunk = this.getVisibleChunkIfPresent(k); -- gameprofilerfiller.pop(); -- if (this.chunkAbsent(playerchunk, l)) { -- throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added")); -+ -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder.ChunkCompletion chunkCompletion = chunkHolder == null ? null : chunkHolder.getLastChunkCompletion(); -+ if (needsFullScheduling || chunkCompletion == null || !chunkCompletion.genStatus().isOrAfter(leastStatus)) { -+ // schedule -+ CompletableFuture> ret = new CompletableFuture<>(); -+ Consumer complete = (ChunkAccess chunk) -> { -+ if (chunk == null) { -+ ret.complete(Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED)); -+ } else { -+ ret.complete(Either.left(chunk)); - } -- } -- } -+ }; - -- return this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(leastStatus, this.chunkMap); -- } -+ this.level.chunkTaskScheduler.scheduleChunkLoad( -+ chunkX, chunkZ, leastStatus, true, -+ isUrgent ? ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.BLOCKING : ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, -+ complete -+ ); - -- private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) { -- return holder == null || holder.oldTicketLevel > maxLevel; // CraftBukkit using oldTicketLevel for isLoaded checks -+ return ret; -+ } else { -+ // can return now -+ return CompletableFuture.completedFuture(Either.left(chunkCompletion.chunk())); -+ } -+ // Paper end - rewrite chunk system - } - -+ // Paper - rewrite chunk system -+ - @Override - public boolean hasChunk(int x, int z) { -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent((new ChunkPos(x, z)).toLong()); -- int k = ChunkLevel.byStatus(ChunkStatus.FULL); -- -- return !this.chunkAbsent(playerchunk, k); -+ return this.getChunkAtIfLoadedImmediately(x, z) != null; // Paper - rewrite chunk system - } - - @Nullable -@@ -391,22 +392,13 @@ public class ServerChunkCache extends ChunkSource { - if (playerchunk == null) { - return null; - } else { -- int l = ServerChunkCache.CHUNK_STATUSES.size() - 1; -- -- while (true) { -- ChunkStatus chunkstatus = (ChunkStatus) ServerChunkCache.CHUNK_STATUSES.get(l); -- Optional optional = ((Either) playerchunk.getFutureIfPresentUnchecked(chunkstatus).getNow(ChunkHolder.UNLOADED_CHUNK)).left(); -- -- if (optional.isPresent()) { -- return (LightChunk) optional.get(); -- } -- -- if (chunkstatus == ChunkStatus.INITIALIZE_LIGHT.getParent()) { -- return null; -- } -- -- --l; -+ // Paper start - rewrite chunk system -+ ChunkStatus status = playerchunk.getChunkHolderStatus(); -+ if (status != null && !status.isOrAfter(ChunkStatus.LIGHT.getParent())) { -+ return null; - } -+ return playerchunk.getAvailableChunkNow(); -+ // Paper end - rewrite chunk system - } - } - -@@ -420,15 +412,7 @@ public class ServerChunkCache extends ChunkSource { - } - - public boolean runDistanceManagerUpdates() { // Paper - public -- boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); -- boolean flag1 = this.chunkMap.promoteChunkMap(); -- -- if (!flag && !flag1) { -- return false; -- } else { -- this.clearCache(); -- return true; -- } -+ return this.level.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(); // Paper - rewrite chunk system - } - - // Paper start -@@ -438,17 +422,10 @@ public class ServerChunkCache extends ChunkSource { - // Paper end - - public boolean isPositionTicking(long pos) { -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); -- -- if (playerchunk == null) { -- return false; -- } else if (!this.level.shouldTickBlocksAt(pos)) { -- return false; -- } else { -- Either either = (Either) playerchunk.getTickingChunkFuture().getNow(null); // CraftBukkit - decompile error -- -- return either != null && either.left().isPresent(); -- } -+ // Paper start - replace player chunk loader system -+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(pos); -+ return holder != null && holder.isTickingReady(); -+ // Paper end - replace player chunk loader system - } - - public void save(boolean flush) { -@@ -464,17 +441,13 @@ public class ServerChunkCache extends ChunkSource { - this.close(true); - } - -- public void close(boolean save) throws IOException { -- if (save) { -- this.save(true); -- } -- // CraftBukkit end -- this.lightEngine.close(); -- this.chunkMap.close(); -+ public void close(boolean save) { // Paper - rewrite chunk system -+ this.level.chunkTaskScheduler.chunkHolderManager.close(save, true); // Paper - rewrite chunk system - } - - // CraftBukkit start - modelled on below - public void purgeUnload() { -+ if (true) return; // Paper - tickets will be removed later, this behavior isn't really well accounted for by the chunk system - this.level.getProfiler().push("purge"); - this.distanceManager.purgeStaleTickets(); - this.runDistanceManagerUpdates(); -@@ -495,6 +468,7 @@ public class ServerChunkCache extends ChunkSource { - this.level.getProfiler().popPush("chunks"); - if (tickChunks) { - this.level.timings.chunks.startTiming(); // Paper - timings -+ this.chunkMap.level.playerChunkLoader.tick(); // Paper - replace player chunk loader - this is mostly required to account for view distance changes - this.tickChunks(); - this.level.timings.chunks.stopTiming(); // Paper - timings - this.chunkMap.tick(); -@@ -597,7 +571,12 @@ public class ServerChunkCache extends ChunkSource { - ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); - - if (playerchunk != null) { -- ((Either) playerchunk.getFullChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left().ifPresent(chunkConsumer); -+ // Paper start - rewrite chunk system -+ LevelChunk chunk = playerchunk.getFullChunk(); -+ if (chunk != null) { -+ chunkConsumer.accept(chunk); -+ } -+ // Paper end - rewrite chunk system - } - - } -@@ -763,17 +742,10 @@ public class ServerChunkCache extends ChunkSource { - @Override - // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task - public boolean pollTask() { -- try { - if (ServerChunkCache.this.runDistanceManagerUpdates()) { - return true; -- } else { -- ServerChunkCache.this.lightEngine.tryScheduleUpdate(); -- return super.pollTask(); - } -- } finally { -- ServerChunkCache.this.chunkMap.callbackExecutor.run(); -- } -- // CraftBukkit end -+ return super.pollTask() | ServerChunkCache.this.level.chunkTaskScheduler.executeMainThreadTask(); // Paper - rewrite chunk system - } - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index b5d6a7eaa24d9968e159d77a4295be00332a5457..dff2dfbe9cc04894d42181c6691e27ad061beb40 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -195,7 +195,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - private final MinecraftServer server; - public final PrimaryLevelData serverLevelData; // CraftBukkit - type - final EntityTickList entityTickList; -- public final PersistentEntitySectionManager entityManager; -+ //public final PersistentEntitySectionManager entityManager; // Paper - rewrite chunk system - private final GameEventDispatcher gameEventDispatcher; - public boolean noSave; - private final SleepStatus sleepStatus; -@@ -263,50 +263,65 @@ public class ServerLevel extends Level implements WorldGenLevel { - return true; - } - -- public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -- java.util.function.Consumer> onLoad) { -- if (Thread.currentThread() != this.thread) { -- this.getChunkSource().mainThreadProcessor.execute(() -> { -- this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad); -- }); -- return; -- } -+ public final void loadChunksAsync(BlockPos pos, int radiusBlocks, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ loadChunksAsync( -+ (pos.getX() - radiusBlocks) >> 4, -+ (pos.getX() + radiusBlocks) >> 4, -+ (pos.getZ() - radiusBlocks) >> 4, -+ (pos.getZ() + radiusBlocks) >> 4, -+ priority, onLoad -+ ); -+ } -+ -+ public final void loadChunksAsync(BlockPos pos, int radiusBlocks, -+ net.minecraft.world.level.chunk.ChunkStatus chunkStatus, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ loadChunksAsync( -+ (pos.getX() - radiusBlocks) >> 4, -+ (pos.getX() + radiusBlocks) >> 4, -+ (pos.getZ() - radiusBlocks) >> 4, -+ (pos.getZ() + radiusBlocks) >> 4, -+ chunkStatus, priority, onLoad -+ ); -+ } -+ -+ public final void loadChunksAsync(int minChunkX, int maxChunkX, int minChunkZ, int maxChunkZ, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ this.loadChunksAsync(minChunkX, maxChunkX, minChunkZ, maxChunkZ, net.minecraft.world.level.chunk.ChunkStatus.FULL, priority, onLoad); -+ } -+ -+ public final void loadChunksAsync(int minChunkX, int maxChunkX, int minChunkZ, int maxChunkZ, -+ net.minecraft.world.level.chunk.ChunkStatus chunkStatus, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { - List ret = new java.util.ArrayList<>(); -- it.unimi.dsi.fastutil.ints.IntArrayList ticketLevels = new it.unimi.dsi.fastutil.ints.IntArrayList(); -- -- int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; -- int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; -- -- int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; -- int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; -- -- int minChunkX = minBlockX >> 4; -- int maxChunkX = maxBlockX >> 4; -- -- int minChunkZ = minBlockZ >> 4; -- int maxChunkZ = maxBlockZ >> 4; - - ServerChunkCache chunkProvider = this.getChunkSource(); - - int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); -- int[] loadedChunks = new int[1]; -+ java.util.concurrent.atomic.AtomicInteger loadedChunks = new java.util.concurrent.atomic.AtomicInteger(); - -- Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++); -+ Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter.getAndIncrement()); -+ -+ int ticketLevel = 33 + net.minecraft.world.level.chunk.ChunkStatus.getDistance(chunkStatus); - - java.util.function.Consumer consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> { - if (chunk != null) { -- int ticketLevel = Math.max(33, chunkProvider.chunkMap.getUpdatingChunkIfPresent(chunk.getPos().toLong()).getTicketLevel()); -+ synchronized (ret) { // Folia - region threading - make callback thread-safe TODO rebase - ret.add(chunk); -- ticketLevels.add(ticketLevel); -+ } // Folia - region threading - make callback thread-safe TODO rebase - chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier); - } -- if (++loadedChunks[0] == requiredChunks) { -+ if (loadedChunks.incrementAndGet() == requiredChunks) { - try { - onLoad.accept(java.util.Collections.unmodifiableList(ret)); - } finally { - for (int i = 0, len = ret.size(); i < len; ++i) { - ChunkPos chunkPos = ret.get(i).getPos(); -- int ticketLevel = ticketLevels.getInt(i); - - chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); - chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier); -@@ -318,12 +333,228 @@ public class ServerLevel extends Level implements WorldGenLevel { - for (int cx = minChunkX; cx <= maxChunkX; ++cx) { - for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { - io.papermc.paper.chunk.system.ChunkSystem.scheduleChunkLoad( -- this, cx, cz, net.minecraft.world.level.chunk.ChunkStatus.FULL, true, priority, consumer -+ this, cx, cz, chunkStatus, true, priority, consumer - ); - } - } - } -- // Paper end -+ -+ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ -+ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; -+ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; -+ -+ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; -+ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; -+ -+ int minChunkX = minBlockX >> 4; -+ int maxChunkX = maxBlockX >> 4; -+ -+ int minChunkZ = minBlockZ >> 4; -+ int maxChunkZ = maxBlockZ >> 4; -+ -+ this.loadChunksAsync(minChunkX, maxChunkX, minChunkZ, maxChunkZ, priority, onLoad); -+ } -+ -+ // Paper start - rewrite chunk system -+ public final io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler chunkTaskScheduler; -+ public final io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController chunkDataControllerNew -+ = new io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController(io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.CHUNK_DATA) { -+ -+ @Override -+ public net.minecraft.world.level.chunk.storage.RegionFileStorage getCache() { -+ return ServerLevel.this.getChunkSource().chunkMap.regionFileCache; -+ } -+ -+ @Override -+ public void writeData(int chunkX, int chunkZ, net.minecraft.nbt.CompoundTag compound) throws IOException { -+ ServerLevel.this.getChunkSource().chunkMap.write(new ChunkPos(chunkX, chunkZ), compound); -+ } -+ -+ @Override -+ public net.minecraft.nbt.CompoundTag readData(int chunkX, int chunkZ) throws IOException { -+ return ServerLevel.this.getChunkSource().chunkMap.readSync(new ChunkPos(chunkX, chunkZ)); -+ } -+ }; -+ public final io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController poiDataControllerNew -+ = new io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController(io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.POI_DATA) { -+ -+ @Override -+ public net.minecraft.world.level.chunk.storage.RegionFileStorage getCache() { -+ return ServerLevel.this.getChunkSource().chunkMap.getPoiManager(); -+ } -+ -+ @Override -+ public void writeData(int chunkX, int chunkZ, net.minecraft.nbt.CompoundTag compound) throws IOException { -+ ServerLevel.this.getChunkSource().chunkMap.getPoiManager().write(new ChunkPos(chunkX, chunkZ), compound); -+ } -+ -+ @Override -+ public net.minecraft.nbt.CompoundTag readData(int chunkX, int chunkZ) throws IOException { -+ return ServerLevel.this.getChunkSource().chunkMap.getPoiManager().read(new ChunkPos(chunkX, chunkZ)); -+ } -+ }; -+ public final io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController entityDataControllerNew -+ = new io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkDataController(io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.ENTITY_DATA) { -+ -+ @Override -+ public net.minecraft.world.level.chunk.storage.RegionFileStorage getCache() { -+ return ServerLevel.this.entityStorage; -+ } -+ -+ @Override -+ public void writeData(int chunkX, int chunkZ, net.minecraft.nbt.CompoundTag compound) throws IOException { -+ ServerLevel.this.writeEntityChunk(chunkX, chunkZ, compound); -+ } -+ -+ @Override -+ public net.minecraft.nbt.CompoundTag readData(int chunkX, int chunkZ) throws IOException { -+ return ServerLevel.this.readEntityChunk(chunkX, chunkZ); -+ } -+ }; -+ private final EntityRegionFileStorage entityStorage; -+ -+ private static final class EntityRegionFileStorage extends net.minecraft.world.level.chunk.storage.RegionFileStorage { -+ -+ public EntityRegionFileStorage(Path directory, boolean dsync) { -+ super(directory, dsync); -+ } -+ -+ protected void write(ChunkPos pos, net.minecraft.nbt.CompoundTag nbt) throws IOException { -+ ChunkPos nbtPos = nbt == null ? null : EntityStorage.readChunkPos(nbt); -+ if (nbtPos != null && !pos.equals(nbtPos)) { -+ throw new IllegalArgumentException( -+ "Entity chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + pos.toString() -+ + " but compound says coordinate is " + nbtPos + " for world: " + this -+ ); -+ } -+ super.write(pos, nbt); -+ } -+ } -+ -+ private void writeEntityChunk(int chunkX, int chunkZ, net.minecraft.nbt.CompoundTag compound) throws IOException { -+ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.scheduleSave( -+ this, chunkX, chunkZ, compound, -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.ENTITY_DATA); -+ return; -+ } -+ this.entityStorage.write(new ChunkPos(chunkX, chunkZ), compound); -+ } -+ -+ private net.minecraft.nbt.CompoundTag readEntityChunk(int chunkX, int chunkZ) throws IOException { -+ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { -+ return io.papermc.paper.chunk.system.io.RegionFileIOThread.loadData( -+ this, chunkX, chunkZ, io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.ENTITY_DATA, -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.getIOBlockingPriorityForCurrentThread() -+ ); -+ } -+ return this.entityStorage.read(new ChunkPos(chunkX, chunkZ)); -+ } -+ -+ private final io.papermc.paper.chunk.system.entity.EntityLookup entityLookup; -+ public final io.papermc.paper.chunk.system.entity.EntityLookup getEntityLookup() { -+ return this.entityLookup; -+ } -+ -+ private final java.util.concurrent.atomic.AtomicLong nonFullSyncLoadIdGenerator = new java.util.concurrent.atomic.AtomicLong(); -+ -+ private ChunkAccess getIfAboveStatus(int chunkX, int chunkZ, net.minecraft.world.level.chunk.ChunkStatus status) { -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder loaded = -+ this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkX, chunkZ); -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder.ChunkCompletion loadedCompletion; -+ if (loaded != null && (loadedCompletion = loaded.getLastChunkCompletion()) != null && loadedCompletion.genStatus().isOrAfter(status)) { -+ return loadedCompletion.chunk(); -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public ChunkAccess syncLoadNonFull(int chunkX, int chunkZ, net.minecraft.world.level.chunk.ChunkStatus status) { -+ if (status == null || status.isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.FULL)) { -+ throw new IllegalArgumentException("Status: " + status); -+ } -+ ChunkAccess loaded = this.getIfAboveStatus(chunkX, chunkZ, status); -+ if (loaded != null) { -+ return loaded; -+ } -+ -+ Long ticketId = Long.valueOf(this.nonFullSyncLoadIdGenerator.getAndIncrement()); -+ int ticketLevel = 33 + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status); -+ this.chunkTaskScheduler.chunkHolderManager.addTicketAtLevel( -+ TicketType.NON_FULL_SYNC_LOAD, chunkX, chunkZ, ticketLevel, ticketId -+ ); -+ this.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(); -+ -+ this.chunkTaskScheduler.beginChunkLoadForNonFullSync(chunkX, chunkZ, status, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.BLOCKING); -+ -+ // we could do a simple spinwait here, since we do not need to process tasks while performing this load -+ // but we process tasks only because it's a better use of the time spent -+ this.chunkSource.mainThreadProcessor.managedBlock(() -> { -+ return ServerLevel.this.getIfAboveStatus(chunkX, chunkZ, status) != null; -+ }); -+ -+ loaded = ServerLevel.this.getIfAboveStatus(chunkX, chunkZ, status); -+ if (loaded == null) { -+ throw new IllegalStateException("Expected chunk to be loaded for status " + status); -+ } -+ -+ this.chunkTaskScheduler.chunkHolderManager.removeTicketAtLevel( -+ TicketType.NON_FULL_SYNC_LOAD, chunkX, chunkZ, ticketLevel, ticketId -+ ); -+ -+ return loaded; -+ } -+ -+ public final int getRegionChunkShift() { -+ // placeholder for folia -+ return io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift(); -+ } -+ // Paper end - rewrite chunk system -+ -+ public final io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader playerChunkLoader = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader(this); -+ private final java.util.concurrent.atomic.AtomicReference viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1)); -+ -+ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances getViewDistances() { -+ return this.viewDistances.get(); -+ } -+ -+ private void updateViewDistance(final java.util.function.Function update) { -+ for (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances curr = this.viewDistances.get();;) { -+ if (this.viewDistances.compareAndSet(curr, update.apply(curr))) { -+ return; -+ } -+ } -+ } -+ -+ public void setTickViewDistance(final int distance) { -+ if ((distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE)) { -+ throw new IllegalArgumentException("Tick view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE) + ", got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setTickViewDistance(distance); -+ }); -+ } -+ -+ public void setLoadViewDistance(final int distance) { -+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Load view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setLoadViewDistance(distance); -+ }); -+ } -+ -+ public void setSendViewDistance(final int distance) { -+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Send view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setSendViewDistance(distance); -+ }); -+ } - - // Paper start - optimise getPlayerByUUID - @Nullable -@@ -376,16 +607,16 @@ public class ServerLevel extends Level implements WorldGenLevel { - // CraftBukkit end - boolean flag2 = minecraftserver.forceSynchronousWrites(); - DataFixer datafixer = minecraftserver.getFixerUpper(); -- EntityPersistentStorage entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver); -+ this.entityStorage = new EntityRegionFileStorage(convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), flag2); // Paper - rewrite chunk system //EntityPersistentStorage entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver); - -- this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage); -+ // this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage, this.entitySliceManager); // Paper // Paper - rewrite chunk system - StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager(); - int j = this.spigotConfig.viewDistance; // Spigot - int k = this.spigotConfig.simulationDistance; // Spigot -- PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; -+ //PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; // Paper - rewrite chunk system - -- Objects.requireNonNull(this.entityManager); -- this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, persistententitysectionmanager::updateChunkStatus, () -> { -+ //Objects.requireNonNull(this.entityManager); // Paper - rewrite chunk system -+ this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, null, () -> { // Paper - rewrite chunk system - return minecraftserver.overworld().getDataStorage(); - }); - this.chunkSource.getGeneratorState().ensureStructuresGenerated(); -@@ -414,6 +645,9 @@ public class ServerLevel extends Level implements WorldGenLevel { - return (RandomSequences) this.getDataStorage().computeIfAbsent(RandomSequences.factory(l), "random_sequences"); - }); - this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit -+ -+ this.chunkTaskScheduler = new io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler(this, io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.workerThreads); // Paper - rewrite chunk system -+ this.entityLookup = new io.papermc.paper.chunk.system.entity.EntityLookup(this, new EntityCallbacks()); // Paper - rewrite chunk system - } - - // Paper start -@@ -546,7 +780,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - gameprofilerfiller.push("checkDespawn"); - entity.checkDespawn(); - gameprofilerfiller.pop(); -- if (this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { -+ if (true || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { // Paper - now always true if in the ticking list - Entity entity1 = entity.getVehicle(); - - if (entity1 != null) { -@@ -571,13 +805,16 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - gameprofilerfiller.push("entityManagement"); -- this.entityManager.tick(); -+ //this.entityManager.tick(); // Paper - rewrite chunk system - gameprofilerfiller.pop(); - } - - @Override - public boolean shouldTickBlocksAt(long chunkPos) { -- return this.chunkSource.chunkMap.getDistanceManager().inBlockTickingRange(chunkPos); -+ // Paper start - replace player chunk loader system -+ ChunkHolder holder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos); -+ return holder != null && holder.isTickingReady(); -+ // Paper end - replace player chunk loader system - } - - protected void tickTime() { -@@ -1054,6 +1291,11 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled) { -+ // Paper start - rewrite chunk system - add close param -+ this.save(progressListener, flush, savingDisabled, false); -+ } -+ public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled, boolean close) { -+ // Paper end - rewrite chunk system - add close param - ServerChunkCache chunkproviderserver = this.getChunkSource(); - - if (!savingDisabled) { -@@ -1069,16 +1311,13 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - timings.worldSaveChunks.startTiming(); // Paper -- chunkproviderserver.save(flush); -+ if (!close) chunkproviderserver.save(flush); // Paper - rewrite chunk system -+ if (close) chunkproviderserver.close(true); // Paper - rewrite chunk system - timings.worldSaveChunks.stopTiming(); // Paper - }// Paper -- if (flush) { -- this.entityManager.saveAll(); -- } else { -- this.entityManager.autoSave(); -- } -+ // Paper - rewrite chunk system - entity saving moved into ChunkHolder - -- } -+ } else if (close) { chunkproviderserver.close(false); } // Paper - rewrite chunk system - - // CraftBukkit start - moved from MinecraftServer.saveChunks - ServerLevel worldserver1 = this; -@@ -1214,7 +1453,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.removePlayerImmediately((ServerPlayer) entity, Entity.RemovalReason.DISCARDED); - } - -- this.entityManager.addNewEntity(player); -+ this.entityLookup.addNewEntity(player); // Paper - rewite chunk system - } - - // CraftBukkit start -@@ -1245,7 +1484,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - // CraftBukkit end - -- return this.entityManager.addNewEntity(entity); -+ return this.entityLookup.addNewEntity(entity); // Paper - rewrite chunk system - } - } - -@@ -1257,10 +1496,10 @@ public class ServerLevel extends Level implements WorldGenLevel { - public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { - // CraftBukkit end - Stream stream = entity.getSelfAndPassengers().map(Entity::getUUID); // CraftBukkit - decompile error -- PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; -+ //PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; // Paper - rewrite chunk system - -- Objects.requireNonNull(this.entityManager); -- if (stream.anyMatch(persistententitysectionmanager::isLoaded)) { -+ //Objects.requireNonNull(this.entityManager); // Paper - rewrite chunk system -+ if (stream.anyMatch(this.entityLookup::hasEntity)) { // Paper - rewrite chunk system - return false; - } else { - this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit -@@ -1894,7 +2133,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - } - -- bufferedwriter.write(String.format(Locale.ROOT, "entities: %s\n", this.entityManager.gatherStats())); -+ bufferedwriter.write(String.format(Locale.ROOT, "entities: %s\n", this.entityLookup.getDebugInfo())); // Paper - rewrite chunk system - bufferedwriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size())); - bufferedwriter.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count())); - bufferedwriter.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count())); -@@ -1943,7 +2182,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - BufferedWriter bufferedwriter2 = Files.newBufferedWriter(path1); - - try { -- playerchunkmap.dumpChunks(bufferedwriter2); -+ //playerchunkmap.dumpChunks(bufferedwriter2); // Paper - rewrite chunk system - } catch (Throwable throwable4) { - if (bufferedwriter2 != null) { - try { -@@ -1964,7 +2203,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - BufferedWriter bufferedwriter3 = Files.newBufferedWriter(path2); - - try { -- this.entityManager.dumpSections(bufferedwriter3); -+ //this.entityManager.dumpSections(bufferedwriter3); // Paper - rewrite chunk system - } catch (Throwable throwable6) { - if (bufferedwriter3 != null) { - try { -@@ -2106,7 +2345,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - - @VisibleForTesting - public String getWatchdogStats() { -- return String.format(Locale.ROOT, "players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s", this.players.size(), this.entityManager.gatherStats(), ServerLevel.getTypeCount(this.entityManager.getEntityGetter().getAll(), (entity) -> { -+ return String.format(Locale.ROOT, "players: %s, entities: %s [%s], block_entities: %d [%s], block_ticks: %d, fluid_ticks: %d, chunk_source: %s", this.players.size(), this.entityLookup.getDebugInfo(), ServerLevel.getTypeCount(this.entityLookup.getAll(), (entity) -> { // Paper - rewrite chunk system - return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); - }), this.blockEntityTickers.size(), ServerLevel.getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType), this.getBlockTicks().count(), this.getFluidTicks().count(), this.gatherChunkSourceStats()); - } -@@ -2166,15 +2405,15 @@ public class ServerLevel extends Level implements WorldGenLevel { - @Override - public LevelEntityGetter getEntities() { - org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot -- return this.entityManager.getEntityGetter(); -+ return this.entityLookup; // Paper - rewrite chunk system - } - -- public void addLegacyChunkEntities(Stream entities) { -- this.entityManager.addLegacyChunkEntities(entities); -+ public void addLegacyChunkEntities(Stream entities, ChunkPos forChunk) { // Paper - rewrite chunk system -+ this.entityLookup.addLegacyChunkEntities(entities.toList(), forChunk); // Paper - rewrite chunk system - } - -- public void addWorldGenChunkEntities(Stream entities) { -- this.entityManager.addWorldGenChunkEntities(entities); -+ public void addWorldGenChunkEntities(Stream entities, ChunkPos forChunk) { // Paper - rewrite chunk system -+ this.entityLookup.addWorldGenChunkEntities(entities.toList(), forChunk); // Paper - rewrite chunk system - } - - public void startTickingChunk(LevelChunk chunk) { -@@ -2190,34 +2429,49 @@ public class ServerLevel extends Level implements WorldGenLevel { - @Override - public void close() throws IOException { - super.close(); -- this.entityManager.close(); -+ //this.entityManager.close(); // Paper - rewrite chunk system - } - - @Override - public String gatherChunkSourceStats() { - String s = this.chunkSource.gatherStats(); - -- return "Chunks[S] W: " + s + " E: " + this.entityManager.gatherStats(); -+ return "Chunks[S] W: " + s + " E: " + this.entityLookup.getDebugInfo(); // Paper - rewrite chunk system - } - - public boolean areEntitiesLoaded(long chunkPos) { -- return this.entityManager.areEntitiesLoaded(chunkPos); -+ // Paper start - rewrite chunk system -+ return this.getChunkIfLoadedImmediately(ChunkPos.getX(chunkPos), ChunkPos.getZ(chunkPos)) != null; -+ // Paper end - rewrite chunk system - } - - private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { -- return this.areEntitiesLoaded(chunkPos) && this.chunkSource.isPositionTicking(chunkPos); -+ // Paper start - optimize is ticking ready type functions -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkPos); -+ // isTicking implies the chunk is loaded, and the chunk is loaded now implies the entities are loaded -+ return chunkHolder != null && chunkHolder.isTickingReady(); -+ // Paper end - } - - public boolean isPositionEntityTicking(BlockPos pos) { -- return this.entityManager.canPositionTick(pos) && this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(ChunkPos.asLong(pos)); -+ // Paper start - rewrite chunk system -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(io.papermc.paper.util.CoordinateUtils.getChunkKey(pos)); -+ return chunkHolder != null && chunkHolder.isEntityTickingReady(); -+ // Paper end - rewrite chunk system - } - - public boolean isNaturalSpawningAllowed(BlockPos pos) { -- return this.entityManager.canPositionTick(pos); -+ // Paper start - rewrite chunk system -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(io.papermc.paper.util.CoordinateUtils.getChunkKey(pos)); -+ return chunkHolder != null && chunkHolder.isEntityTickingReady(); -+ // Paper end - rewrite chunk system - } - - public boolean isNaturalSpawningAllowed(ChunkPos pos) { -- return this.entityManager.canPositionTick(pos); -+ // Paper start - rewrite chunk system -+ io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(io.papermc.paper.util.CoordinateUtils.getChunkKey(pos)); -+ return chunkHolder != null && chunkHolder.isEntityTickingReady(); -+ // Paper end - rewrite chunk system - } - - @Override -@@ -2238,7 +2492,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - CrashReportCategory crashreportsystemdetails = super.fillReportDetails(report); - - crashreportsystemdetails.setDetail("Loaded entity count", () -> { -- return String.valueOf(this.entityManager.count()); -+ return String.valueOf(this.entityLookup.getAllCopy().length); // Paper - }); - return crashreportsystemdetails; - } -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index ae5a2136a0e266d4c35190f5d33552994c842786..5657f1ecbadda96a79978f918393c0c9a58dca83 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -276,6 +276,50 @@ public class ServerPlayer extends Player { - public @Nullable String clientBrandName = null; // Paper - Brand support - public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event - -+ // Paper start - replace player chunk loader -+ private final java.util.concurrent.atomic.AtomicReference viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1)); -+ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; -+ -+ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances getViewDistances() { -+ return this.viewDistances.get(); -+ } -+ -+ private void updateViewDistance(final java.util.function.Function update) { -+ for (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances curr = this.viewDistances.get();;) { -+ if (this.viewDistances.compareAndSet(curr, update.apply(curr))) { -+ return; -+ } -+ } -+ } -+ -+ public void setTickViewDistance(final int distance) { -+ if ((distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE)) { -+ throw new IllegalArgumentException("Tick view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE) + ", got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setTickViewDistance(distance); -+ }); -+ } -+ -+ public void setLoadViewDistance(final int distance) { -+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Load view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setLoadViewDistance(distance); -+ }); -+ } -+ -+ public void setSendViewDistance(final int distance) { -+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Send view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setSendViewDistance(distance); -+ }); -+ } -+ // Paper end - replace player chunk loader -+ - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { - super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); - this.chatVisibility = ChatVisiblity.FULL; -diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -index 97662f8c8c125cb964d46b9095509a0da9796dba..f382d138959b34bfc3a114bc9d96e056cccbfc89 100644 ---- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -+++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -@@ -37,15 +37,12 @@ import net.minecraft.world.level.chunk.ChunkStatus; - public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable { - public static final int DEFAULT_BATCH_SIZE = 1000; - private static final Logger LOGGER = LogUtils.getLogger(); -- private final ProcessorMailbox taskMailbox; -- private final ObjectList> lightTasks = new ObjectArrayList<>(); -+ // Paper - rewrite chunk system - private final ChunkMap chunkMap; -- private final ProcessorHandle> sorterMailbox; -- private final int taskPerBatch = 1000; -- private final AtomicBoolean scheduled = new AtomicBoolean(); -+ // Paper - rewrite chunk system - - // Paper start - replace light engine impl -- protected final ca.spottedleaf.starlight.common.light.StarLightInterface theLightEngine; -+ public final ca.spottedleaf.starlight.common.light.StarLightInterface theLightEngine; - public final boolean hasBlockLight; - public final boolean hasSkyLight; - // Paper end - replace light engine impl -@@ -53,8 +50,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox processor, ProcessorHandle> executor) { - super(chunkProvider, false, false); // Paper - destroy vanilla light engine state - this.chunkMap = chunkStorage; -- this.sorterMailbox = executor; -- this.taskMailbox = processor; -+ // Paper - rewrite chunk system - // Paper start - replace light engine impl - this.hasBlockLight = true; - this.hasSkyLight = hasBlockLight; // Nice variable name. -@@ -98,7 +94,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - ++totalChunks; - } - -- this.taskMailbox.tell(() -> { -+ this.chunkMap.level.chunkTaskScheduler.radiusAwareScheduler.queueInfiniteRadiusTask(() -> { // Paper - rewrite chunk system - this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> { - chunkLightCallback.accept(chunkPos); - ((java.util.concurrent.Executor)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().mainThreadProcessor).execute(() -> { -@@ -115,7 +111,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); - - private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, -- final Supplier runnable) { -+ final Supplier runnable) { // Paper - rewrite chunk system - final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld(); - - final ChunkAccess center = this.theLightEngine.getAnyChunkNow(chunkX, chunkZ); -@@ -142,7 +138,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); - -- final ca.spottedleaf.starlight.common.light.StarLightInterface.LightQueue.ChunkTasks updateFuture = runnable.get(); -+ final io.papermc.paper.chunk.system.light.LightQueue.ChunkTasks updateFuture = runnable.get(); // Paper - rewrite chunk system - - if (updateFuture == null) { - // not scheduled -@@ -282,17 +278,11 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - private void addTask(int x, int z, ThreadedLevelLightEngine.TaskType stage, Runnable task) { -- this.addTask(x, z, this.chunkMap.getChunkQueueLevel(ChunkPos.asLong(x, z)), stage, task); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - private void addTask(int x, int z, IntSupplier completedLevelSupplier, ThreadedLevelLightEngine.TaskType stage, Runnable task) { -- this.sorterMailbox.tell(ChunkTaskPriorityQueueSorter.message(() -> { -- this.lightTasks.add(Pair.of(stage, task)); -- if (this.lightTasks.size() >= 1000) { -- this.runUpdate(); -- } -- -- }, ChunkPos.asLong(x, z), completedLevelSupplier)); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - @Override -@@ -334,90 +324,15 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - public CompletableFuture lightChunk(ChunkAccess chunk, boolean excludeBlocks) { -- // Paper start - replace light engine impl -- if (true) { -- boolean lit = excludeBlocks; -- final ChunkPos chunkPos = chunk.getPos(); -- -- return CompletableFuture.supplyAsync(() -> { -- final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk); -- if (!lit) { -- chunk.setLightCorrect(false); -- this.theLightEngine.lightChunk(chunk, emptySections); -- chunk.setLightCorrect(true); -- } else { -- this.theLightEngine.forceLoadInChunk(chunk, emptySections); -- // can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have -- // them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should -- // catch what we miss here. -- this.theLightEngine.checkChunkEdges(chunkPos.x, chunkPos.z); -- } -- -- this.chunkMap.releaseLightTicket(chunkPos); -- return chunk; -- }, (runnable) -> { -- this.theLightEngine.scheduleChunkLight(chunkPos, runnable); -- this.tryScheduleUpdate(); -- }).whenComplete((final ChunkAccess c, final Throwable throwable) -> { -- if (throwable != null) { -- LOGGER.error("Failed to light chunk " + chunkPos, throwable); -- } -- }); -- } -- // Paper end - replace light engine impl -- ChunkPos chunkPos = chunk.getPos(); -- chunk.setLightCorrect(false); -- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -- if (!excludeBlocks) { -- super.propagateLightSources(chunkPos); -- } -- -- }, () -> { -- return "lightChunk " + chunkPos + " " + excludeBlocks; -- })); -- return CompletableFuture.supplyAsync(() -> { -- chunk.setLightCorrect(true); -- this.chunkMap.releaseLightTicket(chunkPos); -- return chunk; -- }, (task) -> { -- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, task); -- }); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public void tryScheduleUpdate() { -- if (this.hasLightWork() && this.scheduled.compareAndSet(false, true)) { // Paper // Paper - rewrite light engine -- this.taskMailbox.tell(() -> { -- this.runUpdate(); -- this.scheduled.set(false); -- }); -- } -- -+ // Paper - rewrite chunk system - } - - private void runUpdate() { -- int i = Math.min(this.lightTasks.size(), 1000); -- ObjectListIterator> objectListIterator = this.lightTasks.iterator(); -- -- int j; -- for(j = 0; objectListIterator.hasNext() && j < i; ++j) { -- Pair pair = objectListIterator.next(); -- if (pair.getFirst() == ThreadedLevelLightEngine.TaskType.PRE_UPDATE) { -- pair.getSecond().run(); -- } -- } -- -- objectListIterator.back(j); -- this.theLightEngine.propagateChanges(); // Paper - rewrite light engine -- -- for(int var5 = 0; objectListIterator.hasNext() && var5 < i; ++var5) { -- Pair pair2 = objectListIterator.next(); -- if (pair2.getFirst() == ThreadedLevelLightEngine.TaskType.POST_UPDATE) { -- pair2.getSecond().run(); -- } -- -- objectListIterator.remove(); -- } -- -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - public CompletableFuture waitForPendingTasks(int x, int z) { -diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java -index b346fa94b23d81da7da073f71dd12e672e0f079c..0edb97617f0c0da8dda901a26891b33c324715c7 100644 ---- a/src/main/java/net/minecraft/server/level/Ticket.java -+++ b/src/main/java/net/minecraft/server/level/Ticket.java -@@ -6,9 +6,12 @@ public final class Ticket implements Comparable> { - private final TicketType type; - private final int ticketLevel; - public final T key; -- private long createdTick; -+ // Paper start - rewrite chunk system -+ public long removeDelay; - -- protected Ticket(TicketType type, int level, T argument) { -+ public Ticket(TicketType type, int level, T argument, long removeDelay) { -+ this.removeDelay = removeDelay; -+ // Paper end - rewrite chunk system - this.type = type; - this.ticketLevel = level; - this.key = argument; -@@ -44,7 +47,7 @@ public final class Ticket implements Comparable> { - - @Override - public String toString() { -- return "Ticket[" + this.type + " " + this.ticketLevel + " (" + this.key + ")] at " + this.createdTick; -+ return "Ticket[" + this.type + " " + this.ticketLevel + " (" + this.key + ")] to die in " + this.removeDelay; // Paper - rewrite chunk system - } - - public TicketType getType() { -@@ -56,11 +59,10 @@ public final class Ticket implements Comparable> { - } - - protected void setCreatedTick(long tickCreated) { -- this.createdTick = tickCreated; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - - protected boolean timedOut(long currentTick) { -- long l = this.type.timeout(); -- return l != 0L && currentTick - this.createdTick > l; -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - } -diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java -index 6051e5f272838ef23276a90e21c2fc821ca155d1..658e63ebde81dc14c8ab5850fb246dc0aab25dea 100644 ---- a/src/main/java/net/minecraft/server/level/TicketType.java -+++ b/src/main/java/net/minecraft/server/level/TicketType.java -@@ -8,6 +8,7 @@ import net.minecraft.world.level.ChunkPos; - - public class TicketType { - public static final TicketType FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper -+ public static final TicketType ASYNC_LOAD = create("async_load", Long::compareTo); // Paper - - private final String name; - private final Comparator comparator; -@@ -27,6 +28,15 @@ public class TicketType { - public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit - public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit - public static final TicketType CHUNK_RELIGHT = create("light_update", Long::compareTo); // Paper - ensure chunks stay loaded for lighting -+ // Paper start - rewrite chunk system -+ public static final TicketType CHUNK_LOAD = create("chunk_load", Long::compareTo); -+ public static final TicketType STATUS_UPGRADE = create("status_upgrade", Long::compareTo); -+ public static final TicketType ENTITY_LOAD = create("entity_load", Long::compareTo); -+ public static final TicketType POI_LOAD = create("poi_load", Long::compareTo); -+ public static final TicketType UNLOAD_COOLDOWN = create("unload_cooldown", (u1, u2) -> 0, 5 * 20); -+ public static final TicketType NON_FULL_SYNC_LOAD = create("non_full_sync_load", Long::compareTo); -+ public static final TicketType DELAY_UNLOAD = create("delay_unload", Comparator.comparingLong(ChunkPos::toLong), 1); -+ // Paper end - rewrite chunk system - - public static TicketType create(String name, Comparator argumentComparator) { - return new TicketType<>(name, argumentComparator, 0L); -diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -index c3e7bd8865cc8990fc59f1ff0dfc1697cbb5ca49..5ece375eaf6bcc61864997a389bb5e24625e4505 100644 ---- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java -+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -@@ -535,4 +535,21 @@ public class WorldGenRegion implements WorldGenLevel { - public long nextSubTickCount() { - return this.subTickCount.getAndIncrement(); - } -+ -+ // Paper start -+ // No-op, this class doesn't provide entity access -+ @Override -+ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { -+ return Collections.emptyList(); -+ } -+ -+ @Override -+ public void getEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java -index 13209267c26f46492a92e820889a9be0bd2287a0..f3b96a921e7d085b51da62fa5493384a7ded1f9d 100644 ---- a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java -+++ b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java -@@ -44,17 +44,23 @@ public class PlayerChunkSender { - - public void dropChunk(ServerPlayer player, ChunkPos pos) { - if (!this.pendingChunks.remove(pos.toLong()) && player.isAlive()) { -+ // Paper start - rewrite player chunk loader -+ dropChunkStatic(player, pos); -+ } -+ } -+ public static void dropChunkStatic(ServerPlayer player, ChunkPos pos) { -+ player.serverLevel().chunkSource.chunkMap.getVisibleChunkIfPresent(pos.toLong()).removePlayer(player); - player.connection.send(new ClientboundForgetLevelChunkPacket(pos)); - // Paper start - PlayerChunkUnloadEvent - if (io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0) { - new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(player.getBukkitEntity().getWorld().getChunkAt(pos.longKey), player.getBukkitEntity()).callEvent(); - } - // Paper end - PlayerChunkUnloadEvent -- } -- - } -+ // Paper end - rewrite player chunk loader - - public void sendNextChunks(ServerPlayer player) { -+ if (true) return; // Paper - rewrite player chunk loader - if (this.unacknowledgedBatches < this.maxUnacknowledgedBatches) { - float f = Math.max(1.0F, this.desiredChunksPerTick); - this.batchQuota = Math.min(this.batchQuota + this.desiredChunksPerTick, f); -@@ -80,7 +86,8 @@ public class PlayerChunkSender { - } - } - -- private static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) { -+ public static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) { // Paper - rewrite chunk loader - public -+ handler.player.serverLevel().chunkSource.chunkMap.getVisibleChunkIfPresent(chunk.getPos().toLong()).addPlayer(handler.player); - handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), (BitSet)null, (BitSet)null)); - // Paper start - PlayerChunkLoadEvent - if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { -@@ -110,6 +117,7 @@ public class PlayerChunkSender { - } - - public void onChunkBatchReceivedByClient(float desiredBatchSize) { -+ if (true) return; // Paper - rewrite player chunk loader - --this.unacknowledgedBatches; - this.desiredChunksPerTick = Double.isNaN((double)desiredBatchSize) ? 0.01F : Mth.clamp(desiredBatchSize, 0.01F, 64.0F); - if (this.unacknowledgedBatches == 0) { -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 16ef96f0b2f68556b89c9d732d0e1a407f083fdc..d2e65c105c38c71a6b1739b95547772511a36345 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -293,7 +293,7 @@ public abstract class PlayerList { - boolean flag2 = gamerules.getBoolean(GameRules.RULE_LIMITED_CRAFTING); - - // Spigot - view distance -- playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, worldserver1.spigotConfig.simulationDistance, flag1, !flag, flag2, player.createCommonSpawnInfo(worldserver1))); -+ 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())); -@@ -943,8 +943,8 @@ public abstract class PlayerList { - LevelData worlddata = worldserver2.getLevelData(); - - entityplayer1.connection.send(new ClientboundRespawnPacket(entityplayer1.createCommonSpawnInfo(worldserver2), (byte) i)); -- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot -- entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.spigotConfig.simulationDistance)); // Spigot -+ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getWorld().getSendViewDistance())); // Spigot // Paper - replace old player chunk management -+ entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.getWorld().getSimulationDistance())); // Spigot // Paper - replace old player chunk management - entityplayer1.connection.teleport(CraftLocation.toBukkit(entityplayer1.position(), worldserver2.getWorld(), entityplayer1.getYRot(), entityplayer1.getXRot())); // CraftBukkit - entityplayer1.connection.send(new ClientboundSetDefaultSpawnPositionPacket(worldserver1.getSharedSpawnPos(), worldserver1.getSharedSpawnAngle())); - entityplayer1.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); -@@ -1496,7 +1496,7 @@ public abstract class PlayerList { - - public void setViewDistance(int viewDistance) { - this.viewDistance = viewDistance; -- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); -+ //this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); // Paper - move into setViewDistance - Iterator iterator = this.server.getAllLevels().iterator(); - - while (iterator.hasNext()) { -@@ -1511,7 +1511,7 @@ public abstract class PlayerList { - - public void setSimulationDistance(int simulationDistance) { - this.simulationDistance = simulationDistance; -- this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); -+ //this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); // Paper - handled by playerchunkloader - Iterator iterator = this.server.getAllLevels().iterator(); - - while (iterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java -index ca788f0dcec4a117b410fe8348969e056b138b1e..a6ac76707da39cf86113003b1f326433fdc86c86 100644 ---- a/src/main/java/net/minecraft/util/SortedArraySet.java -+++ b/src/main/java/net/minecraft/util/SortedArraySet.java -@@ -14,6 +14,14 @@ public class SortedArraySet extends AbstractSet { - T[] contents; - int size; - -+ // Paper start - rewrite chunk system -+ public SortedArraySet(final SortedArraySet other) { -+ this.comparator = other.comparator; -+ this.size = other.size; -+ this.contents = Arrays.copyOf(other.contents, this.size); -+ } -+ // Paper end - rewrite chunk system -+ - private SortedArraySet(int initialCapacity, Comparator comparator) { - this.comparator = comparator; - if (initialCapacity < 0) { -@@ -22,6 +30,41 @@ public class SortedArraySet extends AbstractSet { - this.contents = (T[])castRawArray(new Object[initialCapacity]); - } - } -+ // Paper start - optimise removeIf -+ @Override -+ public boolean removeIf(java.util.function.Predicate filter) { -+ // prev. impl used an iterator, which could be n^2 and creates garbage -+ int i = 0, len = this.size; -+ T[] backingArray = this.contents; -+ -+ for (;;) { -+ if (i >= len) { -+ return false; -+ } -+ if (!filter.test(backingArray[i])) { -+ ++i; -+ continue; -+ } -+ break; -+ } -+ -+ // we only want to write back to backingArray if we really need to -+ -+ int lastIndex = i; // this is where new elements are shifted to -+ -+ for (; i < len; ++i) { -+ T curr = backingArray[i]; -+ if (!filter.test(curr)) { // if test throws we're screwed -+ backingArray[lastIndex++] = curr; -+ } -+ } -+ -+ // cleanup end -+ Arrays.fill(backingArray, lastIndex, len, null); -+ this.size = lastIndex; -+ return true; -+ } -+ // Paper end - optimise removeIf - - public static > SortedArraySet create() { - return create(10); -@@ -110,6 +153,31 @@ public class SortedArraySet extends AbstractSet { - } - } - -+ // Paper start - rewrite chunk system -+ public T replace(T object) { -+ int i = this.findIndex(object); -+ if (i >= 0) { -+ T old = this.contents[i]; -+ this.contents[i] = object; -+ return old; -+ } else { -+ this.addInternal(object, getInsertionPosition(i)); -+ return object; -+ } -+ } -+ -+ public T removeAndGet(T object) { -+ int i = this.findIndex(object); -+ if (i >= 0) { -+ final T ret = this.contents[i]; -+ this.removeInternal(i); -+ return ret; -+ } else { -+ return null; -+ } -+ } -+ // Paper end - rewrite chunk system -+ - @Override - public boolean remove(Object object) { - int i = this.findIndex((T)object); -diff --git a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java -index 640db9f71608310a64e09f1e3e677c01e6ccd98a..f2a7cb6ebed7a4b4019a09af2a025f624f6fe9c9 100644 ---- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java -+++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java -@@ -186,7 +186,11 @@ public class WorldUpgrader { - } - - WorldUpgrader.LOGGER.error("Error upgrading chunk {}", chunkcoordintpair, throwable); -+ // Paper start -+ } catch (IOException e) { -+ WorldUpgrader.LOGGER.error("Error upgrading chunk {}", chunkcoordintpair, e); - } -+ // Paper end - - if (flag1) { - ++this.converted; -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 86013d6eda6708b38c2013a242ced07eea7a3f01..43d6ed4ab09227ca23cbd51df9a42a5a3b909396 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -479,6 +479,58 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - // Paper end - -+ // Paper start -+ /** -+ * Overriding this field will cause memory leaks. -+ */ -+ private final boolean hardCollides; -+ -+ private static final java.util.Map, Boolean> cachedOverrides = java.util.Collections.synchronizedMap(new java.util.WeakHashMap<>()); -+ { -+ /* // Goodbye, broken on reobf... -+ Boolean hardCollides = cachedOverrides.get(this.getClass()); -+ if (hardCollides == null) { -+ try { -+ java.lang.reflect.Method getHardCollisionBoxEntityMethod = Entity.class.getMethod("canCollideWith", Entity.class); -+ java.lang.reflect.Method hasHardCollisionBoxMethod = Entity.class.getMethod("canBeCollidedWith"); -+ if (!this.getClass().getMethod(hasHardCollisionBoxMethod.getName(), hasHardCollisionBoxMethod.getParameterTypes()).equals(hasHardCollisionBoxMethod) -+ || !this.getClass().getMethod(getHardCollisionBoxEntityMethod.getName(), getHardCollisionBoxEntityMethod.getParameterTypes()).equals(getHardCollisionBoxEntityMethod)) { -+ hardCollides = Boolean.TRUE; -+ } else { -+ hardCollides = Boolean.FALSE; -+ } -+ cachedOverrides.put(this.getClass(), hardCollides); -+ } -+ catch (ThreadDeath thr) { throw thr; } -+ catch (Throwable thr) { -+ // shouldn't happen, just explode -+ throw new RuntimeException(thr); -+ } -+ } */ -+ this.hardCollides = this instanceof Boat -+ || this instanceof net.minecraft.world.entity.monster.Shulker -+ || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart -+ || this.shouldHardCollide(); -+ } -+ -+ // plugins can override -+ protected boolean shouldHardCollide() { -+ return false; -+ } -+ -+ public final boolean hardCollides() { -+ return this.hardCollides; -+ } -+ -+ public net.minecraft.server.level.FullChunkStatus chunkStatus; -+ -+ public int sectionX = Integer.MIN_VALUE; -+ public int sectionY = Integer.MIN_VALUE; -+ public int sectionZ = Integer.MIN_VALUE; -+ -+ public boolean updatingSectionStatus = false; -+ // Paper end -+ - public Entity(EntityType type, Level world) { - this.id = Entity.ENTITY_COUNTER.incrementAndGet(); - this.passengers = ImmutableList.of(); -@@ -2560,11 +2612,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - return InteractionResult.PASS; - } - -- public boolean canCollideWith(Entity other) { -+ public boolean canCollideWith(Entity other) { // Paper - diff on change, hard colliding entities override this - TODO CHECK ON UPDATE - AbstractMinecart/Boat override - return other.canBeCollidedWith() && !this.isPassengerOfSameVehicle(other); - } - -- public boolean canBeCollidedWith() { -+ public boolean canBeCollidedWith() { // Paper - diff on change, hard colliding entities override this TODO CHECK ON UPDATE - Boat/Shulker override - return false; - } - -@@ -3990,6 +4042,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - }).count(); - } - -+ // Paper start - rewrite chunk system -+ public boolean hasAnyPlayerPassengers() { -+ // copied from below -+ if (this.passengers.isEmpty()) { return false; } -+ return this.getIndirectPassengersStream().anyMatch((entity) -> entity instanceof Player); -+ } -+ // Paper end - rewrite chunk system - public boolean hasExactlyOnePlayerPassenger() { - if (this.passengers.isEmpty()) { return false; } // Paper - Optimize indirect passenger iteration - return this.countPlayerPassengers() == 1; -@@ -4342,6 +4401,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - return; - } - // Paper end - Block invalid positions and bounding box -+ // Paper start - rewrite chunk system -+ if (this.updatingSectionStatus) { -+ LOGGER.error("Refusing to update position for entity {} to position {} since it is processing a section status update", this, new Vec3(x, y, z), new Throwable()); -+ return; -+ } -+ // Paper end - rewrite chunk system - // Paper start - Fix MC-4 - if (this instanceof ItemEntity) { - if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.fixEntityPositionDesync) { -@@ -4465,6 +4530,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - - @Override - public final void setRemoved(Entity.RemovalReason reason) { -+ // Paper start - rewrite chunk system -+ io.papermc.paper.util.TickThread.ensureTickThread(this, "Cannot remove entity off-main"); -+ if (!((ServerLevel)this.level).getEntityLookup().canRemoveEntity(this)) { -+ LOGGER.warn("Entity " + this + " is currently prevented from being removed from the world since it is processing section status updates", new Throwable()); -+ return; -+ } -+ // Paper end - rewrite chunk system - final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers - if (this.removalReason == null) { - this.removalReason = reason; -@@ -4474,7 +4546,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - this.stopRiding(); - } - -- this.getPassengers().forEach(Entity::stopRiding); -+ if (reason != RemovalReason.UNLOADED_TO_CHUNK) this.getPassengers().forEach(Entity::stopRiding); // Paper - chunk system - don't adjust passenger state when unloading, it's just not safe (and messes with our logic in entity chunk unload) - this.levelCallback.onRemove(reason); - // Paper start - Folia schedulers - if (!(this instanceof ServerPlayer) && reason != RemovalReason.CHANGED_DIMENSION && !alreadyRemoved) { -@@ -4505,7 +4577,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - - @Override - public boolean shouldBeSaved() { -- return this.removalReason != null && !this.removalReason.shouldSave() ? false : (this.isPassenger() ? false : !this.isVehicle() || !this.hasExactlyOnePlayerPassenger()); -+ return this.removalReason != null && !this.removalReason.shouldSave() ? false : (this.isPassenger() ? false : !this.isVehicle() || !this.hasAnyPlayerPassengers()); // Paper - rewrite chunk system - it should check if the entity has ANY player passengers - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -index b18b896c624d5cadc02b1db9d011d82124d61d54..6f2c7baea0d1ac7813c7b85e1f5558573745762c 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -@@ -38,12 +38,28 @@ import net.minecraft.world.level.chunk.storage.SectionStorage; - public class PoiManager extends SectionStorage { - public static final int MAX_VILLAGE_DISTANCE = 6; - public static final int VILLAGE_SECTION_SIZE = 1; -- private final PoiManager.DistanceTracker distanceTracker; -- private final LongSet loadedChunks = new LongOpenHashSet(); -+ // Paper start - rewrite chunk system -+ // the vanilla tracker needs to be replaced because it does not support level removes -+ public final net.minecraft.server.level.ServerLevel world; -+ private final io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D villageDistanceTracker = new io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D(); -+ static final int POI_DATA_SOURCE = 7; -+ public static int convertBetweenLevels(final int level) { -+ return POI_DATA_SOURCE - level; -+ } -+ -+ protected void updateDistanceTracking(long section) { -+ if (this.isVillageCenter(section)) { -+ this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE); -+ } else { -+ this.villageDistanceTracker.removeSource(section); -+ } -+ } -+ // Paper end - rewrite chunk system -+ - - public PoiManager(Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) { - super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world); -- this.distanceTracker = new PoiManager.DistanceTracker(); -+ this.world = (net.minecraft.server.level.ServerLevel)world; // Paper - rewrite chunk system - } - - public void add(BlockPos pos, Holder type) { -@@ -180,8 +196,8 @@ public class PoiManager extends SectionStorage { - } - - public int sectionsToVillage(SectionPos pos) { -- this.distanceTracker.runAllUpdates(); -- return this.distanceTracker.getLevel(pos.asLong()); -+ this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking util -+ return convertBetweenLevels(this.villageDistanceTracker.getLevel(io.papermc.paper.util.CoordinateUtils.getChunkSectionKey(pos))); // Paper - replace distance tracking util - } - - boolean isVillageCenter(long pos) { -@@ -195,21 +211,118 @@ public class PoiManager extends SectionStorage { - - @Override - public void tick(BooleanSupplier shouldKeepTicking) { -- super.tick(shouldKeepTicking); -- this.distanceTracker.runAllUpdates(); -+ this.villageDistanceTracker.propagateUpdates(); // Paper - rewrite chunk system - } - - @Override -- protected void setDirty(long pos) { -- super.setDirty(pos); -- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false); -+ public void setDirty(long pos) { -+ // Paper start - rewrite chunk system -+ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(pos); -+ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(pos); -+ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager manager = this.world.chunkTaskScheduler.chunkHolderManager; -+ io.papermc.paper.chunk.system.poi.PoiChunk chunk = manager.getPoiChunkIfLoaded(chunkX, chunkZ, false); -+ if (chunk != null) { -+ chunk.setDirty(true); -+ } -+ this.updateDistanceTracking(pos); -+ // Paper end - rewrite chunk system - } - - @Override - protected void onSectionLoad(long pos) { -- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false); -+ this.updateDistanceTracking(pos); // Paper - move to new distance tracking util -+ } -+ -+ // Paper start - rewrite chunk system -+ @Override -+ public Optional get(long pos) { -+ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(pos); -+ int chunkY = io.papermc.paper.util.CoordinateUtils.getChunkSectionY(pos); -+ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(pos); -+ -+ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main"); -+ -+ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager manager = this.world.chunkTaskScheduler.chunkHolderManager; -+ io.papermc.paper.chunk.system.poi.PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true); -+ -+ return ret == null ? Optional.empty() : ret.getSectionForVanilla(chunkY); -+ } -+ -+ @Override -+ public Optional getOrLoad(long pos) { -+ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(pos); -+ int chunkY = io.papermc.paper.util.CoordinateUtils.getChunkSectionY(pos); -+ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(pos); -+ -+ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main"); -+ -+ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager manager = this.world.chunkTaskScheduler.chunkHolderManager; -+ -+ if (chunkY >= io.papermc.paper.util.WorldUtil.getMinSection(this.world) && -+ chunkY <= io.papermc.paper.util.WorldUtil.getMaxSection(this.world)) { -+ io.papermc.paper.chunk.system.poi.PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true); -+ if (ret != null) { -+ return ret.getSectionForVanilla(chunkY); -+ } else { -+ return manager.loadPoiChunk(chunkX, chunkZ).getSectionForVanilla(chunkY); -+ } -+ } -+ // retain vanilla behavior: do not load section if out of bounds! -+ return Optional.empty(); -+ } -+ -+ @Override -+ protected PoiSection getOrCreate(long pos) { -+ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(pos); -+ int chunkY = io.papermc.paper.util.CoordinateUtils.getChunkSectionY(pos); -+ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(pos); -+ -+ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main"); -+ -+ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager manager = this.world.chunkTaskScheduler.chunkHolderManager; -+ -+ io.papermc.paper.chunk.system.poi.PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true); -+ if (ret != null) { -+ return ret.getOrCreateSection(chunkY); -+ } else { -+ return manager.loadPoiChunk(chunkX, chunkZ).getOrCreateSection(chunkY); -+ } -+ } -+ -+ public void onUnload(long coordinate) { // Paper - rewrite chunk system -+ int chunkX = io.papermc.paper.util.MCUtil.getCoordinateX(coordinate); -+ int chunkZ = io.papermc.paper.util.MCUtil.getCoordinateZ(coordinate); -+ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Unloading poi chunk off-main"); -+ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) { -+ long sectionPos = SectionPos.asLong(chunkX, section, chunkZ); -+ this.updateDistanceTracking(sectionPos); -+ } -+ } -+ -+ public void loadInPoiChunk(io.papermc.paper.chunk.system.poi.PoiChunk poiChunk) { -+ int chunkX = poiChunk.chunkX; -+ int chunkZ = poiChunk.chunkZ; -+ io.papermc.paper.util.TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Loading poi chunk off-main"); -+ for (int sectionY = this.levelHeightAccessor.getMinSection(); sectionY < this.levelHeightAccessor.getMaxSection(); ++sectionY) { -+ PoiSection section = poiChunk.getSection(sectionY); -+ if (section != null && !section.isEmpty()) { -+ this.onSectionLoad(SectionPos.asLong(chunkX, sectionY, chunkZ)); -+ } -+ } - } - -+ public void checkConsistency(net.minecraft.world.level.chunk.ChunkAccess chunk) { -+ int chunkX = chunk.getPos().x; -+ int chunkZ = chunk.getPos().z; -+ int minY = io.papermc.paper.util.WorldUtil.getMinSection(chunk); -+ int maxY = io.papermc.paper.util.WorldUtil.getMaxSection(chunk); -+ LevelChunkSection[] sections = chunk.getSections(); -+ for (int section = minY; section <= maxY; ++section) { -+ this.checkConsistencyWithBlocks(SectionPos.of(chunkX, section, chunkZ), sections[section - minY]); -+ } -+ } -+ // Paper end - rewrite chunk system -+ - public void checkConsistencyWithBlocks(SectionPos sectionPos, LevelChunkSection chunkSection) { - Util.ifElse(this.getOrLoad(sectionPos.asLong()), (poiSet) -> { - poiSet.refresh((populator) -> { -@@ -248,7 +361,7 @@ public class PoiManager extends SectionStorage { - }).map((pair) -> { - return pair.getFirst().chunk(); - }).filter((chunkPos) -> { -- return this.loadedChunks.add(chunkPos.toLong()); -+ return true; // Paper - rewrite chunk system - }).forEach((chunkPos) -> { - world.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.EMPTY); - }); -@@ -264,7 +377,7 @@ public class PoiManager extends SectionStorage { - - @Override - protected int getLevelFromSource(long id) { -- return PoiManager.this.isVillageCenter(id) ? 0 : 7; -+ return PoiManager.this.isVillageCenter(id) ? 0 : 7; // Paper - rewrite chunk system - diff on change, this specifies the source level to use for distance tracking - } - - @Override -@@ -287,6 +400,35 @@ public class PoiManager extends SectionStorage { - } - } - -+ // Paper start - Asynchronous chunk io -+ @javax.annotation.Nullable -+ @Override -+ public net.minecraft.nbt.CompoundTag read(ChunkPos chunkcoordintpair) throws java.io.IOException { -+ // Paper start - rewrite chunk system -+ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { -+ return io.papermc.paper.chunk.system.io.RegionFileIOThread.loadData( -+ this.world, chunkcoordintpair.x, chunkcoordintpair.z, io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.POI_DATA, -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.getIOBlockingPriorityForCurrentThread() -+ ); -+ } -+ // Paper end - rewrite chunk system -+ return super.read(chunkcoordintpair); -+ } -+ -+ @Override -+ public void write(ChunkPos chunkcoordintpair, net.minecraft.nbt.CompoundTag nbttagcompound) throws java.io.IOException { -+ // Paper start - rewrite chunk system -+ if (!io.papermc.paper.chunk.system.io.RegionFileIOThread.isRegionFileThread()) { -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.scheduleSave( -+ this.world, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.RegionFileType.POI_DATA); -+ return; -+ } -+ // Paper end - rewrite chunk system -+ super.write(chunkcoordintpair, nbttagcompound); -+ } -+ // Paper end -+ - public static enum Occupancy { - HAS_SPACE(PoiRecord::hasSpace), - IS_OCCUPIED(PoiRecord::isOccupied), -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -index 795a02941d7cecb58ec45b5e79c8d510ff21163a..3fc17817906876e83f040f908b8b1ba6cfa37b8b 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -@@ -29,6 +29,7 @@ public class PoiSection { - private final Map, Set> byType = Maps.newHashMap(); - private final Runnable setDirty; - private boolean isValid; -+ public final Optional noAllocateOptional = Optional.of(this); // Paper - rewrite chunk system - - public static Codec codec(Runnable updateListener) { - return RecordCodecBuilder.create((instance) -> { -@@ -46,6 +47,12 @@ public class PoiSection { - this(updateListener, true, ImmutableList.of()); - } - -+ // Paper start - isEmpty -+ public boolean isEmpty() { -+ return this.isValid && this.records.isEmpty() && this.byType.isEmpty(); -+ } -+ // Paper end -+ - private PoiSection(Runnable updateListener, boolean valid, List pois) { - this.setDirty = updateListener; - this.isValid = valid; -diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index 57d4d2014f33a2f069d6c5aaa8e87e36b63a7177..cc888bbcd6a50124fa553bc4a8ffd1e8885d3856 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -18,6 +18,18 @@ import net.minecraft.world.phys.shapes.Shapes; - import net.minecraft.world.phys.shapes.VoxelShape; - - public interface EntityGetter { -+ -+ // Paper start -+ List getHardCollidingEntities(Entity except, AABB box, Predicate predicate); -+ -+ void getEntities(Entity except, AABB box, Predicate predicate, List into); -+ -+ void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into); -+ -+ void getEntitiesByClass(Class clazz, Entity except, final AABB box, List into, -+ Predicate predicate); -+ // Paper end -+ - List getEntities(@Nullable Entity except, AABB box, Predicate predicate); - - List getEntities(EntityTypeTest filter, AABB box, Predicate predicate); -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index d5290c97babfa9415bd52deb14610821f0fa2575..c9afe29bae7f339184f4f46d8f9828f5762d0a9c 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -545,6 +545,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement - this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i); -+ // Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance -+ // if copied from above -+ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0)) { // Paper - replace old player chunk management -+ ((ServerLevel)this).getChunkSource().blockChanged(blockposition); -+ // Paper end - per player view distance - } - - if ((i & 1) != 0) { -@@ -936,7 +941,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - // Paper end - Perf: Optimize capturedTileEntities lookup - // CraftBukkit end -- return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); -+ return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && !io.papermc.paper.util.TickThread.isTickThread() ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); // Paper - rewrite chunk system - } - - public void setBlockEntity(BlockEntity blockEntity) { -@@ -1027,26 +1032,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public List getEntities(@Nullable Entity except, AABB box, Predicate predicate) { - this.getProfiler().incrementCounter("getEntities"); - List list = Lists.newArrayList(); -- -- this.getEntities().get(box, (entity1) -> { -- if (entity1 != except && predicate.test(entity1)) { -- list.add(entity1); -- } -- -- if (entity1 instanceof EnderDragon) { -- EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity1).getSubEntities(); -- int i = aentitycomplexpart.length; -- -- for (int j = 0; j < i; ++j) { -- EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; -- -- if (entity1 != except && predicate.test(entitycomplexpart)) { -- list.add(entitycomplexpart); -- } -- } -- } -- -- }); -+ ((ServerLevel)this).getEntityLookup().getEntities(except, box, list, predicate); // Paper - optimise this call - return list; - } - -@@ -1064,34 +1050,23 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - public void getEntities(EntityTypeTest filter, AABB box, Predicate predicate, List result, int limit) { - this.getProfiler().incrementCounter("getEntities"); -- this.getEntities().get(filter, box, (entity) -> { -- if (predicate.test(entity)) { -- result.add(entity); -- if (result.size() >= limit) { -- return AbortableIterationConsumer.Continuation.ABORT; -- } -- } -- -- if (entity instanceof EnderDragon) { -- EnderDragon entityenderdragon = (EnderDragon) entity; -- EnderDragonPart[] aentitycomplexpart = entityenderdragon.getSubEntities(); -- int j = aentitycomplexpart.length; -- -- for (int k = 0; k < j; ++k) { -- EnderDragonPart entitycomplexpart = aentitycomplexpart[k]; -- T t0 = filter.tryCast(entitycomplexpart); // CraftBukkit - decompile error -- -- if (t0 != null && predicate.test(t0)) { -- result.add(t0); -- if (result.size() >= limit) { -- return AbortableIterationConsumer.Continuation.ABORT; -- } -- } -- } -+ // Paper start - optimise this call -+ //TODO use limit -+ if (filter instanceof net.minecraft.world.entity.EntityType entityTypeTest) { -+ ((ServerLevel) this).getEntityLookup().getEntities(entityTypeTest, box, result, predicate); -+ } else { -+ Predicate test = (obj) -> { -+ return filter.tryCast(obj) != null; -+ }; -+ predicate = predicate == null ? test : test.and((Predicate) predicate); -+ Class base; -+ if (filter == null || (base = filter.getBaseClass()) == null || base == Entity.class) { -+ ((ServerLevel) this).getEntityLookup().getEntities((Entity) null, box, (List) result, (Predicate)predicate); -+ } else { -+ ((ServerLevel) this).getEntityLookup().getEntities(base, null, box, (List) result, (Predicate)predicate); // Paper - optimise this call - } -- -- return AbortableIterationConsumer.Continuation.CONTINUE; -- }); -+ } -+ // Paper end - optimise this call - } - - @Nullable -@@ -1383,4 +1358,45 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - } - // Paper end - notify observers even if grow failed -+ // Paper start -+ //protected final io.papermc.paper.world.EntitySliceManager entitySliceManager; // Paper - rewrite chunk system -+ -+ 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) { -+ return new org.bukkit.entity.Entity[0]; -+ } -+ return slices.getChunkEntities(); -+ } -+ -+ @Override -+ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { -+ List ret = new java.util.ArrayList<>(); -+ ((ServerLevel)this).getEntityLookup().getHardCollidingEntities(except, box, ret, predicate); -+ return ret; -+ } -+ -+ @Override -+ public void getEntities(Entity except, AABB box, Predicate predicate, List into) { -+ ((ServerLevel)this).getEntityLookup().getEntities(except, box, into, predicate); -+ } -+ -+ @Override -+ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) { -+ ((ServerLevel)this).getEntityLookup().getHardCollidingEntities(except, box, into, predicate); -+ } -+ -+ @Override -+ public void getEntitiesByClass(Class clazz, Entity except, final AABB box, List into, -+ Predicate predicate) { -+ ((ServerLevel)this).getEntityLookup().getEntities((Class)clazz, except, box, (List)into, (Predicate)predicate); -+ } -+ -+ @Override -+ public List getEntitiesOfClass(Class entityClass, AABB box, Predicate predicate) { -+ List ret = new java.util.ArrayList<>(); -+ ((ServerLevel)this).getEntityLookup().getEntities(entityClass, null, box, ret, predicate); -+ return ret; -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/world/level/LevelReader.java b/src/main/java/net/minecraft/world/level/LevelReader.java -index cc0d20e9f851268fe8403ac516f426ec1d008150..12eaafdbd324fa36b3f46c3b644bc8117a4123ad 100644 ---- a/src/main/java/net/minecraft/world/level/LevelReader.java -+++ b/src/main/java/net/minecraft/world/level/LevelReader.java -@@ -26,6 +26,15 @@ public interface LevelReader extends BlockAndTintGetter, CollisionGetter, Signal - @Nullable - ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create); - -+ // Paper start - rewrite chunk system -+ default ChunkAccess syncLoadNonFull(int chunkX, int chunkZ, ChunkStatus status) { -+ if (status == null || status.isOrAfter(ChunkStatus.FULL)) { -+ throw new IllegalArgumentException("Status: " + status.toString()); -+ } -+ return this.getChunk(chunkX, chunkZ, status, true); -+ } -+ // Paper end - rewrite chunk system -+ - @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z); // Paper - ifLoaded api (we need this since current impl blocks if the chunk is loading) - @Nullable default ChunkAccess getChunkIfLoadedImmediately(BlockPos pos) { return this.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4);} - -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -index b26a4eb4951e87f891b59028d98b8ffba8e103a8..b8b78494449c0cd638f9706a803dc54e184d981f 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -114,7 +114,7 @@ public abstract class ChunkGenerator { - return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName("init_biomes", () -> { - chunk.fillBiomesFromNoise(this.biomeSource, noiseConfig.sampler()); - return chunk; -- }), Util.backgroundExecutor()); -+ }), executor); // Paper - run with supplied executor - } - - public abstract void applyCarvers(WorldGenRegion chunkRegion, long seed, RandomState noiseConfig, BiomeManager biomeAccess, StructureManager structureAccessor, ChunkAccess chunk, GenerationStep.Carving carverStep); -@@ -309,7 +309,7 @@ public abstract class ChunkGenerator { - return Pair.of(placement.getLocatePos(pos), holder); - } - -- ChunkAccess ichunkaccess = world.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_STARTS); -+ ChunkAccess ichunkaccess = world.syncLoadNonFull(pos.x, pos.z, ChunkStatus.STRUCTURE_STARTS); // Paper - rewrite chunk system - - structurestart = structureAccessor.getStartForStructure(SectionPos.bottomOf(ichunkaccess), (Structure) holder.value(), ichunkaccess); - } while (structurestart == null); -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java -index 846ae3fd184a1d63b743aa25e045604576697c96..a907b79fd8291a0e92db138f37239d17424188a1 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java -@@ -30,6 +30,30 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp - - public class ChunkStatus { - -+ // Paper start - rewrite chunk system -+ public boolean isParallelCapable; // Paper -+ public int writeRadius = -1; -+ public int loadRange = 0; -+ -+ protected static final java.util.List statuses = new java.util.ArrayList<>(); -+ -+ private ChunkStatus nextStatus; -+ -+ public final ChunkStatus getNextStatus() { -+ return this.nextStatus; -+ } -+ -+ public final boolean isEmptyLoadStatus() { -+ return this.loadingTask == PASSTHROUGH_LOAD_TASK; -+ } -+ -+ public final boolean isEmptyGenStatus() { -+ return this == ChunkStatus.EMPTY; -+ } -+ -+ public final java.util.concurrent.atomic.AtomicBoolean warnedAboutNoImmediateComplete = new java.util.concurrent.atomic.AtomicBoolean(); -+ // Paper end - rewrite chunk system -+ - public static final int MAX_STRUCTURE_DISTANCE = 8; - private static final EnumSet PRE_FEATURES = EnumSet.of(Heightmap.Types.OCEAN_FLOOR_WG, Heightmap.Types.WORLD_SURFACE_WG); - public static final EnumSet POST_FEATURES = EnumSet.of(Heightmap.Types.OCEAN_FLOOR, Heightmap.Types.WORLD_SURFACE, Heightmap.Types.MOTION_BLOCKING, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES); -@@ -151,13 +175,13 @@ public class ChunkStatus { - ((ProtoChunk) chunk).setLightEngine(lightingProvider); - boolean flag = ChunkStatus.isLighted(chunk); - -- return lightingProvider.initializeLight(chunk, flag).thenApply(Either::left); -+ return CompletableFuture.completedFuture(Either.left(chunk)); // Paper - rewrite chunk system - } - - private static CompletableFuture> lightChunk(ThreadedLevelLightEngine lightingProvider, ChunkAccess chunk) { - boolean flag = ChunkStatus.isLighted(chunk); - -- return lightingProvider.lightChunk(chunk, flag).thenApply(Either::left); -+ return CompletableFuture.completedFuture(Either.left(chunk)); // Paper - rewrite chunk system - } - - private static ChunkStatus registerSimple(String id, @Nullable ChunkStatus previous, int taskMargin, EnumSet heightMapTypes, ChunkStatus.ChunkType chunkType, ChunkStatus.SimpleGenerationTask task) { -@@ -211,6 +235,13 @@ public class ChunkStatus { - this.chunkType = chunkType; - this.heightmapsAfter = heightMapTypes; - this.index = previous == null ? 0 : previous.getIndex() + 1; -+ // Paper start -+ this.nextStatus = this; -+ if (statuses.size() > 0) { -+ statuses.get(statuses.size() - 1).nextStatus = this; -+ } -+ statuses.add(this); -+ // Paper end - } - - public int getIndex() { -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 73e682bb3ef3b2e450ec8c594b5365c7a340615e..6a5756bd333d9b221e7770842e5114d295cb7f1d 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -84,6 +84,7 @@ public class LevelChunk extends ChunkAccess { - private final Int2ObjectMap gameEventListenerRegistrySections; - private final LevelChunkTicks blockTicks; - private final LevelChunkTicks fluidTicks; -+ public volatile FullChunkStatus chunkStatus = FullChunkStatus.INACCESSIBLE; // Paper - rewrite chunk system - - public LevelChunk(Level world, ChunkPos pos) { - this(world, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, (LevelChunkSection[]) null, (LevelChunk.PostLoadProcessor) null, (BlendingData) null); -@@ -690,9 +691,26 @@ public class LevelChunk extends ChunkAccess { - - } - -- // CraftBukkit start -- public void loadCallback() { -- // Paper start - neighbour cache -+ // Paper start - new load callbacks -+ private io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder; -+ public io.papermc.paper.chunk.system.scheduling.NewChunkHolder getChunkHolder() { -+ return this.chunkHolder; -+ } -+ -+ public void setChunkHolder(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ if (chunkHolder == null) { -+ throw new NullPointerException("Chunkholder cannot be null"); -+ } -+ if (this.chunkHolder != null) { -+ throw new IllegalStateException("Already have chunkholder: " + this.chunkHolder + ", cannot replace with " + chunkHolder); -+ } -+ this.chunkHolder = chunkHolder; -+ this.playerChunk = chunkHolder.vanillaChunkHolder; -+ } -+ -+ /* Note: We skip the light neighbour chunk loading done for the vanilla full chunk */ -+ /* Starlight does not need these chunks for lighting purposes because of edge checks */ -+ public void pushChunkIntoLoadedMap() { - int chunkX = this.chunkPos.x; - int chunkZ = this.chunkPos.z; - net.minecraft.server.level.ServerChunkCache chunkProvider = this.level.getChunkSource(); -@@ -707,10 +725,55 @@ public class LevelChunk extends ChunkAccess { - } - } - this.setNeighbourLoaded(0, 0, this); -+ this.level.getChunkSource().addLoadedChunk(this); -+ } -+ -+ public void onChunkLoad(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ // figure out how this should interface with: -+ // the entity chunk load event // -> moved to the FULL status -+ // the chunk load event // -> stays here -+ // any entity add to world events // -> in FULL status -+ this.loadCallback(); -+ io.papermc.paper.chunk.system.ChunkSystem.onChunkBorder(this, chunkHolder.vanillaChunkHolder); -+ } -+ -+ public void onChunkUnload(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ // figure out how this should interface with: -+ // the entity chunk load event // -> moved to chunk unload to disk (not written yet) -+ // the chunk load event // -> stays here -+ // any entity add to world events // -> goes into the unload logic, it will completely explode -+ // etc later -+ this.unloadCallback(); -+ io.papermc.paper.chunk.system.ChunkSystem.onChunkNotBorder(this, chunkHolder.vanillaChunkHolder); -+ } -+ -+ public void onChunkTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ this.postProcessGeneration(); -+ this.level.startTickingChunk(this); -+ io.papermc.paper.chunk.system.ChunkSystem.onChunkTicking(this, chunkHolder.vanillaChunkHolder); -+ } -+ -+ public void onChunkNotTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ io.papermc.paper.chunk.system.ChunkSystem.onChunkNotTicking(this, chunkHolder.vanillaChunkHolder); -+ } -+ -+ public void onChunkEntityTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ io.papermc.paper.chunk.system.ChunkSystem.onChunkEntityTicking(this, chunkHolder.vanillaChunkHolder); -+ } -+ -+ public void onChunkNotEntityTicking(io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder) { -+ io.papermc.paper.chunk.system.ChunkSystem.onChunkNotEntityTicking(this, chunkHolder.vanillaChunkHolder); -+ } -+ // Paper end - new load callbacks -+ -+ // CraftBukkit start -+ public void loadCallback() { -+ if (this.loadedTicketLevel) { LOGGER.error("Double calling chunk load!", new Throwable()); } // Paper -+ // Paper - rewrite chunk system - move into separate callback - this.loadedTicketLevel = true; -- // Paper end - neighbour cache -+ // Paper - rewrite chunk system - move into separate callback - org.bukkit.Server server = this.level.getCraftServer(); -- this.level.getChunkSource().addLoadedChunk(this); // Paper -+ // Paper - rewrite chunk system - move into separate callback - if (server != null) { - /* - * If it's a new world, the first few chunks are generated inside -@@ -719,6 +782,7 @@ public class LevelChunk extends ChunkAccess { - */ - org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); - server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration)); -+ this.chunkHolder.getEntityChunk().callEntitiesLoadEvent(); // Paper - rewrite chunk system - - if (this.needsDecoration) { - try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper -@@ -747,9 +811,11 @@ public class LevelChunk extends ChunkAccess { - } - - public void unloadCallback() { -+ if (!this.loadedTicketLevel) { LOGGER.error("Double calling chunk unload!", new Throwable()); } // Paper - org.bukkit.Server server = this.level.getCraftServer(); -+ this.chunkHolder.getEntityChunk().callEntitiesUnloadEvent(); // Paper - rewrite chunk system - org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); -- org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, this.isUnsaved()); -+ org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, true); // Paper - rewrite chunk system - force save to true so that mustNotSave is correctly set below - server.getPluginManager().callEvent(unloadEvent); - // note: saving can be prevented, but not forced if no saving is actually required - this.mustNotSave = !unloadEvent.isSaveChunk(); -@@ -771,9 +837,26 @@ public class LevelChunk extends ChunkAccess { - // Paper end - } - -+ // Paper start - add dirty system to tick lists -+ @Override -+ public void setUnsaved(boolean needsSaving) { -+ if (!needsSaving) { -+ this.blockTicks.clearDirty(); -+ this.fluidTicks.clearDirty(); -+ } -+ super.setUnsaved(needsSaving); -+ } -+ // Paper end - add dirty system to tick lists -+ - @Override - public boolean isUnsaved() { -- return super.isUnsaved() && !this.mustNotSave; -+ // Paper start - add dirty system to tick lists -+ long gameTime = this.level.getLevelData().getGameTime(); -+ if (this.blockTicks.isDirty(gameTime) || this.fluidTicks.isDirty(gameTime)) { -+ return true; -+ } -+ // Paper end - add dirty system to tick lists -+ return super.isUnsaved(); // Paper - rewrite chunk system - do NOT clobber the dirty flag - } - // CraftBukkit end - -@@ -842,7 +925,9 @@ public class LevelChunk extends ChunkAccess { - return this.blockEntities; - } - -+ public boolean isPostProcessingDone; // Paper - replace chunk loader system - public void postProcessGeneration() { -+ try { // Paper - replace chunk loader system - ChunkPos chunkcoordintpair = this.getPos(); - - for (int i = 0; i < this.postProcessing.length; ++i) { -@@ -863,6 +948,7 @@ public class LevelChunk extends ChunkAccess { - BlockState iblockdata1 = Block.updateFromNeighbourShapes(iblockdata, this.level, blockposition); - - this.level.setBlock(blockposition, iblockdata1, 20); -+ if (iblockdata1 != iblockdata) this.level.chunkSource.blockChanged(blockposition); // Paper - replace player chunk loader - notify since we send before processing full updates - } - } - -@@ -880,6 +966,10 @@ public class LevelChunk extends ChunkAccess { - - this.pendingBlockEntities.clear(); - this.upgradeData.upgrade(this); -+ } finally { // Paper start - replace chunk loader system -+ this.isPostProcessingDone = true; -+ } -+ // Paper end - replace chunk loader system - } - - @Nullable -@@ -929,7 +1019,7 @@ public class LevelChunk extends ChunkAccess { - } - - public FullChunkStatus getFullStatus() { -- return this.fullStatus == null ? FullChunkStatus.FULL : (FullChunkStatus) this.fullStatus.get(); -+ return this.chunkHolder == null ? FullChunkStatus.INACCESSIBLE : this.chunkHolder.getChunkStatus(); // Paper - rewrite chunk system - } - - public void setFullStatus(Supplier levelTypeProvider) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 7e416ad09959a08931c207f62d97af4ee868c039..5d50f1bcba507975b8942529104c0ccd5e08c252 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -110,6 +110,17 @@ public class ChunkSerializer { - } - } - // Paper end - guard against serializing mismatching coordinates -+ // Paper start - rewrite chunk system -+ public static final class InProgressChunkHolder { -+ -+ public final ProtoChunk protoChunk; -+ -+ public CompoundTag poiData; -+ -+ public InProgressChunkHolder(final ProtoChunk protoChunk) { -+ this.protoChunk = protoChunk; -+ } -+ } - public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) { - // Paper start - Do not let the server load chunks from newer versions - if (nbt.contains("DataVersion", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) { -@@ -120,6 +131,12 @@ public class ChunkSerializer { - } - } - // Paper end - Do not let the server load chunks from newer versions -+ InProgressChunkHolder holder = readInProgressChunkHolder(world, poiStorage, chunkPos, nbt); -+ return holder.protoChunk; -+ } -+ -+ public static InProgressChunkHolder readInProgressChunkHolder(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) { -+ // Paper end - rewrite chunk system - ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate - - if (!Objects.equals(chunkPos, chunkcoordintpair1)) { -@@ -185,7 +202,7 @@ public class ChunkSerializer { - achunksection[k] = chunksection; - SectionPos sectionposition = SectionPos.of(chunkPos, b0); - -- poiStorage.checkConsistencyWithBlocks(sectionposition, chunksection); -+ // Paper - rewrite chunk system - moved to final load stage - } - - boolean flag3 = nbttagcompound1.contains("BlockLight", 7); -@@ -331,7 +348,7 @@ public class ChunkSerializer { - } - - if (chunkstatus_type == ChunkStatus.ChunkType.LEVELCHUNK) { -- return new ImposterProtoChunk((LevelChunk) object1, false); -+ return new InProgressChunkHolder(new ImposterProtoChunk((LevelChunk) object1, false)); // Paper - Async chunk loading - } else { - ProtoChunk protochunk1 = (ProtoChunk) object1; - -@@ -366,9 +383,41 @@ public class ChunkSerializer { - protochunk1.setCarvingMask(worldgenstage_features, new CarvingMask(nbttagcompound5.getLongArray(s1), ((ChunkAccess) object1).getMinBuildHeight())); - } - -- return protochunk1; -+ return new InProgressChunkHolder(protochunk1); // Paper - Async chunk loading -+ } -+ } -+ -+ // Paper start - async chunk save for unload -+ public record AsyncSaveData( -+ Tag blockTickList, // non-null if we had to go to the server's tick list -+ Tag fluidTickList, // non-null if we had to go to the server's tick list -+ ListTag blockEntities, -+ long worldTime -+ ) {} -+ -+ // must be called sync -+ public static AsyncSaveData getAsyncSaveData(ServerLevel world, ChunkAccess chunk) { -+ org.spigotmc.AsyncCatcher.catchOp("preparation of chunk data for async save"); -+ -+ final CompoundTag tickLists = new CompoundTag(); -+ ChunkSerializer.saveTicks(world, tickLists, chunk.getTicksForSerialization()); -+ -+ ListTag blockEntitiesSerialized = new ListTag(); -+ for (final BlockPos blockPos : chunk.getBlockEntitiesPos()) { -+ final CompoundTag blockEntityNbt = chunk.getBlockEntityNbtForSaving(blockPos); -+ if (blockEntityNbt != null) { -+ blockEntitiesSerialized.add(blockEntityNbt); -+ } - } -+ -+ return new AsyncSaveData( -+ tickLists.get(BLOCK_TICKS_TAG), -+ tickLists.get(FLUID_TICKS_TAG), -+ blockEntitiesSerialized, -+ world.getGameTime() -+ ); - } -+ // Paper end - - private static void logErrors(ChunkPos chunkPos, int y, String message) { - ChunkSerializer.LOGGER.error("Recoverable errors when loading section [" + chunkPos.x + ", " + y + ", " + chunkPos.z + "]: " + message); -@@ -385,6 +434,11 @@ public class ChunkSerializer { - // CraftBukkit end - - public static CompoundTag write(ServerLevel world, ChunkAccess chunk) { -+ // Paper start -+ return saveChunk(world, chunk, null); -+ } -+ public static CompoundTag saveChunk(ServerLevel world, ChunkAccess chunk, @org.checkerframework.checker.nullness.qual.Nullable AsyncSaveData asyncsavedata) { -+ // Paper end - // Paper start - rewrite light impl - final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world); - final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world); -@@ -397,7 +451,7 @@ public class ChunkSerializer { - nbttagcompound.putInt("xPos", chunkcoordintpair.x); - nbttagcompound.putInt("yPos", chunk.getMinSection()); - nbttagcompound.putInt("zPos", chunkcoordintpair.z); -- nbttagcompound.putLong("LastUpdate", world.getGameTime()); -+ nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading - nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime()); - nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString()); - BlendingData blendingdata = chunk.getBlendingData(); -@@ -497,8 +551,17 @@ public class ChunkSerializer { - nbttagcompound.putBoolean("isLightOn", false); // Paper - set to false but still store, this allows us to detect --eraseCache (as eraseCache _removes_) - } - -- ListTag nbttaglist1 = new ListTag(); -- Iterator iterator = chunk.getBlockEntitiesPos().iterator(); -+ // Paper start -+ ListTag nbttaglist1; -+ Iterator iterator; -+ if (asyncsavedata != null) { -+ nbttaglist1 = asyncsavedata.blockEntities; -+ iterator = java.util.Collections.emptyIterator(); -+ } else { -+ nbttaglist1 = new ListTag(); -+ iterator = chunk.getBlockEntitiesPos().iterator(); -+ } -+ // Paper end - - CompoundTag nbttagcompound2; - -@@ -534,7 +597,14 @@ public class ChunkSerializer { - nbttagcompound.put("CarvingMasks", nbttagcompound2); - } - -+ // Paper start -+ if (asyncsavedata != null) { -+ nbttagcompound.put(BLOCK_TICKS_TAG, asyncsavedata.blockTickList); -+ nbttagcompound.put(FLUID_TICKS_TAG, asyncsavedata.fluidTickList); -+ } else { - ChunkSerializer.saveTicks(world, nbttagcompound, chunk.getTicksForSerialization()); -+ } -+ // Paper end - nbttagcompound.put("PostProcessing", ChunkSerializer.packOffsets(chunk.getPostProcessing())); - CompoundTag nbttagcompound3 = new CompoundTag(); - Iterator iterator1 = chunk.getHeightmaps().iterator(); -@@ -590,7 +660,7 @@ public class ChunkSerializer { - - return nbttaglist == null && nbttaglist1 == null ? null : (chunk) -> { - if (nbttaglist != null) { -- world.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(nbttaglist, world)); -+ world.addLegacyChunkEntities(EntityType.loadEntitiesRecursive(nbttaglist, world), chunk.getPos()); // Paper - rewrite chunk system - } - - if (nbttaglist1 != null) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -index eebaf98bc0fa4696af59b2a79563beb73501a554..645a1773237f2002c233ec1f3ff6f0ca46ca4024 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -28,18 +28,21 @@ import net.minecraft.world.level.storage.DimensionDataStorage; - public class ChunkStorage implements AutoCloseable { - - public static final int LAST_MONOLYTH_STRUCTURE_DATA_VERSION = 1493; -- private final IOWorker worker; -+ // Paper start - rewrite chunk system; async chunk IO -+ private final Object persistentDataLock = new Object(); -+ public final RegionFileStorage regionFileCache; -+ // Paper end - rewrite chunk system - protected final DataFixer fixerUpper; - @Nullable - private volatile LegacyStructureDataHandler legacyStructureHandler; - - public ChunkStorage(Path directory, DataFixer dataFixer, boolean dsync) { - this.fixerUpper = dataFixer; -- this.worker = new IOWorker(directory, dsync, "chunk"); -+ this.regionFileCache = new RegionFileStorage(directory, dsync); // Paper - rewrite chunk system; async chunk IO - } - - public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) { -- return this.worker.isOldChunkAround(chunkPos, checkRadius); -+ return true; // Paper - rewrite chunk system; (for now, unoptimised behavior) TODO implement later? the chunk status that blender uses SHOULD already have this radius loaded, no need to go back later for it... - } - - // CraftBukkit start -@@ -47,8 +50,9 @@ public class ChunkStorage implements AutoCloseable { - if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full" - ChunkPos pos = new ChunkPos(x, z); - if (cps != null) { -- com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); -- if (cps.hasChunk(x, z)) { -+ // Paper start - rewrite chunk system; async chunk IO -+ if (cps.getChunkAtIfCachedImmediately(x, z) != null) { // isLoaded is a ticket level check, not a chunk loaded check! -+ // Paper end - rewrite chunk system - return true; - } - } -@@ -76,6 +80,7 @@ public class ChunkStorage implements AutoCloseable { - - public CompoundTag upgradeChunkTag(ResourceKey resourcekey, Supplier supplier, CompoundTag nbttagcompound, Optional>> optional, ChunkPos pos, @Nullable LevelAccessor generatoraccess) { - // CraftBukkit end -+ nbttagcompound = nbttagcompound.copy(); // Paper - defensive copy, another thread might modify this - int i = ChunkStorage.getVersion(nbttagcompound); - - // CraftBukkit start -@@ -93,9 +98,11 @@ public class ChunkStorage implements AutoCloseable { - if (i < 1493) { - ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, i, 1493); // Paper - replace chunk converter - if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) { -+ synchronized (this.persistentDataLock) { // Paper - Async chunk loading - LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier); - - nbttagcompound = persistentstructurelegacy.updateFromLegacy(nbttagcompound); -+ } // Paper - Async chunk loading - } - } - -@@ -128,7 +135,7 @@ public class ChunkStorage implements AutoCloseable { - LegacyStructureDataHandler persistentstructurelegacy = this.legacyStructureHandler; - - if (persistentstructurelegacy == null) { -- synchronized (this) { -+ synchronized (this.persistentDataLock) { // Paper - async chunk loading - persistentstructurelegacy = this.legacyStructureHandler; - if (persistentstructurelegacy == null) { - this.legacyStructureHandler = persistentstructurelegacy = LegacyStructureDataHandler.getLegacyStructureHandler(worldKey, (DimensionDataStorage) stateManagerGetter.get()); -@@ -154,10 +161,20 @@ public class ChunkStorage implements AutoCloseable { - } - - public CompletableFuture> read(ChunkPos chunkPos) { -- return this.worker.loadAsync(chunkPos); -+ // Paper start - async chunk io -+ try { -+ return CompletableFuture.completedFuture(Optional.ofNullable(this.readSync(chunkPos))); -+ } catch (Throwable thr) { -+ return CompletableFuture.failedFuture(thr); -+ } -+ } -+ @Nullable -+ public CompoundTag readSync(ChunkPos chunkPos) throws IOException { -+ return this.regionFileCache.read(chunkPos); - } -+ // Paper end - async chunk io - -- public void write(ChunkPos chunkPos, CompoundTag nbt) { -+ public void write(ChunkPos chunkPos, CompoundTag nbt) throws IOException { // Paper - rewrite chunk system; async chunk io - // Paper start - guard against serializing mismatching coordinates - if (nbt != null && !chunkPos.equals(ChunkSerializer.getChunkCoordinate(nbt))) { - final String world = (this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap) this).level.getWorld().getName() : null; -@@ -165,22 +182,33 @@ public class ChunkStorage implements AutoCloseable { - + " but compound says coordinate is " + ChunkSerializer.getChunkCoordinate(nbt) + (world == null ? " for an unknown world" : (" for world: " + world))); - } - // Paper end - guard against serializing mismatching coordinates -- this.worker.store(chunkPos, nbt); -+ this.regionFileCache.write(chunkPos, nbt); // Paper - rewrite chunk system; async chunk io - if (this.legacyStructureHandler != null) { -+ synchronized (this.persistentDataLock) { // Paper - rewrite chunk system; async chunk io - this.legacyStructureHandler.removeIndex(chunkPos.toLong()); -+ } // Paper - rewrite chunk system; async chunk io - } - - } - - public void flushWorker() { -- this.worker.synchronize(true).join(); -+ io.papermc.paper.chunk.system.io.RegionFileIOThread.flush(); // Paper - rewrite chunk system - } - - public void close() throws IOException { -- this.worker.close(); -+ this.regionFileCache.close(); // Paper - nuke IO worker - } - - public ChunkScanAccess chunkScanner() { -- return this.worker; -+ // Paper start - nuke IO worker -+ return ((chunkPos, streamTagVisitor) -> { -+ try { -+ this.regionFileCache.scanChunk(chunkPos, streamTagVisitor); -+ return java.util.concurrent.CompletableFuture.completedFuture(null); -+ } catch (IOException e) { -+ throw new RuntimeException(e); -+ } -+ }); -+ // Paper end - } - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -index 98b3909b536f11eda9c481ffd74066ad0cdb0ebc..0ec0be22f7292d57c40da6f1f4575bdebf8dbd09 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -@@ -30,43 +30,31 @@ public class EntityStorage implements EntityPersistentStorage { - private static final String ENTITIES_TAG = "Entities"; - private static final String POSITION_TAG = "Position"; - public final ServerLevel level; -- private final IOWorker worker; -+ // Paper - rewrite chunk system - private final LongSet emptyChunks = new LongOpenHashSet(); -- public final ProcessorMailbox entityDeserializerQueue; -+ // Paper - rewrite chunk system - protected final DataFixer fixerUpper; - - public EntityStorage(ServerLevel world, Path path, DataFixer dataFixer, boolean dsync, Executor executor) { - this.level = world; - this.fixerUpper = dataFixer; -- this.entityDeserializerQueue = ProcessorMailbox.create(executor, "entity-deserializer"); -- this.worker = new IOWorker(path, dsync, "entities"); -+ // Paper - rewrite chunk system - } - - @Override - public CompletableFuture> loadEntities(ChunkPos pos) { -- return this.emptyChunks.contains(pos.toLong()) ? CompletableFuture.completedFuture(emptyChunk(pos)) : this.worker.loadAsync(pos).thenApplyAsync((nbt) -> { -- if (nbt.isEmpty()) { -- this.emptyChunks.add(pos.toLong()); -- return emptyChunk(pos); -- } else { -- try { -- ChunkPos chunkPos2 = readChunkPos(nbt.get()); -- if (!Objects.equals(pos, chunkPos2)) { -- LOGGER.error("Chunk file at {} is in the wrong location. (Expected {}, got {})", pos, pos, chunkPos2); -- } -- } catch (Exception var6) { -- LOGGER.warn("Failed to parse chunk {} position info", pos, var6); -- } -- -- CompoundTag compoundTag = this.upgradeChunkTag(nbt.get()); -- ListTag listTag = compoundTag.getList("Entities", 10); -- List list = EntityType.loadEntitiesRecursive(listTag, this.level).collect(ImmutableList.toImmutableList()); -- return new ChunkEntities<>(pos, list); -- } -- }, this.entityDeserializerQueue::tell); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - copy out read logic into readEntities -+ } -+ -+ // Paper start - rewrite chunk system -+ public static List readEntities(ServerLevel level, CompoundTag compoundTag) { -+ ListTag listTag = compoundTag.getList("Entities", 10); -+ List list = EntityType.loadEntitiesRecursive(listTag, level).collect(ImmutableList.toImmutableList()); -+ return list; - } -+ // Paper end - rewrite chunk system - -- private static ChunkPos readChunkPos(CompoundTag chunkNbt) { -+ public static ChunkPos readChunkPos(CompoundTag chunkNbt) { // Paper - public - int[] is = chunkNbt.getIntArray("Position"); - return new ChunkPos(is[0], is[1]); - } -@@ -81,45 +69,75 @@ public class EntityStorage implements EntityPersistentStorage { - - @Override - public void storeEntities(ChunkEntities dataList) { -+ // Paper start - rewrite chunk system -+ if (true) { -+ throw new UnsupportedOperationException(); -+ } -+ // Paper end - rewrite chunk system - ChunkPos chunkPos = dataList.getPos(); - if (dataList.isEmpty()) { - if (this.emptyChunks.add(chunkPos.toLong())) { -- this.worker.store(chunkPos, (CompoundTag)null); -+ // Paper - rewrite chunk system - } - - } else { -- ListTag listTag = new ListTag(); -- dataList.getEntities().forEach((entity) -> { -- CompoundTag compoundTag = new CompoundTag(); -- if (entity.save(compoundTag)) { -- listTag.add(compoundTag); -- } -- -- }); -- CompoundTag compoundTag = NbtUtils.addCurrentDataVersion(new CompoundTag()); -- compoundTag.put("Entities", listTag); -- writeChunkPos(compoundTag, chunkPos); -- this.worker.store(chunkPos, compoundTag).exceptionally((ex) -> { -- LOGGER.error("Failed to store chunk {}", chunkPos, ex); -- return null; -- }); -+ // Paper - move into saveEntityChunk0 - this.emptyChunks.remove(chunkPos.toLong()); - } - } - -+ // Paper start - rewrite chunk system -+ public static void copyEntities(final CompoundTag from, final CompoundTag into) { -+ if (from == null) { -+ return; -+ } -+ final ListTag entitiesFrom = from.getList("Entities", net.minecraft.nbt.Tag.TAG_COMPOUND); -+ if (entitiesFrom == null || entitiesFrom.isEmpty()) { -+ return; -+ } -+ -+ final ListTag entitiesInto = into.getList("Entities", net.minecraft.nbt.Tag.TAG_COMPOUND); -+ into.put("Entities", entitiesInto); // this is in case into doesn't have any entities -+ entitiesInto.addAll(0, entitiesFrom.copy()); // need to copy, this is coming from the save thread -+ } -+ -+ public static CompoundTag saveEntityChunk(List entities, ChunkPos chunkPos, ServerLevel level) { -+ return saveEntityChunk0(entities, chunkPos, level, false); -+ } -+ private static CompoundTag saveEntityChunk0(List entities, ChunkPos chunkPos, ServerLevel level, boolean force) { -+ if (!force && entities.isEmpty()) { -+ return null; -+ } -+ -+ ListTag listTag = new ListTag(); -+ entities.forEach((entity) -> { // diff here: use entities parameter -+ CompoundTag compoundTag = new CompoundTag(); -+ if (entity.save(compoundTag)) { -+ listTag.add(compoundTag); -+ } -+ -+ }); -+ CompoundTag compoundTag = NbtUtils.addCurrentDataVersion(new CompoundTag()); -+ compoundTag.put("Entities", listTag); -+ writeChunkPos(compoundTag, chunkPos); -+ // Paper - remove worker usage -+ -+ return !force && listTag.isEmpty() ? null : compoundTag; -+ } -+ // Paper end - rewrite chunk system -+ - @Override - public void flush(boolean sync) { -- this.worker.synchronize(sync).join(); -- this.entityDeserializerQueue.runAll(); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - -- private CompoundTag upgradeChunkTag(CompoundTag chunkNbt) { -+ public static CompoundTag upgradeChunkTag(CompoundTag chunkNbt) { // Paper - public and static - int i = NbtUtils.getDataVersion(chunkNbt, -1); - return ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY_CHUNK, chunkNbt, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - route to new converter system - } - - @Override - public void close() throws IOException { -- this.worker.close(); -+ throw new UnsupportedOperationException(); // Paper - rewrite chunk system - } - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -index 8d20e265872e1f8200de186a69a29f498ceb8588..bc8038da65f834249c61a262fc1a5abb7cc91a63 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -@@ -44,6 +44,7 @@ public class RegionFile implements AutoCloseable { - private final IntBuffer timestamps; - @VisibleForTesting - protected final RegionBitmap usedSectors; -+ public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // Paper - - public RegionFile(Path file, Path directory, boolean dsync) throws IOException { - this(file, directory, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format -@@ -228,7 +229,7 @@ public class RegionFile implements AutoCloseable { - return (byteCount + 4096 - 1) / 4096; - } - -- public boolean doesChunkExist(ChunkPos pos) { -+ public synchronized boolean doesChunkExist(ChunkPos pos) { // Paper - synchronized - int i = this.getOffset(pos); - - if (i == 0) { -@@ -395,6 +396,11 @@ public class RegionFile implements AutoCloseable { - } - - public void close() throws IOException { -+ // Paper start - Prevent regionfiles from being closed during use -+ this.fileLock.lock(); -+ synchronized (this) { -+ try { -+ // Paper end - try { - this.padToFullSector(); - } finally { -@@ -404,6 +410,10 @@ public class RegionFile implements AutoCloseable { - this.file.close(); - } - } -+ } finally { // Paper start - Prevent regionfiles from being closed during use -+ this.fileLock.unlock(); -+ } -+ } // Paper end - - } - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index fa086a19f038b929f356292b2f657929765f7b6f..f1ecc3832da094400ed9d45bfc60af10da682b4a 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -25,30 +25,98 @@ public class RegionFileStorage implements AutoCloseable { - private final Path folder; - private final boolean sync; - -- RegionFileStorage(Path directory, boolean dsync) { -+ // Paper start - cache regionfile does not exist state -+ static final int MAX_NON_EXISTING_CACHE = 1024 * 64; -+ private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(); -+ private synchronized boolean doesRegionFilePossiblyExist(long position) { -+ if (this.nonExistingRegionFiles.contains(position)) { -+ this.nonExistingRegionFiles.addAndMoveToFirst(position); -+ return false; -+ } -+ return true; -+ } -+ -+ private synchronized void createRegionFile(long position) { -+ this.nonExistingRegionFiles.remove(position); -+ } -+ -+ private synchronized void markNonExisting(long position) { -+ if (this.nonExistingRegionFiles.addAndMoveToFirst(position)) { -+ while (this.nonExistingRegionFiles.size() >= MAX_NON_EXISTING_CACHE) { -+ this.nonExistingRegionFiles.removeLastLong(); -+ } -+ } -+ } -+ -+ public synchronized boolean doesRegionFileNotExistNoIO(ChunkPos pos) { -+ long key = ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ()); -+ return !this.doesRegionFilePossiblyExist(key); -+ } -+ // Paper end - cache regionfile does not exist state -+ -+ protected RegionFileStorage(Path directory, boolean dsync) { // Paper - protected constructor - this.folder = directory; - this.sync = dsync; - } - -- private RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit -- long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); -+ // Paper start -+ public synchronized RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) { -+ return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ())); -+ } -+ -+ public synchronized boolean chunkExists(ChunkPos pos) throws IOException { -+ RegionFile regionfile = getRegionFile(pos, true); -+ -+ return regionfile != null ? regionfile.hasChunk(pos) : false; -+ } -+ -+ public synchronized RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit -+ return this.getRegionFile(chunkcoordintpair, existingOnly, false); -+ } -+ public synchronized RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly, boolean lock) throws IOException { -+ // Paper end -+ long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); final long regionPos = i; // Paper - OBFHELPER - RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i); - - if (regionfile != null) { -+ // Paper start -+ if (lock) { -+ // must be in this synchronized block -+ regionfile.fileLock.lock(); -+ } -+ // Paper end - return regionfile; - } else { -+ // Paper start - cache regionfile does not exist state -+ if (existingOnly && !this.doesRegionFilePossiblyExist(regionPos)) { -+ return null; -+ } -+ // Paper end - cache regionfile does not exist state - if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - Sanitise RegionFileCache and make configurable - ((RegionFile) this.regionCache.removeLast()).close(); - } - -- FileUtil.createDirectoriesSafe(this.folder); -+ // Paper - only create directory if not existing only - moved down - Path path = this.folder; - int j = chunkcoordintpair.getRegionX(); - Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); -- if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit -+ if (existingOnly && !java.nio.file.Files.exists(path1)) { // Paper start - cache regionfile does not exist state -+ this.markNonExisting(regionPos); -+ return null; // CraftBukkit -+ } else { -+ this.createRegionFile(regionPos); -+ } -+ // Paper end - cache regionfile does not exist state -+ FileUtil.createDirectoriesSafe(this.folder); // Paper - only create directory if not existing only - moved from above - RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync); - - this.regionCache.putAndMoveToFirst(i, regionfile1); -+ // Paper start -+ if (lock) { -+ // must be in this synchronized block -+ regionfile1.fileLock.lock(); -+ } -+ // Paper end - return regionfile1; - } - } -@@ -56,11 +124,12 @@ public class RegionFileStorage implements AutoCloseable { - @Nullable - public CompoundTag read(ChunkPos pos) throws IOException { - // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing -- RegionFile regionfile = this.getRegionFile(pos, true); -+ RegionFile regionfile = this.getRegionFile(pos, true, true); // Paper - if (regionfile == null) { - return null; - } - // CraftBukkit end -+ try { // Paper - DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos); - - CompoundTag nbttagcompound; -@@ -97,6 +166,9 @@ public class RegionFileStorage implements AutoCloseable { - } - - return nbttagcompound; -+ } finally { // Paper start -+ regionfile.fileLock.unlock(); -+ } // Paper end - } - - public void scanChunk(ChunkPos chunkPos, StreamTagVisitor scanner) throws IOException { -@@ -131,7 +203,13 @@ public class RegionFileStorage implements AutoCloseable { - } - - protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException { -- RegionFile regionfile = this.getRegionFile(pos, false); // CraftBukkit -+ // Paper start - rewrite chunk system -+ RegionFile regionfile = this.getRegionFile(pos, nbt == null, true); // CraftBukkit -+ if (nbt == null && regionfile == null) { -+ return; -+ } -+ try { // Try finally to unlock the region file -+ // Paper end - rewrite chunk system - // Paper start - Chunk save reattempt - int attempts = 0; - Exception lastException = null; -@@ -177,9 +255,14 @@ public class RegionFileStorage implements AutoCloseable { - net.minecraft.server.MinecraftServer.LOGGER.error("Failed to save chunk {}", pos, lastException); - } - // Paper end - Chunk save reattempt -+ // Paper start - rewrite chunk system -+ } finally { -+ regionfile.fileLock.unlock(); -+ } -+ // Paper end - rewrite chunk system - } - -- public void close() throws IOException { -+ public synchronized void close() throws IOException { // Paper -> synchronized - ExceptionCollector exceptionsuppressor = new ExceptionCollector<>(); - ObjectIterator objectiterator = this.regionCache.values().iterator(); - -@@ -196,7 +279,7 @@ public class RegionFileStorage implements AutoCloseable { - exceptionsuppressor.throwIfPresent(); - } - -- public void flush() throws IOException { -+ public synchronized void flush() throws IOException { // Paper - synchronize - ObjectIterator objectiterator = this.regionCache.values().iterator(); - - while (objectiterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -index 56f0e217276b01aed2f20a71f6849826285fc15b..54db563d80bbabd87a2be6f5ead92b482ac07b10 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -@@ -34,27 +34,28 @@ import net.minecraft.world.level.ChunkPos; - import net.minecraft.world.level.LevelHeightAccessor; - import org.slf4j.Logger; - --public class SectionStorage implements AutoCloseable { -+public class SectionStorage extends RegionFileStorage implements AutoCloseable { // Paper - nuke IOWorker - private static final Logger LOGGER = LogUtils.getLogger(); - private static final String SECTIONS_TAG = "Sections"; -- private final IOWorker worker; -+ // Paper - remove mojang I/O thread - private final Long2ObjectMap> storage = new Long2ObjectOpenHashMap<>(); - private final LongLinkedOpenHashSet dirty = new LongLinkedOpenHashSet(); - private final Function> codec; - private final Function factory; - private final DataFixer fixerUpper; - private final DataFixTypes type; -- private final RegistryAccess registryAccess; -+ public final RegistryAccess registryAccess; // Paper - rewrite chunk system - protected final LevelHeightAccessor levelHeightAccessor; - - public SectionStorage(Path path, Function> codecFactory, Function factory, DataFixer dataFixer, DataFixTypes dataFixTypes, boolean dsync, RegistryAccess dynamicRegistryManager, LevelHeightAccessor world) { -+ super(path, dsync); // Paper - remove mojang I/O thread - this.codec = codecFactory; - this.factory = factory; - this.fixerUpper = dataFixer; - this.type = dataFixTypes; - this.registryAccess = dynamicRegistryManager; - this.levelHeightAccessor = world; -- this.worker = new IOWorker(path, dsync, path.getFileName().toString()); -+ // Paper - remove mojang I/O thread - } - - protected void tick(BooleanSupplier shouldKeepTicking) { -@@ -116,23 +117,21 @@ public class SectionStorage implements AutoCloseable { - } - - private void readColumn(ChunkPos pos) { -- Optional optional = this.tryRead(pos).join(); -- RegistryOps registryOps = RegistryOps.create(NbtOps.INSTANCE, this.registryAccess); -- this.readColumn(pos, registryOps, optional.orElse((CompoundTag)null)); -+ throw new IllegalStateException("Only chunk system can load in state, offending class:" + this.getClass().getName()); // Paper - rewrite chunk system - } - - private CompletableFuture> tryRead(ChunkPos pos) { -- return this.worker.loadAsync(pos).exceptionally((throwable) -> { -- if (throwable instanceof IOException iOException) { -- LOGGER.error("Error reading chunk {} data from disk", pos, iOException); -- return Optional.empty(); -- } else { -- throw new CompletionException(throwable); -- } -- }); -+ // Paper start - rewrite chunk system -+ try { -+ return CompletableFuture.completedFuture(Optional.ofNullable(this.read(pos))); -+ } catch (Throwable thr) { -+ return CompletableFuture.failedFuture(thr); -+ } -+ // Paper end - rewrite chunk system - } - - private void readColumn(ChunkPos pos, DynamicOps ops, @Nullable T data) { -+ if (true) throw new IllegalStateException("Only chunk system can load in state, offending class:" + this.getClass().getName()); // Paper - rewrite chunk system - if (data == null) { - for(int i = this.levelHeightAccessor.getMinSection(); i < this.levelHeightAccessor.getMaxSection(); ++i) { - this.storage.put(getKey(pos, i), Optional.empty()); -@@ -177,7 +176,7 @@ public class SectionStorage implements AutoCloseable { - Dynamic dynamic = this.writeColumn(pos, registryOps); - Tag tag = dynamic.getValue(); - if (tag instanceof CompoundTag) { -- this.worker.store(pos, (CompoundTag)tag); -+ try { this.write(pos, (CompoundTag)tag); } catch (IOException ioexception) { SectionStorage.LOGGER.error("Error writing data to disk", ioexception); } // Paper - nuke IOWorker - } else { - LOGGER.error("Expected compound tag, got {}", (Object)tag); - } -@@ -222,7 +221,7 @@ public class SectionStorage implements AutoCloseable { - } - - private static int getVersion(Dynamic dynamic) { -- return dynamic.get("DataVersion").asInt(1945); -+ return dynamic.get("DataVersion").asInt(1945); // Paper - diff on change, constant used in ChunkLoadTask - } - - public void flush(ChunkPos pos) { -@@ -240,6 +239,9 @@ public class SectionStorage implements AutoCloseable { - - @Override - public void close() throws IOException { -- this.worker.close(); -+ //this.worker.close(); // Paper - nuke I/O worker - don't call the worker -+ super.close(); // Paper - nuke I/O worker - call super.close method which is responsible for closing used files. - } -+ -+ // Paper - rewrite chunk system - } -diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -index 2830d32bba3dc85847e3a5d9b4d98f822e34b606..4cdfc433df67afcd455422e9baf56f167dd712ae 100644 ---- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -@@ -8,54 +8,42 @@ import javax.annotation.Nullable; - import net.minecraft.world.entity.Entity; - - public class EntityTickList { -- private Int2ObjectMap active = new Int2ObjectLinkedOpenHashMap<>(); -- private Int2ObjectMap passive = new Int2ObjectLinkedOpenHashMap<>(); -- @Nullable -- private Int2ObjectMap iterated; -+ private final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking? - - private void ensureActiveIsNotIterated() { -- if (this.iterated == this.active) { -- this.passive.clear(); -- -- for(Int2ObjectMap.Entry entry : Int2ObjectMaps.fastIterable(this.active)) { -- this.passive.put(entry.getIntKey(), entry.getValue()); -- } -- -- Int2ObjectMap int2ObjectMap = this.active; -- this.active = this.passive; -- this.passive = int2ObjectMap; -- } -+ // Paper - replace with better logic, do not delay removals - - } - - public void add(Entity entity) { -+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper - this.ensureActiveIsNotIterated(); -- this.active.put(entity.getId(), entity); -+ this.entities.add(entity); // Paper - replace with better logic, do not delay removals/additions - } - - public void remove(Entity entity) { -+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist removal"); // Paper - this.ensureActiveIsNotIterated(); -- this.active.remove(entity.getId()); -+ this.entities.remove(entity); // Paper - replace with better logic, do not delay removals/additions - } - - public boolean contains(Entity entity) { -- return this.active.containsKey(entity.getId()); -+ return this.entities.contains(entity); // Paper - replace with better logic, do not delay removals/additions - } - - public void forEach(Consumer action) { -- if (this.iterated != null) { -- throw new UnsupportedOperationException("Only one concurrent iteration supported"); -- } else { -- this.iterated = this.active; -- -- try { -- for(Entity entity : this.active.values()) { -- action.accept(entity); -- } -- } finally { -- this.iterated = null; -+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist iteration"); // Paper -+ // Paper start - replace with better logic, do not delay removals/additions -+ // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... -+ // (by dfl iterator() is configured to not iterate over new entries) -+ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.entities.iterator(); -+ try { -+ while (iterator.hasNext()) { -+ action.accept(iterator.next()); - } -- -+ } finally { -+ iterator.finishedIterating(); - } -+ // Paper end - replace with better logic, do not delay removals/additions - } - } -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 54308f1decc3982f30bf8b7a8a9d8865bfdbb9fd..902156477bdfc9917105f1229f760c26e5af302a 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -@@ -87,7 +87,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName("init_biomes", () -> { - this.doCreateBiomes(blender, noiseConfig, structureAccessor, chunk); - return chunk; -- }), Util.backgroundExecutor()); -+ }), executor); // Paper - run with supplied executor - } - - private void doCreateBiomes(Blender blender, RandomState noiseConfig, StructureManager structureAccessor, ChunkAccess chunk) { -@@ -286,7 +286,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - - return CompletableFuture.supplyAsync(Util.wrapThreadWithTaskName("wgen_fill_noise", () -> { - return this.doFill(blender, structureAccessor, noiseConfig, chunk, j, k); -- }), Util.backgroundExecutor()).whenCompleteAsync((ichunkaccess1, throwable) -> { -+ }), executor).whenCompleteAsync((ichunkaccess1, throwable) -> { // Paper - run with supplied executor - Iterator iterator = set.iterator(); - - while (iterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -index 1ca00340aaa201dd34e5c350d23ef53e126a0ca6..16356d7f388561300e794a52f3f263b8e7d9b880 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -@@ -50,8 +50,101 @@ public class StructureCheck { - private final BiomeSource biomeSource; - private final long seed; - private final DataFixer fixerUpper; -- private final Long2ObjectMap> loadedChunks = new Long2ObjectOpenHashMap<>(); -- private final Map featureChecks = new HashMap<>(); -+ // Paper start - rewrite chunk system - synchronise this class -+ // additionally, make sure to purge entries from the maps so it does not leak memory -+ private static final int CHUNK_TOTAL_LIMIT = 50 * (2 * 100 + 1) * (2 * 100 + 1); // cache 50 structure lookups -+ private static final int PER_FEATURE_CHECK_LIMIT = 50 * (2 * 100 + 1) * (2 * 100 + 1); // cache 50 structure lookups -+ -+ private final SynchronisedLong2ObjectMap> loadedChunksSafe = new SynchronisedLong2ObjectMap<>(CHUNK_TOTAL_LIMIT); -+ private final java.util.concurrent.ConcurrentHashMap featureChecksSafe = new java.util.concurrent.ConcurrentHashMap<>(); -+ -+ private static final class SynchronisedLong2ObjectMap { -+ private final it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap map = new it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<>(); -+ private final int limit; -+ -+ public SynchronisedLong2ObjectMap(final int limit) { -+ this.limit = limit; -+ } -+ -+ // must hold lock on map -+ private void purgeEntries() { -+ while (this.map.size() > this.limit) { -+ this.map.removeLast(); -+ } -+ } -+ -+ public V get(final long key) { -+ synchronized (this.map) { -+ return this.map.getAndMoveToFirst(key); -+ } -+ } -+ -+ public V put(final long key, final V value) { -+ synchronized (this.map) { -+ final V ret = this.map.putAndMoveToFirst(key, value); -+ this.purgeEntries(); -+ return ret; -+ } -+ } -+ -+ public V compute(final long key, final java.util.function.BiFunction remappingFunction) { -+ synchronized (this.map) { -+ // first, compute the value - if one is added, it will be at the last entry -+ this.map.compute(key, remappingFunction); -+ // move the entry to first, just in case it was added at last -+ final V ret = this.map.getAndMoveToFirst(key); -+ // now purge the last entries -+ this.purgeEntries(); -+ -+ return ret; -+ } -+ } -+ } -+ -+ private static final class SynchronisedLong2BooleanMap { -+ private final it.unimi.dsi.fastutil.longs.Long2BooleanLinkedOpenHashMap map = new it.unimi.dsi.fastutil.longs.Long2BooleanLinkedOpenHashMap(); -+ private final int limit; -+ -+ public SynchronisedLong2BooleanMap(final int limit) { -+ this.limit = limit; -+ } -+ -+ // must hold lock on map -+ private void purgeEntries() { -+ while (this.map.size() > this.limit) { -+ this.map.removeLastBoolean(); -+ } -+ } -+ -+ public boolean remove(final long key) { -+ synchronized (this.map) { -+ return this.map.remove(key); -+ } -+ } -+ -+ // note: -+ public boolean getOrCompute(final long key, final it.unimi.dsi.fastutil.longs.Long2BooleanFunction ifAbsent) { -+ synchronized (this.map) { -+ if (this.map.containsKey(key)) { -+ return this.map.getAndMoveToFirst(key); -+ } -+ } -+ -+ final boolean put = ifAbsent.get(key); -+ -+ synchronized (this.map) { -+ if (this.map.containsKey(key)) { -+ return this.map.getAndMoveToFirst(key); -+ } -+ this.map.putAndMoveToFirst(key, put); -+ -+ this.purgeEntries(); -+ -+ return put; -+ } -+ } -+ } -+ // Paper end - rewrite chunk system - synchronise this class - - public StructureCheck(ChunkScanAccess chunkIoWorker, RegistryAccess registryManager, StructureTemplateManager structureTemplateManager, ResourceKey worldKey, ChunkGenerator chunkGenerator, RandomState noiseConfig, LevelHeightAccessor world, BiomeSource biomeSource, long seed, DataFixer dataFixer) { // Paper - fix missing CB diff - this.storageAccess = chunkIoWorker; -@@ -70,7 +163,7 @@ public class StructureCheck { - - public StructureCheckResult checkStart(ChunkPos pos, Structure type, boolean skipReferencedStructures) { - long l = pos.toLong(); -- Object2IntMap object2IntMap = this.loadedChunks.get(l); -+ Object2IntMap object2IntMap = this.loadedChunksSafe.get(l); // Paper - rewrite chunk system - synchronise this class - if (object2IntMap != null) { - return this.checkStructureInfo(object2IntMap, type, skipReferencedStructures); - } else { -@@ -78,9 +171,9 @@ public class StructureCheck { - if (structureCheckResult != null) { - return structureCheckResult; - } else { -- boolean bl = this.featureChecks.computeIfAbsent(type, (structure2) -> { -- return new Long2BooleanOpenHashMap(); -- }).computeIfAbsent(l, (chunkPos) -> { -+ boolean bl = this.featureChecksSafe.computeIfAbsent(type, (structure2) -> { // Paper - rewrite chunk system - synchronise this class -+ return new SynchronisedLong2BooleanMap(PER_FEATURE_CHECK_LIMIT); // Paper - rewrite chunk system - synchronise this class -+ }).getOrCompute(l, (chunkPos) -> { // Paper - rewrite chunk system - synchronise this class - return this.canCreateStructure(pos, type); - }); - return !bl ? StructureCheckResult.START_NOT_PRESENT : StructureCheckResult.CHUNK_LOAD_NEEDED; -@@ -193,17 +286,26 @@ public class StructureCheck { - } - - private void storeFullResults(long pos, Object2IntMap referencesByStructure) { -- this.loadedChunks.put(pos, deduplicateEmptyMap(referencesByStructure)); -- this.featureChecks.values().forEach((generationPossibilityByChunkPos) -> { -- generationPossibilityByChunkPos.remove(pos); -- }); -+ // Paper start - rewrite chunk system - synchronise this class -+ this.loadedChunksSafe.put(pos, deduplicateEmptyMap(referencesByStructure)); -+ // once we insert into loadedChunks, we don't really need to be very careful about removing everything -+ // from this map, as everything that checks this map uses loadedChunks first -+ // so, one way or another it's a race condition that doesn't matter -+ for (SynchronisedLong2BooleanMap value : this.featureChecksSafe.values()) { -+ value.remove(pos); -+ } -+ // Paper end - rewrite chunk system - synchronise this class - } - - public void incrementReference(ChunkPos pos, Structure structure) { -- this.loadedChunks.compute(pos.toLong(), (posx, referencesByStructure) -> { -- if (referencesByStructure == null || referencesByStructure.isEmpty()) { -+ this.loadedChunksSafe.compute(pos.toLong(), (posx, referencesByStructure) -> { // Paper start - rewrite chunk system - synchronise this class -+ // make this COW so that we do not mutate state that may be currently in use -+ if (referencesByStructure == null) { - referencesByStructure = new Object2IntOpenHashMap<>(); -+ } else { -+ referencesByStructure = referencesByStructure instanceof Object2IntOpenHashMap fastClone ? fastClone.clone() : new Object2IntOpenHashMap<>(referencesByStructure); - } -+ // Paper end - rewrite chunk system - synchronise this class - - referencesByStructure.computeInt(structure, (feature, references) -> { - return references == null ? 1 : references + 1; -diff --git a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java -index 9f6c2e5b5d9e8d714a47c770e255d06c0ef7c190..ac807277a6b26d140ea9873d17c7aa4fb5fe37b2 100644 ---- a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java -+++ b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java -@@ -25,6 +25,19 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon - @Nullable - private BiConsumer, ScheduledTick> onTickAdded; - -+ // Paper start - add dirty flag -+ private boolean dirty; -+ private long lastSaved = Long.MIN_VALUE; -+ -+ public boolean isDirty(final long tick) { -+ return this.dirty || (!this.tickQueue.isEmpty() && tick != this.lastSaved); -+ } -+ -+ public void clearDirty() { -+ this.dirty = false; -+ } -+ // Paper end - add dirty flag -+ - public LevelChunkTicks() { - } - -@@ -50,6 +63,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon - public ScheduledTick poll() { - ScheduledTick scheduledTick = this.tickQueue.poll(); - if (scheduledTick != null) { -+ this.dirty = true; // Paper - add dirty flag - this.ticksPerPosition.remove(scheduledTick); - } - -@@ -59,6 +73,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon - @Override - public void schedule(ScheduledTick orderedTick) { - if (this.ticksPerPosition.add(orderedTick)) { -+ this.dirty = true; // Paper - add dirty flag - this.scheduleUnchecked(orderedTick); - } - -@@ -83,7 +98,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon - while(iterator.hasNext()) { - ScheduledTick scheduledTick = iterator.next(); - if (predicate.test(scheduledTick)) { -- iterator.remove(); -+ iterator.remove(); this.dirty = true; // Paper - add dirty flag - this.ticksPerPosition.remove(scheduledTick); - } - } -@@ -101,6 +116,7 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon - - @Override - public ListTag save(long l, Function function) { -+ this.lastSaved = l; // Paper - add dirty system to level ticks - ListTag listTag = new ListTag(); - if (this.pendingTicks != null) { - for(SavedTick savedTick : this.pendingTicks) { -@@ -117,6 +133,11 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon - - public void unpack(long time) { - if (this.pendingTicks != null) { -+ // Paper start - add dirty system to level chunk ticks -+ if (this.tickQueue.isEmpty()) { -+ this.lastSaved = time; -+ } -+ // Paper end - add dirty system to level chunk ticks - int i = -this.pendingTicks.size(); - - for(SavedTick savedTick : this.pendingTicks) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index 1eff5e4800ad3b628a42113fb3ba67458e56a40d..d4e0ef75dd12709a0dcf9193821c30b8943e6c36 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -113,7 +113,7 @@ public class CraftChunk implements Chunk { - - @Override - public boolean isEntitiesLoaded() { -- return this.getCraftWorld().getHandle().entityManager.areEntitiesLoaded(ChunkPos.asLong(this.x, this.z)); -+ return this.getCraftWorld().getHandle().areEntitiesLoaded(io.papermc.paper.util.CoordinateUtils.getChunkKey(this.x, this.z)); // Paper - rewrite chunk system - } - - @Override -@@ -122,51 +122,7 @@ public class CraftChunk implements Chunk { - this.getWorld().getChunkAt(this.x, this.z); // Transient load for this tick - } - -- PersistentEntitySectionManager entityManager = this.getCraftWorld().getHandle().entityManager; -- long pair = ChunkPos.asLong(this.x, this.z); -- -- if (entityManager.areEntitiesLoaded(pair)) { -- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream() -- .map(net.minecraft.world.entity.Entity::getBukkitEntity) -- .filter(Objects::nonNull).toArray(Entity[]::new); -- } -- -- entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading -- -- // SPIGOT-6772: Use entity mailbox and re-schedule entities if they get unloaded -- ProcessorMailbox mailbox = ((EntityStorage) entityManager.permanentStorage).entityDeserializerQueue; -- BooleanSupplier supplier = () -> { -- // only execute inbox if our entities are not present -- if (entityManager.areEntitiesLoaded(pair)) { -- return true; -- } -- -- if (!entityManager.isPending(pair)) { -- // Our entities got unloaded, this should normally not happen. -- entityManager.ensureChunkQueuedForLoad(pair); // Re-start entity loading -- } -- -- // tick loading inbox, which loads the created entities to the world -- // (if present) -- entityManager.tick(); -- // check if our entities are loaded -- return entityManager.areEntitiesLoaded(pair); -- }; -- -- // now we wait until the entities are loaded, -- // the converting from NBT to entity object is done on the main Thread which is why we wait -- while (!supplier.getAsBoolean()) { -- if (mailbox.size() != 0) { -- mailbox.run(); -- } else { -- Thread.yield(); -- LockSupport.parkNanos("waiting for entity loading", 100000L); -- } -- } -- -- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream() -- .map(net.minecraft.world.entity.Entity::getBukkitEntity) -- .filter(Objects::nonNull).toArray(Entity[]::new); -+ return this.getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - rewrite chunk system - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index f401aa456e1b03230cdd5d2e109909f7b43f3774..3ea2cee19e6fe1b94e39ef535102d41ec2c0bdad 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1384,7 +1384,6 @@ public final class CraftServer implements Server { - - internal.keepSpawnInMemory = creator.keepSpawnLoaded().toBooleanOrElse(internal.getWorld().getKeepSpawnInMemory()); // Paper - this.getServer().prepareLevels(internal.getChunkSource().chunkMap.progressListener, internal); -- internal.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API - - this.pluginManager.callEvent(new WorldLoadEvent(internal.getWorld())); - return internal.getWorld(); -@@ -1429,7 +1428,7 @@ public final class CraftServer implements Server { - } - - handle.getChunkSource().close(save); -- handle.entityManager.close(save); // SPIGOT-6722: close entityManager -+ // handle.entityManager.close(save); // SPIGOT-6722: close entityManager // Paper - rewrite chunk system - handle.convertable.close(); - } catch (Exception ex) { - this.getLogger().log(Level.SEVERE, null, ex); -@@ -2444,7 +2443,7 @@ public final class CraftServer implements Server { - - @Override - public boolean isPrimaryThread() { -- return Thread.currentThread().equals(this.console.serverThread) || this.console.hasStopped() || !org.spigotmc.AsyncCatcher.enabled; // All bets are off if we have shut down (e.g. due to watchdog) -+ return io.papermc.paper.util.TickThread.isTickThread(); // Paper - rewrite chunk system - } - - // Paper start - Adventure -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 5cc2deb8f170452c7049743068bf281f67687db9..84ec87b889d0d450293310e18c34aef505a8147b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -501,10 +501,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { - ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); - if (playerChunk == null) return false; - -- playerChunk.getTickingChunkFuture().thenAccept(either -> { -- either.left().ifPresent(chunk -> { -+ // Paper start - rewrite player chunk loader -+ net.minecraft.world.level.chunk.LevelChunk chunk = playerChunk.getSendingChunk(); -+ if (chunk == null) { -+ return false; -+ } -+ // Paper end - rewrite player chunk loader - List playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false); -- if (playersInRange.isEmpty()) return; -+ if (playersInRange.isEmpty()) return true; // Paper - rewrite player chunk loader - - ClientboundLevelChunkWithLightPacket refreshPacket = new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null); - for (ServerPlayer player : playersInRange) { -@@ -512,8 +516,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - player.connection.send(refreshPacket); - } -- }); -- }); -+ // Paper - rewrite player chunk loader - - return true; - } -@@ -592,20 +595,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @Override - public Collection getPluginChunkTickets(int x, int z) { - DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager; -- SortedArraySet> tickets = chunkDistanceManager.tickets.get(ChunkPos.asLong(x, z)); -- -- if (tickets == null) { -- return Collections.emptyList(); -- } -- -- ImmutableList.Builder ret = ImmutableList.builder(); -- for (Ticket ticket : tickets) { -- if (ticket.getType() == TicketType.PLUGIN_TICKET) { -- ret.add((Plugin) ticket.key); -- } -- } -- -- return ret.build(); -+ return chunkDistanceManager.getChunkHolderManager().getPluginChunkTickets(x, z); // Paper - rewrite chunk system - } - - @Override -@@ -613,7 +603,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - Map> ret = new HashMap<>(); - DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager; - -- for (Long2ObjectMap.Entry>> chunkTickets : chunkDistanceManager.tickets.long2ObjectEntrySet()) { -+ for (Long2ObjectMap.Entry>> chunkTickets : chunkDistanceManager.getChunkHolderManager().getTicketsCopy().long2ObjectEntrySet()) { // Paper - rewrite chunk system - long chunkKey = chunkTickets.getLongKey(); - SortedArraySet> tickets = chunkTickets.getValue(); - -@@ -1275,12 +1265,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public int getViewDistance() { -- return this.world.getChunkSource().chunkMap.serverViewDistance; -+ return this.getHandle().playerChunkLoader.getAPIViewDistance(); // Paper - replace player chunk loader - } - - @Override - public int getSimulationDistance() { -- return this.world.getChunkSource().chunkMap.getDistanceManager().simulationDistance; -+ return this.getHandle().playerChunkLoader.getAPITickDistance(); // Paper - replace player chunk loader - } - - public BlockMetadataStore getBlockMetadata() { -@@ -2422,17 +2412,20 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void setSimulationDistance(final int simulationDistance) { -- throw new UnsupportedOperationException("Not implemented yet"); -+ if (simulationDistance < 2 || simulationDistance > 32) { -+ throw new IllegalArgumentException("Simulation distance " + simulationDistance + " is out of range of [2, 32]"); -+ } -+ this.getHandle().chunkSource.chunkMap.setTickViewDistance(simulationDistance); - } - - @Override - public int getSendViewDistance() { -- return this.getViewDistance(); -+ return this.getHandle().playerChunkLoader.getAPISendViewDistance(); // Paper - replace player chunk loader - } - - @Override - public void setSendViewDistance(final int viewDistance) { -- throw new UnsupportedOperationException("Not implemented yet"); -+ this.getHandle().chunkSource.chunkMap.setSendViewDistance(viewDistance); // Paper - replace player chunk loader - } - - // Paper start - implement pointers -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index a85f7f56bdabe246ce47ab83f4000bd779cf5c3b..39b25c2478eadd373383a3445a7f27ea30d18550 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -3349,31 +3349,31 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public int getViewDistance() { -- return io.papermc.paper.chunk.system.ChunkSystem.getLoadViewDistance(this.getHandle()); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPIViewDistance(this); - } - - @Override - public void setViewDistance(final int viewDistance) { -- throw new UnsupportedOperationException("Not implemented yet"); -+ this.getHandle().setLoadViewDistance(viewDistance < 0 ? viewDistance : viewDistance + 1); - } - - @Override - public int getSimulationDistance() { -- return io.papermc.paper.chunk.system.ChunkSystem.getTickViewDistance(this.getHandle()); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPITickViewDistance(this); - } - - @Override - public void setSimulationDistance(final int simulationDistance) { -- throw new UnsupportedOperationException("Not implemented yet"); -+ this.getHandle().setTickViewDistance(simulationDistance); - } - - @Override - public int getSendViewDistance() { -- return io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(this.getHandle()); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPISendViewDistance(this); - } - - @Override - public void setSendViewDistance(final int viewDistance) { -- throw new UnsupportedOperationException("Not implemented yet"); -+ this.getHandle().setSendViewDistance(viewDistance); - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java -index f6d003ea707f43287e52f8ffad24be35eeefec69..c6e5d3b7ef3886d0ffa9302d1270c048eaaeb671 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java -@@ -264,7 +264,7 @@ public class CustomChunkGenerator extends InternalChunkGenerator { - return ichunkaccess1; - }; - -- return future == null ? CompletableFuture.supplyAsync(() -> function.apply(chunk), net.minecraft.Util.backgroundExecutor()) : future.thenApply(function); -+ return future == null ? CompletableFuture.supplyAsync(() -> function.apply(chunk), executor) : future.thenApply(function); // Paper - run with supplied executor - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -index 7ed861cd67889e525ab4987c0afed245aca08833..86a20c91beff6b27e6ec886e49ba902b216106f2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -@@ -826,19 +826,39 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { - @Nullable - @Override - public BlockState getBlockStateIfLoaded(final BlockPos blockposition) { -- return null; -+ return this.handle.getBlockStateIfLoaded(blockposition); - } - - @Nullable - @Override - public FluidState getFluidIfLoaded(final BlockPos blockposition) { -- return null; -+ return this.handle.getFluidIfLoaded(blockposition); - } - - @Nullable - @Override - public ChunkAccess getChunkIfLoadedImmediately(final int x, final int z) { -- return null; -+ return this.handle.getChunkIfLoadedImmediately(x, z); -+ } -+ -+ @Override -+ public void getHardCollidingEntities(final Entity except, final AABB box, final Predicate predicate, final List into) { -+ this.handle.getHardCollidingEntities(except, box, predicate, into); -+ } -+ -+ @Override -+ public List getHardCollidingEntities(final Entity except, final AABB box, final Predicate predicate) { -+ return this.handle.getHardCollidingEntities(except, box, predicate); -+ } -+ -+ @Override -+ public void getEntities(final Entity except, final AABB box, final Predicate predicate, final List into) { -+ this.handle.getEntities(except, box, predicate, into); -+ } -+ -+ @Override -+ public void getEntitiesByClass(final Class clazz, final Entity except, final AABB box, final List into, final Predicate predicate) { -+ this.handle.getEntitiesByClass(clazz, except, box, into, predicate); - } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -index 7956002e2d4d583c27e277562312d27ea6871557..819a67aa19c6bd624f5ed28d09b35ff2c151749a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -@@ -268,4 +268,19 @@ public class DummyGeneratorAccess implements WorldGenLevel { - @Override - public void scheduleTick(BlockPos pos, Fluid fluid, int delay, net.minecraft.world.ticks.TickPriority priority) {} - // Paper end - add more methods -+ // Paper start -+ @Override -+ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { -+ return java.util.Collections.emptyList(); -+ } -+ -+ @Override -+ public void getEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} -+ // Paper end - } -diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java -index e8e3cc48cf1c58bd8151d1f28df28781859cd0e3..2e074c16dab1ead47914070329da0398c3274048 100644 ---- a/src/main/java/org/spigotmc/AsyncCatcher.java -+++ b/src/main/java/org/spigotmc/AsyncCatcher.java -@@ -9,7 +9,7 @@ public class AsyncCatcher - - public static void catchOp(String reason) - { -- if ( (AsyncCatcher.enabled || io.papermc.paper.util.TickThread.STRICT_THREAD_CHECKS) && Thread.currentThread() != MinecraftServer.getServer().serverThread ) // Paper -+ if (!(io.papermc.paper.util.TickThread.isTickThread())) // Paper - { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper - throw new IllegalStateException( "Asynchronous " + reason + "!" ); -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index b1ac7338fa632611ea8332044b09070f78f8f5f1..a284d3b8526a743ba4389ec5b44d80af6d0e5a5f 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -8,7 +8,7 @@ import java.util.logging.Logger; - import net.minecraft.server.MinecraftServer; - import org.bukkit.Bukkit; - --public class WatchdogThread extends Thread -+public final class WatchdogThread extends io.papermc.paper.util.TickThread // Paper - rewrite chunk system - { - - private static WatchdogThread instance; -@@ -115,6 +115,7 @@ public class WatchdogThread extends Thread - // Paper end - Different message for short timeout - log.log( Level.SEVERE, "------------------------------" ); - log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper -+ io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(isLongTimeout); // Paper - rewrite chunk system - WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); - log.log( Level.SEVERE, "------------------------------" ); - // diff --git a/patches/server/0992-Fix-World-isChunkGenerated-calls.patch b/patches/server/0992-Fix-World-isChunkGenerated-calls.patch new file mode 100644 index 000000000000..1cec6fc43bf2 --- /dev/null +++ b/patches/server/0992-Fix-World-isChunkGenerated-calls.patch @@ -0,0 +1,244 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 15 Jun 2019 08:54:33 -0700 +Subject: [PATCH] Fix World#isChunkGenerated calls + +Optimize World#loadChunk() too +This patch also adds a chunk status cache on region files (note that +its only purpose is to cache the status on DISK) + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 5c1accb75655eadd4858ee24cdcdf9b200fbbcb2..42dde36273030494a6e7ff19e55d3b6a7da06fee 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -717,9 +717,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper end + + private CompletableFuture> readChunk(ChunkPos chunkPos) { +- return this.read(chunkPos).thenApplyAsync((optional) -> { +- return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit +- }, Util.backgroundExecutor()); ++ // Paper start - Cache chunk status on disk ++ try { ++ return CompletableFuture.completedFuture(Optional.ofNullable(this.readConvertChunkSync(chunkPos))); ++ } catch (Throwable thr) { ++ return CompletableFuture.failedFuture(thr); ++ } ++ // Paper end - Cache chunk status on disk + } + + // CraftBukkit start +@@ -728,6 +732,60 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // CraftBukkit end + } + ++ // Paper start - Cache chunk status on disk ++ @Nullable ++ public CompoundTag readConvertChunkSync(ChunkPos pos) throws IOException { ++ CompoundTag nbttagcompound = this.readSync(pos); ++ if (nbttagcompound == null) { ++ return null; ++ } ++ ++ nbttagcompound = this.upgradeChunkTag(nbttagcompound, pos); // CraftBukkit ++ if (nbttagcompound == null) { ++ return null; ++ } ++ ++ this.updateChunkStatusOnDisk(pos, nbttagcompound); ++ ++ return nbttagcompound; ++ } ++ ++ public ChunkStatus getChunkStatusOnDiskIfCached(ChunkPos chunkPos) { ++ net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFileIfLoaded(chunkPos); ++ ++ return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ } ++ ++ public ChunkStatus getChunkStatusOnDisk(ChunkPos chunkPos) throws IOException { ++ net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, true); ++ ++ if (regionFile == null || !regionFileCache.chunkExists(chunkPos)) { ++ return null; ++ } ++ ++ ChunkStatus status = regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ ++ if (status != null) { ++ return status; ++ } ++ ++ this.readChunk(chunkPos); ++ ++ return regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ } ++ ++ public void updateChunkStatusOnDisk(ChunkPos chunkPos, @Nullable CompoundTag compound) throws IOException { ++ net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, false); ++ ++ regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkSerializer.getStatus(compound)); ++ } ++ ++ public ChunkAccess getUnloadingChunk(int chunkX, int chunkZ) { ++ ChunkHolder chunkHolder = io.papermc.paper.chunk.system.ChunkSystem.getUnloadingChunkHolder(this.level, chunkX, chunkZ); ++ return chunkHolder == null ? null : chunkHolder.getAvailableChunkNow(); ++ } ++ // Paper end - Cache chunk status on disk ++ + public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { // Paper - public + // Spigot start + return this.anyPlayerCloseEnoughForSpawning(pos, false); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +index 6c89b92cac521808873e9e1eccc363695275cd7a..92ba75254f6ffca40abd5485dbb4789de59edebd 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -50,6 +50,30 @@ public class RegionFile implements AutoCloseable { + public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // Paper + public final Path regionFile; // Paper + ++ // Paper start - Cache chunk status ++ private final net.minecraft.world.level.chunk.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.ChunkStatus[32 * 32]; ++ ++ private boolean closed; ++ ++ // invoked on write/read ++ public void setStatus(int x, int z, net.minecraft.world.level.chunk.ChunkStatus status) { ++ if (this.closed) { ++ // We've used an invalid region file. ++ throw new IllegalStateException("RegionFile is closed"); ++ } ++ this.statuses[getChunkLocation(x, z)] = status; ++ } ++ ++ public net.minecraft.world.level.chunk.ChunkStatus getStatusIfCached(int x, int z) { ++ if (this.closed) { ++ // We've used an invalid region file. ++ throw new IllegalStateException("RegionFile is closed"); ++ } ++ final int location = getChunkLocation(x, z); ++ return this.statuses[location]; ++ } ++ // Paper end - Cache chunk status ++ + public RegionFile(Path file, Path directory, boolean dsync) throws IOException { + this(file, directory, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format + } +@@ -397,6 +421,7 @@ public class RegionFile implements AutoCloseable { + return this.getOffset(pos) != 0; + } + ++ private static int getChunkLocation(int x, int z) { return (x & 31) + (z & 31) * 32; } // Paper - Cache chunk status; OBFHELPER - sort of, mirror of logic below + private static int getOffsetIndex(ChunkPos pos) { + return pos.getRegionLocalX() + pos.getRegionLocalZ() * 32; + } +@@ -407,6 +432,7 @@ public class RegionFile implements AutoCloseable { + synchronized (this) { + try { + // Paper end ++ this.closed = true; // Paper - Cache chunk status + try { + this.padToFullSector(); + } finally { +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index f27cf743bbc379520263909541d653dd38d1be58..0db8ee3b640e6d1268e9c1cccda85459bd447105 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -266,6 +266,7 @@ public class RegionFileStorage implements AutoCloseable { + + try { + NbtIo.write(nbt, (DataOutput) dataoutputstream); ++ regionfile.setStatus(pos.x, pos.z, ChunkSerializer.getStatus(nbt)); // Paper - Cache chunk status + regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone + // Paper start - don't write garbage data to disk if writing serialization fails + dataoutputstream.close(); // Only write if successful +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 726aa0461bbf380989a5b51dbfdfdda259b8632b..49d8fd363391d085067253504c146c6fcf297d8f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -382,9 +382,23 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean isChunkGenerated(int x, int z) { ++ // Paper start - Fix this method ++ if (!Bukkit.isPrimaryThread()) { ++ return java.util.concurrent.CompletableFuture.supplyAsync(() -> { ++ return CraftWorld.this.isChunkGenerated(x, z); ++ }, world.getChunkSource().mainThreadProcessor).join(); ++ } ++ ChunkAccess chunk = world.getChunkSource().getChunkAtImmediately(x, z); ++ if (chunk == null) { ++ chunk = world.getChunkSource().chunkMap.getUnloadingChunk(x, z); ++ } ++ if (chunk != null) { ++ return chunk instanceof ImposterProtoChunk || chunk instanceof net.minecraft.world.level.chunk.LevelChunk; ++ } + try { +- return this.isChunkLoaded(x, z) || this.world.getChunkSource().chunkMap.read(new ChunkPos(x, z)).get().isPresent(); +- } catch (InterruptedException | ExecutionException ex) { ++ return world.getChunkSource().chunkMap.getChunkStatusOnDisk(new ChunkPos(x, z)) == ChunkStatus.FULL; ++ } catch (java.io.IOException ex) { ++ // Paper end - Fix this method + throw new RuntimeException(ex); + } + } +@@ -536,20 +550,48 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public boolean loadChunk(int x, int z, boolean generate) { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot + warnUnsafeChunk("loading a faraway chunk", x, z); // Paper +- ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper +- +- // If generate = false, but the chunk already exists, we will get this back. +- if (chunk instanceof ImposterProtoChunk) { +- // We then cycle through again to get the full chunk immediately, rather than after the ticket addition +- chunk = this.world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true); +- } +- +- if (chunk instanceof net.minecraft.world.level.chunk.LevelChunk) { +- this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper ++ // Paper start - Optimize this method ++ ChunkPos chunkPos = new ChunkPos(x, z); ++ ChunkAccess immediate = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); ++ if (immediate != null) { ++ // Plugins should use plugin tickets instead of this method to keep a chunk perpetually loaded + return true; + } + +- return false; ++ if (!generate) { ++ immediate = world.getChunkSource().chunkMap.getUnloadingChunk(x, z); ++ if (immediate != null) { ++ if (!(immediate instanceof ImposterProtoChunk) && !(immediate instanceof net.minecraft.world.level.chunk.LevelChunk)) { ++ return false; // not full status ++ } ++ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper ++ world.getChunk(x, z); // make sure we're at ticket level 32 or lower ++ return true; ++ } ++ net.minecraft.world.level.chunk.storage.RegionFile file; ++ try { ++ file = world.getChunkSource().chunkMap.regionFileCache.getRegionFile(chunkPos, false); ++ } catch (java.io.IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ ChunkStatus status = file.getStatusIfCached(x, z); ++ if (!file.hasChunk(chunkPos) || (status != null && status != ChunkStatus.FULL)) { ++ return false; ++ } ++ ++ ChunkAccess chunk = world.getChunkSource().getChunk(x, z, ChunkStatus.EMPTY, true); ++ if (!(chunk instanceof ImposterProtoChunk) && !(chunk instanceof net.minecraft.world.level.chunk.LevelChunk)) { ++ return false; ++ } ++ ++ // fall through to load ++ // we do this so we do not re-read the chunk data on disk ++ } ++ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper ++ world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true); ++ return true; ++ // Paper end - Optimize this method + } + + @Override diff --git a/patches/server/0992-incremental-chunk-and-player-saving.patch b/patches/server/0992-incremental-chunk-and-player-saving.patch deleted file mode 100644 index 5a4618b85dae..000000000000 --- a/patches/server/0992-incremental-chunk-and-player-saving.patch +++ /dev/null @@ -1,167 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sun, 9 Jun 2019 03:53:22 +0100 -Subject: [PATCH] incremental chunk and player saving - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 365b11eb92e76cda975a5989a556abcf4c8fdaa4..0b1de9a1b7ae9ed7a938b8f3482d82a106644b7a 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -908,7 +908,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && this.ticksUntilAutosave <= 0) { -- this.ticksUntilAutosave = this.autosavePeriod; -- // CraftBukkit end -- MinecraftServer.LOGGER.debug("Autosave started"); -- this.profiler.push("save"); -- this.saveEverything(true, false, false); -- this.profiler.pop(); -- MinecraftServer.LOGGER.debug("Autosave finished"); -+ // Paper start - Incremental chunk and player saving -+ int playerSaveInterval = io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.rate; -+ if (playerSaveInterval < 0) { -+ playerSaveInterval = autosavePeriod; - } -+ this.profiler.push("save"); -+ final boolean fullSave = autosavePeriod > 0 && this.tickCount % autosavePeriod == 0; -+ try { -+ this.isSaving = true; -+ if (playerSaveInterval > 0) { -+ this.playerList.saveAll(playerSaveInterval); -+ } -+ for (ServerLevel level : this.getAllLevels()) { -+ if (level.paperConfig().chunks.autoSaveInterval.value() > 0) { -+ level.saveIncrementally(fullSave); -+ } -+ } -+ } finally { -+ this.isSaving = false; -+ } -+ this.profiler.pop(); -+ // Paper end - Incremental chunk and player saving - io.papermc.paper.util.CachedLists.reset(); // Paper - // Paper start - move executeAll() into full server tick timing - try (co.aikar.timings.Timing ignored = MinecraftTimings.processTasksTimer.startTiming()) { -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 9bb4223fbb665211df11dc89fcd13cb7a92cd5dd..20cdfd2bbd5dc71fd37ccedaf3a8d06b45553c9b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -435,6 +435,15 @@ public class ServerChunkCache extends ChunkSource { - } // Paper - Timings - } - -+ // Paper start - Incremental chunk and player saving; duplicate save, but call incremental -+ public void saveIncrementally() { -+ this.runDistanceManagerUpdates(); -+ try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings -+ this.chunkMap.saveIncrementally(); -+ } // Paper - Timings -+ } -+ // Paper end - Incremental chunk and player saving -+ - @Override - public void close() throws IOException { - // CraftBukkit start -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index dff2dfbe9cc04894d42181c6691e27ad061beb40..09a9452705cc8d4133940c081583d6d38d226f71 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1290,6 +1290,37 @@ public class ServerLevel extends Level implements WorldGenLevel { - return !this.server.isUnderSpawnProtection(this, pos, player) && this.getWorldBorder().isWithinBounds(pos); - } - -+ // Paper start - Incremental chunk and player saving -+ public void saveIncrementally(boolean doFull) { -+ ServerChunkCache chunkproviderserver = this.getChunkSource(); -+ -+ if (doFull) { -+ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); -+ } -+ -+ try (co.aikar.timings.Timing ignored = this.timings.worldSave.startTiming()) { -+ if (doFull) { -+ this.saveLevelData(); -+ } -+ -+ this.timings.worldSaveChunks.startTiming(); // Paper -+ if (!this.noSave()) chunkproviderserver.saveIncrementally(); -+ this.timings.worldSaveChunks.stopTiming(); // Paper -+ -+ // Copied from save() -+ // CraftBukkit start - moved from MinecraftServer.saveChunks -+ if (doFull) { // Paper -+ ServerLevel worldserver1 = this; -+ -+ this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings()); -+ this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save()); -+ this.convertable.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData()); -+ } -+ // CraftBukkit end -+ } -+ } -+ // Paper end - Incremental chunk and player saving -+ - public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled) { - // Paper start - rewrite chunk system - add close param - this.save(progressListener, flush, savingDisabled, false); -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 36aa6fa15e12da2caec671895d5e627da0e9ff95..e03d2d3d35470cb3971606c66da382672eef1f82 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -191,6 +191,7 @@ import org.bukkit.inventory.MainHand; - public class ServerPlayer extends Player { - - private static final Logger LOGGER = LogUtils.getLogger(); -+ public long lastSave = MinecraftServer.currentTick; // Paper - Incremental chunk and player saving - private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32; - private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10; - private static final int FLY_STAT_RECORDING_SPEED = 25; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 39ebcbac5b735582717dd98c84f065614f361805..34bd7e81f9480c97afd69c11eca216b03e6a5a1f 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -576,6 +576,7 @@ public abstract class PlayerList { - - protected void save(ServerPlayer player) { - if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit -+ player.lastSave = MinecraftServer.currentTick; // Paper - Incremental chunk and player saving - this.playerIo.save(player); - ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit - -@@ -1227,10 +1228,22 @@ public abstract class PlayerList { - } - - public void saveAll() { -+ // Paper start - Incremental chunk and player saving -+ this.saveAll(-1); -+ } -+ -+ public void saveAll(int interval) { - io.papermc.paper.util.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main - MinecraftTimings.savePlayers.startTiming(); // Paper -+ int numSaved = 0; -+ long now = MinecraftServer.currentTick; - for (int i = 0; i < this.players.size(); ++i) { -- this.save(this.players.get(i)); -+ ServerPlayer entityplayer = this.players.get(i); -+ if (interval == -1 || now - entityplayer.lastSave >= interval) { -+ this.save(entityplayer); -+ if (interval != -1 && ++numSaved >= io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.maxPerTick()) { break; } -+ } -+ // Paper end - Incremental chunk and player saving - } - MinecraftTimings.savePlayers.stopTiming(); // Paper - return null; }); // Paper - ensure main diff --git a/patches/server/0999-Flat-bedrock-generator-settings.patch b/patches/server/0993-Flat-bedrock-generator-settings.patch similarity index 100% rename from patches/server/0999-Flat-bedrock-generator-settings.patch rename to patches/server/0993-Flat-bedrock-generator-settings.patch diff --git a/patches/server/0994-Entity-Activation-Range-2.0.patch b/patches/server/0994-Entity-Activation-Range-2.0.patch new file mode 100644 index 000000000000..0e1d181c8bd7 --- /dev/null +++ b/patches/server/0994-Entity-Activation-Range-2.0.patch @@ -0,0 +1,811 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 13 May 2016 01:38:06 -0400 +Subject: [PATCH] Entity Activation Range 2.0 + +Optimizes performance of Activation Range + +Adds many new configurations and a new wake up inactive system + +Fixes and adds new Immunities to improve gameplay behavior + +Adds water Mobs to activation range config and nerfs fish +Adds flying monsters to control ghast and phantoms +Adds villagers as separate config + +== AT == +public net.minecraft.world.entity.Entity isInsidePortal + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 5f3502b148588a76079c1d9f55e4203f6de56406..4357d45305cdf82659fcc0df9fa42b1ae1029cc1 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2,7 +2,6 @@ package net.minecraft.server.level; + + import com.google.common.annotations.VisibleForTesting; + import co.aikar.timings.TimingHistory; // Paper +-import co.aikar.timings.Timings; // Paper + import com.google.common.collect.Lists; + import com.mojang.datafixers.DataFixer; + import com.mojang.datafixers.util.Pair; +@@ -1222,17 +1221,17 @@ public class ServerLevel extends Level implements WorldGenLevel { + ++TimingHistory.entityTicks; // Paper - timings + // Spigot start + co.aikar.timings.Timing timer; // Paper +- if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { ++ /*if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { // Paper - comment out - EAR 2, reimplement below + entity.tickCount++; + timer = entity.getType().inactiveTickTimer.startTiming(); try { // Paper - timings + entity.inactiveTick(); + } finally { timer.stopTiming(); } // Paper + return; +- } ++ }*/ // Paper - comment out EAR 2 + // Spigot end + // Paper start- timings +- TimingHistory.activatedEntityTicks++; +- timer = entity.getVehicle() != null ? entity.getType().passengerTickTimer.startTiming() : entity.getType().tickTimer.startTiming(); ++ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); ++ timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper + try { + // Paper end - timings + entity.setOldPosAndRot(); +@@ -1243,9 +1242,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); + }); + gameprofilerfiller.incrementCounter("tickNonPassenger"); ++ if (isActive) { // Paper - EAR 2 ++ TimingHistory.activatedEntityTicks++; + entity.tick(); + entity.postTick(); // CraftBukkit ++ } else { entity.inactiveTick(); } // Paper - EAR 2 + this.getProfiler().pop(); ++ } finally { timer.stopTiming(); } // Paper - timings + Iterator iterator = entity.getPassengers().iterator(); + + while (iterator.hasNext()) { +@@ -1253,13 +1256,18 @@ public class ServerLevel extends Level implements WorldGenLevel { + + this.tickPassenger(entity, entity1); + } +- } finally { timer.stopTiming(); } // Paper - timings ++ // } finally { timer.stopTiming(); } // Paper - timings - move up + + } + + private void tickPassenger(Entity vehicle, Entity passenger) { + if (!passenger.isRemoved() && passenger.getVehicle() == vehicle) { + if (passenger instanceof Player || this.entityTickList.contains(passenger)) { ++ // Paper - EAR 2 ++ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(passenger); ++ co.aikar.timings.Timing timer = isActive ? passenger.getType().passengerTickTimer.startTiming() : passenger.getType().passengerInactiveTickTimer.startTiming(); // Paper ++ try { ++ // Paper end + passenger.setOldPosAndRot(); + ++passenger.tickCount; + ProfilerFiller gameprofilerfiller = this.getProfiler(); +@@ -1268,8 +1276,17 @@ public class ServerLevel extends Level implements WorldGenLevel { + return BuiltInRegistries.ENTITY_TYPE.getKey(passenger.getType()).toString(); + }); + gameprofilerfiller.incrementCounter("tickPassenger"); ++ // Paper start - EAR 2 ++ if (isActive) { + passenger.rideTick(); + passenger.postTick(); // CraftBukkit ++ } else { ++ passenger.setDeltaMovement(Vec3.ZERO); ++ passenger.inactiveTick(); ++ // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary ++ vehicle.positionRider(passenger); ++ } ++ // Paper end - EAR 2 + gameprofilerfiller.pop(); + Iterator iterator = passenger.getPassengers().iterator(); + +@@ -1279,6 +1296,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + this.tickPassenger(passenger, entity2); + } + ++ } finally { timer.stopTiming(); }// Paper - EAR2 timings + } + } else { + passenger.stopRiding(); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 8d30abbfd805eaffc28dba9c8cc50bc6087027be..fc5be14cade9f9b6bf67e4b66d33290eeb20ff59 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -411,6 +411,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + // Spigot end + protected int numCollisions = 0; // Paper - Cap entity collisions + public boolean fromNetherPortal; // Paper - Add option to nerf pigmen from nether portals ++ public long activatedImmunityTick = Integer.MIN_VALUE; // Paper - EAR ++ public boolean isTemporarilyActive; // Paper - EAR + public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one + // Paper start - Entity origin API + @javax.annotation.Nullable +@@ -1021,6 +1023,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } else { + this.wasOnFire = this.isOnFire(); + if (movementType == MoverType.PISTON) { ++ this.activatedTick = Math.max(this.activatedTick, MinecraftServer.currentTick + 20); // Paper ++ this.activatedImmunityTick = Math.max(this.activatedImmunityTick, MinecraftServer.currentTick + 20); // Paper + movement = this.limitPistonMovement(movement); + if (movement.equals(Vec3.ZERO)) { + return; +@@ -1033,6 +1037,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + this.stuckSpeedMultiplier = Vec3.ZERO; + this.setDeltaMovement(Vec3.ZERO); + } ++ // Paper start - ignore movement changes while inactive. ++ if (isTemporarilyActive && !(this instanceof ItemEntity || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) && movement == getDeltaMovement() && movementType == MoverType.SELF) { ++ setDeltaMovement(Vec3.ZERO); ++ this.level.getProfiler().pop(); ++ return; ++ } ++ // Paper end + + movement = this.maybeBackOffFromEdge(movement, movementType); + Vec3 vec3d1 = this.collide(movement); +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index fd093e5bc79a44e02f57bacd8273dc87342f5709..fa0b78139fecc0245e168ebeb4172ea2531a3fec 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -221,6 +221,19 @@ public abstract class Mob extends LivingEntity implements Targeting { + return this.lookControl; + } + ++ // Paper start ++ @Override ++ public void inactiveTick() { ++ super.inactiveTick(); ++ if (this.goalSelector.inactiveTick()) { ++ this.goalSelector.tick(); ++ } ++ if (this.targetSelector.inactiveTick()) { ++ this.targetSelector.tick(); ++ } ++ } ++ // Paper end ++ + public MoveControl getMoveControl() { + Entity entity = this.getControlledVehicle(); + +diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +index d6393210cfee53685f83c8491bea8b9c13b01eea..3d95257d2203fe40bb1fab58ad2a1f9e815184a9 100644 +--- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java ++++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +@@ -21,6 +21,7 @@ public abstract class PathfinderMob extends Mob { + } + + public org.bukkit.craftbukkit.entity.CraftCreature getBukkitCreature() { return (org.bukkit.craftbukkit.entity.CraftCreature) super.getBukkitEntity(); } // Paper ++ public BlockPos movingTarget = null; public BlockPos getMovingTarget() { return movingTarget; } // Paper + + public float getWalkTargetValue(BlockPos pos) { + return this.getWalkTargetValue(pos, this.level()); +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +index 07c1ca01c38d5d7d0a95ad5004b5df9f4a222935..e5995d0db5dcfba59a873ff439601894fdacd556 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +@@ -33,6 +33,7 @@ public class GoalSelector { + private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.class); + private int tickCount; + private int newGoalRate = 3; ++ private int curRate; + + public GoalSelector(Supplier profiler) { + this.profiler = profiler; +@@ -49,6 +50,20 @@ public class GoalSelector { + }); + } + ++ // Paper start ++ public boolean inactiveTick() { ++ this.curRate++; ++ return this.curRate % this.newGoalRate == 0; ++ } ++ public boolean hasTasks() { ++ for (WrappedGoal task : this.availableGoals) { ++ if (task.isRunning()) { ++ return true; ++ } ++ } ++ return false; ++ } ++ // Paper end + public void removeGoal(Goal goal) { + this.availableGoals.stream().filter((wrappedGoal) -> { + return wrappedGoal.getGoal() == goal; +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +index 9fc374c17f6b3ee4ab3c582d05e96321b772f2d6..07519c817cc6de04a98198c43a0c2b02ba3141eb 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +@@ -23,6 +23,14 @@ public abstract class MoveToBlockGoal extends Goal { + public MoveToBlockGoal(PathfinderMob mob, double speed, int range) { + this(mob, speed, range, 1); + } ++ // Paper start - activation range improvements ++ @Override ++ public void stop() { ++ super.stop(); ++ this.blockPos = BlockPos.ZERO; ++ this.mob.movingTarget = null; ++ } ++ // Paper end + + public MoveToBlockGoal(PathfinderMob mob, double speed, int range, int maxYDifference) { + this.mob = mob; +@@ -114,6 +122,7 @@ public abstract class MoveToBlockGoal extends Goal { + mutableBlockPos.setWithOffset(blockPos, m, k - 1, n); + if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level(), mutableBlockPos)) { + this.blockPos = mutableBlockPos; ++ this.mob.movingTarget = mutableBlockPos == BlockPos.ZERO ? null : mutableBlockPos.immutable(); // Paper + return true; + } + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index 4a19e6b83147ae22ade70fdd445c5d7df3b07a0f..1aae466e3e334d7f4bbb3ea9365a255afcc3dd3a 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -227,17 +227,34 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + @Override + public void inactiveTick() { + // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :( +- if (this.level().spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) { +- this.customServerAiStep(); ++ // Paper start ++ if (this.getUnhappyCounter() > 0) { ++ this.setUnhappyCounter(this.getUnhappyCounter() - 1); + } ++ if (this.isEffectiveAi()) { ++ if (this.level().spigotConfig.tickInactiveVillagers) { ++ this.customServerAiStep(); ++ } else { ++ this.customServerAiStep(true); ++ } ++ } ++ maybeDecayGossip(); ++ // Paper end ++ + super.inactiveTick(); + } + // Spigot End + + @Override ++ @Deprecated // Paper + protected void customServerAiStep() { ++ // Paper start ++ this.customServerAiStep(false); ++ } ++ protected void customServerAiStep(final boolean inactive) { ++ // Paper end + this.level().getProfiler().push("villagerBrain"); +- this.getBrain().tick((ServerLevel) this.level(), this); ++ if (!inactive) this.getBrain().tick((ServerLevel) this.level(), this); // Paper + this.level().getProfiler().pop(); + if (this.assignProfessionWhenSpawned) { + this.assignProfessionWhenSpawned = false; +@@ -261,7 +278,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + this.lastTradedPlayer = null; + } + +- if (!this.isNoAi() && this.random.nextInt(100) == 0) { ++ if (!inactive && !this.isNoAi() && this.random.nextInt(100) == 0) { // Paper + Raid raid = ((ServerLevel) this.level()).getRaidAt(this.blockPosition()); + + if (raid != null && raid.isActive() && !raid.isOver()) { +@@ -272,6 +289,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + if (this.getVillagerData().getProfession() == VillagerProfession.NONE && this.isTrading()) { + this.stopTrading(); + } ++ if (inactive) return; // Paper + + super.customServerAiStep(); + } +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java b/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java +index b149e8bcac034bb3fc118a9adcb0de45e18ed5e9..fc35cfc9d045f3e5b6a50af1d0ba83b6e322091f 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java +@@ -52,6 +52,7 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper + if (bl != this.isEnabled()) { + this.setEnabled(bl); + } ++ this.immunize(); // Paper + + } + +@@ -89,10 +90,12 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper + + public boolean suckInItems() { + if (HopperBlockEntity.suckInItems(this.level(), this)) { ++ this.immunize(); // Paper + return true; + } else { + for(ItemEntity itemEntity : this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(0.25D, 0.0D, 0.25D), EntitySelector.ENTITY_STILL_ALIVE)) { + if (HopperBlockEntity.addItem(this, itemEntity)) { ++ this.immunize(); // Paper + return true; + } + } +@@ -122,4 +125,11 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper + public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { + return new HopperMenu(syncId, playerInventory, this); + } ++ ++ // Paper start ++ public void immunize() { ++ this.activatedImmunityTick = Math.max(this.activatedImmunityTick, net.minecraft.server.MinecraftServer.currentTick + 20); ++ } ++ // Paper end ++ + } +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 29b6494d17bc8b9926244c286e05c821a6297f7b..6f2515d3476f7a5da898efb3c60e8f4aaad09b06 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -157,6 +157,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates + public List captureDrops; + public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); ++ // Paper start ++ public int wakeupInactiveRemainingAnimals; ++ public int wakeupInactiveRemainingFlying; ++ public int wakeupInactiveRemainingMonsters; ++ public int wakeupInactiveRemainingVillagers; ++ // Paper end + public boolean populating; + public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot + // Paper start - add paper world config +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 b1061db1d9b3bfde61d5016e10556c4320095827..c71690dbc3dc52803945f1608f0ee3ba94146354 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 +@@ -143,6 +143,10 @@ public class PistonMovingBlockEntity extends BlockEntity { + } + + entity.setDeltaMovement(e, g, h); ++ // 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); ++ // Paper end + break; + } + } +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 90df7e83d9166c22a56a31db22d843768229b9ab..c39894e824334f1dc52e0466cf9d84f7e219be70 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -1,33 +1,43 @@ + package org.spigotmc; + ++import net.minecraft.core.BlockPos; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.ExperienceOrb; ++import net.minecraft.world.entity.FlyingMob; + import net.minecraft.world.entity.LightningBolt; + import net.minecraft.world.entity.LivingEntity; ++import net.minecraft.world.entity.Mob; + import net.minecraft.world.entity.PathfinderMob; ++import net.minecraft.world.entity.ai.Brain; + import net.minecraft.world.entity.ambient.AmbientCreature; + import net.minecraft.world.entity.animal.Animal; ++import net.minecraft.world.entity.animal.Bee; + import net.minecraft.world.entity.animal.Sheep; ++import net.minecraft.world.entity.animal.WaterAnimal; ++import net.minecraft.world.entity.animal.horse.Llama; + import net.minecraft.world.entity.boss.EnderDragonPart; + import net.minecraft.world.entity.boss.enderdragon.EndCrystal; + import net.minecraft.world.entity.boss.enderdragon.EnderDragon; + import net.minecraft.world.entity.boss.wither.WitherBoss; + import net.minecraft.world.entity.item.PrimedTnt; + import net.minecraft.world.entity.monster.Creeper; +-import net.minecraft.world.entity.monster.Monster; +-import net.minecraft.world.entity.monster.Slime; ++import net.minecraft.world.entity.monster.Enemy; ++import net.minecraft.world.entity.monster.Pillager; + import net.minecraft.world.entity.npc.Villager; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.projectile.AbstractArrow; + import net.minecraft.world.entity.projectile.AbstractHurtingProjectile; ++import net.minecraft.world.entity.projectile.EyeOfEnder; + import net.minecraft.world.entity.projectile.FireworkRocketEntity; + import net.minecraft.world.entity.projectile.ThrowableProjectile; + import net.minecraft.world.entity.projectile.ThrownTrident; + import net.minecraft.world.entity.raid.Raider; ++import co.aikar.timings.MinecraftTimings; ++import net.minecraft.world.entity.schedule.Activity; + import net.minecraft.world.level.Level; + import net.minecraft.world.phys.AABB; +-import co.aikar.timings.MinecraftTimings; + + public class ActivationRange + { +@@ -44,6 +54,43 @@ public class ActivationRange + + AABB boundingBox = new AABB( 0, 0, 0, 0, 0, 0 ); + } ++ // Paper start ++ ++ static Activity[] VILLAGER_PANIC_IMMUNITIES = { ++ Activity.HIDE, ++ Activity.PRE_RAID, ++ Activity.RAID, ++ Activity.PANIC ++ }; ++ ++ private static int checkInactiveWakeup(Entity entity) { ++ Level world = entity.level(); ++ SpigotWorldConfig config = world.spigotConfig; ++ long inactiveFor = MinecraftServer.currentTick - entity.activatedTick; ++ if (entity.activationType == ActivationType.VILLAGER) { ++ if (inactiveFor > config.wakeUpInactiveVillagersEvery && world.wakeupInactiveRemainingVillagers > 0) { ++ world.wakeupInactiveRemainingVillagers--; ++ return config.wakeUpInactiveVillagersFor; ++ } ++ } else if (entity.activationType == ActivationType.ANIMAL) { ++ if (inactiveFor > config.wakeUpInactiveAnimalsEvery && world.wakeupInactiveRemainingAnimals > 0) { ++ world.wakeupInactiveRemainingAnimals--; ++ return config.wakeUpInactiveAnimalsFor; ++ } ++ } else if (entity.activationType == ActivationType.FLYING_MONSTER) { ++ if (inactiveFor > config.wakeUpInactiveFlyingEvery && world.wakeupInactiveRemainingFlying > 0) { ++ world.wakeupInactiveRemainingFlying--; ++ return config.wakeUpInactiveFlyingFor; ++ } ++ } else if (entity.activationType == ActivationType.MONSTER || entity.activationType == ActivationType.RAIDER) { ++ if (inactiveFor > config.wakeUpInactiveMonstersEvery && world.wakeupInactiveRemainingMonsters > 0) { ++ world.wakeupInactiveRemainingMonsters--; ++ return config.wakeUpInactiveMonstersFor; ++ } ++ } ++ return -1; ++ } ++ // Paper end + + static AABB maxBB = new AABB( 0, 0, 0, 0, 0, 0 ); + +@@ -56,10 +103,13 @@ public class ActivationRange + */ + public static ActivationType initializeEntityActivationType(Entity entity) + { ++ if (entity instanceof WaterAnimal) { return ActivationType.WATER; } // Paper ++ else if (entity instanceof Villager) { return ActivationType.VILLAGER; } // Paper ++ else if (entity instanceof FlyingMob && entity instanceof Enemy) { return ActivationType.FLYING_MONSTER; } // Paper - doing & Monster incase Flying no longer includes monster in future + if ( entity instanceof Raider ) + { + return ActivationType.RAIDER; +- } else if ( entity instanceof Monster || entity instanceof Slime ) ++ } else if ( entity instanceof Enemy ) // Paper - correct monster check + { + return ActivationType.MONSTER; + } else if ( entity instanceof PathfinderMob || entity instanceof AmbientCreature ) +@@ -80,10 +130,14 @@ public class ActivationRange + */ + public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config) + { +- if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange == 0 ) +- || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0 ) +- || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0 ) +- || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0 ) ++ if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange <= 0 ) ++ || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange <= 0 ) ++ || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange <= 0 ) ++ || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange <= 0 ) ++ || ( entity.activationType == ActivationType.VILLAGER && config.villagerActivationRange <= 0 ) // Paper ++ || ( entity.activationType == ActivationType.WATER && config.waterActivationRange <= 0 ) // Paper ++ || ( entity.activationType == ActivationType.FLYING_MONSTER && config.flyingMonsterActivationRange <= 0 ) // Paper ++ || entity instanceof EyeOfEnder // Paper + || entity instanceof Player + || entity instanceof ThrowableProjectile + || entity instanceof EnderDragon +@@ -116,10 +170,25 @@ public class ActivationRange + final int raiderActivationRange = world.spigotConfig.raiderActivationRange; + final int animalActivationRange = world.spigotConfig.animalActivationRange; + final int monsterActivationRange = world.spigotConfig.monsterActivationRange; ++ // Paper start ++ final int waterActivationRange = world.spigotConfig.waterActivationRange; ++ final int flyingActivationRange = world.spigotConfig.flyingMonsterActivationRange; ++ final int villagerActivationRange = world.spigotConfig.villagerActivationRange; ++ world.wakeupInactiveRemainingAnimals = Math.min(world.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals); ++ world.wakeupInactiveRemainingVillagers = Math.min(world.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers); ++ world.wakeupInactiveRemainingMonsters = Math.min(world.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters); ++ world.wakeupInactiveRemainingFlying = Math.min(world.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying); ++ final ServerChunkCache chunkProvider = (ServerChunkCache) world.getChunkSource(); ++ // Paper end + + int maxRange = Math.max( monsterActivationRange, animalActivationRange ); + maxRange = Math.max( maxRange, raiderActivationRange ); + maxRange = Math.max( maxRange, miscActivationRange ); ++ // Paper start ++ maxRange = Math.max( maxRange, flyingActivationRange ); ++ maxRange = Math.max( maxRange, waterActivationRange ); ++ maxRange = Math.max( maxRange, villagerActivationRange ); ++ // Paper end + maxRange = Math.min( ( world.spigotConfig.simulationDistance << 4 ) - 8, maxRange ); + + for ( Player player : world.players() ) +@@ -130,13 +199,30 @@ public class ActivationRange + continue; + } + +- ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, 256, maxRange ); +- ActivationType.MISC.boundingBox = player.getBoundingBox().inflate( miscActivationRange, 256, miscActivationRange ); +- ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, 256, raiderActivationRange ); +- ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, 256, animalActivationRange ); +- ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, 256, monsterActivationRange ); ++ // Paper start ++ int worldHeight = world.getHeight(); ++ ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, worldHeight, maxRange ); ++ ActivationType.MISC.boundingBox = player.getBoundingBox().inflate( miscActivationRange, worldHeight, miscActivationRange ); ++ ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, worldHeight, raiderActivationRange ); ++ ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, worldHeight, animalActivationRange ); ++ ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, worldHeight, monsterActivationRange ); ++ ActivationType.WATER.boundingBox = player.getBoundingBox().inflate( waterActivationRange, worldHeight, waterActivationRange ); ++ ActivationType.FLYING_MONSTER.boundingBox = player.getBoundingBox().inflate( flyingActivationRange, worldHeight, flyingActivationRange ); ++ ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate( villagerActivationRange, worldHeight, villagerActivationRange ); ++ // Paper end + +- world.getEntities().get(ActivationRange.maxBB, ActivationRange::activateEntity); ++ // Paper start ++ java.util.List entities = world.getEntities((Entity)null, ActivationRange.maxBB, null); ++ boolean tickMarkers = world.paperConfig().entities.markers.tick; // Paper - Configurable marker ticking ++ for (Entity entity : entities) { ++ // Paper start - Configurable marker ticking ++ if (!tickMarkers && entity instanceof net.minecraft.world.entity.Marker) { ++ continue; ++ } ++ // Paper end - Configurable marker ticking ++ ActivationRange.activateEntity(entity); ++ } ++ // Paper end + } + MinecraftTimings.entityActivationCheckTimer.stopTiming(); + } +@@ -169,60 +255,118 @@ public class ActivationRange + * @param entity + * @return + */ +- public static boolean checkEntityImmunities(Entity entity) ++ public static int checkEntityImmunities(Entity entity) // Paper - return # of ticks to get immunity + { ++ // Paper start ++ SpigotWorldConfig config = entity.level().spigotConfig; ++ int inactiveWakeUpImmunity = checkInactiveWakeup(entity); ++ if (inactiveWakeUpImmunity > -1) { ++ return inactiveWakeUpImmunity; ++ } ++ if (entity.getRemainingFireTicks() > 0) { ++ return 2; ++ } ++ if (entity.activatedImmunityTick >= MinecraftServer.currentTick) { ++ return 1; ++ } ++ long inactiveFor = MinecraftServer.currentTick - entity.activatedTick; ++ // Paper end + // quick checks. +- if ( entity.wasTouchingWater || entity.getRemainingFireTicks() > 0 ) ++ if ( (entity.activationType != ActivationType.WATER && entity.wasTouchingWater && entity.isPushedByFluid()) ) // Paper + { +- return true; ++ return 100; // Paper ++ } ++ // Paper start ++ if ( !entity.onGround() || entity.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D ) ++ { ++ return 100; + } ++ // Paper end + if ( !( entity instanceof AbstractArrow ) ) + { +- if ( !entity.onGround() || !entity.passengers.isEmpty() || entity.isPassenger() ) ++ if ( (!entity.onGround() && !(entity instanceof FlyingMob)) ) // Paper - remove passengers logic + { +- return true; ++ return 10; // Paper + } + } else if ( !( (AbstractArrow) entity ).inGround ) + { +- return true; ++ return 1; // Paper + } + // special cases. + if ( entity instanceof LivingEntity ) + { + LivingEntity living = (LivingEntity) entity; +- if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTime > 0 || living.activeEffects.size() > 0 ) ++ if ( living.onClimbable() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 || living.isFreezing()) // Paper + { +- return true; ++ return 1; // Paper + } +- if ( entity instanceof PathfinderMob && ( (PathfinderMob) entity ).getTarget() != null ) ++ if ( entity instanceof Mob && ((Mob) entity ).getTarget() != null) // Paper + { +- return true; ++ return 20; // Paper ++ } ++ // Paper start ++ if (entity instanceof Bee) { ++ Bee bee = (Bee)entity; ++ BlockPos movingTarget = bee.getMovingTarget(); ++ if (bee.isAngry() || ++ (bee.getHivePos() != null && bee.getHivePos().equals(movingTarget)) || ++ (bee.getSavedFlowerPos() != null && bee.getSavedFlowerPos().equals(movingTarget)) ++ ) { ++ return 20; ++ } + } +- if ( entity instanceof Villager && ( (Villager) entity ).canBreed() ) ++ if ( entity instanceof Villager ) { ++ Brain behaviorController = ((Villager) entity).getBrain(); ++ ++ if (config.villagersActiveForPanic) { ++ for (Activity activity : VILLAGER_PANIC_IMMUNITIES) { ++ if (behaviorController.isActive(activity)) { ++ return 20*5; ++ } ++ } ++ } ++ ++ if (config.villagersWorkImmunityAfter > 0 && inactiveFor >= config.villagersWorkImmunityAfter) { ++ if (behaviorController.isActive(Activity.WORK)) { ++ return config.villagersWorkImmunityFor; ++ } ++ } ++ } ++ if ( entity instanceof Llama && ( (Llama) entity ).inCaravan() ) + { +- return true; ++ return 1; + } ++ // Paper end + if ( entity instanceof Animal ) + { + Animal animal = (Animal) entity; + if ( animal.isBaby() || animal.isInLove() ) + { +- return true; ++ return 5; // Paper + } + if ( entity instanceof Sheep && ( (Sheep) entity ).isSheared() ) + { +- return true; ++ return 1; // Paper + } + } + if (entity instanceof Creeper && ((Creeper) entity).isIgnited()) { // isExplosive +- return true; ++ return 20; // Paper ++ } ++ // Paper start ++ if (entity instanceof Mob && ((Mob) entity).targetSelector.hasTasks() ) { ++ return 0; + } ++ if (entity instanceof Pillager) { ++ Pillager pillager = (Pillager) entity; ++ // TODO:? ++ } ++ // Paper end + } + // SPIGOT-6644: Otherwise the target refresh tick will be missed + if (entity instanceof ExperienceOrb) { +- return true; ++ return 20; // Paper + } +- return false; ++ return -1; // Paper + } + + /** +@@ -237,8 +381,19 @@ public class ActivationRange + if ( entity instanceof FireworkRocketEntity ) { + return true; + } ++ // Paper start - special case always immunities ++ // immunize brand new entities, dead entities, and portal scenarios ++ if (entity.defaultActivationState || entity.tickCount < 20*10 || !entity.isAlive() || entity.isInsidePortal || entity.portalCooldown > 0) { ++ return true; ++ } ++ // immunize leashed entities ++ if (entity instanceof Mob && ((Mob)entity).getLeashHolder() instanceof Player) { ++ return true; ++ } ++ // Paper end + +- boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState; ++ boolean isActive = entity.activatedTick >= MinecraftServer.currentTick; ++ entity.isTemporarilyActive = false; // Paper + + // Should this entity tick? + if ( !isActive ) +@@ -246,15 +401,19 @@ public class ActivationRange + if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 ) + { + // Check immunities every 20 ticks. +- if ( ActivationRange.checkEntityImmunities( entity ) ) +- { +- // Triggered some sort of immunity, give 20 full ticks before we check again. +- entity.activatedTick = MinecraftServer.currentTick + 20; ++ // Paper start ++ int immunity = checkEntityImmunities(entity); ++ if (immunity >= 0) { ++ entity.activatedTick = MinecraftServer.currentTick + immunity; ++ } else { ++ entity.isTemporarilyActive = true; + } ++ // Paper end + isActive = true; ++ + } + // Add a little performance juice to active entities. Skip 1/4 if not immune. +- } else if ( !entity.defaultActivationState && (entity.tickCount + entity.getId()) % 4 == 0 && !ActivationRange.checkEntityImmunities( entity ) ) // Paper - Ensure checking item movement is offset from Spigot's entity activation range check ++ } else if ( (entity.tickCount + entity.getId()) % 4 == 0 && ActivationRange.checkEntityImmunities( entity ) < 0 ) // Paper + { + isActive = false; + } +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index 5485df1a1b59e81f4dcedd21dd972e1fd2759573..1cf6d4f854d89c515e48e1fb365eb95ff9340765 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -211,14 +211,60 @@ public class SpigotWorldConfig + public int monsterActivationRange = 32; + public int raiderActivationRange = 48; + public int miscActivationRange = 16; ++ // Paper start ++ public int flyingMonsterActivationRange = 32; ++ public int waterActivationRange = 16; ++ public int villagerActivationRange = 32; ++ public int wakeUpInactiveAnimals = 4; ++ public int wakeUpInactiveAnimalsEvery = 60*20; ++ public int wakeUpInactiveAnimalsFor = 5*20; ++ public int wakeUpInactiveMonsters = 8; ++ public int wakeUpInactiveMonstersEvery = 20*20; ++ public int wakeUpInactiveMonstersFor = 5*20; ++ public int wakeUpInactiveVillagers = 4; ++ public int wakeUpInactiveVillagersEvery = 30*20; ++ public int wakeUpInactiveVillagersFor = 5*20; ++ public int wakeUpInactiveFlying = 8; ++ public int wakeUpInactiveFlyingEvery = 10*20; ++ public int wakeUpInactiveFlyingFor = 5*20; ++ public int villagersWorkImmunityAfter = 5*20; ++ public int villagersWorkImmunityFor = 20; ++ public boolean villagersActiveForPanic = true; ++ // Paper end + public boolean tickInactiveVillagers = true; + public boolean ignoreSpectatorActivation = false; + private void activationRange() + { ++ boolean hasAnimalsConfig = config.getInt("entity-activation-range.animals", this.animalActivationRange) != this.animalActivationRange; // Paper + this.animalActivationRange = this.getInt( "entity-activation-range.animals", this.animalActivationRange ); + this.monsterActivationRange = this.getInt( "entity-activation-range.monsters", this.monsterActivationRange ); + this.raiderActivationRange = this.getInt( "entity-activation-range.raiders", this.raiderActivationRange ); + this.miscActivationRange = this.getInt( "entity-activation-range.misc", this.miscActivationRange ); ++ // Paper start ++ this.waterActivationRange = this.getInt( "entity-activation-range.water", this.waterActivationRange ); ++ this.villagerActivationRange = this.getInt( "entity-activation-range.villagers", hasAnimalsConfig ? this.animalActivationRange : this.villagerActivationRange ); ++ this.flyingMonsterActivationRange = this.getInt( "entity-activation-range.flying-monsters", this.flyingMonsterActivationRange ); ++ ++ this.wakeUpInactiveAnimals = this.getInt("entity-activation-range.wake-up-inactive.animals-max-per-tick", this.wakeUpInactiveAnimals); ++ this.wakeUpInactiveAnimalsEvery = this.getInt("entity-activation-range.wake-up-inactive.animals-every", this.wakeUpInactiveAnimalsEvery); ++ this.wakeUpInactiveAnimalsFor = this.getInt("entity-activation-range.wake-up-inactive.animals-for", this.wakeUpInactiveAnimalsFor); ++ ++ this.wakeUpInactiveMonsters = this.getInt("entity-activation-range.wake-up-inactive.monsters-max-per-tick", this.wakeUpInactiveMonsters); ++ this.wakeUpInactiveMonstersEvery = this.getInt("entity-activation-range.wake-up-inactive.monsters-every", this.wakeUpInactiveMonstersEvery); ++ this.wakeUpInactiveMonstersFor = this.getInt("entity-activation-range.wake-up-inactive.monsters-for", this.wakeUpInactiveMonstersFor); ++ ++ this.wakeUpInactiveVillagers = this.getInt("entity-activation-range.wake-up-inactive.villagers-max-per-tick", this.wakeUpInactiveVillagers); ++ this.wakeUpInactiveVillagersEvery = this.getInt("entity-activation-range.wake-up-inactive.villagers-every", this.wakeUpInactiveVillagersEvery); ++ this.wakeUpInactiveVillagersFor = this.getInt("entity-activation-range.wake-up-inactive.villagers-for", this.wakeUpInactiveVillagersFor); ++ ++ this.wakeUpInactiveFlying = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-max-per-tick", this.wakeUpInactiveFlying); ++ this.wakeUpInactiveFlyingEvery = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-every", this.wakeUpInactiveFlyingEvery); ++ this.wakeUpInactiveFlyingFor = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-for", this.wakeUpInactiveFlyingFor); ++ ++ this.villagersWorkImmunityAfter = this.getInt( "entity-activation-range.villagers-work-immunity-after", this.villagersWorkImmunityAfter ); ++ this.villagersWorkImmunityFor = this.getInt( "entity-activation-range.villagers-work-immunity-for", this.villagersWorkImmunityFor ); ++ this.villagersActiveForPanic = this.getBoolean( "entity-activation-range.villagers-active-for-panic", this.villagersActiveForPanic ); ++ // Paper end + this.tickInactiveVillagers = this.getBoolean( "entity-activation-range.tick-inactive-villagers", this.tickInactiveVillagers ); + this.ignoreSpectatorActivation = this.getBoolean( "entity-activation-range.ignore-spectators", this.ignoreSpectatorActivation ); + this.log( "Entity Activation Range: An " + this.animalActivationRange + " / Mo " + this.monsterActivationRange + " / Ra " + this.raiderActivationRange + " / Mi " + this.miscActivationRange + " / Tiv " + this.tickInactiveVillagers + " / Isa " + this.ignoreSpectatorActivation ); diff --git a/patches/server/0994-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch b/patches/server/0994-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch deleted file mode 100644 index 262508bd2985..000000000000 --- a/patches/server/0994-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch +++ /dev/null @@ -1,128 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 29 Apr 2016 20:02:00 -0400 -Subject: [PATCH] Improve Maps (in item frames) performance and bug fixes - -Maps used a modified version of rendering to support plugin controlled -imaging on maps. The Craft Map Renderer is much slower than Vanilla, -causing maps in item frames to cause a noticeable hit on server performance. - -This updates the map system to not use the Craft system if we detect that no -custom renderers are in use, defaulting to the much simpler Vanilla system. - -Additionally, numerous issues to player position tracking on maps has been fixed. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 09a9452705cc8d4133940c081583d6d38d226f71..5f3502b148588a76079c1d9f55e4203f6de56406 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2620,6 +2620,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - { - if ( iter.next().player == entity ) - { -+ map.decorations.remove(entity.getName().getString()); // Paper - iter.remove(); - } - } -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 4f2d4ed485ce0d5f82f562281c40dc6a660e554b..df8d6f3eb675354ce0d180fc56886ce12788d6ae 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -788,6 +788,14 @@ public abstract class Player extends LivingEntity { - return null; - } - // CraftBukkit end -+ // Paper start - remove player from map on drop -+ if (itemstack.getItem() == Items.FILLED_MAP) { -+ net.minecraft.world.level.saveddata.maps.MapItemSavedData worldmap = net.minecraft.world.item.MapItem.getSavedData(itemstack, this.level()); -+ if (worldmap != null) { -+ worldmap.tickCarriedBy(this, itemstack); -+ } -+ } -+ // Paper end - - return entityitem; - } -diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -index ed57ce12d4d1cc632431a654cad648a8015402b1..45269115e63cfc3bd7dc740a5694e2cc7c35bcb1 100644 ---- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -+++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -@@ -66,6 +66,7 @@ public class MapItemSavedData extends SavedData { - public final Map decorations = Maps.newLinkedHashMap(); - private final Map frameMarkers = Maps.newHashMap(); - private int trackedDecorationCount; -+ private org.bukkit.craftbukkit.map.RenderData vanillaRender = new org.bukkit.craftbukkit.map.RenderData(); // Paper - - // CraftBukkit start - public final CraftMapView mapView; -@@ -92,6 +93,7 @@ public class MapItemSavedData extends SavedData { - // CraftBukkit start - this.mapView = new CraftMapView(this); - this.server = (CraftServer) org.bukkit.Bukkit.getServer(); -+ this.vanillaRender.buffer = colors; // Paper - // CraftBukkit end - } - -@@ -166,6 +168,7 @@ public class MapItemSavedData extends SavedData { - if (abyte.length == 16384) { - worldmap.colors = abyte; - } -+ worldmap.vanillaRender.buffer = abyte; // Paper - - ListTag nbttaglist = nbt.getList("banners", 10); - -@@ -578,6 +581,21 @@ public class MapItemSavedData extends SavedData { - - public class HoldingPlayer { - -+ // Paper start -+ private void addSeenPlayers(java.util.Collection icons) { -+ org.bukkit.entity.Player player = (org.bukkit.entity.Player) this.player.getBukkitEntity(); -+ MapItemSavedData.this.decorations.forEach((name, mapIcon) -> { -+ // If this cursor is for a player check visibility with vanish system -+ org.bukkit.entity.Player other = org.bukkit.Bukkit.getPlayerExact(name); // Spigot -+ if (other == null || player.canSee(other)) { -+ icons.add(mapIcon); -+ } -+ }); -+ } -+ private boolean shouldUseVanillaMap() { -+ return mapView.getRenderers().size() == 1 && mapView.getRenderers().get(0).getClass() == org.bukkit.craftbukkit.map.CraftMapRenderer.class; -+ } -+ // Paper end - public final Player player; - private boolean dirtyData = true; - private int minDirtyX; -@@ -611,7 +629,9 @@ public class MapItemSavedData extends SavedData { - @Nullable - Packet nextUpdatePacket(int mapId) { - MapItemSavedData.MapPatch worldmap_b; -- org.bukkit.craftbukkit.map.RenderData render = MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()); // CraftBukkit -+ if (!this.dirtyData && this.tick % 5 != 0) { this.tick++; return null; } // Paper - this won't end up sending, so don't render it! -+ boolean vanillaMaps = shouldUseVanillaMap(); // Paper -+ org.bukkit.craftbukkit.map.RenderData render = !vanillaMaps ? MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()) : MapItemSavedData.this.vanillaRender; // CraftBukkit // Paper - - if (this.dirtyData) { - this.dirtyData = false; -@@ -627,6 +647,8 @@ public class MapItemSavedData extends SavedData { - // CraftBukkit start - java.util.Collection icons = new java.util.ArrayList(); - -+ if (vanillaMaps) addSeenPlayers(icons); // Paper -+ - for (org.bukkit.map.MapCursor cursor : render.cursors) { - if (cursor.isVisible()) { - icons.add(new MapDecoration(MapDecoration.Type.byIcon(cursor.getRawType()), cursor.getX(), cursor.getY(), cursor.getDirection(), PaperAdventure.asVanilla(cursor.caption()))); // Paper - Adventure -diff --git a/src/main/java/org/bukkit/craftbukkit/map/RenderData.java b/src/main/java/org/bukkit/craftbukkit/map/RenderData.java -index 256a131781721c86dd6cdbc329335964570cbe8c..5768cd512ec166f1e8d1f4a28792015347297c3f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/map/RenderData.java -+++ b/src/main/java/org/bukkit/craftbukkit/map/RenderData.java -@@ -5,7 +5,7 @@ import org.bukkit.map.MapCursor; - - public class RenderData { - -- public final byte[] buffer; -+ public byte[] buffer; // Paper - public final ArrayList cursors; - - public RenderData() { diff --git a/patches/server/0995-Optional-per-player-mob-spawns.patch b/patches/server/0995-Optional-per-player-mob-spawns.patch new file mode 100644 index 000000000000..7347555f67bd --- /dev/null +++ b/patches/server/0995-Optional-per-player-mob-spawns.patch @@ -0,0 +1,255 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Mon, 19 Aug 2019 01:27:58 +0500 +Subject: [PATCH] Optional per player mob spawns + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 42dde36273030494a6e7ff19e55d3b6a7da06fee..0d552d4b967687e2bfb92b1e5106071460082409 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -288,9 +288,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + }); + } + ++ // Paper start - Optional per player mob spawns ++ public void updatePlayerMobTypeMap(final Entity entity) { ++ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { ++ return; ++ } ++ int index = entity.getType().getCategory().ordinal(); ++ ++ final com.destroystokyo.paper.util.maplist.ReferenceList inRange = ++ this.getNearbyPlayers().getPlayers(entity.chunkPosition(), io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE); ++ if (inRange == null) { ++ return; ++ } ++ final Object[] backingSet = inRange.getRawData(); ++ for (int i = 0, len = inRange.size(); i < len; i++) { ++ ++((ServerPlayer)backingSet[i]).mobCounts[index]; ++ } ++ } ++ + public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) { +- return -1; ++ return player.mobCounts[mobCategory.ordinal()]; + } ++ // Paper end - Optional per player mob spawns + + private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { + double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8); +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 20cdfd2bbd5dc71fd37ccedaf3a8d06b45553c9b..059ab637adf1be576fa1fff36a91b6c5f1b5f035 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -518,7 +518,19 @@ public class ServerChunkCache extends ChunkSource { + gameprofilerfiller.popPush("naturalSpawnCount"); + this.level.timings.countNaturalMobs.startTiming(); // Paper - timings + int k = this.distanceManager.getNaturalSpawnChunkCount(); +- NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(k, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap)); ++ // Paper start - Optional per player mob spawns ++ int naturalSpawnChunkCount = k; ++ NaturalSpawner.SpawnState spawnercreature_d; // moved down ++ if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled ++ // re-set mob counts ++ for (ServerPlayer player : this.level.players) { ++ Arrays.fill(player.mobCounts, 0); ++ } ++ spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); ++ } else { ++ spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); ++ } ++ // Paper end - Optional per player mob spawns + this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings + + this.lastSpawnState = spawnercreature_d; +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 910c5087406837033e580ec2a23f5d30d807b723..651aceca9030e1f6060af9fc9f8d349dc0451728 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -256,6 +256,10 @@ public class ServerPlayer extends Player { + public boolean queueHealthUpdatePacket; + public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; + // Paper end - cancellable death event ++ // Paper start - Optional per player mob spawns ++ public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length; ++ public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper ++ // Paper end - Optional per player mob spawns + + // CraftBukkit start + public String displayName; +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index c44c10e15564af6ba0f6d60a1b5f38c6e874a43a..14f4ceb6c0be34d23b24c1695f966145c3aaee96 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -70,6 +70,12 @@ public final class NaturalSpawner { + private NaturalSpawner() {} + + public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator densityCapper) { ++ // Paper start - Optional per player mob spawns ++ return createState(spawningChunkCount, entities, chunkSource, densityCapper, false); ++ } ++ ++ public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator densityCapper, boolean countMobs) { ++ // Paper end - Optional per player mob spawns + PotentialCalculator spawnercreatureprobabilities = new PotentialCalculator(); + Object2IntOpenHashMap object2intopenhashmap = new Object2IntOpenHashMap(); + Iterator iterator = entities.iterator(); +@@ -104,11 +110,16 @@ public final class NaturalSpawner { + spawnercreatureprobabilities.addCharge(entity.blockPosition(), biomesettingsmobs_b.charge()); + } + +- if (entity instanceof Mob) { ++ if (densityCapper != null && entity instanceof Mob) { // Paper - Optional per player mob spawns + densityCapper.addMob(chunk.getPos(), enumcreaturetype); + } + + object2intopenhashmap.addTo(enumcreaturetype, 1); ++ // Paper start - Optional per player mob spawns ++ if (countMobs) { ++ chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity); ++ } ++ // Paper end - Optional per player mob spawns + }); + } + } +@@ -143,13 +154,35 @@ public final class NaturalSpawner { + continue; + } + +- if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && info.canSpawnForCategory(enumcreaturetype, chunk.getPos(), limit)) { ++ // Paper start - Optional per player mob spawns; only allow spawns upto the limit per chunk and update count afterwards ++ int currEntityCount = info.mobCategoryCounts.getInt(enumcreaturetype); ++ int k1 = limit * info.getSpawnableChunkCount() / NaturalSpawner.MAGIC_NUMBER; ++ int difference = k1 - currEntityCount; ++ ++ if (world.paperConfig().entities.spawning.perPlayerMobSpawns) { ++ int minDiff = Integer.MAX_VALUE; ++ final com.destroystokyo.paper.util.maplist.ReferenceList inRange = ++ world.chunkSource.chunkMap.getNearbyPlayers().getPlayers(chunk.getPos(), io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE); ++ if (inRange != null) { ++ final Object[] backingSet = inRange.getRawData(); ++ for (int k = 0, len = inRange.size(); k < len; k++) { ++ minDiff = Math.min(limit - world.getChunkSource().chunkMap.getMobCountNear((net.minecraft.server.level.ServerPlayer)backingSet[k], enumcreaturetype), minDiff); ++ } ++ } ++ difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff; ++ } ++ if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && difference > 0) { ++ // Paper end - Optional per player mob spawns + // CraftBukkit end + Objects.requireNonNull(info); + NaturalSpawner.SpawnPredicate spawnercreature_c = info::canSpawn; + + Objects.requireNonNull(info); +- NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn); ++ // Paper start - Optional per player mob spawns ++ int spawnCount = NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn, ++ difference, world.paperConfig().entities.spawning.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null); ++ info.mobCategoryCounts.mergeInt(enumcreaturetype, spawnCount, Integer::sum); ++ // Paper end - Optional per player mob spawns + } + } + +@@ -168,11 +201,17 @@ public final class NaturalSpawner { + // Paper end - Add mobcaps commands + + public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { ++ // Paper start - Optional per player mob spawns ++ spawnCategoryForChunk(group, world, chunk, checker, runner, Integer.MAX_VALUE, null); ++ } ++ 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 + BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk); + + if (blockposition.getY() >= world.getMinBuildHeight() + 1) { +- NaturalSpawner.spawnCategoryForPosition(group, world, chunk, blockposition, checker, runner); ++ return NaturalSpawner.spawnCategoryForPosition(group, world, chunk, blockposition, checker, runner, maxSpawns, trackEntity); // Paper - Optional per player mob spawns + } ++ return 0; // Paper - Optional per player mob spawns + } + + @VisibleForDebug +@@ -183,15 +222,21 @@ public final class NaturalSpawner { + }); + } + ++ // Paper start - Optional per player mob spawns + public static void spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { ++ spawnCategoryForPosition(group, world,chunk, pos, checker, runner, Integer.MAX_VALUE, null); ++ } ++ public static int spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer trackEntity) { ++ // Paper end - Optional per player mob spawns + StructureManager structuremanager = world.structureManager(); + ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); + int i = pos.getY(); + BlockState iblockdata = world.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn ++ int j = 0; // Paper - Optional per player mob spawns; moved up + + if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); +- int j = 0; ++ //int j = 0; // Paper - Optional per player mob spawns; moved up + int k = 0; + + while (k < 3) { +@@ -233,14 +278,14 @@ public final class NaturalSpawner { + // Paper start - PreCreatureSpawnEvent + PreSpawnStatus doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2); + if (doSpawning == PreSpawnStatus.ABORT) { +- return; ++ return j; // Paper - Optional per player mob spawns + } + if (doSpawning == PreSpawnStatus.SUCCESS && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) { + // Paper end - PreCreatureSpawnEvent + Mob entityinsentient = NaturalSpawner.getMobForSpawn(world, biomesettingsmobs_c.type); + + if (entityinsentient == null) { +- return; ++ return j; // Paper - Optional per player mob spawns + } + + entityinsentient.moveTo(d0, (double) i, d1, world.random.nextFloat() * 360.0F, 0.0F); +@@ -253,10 +298,15 @@ public final class NaturalSpawner { + ++j; + ++k1; + runner.run(entityinsentient, chunk); ++ // Paper start - Optional per player mob spawns ++ if (trackEntity != null) { ++ trackEntity.accept(entityinsentient); ++ } ++ // Paper end - Optional per player mob spawns + } + // CraftBukkit end +- if (j >= entityinsentient.getMaxSpawnClusterSize()) { +- return; ++ if (j >= entityinsentient.getMaxSpawnClusterSize() || j >= maxSpawns) { // Paper - Optional per player mob spawns ++ return j; // Paper - Optional per player mob spawns + } + + if (entityinsentient.isMaxGroupSizeReached(k1)) { +@@ -278,6 +328,7 @@ public final class NaturalSpawner { + } + + } ++ return j; // Paper - Optional per player mob spawns + } + + private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) { +@@ -573,7 +624,7 @@ public final class NaturalSpawner { + MobCategory enumcreaturetype = entitytypes.getCategory(); + + this.mobCategoryCounts.addTo(enumcreaturetype, 1); +- this.localMobCapCalculator.addMob(new ChunkPos(blockposition), enumcreaturetype); ++ if (this.localMobCapCalculator != null) this.localMobCapCalculator.addMob(new ChunkPos(blockposition), enumcreaturetype); // Paper - Optional per player mob spawns + } + + public int getSpawnableChunkCount() { +@@ -589,6 +640,7 @@ public final class NaturalSpawner { + int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; + // CraftBukkit end + ++ if (this.localMobCapCalculator == null) return this.mobCategoryCounts.getInt(enumcreaturetype) < i; // Paper - Optional per player mob spawns + return this.mobCategoryCounts.getInt(enumcreaturetype) >= i ? false : this.localMobCapCalculator.canSpawn(enumcreaturetype, chunkcoordintpair); + } + } diff --git a/patches/server/0995-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch b/patches/server/0995-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch deleted file mode 100644 index 6cb17eca1061..000000000000 --- a/patches/server/0995-Strip-raytracing-for-EntityLiving-hasLineOfSight.patch +++ /dev/null @@ -1,158 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Paul Sauve -Date: Sat, 31 Oct 2020 18:43:02 -0500 -Subject: [PATCH] Strip raytracing for EntityLiving#hasLineOfSight - -The BlockGetter#clip method is very wasteful in both allocations, -and in logic. While EntityLiving#hasLineOfSight provides static -parameters for collisions with blocks and fluids, the method still does -a lot of dynamic checks for both of these, which result in extra work. -As well, since the fluid collision option is set to NONE, the entire -fluid collision system is completely unneeded, yet used anyways. - -Copyright (C) 2020 Technove LLC - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index a55985205cbd5d318a15552816ce44560d323559..2b77cc316a8ca5bf75b4aa7f5e881d920bef094c 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3725,7 +3725,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - Vec3 vec3d1 = new Vec3(entity.getX(), entity.getEyeY(), entity.getZ()); - - // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists -- return vec3d1.distanceToSqr(vec3d) > 128.0D * 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; // Paper - Perf: Use distance squared -+ return vec3d1.distanceToSqr(vec3d) > 128.0D * 128.0D ? false : this.level().clipDirect(vec3d, vec3d1, net.minecraft.world.phys.shapes.CollisionContext.of(this)) == HitResult.Type.MISS; // Paper - Perf: Use distance squared & strip raytracing - } - } - -diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java -index bb8e962e63c7a2d931f9bd7f7c002aa35cfa5fd3..0fa131a6c98adb498fc8d534e0e39647e80c6923 100644 ---- a/src/main/java/net/minecraft/world/level/BlockGetter.java -+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java -@@ -68,6 +68,18 @@ public interface BlockGetter extends LevelHeightAccessor { - }); - } - -+ // Paper start - Broken down variant of the method below, used by Level#clipDirect -+ @Nullable -+ default BlockHitResult.Type clipDirect(Vec3 start, Vec3 end, BlockPos pos, BlockState state, net.minecraft.world.phys.shapes.CollisionContext collisionContext) { -+ if (state.isAir()) { -+ return null; -+ } -+ -+ final VoxelShape voxelshape = ClipContext.Block.COLLIDER.get(state, this, pos, collisionContext); -+ final BlockHitResult hitResult = this.clipWithInteractionOverride(start, end, pos, voxelshape, state); -+ return hitResult == null ? null : hitResult.getType(); -+ } -+ // Paper end - // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace - default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) { - // Paper start - Add predicate for blocks when raytracing -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 5f98f3e1bc76076278cbe63d5fbb8ec75b3bf04b..29b6494d17bc8b9926244c286e05c821a6297f7b 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -329,10 +329,87 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return null; - } - -- // Paper start -+ // Paper start - Broken down method of raytracing for EntityLiving#hasLineOfSight, replaces BlockGetter#clip(CollisionContext) - public net.minecraft.world.phys.BlockHitResult.Type clipDirect(Vec3 start, Vec3 end, net.minecraft.world.phys.shapes.CollisionContext context) { -- // To be patched over -- return this.clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, context)).getType(); -+ // most of this code comes from BlockGetter#clip(CollisionContext, BiFunction, Function), but removes the needless functions -+ if (start.equals(end)) { -+ return net.minecraft.world.phys.BlockHitResult.Type.MISS; -+ } -+ -+ final double endX = Mth.lerp(-1.0E-7D, end.x, start.x); -+ final double endY = Mth.lerp(-1.0E-7D, end.y, start.y); -+ final double endZ = Mth.lerp(-1.0E-7D, end.z, start.z); -+ -+ final double startX = Mth.lerp(-1.0E-7D, start.x, end.x); -+ final double startY = Mth.lerp(-1.0E-7D, start.y, end.y); -+ final double startZ = Mth.lerp(-1.0E-7D, start.z, end.z); -+ -+ int currentX = Mth.floor(startX); -+ int currentY = Mth.floor(startY); -+ int currentZ = Mth.floor(startZ); -+ -+ final BlockPos.MutableBlockPos currentBlock = new BlockPos.MutableBlockPos(currentX, currentY, currentZ); -+ -+ LevelChunk chunk = this.getChunkIfLoaded(currentBlock); -+ if (chunk == null) { -+ return net.minecraft.world.phys.BlockHitResult.Type.MISS; -+ } -+ -+ final net.minecraft.world.phys.BlockHitResult.Type initialCheck = this.clipDirect(start, end, currentBlock, chunk.getBlockState(currentBlock), context); -+ if (initialCheck != null) { -+ return initialCheck; -+ } -+ -+ final double diffX = endX - startX; -+ final double diffY = endY - startY; -+ final double diffZ = endZ - startZ; -+ -+ final int xDirection = Mth.sign(diffX); -+ final int yDirection = Mth.sign(diffY); -+ final int zDirection = Mth.sign(diffZ); -+ -+ final double normalizedX = xDirection == 0 ? Double.MAX_VALUE : (double) xDirection / diffX; -+ final double normalizedY = yDirection == 0 ? Double.MAX_VALUE : (double) yDirection / diffY; -+ final double normalizedZ = zDirection == 0 ? Double.MAX_VALUE : (double) zDirection / diffZ; -+ -+ double normalizedXDirection = normalizedX * (xDirection > 0 ? 1.0D - Mth.frac(startX) : Mth.frac(startX)); -+ double normalizedYDirection = normalizedY * (yDirection > 0 ? 1.0D - Mth.frac(startY) : Mth.frac(startY)); -+ double normalizedZDirection = normalizedZ * (zDirection > 0 ? 1.0D - Mth.frac(startZ) : Mth.frac(startZ)); -+ -+ net.minecraft.world.phys.BlockHitResult.Type result; -+ -+ do { -+ if (normalizedXDirection > 1.0D && normalizedYDirection > 1.0D && normalizedZDirection > 1.0D) { -+ return net.minecraft.world.phys.BlockHitResult.Type.MISS; -+ } -+ -+ if (normalizedXDirection < normalizedYDirection) { -+ if (normalizedXDirection < normalizedZDirection) { -+ currentX += xDirection; -+ normalizedXDirection += normalizedX; -+ } else { -+ currentZ += zDirection; -+ normalizedZDirection += normalizedZ; -+ } -+ } else if (normalizedYDirection < normalizedZDirection) { -+ currentY += yDirection; -+ normalizedYDirection += normalizedY; -+ } else { -+ currentZ += zDirection; -+ normalizedZDirection += normalizedZ; -+ } -+ -+ currentBlock.set(currentX, currentY, currentZ); -+ if (chunk.getPos().x != currentBlock.getX() >> 4 || chunk.getPos().z != currentBlock.getZ() >> 4) { -+ chunk = this.getChunkIfLoaded(currentBlock); -+ if (chunk == null) { -+ return net.minecraft.world.phys.BlockHitResult.Type.MISS; -+ } -+ } -+ result = this.clipDirect(start, end, currentBlock, chunk.getBlockState(currentBlock), context); -+ } while (result == null); -+ -+ return result; - } - // Paper end - diff --git a/patches/server/0996-Anti-Xray.patch b/patches/server/0996-Anti-Xray.patch new file mode 100644 index 000000000000..c1bbc40a81af --- /dev/null +++ b/patches/server/0996-Anti-Xray.patch @@ -0,0 +1,1636 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: stonar96 +Date: Thu, 25 Nov 2021 13:27:51 +0100 +Subject: [PATCH] Anti-Xray + + +diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e448c26327b5f6189c3c52e698cff66c8f9ad81a +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java +@@ -0,0 +1,51 @@ ++package com.destroystokyo.paper.antixray; ++ ++public final class BitStorageReader { ++ ++ private byte[] buffer; ++ private int bits; ++ private int mask; ++ private int longInBufferIndex; ++ private int bitInLongIndex; ++ private long current; ++ ++ public void setBuffer(byte[] buffer) { ++ this.buffer = buffer; ++ } ++ ++ public void setBits(int bits) { ++ this.bits = bits; ++ mask = (1 << bits) - 1; ++ } ++ ++ public void setIndex(int index) { ++ longInBufferIndex = index; ++ bitInLongIndex = 0; ++ init(); ++ } ++ ++ private void init() { ++ if (buffer.length > longInBufferIndex + 7) { ++ current = ((((long) buffer[longInBufferIndex]) << 56) ++ | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48) ++ | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40) ++ | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32) ++ | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24) ++ | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16) ++ | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8) ++ | (((long) buffer[longInBufferIndex + 7] & 0xff))); ++ } ++ } ++ ++ public int read() { ++ if (bitInLongIndex + bits > 64) { ++ bitInLongIndex = 0; ++ longInBufferIndex += 8; ++ init(); ++ } ++ ++ int value = (int) (current >>> bitInLongIndex) & mask; ++ bitInLongIndex += bits; ++ return value; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e4540ea278f2dc871cb6a3cb8897559bfd65e134 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java +@@ -0,0 +1,79 @@ ++package com.destroystokyo.paper.antixray; ++ ++public final class BitStorageWriter { ++ ++ private byte[] buffer; ++ private int bits; ++ private long mask; ++ private int longInBufferIndex; ++ private int bitInLongIndex; ++ private long current; ++ private boolean dirty; ++ ++ public void setBuffer(byte[] buffer) { ++ this.buffer = buffer; ++ } ++ ++ public void setBits(int bits) { ++ this.bits = bits; ++ mask = (1L << bits) - 1; ++ } ++ ++ public void setIndex(int index) { ++ longInBufferIndex = index; ++ bitInLongIndex = 0; ++ init(); ++ } ++ ++ private void init() { ++ if (buffer.length > longInBufferIndex + 7) { ++ current = ((((long) buffer[longInBufferIndex]) << 56) ++ | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48) ++ | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40) ++ | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32) ++ | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24) ++ | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16) ++ | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8) ++ | (((long) buffer[longInBufferIndex + 7] & 0xff))); ++ } ++ ++ dirty = false; ++ } ++ ++ public void flush() { ++ if (dirty && buffer.length > longInBufferIndex + 7) { ++ buffer[longInBufferIndex] = (byte) (current >> 56 & 0xff); ++ buffer[longInBufferIndex + 1] = (byte) (current >> 48 & 0xff); ++ buffer[longInBufferIndex + 2] = (byte) (current >> 40 & 0xff); ++ buffer[longInBufferIndex + 3] = (byte) (current >> 32 & 0xff); ++ buffer[longInBufferIndex + 4] = (byte) (current >> 24 & 0xff); ++ buffer[longInBufferIndex + 5] = (byte) (current >> 16 & 0xff); ++ buffer[longInBufferIndex + 6] = (byte) (current >> 8 & 0xff); ++ buffer[longInBufferIndex + 7] = (byte) (current & 0xff); ++ } ++ } ++ ++ public void write(int value) { ++ if (bitInLongIndex + bits > 64) { ++ flush(); ++ bitInLongIndex = 0; ++ longInBufferIndex += 8; ++ init(); ++ } ++ ++ current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex; ++ dirty = true; ++ bitInLongIndex += bits; ++ } ++ ++ public void skip() { ++ bitInLongIndex += bits; ++ ++ if (bitInLongIndex > 64) { ++ flush(); ++ bitInLongIndex = bits; ++ longInBufferIndex += 8; ++ init(); ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java +new file mode 100644 +index 0000000000000000000000000000000000000000..52d2e2b744f91914802506e52a07161729bbcf3a +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java +@@ -0,0 +1,45 @@ ++package com.destroystokyo.paper.antixray; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; ++import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.ServerPlayerGameMode; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.LevelChunk; ++ ++public class ChunkPacketBlockController { ++ ++ public static final ChunkPacketBlockController NO_OPERATION_INSTANCE = new ChunkPacketBlockController(); ++ ++ protected ChunkPacketBlockController() { ++ ++ } ++ ++ public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int chunkSectionY) { ++ return null; ++ } ++ ++ public boolean shouldModify(ServerPlayer player, LevelChunk chunk) { ++ return false; ++ } ++ ++ public ChunkPacketInfo getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { ++ return null; ++ } ++ ++ public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo chunkPacketInfo) { ++ chunkPacket.setReady(true); ++ } ++ ++ public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) { ++ ++ } ++ ++ public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { ++ ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e7fe98ea30ae6d0baea3ec1f9f98a89502a49a12 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java +@@ -0,0 +1,676 @@ ++package com.destroystokyo.paper.antixray; ++ ++import io.papermc.paper.configuration.WorldConfiguration; ++import io.papermc.paper.configuration.type.EngineMode; ++import java.util.ArrayList; ++import java.util.LinkedHashSet; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Set; ++import java.util.concurrent.Executor; ++import java.util.concurrent.ThreadLocalRandom; ++import java.util.function.IntSupplier; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; ++import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.ServerPlayerGameMode; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.biome.Biomes; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.EntityBlock; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.EmptyLevelChunk; ++import net.minecraft.world.level.chunk.GlobalPalette; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.MissingPaletteEntryException; ++import net.minecraft.world.level.chunk.Palette; ++import org.bukkit.Bukkit; ++ ++public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController { ++ ++ private static final Palette GLOBAL_BLOCKSTATE_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY); ++ private static final LevelChunkSection EMPTY_SECTION = null; ++ private final Executor executor; ++ private final EngineMode engineMode; ++ private final int maxBlockHeight; ++ private final int updateRadius; ++ private final boolean usePermission; ++ private final BlockState[] presetBlockStates; ++ private final BlockState[] presetBlockStatesFull; ++ private final BlockState[] presetBlockStatesStone; ++ private final BlockState[] presetBlockStatesDeepslate; ++ private final BlockState[] presetBlockStatesNetherrack; ++ private final BlockState[] presetBlockStatesEndStone; ++ private final int[] presetBlockStateBitsGlobal; ++ private final int[] presetBlockStateBitsStoneGlobal; ++ private final int[] presetBlockStateBitsDeepslateGlobal; ++ private final int[] presetBlockStateBitsNetherrackGlobal; ++ private final int[] presetBlockStateBitsEndStoneGlobal; ++ private final boolean[] solidGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()]; ++ private final boolean[] obfuscateGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()]; ++ private final LevelChunkSection[] emptyNearbyChunkSections = {EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION}; ++ private final int maxBlockHeightUpdatePosition; ++ ++ public ChunkPacketBlockControllerAntiXray(Level level, Executor executor) { ++ this.executor = executor; ++ WorldConfiguration.Anticheat.AntiXray paperWorldConfig = level.paperConfig().anticheat.antiXray; ++ engineMode = paperWorldConfig.engineMode; ++ maxBlockHeight = paperWorldConfig.maxBlockHeight >> 4 << 4; ++ updateRadius = paperWorldConfig.updateRadius; ++ usePermission = paperWorldConfig.usePermission; ++ List toObfuscate; ++ ++ if (engineMode == EngineMode.HIDE) { ++ toObfuscate = paperWorldConfig.hiddenBlocks; ++ presetBlockStates = null; ++ presetBlockStatesFull = null; ++ presetBlockStatesStone = new BlockState[]{Blocks.STONE.defaultBlockState()}; ++ presetBlockStatesDeepslate = new BlockState[]{Blocks.DEEPSLATE.defaultBlockState()}; ++ presetBlockStatesNetherrack = new BlockState[]{Blocks.NETHERRACK.defaultBlockState()}; ++ presetBlockStatesEndStone = new BlockState[]{Blocks.END_STONE.defaultBlockState()}; ++ presetBlockStateBitsGlobal = null; ++ presetBlockStateBitsStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.STONE.defaultBlockState())}; ++ presetBlockStateBitsDeepslateGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.DEEPSLATE.defaultBlockState())}; ++ presetBlockStateBitsNetherrackGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.NETHERRACK.defaultBlockState())}; ++ presetBlockStateBitsEndStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.END_STONE.defaultBlockState())}; ++ } else { ++ toObfuscate = new ArrayList<>(paperWorldConfig.replacementBlocks); ++ List presetBlockStateList = new LinkedList<>(); ++ ++ for (Block block : paperWorldConfig.hiddenBlocks) { ++ ++ if (!(block instanceof EntityBlock)) { ++ toObfuscate.add(block); ++ presetBlockStateList.add(block.defaultBlockState()); ++ } ++ } ++ ++ // The doc of the LinkedHashSet(Collection) constructor doesn't specify that the insertion order is the predictable iteration order of the specified Collection, although it is in the implementation ++ Set presetBlockStateSet = new LinkedHashSet<>(); ++ // Therefore addAll(Collection) is used, which guarantees this order in the doc ++ presetBlockStateSet.addAll(presetBlockStateList); ++ presetBlockStates = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateSet.toArray(new BlockState[0]); ++ presetBlockStatesFull = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateList.toArray(new BlockState[0]); ++ presetBlockStatesStone = null; ++ presetBlockStatesDeepslate = null; ++ presetBlockStatesNetherrack = null; ++ presetBlockStatesEndStone = null; ++ presetBlockStateBitsGlobal = new int[presetBlockStatesFull.length]; ++ ++ for (int i = 0; i < presetBlockStatesFull.length; i++) { ++ presetBlockStateBitsGlobal[i] = GLOBAL_BLOCKSTATE_PALETTE.idFor(presetBlockStatesFull[i]); ++ } ++ ++ presetBlockStateBitsStoneGlobal = null; ++ presetBlockStateBitsDeepslateGlobal = null; ++ presetBlockStateBitsNetherrackGlobal = null; ++ presetBlockStateBitsEndStoneGlobal = null; ++ } ++ ++ for (Block block : toObfuscate) { ++ ++ // Don't obfuscate air because air causes unnecessary block updates and causes block updates to fail in the void ++ if (block != null && !block.defaultBlockState().isAir()) { ++ // Replace all block states of a specified block ++ for (BlockState blockState : block.getStateDefinition().getPossibleStates()) { ++ obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = true; ++ } ++ } ++ } ++ ++ EmptyLevelChunk emptyChunk = new EmptyLevelChunk(level, new ChunkPos(0, 0), MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.PLAINS)); ++ BlockPos zeroPos = new BlockPos(0, 0, 0); ++ ++ for (int i = 0; i < solidGlobal.length; i++) { ++ BlockState blockState = GLOBAL_BLOCKSTATE_PALETTE.valueFor(i); ++ ++ if (blockState != null) { ++ solidGlobal[i] = blockState.isRedstoneConductor(emptyChunk, zeroPos) ++ && blockState.getBlock() != Blocks.SPAWNER && blockState.getBlock() != Blocks.BARRIER && blockState.getBlock() != Blocks.SHULKER_BOX && blockState.getBlock() != Blocks.SLIME_BLOCK && blockState.getBlock() != Blocks.MANGROVE_ROOTS || paperWorldConfig.lavaObscures && blockState == Blocks.LAVA.defaultBlockState(); ++ // Comparing blockState == Blocks.LAVA.defaultBlockState() instead of blockState.getBlock() == Blocks.LAVA ensures that only "stationary lava" is used ++ // shulker box checks TE. ++ } ++ } ++ ++ maxBlockHeightUpdatePosition = maxBlockHeight + updateRadius - 1; ++ } ++ ++ private int getPresetBlockStatesFullLength() { ++ return engineMode == EngineMode.HIDE ? 1 : presetBlockStatesFull.length; ++ } ++ ++ @Override ++ public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int chunkSectionY) { ++ // Return the block states to be added to the paletted containers so that they can be used for obfuscation ++ int bottomBlockY = chunkSectionY << 4; ++ ++ if (bottomBlockY < maxBlockHeight) { ++ if (engineMode == EngineMode.HIDE) { ++ return switch (level.getWorld().getEnvironment()) { ++ case NETHER -> presetBlockStatesNetherrack; ++ case THE_END -> presetBlockStatesEndStone; ++ default -> bottomBlockY < 0 ? presetBlockStatesDeepslate : presetBlockStatesStone; ++ }; ++ } ++ ++ return presetBlockStates; ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public boolean shouldModify(ServerPlayer player, LevelChunk chunk) { ++ return !usePermission || !player.getBukkitEntity().hasPermission("paper.antixray.bypass"); ++ } ++ ++ @Override ++ public ChunkPacketInfoAntiXray getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { ++ // Return a new instance to collect data and objects in the right state while creating the chunk packet for thread safe access later ++ return new ChunkPacketInfoAntiXray(chunkPacket, chunk, this); ++ } ++ ++ @Override ++ public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo chunkPacketInfo) { ++ if (!(chunkPacketInfo instanceof ChunkPacketInfoAntiXray)) { ++ chunkPacket.setReady(true); ++ return; ++ } ++ ++ if (!Bukkit.isPrimaryThread()) { ++ // Plugins? ++ MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo)); ++ return; ++ } ++ ++ LevelChunk chunk = chunkPacketInfo.getChunk(); ++ int x = chunk.getPos().x; ++ int z = chunk.getPos().z; ++ Level level = chunk.getLevel(); ++ ((ChunkPacketInfoAntiXray) chunkPacketInfo).setNearbyChunks(level.getChunkIfLoaded(x - 1, z), level.getChunkIfLoaded(x + 1, z), level.getChunkIfLoaded(x, z - 1), level.getChunkIfLoaded(x, z + 1)); ++ executor.execute((Runnable) chunkPacketInfo); ++ } ++ ++ // Actually these fields should be variables inside the obfuscate method but in sync mode or with SingleThreadExecutor in async mode it's okay (even without ThreadLocal) ++ // If an ExecutorService with multiple threads is used, ThreadLocal must be used here ++ private final ThreadLocal presetBlockStateBits = ThreadLocal.withInitial(() -> new int[getPresetBlockStatesFullLength()]); ++ private static final ThreadLocal SOLID = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]); ++ private static final ThreadLocal OBFUSCATE = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]); ++ // These boolean arrays represent chunk layers, true means don't obfuscate, false means obfuscate ++ private static final ThreadLocal CURRENT = ThreadLocal.withInitial(() -> new boolean[16][16]); ++ private static final ThreadLocal NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]); ++ private static final ThreadLocal NEXT_NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]); ++ ++ public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) { ++ int[] presetBlockStateBits = this.presetBlockStateBits.get(); ++ boolean[] solid = SOLID.get(); ++ boolean[] obfuscate = OBFUSCATE.get(); ++ boolean[][] current = CURRENT.get(); ++ boolean[][] next = NEXT.get(); ++ boolean[][] nextNext = NEXT_NEXT.get(); ++ // bitStorageReader, bitStorageWriter and nearbyChunkSections could also be reused (with ThreadLocal if necessary) but it's not worth it ++ BitStorageReader bitStorageReader = new BitStorageReader(); ++ BitStorageWriter bitStorageWriter = new BitStorageWriter(); ++ LevelChunkSection[] nearbyChunkSections = new LevelChunkSection[4]; ++ LevelChunk chunk = chunkPacketInfoAntiXray.getChunk(); ++ Level level = chunk.getLevel(); ++ int maxChunkSectionIndex = Math.min((maxBlockHeight >> 4) - chunk.getMinSection(), chunk.getSectionsCount()) - 1; ++ boolean[] solidTemp = null; ++ boolean[] obfuscateTemp = null; ++ bitStorageReader.setBuffer(chunkPacketInfoAntiXray.getBuffer()); ++ bitStorageWriter.setBuffer(chunkPacketInfoAntiXray.getBuffer()); ++ int numberOfBlocks = presetBlockStateBits.length; ++ // Keep the lambda expressions as simple as possible. They are used very frequently. ++ LayeredIntSupplier random = numberOfBlocks == 1 ? (() -> 0) : engineMode == EngineMode.OBFUSCATE_LAYER ? new LayeredIntSupplier() { ++ // engine-mode: 3 ++ private int state; ++ private int next; ++ ++ { ++ while ((state = ThreadLocalRandom.current().nextInt()) == 0) ; ++ } ++ ++ @Override ++ public void nextLayer() { ++ // https://en.wikipedia.org/wiki/Xorshift ++ state ^= state << 13; ++ state ^= state >>> 17; ++ state ^= state << 5; ++ // https://www.pcg-random.org/posts/bounded-rands.html ++ next = (int) ((Integer.toUnsignedLong(state) * numberOfBlocks) >>> 32); ++ } ++ ++ @Override ++ public int getAsInt() { ++ return next; ++ } ++ } : new LayeredIntSupplier() { ++ // engine-mode: 2 ++ private int state; ++ ++ { ++ while ((state = ThreadLocalRandom.current().nextInt()) == 0) ; ++ } ++ ++ @Override ++ public int getAsInt() { ++ // https://en.wikipedia.org/wiki/Xorshift ++ state ^= state << 13; ++ state ^= state >>> 17; ++ state ^= state << 5; ++ // https://www.pcg-random.org/posts/bounded-rands.html ++ return (int) ((Integer.toUnsignedLong(state) * numberOfBlocks) >>> 32); ++ } ++ }; ++ ++ for (int chunkSectionIndex = 0; chunkSectionIndex <= maxChunkSectionIndex; chunkSectionIndex++) { ++ if (chunkPacketInfoAntiXray.isWritten(chunkSectionIndex) && chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) != null) { ++ int[] presetBlockStateBitsTemp; ++ ++ if (chunkPacketInfoAntiXray.getPalette(chunkSectionIndex) instanceof GlobalPalette) { ++ if (engineMode == EngineMode.HIDE) { ++ presetBlockStateBitsTemp = switch (level.getWorld().getEnvironment()) { ++ case NETHER -> presetBlockStateBitsNetherrackGlobal; ++ case THE_END -> presetBlockStateBitsEndStoneGlobal; ++ default -> chunkSectionIndex + chunk.getMinSection() < 0 ? presetBlockStateBitsDeepslateGlobal : presetBlockStateBitsStoneGlobal; ++ }; ++ } else { ++ presetBlockStateBitsTemp = presetBlockStateBitsGlobal; ++ } ++ } else { ++ // If it's presetBlockStates, use this.presetBlockStatesFull instead ++ BlockState[] presetBlockStatesFull = chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) == presetBlockStates ? this.presetBlockStatesFull : chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex); ++ presetBlockStateBitsTemp = presetBlockStateBits; ++ ++ for (int i = 0; i < presetBlockStateBitsTemp.length; i++) { ++ // This is thread safe because we only request IDs that are guaranteed to be in the palette and are visible ++ // For more details see the comments in the readPalette method ++ presetBlockStateBitsTemp[i] = chunkPacketInfoAntiXray.getPalette(chunkSectionIndex).idFor(presetBlockStatesFull[i]); ++ } ++ } ++ ++ bitStorageWriter.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex)); ++ ++ // Check if the chunk section below was not obfuscated ++ if (chunkSectionIndex == 0 || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex - 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex - 1) == null) { ++ // If so, initialize some stuff ++ bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex)); ++ bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex)); ++ solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), solid, solidGlobal); ++ obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), obfuscate, obfuscateGlobal); ++ // Read the blocks of the upper layer of the chunk section below if it exists ++ LevelChunkSection belowChunkSection = null; ++ boolean skipFirstLayer = chunkSectionIndex == 0 || (belowChunkSection = chunk.getSections()[chunkSectionIndex - 1]) == EMPTY_SECTION; ++ ++ for (int z = 0; z < 16; z++) { ++ for (int x = 0; x < 16; x++) { ++ current[z][x] = true; ++ next[z][x] = skipFirstLayer || isTransparent(belowChunkSection, x, 15, z); ++ } ++ } ++ ++ // Abuse the obfuscateLayer method to read the blocks of the first layer of the current chunk section ++ bitStorageWriter.setBits(0); ++ obfuscateLayer(-1, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, emptyNearbyChunkSections, random); ++ } ++ ++ bitStorageWriter.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex)); ++ nearbyChunkSections[0] = chunkPacketInfoAntiXray.getNearbyChunks()[0] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[0].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[1] = chunkPacketInfoAntiXray.getNearbyChunks()[1] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[1].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[2] = chunkPacketInfoAntiXray.getNearbyChunks()[2] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[2].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[3] = chunkPacketInfoAntiXray.getNearbyChunks()[3] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[3].getSections()[chunkSectionIndex]; ++ ++ // Obfuscate all layers of the current chunk section except the upper one ++ for (int y = 0; y < 15; y++) { ++ boolean[][] temp = current; ++ current = next; ++ next = nextNext; ++ nextNext = temp; ++ random.nextLayer(); ++ obfuscateLayer(y, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); ++ } ++ ++ // Check if the chunk section above doesn't need obfuscation ++ if (chunkSectionIndex == maxChunkSectionIndex || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex + 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex + 1) == null) { ++ // If so, obfuscate the upper layer of the current chunk section by reading blocks of the first layer from the chunk section above if it exists ++ LevelChunkSection aboveChunkSection; ++ ++ if (chunkSectionIndex != chunk.getSectionsCount() - 1 && (aboveChunkSection = chunk.getSections()[chunkSectionIndex + 1]) != EMPTY_SECTION) { ++ boolean[][] temp = current; ++ current = next; ++ next = nextNext; ++ nextNext = temp; ++ ++ for (int z = 0; z < 16; z++) { ++ for (int x = 0; x < 16; x++) { ++ if (isTransparent(aboveChunkSection, x, 0, z)) { ++ current[z][x] = true; ++ } ++ } ++ } ++ ++ // There is nothing to read anymore ++ bitStorageReader.setBits(0); ++ solid[0] = true; ++ random.nextLayer(); ++ obfuscateLayer(15, bitStorageReader, bitStorageWriter, solid, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); ++ } ++ } else { ++ // If not, initialize the reader and other stuff for the chunk section above to obfuscate the upper layer of the current chunk section ++ bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex + 1)); ++ bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex + 1)); ++ solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), solid, solidGlobal); ++ obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), obfuscate, obfuscateGlobal); ++ boolean[][] temp = current; ++ current = next; ++ next = nextNext; ++ nextNext = temp; ++ random.nextLayer(); ++ obfuscateLayer(15, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); ++ } ++ ++ bitStorageWriter.flush(); ++ } ++ } ++ ++ chunkPacketInfoAntiXray.getChunkPacket().setReady(true); ++ } ++ ++ private void obfuscateLayer(int y, BitStorageReader bitStorageReader, BitStorageWriter bitStorageWriter, boolean[] solid, boolean[] obfuscate, int[] presetBlockStateBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, LevelChunkSection[] nearbyChunkSections, IntSupplier random) { ++ // First block of first line ++ int bits = bitStorageReader.read(); ++ ++ if (nextNext[0][0] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[0][1] = true; ++ next[1][0] = true; ++ } else { ++ if (current[0][0] || isTransparent(nearbyChunkSections[2], 0, y, 15) || isTransparent(nearbyChunkSections[0], 15, y, 0)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[0][0] = true; ++ } ++ ++ // First line ++ for (int x = 1; x < 15; x++) { ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[0][x] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[0][x - 1] = true; ++ next[0][x + 1] = true; ++ next[1][x] = true; ++ } else { ++ if (current[0][x] || isTransparent(nearbyChunkSections[2], x, y, 15)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[0][x] = true; ++ } ++ } ++ ++ // Last block of first line ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[0][15] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[0][14] = true; ++ next[1][15] = true; ++ } else { ++ if (current[0][15] || isTransparent(nearbyChunkSections[2], 15, y, 15) || isTransparent(nearbyChunkSections[1], 0, y, 0)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[0][15] = true; ++ } ++ ++ // All inner lines ++ for (int z = 1; z < 15; z++) { ++ // First block ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[z][0] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[z][1] = true; ++ next[z - 1][0] = true; ++ next[z + 1][0] = true; ++ } else { ++ if (current[z][0] || isTransparent(nearbyChunkSections[0], 15, y, z)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[z][0] = true; ++ } ++ ++ // All inner blocks ++ for (int x = 1; x < 15; x++) { ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[z][x] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[z][x - 1] = true; ++ next[z][x + 1] = true; ++ next[z - 1][x] = true; ++ next[z + 1][x] = true; ++ } else { ++ if (current[z][x]) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[z][x] = true; ++ } ++ } ++ ++ // Last block ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[z][15] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[z][14] = true; ++ next[z - 1][15] = true; ++ next[z + 1][15] = true; ++ } else { ++ if (current[z][15] || isTransparent(nearbyChunkSections[1], 0, y, z)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[z][15] = true; ++ } ++ } ++ ++ // First block of last line ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[15][0] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[15][1] = true; ++ next[14][0] = true; ++ } else { ++ if (current[15][0] || isTransparent(nearbyChunkSections[3], 0, y, 0) || isTransparent(nearbyChunkSections[0], 15, y, 15)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[15][0] = true; ++ } ++ ++ // Last line ++ for (int x = 1; x < 15; x++) { ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[15][x] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[15][x - 1] = true; ++ next[15][x + 1] = true; ++ next[14][x] = true; ++ } else { ++ if (current[15][x] || isTransparent(nearbyChunkSections[3], x, y, 0)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[15][x] = true; ++ } ++ } ++ ++ // Last block of last line ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[15][15] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[15][14] = true; ++ next[14][15] = true; ++ } else { ++ if (current[15][15] || isTransparent(nearbyChunkSections[3], 15, y, 0) || isTransparent(nearbyChunkSections[1], 0, y, 15)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[15][15] = true; ++ } ++ } ++ ++ private boolean isTransparent(LevelChunkSection chunkSection, int x, int y, int z) { ++ if (chunkSection == EMPTY_SECTION) { ++ return true; ++ } ++ ++ try { ++ return !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(chunkSection.getBlockState(x, y, z))]; ++ } catch (MissingPaletteEntryException e) { ++ // Race condition / visibility issue / no happens-before relationship ++ // We don't care and treat the block as transparent ++ // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur ++ return true; ++ } ++ } ++ ++ private boolean[] readPalette(Palette palette, boolean[] temp, boolean[] global) { ++ if (palette instanceof GlobalPalette) { ++ return global; ++ } ++ ++ try { ++ for (int i = 0; i < palette.getSize(); i++) { ++ temp[i] = global[GLOBAL_BLOCKSTATE_PALETTE.idFor(palette.valueFor(i))]; ++ } ++ } catch (MissingPaletteEntryException e) { ++ // Race condition / visibility issue / no happens-before relationship ++ // We don't care because we at least see the state as it was when the chunk packet was created ++ // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur until we have all the data that we need here ++ // Since all palettes have a fixed initial maximum size and there is no internal restructuring and no values are removed from palettes, we are also guaranteed to see the data ++ } ++ ++ return temp; ++ } ++ ++ @Override ++ public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) { ++ if (oldBlockState != null && solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(oldBlockState)] && !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(newBlockState)] && blockPos.getY() <= maxBlockHeightUpdatePosition) { ++ updateNearbyBlocks(level, blockPos); ++ } ++ } ++ ++ @Override ++ public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { ++ if (blockPos.getY() <= maxBlockHeightUpdatePosition) { ++ updateNearbyBlocks(serverPlayerGameMode.level, blockPos); ++ } ++ } ++ ++ private void updateNearbyBlocks(Level level, BlockPos blockPos) { ++ if (updateRadius >= 2) { ++ BlockPos temp = blockPos.west(); ++ updateBlock(level, temp); ++ updateBlock(level, temp.west()); ++ updateBlock(level, temp.below()); ++ updateBlock(level, temp.above()); ++ updateBlock(level, temp.north()); ++ updateBlock(level, temp.south()); ++ updateBlock(level, temp = blockPos.east()); ++ updateBlock(level, temp.east()); ++ updateBlock(level, temp.below()); ++ updateBlock(level, temp.above()); ++ updateBlock(level, temp.north()); ++ updateBlock(level, temp.south()); ++ updateBlock(level, temp = blockPos.below()); ++ updateBlock(level, temp.below()); ++ updateBlock(level, temp.north()); ++ updateBlock(level, temp.south()); ++ updateBlock(level, temp = blockPos.above()); ++ updateBlock(level, temp.above()); ++ updateBlock(level, temp.north()); ++ updateBlock(level, temp.south()); ++ updateBlock(level, temp = blockPos.north()); ++ updateBlock(level, temp.north()); ++ updateBlock(level, temp = blockPos.south()); ++ updateBlock(level, temp.south()); ++ } else if (updateRadius == 1) { ++ updateBlock(level, blockPos.west()); ++ updateBlock(level, blockPos.east()); ++ updateBlock(level, blockPos.below()); ++ updateBlock(level, blockPos.above()); ++ updateBlock(level, blockPos.north()); ++ updateBlock(level, blockPos.south()); ++ } else { ++ // Do nothing if updateRadius <= 0 (test mode) ++ } ++ } ++ ++ private void updateBlock(Level level, BlockPos blockPos) { ++ BlockState blockState = level.getBlockStateIfLoaded(blockPos); ++ ++ if (blockState != null && obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)]) { ++ ((ServerLevel) level).getChunkSource().blockChanged(blockPos); ++ } ++ } ++ ++ @FunctionalInterface ++ private interface LayeredIntSupplier extends IntSupplier { ++ default void nextLayer() { ++ ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d98a3f5c54c67a673eb7dc456dd039cd78f9c34d +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java +@@ -0,0 +1,80 @@ ++package com.destroystokyo.paper.antixray; ++ ++import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.Palette; ++ ++public class ChunkPacketInfo { ++ ++ private final ClientboundLevelChunkWithLightPacket chunkPacket; ++ private final LevelChunk chunk; ++ private final int[] bits; ++ private final Object[] palettes; ++ private final int[] indexes; ++ private final Object[][] presetValues; ++ private byte[] buffer; ++ ++ public ChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { ++ this.chunkPacket = chunkPacket; ++ this.chunk = chunk; ++ int sections = chunk.getSectionsCount(); ++ bits = new int[sections]; ++ palettes = new Object[sections]; ++ indexes = new int[sections]; ++ presetValues = new Object[sections][]; ++ } ++ ++ public ClientboundLevelChunkWithLightPacket getChunkPacket() { ++ return chunkPacket; ++ } ++ ++ public LevelChunk getChunk() { ++ return chunk; ++ } ++ ++ public byte[] getBuffer() { ++ return buffer; ++ } ++ ++ public void setBuffer(byte[] buffer) { ++ this.buffer = buffer; ++ } ++ ++ public int getBits(int chunkSectionIndex) { ++ return bits[chunkSectionIndex]; ++ } ++ ++ public void setBits(int chunkSectionIndex, int bits) { ++ this.bits[chunkSectionIndex] = bits; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public Palette getPalette(int chunkSectionIndex) { ++ return (Palette) palettes[chunkSectionIndex]; ++ } ++ ++ public void setPalette(int chunkSectionIndex, Palette palette) { ++ palettes[chunkSectionIndex] = palette; ++ } ++ ++ public int getIndex(int chunkSectionIndex) { ++ return indexes[chunkSectionIndex]; ++ } ++ ++ public void setIndex(int chunkSectionIndex, int index) { ++ indexes[chunkSectionIndex] = index; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public T[] getPresetValues(int chunkSectionIndex) { ++ return (T[]) presetValues[chunkSectionIndex]; ++ } ++ ++ public void setPresetValues(int chunkSectionIndex, T[] presetValues) { ++ this.presetValues[chunkSectionIndex] = presetValues; ++ } ++ ++ public boolean isWritten(int chunkSectionIndex) { ++ return bits[chunkSectionIndex] != 0; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java +new file mode 100644 +index 0000000000000000000000000000000000000000..80a2dfb266ae1221680a7b24fee2f7e2a8330b7d +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java +@@ -0,0 +1,29 @@ ++package com.destroystokyo.paper.antixray; ++ ++import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.LevelChunk; ++ ++public final class ChunkPacketInfoAntiXray extends ChunkPacketInfo implements Runnable { ++ ++ private final ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray; ++ private LevelChunk[] nearbyChunks; ++ ++ public ChunkPacketInfoAntiXray(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk, ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray) { ++ super(chunkPacket, chunk); ++ this.chunkPacketBlockControllerAntiXray = chunkPacketBlockControllerAntiXray; ++ } ++ ++ public LevelChunk[] getNearbyChunks() { ++ return nearbyChunks; ++ } ++ ++ public void setNearbyChunks(LevelChunk... nearbyChunks) { ++ this.nearbyChunks = nearbyChunks; ++ } ++ ++ @Override ++ public void run() { ++ chunkPacketBlockControllerAntiXray.obfuscate(this); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java +index 6cff1a98dc7cf33947ec760dbc3d3d0ec5db5f6c..51f647de153255c919b1440338cf1b3e2d6b5dbf 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java +@@ -63,8 +63,10 @@ public record ClientboundChunksBiomesPacket(List blockEntitiesData; + +- public ClientboundLevelChunkPacketData(LevelChunk chunk) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public ClientboundLevelChunkPacketData(LevelChunk chunk) { this(chunk, null); } ++ public ClientboundLevelChunkPacketData(LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo) { ++ // Paper end + this.heightmaps = new CompoundTag(); + + for(Map.Entry entry : chunk.getHeightmaps()) { +@@ -35,7 +38,14 @@ public class ClientboundLevelChunkPacketData { + } + + this.buffer = new byte[calculateChunkSize(chunk)]; +- extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk); ++ ++ // Paper start - Anti-Xray - Add chunk packet info ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setBuffer(this.buffer); ++ } ++ ++ extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk, chunkPacketInfo); ++ // Paper end + this.blockEntitiesData = Lists.newArrayList(); + + for(Map.Entry entry2 : chunk.getBlockEntities().entrySet()) { +@@ -85,9 +95,15 @@ public class ClientboundLevelChunkPacketData { + return byteBuf; + } + +- public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { ClientboundLevelChunkPacketData.extractChunkData(buf, chunk, null); } ++ public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo) { ++ int chunkSectionIndex = 0; ++ + for(LevelChunkSection levelChunkSection : chunk.getSections()) { +- levelChunkSection.write(buf); ++ levelChunkSection.write(buf, chunkPacketInfo, chunkSectionIndex); ++ chunkSectionIndex++; ++ // Paper end + } + + } +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java +index 26e46d751c8f8162c2bafe2fc109fc91dc4b7c0f..6412dff5ed0505f62dd5b71ab9606257858a7317 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java +@@ -13,13 +13,30 @@ public class ClientboundLevelChunkWithLightPacket implements Packet chunkPacketInfo = modifyBlocks ? chunk.getLevel().chunkPacketBlockController.getChunkPacketInfo(this, chunk) : null; ++ this.chunkData = new ClientboundLevelChunkPacketData(chunk, chunkPacketInfo); ++ // Paper end + this.lightData = new ClientboundLightUpdatePacketData(chunkPos, lightProvider, skyBits, blockBits); ++ chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks + } + + public ClientboundLevelChunkWithLightPacket(FriendlyByteBuf buf) { +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 4357d45305cdf82659fcc0df9fa42b1ae1029cc1..811c9c7970dfef290acdf0bbd803b27ca81a4767 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -570,7 +570,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + // Holder holder = worlddimension.type(); // CraftBukkit - decompile error + + // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error +- super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess()))); // Paper - create paper world configs ++ super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess())), executor); // Paper - create paper world configs; Async-Anti-Xray: Pass executor + this.pvpMode = minecraftserver.isPvpAllowed(); + this.convertable = convertable_conversionsession; + this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 5063eb6d4a24600262c32d2c9eb5fb5bf8fa354e..692a01b52a71e26887ee42cbd5fd64b0a81bfc99 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -49,7 +49,7 @@ import org.bukkit.event.player.PlayerInteractEvent; + public class ServerPlayerGameMode { + + private static final Logger LOGGER = LogUtils.getLogger(); +- protected ServerLevel level; ++ public ServerLevel level; // Paper - Anti-Xray - protected -> public + protected final ServerPlayer player; + private GameType gameModeForPlayer; + @Nullable +@@ -326,6 +326,8 @@ public class ServerPlayerGameMode { + } + + } ++ ++ this.level.chunkPacketBlockController.onPlayerLeftClickBlock(this, pos, action, direction, worldHeight, sequence); // Paper - Anti-Xray + } + + public void destroyAndAck(BlockPos pos, int sequence, String reason) { +diff --git a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java +index f3b96a921e7d085b51da62fa5493384a7ded1f9d..12f2bf95d3ea3d29f6b4b9ec38a92f7102daa4a1 100644 +--- a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java ++++ b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java +@@ -88,7 +88,10 @@ public class PlayerChunkSender { + + public static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) { // Paper - rewrite chunk loader - public + handler.player.serverLevel().chunkSource.chunkMap.getVisibleChunkIfPresent(chunk.getPos().toLong()).addPlayer(handler.player); +- handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), (BitSet)null, (BitSet)null)); ++ // Paper start - Anti-Xray ++ final boolean shouldModify = world.chunkPacketBlockController.shouldModify(handler.player, chunk); ++ handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), (BitSet)null, (BitSet)null, shouldModify)); ++ // Paper end - Anti-Xray + // Paper start - PlayerChunkLoadEvent + if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { + new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), handler.getPlayer().getBukkitEntity()).callEvent(); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 23443444ae0c52392bd9cdd758057437d99e376a..10f2222c95362c41cab693410d0e7bb9746f3e4d 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -418,7 +418,7 @@ public abstract class PlayerList { + .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) ++ worldserver1.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null, true) + ); + } + // Paper end - Send empty chunk +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 6f2515d3476f7a5da898efb3c60e8f4aaad09b06..927c7948e567764e8cf75c7ce486e1ea6c9a8d87 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -172,6 +172,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + // Paper end - add paper world config + ++ public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final co.aikar.timings.WorldTimingsHandler timings; // Paper + public static BlockPos lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; +@@ -197,7 +198,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public abstract ResourceKey getTypeKey(); + +- protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator) { // Paper - create paper world config ++ protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - create paper world config; Async-Anti-Xray: Pass executor + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot + this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config + this.generator = gen; +@@ -283,6 +284,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.keepSpawnInMemory = this.paperConfig().spawn.keepSpawnLoaded; // Paper - Option to keep spawn chunks loaded + this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime); + this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); ++ this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + } + + // Paper start - Cancel hit for vanished players +@@ -558,6 +560,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // CraftBukkit end + + BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag ++ this.chunkPacketBlockController.onBlockChange(this, pos, state, iblockdata1, flags, maxUpdateDepth); // Paper - Anti-Xray + + if (iblockdata1 == null) { + // CraftBukkit start - remove blockstate if failed (or the same) +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +index 5e8d2e4245757a0889645ea79ee68afb53f7dde4..f7e5e016a7028a9196e689e950805b0d5b31fe38 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -152,17 +152,17 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom + } + } + +- ChunkAccess.replaceMissingSections(biomeRegistry, this.sections); ++ this.replaceMissingSections(biomeRegistry, this.sections); // Paper - Anti-Xray - make it a non-static method + // CraftBukkit start + this.biomeRegistry = biomeRegistry; + } + public final Registry biomeRegistry; + // CraftBukkit end + +- private static void replaceMissingSections(Registry biomeRegistry, LevelChunkSection[] sectionArray) { ++ private void replaceMissingSections(Registry biomeRegistry, LevelChunkSection[] sectionArray) { // Paper - Anti-Xray - static -> non-static + for (int i = 0; i < sectionArray.length; ++i) { + if (sectionArray[i] == null) { +- sectionArray[i] = new LevelChunkSection(biomeRegistry); ++ sectionArray[i] = new LevelChunkSection(biomeRegistry, this.levelHeightAccessor instanceof net.minecraft.world.level.Level ? (net.minecraft.world.level.Level) this.levelHeightAccessor : null, this.chunkPos, this.levelHeightAccessor.getSectionYFromSectionIndex(i)); // Paper start - Anti-Xray - Add parameters + } + } + +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 6a5756bd333d9b221e7770842e5114d295cb7f1d..2eeb0c78f2b717b59542b6b668371558ae2fcc25 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -91,7 +91,7 @@ public class LevelChunk extends ChunkAccess { + } + + public LevelChunk(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks blockTickScheduler, LevelChunkTicks fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) { +- super(pos, upgradeData, world, world.registryAccess().registryOrThrow(Registries.BIOME), inhabitedTime, sectionArrayInitializer, blendingData); ++ super(pos, upgradeData, world, net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME), inhabitedTime, sectionArrayInitializer, blendingData); // Paper - Anti-Xray - The world isn't ready yet, use server singleton for registry + this.tickersInLevel = Maps.newHashMap(); + this.level = (ServerLevel) world; // CraftBukkit - type + this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index b606e33f8b64eaba28c008cc353d88aa45549e31..8852263cb6faec1b68326145aa30e5cd36d066e7 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -33,9 +33,12 @@ public class LevelChunkSection { + this.recalcBlockCounts(); + } + +- public LevelChunkSection(Registry biomeRegistry) { +- this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); +- this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); ++ // Paper start - Anti-Xray - Add parameters ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public LevelChunkSection(Registry biomeRegistry) { this(biomeRegistry, null, null, 0); } ++ public LevelChunkSection(Registry biomeRegistry, net.minecraft.world.level.Level level, net.minecraft.world.level.ChunkPos chunkPos, int chunkSectionY) { ++ // Paper end ++ this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, level == null || level.chunkPacketBlockController == null ? null : level.chunkPacketBlockController.getPresetBlockStates(level, chunkPos, chunkSectionY)); // Paper - Anti-Xray - Add preset block states ++ this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes + } + + public BlockState getBlockState(int x, int y, int z) { +@@ -172,10 +175,13 @@ public class LevelChunkSection { + this.biomes = datapaletteblock; + } + +- public void write(FriendlyByteBuf buf) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); } ++ public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { + buf.writeShort(this.nonEmptyBlockCount); +- this.states.write(buf); +- this.biomes.write(buf); ++ this.states.write(buf, chunkPacketInfo, chunkSectionIndex); ++ this.biomes.write(buf, null, chunkSectionIndex); ++ // Paper end + } + + public int getSerializedSize() { +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 dfae0918079425df92d958b04275be8ae60d4b60..0f930f8355ea99d1cb1a8d27edc1c224588f852f 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -30,6 +30,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + return 0; + }; + public final IdMap registry; ++ private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values + private volatile PalettedContainer.Data data; + private final PalettedContainer.Strategy strategy; + // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused +@@ -42,14 +43,19 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + // this.threadingDetector.checkAndUnlock(); // Paper - disable this + } + +- public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { +- PalettedContainerRO.Unpacker> unpacker = PalettedContainer::unpack; ++ // Paper start - Anti-Xray - Add preset values ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { return PalettedContainer.codecRW(idList, entryCodec, paletteProvider, defaultValue, null); } ++ public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { ++ PalettedContainerRO.Unpacker> unpacker = (idListx, paletteProviderx, serialized) -> { ++ return unpack(idListx, paletteProviderx, serialized, defaultValue, presetValues); ++ }; ++ // Paper end + return codec(idList, entryCodec, paletteProvider, defaultValue, unpacker); + } + + public static Codec> codecRO(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { + PalettedContainerRO.Unpacker> unpacker = (idListx, paletteProviderx, serialized) -> { +- return unpack(idListx, paletteProviderx, serialized).map((result) -> { ++ return unpack(idListx, paletteProviderx, serialized, defaultValue, null).map((result) -> { // Paper - Anti-Xray - Add preset values + return result; + }); + }; +@@ -66,19 +72,52 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + }); + } + +- public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries) { ++ // Paper start - Anti-Xray - Add preset values ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries) { this(idList, paletteProvider, dataProvider, storage, paletteEntries, null, null); } ++ public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { ++ this.presetValues = presetValues; + this.registry = idList; + this.strategy = paletteProvider; + this.data = new PalettedContainer.Data<>(dataProvider, storage, dataProvider.factory().create(dataProvider.bits(), idList, this, paletteEntries)); ++ ++ if (presetValues != null && (dataProvider.factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY ? this.data.palette.valueFor(0) != defaultValue : dataProvider.factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY)) { ++ // In 1.18 Mojang unfortunately removed code that already handled possible resize operations on read from disk for us ++ // We readd this here but in a smarter way than it was before ++ int maxSize = 1 << dataProvider.bits(); ++ ++ for (T presetValue : presetValues) { ++ if (this.data.palette.getSize() >= maxSize) { ++ java.util.Set allValues = new java.util.HashSet<>(paletteEntries); ++ allValues.addAll(Arrays.asList(presetValues)); ++ int newBits = Mth.ceillog2(allValues.size()); ++ ++ if (newBits > dataProvider.bits()) { ++ this.onResize(newBits, null); ++ } ++ ++ break; ++ } ++ ++ this.data.palette.idFor(presetValue); ++ } ++ } ++ // Paper end + } + +- private PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data data) { ++ // Paper start - Anti-Xray - Add preset values ++ private PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data data, T @org.jetbrains.annotations.Nullable [] presetValues) { ++ this.presetValues = presetValues; ++ // Paper end + this.registry = idList; + this.strategy = paletteProvider; + this.data = data; + } + +- public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider) { ++ // Paper start - Anti-Xray - Add preset values ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider) { this(idList, object, paletteProvider, null); } ++ public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider, T @org.jetbrains.annotations.Nullable [] presetValues) { ++ this.presetValues = presetValues; ++ // Paper end + this.strategy = paletteProvider; + this.registry = idList; + this.data = this.createOrReuseData((PalettedContainer.Data)null, 0); +@@ -93,11 +132,33 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + @Override + public synchronized int onResize(int newBits, T object) { // Paper - synchronize + PalettedContainer.Data data = this.data; ++ ++ // Paper start - Anti-Xray - Add preset values ++ if (this.presetValues != null && object != null && data.configuration().factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY) { ++ int duplicates = 0; ++ List presetValues = Arrays.asList(this.presetValues); ++ duplicates += presetValues.contains(object) ? 1 : 0; ++ duplicates += presetValues.contains(data.palette.valueFor(0)) ? 1 : 0; ++ newBits = Mth.ceillog2((1 << this.strategy.calculateBitsForSerialization(this.registry, 1 << newBits)) + presetValues.size() - duplicates); ++ } ++ + PalettedContainer.Data data2 = this.createOrReuseData(data, newBits); + data2.copyFrom(data.palette, data.storage); + this.data = data2; +- return data2.palette.idFor(object); ++ this.addPresetValues(); ++ return object == null ? -1 : data2.palette.idFor(object); ++ // Paper end ++ } ++ ++ // Paper start - Anti-Xray - Add preset values ++ private void addPresetValues() { ++ if (this.presetValues != null && this.data.configuration().factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY) { ++ for (T presetValue : this.presetValues) { ++ this.data.palette.idFor(presetValue); ++ } ++ } + } ++ // Paper end + + public T getAndSet(int x, int y, int z, T value) { + this.acquire(); +@@ -167,25 +228,34 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + data.palette.read(buf); + buf.readLongArray(data.storage.getRaw()); + this.data = data; ++ this.addPresetValues(); // Paper - Anti-Xray - Add preset values (inefficient, but this isn't used by the server) + } finally { + this.release(); + } + + } + ++ // Paper start - Anti-Xray; Add chunk packet info ++ @Override ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); } + @Override +- public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize ++ public synchronized void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { // Paper - Synchronize + this.acquire(); + + try { +- this.data.write(buf); ++ this.data.write(buf, chunkPacketInfo, chunkSectionIndex); ++ ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setPresetValues(chunkSectionIndex, this.presetValues); ++ } ++ // Paper end + } finally { + this.release(); + } + + } + +- private static DataResult> unpack(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainerRO.PackedData serialized) { ++ private static DataResult> unpack(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainerRO.PackedData serialized, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { // Paper - Anti-Xray - Add preset values + List list = serialized.paletteEntries(); + int i = paletteProvider.size(); + int j = paletteProvider.calculateBitsForSerialization(idList, list.size()); +@@ -225,7 +295,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + } + +- return DataResult.success(new PalettedContainer<>(idList, paletteProvider, configuration, bitStorage, list)); ++ return DataResult.success(new PalettedContainer<>(idList, paletteProvider, configuration, bitStorage, list, defaultValue, presetValues)); // Paper - Anti-Xray - Add preset values + } + + @Override +@@ -285,12 +355,12 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + + public PalettedContainer copy() { +- return new PalettedContainer<>(this.registry, this.strategy, this.data.copy()); ++ return new PalettedContainer<>(this.registry, this.strategy, this.data.copy(), this.presetValues); // Paper - Anti-Xray - Add preset values + } + + @Override + public PalettedContainer recreate() { +- return new PalettedContainer<>(this.registry, this.data.palette.valueFor(0), this.strategy); ++ return new PalettedContainer<>(this.registry, this.data.palette.valueFor(0), this.strategy, this.presetValues); // Paper - Anti-Xray - Add preset values + } + + @Override +@@ -334,9 +404,18 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + return 1 + this.palette.getSerializedSize() + VarInt.getByteSize(this.storage.getRaw().length) + this.storage.getRaw().length * 8; + } + +- public void write(FriendlyByteBuf buf) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ public void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { + buf.writeByte(this.storage.getBits()); + this.palette.write(buf); ++ ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setBits(chunkSectionIndex, this.configuration.bits()); ++ chunkPacketInfo.setPalette(chunkSectionIndex, this.palette); ++ chunkPacketInfo.setIndex(chunkSectionIndex, buf.writerIndex() + VarInt.getByteSize(this.storage.getRaw().length)); ++ } ++ // Paper end ++ + buf.writeLongArray(this.storage.getRaw()); + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java +index 9a2bf744abd8916d492e901be889223591bac3fd..1dd415c96d17eff8e7555c33d3c52e57f2559fa5 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java +@@ -14,7 +14,10 @@ public interface PalettedContainerRO { + + void getAll(Consumer action); + +- void write(FriendlyByteBuf buf); ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated @io.papermc.paper.annotation.DoNotUse void write(FriendlyByteBuf buf); ++ void write(FriendlyByteBuf buf, @javax.annotation.Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex); ++ // Paper end + + int getSerializedSize(); + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index e67ebc8517a1afb0c7fe23f19a781942dded3241..539b36bde9cba3a44184eba36df9aa4c345a5b84 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -71,7 +71,7 @@ import org.slf4j.Logger; + + public class ChunkSerializer { + +- public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); ++ public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states + private static final Logger LOGGER = LogUtils.getLogger(); + private static final String TAG_UPGRADE_DATA = "UpgradeData"; + private static final String BLOCK_TICKS_TAG = "block_ticks"; +@@ -172,16 +172,20 @@ public class ChunkSerializer { + if (k >= 0 && k < achunksection.length) { + Logger logger; + PalettedContainer datapaletteblock; ++ // Paper start - Anti-Xray - Add preset block states ++ BlockState[] presetBlockStates = world.chunkPacketBlockController.getPresetBlockStates(world, chunkPos, b0); + + if (nbttagcompound1.contains("block_states", 10)) { +- dataresult = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> { ++ Codec> blockStateCodec = presetBlockStates == null ? ChunkSerializer.BLOCK_STATE_CODEC : PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), presetBlockStates); ++ dataresult = blockStateCodec.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> { + ChunkSerializer.logErrors(chunkPos, b0, s); + }); + logger = ChunkSerializer.LOGGER; + Objects.requireNonNull(logger); + datapaletteblock = (PalettedContainer) ((DataResult>) dataresult).getOrThrow(false, logger::error); // CraftBukkit - decompile error + } else { +- datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); ++ datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, presetBlockStates); ++ // Paper end + } + + PalettedContainer object; // CraftBukkit - read/write +@@ -194,7 +198,7 @@ public class ChunkSerializer { + Objects.requireNonNull(logger); + object = ((DataResult>>) dataresult).getOrThrow(false, logger::error); // CraftBukkit - decompile error + } else { +- object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); ++ object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes + } + + LevelChunkSection chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write +@@ -429,7 +433,7 @@ public class ChunkSerializer { + + // CraftBukkit start - read/write + private static Codec>> makeBiomeCodecRW(Registry iregistry) { +- return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS)); ++ return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS), null); // Paper - Anti-Xray - Add preset biomes + } + // CraftBukkit end + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index e47b00912fc76e9639f9c51d96e6d39da3c963e3..105f925fb1a78879d2eb618f0c672c8b9a759dd9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -55,7 +55,7 @@ public class CraftChunk implements Chunk { + private final ServerLevel worldServer; + private final int x; + private final int z; +- private static final PalettedContainer emptyBlockIDs = new PalettedContainer<>(net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); ++ private static final PalettedContainer emptyBlockIDs = new PalettedContainer<>(net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null); // Paper - Anti-Xray - Add preset block states + private static final byte[] FULL_LIGHT = new byte[2048]; + private static final byte[] EMPTY_LIGHT = new byte[2048]; + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index a76f966d72b749f1706363d33caa351b54e9fa14..c9137151f0d2978adb432c40da68689465d2325d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2622,7 +2622,7 @@ public final class CraftServer implements Server { + public ChunkGenerator.ChunkData createChunkData(World world) { + Preconditions.checkArgument(world != null, "World cannot be null"); + ServerLevel handle = ((CraftWorld) world).getHandle(); +- return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(Registries.BIOME)); ++ return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(Registries.BIOME), world); // Paper - Anti-Xray - Add parameters + } + + // Paper start - Allow delegation to vanilla chunk gen +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 49d8fd363391d085067253504c146c6fcf297d8f..bfb178c69026e9759e9afaebb9da141b62d1f144 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -530,11 +530,16 @@ public class CraftWorld extends CraftRegionAccessor implements World { + List playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false); + if (playersInRange.isEmpty()) return true; // Paper - rewrite player chunk loader + +- ClientboundLevelChunkWithLightPacket refreshPacket = new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null); ++ // Paper start - Anti-Xray - Bypass ++ Map refreshPackets = new HashMap<>(); + for (ServerPlayer player : playersInRange) { + if (player.connection == null) continue; + +- player.connection.send(refreshPacket); ++ Boolean shouldModify = chunk.getLevel().chunkPacketBlockController.shouldModify(player, chunk); ++ player.connection.send(refreshPackets.computeIfAbsent(shouldModify, s -> { // Use connection to prevent creating firing event ++ return new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null, (Boolean) s); ++ })); ++ // Paper end + } + // Paper - rewrite player chunk loader + +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java +index 9b640705f2c810160aa7fea5006429ec41d0c858..44a010590e830fd238cf6fdda443e28b72022e66 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java +@@ -27,8 +27,13 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData { + private final Registry biomes; + private Set tiles; + private final Set lights = new HashSet<>(); ++ // Paper start - Anti-Xray - Add parameters ++ private final org.bukkit.World world; + +- public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes) { ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes) { this(minHeight, maxHeight, biomes, null); } ++ public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes, org.bukkit.World world) { ++ this.world = world; ++ // Paper end + this.minHeight = minHeight; + this.maxHeight = maxHeight; + this.biomes = biomes; +@@ -176,7 +181,7 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData { + int offset = (y - this.minHeight) >> 4; + LevelChunkSection section = this.sections[offset]; + if (create && section == null) { +- this.sections[offset] = section = new LevelChunkSection(this.biomes); ++ this.sections[offset] = section = new LevelChunkSection(this.biomes, this.world instanceof org.bukkit.craftbukkit.CraftWorld ? ((org.bukkit.craftbukkit.CraftWorld) this.world).getHandle() : null, null, offset + (this.minHeight >> 4)); // Paper - Anti-Xray - Add parameters + } + return section; + } diff --git a/patches/server/0996-Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/patches/server/0996-Optimize-Network-Manager-and-add-advanced-packet-sup.patch deleted file mode 100644 index 4c83d00b5651..000000000000 --- a/patches/server/0996-Optimize-Network-Manager-and-add-advanced-packet-sup.patch +++ /dev/null @@ -1,394 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 6 May 2020 04:53:35 -0400 -Subject: [PATCH] Optimize Network Manager and add advanced packet support - -Adds ability for 1 packet to bundle other packets to follow it -Adds ability for a packet to delay sending more packets until a state is ready. - -Removes synchronization from sending packets -Removes processing packet queue off of main thread - - for the few cases where it is allowed, order is not necessary nor - should it even be happening concurrently in first place (handshaking/login/status) - -Ensures packets sent asynchronously are dispatched on main thread - -This helps ensure safety for ProtocolLib as packet listeners -are commonly accessing world state. This will allow you to schedule -a packet to be sent async, but itll be dispatched sync for packet -listeners to process. - -This should solve some deadlock risks - -Also adds Netty Channel Flush Consolidation to reduce the amount of flushing - -Also avoids spamming closed channel exception by rechecking closed state in dispatch -and then catch exceptions and close if they fire. - -Part of this commit was authored by: Spottedleaf, sandtechnology - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index e6a8f36fa07561b69b9d869022234182bdd62da0..a0434b92615c10a319eb4528808a83d01df2c516 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -84,7 +84,7 @@ public class Connection extends SimpleChannelInboundHandler> { - return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper - }); - private final PacketFlow receiving; -- private final Queue> pendingActions = Queues.newConcurrentLinkedQueue(); -+ private final Queue pendingActions = Queues.newConcurrentLinkedQueue(); - public Channel channel; - public SocketAddress address; - // Spigot Start -@@ -116,6 +116,10 @@ public class Connection extends SimpleChannelInboundHandler> { - public java.net.InetSocketAddress virtualHost; - private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush"); // Paper - Disable explicit network manager flushing - // Paper end -+ // Paper start - Optimize network -+ public boolean isPending = true; -+ public boolean queueImmunity; -+ // Paper end - Optimize network - - // Paper start - add utility methods - public final net.minecraft.server.level.ServerPlayer getPlayer() { -@@ -375,15 +379,39 @@ public class Connection extends SimpleChannelInboundHandler> { - } - - public void send(Packet packet, @Nullable PacketSendListener callbacks, boolean flush) { -- if (this.isConnected()) { -- this.flushQueue(); -+ // Paper start - Optimize network: Handle oversized packets better -+ final boolean connected = this.isConnected(); -+ if (!connected && !this.preparing) { -+ return; -+ } -+ -+ packet.onPacketDispatch(this.getPlayer()); -+ if (connected && (InnerUtil.canSendImmediate(this, packet) -+ || (io.papermc.paper.util.MCUtil.isMainThread() && packet.isReady() && this.pendingActions.isEmpty() -+ && (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty())))) { - this.sendPacket(packet, callbacks, flush); - } else { -- this.pendingActions.add((networkmanager) -> { -- networkmanager.sendPacket(packet, callbacks, flush); -- }); -- } -+ // Write the packets to the queue, then flush - antixray hooks there already -+ final java.util.List> extraPackets = InnerUtil.buildExtraPackets(packet); -+ final boolean hasExtraPackets = extraPackets != null && !extraPackets.isEmpty(); -+ if (!hasExtraPackets) { -+ this.pendingActions.add(new PacketSendAction(packet, callbacks, flush)); -+ } else { -+ final java.util.List actions = new java.util.ArrayList<>(1 + extraPackets.size()); -+ actions.add(new PacketSendAction(packet, null, false)); // Delay the future listener until the end of the extra packets - -+ for (int i = 0, len = extraPackets.size(); i < len;) { -+ final Packet extraPacket = extraPackets.get(i); -+ final boolean end = ++i == len; -+ actions.add(new PacketSendAction(extraPacket, end ? callbacks : null, end)); // Append listener to the end -+ } -+ -+ this.pendingActions.addAll(actions); -+ } -+ -+ this.flushQueue(); -+ // Paper end - Optimize network -+ } - } - - public void runOnceConnected(Consumer task) { -@@ -391,7 +419,7 @@ public class Connection extends SimpleChannelInboundHandler> { - this.flushQueue(); - task.accept(this); - } else { -- this.pendingActions.add(task); -+ this.pendingActions.add(new WrappedConsumer(task)); // Paper - Optimize network - } - - } -@@ -409,6 +437,14 @@ public class Connection extends SimpleChannelInboundHandler> { - } - - private void doSendPacket(Packet packet, @Nullable PacketSendListener callbacks, boolean flush) { -+ // Paper start - Optimize network -+ final net.minecraft.server.level.ServerPlayer player = this.getPlayer(); -+ if (!this.isConnected()) { -+ packet.onPacketDispatchFinish(player, null); -+ return; -+ } -+ try { -+ // Paper end - Optimize network - ChannelFuture channelfuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet); - - if (callbacks != null) { -@@ -428,14 +464,24 @@ public class Connection extends SimpleChannelInboundHandler> { - }); - } - -+ // Paper start - Optimize network -+ if (packet.hasFinishListener()) { -+ channelfuture.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture)); -+ } - channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); -+ } catch (final Exception e) { -+ LOGGER.error("NetworkException: {}", player, e); -+ this.disconnect(Component.translatable("disconnect.genericReason", "Internal Exception: " + e.getMessage())); -+ packet.onPacketDispatchFinish(player, null); -+ } -+ // Paper end - Optimize network - } - - public void flushChannel() { - if (this.isConnected()) { - this.flush(); - } else { -- this.pendingActions.add(Connection::flush); -+ this.pendingActions.add(new WrappedConsumer(Connection::flush)); // Paper - Optimize network - } - - } -@@ -468,20 +514,57 @@ public class Connection extends SimpleChannelInboundHandler> { - return attributekey; - } - -- private void flushQueue() { -- if (this.channel != null && this.channel.isOpen()) { -- Queue queue = this.pendingActions; -- -+ // Paper start - Optimize network: Rewrite this to be safer if ran off main thread -+ private boolean flushQueue() { -+ if (!this.isConnected()) { -+ return true; -+ } -+ if (io.papermc.paper.util.MCUtil.isMainThread()) { -+ return this.processQueue(); -+ } else if (this.isPending) { -+ // Should only happen during login/status stages - synchronized (this.pendingActions) { -- Consumer consumer; -+ return this.processQueue(); -+ } -+ } -+ return false; -+ } -+ -+ private boolean processQueue() { -+ if (this.pendingActions.isEmpty()) { -+ return true; -+ } - -- while ((consumer = (Consumer) this.pendingActions.poll()) != null) { -- consumer.accept(this); -+ // If we are on main, we are safe here in that nothing else should be processing queue off main anymore -+ // But if we are not on main due to login/status, the parent is synchronized on packetQueue -+ final java.util.Iterator iterator = this.pendingActions.iterator(); -+ while (iterator.hasNext()) { -+ final WrappedConsumer queued = iterator.next(); // poll -> peek -+ -+ // Fix NPE (Spigot bug caused by handleDisconnection()) -+ if (queued == null) { -+ return true; -+ } -+ -+ if (queued.isConsumed()) { -+ continue; -+ } -+ -+ if (queued instanceof PacketSendAction packetSendAction) { -+ final Packet packet = packetSendAction.packet; -+ if (!packet.isReady()) { -+ return false; - } -+ } - -+ iterator.remove(); -+ if (queued.tryMarkConsumed()) { -+ queued.accept(this); - } - } -+ return true; - } -+ // Paper end - Optimize network - - private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world - private static int joinAttemptsThisTick; // Paper - Buffer joins to world -@@ -544,6 +627,7 @@ public class Connection extends SimpleChannelInboundHandler> { - public void disconnect(Component disconnectReason) { - // Spigot Start - this.preparing = false; -+ this.clearPacketQueue(); // Paper - Optimize network - // Spigot End - if (this.channel == null) { - this.delayedDisconnect = disconnectReason; -@@ -715,7 +799,7 @@ public class Connection extends SimpleChannelInboundHandler> { - public void handleDisconnection() { - if (this.channel != null && !this.channel.isOpen()) { - if (this.disconnectionHandled) { -- Connection.LOGGER.warn("handleDisconnection() called twice"); -+ // Connection.LOGGER.warn("handleDisconnection() called twice"); // Paper - Don't log useless message - } else { - this.disconnectionHandled = true; - PacketListener packetlistener = this.getPacketListener(); -@@ -728,7 +812,7 @@ public class Connection extends SimpleChannelInboundHandler> { - - packetlistener1.onDisconnect(ichatbasecomponent); - } -- this.pendingActions.clear(); // Free up packet queue. -+ this.clearPacketQueue(); // Paper - Optimize network - // Paper start - Add PlayerConnectionCloseEvent - final PacketListener packetListener = this.getPacketListener(); - if (packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) { -@@ -765,4 +849,93 @@ public class Connection extends SimpleChannelInboundHandler> { - public void setBandwidthLogger(SampleLogger log) { - this.bandwidthDebugMonitor = new BandwidthDebugMonitor(log); - } -+ -+ // Paper start - Optimize network -+ public void clearPacketQueue() { -+ final net.minecraft.server.level.ServerPlayer player = getPlayer(); -+ for (final Consumer queuedAction : this.pendingActions) { -+ if (queuedAction instanceof PacketSendAction packetSendAction) { -+ final Packet packet = packetSendAction.packet; -+ if (packet.hasFinishListener()) { -+ packet.onPacketDispatchFinish(player, null); -+ } -+ } -+ } -+ this.pendingActions.clear(); -+ } -+ -+ private static class InnerUtil { // Attempt to hide these methods from ProtocolLib, so it doesn't accidently pick them up. -+ -+ @Nullable -+ private static java.util.List> buildExtraPackets(final Packet packet) { -+ final java.util.List> extra = packet.getExtraPackets(); -+ if (extra == null || extra.isEmpty()) { -+ return null; -+ } -+ -+ final java.util.List> ret = new java.util.ArrayList<>(1 + extra.size()); -+ buildExtraPackets0(extra, ret); -+ return ret; -+ } -+ -+ private static void buildExtraPackets0(final java.util.List> extraPackets, final java.util.List> into) { -+ for (final Packet extra : extraPackets) { -+ into.add(extra); -+ final java.util.List> extraExtra = extra.getExtraPackets(); -+ if (extraExtra != null && !extraExtra.isEmpty()) { -+ buildExtraPackets0(extraExtra, into); -+ } -+ } -+ } -+ -+ private static boolean canSendImmediate(final Connection networkManager, final net.minecraft.network.protocol.Packet packet) { -+ return networkManager.isPending || networkManager.packetListener.protocol() != ConnectionProtocol.PLAY || -+ packet instanceof net.minecraft.network.protocol.common.ClientboundKeepAlivePacket || -+ packet instanceof net.minecraft.network.protocol.game.ClientboundPlayerChatPacket || -+ packet instanceof net.minecraft.network.protocol.game.ClientboundSystemChatPacket || -+ packet instanceof net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket || -+ packet instanceof net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket || -+ packet instanceof net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket || -+ packet instanceof net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket || -+ packet instanceof net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket || -+ packet instanceof net.minecraft.network.protocol.game.ClientboundClearTitlesPacket || -+ packet instanceof net.minecraft.network.protocol.game.ClientboundSoundPacket || -+ packet instanceof net.minecraft.network.protocol.game.ClientboundSoundEntityPacket || -+ packet instanceof net.minecraft.network.protocol.game.ClientboundStopSoundPacket || -+ packet instanceof net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket || -+ packet instanceof net.minecraft.network.protocol.game.ClientboundBossEventPacket; -+ } -+ } -+ -+ private static class WrappedConsumer implements Consumer { -+ private final Consumer delegate; -+ private final java.util.concurrent.atomic.AtomicBoolean consumed = new java.util.concurrent.atomic.AtomicBoolean(false); -+ -+ private WrappedConsumer(final Consumer delegate) { -+ this.delegate = delegate; -+ } -+ -+ @Override -+ public void accept(final Connection connection) { -+ this.delegate.accept(connection); -+ } -+ -+ public boolean tryMarkConsumed() { -+ return consumed.compareAndSet(false, true); -+ } -+ -+ public boolean isConsumed() { -+ return consumed.get(); -+ } -+ } -+ -+ private static final class PacketSendAction extends WrappedConsumer { -+ private final Packet packet; -+ -+ private PacketSendAction(final Packet packet, @Nullable final PacketSendListener packetSendListener, final boolean flush) { -+ super(connection -> connection.sendPacket(packet, packetSendListener, flush)); -+ this.packet = packet; -+ } -+ } -+ // Paper end - Optimize network - } -diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java -index cc658a61065d5c0021a4b88fa58b40211b94f8ec..da11266a0a23f446196e6facf2c358cfcc18070f 100644 ---- a/src/main/java/net/minecraft/network/protocol/Packet.java -+++ b/src/main/java/net/minecraft/network/protocol/Packet.java -@@ -11,6 +11,30 @@ public interface Packet { - void handle(T listener); - - // Paper start -+ /** -+ * @param player Null if not at PLAY stage yet -+ */ -+ default void onPacketDispatch(@Nullable net.minecraft.server.level.ServerPlayer player) { -+ } -+ -+ /** -+ * @param player Null if not at PLAY stage yet -+ * @param future Can be null if packet was cancelled -+ */ -+ default void onPacketDispatchFinish(@Nullable net.minecraft.server.level.ServerPlayer player, @Nullable io.netty.channel.ChannelFuture future) {} -+ -+ default boolean hasFinishListener() { -+ return false; -+ } -+ -+ default boolean isReady() { -+ return true; -+ } -+ -+ @Nullable -+ default java.util.List> getExtraPackets() { -+ return null; -+ } - default boolean packetTooLarge(net.minecraft.network.Connection manager) { - return false; - } -diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -index 4f330a44c77a7ec3237a86fda04921a8c4a1c00f..a4a29a7ea0035ecf4c61ee8547a9eb24acb667d0 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -@@ -63,10 +63,12 @@ public class ServerConnectionListener { - final List connections = Collections.synchronizedList(Lists.newArrayList()); - // Paper start - prevent blocking on adding a new connection while the server is ticking - private final java.util.Queue pending = new java.util.concurrent.ConcurrentLinkedQueue<>(); -+ private static final boolean disableFlushConsolidation = Boolean.getBoolean("Paper.disableFlushConsolidate"); // Paper - Optimize network - private final void addPending() { - Connection connection; - while ((connection = pending.poll()) != null) { - connections.add(connection); -+ connection.isPending = false; // Paper - Optimize network - } - } - // Paper end - prevent blocking on adding a new connection while the server is ticking -@@ -114,6 +116,7 @@ public class ServerConnectionListener { - ; - } - -+ if (!disableFlushConsolidation) channel.pipeline().addFirst(new io.netty.handler.flush.FlushConsolidationHandler()); // Paper - Optimize network - ChannelPipeline channelpipeline = channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30)).addLast("legacy_query", new LegacyQueryHandler(ServerConnectionListener.this.getServer())); - - Connection.configureSerialization(channelpipeline, PacketFlow.SERVERBOUND, (BandwidthDebugMonitor) null); diff --git a/patches/server/0997-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch b/patches/server/0997-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch new file mode 100644 index 000000000000..0997fb9da217 --- /dev/null +++ b/patches/server/0997-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Mon, 5 Apr 2021 01:42:35 -0400 +Subject: [PATCH] Improve cancelling PreCreatureSpawnEvent with per player mob + spawns + + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 0d552d4b967687e2bfb92b1e5106071460082409..9037ba5197eed9d8e616fb65369f6b1a5ea9562c 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -306,8 +306,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + } + ++ // Paper start - per player mob count backoff ++ public void updateFailurePlayerMobTypeMap(int chunkX, int chunkZ, net.minecraft.world.entity.MobCategory mobCategory) { ++ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { ++ return; ++ } ++ int idx = mobCategory.ordinal(); ++ final com.destroystokyo.paper.util.maplist.ReferenceList inRange = ++ this.getNearbyPlayers().getPlayersByChunk(chunkX, chunkZ, io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE); ++ if (inRange == null) { ++ return; ++ } ++ final Object[] backingSet = inRange.getRawData(); ++ for (int i = 0, len = inRange.size(); i < len; i++) { ++ ++((ServerPlayer)backingSet[i]).mobBackoffCounts[idx]; ++ } ++ } ++ // Paper end - per player mob count backoff + public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) { +- return player.mobCounts[mobCategory.ordinal()]; ++ return player.mobCounts[mobCategory.ordinal()] + player.mobBackoffCounts[mobCategory.ordinal()]; // Paper - per player mob count backoff + } + // Paper end - Optional per player mob spawns + +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 059ab637adf1be576fa1fff36a91b6c5f1b5f035..5afbb5b307cc67d86dd916dc8f7521d5d021e056 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -524,7 +524,17 @@ public class ServerChunkCache extends ChunkSource { + if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled + // re-set mob counts + for (ServerPlayer player : this.level.players) { +- Arrays.fill(player.mobCounts, 0); ++ // Paper start - per player mob spawning backoff ++ for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) { ++ player.mobCounts[ii] = 0; ++ ++ int newBackoff = player.mobBackoffCounts[ii] - 1; // TODO make configurable bleed // TODO use nonlinear algorithm? ++ if (newBackoff < 0) { ++ newBackoff = 0; ++ } ++ player.mobBackoffCounts[ii] = newBackoff; ++ } ++ // Paper end - per player mob spawning backoff + } + spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); + } else { +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 651aceca9030e1f6060af9fc9f8d349dc0451728..acc9858e0cf10cb2aae0554037096411a208bd05 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -260,6 +260,7 @@ public class ServerPlayer extends Player { + public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length; + public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper + // Paper end - Optional per player mob spawns ++ public final int[] mobBackoffCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper - per player mob count backoff + + // CraftBukkit start + public String displayName; +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index 14f4ceb6c0be34d23b24c1695f966145c3aaee96..da7489986848316fed029b71d1bc4e1248c9c9a8 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -277,6 +277,11 @@ public final class NaturalSpawner { + + // Paper start - PreCreatureSpawnEvent + PreSpawnStatus doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2); ++ // Paper start - per player mob count backoff ++ if (doSpawning == PreSpawnStatus.ABORT || doSpawning == PreSpawnStatus.CANCELLED) { ++ world.getChunkSource().chunkMap.updateFailurePlayerMobTypeMap(blockposition_mutableblockposition.getX() >> 4, blockposition_mutableblockposition.getZ() >> 4, group); ++ } ++ // Paper end - per player mob count backoff + if (doSpawning == PreSpawnStatus.ABORT) { + return j; // Paper - Optional per player mob spawns + } diff --git a/patches/server/0998-Fix-World-isChunkGenerated-calls.patch b/patches/server/0998-Fix-World-isChunkGenerated-calls.patch deleted file mode 100644 index dd397e8f64d9..000000000000 --- a/patches/server/0998-Fix-World-isChunkGenerated-calls.patch +++ /dev/null @@ -1,244 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 15 Jun 2019 08:54:33 -0700 -Subject: [PATCH] Fix World#isChunkGenerated calls - -Optimize World#loadChunk() too -This patch also adds a chunk status cache on region files (note that -its only purpose is to cache the status on DISK) - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 5c1accb75655eadd4858ee24cdcdf9b200fbbcb2..42dde36273030494a6e7ff19e55d3b6a7da06fee 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -717,9 +717,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Paper end - - private CompletableFuture> readChunk(ChunkPos chunkPos) { -- return this.read(chunkPos).thenApplyAsync((optional) -> { -- return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit -- }, Util.backgroundExecutor()); -+ // Paper start - Cache chunk status on disk -+ try { -+ return CompletableFuture.completedFuture(Optional.ofNullable(this.readConvertChunkSync(chunkPos))); -+ } catch (Throwable thr) { -+ return CompletableFuture.failedFuture(thr); -+ } -+ // Paper end - Cache chunk status on disk - } - - // CraftBukkit start -@@ -728,6 +732,60 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // CraftBukkit end - } - -+ // Paper start - Cache chunk status on disk -+ @Nullable -+ public CompoundTag readConvertChunkSync(ChunkPos pos) throws IOException { -+ CompoundTag nbttagcompound = this.readSync(pos); -+ if (nbttagcompound == null) { -+ return null; -+ } -+ -+ nbttagcompound = this.upgradeChunkTag(nbttagcompound, pos); // CraftBukkit -+ if (nbttagcompound == null) { -+ return null; -+ } -+ -+ this.updateChunkStatusOnDisk(pos, nbttagcompound); -+ -+ return nbttagcompound; -+ } -+ -+ public ChunkStatus getChunkStatusOnDiskIfCached(ChunkPos chunkPos) { -+ net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFileIfLoaded(chunkPos); -+ -+ return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); -+ } -+ -+ public ChunkStatus getChunkStatusOnDisk(ChunkPos chunkPos) throws IOException { -+ net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, true); -+ -+ if (regionFile == null || !regionFileCache.chunkExists(chunkPos)) { -+ return null; -+ } -+ -+ ChunkStatus status = regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); -+ -+ if (status != null) { -+ return status; -+ } -+ -+ this.readChunk(chunkPos); -+ -+ return regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); -+ } -+ -+ public void updateChunkStatusOnDisk(ChunkPos chunkPos, @Nullable CompoundTag compound) throws IOException { -+ net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, false); -+ -+ regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkSerializer.getStatus(compound)); -+ } -+ -+ public ChunkAccess getUnloadingChunk(int chunkX, int chunkZ) { -+ ChunkHolder chunkHolder = io.papermc.paper.chunk.system.ChunkSystem.getUnloadingChunkHolder(this.level, chunkX, chunkZ); -+ return chunkHolder == null ? null : chunkHolder.getAvailableChunkNow(); -+ } -+ // Paper end - Cache chunk status on disk -+ - public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { // Paper - public - // Spigot start - return this.anyPlayerCloseEnoughForSpawning(pos, false); -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -index 6c89b92cac521808873e9e1eccc363695275cd7a..92ba75254f6ffca40abd5485dbb4789de59edebd 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -@@ -50,6 +50,30 @@ public class RegionFile implements AutoCloseable { - public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // Paper - public final Path regionFile; // Paper - -+ // Paper start - Cache chunk status -+ private final net.minecraft.world.level.chunk.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.ChunkStatus[32 * 32]; -+ -+ private boolean closed; -+ -+ // invoked on write/read -+ public void setStatus(int x, int z, net.minecraft.world.level.chunk.ChunkStatus status) { -+ if (this.closed) { -+ // We've used an invalid region file. -+ throw new IllegalStateException("RegionFile is closed"); -+ } -+ this.statuses[getChunkLocation(x, z)] = status; -+ } -+ -+ public net.minecraft.world.level.chunk.ChunkStatus getStatusIfCached(int x, int z) { -+ if (this.closed) { -+ // We've used an invalid region file. -+ throw new IllegalStateException("RegionFile is closed"); -+ } -+ final int location = getChunkLocation(x, z); -+ return this.statuses[location]; -+ } -+ // Paper end - Cache chunk status -+ - public RegionFile(Path file, Path directory, boolean dsync) throws IOException { - this(file, directory, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format - } -@@ -397,6 +421,7 @@ public class RegionFile implements AutoCloseable { - return this.getOffset(pos) != 0; - } - -+ private static int getChunkLocation(int x, int z) { return (x & 31) + (z & 31) * 32; } // Paper - Cache chunk status; OBFHELPER - sort of, mirror of logic below - private static int getOffsetIndex(ChunkPos pos) { - return pos.getRegionLocalX() + pos.getRegionLocalZ() * 32; - } -@@ -407,6 +432,7 @@ public class RegionFile implements AutoCloseable { - synchronized (this) { - try { - // Paper end -+ this.closed = true; // Paper - Cache chunk status - try { - this.padToFullSector(); - } finally { -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index f27cf743bbc379520263909541d653dd38d1be58..0db8ee3b640e6d1268e9c1cccda85459bd447105 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -266,6 +266,7 @@ public class RegionFileStorage implements AutoCloseable { - - try { - NbtIo.write(nbt, (DataOutput) dataoutputstream); -+ regionfile.setStatus(pos.x, pos.z, ChunkSerializer.getStatus(nbt)); // Paper - Cache chunk status - regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone - // Paper start - don't write garbage data to disk if writing serialization fails - dataoutputstream.close(); // Only write if successful -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 84ec87b889d0d450293310e18c34aef505a8147b..d1cff7794313fd29717e9d7818ccf00e340f08a9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -376,9 +376,23 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public boolean isChunkGenerated(int x, int z) { -+ // Paper start - Fix this method -+ if (!Bukkit.isPrimaryThread()) { -+ return java.util.concurrent.CompletableFuture.supplyAsync(() -> { -+ return CraftWorld.this.isChunkGenerated(x, z); -+ }, world.getChunkSource().mainThreadProcessor).join(); -+ } -+ ChunkAccess chunk = world.getChunkSource().getChunkAtImmediately(x, z); -+ if (chunk == null) { -+ chunk = world.getChunkSource().chunkMap.getUnloadingChunk(x, z); -+ } -+ if (chunk != null) { -+ return chunk instanceof ImposterProtoChunk || chunk instanceof net.minecraft.world.level.chunk.LevelChunk; -+ } - try { -- return this.isChunkLoaded(x, z) || this.world.getChunkSource().chunkMap.read(new ChunkPos(x, z)).get().isPresent(); -- } catch (InterruptedException | ExecutionException ex) { -+ return world.getChunkSource().chunkMap.getChunkStatusOnDisk(new ChunkPos(x, z)) == ChunkStatus.FULL; -+ } catch (java.io.IOException ex) { -+ // Paper end - Fix this method - throw new RuntimeException(ex); - } - } -@@ -530,20 +544,48 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public boolean loadChunk(int x, int z, boolean generate) { - org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot - warnUnsafeChunk("loading a faraway chunk", x, z); // Paper -- ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper -- -- // If generate = false, but the chunk already exists, we will get this back. -- if (chunk instanceof ImposterProtoChunk) { -- // We then cycle through again to get the full chunk immediately, rather than after the ticket addition -- chunk = this.world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true); -- } -- -- if (chunk instanceof net.minecraft.world.level.chunk.LevelChunk) { -- this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper -+ // Paper start - Optimize this method -+ ChunkPos chunkPos = new ChunkPos(x, z); -+ ChunkAccess immediate = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); -+ if (immediate != null) { -+ // Plugins should use plugin tickets instead of this method to keep a chunk perpetually loaded - return true; - } - -- return false; -+ if (!generate) { -+ immediate = world.getChunkSource().chunkMap.getUnloadingChunk(x, z); -+ if (immediate != null) { -+ if (!(immediate instanceof ImposterProtoChunk) && !(immediate instanceof net.minecraft.world.level.chunk.LevelChunk)) { -+ return false; // not full status -+ } -+ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper -+ world.getChunk(x, z); // make sure we're at ticket level 32 or lower -+ return true; -+ } -+ net.minecraft.world.level.chunk.storage.RegionFile file; -+ try { -+ file = world.getChunkSource().chunkMap.regionFileCache.getRegionFile(chunkPos, false); -+ } catch (java.io.IOException ex) { -+ throw new RuntimeException(ex); -+ } -+ -+ ChunkStatus status = file.getStatusIfCached(x, z); -+ if (!file.hasChunk(chunkPos) || (status != null && status != ChunkStatus.FULL)) { -+ return false; -+ } -+ -+ ChunkAccess chunk = world.getChunkSource().getChunk(x, z, ChunkStatus.EMPTY, true); -+ if (!(chunk instanceof ImposterProtoChunk) && !(chunk instanceof net.minecraft.world.level.chunk.LevelChunk)) { -+ return false; -+ } -+ -+ // fall through to load -+ // we do this so we do not re-read the chunk data on disk -+ } -+ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper -+ world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true); -+ return true; -+ // Paper end - Optimize this method - } - - @Override diff --git a/patches/server/0998-Optimize-Collision-to-not-load-chunks.patch b/patches/server/0998-Optimize-Collision-to-not-load-chunks.patch new file mode 100644 index 000000000000..3d403ed2985c --- /dev/null +++ b/patches/server/0998-Optimize-Collision-to-not-load-chunks.patch @@ -0,0 +1,111 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 02:37:57 -0400 +Subject: [PATCH] Optimize Collision to not load chunks + +The collision code takes an AABB and generates a cuboid of checks rather +than a cylinder, so at high velocity this can generate a lot of chunk checks. + +Treat an unloaded chunk as a collision for entities, and also for players if +the "prevent moving into unloaded chunks" setting is enabled. + +If that serting is not enabled, collisions will be ignored for players, since +movement will load only the chunk the player enters anyways and avoids loading +massive amounts of surrounding chunks due to large AABB lookups. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 10f2222c95362c41cab693410d0e7bb9746f3e4d..d5908d63b6dd23279951f0691d55c660b3a788a8 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -934,6 +934,7 @@ public abstract class PlayerList { + entityplayer1.setShiftKeyDown(false); + entityplayer1.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + ++ worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper + while (avoidSuffocation && !worldserver1.noCollision((Entity) entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { + // CraftBukkit end + entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index fc5be14cade9f9b6bf67e4b66d33290eeb20ff59..79f6cb643ca97617e25b96c73695393aed8daa6e 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -241,6 +241,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason + + public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper ++ public boolean collisionLoadChunks = false; // Paper + private CraftEntity bukkitEntity; + + public @org.jetbrains.annotations.Nullable net.minecraft.server.level.ChunkMap.TrackedEntity tracker; // Paper +diff --git a/src/main/java/net/minecraft/world/level/BlockCollisions.java b/src/main/java/net/minecraft/world/level/BlockCollisions.java +index f2c423154ed6a00882a46d93b69ed4f6ba73782c..a3eaf80b020c3bbc0306c5d17659ee661dfd275b 100644 +--- a/src/main/java/net/minecraft/world/level/BlockCollisions.java ++++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java +@@ -65,22 +65,41 @@ public class BlockCollisions extends AbstractIterator { + protected T computeNext() { + while(true) { + if (this.cursor.advance()) { +- int i = this.cursor.nextX(); +- int j = this.cursor.nextY(); +- int k = this.cursor.nextZ(); ++ int i = this.cursor.nextX(); final int x = i; // Paper ++ int j = this.cursor.nextY(); final int y = j; // Paper ++ int k = this.cursor.nextZ(); final int z = k; // Paper + int l = this.cursor.getNextType(); + if (l == 3) { + continue; + } ++ // Paper start - ensure we don't load chunks ++ final @Nullable Entity source = this.context instanceof net.minecraft.world.phys.shapes.EntityCollisionContext entityContext ? entityContext.getEntity() : null; ++ boolean far = source != null && io.papermc.paper.util.MCUtil.distanceSq(source.getX(), y, source.getZ(), x, y, z) > 14; ++ this.pos.set(x, y, z); + +- BlockGetter blockGetter = this.getChunk(i, k); +- if (blockGetter == null) { ++ BlockState blockState; ++ if (this.collisionGetter instanceof net.minecraft.server.level.WorldGenRegion) { ++ BlockGetter blockGetter = this.getChunk(x, z); ++ if (blockGetter == null) { ++ continue; ++ } ++ blockState = blockGetter.getBlockState(this.pos); ++ } else if ((!far && source instanceof net.minecraft.server.level.ServerPlayer) || (source != null && source.collisionLoadChunks)) { ++ blockState = this.collisionGetter.getBlockState(this.pos); ++ } else { ++ blockState = this.collisionGetter.getBlockStateIfLoaded(this.pos); ++ } ++ ++ if (blockState == null) { ++ if (!(source instanceof net.minecraft.server.level.ServerPlayer) || source.level().paperConfig().chunks.preventMovingIntoUnloadedChunks) { ++ return this.resultProvider.apply(new BlockPos.MutableBlockPos(x, y, z), Shapes.create(far ? source.getBoundingBox() : new AABB(new BlockPos(x, y, z)))); ++ } ++ // Paper end + continue; + } + +- this.pos.set(i, j, k); +- BlockState blockState = blockGetter.getBlockState(this.pos); +- if (this.onlySuffocatingBlocks && !blockState.isSuffocating(blockGetter, this.pos) || l == 1 && !blockState.hasLargeCollisionShape() || l == 2 && !blockState.is(Blocks.MOVING_PISTON)) { ++ // Paper - moved up ++ if (/*this.onlySuffocatingBlocks && (!blockState.isSuffocating(blockGetter, this.pos)) ||*/ l == 1 && !blockState.hasLargeCollisionShape() || l == 2 && !blockState.is(Blocks.MOVING_PISTON)) { // Paper - onlySuffocatingBlocks is only true on the client, so we don't care about it here + continue; + } + +diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java +index 140d10807a3a6806578cd203ba58383590c2f2c0..c476e37df8a75d77f5093b2a449e04f25ef2c2dd 100644 +--- a/src/main/java/net/minecraft/world/level/CollisionGetter.java ++++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java +@@ -44,11 +44,13 @@ public interface CollisionGetter extends BlockGetter { + } + + default boolean noCollision(@Nullable Entity entity, AABB box) { ++ try { if (entity != null) entity.collisionLoadChunks = true; // Paper + for(VoxelShape voxelShape : this.getBlockCollisions(entity, box)) { + if (!voxelShape.isEmpty()) { + return false; + } + } ++ } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper + + if (!this.getEntityCollisions(entity, box).isEmpty()) { + return false; diff --git a/patches/server/1005-Optimize-GoalSelector-Goal.Flag-Set-operations.patch b/patches/server/0999-Optimize-GoalSelector-Goal.Flag-Set-operations.patch similarity index 100% rename from patches/server/1005-Optimize-GoalSelector-Goal.Flag-Set-operations.patch rename to patches/server/0999-Optimize-GoalSelector-Goal.Flag-Set-operations.patch diff --git a/patches/server/1000-Entity-Activation-Range-2.0.patch b/patches/server/1000-Entity-Activation-Range-2.0.patch deleted file mode 100644 index f92f377c42cd..000000000000 --- a/patches/server/1000-Entity-Activation-Range-2.0.patch +++ /dev/null @@ -1,811 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 13 May 2016 01:38:06 -0400 -Subject: [PATCH] Entity Activation Range 2.0 - -Optimizes performance of Activation Range - -Adds many new configurations and a new wake up inactive system - -Fixes and adds new Immunities to improve gameplay behavior - -Adds water Mobs to activation range config and nerfs fish -Adds flying monsters to control ghast and phantoms -Adds villagers as separate config - -== AT == -public net.minecraft.world.entity.Entity isInsidePortal - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 5f3502b148588a76079c1d9f55e4203f6de56406..4357d45305cdf82659fcc0df9fa42b1ae1029cc1 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2,7 +2,6 @@ package net.minecraft.server.level; - - import com.google.common.annotations.VisibleForTesting; - import co.aikar.timings.TimingHistory; // Paper --import co.aikar.timings.Timings; // Paper - import com.google.common.collect.Lists; - import com.mojang.datafixers.DataFixer; - import com.mojang.datafixers.util.Pair; -@@ -1222,17 +1221,17 @@ public class ServerLevel extends Level implements WorldGenLevel { - ++TimingHistory.entityTicks; // Paper - timings - // Spigot start - co.aikar.timings.Timing timer; // Paper -- if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { -+ /*if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { // Paper - comment out - EAR 2, reimplement below - entity.tickCount++; - timer = entity.getType().inactiveTickTimer.startTiming(); try { // Paper - timings - entity.inactiveTick(); - } finally { timer.stopTiming(); } // Paper - return; -- } -+ }*/ // Paper - comment out EAR 2 - // Spigot end - // Paper start- timings -- TimingHistory.activatedEntityTicks++; -- timer = entity.getVehicle() != null ? entity.getType().passengerTickTimer.startTiming() : entity.getType().tickTimer.startTiming(); -+ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); -+ timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper - try { - // Paper end - timings - entity.setOldPosAndRot(); -@@ -1243,9 +1242,13 @@ public class ServerLevel extends Level implements WorldGenLevel { - return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); - }); - gameprofilerfiller.incrementCounter("tickNonPassenger"); -+ if (isActive) { // Paper - EAR 2 -+ TimingHistory.activatedEntityTicks++; - entity.tick(); - entity.postTick(); // CraftBukkit -+ } else { entity.inactiveTick(); } // Paper - EAR 2 - this.getProfiler().pop(); -+ } finally { timer.stopTiming(); } // Paper - timings - Iterator iterator = entity.getPassengers().iterator(); - - while (iterator.hasNext()) { -@@ -1253,13 +1256,18 @@ public class ServerLevel extends Level implements WorldGenLevel { - - this.tickPassenger(entity, entity1); - } -- } finally { timer.stopTiming(); } // Paper - timings -+ // } finally { timer.stopTiming(); } // Paper - timings - move up - - } - - private void tickPassenger(Entity vehicle, Entity passenger) { - if (!passenger.isRemoved() && passenger.getVehicle() == vehicle) { - if (passenger instanceof Player || this.entityTickList.contains(passenger)) { -+ // Paper - EAR 2 -+ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(passenger); -+ co.aikar.timings.Timing timer = isActive ? passenger.getType().passengerTickTimer.startTiming() : passenger.getType().passengerInactiveTickTimer.startTiming(); // Paper -+ try { -+ // Paper end - passenger.setOldPosAndRot(); - ++passenger.tickCount; - ProfilerFiller gameprofilerfiller = this.getProfiler(); -@@ -1268,8 +1276,17 @@ public class ServerLevel extends Level implements WorldGenLevel { - return BuiltInRegistries.ENTITY_TYPE.getKey(passenger.getType()).toString(); - }); - gameprofilerfiller.incrementCounter("tickPassenger"); -+ // Paper start - EAR 2 -+ if (isActive) { - passenger.rideTick(); - passenger.postTick(); // CraftBukkit -+ } else { -+ passenger.setDeltaMovement(Vec3.ZERO); -+ passenger.inactiveTick(); -+ // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary -+ vehicle.positionRider(passenger); -+ } -+ // Paper end - EAR 2 - gameprofilerfiller.pop(); - Iterator iterator = passenger.getPassengers().iterator(); - -@@ -1279,6 +1296,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.tickPassenger(passenger, entity2); - } - -+ } finally { timer.stopTiming(); }// Paper - EAR2 timings - } - } else { - passenger.stopRiding(); -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 43d6ed4ab09227ca23cbd51df9a42a5a3b909396..9ac9b5f9461e02d3d280125bb9b66c5c53ab35d7 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -411,6 +411,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - // Spigot end - protected int numCollisions = 0; // Paper - Cap entity collisions - public boolean fromNetherPortal; // Paper - Add option to nerf pigmen from nether portals -+ public long activatedImmunityTick = Integer.MIN_VALUE; // Paper - EAR -+ public boolean isTemporarilyActive; // Paper - EAR - public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one - // Paper start - Entity origin API - @javax.annotation.Nullable -@@ -1022,6 +1024,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } else { - this.wasOnFire = this.isOnFire(); - if (movementType == MoverType.PISTON) { -+ this.activatedTick = Math.max(this.activatedTick, MinecraftServer.currentTick + 20); // Paper -+ this.activatedImmunityTick = Math.max(this.activatedImmunityTick, MinecraftServer.currentTick + 20); // Paper - movement = this.limitPistonMovement(movement); - if (movement.equals(Vec3.ZERO)) { - return; -@@ -1034,6 +1038,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - this.stuckSpeedMultiplier = Vec3.ZERO; - this.setDeltaMovement(Vec3.ZERO); - } -+ // Paper start - ignore movement changes while inactive. -+ if (isTemporarilyActive && !(this instanceof ItemEntity || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) && movement == getDeltaMovement() && movementType == MoverType.SELF) { -+ setDeltaMovement(Vec3.ZERO); -+ this.level.getProfiler().pop(); -+ return; -+ } -+ // Paper end - - movement = this.maybeBackOffFromEdge(movement, movementType); - Vec3 vec3d1 = this.collide(movement); -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 1881deb9d8ffc884ba23843936615181f4220623..24629412d2b4acaa81788ce70412b03387cc777c 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -221,6 +221,19 @@ public abstract class Mob extends LivingEntity implements Targeting { - return this.lookControl; - } - -+ // Paper start -+ @Override -+ public void inactiveTick() { -+ super.inactiveTick(); -+ if (this.goalSelector.inactiveTick()) { -+ this.goalSelector.tick(); -+ } -+ if (this.targetSelector.inactiveTick()) { -+ this.targetSelector.tick(); -+ } -+ } -+ // Paper end -+ - public MoveControl getMoveControl() { - Entity entity = this.getControlledVehicle(); - -diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java -index d6393210cfee53685f83c8491bea8b9c13b01eea..3d95257d2203fe40bb1fab58ad2a1f9e815184a9 100644 ---- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java -+++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java -@@ -21,6 +21,7 @@ public abstract class PathfinderMob extends Mob { - } - - public org.bukkit.craftbukkit.entity.CraftCreature getBukkitCreature() { return (org.bukkit.craftbukkit.entity.CraftCreature) super.getBukkitEntity(); } // Paper -+ public BlockPos movingTarget = null; public BlockPos getMovingTarget() { return movingTarget; } // Paper - - public float getWalkTargetValue(BlockPos pos) { - return this.getWalkTargetValue(pos, this.level()); -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java -index 07c1ca01c38d5d7d0a95ad5004b5df9f4a222935..e5995d0db5dcfba59a873ff439601894fdacd556 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java -@@ -33,6 +33,7 @@ public class GoalSelector { - private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.class); - private int tickCount; - private int newGoalRate = 3; -+ private int curRate; - - public GoalSelector(Supplier profiler) { - this.profiler = profiler; -@@ -49,6 +50,20 @@ public class GoalSelector { - }); - } - -+ // Paper start -+ public boolean inactiveTick() { -+ this.curRate++; -+ return this.curRate % this.newGoalRate == 0; -+ } -+ public boolean hasTasks() { -+ for (WrappedGoal task : this.availableGoals) { -+ if (task.isRunning()) { -+ return true; -+ } -+ } -+ return false; -+ } -+ // Paper end - public void removeGoal(Goal goal) { - this.availableGoals.stream().filter((wrappedGoal) -> { - return wrappedGoal.getGoal() == goal; -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -index 9fc374c17f6b3ee4ab3c582d05e96321b772f2d6..07519c817cc6de04a98198c43a0c2b02ba3141eb 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -@@ -23,6 +23,14 @@ public abstract class MoveToBlockGoal extends Goal { - public MoveToBlockGoal(PathfinderMob mob, double speed, int range) { - this(mob, speed, range, 1); - } -+ // Paper start - activation range improvements -+ @Override -+ public void stop() { -+ super.stop(); -+ this.blockPos = BlockPos.ZERO; -+ this.mob.movingTarget = null; -+ } -+ // Paper end - - public MoveToBlockGoal(PathfinderMob mob, double speed, int range, int maxYDifference) { - this.mob = mob; -@@ -114,6 +122,7 @@ public abstract class MoveToBlockGoal extends Goal { - mutableBlockPos.setWithOffset(blockPos, m, k - 1, n); - if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level(), mutableBlockPos)) { - this.blockPos = mutableBlockPos; -+ this.mob.movingTarget = mutableBlockPos == BlockPos.ZERO ? null : mutableBlockPos.immutable(); // Paper - return true; - } - } -diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index 4a19e6b83147ae22ade70fdd445c5d7df3b07a0f..1aae466e3e334d7f4bbb3ea9365a255afcc3dd3a 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -227,17 +227,34 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - @Override - public void inactiveTick() { - // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :( -- if (this.level().spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) { -- this.customServerAiStep(); -+ // Paper start -+ if (this.getUnhappyCounter() > 0) { -+ this.setUnhappyCounter(this.getUnhappyCounter() - 1); - } -+ if (this.isEffectiveAi()) { -+ if (this.level().spigotConfig.tickInactiveVillagers) { -+ this.customServerAiStep(); -+ } else { -+ this.customServerAiStep(true); -+ } -+ } -+ maybeDecayGossip(); -+ // Paper end -+ - super.inactiveTick(); - } - // Spigot End - - @Override -+ @Deprecated // Paper - protected void customServerAiStep() { -+ // Paper start -+ this.customServerAiStep(false); -+ } -+ protected void customServerAiStep(final boolean inactive) { -+ // Paper end - this.level().getProfiler().push("villagerBrain"); -- this.getBrain().tick((ServerLevel) this.level(), this); -+ if (!inactive) this.getBrain().tick((ServerLevel) this.level(), this); // Paper - this.level().getProfiler().pop(); - if (this.assignProfessionWhenSpawned) { - this.assignProfessionWhenSpawned = false; -@@ -261,7 +278,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - this.lastTradedPlayer = null; - } - -- if (!this.isNoAi() && this.random.nextInt(100) == 0) { -+ if (!inactive && !this.isNoAi() && this.random.nextInt(100) == 0) { // Paper - Raid raid = ((ServerLevel) this.level()).getRaidAt(this.blockPosition()); - - if (raid != null && raid.isActive() && !raid.isOver()) { -@@ -272,6 +289,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - if (this.getVillagerData().getProfession() == VillagerProfession.NONE && this.isTrading()) { - this.stopTrading(); - } -+ if (inactive) return; // Paper - - super.customServerAiStep(); - } -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java b/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java -index b149e8bcac034bb3fc118a9adcb0de45e18ed5e9..fc35cfc9d045f3e5b6a50af1d0ba83b6e322091f 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java -@@ -52,6 +52,7 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper - if (bl != this.isEnabled()) { - this.setEnabled(bl); - } -+ this.immunize(); // Paper - - } - -@@ -89,10 +90,12 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper - - public boolean suckInItems() { - if (HopperBlockEntity.suckInItems(this.level(), this)) { -+ this.immunize(); // Paper - return true; - } else { - for(ItemEntity itemEntity : this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(0.25D, 0.0D, 0.25D), EntitySelector.ENTITY_STILL_ALIVE)) { - if (HopperBlockEntity.addItem(this, itemEntity)) { -+ this.immunize(); // Paper - return true; - } - } -@@ -122,4 +125,11 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper - public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { - return new HopperMenu(syncId, playerInventory, this); - } -+ -+ // Paper start -+ public void immunize() { -+ this.activatedImmunityTick = Math.max(this.activatedImmunityTick, net.minecraft.server.MinecraftServer.currentTick + 20); -+ } -+ // Paper end -+ - } -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 29b6494d17bc8b9926244c286e05c821a6297f7b..6f2515d3476f7a5da898efb3c60e8f4aaad09b06 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -157,6 +157,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates - public List captureDrops; - public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); -+ // Paper start -+ public int wakeupInactiveRemainingAnimals; -+ public int wakeupInactiveRemainingFlying; -+ public int wakeupInactiveRemainingMonsters; -+ public int wakeupInactiveRemainingVillagers; -+ // Paper end - public boolean populating; - public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot - // Paper start - add paper world config -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 b1061db1d9b3bfde61d5016e10556c4320095827..c71690dbc3dc52803945f1608f0ee3ba94146354 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 -@@ -143,6 +143,10 @@ public class PistonMovingBlockEntity extends BlockEntity { - } - - entity.setDeltaMovement(e, g, h); -+ // 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); -+ // Paper end - break; - } - } -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 90df7e83d9166c22a56a31db22d843768229b9ab..c39894e824334f1dc52e0466cf9d84f7e219be70 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -1,33 +1,43 @@ - package org.spigotmc; - -+import net.minecraft.core.BlockPos; - import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerChunkCache; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.entity.ExperienceOrb; -+import net.minecraft.world.entity.FlyingMob; - import net.minecraft.world.entity.LightningBolt; - import net.minecraft.world.entity.LivingEntity; -+import net.minecraft.world.entity.Mob; - import net.minecraft.world.entity.PathfinderMob; -+import net.minecraft.world.entity.ai.Brain; - import net.minecraft.world.entity.ambient.AmbientCreature; - import net.minecraft.world.entity.animal.Animal; -+import net.minecraft.world.entity.animal.Bee; - import net.minecraft.world.entity.animal.Sheep; -+import net.minecraft.world.entity.animal.WaterAnimal; -+import net.minecraft.world.entity.animal.horse.Llama; - import net.minecraft.world.entity.boss.EnderDragonPart; - import net.minecraft.world.entity.boss.enderdragon.EndCrystal; - import net.minecraft.world.entity.boss.enderdragon.EnderDragon; - import net.minecraft.world.entity.boss.wither.WitherBoss; - import net.minecraft.world.entity.item.PrimedTnt; - import net.minecraft.world.entity.monster.Creeper; --import net.minecraft.world.entity.monster.Monster; --import net.minecraft.world.entity.monster.Slime; -+import net.minecraft.world.entity.monster.Enemy; -+import net.minecraft.world.entity.monster.Pillager; - import net.minecraft.world.entity.npc.Villager; - import net.minecraft.world.entity.player.Player; - import net.minecraft.world.entity.projectile.AbstractArrow; - import net.minecraft.world.entity.projectile.AbstractHurtingProjectile; -+import net.minecraft.world.entity.projectile.EyeOfEnder; - import net.minecraft.world.entity.projectile.FireworkRocketEntity; - import net.minecraft.world.entity.projectile.ThrowableProjectile; - import net.minecraft.world.entity.projectile.ThrownTrident; - import net.minecraft.world.entity.raid.Raider; -+import co.aikar.timings.MinecraftTimings; -+import net.minecraft.world.entity.schedule.Activity; - import net.minecraft.world.level.Level; - import net.minecraft.world.phys.AABB; --import co.aikar.timings.MinecraftTimings; - - public class ActivationRange - { -@@ -44,6 +54,43 @@ public class ActivationRange - - AABB boundingBox = new AABB( 0, 0, 0, 0, 0, 0 ); - } -+ // Paper start -+ -+ static Activity[] VILLAGER_PANIC_IMMUNITIES = { -+ Activity.HIDE, -+ Activity.PRE_RAID, -+ Activity.RAID, -+ Activity.PANIC -+ }; -+ -+ private static int checkInactiveWakeup(Entity entity) { -+ Level world = entity.level(); -+ SpigotWorldConfig config = world.spigotConfig; -+ long inactiveFor = MinecraftServer.currentTick - entity.activatedTick; -+ if (entity.activationType == ActivationType.VILLAGER) { -+ if (inactiveFor > config.wakeUpInactiveVillagersEvery && world.wakeupInactiveRemainingVillagers > 0) { -+ world.wakeupInactiveRemainingVillagers--; -+ return config.wakeUpInactiveVillagersFor; -+ } -+ } else if (entity.activationType == ActivationType.ANIMAL) { -+ if (inactiveFor > config.wakeUpInactiveAnimalsEvery && world.wakeupInactiveRemainingAnimals > 0) { -+ world.wakeupInactiveRemainingAnimals--; -+ return config.wakeUpInactiveAnimalsFor; -+ } -+ } else if (entity.activationType == ActivationType.FLYING_MONSTER) { -+ if (inactiveFor > config.wakeUpInactiveFlyingEvery && world.wakeupInactiveRemainingFlying > 0) { -+ world.wakeupInactiveRemainingFlying--; -+ return config.wakeUpInactiveFlyingFor; -+ } -+ } else if (entity.activationType == ActivationType.MONSTER || entity.activationType == ActivationType.RAIDER) { -+ if (inactiveFor > config.wakeUpInactiveMonstersEvery && world.wakeupInactiveRemainingMonsters > 0) { -+ world.wakeupInactiveRemainingMonsters--; -+ return config.wakeUpInactiveMonstersFor; -+ } -+ } -+ return -1; -+ } -+ // Paper end - - static AABB maxBB = new AABB( 0, 0, 0, 0, 0, 0 ); - -@@ -56,10 +103,13 @@ public class ActivationRange - */ - public static ActivationType initializeEntityActivationType(Entity entity) - { -+ if (entity instanceof WaterAnimal) { return ActivationType.WATER; } // Paper -+ else if (entity instanceof Villager) { return ActivationType.VILLAGER; } // Paper -+ else if (entity instanceof FlyingMob && entity instanceof Enemy) { return ActivationType.FLYING_MONSTER; } // Paper - doing & Monster incase Flying no longer includes monster in future - if ( entity instanceof Raider ) - { - return ActivationType.RAIDER; -- } else if ( entity instanceof Monster || entity instanceof Slime ) -+ } else if ( entity instanceof Enemy ) // Paper - correct monster check - { - return ActivationType.MONSTER; - } else if ( entity instanceof PathfinderMob || entity instanceof AmbientCreature ) -@@ -80,10 +130,14 @@ public class ActivationRange - */ - public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config) - { -- if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange == 0 ) -- || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0 ) -- || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0 ) -- || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0 ) -+ if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange <= 0 ) -+ || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange <= 0 ) -+ || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange <= 0 ) -+ || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange <= 0 ) -+ || ( entity.activationType == ActivationType.VILLAGER && config.villagerActivationRange <= 0 ) // Paper -+ || ( entity.activationType == ActivationType.WATER && config.waterActivationRange <= 0 ) // Paper -+ || ( entity.activationType == ActivationType.FLYING_MONSTER && config.flyingMonsterActivationRange <= 0 ) // Paper -+ || entity instanceof EyeOfEnder // Paper - || entity instanceof Player - || entity instanceof ThrowableProjectile - || entity instanceof EnderDragon -@@ -116,10 +170,25 @@ public class ActivationRange - final int raiderActivationRange = world.spigotConfig.raiderActivationRange; - final int animalActivationRange = world.spigotConfig.animalActivationRange; - final int monsterActivationRange = world.spigotConfig.monsterActivationRange; -+ // Paper start -+ final int waterActivationRange = world.spigotConfig.waterActivationRange; -+ final int flyingActivationRange = world.spigotConfig.flyingMonsterActivationRange; -+ final int villagerActivationRange = world.spigotConfig.villagerActivationRange; -+ world.wakeupInactiveRemainingAnimals = Math.min(world.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals); -+ world.wakeupInactiveRemainingVillagers = Math.min(world.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers); -+ world.wakeupInactiveRemainingMonsters = Math.min(world.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters); -+ world.wakeupInactiveRemainingFlying = Math.min(world.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying); -+ final ServerChunkCache chunkProvider = (ServerChunkCache) world.getChunkSource(); -+ // Paper end - - int maxRange = Math.max( monsterActivationRange, animalActivationRange ); - maxRange = Math.max( maxRange, raiderActivationRange ); - maxRange = Math.max( maxRange, miscActivationRange ); -+ // Paper start -+ maxRange = Math.max( maxRange, flyingActivationRange ); -+ maxRange = Math.max( maxRange, waterActivationRange ); -+ maxRange = Math.max( maxRange, villagerActivationRange ); -+ // Paper end - maxRange = Math.min( ( world.spigotConfig.simulationDistance << 4 ) - 8, maxRange ); - - for ( Player player : world.players() ) -@@ -130,13 +199,30 @@ public class ActivationRange - continue; - } - -- ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, 256, maxRange ); -- ActivationType.MISC.boundingBox = player.getBoundingBox().inflate( miscActivationRange, 256, miscActivationRange ); -- ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, 256, raiderActivationRange ); -- ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, 256, animalActivationRange ); -- ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, 256, monsterActivationRange ); -+ // Paper start -+ int worldHeight = world.getHeight(); -+ ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, worldHeight, maxRange ); -+ ActivationType.MISC.boundingBox = player.getBoundingBox().inflate( miscActivationRange, worldHeight, miscActivationRange ); -+ ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, worldHeight, raiderActivationRange ); -+ ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, worldHeight, animalActivationRange ); -+ ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, worldHeight, monsterActivationRange ); -+ ActivationType.WATER.boundingBox = player.getBoundingBox().inflate( waterActivationRange, worldHeight, waterActivationRange ); -+ ActivationType.FLYING_MONSTER.boundingBox = player.getBoundingBox().inflate( flyingActivationRange, worldHeight, flyingActivationRange ); -+ ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate( villagerActivationRange, worldHeight, villagerActivationRange ); -+ // Paper end - -- world.getEntities().get(ActivationRange.maxBB, ActivationRange::activateEntity); -+ // Paper start -+ java.util.List entities = world.getEntities((Entity)null, ActivationRange.maxBB, null); -+ boolean tickMarkers = world.paperConfig().entities.markers.tick; // Paper - Configurable marker ticking -+ for (Entity entity : entities) { -+ // Paper start - Configurable marker ticking -+ if (!tickMarkers && entity instanceof net.minecraft.world.entity.Marker) { -+ continue; -+ } -+ // Paper end - Configurable marker ticking -+ ActivationRange.activateEntity(entity); -+ } -+ // Paper end - } - MinecraftTimings.entityActivationCheckTimer.stopTiming(); - } -@@ -169,60 +255,118 @@ public class ActivationRange - * @param entity - * @return - */ -- public static boolean checkEntityImmunities(Entity entity) -+ public static int checkEntityImmunities(Entity entity) // Paper - return # of ticks to get immunity - { -+ // Paper start -+ SpigotWorldConfig config = entity.level().spigotConfig; -+ int inactiveWakeUpImmunity = checkInactiveWakeup(entity); -+ if (inactiveWakeUpImmunity > -1) { -+ return inactiveWakeUpImmunity; -+ } -+ if (entity.getRemainingFireTicks() > 0) { -+ return 2; -+ } -+ if (entity.activatedImmunityTick >= MinecraftServer.currentTick) { -+ return 1; -+ } -+ long inactiveFor = MinecraftServer.currentTick - entity.activatedTick; -+ // Paper end - // quick checks. -- if ( entity.wasTouchingWater || entity.getRemainingFireTicks() > 0 ) -+ if ( (entity.activationType != ActivationType.WATER && entity.wasTouchingWater && entity.isPushedByFluid()) ) // Paper - { -- return true; -+ return 100; // Paper -+ } -+ // Paper start -+ if ( !entity.onGround() || entity.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D ) -+ { -+ return 100; - } -+ // Paper end - if ( !( entity instanceof AbstractArrow ) ) - { -- if ( !entity.onGround() || !entity.passengers.isEmpty() || entity.isPassenger() ) -+ if ( (!entity.onGround() && !(entity instanceof FlyingMob)) ) // Paper - remove passengers logic - { -- return true; -+ return 10; // Paper - } - } else if ( !( (AbstractArrow) entity ).inGround ) - { -- return true; -+ return 1; // Paper - } - // special cases. - if ( entity instanceof LivingEntity ) - { - LivingEntity living = (LivingEntity) entity; -- if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTime > 0 || living.activeEffects.size() > 0 ) -+ if ( living.onClimbable() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 || living.isFreezing()) // Paper - { -- return true; -+ return 1; // Paper - } -- if ( entity instanceof PathfinderMob && ( (PathfinderMob) entity ).getTarget() != null ) -+ if ( entity instanceof Mob && ((Mob) entity ).getTarget() != null) // Paper - { -- return true; -+ return 20; // Paper -+ } -+ // Paper start -+ if (entity instanceof Bee) { -+ Bee bee = (Bee)entity; -+ BlockPos movingTarget = bee.getMovingTarget(); -+ if (bee.isAngry() || -+ (bee.getHivePos() != null && bee.getHivePos().equals(movingTarget)) || -+ (bee.getSavedFlowerPos() != null && bee.getSavedFlowerPos().equals(movingTarget)) -+ ) { -+ return 20; -+ } - } -- if ( entity instanceof Villager && ( (Villager) entity ).canBreed() ) -+ if ( entity instanceof Villager ) { -+ Brain behaviorController = ((Villager) entity).getBrain(); -+ -+ if (config.villagersActiveForPanic) { -+ for (Activity activity : VILLAGER_PANIC_IMMUNITIES) { -+ if (behaviorController.isActive(activity)) { -+ return 20*5; -+ } -+ } -+ } -+ -+ if (config.villagersWorkImmunityAfter > 0 && inactiveFor >= config.villagersWorkImmunityAfter) { -+ if (behaviorController.isActive(Activity.WORK)) { -+ return config.villagersWorkImmunityFor; -+ } -+ } -+ } -+ if ( entity instanceof Llama && ( (Llama) entity ).inCaravan() ) - { -- return true; -+ return 1; - } -+ // Paper end - if ( entity instanceof Animal ) - { - Animal animal = (Animal) entity; - if ( animal.isBaby() || animal.isInLove() ) - { -- return true; -+ return 5; // Paper - } - if ( entity instanceof Sheep && ( (Sheep) entity ).isSheared() ) - { -- return true; -+ return 1; // Paper - } - } - if (entity instanceof Creeper && ((Creeper) entity).isIgnited()) { // isExplosive -- return true; -+ return 20; // Paper -+ } -+ // Paper start -+ if (entity instanceof Mob && ((Mob) entity).targetSelector.hasTasks() ) { -+ return 0; - } -+ if (entity instanceof Pillager) { -+ Pillager pillager = (Pillager) entity; -+ // TODO:? -+ } -+ // Paper end - } - // SPIGOT-6644: Otherwise the target refresh tick will be missed - if (entity instanceof ExperienceOrb) { -- return true; -+ return 20; // Paper - } -- return false; -+ return -1; // Paper - } - - /** -@@ -237,8 +381,19 @@ public class ActivationRange - if ( entity instanceof FireworkRocketEntity ) { - return true; - } -+ // Paper start - special case always immunities -+ // immunize brand new entities, dead entities, and portal scenarios -+ if (entity.defaultActivationState || entity.tickCount < 20*10 || !entity.isAlive() || entity.isInsidePortal || entity.portalCooldown > 0) { -+ return true; -+ } -+ // immunize leashed entities -+ if (entity instanceof Mob && ((Mob)entity).getLeashHolder() instanceof Player) { -+ return true; -+ } -+ // Paper end - -- boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState; -+ boolean isActive = entity.activatedTick >= MinecraftServer.currentTick; -+ entity.isTemporarilyActive = false; // Paper - - // Should this entity tick? - if ( !isActive ) -@@ -246,15 +401,19 @@ public class ActivationRange - if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 ) - { - // Check immunities every 20 ticks. -- if ( ActivationRange.checkEntityImmunities( entity ) ) -- { -- // Triggered some sort of immunity, give 20 full ticks before we check again. -- entity.activatedTick = MinecraftServer.currentTick + 20; -+ // Paper start -+ int immunity = checkEntityImmunities(entity); -+ if (immunity >= 0) { -+ entity.activatedTick = MinecraftServer.currentTick + immunity; -+ } else { -+ entity.isTemporarilyActive = true; - } -+ // Paper end - isActive = true; -+ - } - // Add a little performance juice to active entities. Skip 1/4 if not immune. -- } else if ( !entity.defaultActivationState && (entity.tickCount + entity.getId()) % 4 == 0 && !ActivationRange.checkEntityImmunities( entity ) ) // Paper - Ensure checking item movement is offset from Spigot's entity activation range check -+ } else if ( (entity.tickCount + entity.getId()) % 4 == 0 && ActivationRange.checkEntityImmunities( entity ) < 0 ) // Paper - { - isActive = false; - } -diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java -index 5485df1a1b59e81f4dcedd21dd972e1fd2759573..1cf6d4f854d89c515e48e1fb365eb95ff9340765 100644 ---- a/src/main/java/org/spigotmc/SpigotWorldConfig.java -+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java -@@ -211,14 +211,60 @@ public class SpigotWorldConfig - public int monsterActivationRange = 32; - public int raiderActivationRange = 48; - public int miscActivationRange = 16; -+ // Paper start -+ public int flyingMonsterActivationRange = 32; -+ public int waterActivationRange = 16; -+ public int villagerActivationRange = 32; -+ public int wakeUpInactiveAnimals = 4; -+ public int wakeUpInactiveAnimalsEvery = 60*20; -+ public int wakeUpInactiveAnimalsFor = 5*20; -+ public int wakeUpInactiveMonsters = 8; -+ public int wakeUpInactiveMonstersEvery = 20*20; -+ public int wakeUpInactiveMonstersFor = 5*20; -+ public int wakeUpInactiveVillagers = 4; -+ public int wakeUpInactiveVillagersEvery = 30*20; -+ public int wakeUpInactiveVillagersFor = 5*20; -+ public int wakeUpInactiveFlying = 8; -+ public int wakeUpInactiveFlyingEvery = 10*20; -+ public int wakeUpInactiveFlyingFor = 5*20; -+ public int villagersWorkImmunityAfter = 5*20; -+ public int villagersWorkImmunityFor = 20; -+ public boolean villagersActiveForPanic = true; -+ // Paper end - public boolean tickInactiveVillagers = true; - public boolean ignoreSpectatorActivation = false; - private void activationRange() - { -+ boolean hasAnimalsConfig = config.getInt("entity-activation-range.animals", this.animalActivationRange) != this.animalActivationRange; // Paper - this.animalActivationRange = this.getInt( "entity-activation-range.animals", this.animalActivationRange ); - this.monsterActivationRange = this.getInt( "entity-activation-range.monsters", this.monsterActivationRange ); - this.raiderActivationRange = this.getInt( "entity-activation-range.raiders", this.raiderActivationRange ); - this.miscActivationRange = this.getInt( "entity-activation-range.misc", this.miscActivationRange ); -+ // Paper start -+ this.waterActivationRange = this.getInt( "entity-activation-range.water", this.waterActivationRange ); -+ this.villagerActivationRange = this.getInt( "entity-activation-range.villagers", hasAnimalsConfig ? this.animalActivationRange : this.villagerActivationRange ); -+ this.flyingMonsterActivationRange = this.getInt( "entity-activation-range.flying-monsters", this.flyingMonsterActivationRange ); -+ -+ this.wakeUpInactiveAnimals = this.getInt("entity-activation-range.wake-up-inactive.animals-max-per-tick", this.wakeUpInactiveAnimals); -+ this.wakeUpInactiveAnimalsEvery = this.getInt("entity-activation-range.wake-up-inactive.animals-every", this.wakeUpInactiveAnimalsEvery); -+ this.wakeUpInactiveAnimalsFor = this.getInt("entity-activation-range.wake-up-inactive.animals-for", this.wakeUpInactiveAnimalsFor); -+ -+ this.wakeUpInactiveMonsters = this.getInt("entity-activation-range.wake-up-inactive.monsters-max-per-tick", this.wakeUpInactiveMonsters); -+ this.wakeUpInactiveMonstersEvery = this.getInt("entity-activation-range.wake-up-inactive.monsters-every", this.wakeUpInactiveMonstersEvery); -+ this.wakeUpInactiveMonstersFor = this.getInt("entity-activation-range.wake-up-inactive.monsters-for", this.wakeUpInactiveMonstersFor); -+ -+ this.wakeUpInactiveVillagers = this.getInt("entity-activation-range.wake-up-inactive.villagers-max-per-tick", this.wakeUpInactiveVillagers); -+ this.wakeUpInactiveVillagersEvery = this.getInt("entity-activation-range.wake-up-inactive.villagers-every", this.wakeUpInactiveVillagersEvery); -+ this.wakeUpInactiveVillagersFor = this.getInt("entity-activation-range.wake-up-inactive.villagers-for", this.wakeUpInactiveVillagersFor); -+ -+ this.wakeUpInactiveFlying = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-max-per-tick", this.wakeUpInactiveFlying); -+ this.wakeUpInactiveFlyingEvery = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-every", this.wakeUpInactiveFlyingEvery); -+ this.wakeUpInactiveFlyingFor = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-for", this.wakeUpInactiveFlyingFor); -+ -+ this.villagersWorkImmunityAfter = this.getInt( "entity-activation-range.villagers-work-immunity-after", this.villagersWorkImmunityAfter ); -+ this.villagersWorkImmunityFor = this.getInt( "entity-activation-range.villagers-work-immunity-for", this.villagersWorkImmunityFor ); -+ this.villagersActiveForPanic = this.getBoolean( "entity-activation-range.villagers-active-for-panic", this.villagersActiveForPanic ); -+ // Paper end - this.tickInactiveVillagers = this.getBoolean( "entity-activation-range.tick-inactive-villagers", this.tickInactiveVillagers ); - this.ignoreSpectatorActivation = this.getBoolean( "entity-activation-range.ignore-spectators", this.ignoreSpectatorActivation ); - this.log( "Entity Activation Range: An " + this.animalActivationRange + " / Mo " + this.monsterActivationRange + " / Ra " + this.raiderActivationRange + " / Mi " + this.miscActivationRange + " / Tiv " + this.tickInactiveVillagers + " / Isa " + this.ignoreSpectatorActivation ); diff --git a/patches/server/1006-Entity-load-save-limit-per-chunk.patch b/patches/server/1000-Entity-load-save-limit-per-chunk.patch similarity index 100% rename from patches/server/1006-Entity-load-save-limit-per-chunk.patch rename to patches/server/1000-Entity-load-save-limit-per-chunk.patch diff --git a/patches/server/1001-Fix-and-optimise-world-force-upgrading.patch b/patches/server/1001-Fix-and-optimise-world-force-upgrading.patch new file mode 100644 index 000000000000..e42149db917e --- /dev/null +++ b/patches/server/1001-Fix-and-optimise-world-force-upgrading.patch @@ -0,0 +1,382 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 20 May 2021 07:02:22 -0700 +Subject: [PATCH] Fix and optimise world force upgrading + +The WorldUpgrader class was incorrectly modified by +CB. It will store an IChunkLoader instance for all +dimension types in the world, but obviously with how +CB shifts around worlds only one dimension type exists +per world. But this would be OK if CB did this +change correctly. All IChunkLoader instances +will point to the same regionfiles. And all +IChunkLoader instances are going to be read from. + +This problem hasn't really been reported because +it relies on the persistent legacy data to be converted +as well to cause corruption. Why? Because the legacy +data is also shared, it will result in different +outputs from conversion (as once conversion for legacy +persistent data takes place, it is REMOVED - so the next +convert will _not_ have the data). Which means different +sizes on disk. Which means different regionfile sector +allocations. Which means there are 3 different possible +regionfile sector allocations in memory, and none of them +are going to be correct. + +I've fixed this by writing a world upgrader suited to +CB's changes to world folder format. It was brain dead +easy to add threading, so I did. + +== AT == +public net.minecraft.util.worldupdate.WorldUpgrader REGEX + +diff --git a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..513833c2ea23df5b079d157bc5cb89d5c9754c0b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java +@@ -0,0 +1,209 @@ ++package io.papermc.paper.world; ++ ++import com.mojang.datafixers.DataFixer; ++import com.mojang.serialization.Codec; ++import net.minecraft.SharedConstants; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.util.worldupdate.WorldUpgrader; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.chunk.ChunkGenerator; ++import net.minecraft.world.level.chunk.storage.ChunkStorage; ++import net.minecraft.world.level.chunk.storage.RegionFileStorage; ++import net.minecraft.world.level.dimension.DimensionType; ++import net.minecraft.world.level.dimension.LevelStem; ++import net.minecraft.world.level.levelgen.WorldGenSettings; ++import net.minecraft.world.level.storage.DimensionDataStorage; ++import net.minecraft.world.level.storage.LevelStorageSource; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++import java.io.File; ++import java.io.IOException; ++import java.text.DecimalFormat; ++import java.util.Optional; ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.Executors; ++import java.util.concurrent.ThreadFactory; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.function.Supplier; ++ ++public class ThreadedWorldUpgrader { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ ++ private final ResourceKey dimensionType; ++ private final String worldName; ++ private final File worldDir; ++ private final ExecutorService threadPool; ++ private final DataFixer dataFixer; ++ private final Optional>> generatorKey; ++ private final boolean removeCaches; ++ ++ public ThreadedWorldUpgrader(final ResourceKey dimensionType, final String worldName, final File worldDir, final int threads, ++ final DataFixer dataFixer, final Optional>> generatorKey, final boolean removeCaches) { ++ this.dimensionType = dimensionType; ++ this.worldName = worldName; ++ this.worldDir = worldDir; ++ this.threadPool = Executors.newFixedThreadPool(Math.max(1, threads), new ThreadFactory() { ++ private final AtomicInteger threadCounter = new AtomicInteger(); ++ ++ @Override ++ public Thread newThread(final Runnable run) { ++ final Thread ret = new Thread(run); ++ ++ ret.setName("World upgrader thread for world " + ThreadedWorldUpgrader.this.worldName + " #" + this.threadCounter.getAndIncrement()); ++ ret.setUncaughtExceptionHandler((thread, throwable) -> { ++ LOGGER.fatal("Error upgrading world", throwable); ++ }); ++ ++ return ret; ++ } ++ }); ++ this.dataFixer = dataFixer; ++ this.generatorKey = generatorKey; ++ this.removeCaches = removeCaches; ++ } ++ ++ public void convert() { ++ final File worldFolder = LevelStorageSource.getStorageFolder(this.worldDir.toPath(), this.dimensionType).toFile(); ++ final DimensionDataStorage worldPersistentData = new DimensionDataStorage(new File(worldFolder, "data"), this.dataFixer); ++ ++ final File regionFolder = new File(worldFolder, "region"); ++ ++ LOGGER.info("Force upgrading " + this.worldName); ++ LOGGER.info("Counting regionfiles for " + this.worldName); ++ final File[] regionFiles = regionFolder.listFiles((final File dir, final String name) -> { ++ return WorldUpgrader.REGEX.matcher(name).matches(); ++ }); ++ if (regionFiles == null) { ++ LOGGER.info("Found no regionfiles to convert for world " + this.worldName); ++ return; ++ } ++ LOGGER.info("Found " + regionFiles.length + " regionfiles to convert"); ++ LOGGER.info("Starting conversion now for world " + this.worldName); ++ ++ final WorldInfo info = new WorldInfo(() -> worldPersistentData, ++ new ChunkStorage(regionFolder.toPath(), this.dataFixer, false), this.removeCaches, this.dimensionType, this.generatorKey); ++ ++ long expectedChunks = (long)regionFiles.length * (32L * 32L); ++ ++ for (final File regionFile : regionFiles) { ++ final ChunkPos regionPos = RegionFileStorage.getRegionFileCoordinates(regionFile.toPath()); ++ if (regionPos == null) { ++ expectedChunks -= (32L * 32L); ++ continue; ++ } ++ ++ this.threadPool.execute(new ConvertTask(info, regionPos.x >> 5, regionPos.z >> 5)); ++ } ++ this.threadPool.shutdown(); ++ ++ final DecimalFormat format = new DecimalFormat("#0.00"); ++ ++ final long start = System.nanoTime(); ++ ++ while (!this.threadPool.isTerminated()) { ++ final long current = info.convertedChunks.get(); ++ ++ LOGGER.info("{}% completed ({} / {} chunks)...", format.format((double)current / (double)expectedChunks * 100.0), current, expectedChunks); ++ ++ try { ++ Thread.sleep(1000L); ++ } catch (final InterruptedException ignore) {} ++ } ++ ++ final long end = System.nanoTime(); ++ ++ try { ++ info.loader.close(); ++ } catch (final IOException ex) { ++ LOGGER.fatal("Failed to close chunk loader", ex); ++ } ++ LOGGER.info("Completed conversion. Took {}s, {} out of {} chunks needed to be converted/modified ({}%)", ++ (int)Math.ceil((end - start) * 1.0e-9), info.modifiedChunks.get(), expectedChunks, format.format((double)info.modifiedChunks.get() / (double)expectedChunks * 100.0)); ++ } ++ ++ private static final class WorldInfo { ++ ++ public final Supplier persistentDataSupplier; ++ public final ChunkStorage loader; ++ public final boolean removeCaches; ++ public final ResourceKey worldKey; ++ public final Optional>> generatorKey; ++ public final AtomicLong convertedChunks = new AtomicLong(); ++ public final AtomicLong modifiedChunks = new AtomicLong(); ++ ++ private WorldInfo(final Supplier persistentDataSupplier, final ChunkStorage loader, final boolean removeCaches, ++ final ResourceKey worldKey, Optional>> generatorKey) { ++ this.persistentDataSupplier = persistentDataSupplier; ++ this.loader = loader; ++ this.removeCaches = removeCaches; ++ this.worldKey = worldKey; ++ this.generatorKey = generatorKey; ++ } ++ } ++ ++ private static final class ConvertTask implements Runnable { ++ ++ private final WorldInfo worldInfo; ++ private final int regionX; ++ private final int regionZ; ++ ++ public ConvertTask(final WorldInfo worldInfo, final int regionX, final int regionZ) { ++ this.worldInfo = worldInfo; ++ this.regionX = regionX; ++ this.regionZ = regionZ; ++ } ++ ++ @Override ++ public void run() { ++ final int regionCX = this.regionX << 5; ++ final int regionCZ = this.regionZ << 5; ++ ++ final Supplier persistentDataSupplier = this.worldInfo.persistentDataSupplier; ++ final ChunkStorage loader = this.worldInfo.loader; ++ final boolean removeCaches = this.worldInfo.removeCaches; ++ final ResourceKey worldKey = this.worldInfo.worldKey; ++ ++ for (int cz = regionCZ; cz < (regionCZ + 32); ++cz) { ++ for (int cx = regionCX; cx < (regionCX + 32); ++cx) { ++ final ChunkPos chunkPos = new ChunkPos(cx, cz); ++ try { ++ // no need to check the coordinate of the chunk, the regionfilecache does that for us ++ ++ CompoundTag chunkNBT = (loader.read(chunkPos).join()).orElse(null); ++ ++ if (chunkNBT == null) { ++ continue; ++ } ++ ++ final int versionBefore = ChunkStorage.getVersion(chunkNBT); ++ ++ chunkNBT = loader.upgradeChunkTag(worldKey, persistentDataSupplier, chunkNBT, this.worldInfo.generatorKey, chunkPos, null); ++ ++ boolean modified = versionBefore < SharedConstants.getCurrentVersion().getDataVersion().getVersion(); ++ ++ if (removeCaches) { ++ final CompoundTag level = chunkNBT.getCompound("Level"); ++ modified |= level.contains("Heightmaps"); ++ level.remove("Heightmaps"); ++ modified |= level.contains("isLightOn"); ++ level.remove("isLightOn"); ++ } ++ ++ if (modified) { ++ this.worldInfo.modifiedChunks.getAndIncrement(); ++ loader.write(chunkPos, chunkNBT); ++ } ++ } catch (final Exception ex) { ++ LOGGER.error("Error upgrading chunk {}", chunkPos, ex); ++ } finally { ++ this.worldInfo.convertedChunks.getAndIncrement(); ++ } ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 72d013d06705b08ed696e3d3b6d631d65800c2c9..61840cfd64caba6595dfc99c91c76a195638d4ee 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -399,6 +399,15 @@ public class Main { + return new WorldLoader.InitConfig(worldloader_d, Commands.CommandSelection.DEDICATED, serverPropertiesHandler.functionPermissionLevel); + } + ++ // Paper start - fix and optimise world upgrading ++ public static void convertWorldButItWorks(net.minecraft.resources.ResourceKey dimensionType, net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess worldSession, ++ DataFixer dataFixer, Optional>> generatorKey, boolean removeCaches) { ++ int threads = Runtime.getRuntime().availableProcessors() * 3 / 8; ++ final io.papermc.paper.world.ThreadedWorldUpgrader worldUpgrader = new io.papermc.paper.world.ThreadedWorldUpgrader(dimensionType, worldSession.getLevelId(), worldSession.levelDirectory.path().toFile(), threads, dataFixer, generatorKey, removeCaches); ++ worldUpgrader.convert(); ++ } ++ // Paper end - fix and optimise world upgrading ++ + public static void forceUpgrade(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, boolean eraseCache, BooleanSupplier continueCheck, Registry dimensionOptionsRegistry) { + Main.LOGGER.info("Forcing world upgrade! {}", session.getLevelId()); // CraftBukkit + WorldUpgrader worldupgrader = new WorldUpgrader(session, dataFixer, dimensionOptionsRegistry, eraseCache); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 79de3c639795cfc0bd86f842446e2bb3ab71d23a..1dfafbe508b4e4598339f412e5fb9d92717b5d26 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -584,11 +584,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { +- return true; +- }, dimensions); +- } ++ // Paper - fix and optimise world upgrading; move down + + PrimaryLevelData iworlddataserver = worlddata; + boolean flag = worlddata.isDebugWorld(); +@@ -603,6 +599,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop worldKey = ResourceKey.create(Registries.DIMENSION, dimensionKey.location()); + + if (dimensionKey == LevelStem.OVERWORLD) { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 927c7948e567764e8cf75c7ce486e1ea6c9a8d87..7694b7f299495a084ce71c5d04e5e690a75fe55b 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -181,6 +181,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions + public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here + ++ // Paper start - fix and optimise world upgrading ++ // copied from below ++ public static ResourceKey getDimensionKey(DimensionType manager) { ++ return ((org.bukkit.craftbukkit.CraftServer)org.bukkit.Bukkit.getServer()).getHandle().getServer().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.DIMENSION_TYPE).getResourceKey(manager).orElseThrow(() -> { ++ return new IllegalStateException("Unregistered dimension type: " + manager); ++ }); ++ } ++ // Paper end - fix and optimise world upgrading ++ + public CraftWorld getWorld() { + return this.world; + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 0db8ee3b640e6d1268e9c1cccda85459bd447105..42d37bee3a459adcd46408596ccf93abbcff51fe 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -60,6 +60,29 @@ public class RegionFileStorage implements AutoCloseable { + } + + // Paper start ++ @Nullable ++ public static ChunkPos getRegionFileCoordinates(Path file) { ++ String fileName = file.getFileName().toString(); ++ if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) { ++ return null; ++ } ++ ++ String[] split = fileName.split("\\."); ++ ++ if (split.length != 4) { ++ return null; ++ } ++ ++ try { ++ int x = Integer.parseInt(split[1]); ++ int z = Integer.parseInt(split[2]); ++ ++ return new ChunkPos(x << 5, z << 5); ++ } catch (NumberFormatException ex) { ++ return null; ++ } ++ } ++ + public synchronized RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) { + return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ())); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index c9137151f0d2978adb432c40da68689465d2325d..ab7bc27e870227e6746b77a7b5e109e2cf198b5f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1357,9 +1357,7 @@ public final class CraftServer implements Server { + worlddata.checkName(name); + worlddata.setModdedInfo(this.console.getServerModName(), this.console.getModdedStatus().shouldReportAsModified()); + +- if (this.console.options.has("forceUpgrade")) { +- net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), this.console.options.has("eraseCache"), () -> true, iregistry); +- } ++ // Paper - fix and optimise world upgrading; move down + + long j = BiomeManager.obfuscateSeed(worlddata.worldGenOptions().seed()); // Paper - use world seed + List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); +@@ -1370,6 +1368,13 @@ public final class CraftServer implements Server { + biomeProvider = generator.getDefaultBiomeProvider(worldInfo); + } + ++ // Paper start - fix and optimise world upgrading ++ if (this.console.options.has("forceUpgrade")) { ++ net.minecraft.server.Main.convertWorldButItWorks( ++ actualDimension, worldSession, DataFixers.getDataFixer(), worlddimension.generator().getTypeNameForDataFixer(), this.console.options.has("eraseCache") ++ ); ++ } ++ // Paper end - fix and optimise world upgrading + ResourceKey worldKey; + String levelName = this.getServer().getProperties().levelName; + if (name.equals(levelName + "_nether")) { diff --git a/patches/server/1001-Optional-per-player-mob-spawns.patch b/patches/server/1001-Optional-per-player-mob-spawns.patch deleted file mode 100644 index e09c3a2962a6..000000000000 --- a/patches/server/1001-Optional-per-player-mob-spawns.patch +++ /dev/null @@ -1,255 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Mon, 19 Aug 2019 01:27:58 +0500 -Subject: [PATCH] Optional per player mob spawns - - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 42dde36273030494a6e7ff19e55d3b6a7da06fee..0d552d4b967687e2bfb92b1e5106071460082409 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -288,9 +288,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - }); - } - -+ // Paper start - Optional per player mob spawns -+ public void updatePlayerMobTypeMap(final Entity entity) { -+ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { -+ return; -+ } -+ int index = entity.getType().getCategory().ordinal(); -+ -+ final com.destroystokyo.paper.util.maplist.ReferenceList inRange = -+ this.getNearbyPlayers().getPlayers(entity.chunkPosition(), io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE); -+ if (inRange == null) { -+ return; -+ } -+ final Object[] backingSet = inRange.getRawData(); -+ for (int i = 0, len = inRange.size(); i < len; i++) { -+ ++((ServerPlayer)backingSet[i]).mobCounts[index]; -+ } -+ } -+ - public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) { -- return -1; -+ return player.mobCounts[mobCategory.ordinal()]; - } -+ // Paper end - Optional per player mob spawns - - private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { - double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8); -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 20cdfd2bbd5dc71fd37ccedaf3a8d06b45553c9b..059ab637adf1be576fa1fff36a91b6c5f1b5f035 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -518,7 +518,19 @@ public class ServerChunkCache extends ChunkSource { - gameprofilerfiller.popPush("naturalSpawnCount"); - this.level.timings.countNaturalMobs.startTiming(); // Paper - timings - int k = this.distanceManager.getNaturalSpawnChunkCount(); -- NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(k, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap)); -+ // Paper start - Optional per player mob spawns -+ int naturalSpawnChunkCount = k; -+ NaturalSpawner.SpawnState spawnercreature_d; // moved down -+ if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled -+ // re-set mob counts -+ for (ServerPlayer player : this.level.players) { -+ Arrays.fill(player.mobCounts, 0); -+ } -+ spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); -+ } else { -+ spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); -+ } -+ // Paper end - Optional per player mob spawns - this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings - - this.lastSpawnState = spawnercreature_d; -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index e03d2d3d35470cb3971606c66da382672eef1f82..4bfc12a50a262f49c0262b06b39faa8116b3807f 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -256,6 +256,10 @@ public class ServerPlayer extends Player { - public boolean queueHealthUpdatePacket; - public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; - // Paper end - cancellable death event -+ // Paper start - Optional per player mob spawns -+ public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length; -+ public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper -+ // Paper end - Optional per player mob spawns - - // CraftBukkit start - public String displayName; -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index c44c10e15564af6ba0f6d60a1b5f38c6e874a43a..14f4ceb6c0be34d23b24c1695f966145c3aaee96 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -70,6 +70,12 @@ public final class NaturalSpawner { - private NaturalSpawner() {} - - public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator densityCapper) { -+ // Paper start - Optional per player mob spawns -+ return createState(spawningChunkCount, entities, chunkSource, densityCapper, false); -+ } -+ -+ public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator densityCapper, boolean countMobs) { -+ // Paper end - Optional per player mob spawns - PotentialCalculator spawnercreatureprobabilities = new PotentialCalculator(); - Object2IntOpenHashMap object2intopenhashmap = new Object2IntOpenHashMap(); - Iterator iterator = entities.iterator(); -@@ -104,11 +110,16 @@ public final class NaturalSpawner { - spawnercreatureprobabilities.addCharge(entity.blockPosition(), biomesettingsmobs_b.charge()); - } - -- if (entity instanceof Mob) { -+ if (densityCapper != null && entity instanceof Mob) { // Paper - Optional per player mob spawns - densityCapper.addMob(chunk.getPos(), enumcreaturetype); - } - - object2intopenhashmap.addTo(enumcreaturetype, 1); -+ // Paper start - Optional per player mob spawns -+ if (countMobs) { -+ chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity); -+ } -+ // Paper end - Optional per player mob spawns - }); - } - } -@@ -143,13 +154,35 @@ public final class NaturalSpawner { - continue; - } - -- if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && info.canSpawnForCategory(enumcreaturetype, chunk.getPos(), limit)) { -+ // Paper start - Optional per player mob spawns; only allow spawns upto the limit per chunk and update count afterwards -+ int currEntityCount = info.mobCategoryCounts.getInt(enumcreaturetype); -+ int k1 = limit * info.getSpawnableChunkCount() / NaturalSpawner.MAGIC_NUMBER; -+ int difference = k1 - currEntityCount; -+ -+ if (world.paperConfig().entities.spawning.perPlayerMobSpawns) { -+ int minDiff = Integer.MAX_VALUE; -+ final com.destroystokyo.paper.util.maplist.ReferenceList inRange = -+ world.chunkSource.chunkMap.getNearbyPlayers().getPlayers(chunk.getPos(), io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE); -+ if (inRange != null) { -+ final Object[] backingSet = inRange.getRawData(); -+ for (int k = 0, len = inRange.size(); k < len; k++) { -+ minDiff = Math.min(limit - world.getChunkSource().chunkMap.getMobCountNear((net.minecraft.server.level.ServerPlayer)backingSet[k], enumcreaturetype), minDiff); -+ } -+ } -+ difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff; -+ } -+ if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && difference > 0) { -+ // Paper end - Optional per player mob spawns - // CraftBukkit end - Objects.requireNonNull(info); - NaturalSpawner.SpawnPredicate spawnercreature_c = info::canSpawn; - - Objects.requireNonNull(info); -- NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn); -+ // Paper start - Optional per player mob spawns -+ int spawnCount = NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn, -+ difference, world.paperConfig().entities.spawning.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null); -+ info.mobCategoryCounts.mergeInt(enumcreaturetype, spawnCount, Integer::sum); -+ // Paper end - Optional per player mob spawns - } - } - -@@ -168,11 +201,17 @@ public final class NaturalSpawner { - // Paper end - Add mobcaps commands - - public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { -+ // Paper start - Optional per player mob spawns -+ spawnCategoryForChunk(group, world, chunk, checker, runner, Integer.MAX_VALUE, null); -+ } -+ 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 - BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk); - - if (blockposition.getY() >= world.getMinBuildHeight() + 1) { -- NaturalSpawner.spawnCategoryForPosition(group, world, chunk, blockposition, checker, runner); -+ return NaturalSpawner.spawnCategoryForPosition(group, world, chunk, blockposition, checker, runner, maxSpawns, trackEntity); // Paper - Optional per player mob spawns - } -+ return 0; // Paper - Optional per player mob spawns - } - - @VisibleForDebug -@@ -183,15 +222,21 @@ public final class NaturalSpawner { - }); - } - -+ // Paper start - Optional per player mob spawns - public static void spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { -+ spawnCategoryForPosition(group, world,chunk, pos, checker, runner, Integer.MAX_VALUE, null); -+ } -+ public static int spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer trackEntity) { -+ // Paper end - Optional per player mob spawns - StructureManager structuremanager = world.structureManager(); - ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); - int i = pos.getY(); - BlockState iblockdata = world.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn -+ int j = 0; // Paper - Optional per player mob spawns; moved up - - if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn - BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); -- int j = 0; -+ //int j = 0; // Paper - Optional per player mob spawns; moved up - int k = 0; - - while (k < 3) { -@@ -233,14 +278,14 @@ public final class NaturalSpawner { - // Paper start - PreCreatureSpawnEvent - PreSpawnStatus doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2); - if (doSpawning == PreSpawnStatus.ABORT) { -- return; -+ return j; // Paper - Optional per player mob spawns - } - if (doSpawning == PreSpawnStatus.SUCCESS && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) { - // Paper end - PreCreatureSpawnEvent - Mob entityinsentient = NaturalSpawner.getMobForSpawn(world, biomesettingsmobs_c.type); - - if (entityinsentient == null) { -- return; -+ return j; // Paper - Optional per player mob spawns - } - - entityinsentient.moveTo(d0, (double) i, d1, world.random.nextFloat() * 360.0F, 0.0F); -@@ -253,10 +298,15 @@ public final class NaturalSpawner { - ++j; - ++k1; - runner.run(entityinsentient, chunk); -+ // Paper start - Optional per player mob spawns -+ if (trackEntity != null) { -+ trackEntity.accept(entityinsentient); -+ } -+ // Paper end - Optional per player mob spawns - } - // CraftBukkit end -- if (j >= entityinsentient.getMaxSpawnClusterSize()) { -- return; -+ if (j >= entityinsentient.getMaxSpawnClusterSize() || j >= maxSpawns) { // Paper - Optional per player mob spawns -+ return j; // Paper - Optional per player mob spawns - } - - if (entityinsentient.isMaxGroupSizeReached(k1)) { -@@ -278,6 +328,7 @@ public final class NaturalSpawner { - } - - } -+ return j; // Paper - Optional per player mob spawns - } - - private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) { -@@ -573,7 +624,7 @@ public final class NaturalSpawner { - MobCategory enumcreaturetype = entitytypes.getCategory(); - - this.mobCategoryCounts.addTo(enumcreaturetype, 1); -- this.localMobCapCalculator.addMob(new ChunkPos(blockposition), enumcreaturetype); -+ if (this.localMobCapCalculator != null) this.localMobCapCalculator.addMob(new ChunkPos(blockposition), enumcreaturetype); // Paper - Optional per player mob spawns - } - - public int getSpawnableChunkCount() { -@@ -589,6 +640,7 @@ public final class NaturalSpawner { - int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; - // CraftBukkit end - -+ if (this.localMobCapCalculator == null) return this.mobCategoryCounts.getInt(enumcreaturetype) < i; // Paper - Optional per player mob spawns - return this.mobCategoryCounts.getInt(enumcreaturetype) >= i ? false : this.localMobCapCalculator.canSpawn(enumcreaturetype, chunkcoordintpair); - } - } diff --git a/patches/server/1002-Anti-Xray.patch b/patches/server/1002-Anti-Xray.patch deleted file mode 100644 index f2e62d3779b9..000000000000 --- a/patches/server/1002-Anti-Xray.patch +++ /dev/null @@ -1,1636 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: stonar96 -Date: Thu, 25 Nov 2021 13:27:51 +0100 -Subject: [PATCH] Anti-Xray - - -diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e448c26327b5f6189c3c52e698cff66c8f9ad81a ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java -@@ -0,0 +1,51 @@ -+package com.destroystokyo.paper.antixray; -+ -+public final class BitStorageReader { -+ -+ private byte[] buffer; -+ private int bits; -+ private int mask; -+ private int longInBufferIndex; -+ private int bitInLongIndex; -+ private long current; -+ -+ public void setBuffer(byte[] buffer) { -+ this.buffer = buffer; -+ } -+ -+ public void setBits(int bits) { -+ this.bits = bits; -+ mask = (1 << bits) - 1; -+ } -+ -+ public void setIndex(int index) { -+ longInBufferIndex = index; -+ bitInLongIndex = 0; -+ init(); -+ } -+ -+ private void init() { -+ if (buffer.length > longInBufferIndex + 7) { -+ current = ((((long) buffer[longInBufferIndex]) << 56) -+ | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48) -+ | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40) -+ | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32) -+ | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24) -+ | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16) -+ | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8) -+ | (((long) buffer[longInBufferIndex + 7] & 0xff))); -+ } -+ } -+ -+ public int read() { -+ if (bitInLongIndex + bits > 64) { -+ bitInLongIndex = 0; -+ longInBufferIndex += 8; -+ init(); -+ } -+ -+ int value = (int) (current >>> bitInLongIndex) & mask; -+ bitInLongIndex += bits; -+ return value; -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e4540ea278f2dc871cb6a3cb8897559bfd65e134 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java -@@ -0,0 +1,79 @@ -+package com.destroystokyo.paper.antixray; -+ -+public final class BitStorageWriter { -+ -+ private byte[] buffer; -+ private int bits; -+ private long mask; -+ private int longInBufferIndex; -+ private int bitInLongIndex; -+ private long current; -+ private boolean dirty; -+ -+ public void setBuffer(byte[] buffer) { -+ this.buffer = buffer; -+ } -+ -+ public void setBits(int bits) { -+ this.bits = bits; -+ mask = (1L << bits) - 1; -+ } -+ -+ public void setIndex(int index) { -+ longInBufferIndex = index; -+ bitInLongIndex = 0; -+ init(); -+ } -+ -+ private void init() { -+ if (buffer.length > longInBufferIndex + 7) { -+ current = ((((long) buffer[longInBufferIndex]) << 56) -+ | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48) -+ | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40) -+ | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32) -+ | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24) -+ | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16) -+ | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8) -+ | (((long) buffer[longInBufferIndex + 7] & 0xff))); -+ } -+ -+ dirty = false; -+ } -+ -+ public void flush() { -+ if (dirty && buffer.length > longInBufferIndex + 7) { -+ buffer[longInBufferIndex] = (byte) (current >> 56 & 0xff); -+ buffer[longInBufferIndex + 1] = (byte) (current >> 48 & 0xff); -+ buffer[longInBufferIndex + 2] = (byte) (current >> 40 & 0xff); -+ buffer[longInBufferIndex + 3] = (byte) (current >> 32 & 0xff); -+ buffer[longInBufferIndex + 4] = (byte) (current >> 24 & 0xff); -+ buffer[longInBufferIndex + 5] = (byte) (current >> 16 & 0xff); -+ buffer[longInBufferIndex + 6] = (byte) (current >> 8 & 0xff); -+ buffer[longInBufferIndex + 7] = (byte) (current & 0xff); -+ } -+ } -+ -+ public void write(int value) { -+ if (bitInLongIndex + bits > 64) { -+ flush(); -+ bitInLongIndex = 0; -+ longInBufferIndex += 8; -+ init(); -+ } -+ -+ current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex; -+ dirty = true; -+ bitInLongIndex += bits; -+ } -+ -+ public void skip() { -+ bitInLongIndex += bits; -+ -+ if (bitInLongIndex > 64) { -+ flush(); -+ bitInLongIndex = bits; -+ longInBufferIndex += 8; -+ init(); -+ } -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java -new file mode 100644 -index 0000000000000000000000000000000000000000..52d2e2b744f91914802506e52a07161729bbcf3a ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java -@@ -0,0 +1,45 @@ -+package com.destroystokyo.paper.antixray; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; -+import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.ServerPlayerGameMode; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.LevelChunk; -+ -+public class ChunkPacketBlockController { -+ -+ public static final ChunkPacketBlockController NO_OPERATION_INSTANCE = new ChunkPacketBlockController(); -+ -+ protected ChunkPacketBlockController() { -+ -+ } -+ -+ public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int chunkSectionY) { -+ return null; -+ } -+ -+ public boolean shouldModify(ServerPlayer player, LevelChunk chunk) { -+ return false; -+ } -+ -+ public ChunkPacketInfo getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { -+ return null; -+ } -+ -+ public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo chunkPacketInfo) { -+ chunkPacket.setReady(true); -+ } -+ -+ public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) { -+ -+ } -+ -+ public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { -+ -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e7fe98ea30ae6d0baea3ec1f9f98a89502a49a12 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java -@@ -0,0 +1,676 @@ -+package com.destroystokyo.paper.antixray; -+ -+import io.papermc.paper.configuration.WorldConfiguration; -+import io.papermc.paper.configuration.type.EngineMode; -+import java.util.ArrayList; -+import java.util.LinkedHashSet; -+import java.util.LinkedList; -+import java.util.List; -+import java.util.Set; -+import java.util.concurrent.Executor; -+import java.util.concurrent.ThreadLocalRandom; -+import java.util.function.IntSupplier; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.core.registries.Registries; -+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; -+import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.ServerPlayerGameMode; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.biome.Biomes; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.EntityBlock; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.EmptyLevelChunk; -+import net.minecraft.world.level.chunk.GlobalPalette; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.MissingPaletteEntryException; -+import net.minecraft.world.level.chunk.Palette; -+import org.bukkit.Bukkit; -+ -+public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController { -+ -+ private static final Palette GLOBAL_BLOCKSTATE_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY); -+ private static final LevelChunkSection EMPTY_SECTION = null; -+ private final Executor executor; -+ private final EngineMode engineMode; -+ private final int maxBlockHeight; -+ private final int updateRadius; -+ private final boolean usePermission; -+ private final BlockState[] presetBlockStates; -+ private final BlockState[] presetBlockStatesFull; -+ private final BlockState[] presetBlockStatesStone; -+ private final BlockState[] presetBlockStatesDeepslate; -+ private final BlockState[] presetBlockStatesNetherrack; -+ private final BlockState[] presetBlockStatesEndStone; -+ private final int[] presetBlockStateBitsGlobal; -+ private final int[] presetBlockStateBitsStoneGlobal; -+ private final int[] presetBlockStateBitsDeepslateGlobal; -+ private final int[] presetBlockStateBitsNetherrackGlobal; -+ private final int[] presetBlockStateBitsEndStoneGlobal; -+ private final boolean[] solidGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()]; -+ private final boolean[] obfuscateGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()]; -+ private final LevelChunkSection[] emptyNearbyChunkSections = {EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION}; -+ private final int maxBlockHeightUpdatePosition; -+ -+ public ChunkPacketBlockControllerAntiXray(Level level, Executor executor) { -+ this.executor = executor; -+ WorldConfiguration.Anticheat.AntiXray paperWorldConfig = level.paperConfig().anticheat.antiXray; -+ engineMode = paperWorldConfig.engineMode; -+ maxBlockHeight = paperWorldConfig.maxBlockHeight >> 4 << 4; -+ updateRadius = paperWorldConfig.updateRadius; -+ usePermission = paperWorldConfig.usePermission; -+ List toObfuscate; -+ -+ if (engineMode == EngineMode.HIDE) { -+ toObfuscate = paperWorldConfig.hiddenBlocks; -+ presetBlockStates = null; -+ presetBlockStatesFull = null; -+ presetBlockStatesStone = new BlockState[]{Blocks.STONE.defaultBlockState()}; -+ presetBlockStatesDeepslate = new BlockState[]{Blocks.DEEPSLATE.defaultBlockState()}; -+ presetBlockStatesNetherrack = new BlockState[]{Blocks.NETHERRACK.defaultBlockState()}; -+ presetBlockStatesEndStone = new BlockState[]{Blocks.END_STONE.defaultBlockState()}; -+ presetBlockStateBitsGlobal = null; -+ presetBlockStateBitsStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.STONE.defaultBlockState())}; -+ presetBlockStateBitsDeepslateGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.DEEPSLATE.defaultBlockState())}; -+ presetBlockStateBitsNetherrackGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.NETHERRACK.defaultBlockState())}; -+ presetBlockStateBitsEndStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.END_STONE.defaultBlockState())}; -+ } else { -+ toObfuscate = new ArrayList<>(paperWorldConfig.replacementBlocks); -+ List presetBlockStateList = new LinkedList<>(); -+ -+ for (Block block : paperWorldConfig.hiddenBlocks) { -+ -+ if (!(block instanceof EntityBlock)) { -+ toObfuscate.add(block); -+ presetBlockStateList.add(block.defaultBlockState()); -+ } -+ } -+ -+ // The doc of the LinkedHashSet(Collection) constructor doesn't specify that the insertion order is the predictable iteration order of the specified Collection, although it is in the implementation -+ Set presetBlockStateSet = new LinkedHashSet<>(); -+ // Therefore addAll(Collection) is used, which guarantees this order in the doc -+ presetBlockStateSet.addAll(presetBlockStateList); -+ presetBlockStates = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateSet.toArray(new BlockState[0]); -+ presetBlockStatesFull = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateList.toArray(new BlockState[0]); -+ presetBlockStatesStone = null; -+ presetBlockStatesDeepslate = null; -+ presetBlockStatesNetherrack = null; -+ presetBlockStatesEndStone = null; -+ presetBlockStateBitsGlobal = new int[presetBlockStatesFull.length]; -+ -+ for (int i = 0; i < presetBlockStatesFull.length; i++) { -+ presetBlockStateBitsGlobal[i] = GLOBAL_BLOCKSTATE_PALETTE.idFor(presetBlockStatesFull[i]); -+ } -+ -+ presetBlockStateBitsStoneGlobal = null; -+ presetBlockStateBitsDeepslateGlobal = null; -+ presetBlockStateBitsNetherrackGlobal = null; -+ presetBlockStateBitsEndStoneGlobal = null; -+ } -+ -+ for (Block block : toObfuscate) { -+ -+ // Don't obfuscate air because air causes unnecessary block updates and causes block updates to fail in the void -+ if (block != null && !block.defaultBlockState().isAir()) { -+ // Replace all block states of a specified block -+ for (BlockState blockState : block.getStateDefinition().getPossibleStates()) { -+ obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = true; -+ } -+ } -+ } -+ -+ EmptyLevelChunk emptyChunk = new EmptyLevelChunk(level, new ChunkPos(0, 0), MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.PLAINS)); -+ BlockPos zeroPos = new BlockPos(0, 0, 0); -+ -+ for (int i = 0; i < solidGlobal.length; i++) { -+ BlockState blockState = GLOBAL_BLOCKSTATE_PALETTE.valueFor(i); -+ -+ if (blockState != null) { -+ solidGlobal[i] = blockState.isRedstoneConductor(emptyChunk, zeroPos) -+ && blockState.getBlock() != Blocks.SPAWNER && blockState.getBlock() != Blocks.BARRIER && blockState.getBlock() != Blocks.SHULKER_BOX && blockState.getBlock() != Blocks.SLIME_BLOCK && blockState.getBlock() != Blocks.MANGROVE_ROOTS || paperWorldConfig.lavaObscures && blockState == Blocks.LAVA.defaultBlockState(); -+ // Comparing blockState == Blocks.LAVA.defaultBlockState() instead of blockState.getBlock() == Blocks.LAVA ensures that only "stationary lava" is used -+ // shulker box checks TE. -+ } -+ } -+ -+ maxBlockHeightUpdatePosition = maxBlockHeight + updateRadius - 1; -+ } -+ -+ private int getPresetBlockStatesFullLength() { -+ return engineMode == EngineMode.HIDE ? 1 : presetBlockStatesFull.length; -+ } -+ -+ @Override -+ public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int chunkSectionY) { -+ // Return the block states to be added to the paletted containers so that they can be used for obfuscation -+ int bottomBlockY = chunkSectionY << 4; -+ -+ if (bottomBlockY < maxBlockHeight) { -+ if (engineMode == EngineMode.HIDE) { -+ return switch (level.getWorld().getEnvironment()) { -+ case NETHER -> presetBlockStatesNetherrack; -+ case THE_END -> presetBlockStatesEndStone; -+ default -> bottomBlockY < 0 ? presetBlockStatesDeepslate : presetBlockStatesStone; -+ }; -+ } -+ -+ return presetBlockStates; -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public boolean shouldModify(ServerPlayer player, LevelChunk chunk) { -+ return !usePermission || !player.getBukkitEntity().hasPermission("paper.antixray.bypass"); -+ } -+ -+ @Override -+ public ChunkPacketInfoAntiXray getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { -+ // Return a new instance to collect data and objects in the right state while creating the chunk packet for thread safe access later -+ return new ChunkPacketInfoAntiXray(chunkPacket, chunk, this); -+ } -+ -+ @Override -+ public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo chunkPacketInfo) { -+ if (!(chunkPacketInfo instanceof ChunkPacketInfoAntiXray)) { -+ chunkPacket.setReady(true); -+ return; -+ } -+ -+ if (!Bukkit.isPrimaryThread()) { -+ // Plugins? -+ MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo)); -+ return; -+ } -+ -+ LevelChunk chunk = chunkPacketInfo.getChunk(); -+ int x = chunk.getPos().x; -+ int z = chunk.getPos().z; -+ Level level = chunk.getLevel(); -+ ((ChunkPacketInfoAntiXray) chunkPacketInfo).setNearbyChunks(level.getChunkIfLoaded(x - 1, z), level.getChunkIfLoaded(x + 1, z), level.getChunkIfLoaded(x, z - 1), level.getChunkIfLoaded(x, z + 1)); -+ executor.execute((Runnable) chunkPacketInfo); -+ } -+ -+ // Actually these fields should be variables inside the obfuscate method but in sync mode or with SingleThreadExecutor in async mode it's okay (even without ThreadLocal) -+ // If an ExecutorService with multiple threads is used, ThreadLocal must be used here -+ private final ThreadLocal presetBlockStateBits = ThreadLocal.withInitial(() -> new int[getPresetBlockStatesFullLength()]); -+ private static final ThreadLocal SOLID = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]); -+ private static final ThreadLocal OBFUSCATE = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]); -+ // These boolean arrays represent chunk layers, true means don't obfuscate, false means obfuscate -+ private static final ThreadLocal CURRENT = ThreadLocal.withInitial(() -> new boolean[16][16]); -+ private static final ThreadLocal NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]); -+ private static final ThreadLocal NEXT_NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]); -+ -+ public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) { -+ int[] presetBlockStateBits = this.presetBlockStateBits.get(); -+ boolean[] solid = SOLID.get(); -+ boolean[] obfuscate = OBFUSCATE.get(); -+ boolean[][] current = CURRENT.get(); -+ boolean[][] next = NEXT.get(); -+ boolean[][] nextNext = NEXT_NEXT.get(); -+ // bitStorageReader, bitStorageWriter and nearbyChunkSections could also be reused (with ThreadLocal if necessary) but it's not worth it -+ BitStorageReader bitStorageReader = new BitStorageReader(); -+ BitStorageWriter bitStorageWriter = new BitStorageWriter(); -+ LevelChunkSection[] nearbyChunkSections = new LevelChunkSection[4]; -+ LevelChunk chunk = chunkPacketInfoAntiXray.getChunk(); -+ Level level = chunk.getLevel(); -+ int maxChunkSectionIndex = Math.min((maxBlockHeight >> 4) - chunk.getMinSection(), chunk.getSectionsCount()) - 1; -+ boolean[] solidTemp = null; -+ boolean[] obfuscateTemp = null; -+ bitStorageReader.setBuffer(chunkPacketInfoAntiXray.getBuffer()); -+ bitStorageWriter.setBuffer(chunkPacketInfoAntiXray.getBuffer()); -+ int numberOfBlocks = presetBlockStateBits.length; -+ // Keep the lambda expressions as simple as possible. They are used very frequently. -+ LayeredIntSupplier random = numberOfBlocks == 1 ? (() -> 0) : engineMode == EngineMode.OBFUSCATE_LAYER ? new LayeredIntSupplier() { -+ // engine-mode: 3 -+ private int state; -+ private int next; -+ -+ { -+ while ((state = ThreadLocalRandom.current().nextInt()) == 0) ; -+ } -+ -+ @Override -+ public void nextLayer() { -+ // https://en.wikipedia.org/wiki/Xorshift -+ state ^= state << 13; -+ state ^= state >>> 17; -+ state ^= state << 5; -+ // https://www.pcg-random.org/posts/bounded-rands.html -+ next = (int) ((Integer.toUnsignedLong(state) * numberOfBlocks) >>> 32); -+ } -+ -+ @Override -+ public int getAsInt() { -+ return next; -+ } -+ } : new LayeredIntSupplier() { -+ // engine-mode: 2 -+ private int state; -+ -+ { -+ while ((state = ThreadLocalRandom.current().nextInt()) == 0) ; -+ } -+ -+ @Override -+ public int getAsInt() { -+ // https://en.wikipedia.org/wiki/Xorshift -+ state ^= state << 13; -+ state ^= state >>> 17; -+ state ^= state << 5; -+ // https://www.pcg-random.org/posts/bounded-rands.html -+ return (int) ((Integer.toUnsignedLong(state) * numberOfBlocks) >>> 32); -+ } -+ }; -+ -+ for (int chunkSectionIndex = 0; chunkSectionIndex <= maxChunkSectionIndex; chunkSectionIndex++) { -+ if (chunkPacketInfoAntiXray.isWritten(chunkSectionIndex) && chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) != null) { -+ int[] presetBlockStateBitsTemp; -+ -+ if (chunkPacketInfoAntiXray.getPalette(chunkSectionIndex) instanceof GlobalPalette) { -+ if (engineMode == EngineMode.HIDE) { -+ presetBlockStateBitsTemp = switch (level.getWorld().getEnvironment()) { -+ case NETHER -> presetBlockStateBitsNetherrackGlobal; -+ case THE_END -> presetBlockStateBitsEndStoneGlobal; -+ default -> chunkSectionIndex + chunk.getMinSection() < 0 ? presetBlockStateBitsDeepslateGlobal : presetBlockStateBitsStoneGlobal; -+ }; -+ } else { -+ presetBlockStateBitsTemp = presetBlockStateBitsGlobal; -+ } -+ } else { -+ // If it's presetBlockStates, use this.presetBlockStatesFull instead -+ BlockState[] presetBlockStatesFull = chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) == presetBlockStates ? this.presetBlockStatesFull : chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex); -+ presetBlockStateBitsTemp = presetBlockStateBits; -+ -+ for (int i = 0; i < presetBlockStateBitsTemp.length; i++) { -+ // This is thread safe because we only request IDs that are guaranteed to be in the palette and are visible -+ // For more details see the comments in the readPalette method -+ presetBlockStateBitsTemp[i] = chunkPacketInfoAntiXray.getPalette(chunkSectionIndex).idFor(presetBlockStatesFull[i]); -+ } -+ } -+ -+ bitStorageWriter.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex)); -+ -+ // Check if the chunk section below was not obfuscated -+ if (chunkSectionIndex == 0 || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex - 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex - 1) == null) { -+ // If so, initialize some stuff -+ bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex)); -+ bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex)); -+ solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), solid, solidGlobal); -+ obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), obfuscate, obfuscateGlobal); -+ // Read the blocks of the upper layer of the chunk section below if it exists -+ LevelChunkSection belowChunkSection = null; -+ boolean skipFirstLayer = chunkSectionIndex == 0 || (belowChunkSection = chunk.getSections()[chunkSectionIndex - 1]) == EMPTY_SECTION; -+ -+ for (int z = 0; z < 16; z++) { -+ for (int x = 0; x < 16; x++) { -+ current[z][x] = true; -+ next[z][x] = skipFirstLayer || isTransparent(belowChunkSection, x, 15, z); -+ } -+ } -+ -+ // Abuse the obfuscateLayer method to read the blocks of the first layer of the current chunk section -+ bitStorageWriter.setBits(0); -+ obfuscateLayer(-1, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, emptyNearbyChunkSections, random); -+ } -+ -+ bitStorageWriter.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex)); -+ nearbyChunkSections[0] = chunkPacketInfoAntiXray.getNearbyChunks()[0] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[0].getSections()[chunkSectionIndex]; -+ nearbyChunkSections[1] = chunkPacketInfoAntiXray.getNearbyChunks()[1] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[1].getSections()[chunkSectionIndex]; -+ nearbyChunkSections[2] = chunkPacketInfoAntiXray.getNearbyChunks()[2] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[2].getSections()[chunkSectionIndex]; -+ nearbyChunkSections[3] = chunkPacketInfoAntiXray.getNearbyChunks()[3] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[3].getSections()[chunkSectionIndex]; -+ -+ // Obfuscate all layers of the current chunk section except the upper one -+ for (int y = 0; y < 15; y++) { -+ boolean[][] temp = current; -+ current = next; -+ next = nextNext; -+ nextNext = temp; -+ random.nextLayer(); -+ obfuscateLayer(y, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); -+ } -+ -+ // Check if the chunk section above doesn't need obfuscation -+ if (chunkSectionIndex == maxChunkSectionIndex || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex + 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex + 1) == null) { -+ // If so, obfuscate the upper layer of the current chunk section by reading blocks of the first layer from the chunk section above if it exists -+ LevelChunkSection aboveChunkSection; -+ -+ if (chunkSectionIndex != chunk.getSectionsCount() - 1 && (aboveChunkSection = chunk.getSections()[chunkSectionIndex + 1]) != EMPTY_SECTION) { -+ boolean[][] temp = current; -+ current = next; -+ next = nextNext; -+ nextNext = temp; -+ -+ for (int z = 0; z < 16; z++) { -+ for (int x = 0; x < 16; x++) { -+ if (isTransparent(aboveChunkSection, x, 0, z)) { -+ current[z][x] = true; -+ } -+ } -+ } -+ -+ // There is nothing to read anymore -+ bitStorageReader.setBits(0); -+ solid[0] = true; -+ random.nextLayer(); -+ obfuscateLayer(15, bitStorageReader, bitStorageWriter, solid, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); -+ } -+ } else { -+ // If not, initialize the reader and other stuff for the chunk section above to obfuscate the upper layer of the current chunk section -+ bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex + 1)); -+ bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex + 1)); -+ solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), solid, solidGlobal); -+ obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), obfuscate, obfuscateGlobal); -+ boolean[][] temp = current; -+ current = next; -+ next = nextNext; -+ nextNext = temp; -+ random.nextLayer(); -+ obfuscateLayer(15, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); -+ } -+ -+ bitStorageWriter.flush(); -+ } -+ } -+ -+ chunkPacketInfoAntiXray.getChunkPacket().setReady(true); -+ } -+ -+ private void obfuscateLayer(int y, BitStorageReader bitStorageReader, BitStorageWriter bitStorageWriter, boolean[] solid, boolean[] obfuscate, int[] presetBlockStateBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, LevelChunkSection[] nearbyChunkSections, IntSupplier random) { -+ // First block of first line -+ int bits = bitStorageReader.read(); -+ -+ if (nextNext[0][0] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[0][1] = true; -+ next[1][0] = true; -+ } else { -+ if (current[0][0] || isTransparent(nearbyChunkSections[2], 0, y, 15) || isTransparent(nearbyChunkSections[0], 15, y, 0)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[0][0] = true; -+ } -+ -+ // First line -+ for (int x = 1; x < 15; x++) { -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[0][x] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[0][x - 1] = true; -+ next[0][x + 1] = true; -+ next[1][x] = true; -+ } else { -+ if (current[0][x] || isTransparent(nearbyChunkSections[2], x, y, 15)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[0][x] = true; -+ } -+ } -+ -+ // Last block of first line -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[0][15] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[0][14] = true; -+ next[1][15] = true; -+ } else { -+ if (current[0][15] || isTransparent(nearbyChunkSections[2], 15, y, 15) || isTransparent(nearbyChunkSections[1], 0, y, 0)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[0][15] = true; -+ } -+ -+ // All inner lines -+ for (int z = 1; z < 15; z++) { -+ // First block -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[z][0] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[z][1] = true; -+ next[z - 1][0] = true; -+ next[z + 1][0] = true; -+ } else { -+ if (current[z][0] || isTransparent(nearbyChunkSections[0], 15, y, z)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[z][0] = true; -+ } -+ -+ // All inner blocks -+ for (int x = 1; x < 15; x++) { -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[z][x] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[z][x - 1] = true; -+ next[z][x + 1] = true; -+ next[z - 1][x] = true; -+ next[z + 1][x] = true; -+ } else { -+ if (current[z][x]) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[z][x] = true; -+ } -+ } -+ -+ // Last block -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[z][15] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[z][14] = true; -+ next[z - 1][15] = true; -+ next[z + 1][15] = true; -+ } else { -+ if (current[z][15] || isTransparent(nearbyChunkSections[1], 0, y, z)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[z][15] = true; -+ } -+ } -+ -+ // First block of last line -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[15][0] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[15][1] = true; -+ next[14][0] = true; -+ } else { -+ if (current[15][0] || isTransparent(nearbyChunkSections[3], 0, y, 0) || isTransparent(nearbyChunkSections[0], 15, y, 15)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[15][0] = true; -+ } -+ -+ // Last line -+ for (int x = 1; x < 15; x++) { -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[15][x] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[15][x - 1] = true; -+ next[15][x + 1] = true; -+ next[14][x] = true; -+ } else { -+ if (current[15][x] || isTransparent(nearbyChunkSections[3], x, y, 0)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[15][x] = true; -+ } -+ } -+ -+ // Last block of last line -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[15][15] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[15][14] = true; -+ next[14][15] = true; -+ } else { -+ if (current[15][15] || isTransparent(nearbyChunkSections[3], 15, y, 0) || isTransparent(nearbyChunkSections[1], 0, y, 15)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[15][15] = true; -+ } -+ } -+ -+ private boolean isTransparent(LevelChunkSection chunkSection, int x, int y, int z) { -+ if (chunkSection == EMPTY_SECTION) { -+ return true; -+ } -+ -+ try { -+ return !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(chunkSection.getBlockState(x, y, z))]; -+ } catch (MissingPaletteEntryException e) { -+ // Race condition / visibility issue / no happens-before relationship -+ // We don't care and treat the block as transparent -+ // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur -+ return true; -+ } -+ } -+ -+ private boolean[] readPalette(Palette palette, boolean[] temp, boolean[] global) { -+ if (palette instanceof GlobalPalette) { -+ return global; -+ } -+ -+ try { -+ for (int i = 0; i < palette.getSize(); i++) { -+ temp[i] = global[GLOBAL_BLOCKSTATE_PALETTE.idFor(palette.valueFor(i))]; -+ } -+ } catch (MissingPaletteEntryException e) { -+ // Race condition / visibility issue / no happens-before relationship -+ // We don't care because we at least see the state as it was when the chunk packet was created -+ // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur until we have all the data that we need here -+ // Since all palettes have a fixed initial maximum size and there is no internal restructuring and no values are removed from palettes, we are also guaranteed to see the data -+ } -+ -+ return temp; -+ } -+ -+ @Override -+ public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) { -+ if (oldBlockState != null && solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(oldBlockState)] && !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(newBlockState)] && blockPos.getY() <= maxBlockHeightUpdatePosition) { -+ updateNearbyBlocks(level, blockPos); -+ } -+ } -+ -+ @Override -+ public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { -+ if (blockPos.getY() <= maxBlockHeightUpdatePosition) { -+ updateNearbyBlocks(serverPlayerGameMode.level, blockPos); -+ } -+ } -+ -+ private void updateNearbyBlocks(Level level, BlockPos blockPos) { -+ if (updateRadius >= 2) { -+ BlockPos temp = blockPos.west(); -+ updateBlock(level, temp); -+ updateBlock(level, temp.west()); -+ updateBlock(level, temp.below()); -+ updateBlock(level, temp.above()); -+ updateBlock(level, temp.north()); -+ updateBlock(level, temp.south()); -+ updateBlock(level, temp = blockPos.east()); -+ updateBlock(level, temp.east()); -+ updateBlock(level, temp.below()); -+ updateBlock(level, temp.above()); -+ updateBlock(level, temp.north()); -+ updateBlock(level, temp.south()); -+ updateBlock(level, temp = blockPos.below()); -+ updateBlock(level, temp.below()); -+ updateBlock(level, temp.north()); -+ updateBlock(level, temp.south()); -+ updateBlock(level, temp = blockPos.above()); -+ updateBlock(level, temp.above()); -+ updateBlock(level, temp.north()); -+ updateBlock(level, temp.south()); -+ updateBlock(level, temp = blockPos.north()); -+ updateBlock(level, temp.north()); -+ updateBlock(level, temp = blockPos.south()); -+ updateBlock(level, temp.south()); -+ } else if (updateRadius == 1) { -+ updateBlock(level, blockPos.west()); -+ updateBlock(level, blockPos.east()); -+ updateBlock(level, blockPos.below()); -+ updateBlock(level, blockPos.above()); -+ updateBlock(level, blockPos.north()); -+ updateBlock(level, blockPos.south()); -+ } else { -+ // Do nothing if updateRadius <= 0 (test mode) -+ } -+ } -+ -+ private void updateBlock(Level level, BlockPos blockPos) { -+ BlockState blockState = level.getBlockStateIfLoaded(blockPos); -+ -+ if (blockState != null && obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)]) { -+ ((ServerLevel) level).getChunkSource().blockChanged(blockPos); -+ } -+ } -+ -+ @FunctionalInterface -+ private interface LayeredIntSupplier extends IntSupplier { -+ default void nextLayer() { -+ -+ } -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d98a3f5c54c67a673eb7dc456dd039cd78f9c34d ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java -@@ -0,0 +1,80 @@ -+package com.destroystokyo.paper.antixray; -+ -+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.Palette; -+ -+public class ChunkPacketInfo { -+ -+ private final ClientboundLevelChunkWithLightPacket chunkPacket; -+ private final LevelChunk chunk; -+ private final int[] bits; -+ private final Object[] palettes; -+ private final int[] indexes; -+ private final Object[][] presetValues; -+ private byte[] buffer; -+ -+ public ChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { -+ this.chunkPacket = chunkPacket; -+ this.chunk = chunk; -+ int sections = chunk.getSectionsCount(); -+ bits = new int[sections]; -+ palettes = new Object[sections]; -+ indexes = new int[sections]; -+ presetValues = new Object[sections][]; -+ } -+ -+ public ClientboundLevelChunkWithLightPacket getChunkPacket() { -+ return chunkPacket; -+ } -+ -+ public LevelChunk getChunk() { -+ return chunk; -+ } -+ -+ public byte[] getBuffer() { -+ return buffer; -+ } -+ -+ public void setBuffer(byte[] buffer) { -+ this.buffer = buffer; -+ } -+ -+ public int getBits(int chunkSectionIndex) { -+ return bits[chunkSectionIndex]; -+ } -+ -+ public void setBits(int chunkSectionIndex, int bits) { -+ this.bits[chunkSectionIndex] = bits; -+ } -+ -+ @SuppressWarnings("unchecked") -+ public Palette getPalette(int chunkSectionIndex) { -+ return (Palette) palettes[chunkSectionIndex]; -+ } -+ -+ public void setPalette(int chunkSectionIndex, Palette palette) { -+ palettes[chunkSectionIndex] = palette; -+ } -+ -+ public int getIndex(int chunkSectionIndex) { -+ return indexes[chunkSectionIndex]; -+ } -+ -+ public void setIndex(int chunkSectionIndex, int index) { -+ indexes[chunkSectionIndex] = index; -+ } -+ -+ @SuppressWarnings("unchecked") -+ public T[] getPresetValues(int chunkSectionIndex) { -+ return (T[]) presetValues[chunkSectionIndex]; -+ } -+ -+ public void setPresetValues(int chunkSectionIndex, T[] presetValues) { -+ this.presetValues[chunkSectionIndex] = presetValues; -+ } -+ -+ public boolean isWritten(int chunkSectionIndex) { -+ return bits[chunkSectionIndex] != 0; -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java -new file mode 100644 -index 0000000000000000000000000000000000000000..80a2dfb266ae1221680a7b24fee2f7e2a8330b7d ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java -@@ -0,0 +1,29 @@ -+package com.destroystokyo.paper.antixray; -+ -+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.LevelChunk; -+ -+public final class ChunkPacketInfoAntiXray extends ChunkPacketInfo implements Runnable { -+ -+ private final ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray; -+ private LevelChunk[] nearbyChunks; -+ -+ public ChunkPacketInfoAntiXray(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk, ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray) { -+ super(chunkPacket, chunk); -+ this.chunkPacketBlockControllerAntiXray = chunkPacketBlockControllerAntiXray; -+ } -+ -+ public LevelChunk[] getNearbyChunks() { -+ return nearbyChunks; -+ } -+ -+ public void setNearbyChunks(LevelChunk... nearbyChunks) { -+ this.nearbyChunks = nearbyChunks; -+ } -+ -+ @Override -+ public void run() { -+ chunkPacketBlockControllerAntiXray.obfuscate(this); -+ } -+} -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java -index 6cff1a98dc7cf33947ec760dbc3d3d0ec5db5f6c..51f647de153255c919b1440338cf1b3e2d6b5dbf 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundChunksBiomesPacket.java -@@ -63,8 +63,10 @@ public record ClientboundChunksBiomesPacket(List blockEntitiesData; - -- public ClientboundLevelChunkPacketData(LevelChunk chunk) { -+ // Paper start - Anti-Xray - Add chunk packet info -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public ClientboundLevelChunkPacketData(LevelChunk chunk) { this(chunk, null); } -+ public ClientboundLevelChunkPacketData(LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo) { -+ // Paper end - this.heightmaps = new CompoundTag(); - - for(Map.Entry entry : chunk.getHeightmaps()) { -@@ -35,7 +38,14 @@ public class ClientboundLevelChunkPacketData { - } - - this.buffer = new byte[calculateChunkSize(chunk)]; -- extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk); -+ -+ // Paper start - Anti-Xray - Add chunk packet info -+ if (chunkPacketInfo != null) { -+ chunkPacketInfo.setBuffer(this.buffer); -+ } -+ -+ extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk, chunkPacketInfo); -+ // Paper end - this.blockEntitiesData = Lists.newArrayList(); - - for(Map.Entry entry2 : chunk.getBlockEntities().entrySet()) { -@@ -85,9 +95,15 @@ public class ClientboundLevelChunkPacketData { - return byteBuf; - } - -- public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { -+ // Paper start - Anti-Xray - Add chunk packet info -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { ClientboundLevelChunkPacketData.extractChunkData(buf, chunk, null); } -+ public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo) { -+ int chunkSectionIndex = 0; -+ - for(LevelChunkSection levelChunkSection : chunk.getSections()) { -- levelChunkSection.write(buf); -+ levelChunkSection.write(buf, chunkPacketInfo, chunkSectionIndex); -+ chunkSectionIndex++; -+ // Paper end - } - - } -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java -index 26e46d751c8f8162c2bafe2fc109fc91dc4b7c0f..6412dff5ed0505f62dd5b71ab9606257858a7317 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java -@@ -13,13 +13,30 @@ public class ClientboundLevelChunkWithLightPacket implements Packet chunkPacketInfo = modifyBlocks ? chunk.getLevel().chunkPacketBlockController.getChunkPacketInfo(this, chunk) : null; -+ this.chunkData = new ClientboundLevelChunkPacketData(chunk, chunkPacketInfo); -+ // Paper end - this.lightData = new ClientboundLightUpdatePacketData(chunkPos, lightProvider, skyBits, blockBits); -+ chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks - } - - public ClientboundLevelChunkWithLightPacket(FriendlyByteBuf buf) { -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 4357d45305cdf82659fcc0df9fa42b1ae1029cc1..811c9c7970dfef290acdf0bbd803b27ca81a4767 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -570,7 +570,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - // Holder holder = worlddimension.type(); // CraftBukkit - decompile error - - // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error -- super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess()))); // Paper - create paper world configs -+ super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess())), executor); // Paper - create paper world configs; Async-Anti-Xray: Pass executor - this.pvpMode = minecraftserver.isPvpAllowed(); - this.convertable = convertable_conversionsession; - this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 5063eb6d4a24600262c32d2c9eb5fb5bf8fa354e..692a01b52a71e26887ee42cbd5fd64b0a81bfc99 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -49,7 +49,7 @@ import org.bukkit.event.player.PlayerInteractEvent; - public class ServerPlayerGameMode { - - private static final Logger LOGGER = LogUtils.getLogger(); -- protected ServerLevel level; -+ public ServerLevel level; // Paper - Anti-Xray - protected -> public - protected final ServerPlayer player; - private GameType gameModeForPlayer; - @Nullable -@@ -326,6 +326,8 @@ public class ServerPlayerGameMode { - } - - } -+ -+ this.level.chunkPacketBlockController.onPlayerLeftClickBlock(this, pos, action, direction, worldHeight, sequence); // Paper - Anti-Xray - } - - public void destroyAndAck(BlockPos pos, int sequence, String reason) { -diff --git a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java -index f3b96a921e7d085b51da62fa5493384a7ded1f9d..12f2bf95d3ea3d29f6b4b9ec38a92f7102daa4a1 100644 ---- a/src/main/java/net/minecraft/server/network/PlayerChunkSender.java -+++ b/src/main/java/net/minecraft/server/network/PlayerChunkSender.java -@@ -88,7 +88,10 @@ public class PlayerChunkSender { - - public static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) { // Paper - rewrite chunk loader - public - handler.player.serverLevel().chunkSource.chunkMap.getVisibleChunkIfPresent(chunk.getPos().toLong()).addPlayer(handler.player); -- handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), (BitSet)null, (BitSet)null)); -+ // Paper start - Anti-Xray -+ final boolean shouldModify = world.chunkPacketBlockController.shouldModify(handler.player, chunk); -+ handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), (BitSet)null, (BitSet)null, shouldModify)); -+ // Paper end - Anti-Xray - // Paper start - PlayerChunkLoadEvent - if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { - new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), handler.getPlayer().getBukkitEntity()).callEvent(); -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 23443444ae0c52392bd9cdd758057437d99e376a..10f2222c95362c41cab693410d0e7bb9746f3e4d 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -418,7 +418,7 @@ public abstract class PlayerList { - .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) -+ worldserver1.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null, true) - ); - } - // Paper end - Send empty chunk -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 6f2515d3476f7a5da898efb3c60e8f4aaad09b06..927c7948e567764e8cf75c7ce486e1ea6c9a8d87 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -172,6 +172,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - // Paper end - add paper world config - -+ public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray - public final co.aikar.timings.WorldTimingsHandler timings; // Paper - public static BlockPos lastPhysicsProblem; // Spigot - private org.spigotmc.TickLimiter entityLimiter; -@@ -197,7 +198,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - public abstract ResourceKey getTypeKey(); - -- protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator) { // Paper - create paper world config -+ protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - create paper world config; Async-Anti-Xray: Pass executor - this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot - this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config - this.generator = gen; -@@ -283,6 +284,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - this.keepSpawnInMemory = this.paperConfig().spawn.keepSpawnLoaded; // Paper - Option to keep spawn chunks loaded - this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime); - this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); -+ this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray - } - - // Paper start - Cancel hit for vanished players -@@ -558,6 +560,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - // CraftBukkit end - - BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag -+ this.chunkPacketBlockController.onBlockChange(this, pos, state, iblockdata1, flags, maxUpdateDepth); // Paper - Anti-Xray - - if (iblockdata1 == null) { - // CraftBukkit start - remove blockstate if failed (or the same) -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -index 5e8d2e4245757a0889645ea79ee68afb53f7dde4..f7e5e016a7028a9196e689e950805b0d5b31fe38 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -@@ -152,17 +152,17 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom - } - } - -- ChunkAccess.replaceMissingSections(biomeRegistry, this.sections); -+ this.replaceMissingSections(biomeRegistry, this.sections); // Paper - Anti-Xray - make it a non-static method - // CraftBukkit start - this.biomeRegistry = biomeRegistry; - } - public final Registry biomeRegistry; - // CraftBukkit end - -- private static void replaceMissingSections(Registry biomeRegistry, LevelChunkSection[] sectionArray) { -+ private void replaceMissingSections(Registry biomeRegistry, LevelChunkSection[] sectionArray) { // Paper - Anti-Xray - static -> non-static - for (int i = 0; i < sectionArray.length; ++i) { - if (sectionArray[i] == null) { -- sectionArray[i] = new LevelChunkSection(biomeRegistry); -+ sectionArray[i] = new LevelChunkSection(biomeRegistry, this.levelHeightAccessor instanceof net.minecraft.world.level.Level ? (net.minecraft.world.level.Level) this.levelHeightAccessor : null, this.chunkPos, this.levelHeightAccessor.getSectionYFromSectionIndex(i)); // Paper start - Anti-Xray - Add parameters - } - } - -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 6a5756bd333d9b221e7770842e5114d295cb7f1d..2eeb0c78f2b717b59542b6b668371558ae2fcc25 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -91,7 +91,7 @@ public class LevelChunk extends ChunkAccess { - } - - public LevelChunk(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks blockTickScheduler, LevelChunkTicks fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) { -- super(pos, upgradeData, world, world.registryAccess().registryOrThrow(Registries.BIOME), inhabitedTime, sectionArrayInitializer, blendingData); -+ super(pos, upgradeData, world, net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME), inhabitedTime, sectionArrayInitializer, blendingData); // Paper - Anti-Xray - The world isn't ready yet, use server singleton for registry - this.tickersInLevel = Maps.newHashMap(); - this.level = (ServerLevel) world; // CraftBukkit - type - this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap(); -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -index b606e33f8b64eaba28c008cc353d88aa45549e31..8852263cb6faec1b68326145aa30e5cd36d066e7 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -33,9 +33,12 @@ public class LevelChunkSection { - this.recalcBlockCounts(); - } - -- public LevelChunkSection(Registry biomeRegistry) { -- this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); -- this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); -+ // Paper start - Anti-Xray - Add parameters -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public LevelChunkSection(Registry biomeRegistry) { this(biomeRegistry, null, null, 0); } -+ public LevelChunkSection(Registry biomeRegistry, net.minecraft.world.level.Level level, net.minecraft.world.level.ChunkPos chunkPos, int chunkSectionY) { -+ // Paper end -+ this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, level == null || level.chunkPacketBlockController == null ? null : level.chunkPacketBlockController.getPresetBlockStates(level, chunkPos, chunkSectionY)); // Paper - Anti-Xray - Add preset block states -+ this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes - } - - public BlockState getBlockState(int x, int y, int z) { -@@ -172,10 +175,13 @@ public class LevelChunkSection { - this.biomes = datapaletteblock; - } - -- public void write(FriendlyByteBuf buf) { -+ // Paper start - Anti-Xray - Add chunk packet info -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); } -+ public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { - buf.writeShort(this.nonEmptyBlockCount); -- this.states.write(buf); -- this.biomes.write(buf); -+ this.states.write(buf, chunkPacketInfo, chunkSectionIndex); -+ this.biomes.write(buf, null, chunkSectionIndex); -+ // Paper end - } - - public int getSerializedSize() { -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 dfae0918079425df92d958b04275be8ae60d4b60..0f930f8355ea99d1cb1a8d27edc1c224588f852f 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -30,6 +30,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - return 0; - }; - public final IdMap registry; -+ private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values - private volatile PalettedContainer.Data data; - private final PalettedContainer.Strategy strategy; - // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused -@@ -42,14 +43,19 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - // this.threadingDetector.checkAndUnlock(); // Paper - disable this - } - -- public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { -- PalettedContainerRO.Unpacker> unpacker = PalettedContainer::unpack; -+ // Paper start - Anti-Xray - Add preset values -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { return PalettedContainer.codecRW(idList, entryCodec, paletteProvider, defaultValue, null); } -+ public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { -+ PalettedContainerRO.Unpacker> unpacker = (idListx, paletteProviderx, serialized) -> { -+ return unpack(idListx, paletteProviderx, serialized, defaultValue, presetValues); -+ }; -+ // Paper end - return codec(idList, entryCodec, paletteProvider, defaultValue, unpacker); - } - - public static Codec> codecRO(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { - PalettedContainerRO.Unpacker> unpacker = (idListx, paletteProviderx, serialized) -> { -- return unpack(idListx, paletteProviderx, serialized).map((result) -> { -+ return unpack(idListx, paletteProviderx, serialized, defaultValue, null).map((result) -> { // Paper - Anti-Xray - Add preset values - return result; - }); - }; -@@ -66,19 +72,52 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - }); - } - -- public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries) { -+ // Paper start - Anti-Xray - Add preset values -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries) { this(idList, paletteProvider, dataProvider, storage, paletteEntries, null, null); } -+ public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { -+ this.presetValues = presetValues; - this.registry = idList; - this.strategy = paletteProvider; - this.data = new PalettedContainer.Data<>(dataProvider, storage, dataProvider.factory().create(dataProvider.bits(), idList, this, paletteEntries)); -+ -+ if (presetValues != null && (dataProvider.factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY ? this.data.palette.valueFor(0) != defaultValue : dataProvider.factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY)) { -+ // In 1.18 Mojang unfortunately removed code that already handled possible resize operations on read from disk for us -+ // We readd this here but in a smarter way than it was before -+ int maxSize = 1 << dataProvider.bits(); -+ -+ for (T presetValue : presetValues) { -+ if (this.data.palette.getSize() >= maxSize) { -+ java.util.Set allValues = new java.util.HashSet<>(paletteEntries); -+ allValues.addAll(Arrays.asList(presetValues)); -+ int newBits = Mth.ceillog2(allValues.size()); -+ -+ if (newBits > dataProvider.bits()) { -+ this.onResize(newBits, null); -+ } -+ -+ break; -+ } -+ -+ this.data.palette.idFor(presetValue); -+ } -+ } -+ // Paper end - } - -- private PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data data) { -+ // Paper start - Anti-Xray - Add preset values -+ private PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data data, T @org.jetbrains.annotations.Nullable [] presetValues) { -+ this.presetValues = presetValues; -+ // Paper end - this.registry = idList; - this.strategy = paletteProvider; - this.data = data; - } - -- public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider) { -+ // Paper start - Anti-Xray - Add preset values -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider) { this(idList, object, paletteProvider, null); } -+ public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider, T @org.jetbrains.annotations.Nullable [] presetValues) { -+ this.presetValues = presetValues; -+ // Paper end - this.strategy = paletteProvider; - this.registry = idList; - this.data = this.createOrReuseData((PalettedContainer.Data)null, 0); -@@ -93,11 +132,33 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - @Override - public synchronized int onResize(int newBits, T object) { // Paper - synchronize - PalettedContainer.Data data = this.data; -+ -+ // Paper start - Anti-Xray - Add preset values -+ if (this.presetValues != null && object != null && data.configuration().factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY) { -+ int duplicates = 0; -+ List presetValues = Arrays.asList(this.presetValues); -+ duplicates += presetValues.contains(object) ? 1 : 0; -+ duplicates += presetValues.contains(data.palette.valueFor(0)) ? 1 : 0; -+ newBits = Mth.ceillog2((1 << this.strategy.calculateBitsForSerialization(this.registry, 1 << newBits)) + presetValues.size() - duplicates); -+ } -+ - PalettedContainer.Data data2 = this.createOrReuseData(data, newBits); - data2.copyFrom(data.palette, data.storage); - this.data = data2; -- return data2.palette.idFor(object); -+ this.addPresetValues(); -+ return object == null ? -1 : data2.palette.idFor(object); -+ // Paper end -+ } -+ -+ // Paper start - Anti-Xray - Add preset values -+ private void addPresetValues() { -+ if (this.presetValues != null && this.data.configuration().factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY) { -+ for (T presetValue : this.presetValues) { -+ this.data.palette.idFor(presetValue); -+ } -+ } - } -+ // Paper end - - public T getAndSet(int x, int y, int z, T value) { - this.acquire(); -@@ -167,25 +228,34 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - data.palette.read(buf); - buf.readLongArray(data.storage.getRaw()); - this.data = data; -+ this.addPresetValues(); // Paper - Anti-Xray - Add preset values (inefficient, but this isn't used by the server) - } finally { - this.release(); - } - - } - -+ // Paper start - Anti-Xray; Add chunk packet info -+ @Override -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); } - @Override -- public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize -+ public synchronized void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { // Paper - Synchronize - this.acquire(); - - try { -- this.data.write(buf); -+ this.data.write(buf, chunkPacketInfo, chunkSectionIndex); -+ -+ if (chunkPacketInfo != null) { -+ chunkPacketInfo.setPresetValues(chunkSectionIndex, this.presetValues); -+ } -+ // Paper end - } finally { - this.release(); - } - - } - -- private static DataResult> unpack(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainerRO.PackedData serialized) { -+ private static DataResult> unpack(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainerRO.PackedData serialized, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { // Paper - Anti-Xray - Add preset values - List list = serialized.paletteEntries(); - int i = paletteProvider.size(); - int j = paletteProvider.calculateBitsForSerialization(idList, list.size()); -@@ -225,7 +295,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - } - -- return DataResult.success(new PalettedContainer<>(idList, paletteProvider, configuration, bitStorage, list)); -+ return DataResult.success(new PalettedContainer<>(idList, paletteProvider, configuration, bitStorage, list, defaultValue, presetValues)); // Paper - Anti-Xray - Add preset values - } - - @Override -@@ -285,12 +355,12 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - - public PalettedContainer copy() { -- return new PalettedContainer<>(this.registry, this.strategy, this.data.copy()); -+ return new PalettedContainer<>(this.registry, this.strategy, this.data.copy(), this.presetValues); // Paper - Anti-Xray - Add preset values - } - - @Override - public PalettedContainer recreate() { -- return new PalettedContainer<>(this.registry, this.data.palette.valueFor(0), this.strategy); -+ return new PalettedContainer<>(this.registry, this.data.palette.valueFor(0), this.strategy, this.presetValues); // Paper - Anti-Xray - Add preset values - } - - @Override -@@ -334,9 +404,18 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - return 1 + this.palette.getSerializedSize() + VarInt.getByteSize(this.storage.getRaw().length) + this.storage.getRaw().length * 8; - } - -- public void write(FriendlyByteBuf buf) { -+ // Paper start - Anti-Xray - Add chunk packet info -+ public void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex) { - buf.writeByte(this.storage.getBits()); - this.palette.write(buf); -+ -+ if (chunkPacketInfo != null) { -+ chunkPacketInfo.setBits(chunkSectionIndex, this.configuration.bits()); -+ chunkPacketInfo.setPalette(chunkSectionIndex, this.palette); -+ chunkPacketInfo.setIndex(chunkSectionIndex, buf.writerIndex() + VarInt.getByteSize(this.storage.getRaw().length)); -+ } -+ // Paper end -+ - buf.writeLongArray(this.storage.getRaw()); - } - -diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java -index 9a2bf744abd8916d492e901be889223591bac3fd..1dd415c96d17eff8e7555c33d3c52e57f2559fa5 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java -@@ -14,7 +14,10 @@ public interface PalettedContainerRO { - - void getAll(Consumer action); - -- void write(FriendlyByteBuf buf); -+ // Paper start - Anti-Xray - Add chunk packet info -+ @Deprecated @io.papermc.paper.annotation.DoNotUse void write(FriendlyByteBuf buf); -+ void write(FriendlyByteBuf buf, @javax.annotation.Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int chunkSectionIndex); -+ // Paper end - - int getSerializedSize(); - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 5d50f1bcba507975b8942529104c0ccd5e08c252..1ee35c828ea637e2954158060e1ad98f2649cedd 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -71,7 +71,7 @@ import org.slf4j.Logger; - - public class ChunkSerializer { - -- public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); -+ public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states - private static final Logger LOGGER = LogUtils.getLogger(); - private static final String TAG_UPGRADE_DATA = "UpgradeData"; - private static final String BLOCK_TICKS_TAG = "block_ticks"; -@@ -172,16 +172,20 @@ public class ChunkSerializer { - if (k >= 0 && k < achunksection.length) { - Logger logger; - PalettedContainer datapaletteblock; -+ // Paper start - Anti-Xray - Add preset block states -+ BlockState[] presetBlockStates = world.chunkPacketBlockController.getPresetBlockStates(world, chunkPos, b0); - - if (nbttagcompound1.contains("block_states", 10)) { -- dataresult = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> { -+ Codec> blockStateCodec = presetBlockStates == null ? ChunkSerializer.BLOCK_STATE_CODEC : PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), presetBlockStates); -+ dataresult = blockStateCodec.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> { - ChunkSerializer.logErrors(chunkPos, b0, s); - }); - logger = ChunkSerializer.LOGGER; - Objects.requireNonNull(logger); - datapaletteblock = (PalettedContainer) ((DataResult>) dataresult).getOrThrow(false, logger::error); // CraftBukkit - decompile error - } else { -- datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); -+ datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, presetBlockStates); -+ // Paper end - } - - PalettedContainer object; // CraftBukkit - read/write -@@ -194,7 +198,7 @@ public class ChunkSerializer { - Objects.requireNonNull(logger); - object = ((DataResult>>) dataresult).getOrThrow(false, logger::error); // CraftBukkit - decompile error - } else { -- object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); -+ object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes - } - - LevelChunkSection chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write -@@ -429,7 +433,7 @@ public class ChunkSerializer { - - // CraftBukkit start - read/write - private static Codec>> makeBiomeCodecRW(Registry iregistry) { -- return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS)); -+ return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS), null); // Paper - Anti-Xray - Add preset biomes - } - // CraftBukkit end - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index d4e0ef75dd12709a0dcf9193821c30b8943e6c36..fd702027e62eb38d51fb7c46ef268e9bb94e1e92 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -53,7 +53,7 @@ public class CraftChunk implements Chunk { - private final ServerLevel worldServer; - private final int x; - private final int z; -- private static final PalettedContainer emptyBlockIDs = new PalettedContainer<>(net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); -+ private static final PalettedContainer emptyBlockIDs = new PalettedContainer<>(net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null); // Paper - Anti-Xray - Add preset block states - private static final byte[] FULL_LIGHT = new byte[2048]; - private static final byte[] EMPTY_LIGHT = new byte[2048]; - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 3ea2cee19e6fe1b94e39ef535102d41ec2c0bdad..b8f05300a17d415505deb8da4a923357220ae1bd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2610,7 +2610,7 @@ public final class CraftServer implements Server { - public ChunkGenerator.ChunkData createChunkData(World world) { - Preconditions.checkArgument(world != null, "World cannot be null"); - ServerLevel handle = ((CraftWorld) world).getHandle(); -- return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(Registries.BIOME)); -+ return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(Registries.BIOME), world); // Paper - Anti-Xray - Add parameters - } - - // Paper start - Allow delegation to vanilla chunk gen -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index d1cff7794313fd29717e9d7818ccf00e340f08a9..a139601888b88e8580bdb9c2469386a94abae975 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -524,11 +524,16 @@ public class CraftWorld extends CraftRegionAccessor implements World { - List playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false); - if (playersInRange.isEmpty()) return true; // Paper - rewrite player chunk loader - -- ClientboundLevelChunkWithLightPacket refreshPacket = new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null); -+ // Paper start - Anti-Xray - Bypass -+ Map refreshPackets = new HashMap<>(); - for (ServerPlayer player : playersInRange) { - if (player.connection == null) continue; - -- player.connection.send(refreshPacket); -+ Boolean shouldModify = chunk.getLevel().chunkPacketBlockController.shouldModify(player, chunk); -+ player.connection.send(refreshPackets.computeIfAbsent(shouldModify, s -> { // Use connection to prevent creating firing event -+ return new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null, (Boolean) s); -+ })); -+ // Paper end - } - // Paper - rewrite player chunk loader - -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java -index 9b640705f2c810160aa7fea5006429ec41d0c858..44a010590e830fd238cf6fdda443e28b72022e66 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java -@@ -27,8 +27,13 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData { - private final Registry biomes; - private Set tiles; - private final Set lights = new HashSet<>(); -+ // Paper start - Anti-Xray - Add parameters -+ private final org.bukkit.World world; - -- public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes) { -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes) { this(minHeight, maxHeight, biomes, null); } -+ public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes, org.bukkit.World world) { -+ this.world = world; -+ // Paper end - this.minHeight = minHeight; - this.maxHeight = maxHeight; - this.biomes = biomes; -@@ -176,7 +181,7 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData { - int offset = (y - this.minHeight) >> 4; - LevelChunkSection section = this.sections[offset]; - if (create && section == null) { -- this.sections[offset] = section = new LevelChunkSection(this.biomes); -+ this.sections[offset] = section = new LevelChunkSection(this.biomes, this.world instanceof org.bukkit.craftbukkit.CraftWorld ? ((org.bukkit.craftbukkit.CraftWorld) this.world).getHandle() : null, null, offset + (this.minHeight >> 4)); // Paper - Anti-Xray - Add parameters - } - return section; - } diff --git a/patches/server/1002-Improved-Watchdog-Support.patch b/patches/server/1002-Improved-Watchdog-Support.patch new file mode 100644 index 000000000000..0f1593abcebf --- /dev/null +++ b/patches/server/1002-Improved-Watchdog-Support.patch @@ -0,0 +1,544 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 12 Apr 2020 15:50:48 -0400 +Subject: [PATCH] Improved Watchdog Support + +Forced Watchdog Crash support and Improve Async Shutdown + +If the request to shut down the server is received while we are in +a watchdog hang, immediately treat it as a crash and begin the shutdown +process. Shutdown process is now improved to also shutdown cleanly when +not using restart scripts either. + +If a server is deadlocked, a server owner can send SIGUP (or any other signal +the JVM understands to shut down as it currently does) and the watchdog +will no longer need to wait until the full timeout, allowing you to trigger +a close process and try to shut the server down gracefully, saving player and +world data. + +Previously there was no way to trigger this outside of waiting for a full watchdog +timeout, which may be set to a really long time... + +Additionally, fix everything to do with shutting the server down asynchronously. + +Previously, nearly everything about the process was fragile and unsafe. Main might +not have actually been frozen, and might still be manipulating state. + +Or, some reuest might ask main to do something in the shutdown but main is dead. + +Or worse, other things might start closing down items such as the Console or Thread Pool +before we are fully shutdown. + +This change tries to resolve all of these issues by moving everything into the stop +method and guaranteeing only one thread is stopping the server. + +We then issue Thread Death to the main thread of another thread initiates the stop process. +We have to ensure Thread Death propagates correctly though to stop main completely. + +This is to ensure that if main isn't truely stuck, it's not manipulating state we are trying to save. + +This also moves all plugins who register "delayed init" tasks to occur just before "Done" so they +are properly accounted for and wont trip watchdog on init. + +diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java +index 6aaed8e8bf8c721fc834da5c76ac72a4c3e92458..4b002e8b75d117b726b0de274a76d3596fce015b 100644 +--- a/src/main/java/com/destroystokyo/paper/Metrics.java ++++ b/src/main/java/com/destroystokyo/paper/Metrics.java +@@ -92,7 +92,12 @@ public class Metrics { + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { +- final Runnable submitTask = this::submitData; ++ final Runnable submitTask = () -> { ++ if (MinecraftServer.getServer().hasStopped()) { ++ return; ++ } ++ submitData(); ++ }; + + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into the initial and second delay. +diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java +index 6f2452de76e8f5fcc1367066e0e753740764eb98..e047dee632022abfe05865d1e71838be8d5d053a 100644 +--- a/src/main/java/net/minecraft/CrashReport.java ++++ b/src/main/java/net/minecraft/CrashReport.java +@@ -234,6 +234,7 @@ public class CrashReport { + } + + public static CrashReport forThrowable(Throwable cause, String title) { ++ if (cause instanceof ThreadDeath) com.destroystokyo.paper.util.SneakyThrow.sneaky(cause); // Paper + while (cause instanceof CompletionException && cause.getCause() != null) { + cause = cause.getCause(); + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 1dfafbe508b4e4598339f412e5fb9d92717b5d26..97826afb097851f5736a64ae154d42147de55648 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -296,7 +296,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); + public int autosavePeriod; + public Commands vanillaCommandDispatcher; +- private boolean forceTicks; ++ public boolean forceTicks; // Paper + // CraftBukkit end + // Spigot start + public static final int TPS = 20; +@@ -309,6 +309,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { + AtomicReference atomicreference = new AtomicReference(); + Thread thread = new io.papermc.paper.util.TickThread(() -> { // Paper - rewrite chunk system +@@ -928,6 +931,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop {}; ++ } ++ // Paper end + return new TickTask(this.tickCount, runnable); + } + +@@ -2111,7 +2167,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements Profiler + try { + task.run(); + } catch (Exception var3) { ++ if (var3.getCause() instanceof ThreadDeath) throw var3; // Paper + LOGGER.error(LogUtils.FATAL_MARKER, "Error executing task on {}", this.name(), var3); + } + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 7694b7f299495a084ce71c5d04e5e690a75fe55b..9149ddd3d950d1616ec3c85d54c494bcc6501e83 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -935,6 +935,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + try { + tickConsumer.accept(entity); + } catch (Throwable throwable) { ++ if (throwable instanceof ThreadDeath) throw throwable; // Paper + // Paper start - Prevent block entity and entity crashes + final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); + MinecraftServer.LOGGER.error(msg, throwable); +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 2eeb0c78f2b717b59542b6b668371558ae2fcc25..6ec3fc801453fd54c25b642e6fa71c19b463311d 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -1176,6 +1176,7 @@ public class LevelChunk extends ChunkAccess { + + gameprofilerfiller.pop(); + } catch (Throwable throwable) { ++ if (throwable instanceof ThreadDeath) throw throwable; // Paper + // Paper start - Prevent block entity and entity crashes + final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ()); + net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index d931a4c6e633bf9c1b3e5e18e880e2ddbfe4aa2a..ce341f42b3a5e17fb6d1f7de8057e73137ae2a6e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -186,6 +186,36 @@ public class Main { + + OptionSet options = null; + ++ // Paper start - preload logger classes to avoid plugins mixing versions ++ tryPreloadClass("org.apache.logging.log4j.core.Core"); ++ tryPreloadClass("org.apache.logging.log4j.core.appender.AsyncAppender"); ++ tryPreloadClass("org.apache.logging.log4j.core.Appender"); ++ tryPreloadClass("org.apache.logging.log4j.core.ContextDataInjector"); ++ tryPreloadClass("org.apache.logging.log4j.core.Filter"); ++ tryPreloadClass("org.apache.logging.log4j.core.ErrorHandler"); ++ tryPreloadClass("org.apache.logging.log4j.core.LogEvent"); ++ tryPreloadClass("org.apache.logging.log4j.core.Logger"); ++ tryPreloadClass("org.apache.logging.log4j.core.LoggerContext"); ++ tryPreloadClass("org.apache.logging.log4j.core.LogEventListener"); ++ tryPreloadClass("org.apache.logging.log4j.core.AbstractLogEvent"); ++ tryPreloadClass("org.apache.logging.log4j.message.AsynchronouslyFormattable"); ++ tryPreloadClass("org.apache.logging.log4j.message.FormattedMessage"); ++ tryPreloadClass("org.apache.logging.log4j.message.ParameterizedMessage"); ++ tryPreloadClass("org.apache.logging.log4j.message.Message"); ++ tryPreloadClass("org.apache.logging.log4j.message.MessageFactory"); ++ tryPreloadClass("org.apache.logging.log4j.message.TimestampMessage"); ++ tryPreloadClass("org.apache.logging.log4j.message.SimpleMessage"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLogger"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerContext"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncQueueFullPolicy"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerDisruptor"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEvent"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.DisruptorUtil"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEventHandler"); ++ tryPreloadClass("org.apache.logging.log4j.core.impl.ThrowableProxy"); ++ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedClassInfo"); ++ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedStackTraceElement"); ++ // Paper end + try { + options = parser.parse(args); + } catch (joptsimple.OptionException ex) { +@@ -297,8 +327,65 @@ public class Main { + } catch (Throwable t) { + t.printStackTrace(); + } ++ // Paper start ++ // load some required classes to avoid errors during shutdown if jar is replaced ++ // also to guarantee our version loads over plugins ++ tryPreloadClass("com.destroystokyo.paper.util.SneakyThrow"); ++ tryPreloadClass("com.google.common.collect.Iterators$PeekingImpl"); ++ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$Values"); ++ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$ValueIterator"); ++ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$WriteThroughEntry"); ++ tryPreloadClass("com.google.common.collect.Iterables"); ++ for (int i = 1; i <= 15; i++) { ++ tryPreloadClass("com.google.common.collect.Iterables$" + i, false); ++ } ++ tryPreloadClass("org.apache.commons.lang3.mutable.MutableBoolean"); ++ tryPreloadClass("org.apache.commons.lang3.mutable.MutableInt"); ++ tryPreloadClass("org.jline.terminal.impl.MouseSupport"); ++ tryPreloadClass("org.jline.terminal.impl.MouseSupport$1"); ++ tryPreloadClass("org.jline.terminal.Terminal$MouseTracking"); ++ tryPreloadClass("co.aikar.timings.TimingHistory"); ++ tryPreloadClass("co.aikar.timings.TimingHistory$MinuteReport"); ++ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext"); ++ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$11"); ++ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$12"); ++ tryPreloadClass("io.netty.channel.AbstractChannel$AbstractUnsafe$8"); ++ tryPreloadClass("io.netty.util.concurrent.DefaultPromise"); ++ tryPreloadClass("io.netty.util.concurrent.DefaultPromise$1"); ++ tryPreloadClass("io.netty.util.internal.PromiseNotificationUtil"); ++ tryPreloadClass("io.netty.util.internal.SystemPropertyUtil"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$1"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$2"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$3"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$4"); ++ tryPreloadClass("org.slf4j.helpers.MessageFormatter"); ++ tryPreloadClass("org.slf4j.helpers.FormattingTuple"); ++ tryPreloadClass("org.slf4j.helpers.BasicMarker"); ++ tryPreloadClass("org.slf4j.helpers.Util"); ++ tryPreloadClass("com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent"); ++ tryPreloadClass("com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent"); ++ // Minecraft, seen during saving ++ tryPreloadClass(net.minecraft.world.level.lighting.LayerLightEventListener.DummyLightLayerEventListener.class.getName()); ++ tryPreloadClass(net.minecraft.world.level.lighting.LayerLightEventListener.class.getName()); ++ tryPreloadClass(net.minecraft.util.ExceptionCollector.class.getName()); ++ tryPreloadClass(io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.PlayerChunkLoaderData.class.getName()); ++ // Paper end ++ } ++ } ++ ++ // Paper start ++ private static void tryPreloadClass(String className) { ++ tryPreloadClass(className, true); ++ } ++ private static void tryPreloadClass(String className, boolean printError) { ++ try { ++ Class.forName(className); ++ } catch (ClassNotFoundException e) { ++ if (printError) System.err.println("An expected class " + className + " was not found for preloading: " + e.getMessage()); + } + } ++ // Paper end + + private static List asList(String... params) { + return Arrays.asList(params); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +index c6e8441e299f477ddb22c1ce2618710763978f1a..e8e93538dfd71de86515d9405f728db1631e949a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +@@ -12,11 +12,27 @@ public class ServerShutdownThread extends Thread { + @Override + public void run() { + try { ++ // Paper start - try to shutdown on main ++ server.safeShutdown(false, false); ++ for (int i = 1000; i > 0 && !server.hasStopped(); i -= 100) { ++ Thread.sleep(100); ++ } ++ if (server.hasStopped()) { ++ while (!server.hasFullyShutdown) Thread.sleep(1000); ++ return; ++ } ++ // Looks stalled, close async + org.spigotmc.AsyncCatcher.enabled = false; // Spigot ++ server.forceTicks = true; + this.server.close(); ++ while (!server.hasFullyShutdown) Thread.sleep(1000); ++ } catch (InterruptedException e) { ++ e.printStackTrace(); ++ // Paper end + } finally { ++ org.apache.logging.log4j.LogManager.shutdown(); // Paper + try { +- net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender ++ //net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Move into stop + } catch (Exception e) { + } + } +diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java +index e3b262add194a126e731c68e68f3139a00cacacb..da7d5efd76c9ef92e9ce22860fec791890a687be 100644 +--- a/src/main/java/org/spigotmc/RestartCommand.java ++++ b/src/main/java/org/spigotmc/RestartCommand.java +@@ -138,7 +138,7 @@ public class RestartCommand extends Command + // Paper end + + // Paper start - copied from above and modified to return if the hook registered +- private static boolean addShutdownHook(String restartScript) ++ public static boolean addShutdownHook(String restartScript) + { + String[] split = restartScript.split( " " ); + if ( split.length > 0 && new File( split[0] ).isFile() ) +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index a284d3b8526a743ba4389ec5b44d80af6d0e5a5f..0234555978d1b13051f876a257e47bafad37b0f8 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -11,6 +11,7 @@ import org.bukkit.Bukkit; + public final class WatchdogThread extends io.papermc.paper.util.TickThread // Paper - rewrite chunk system + { + ++ public static final boolean DISABLE_WATCHDOG = Boolean.getBoolean("disable.watchdog"); // Paper + private static WatchdogThread instance; + private long timeoutTime; + private boolean restart; +@@ -39,6 +40,7 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa + { + if ( WatchdogThread.instance == null ) + { ++ if (timeoutTime <= 0) timeoutTime = 300; // Paper + WatchdogThread.instance = new WatchdogThread( timeoutTime * 1000L, restart ); + WatchdogThread.instance.start(); + } else +@@ -70,12 +72,13 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa + // Paper start + Logger log = Bukkit.getServer().getLogger(); + long currentTime = WatchdogThread.monotonicMillis(); +- if ( this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable ++ MinecraftServer server = MinecraftServer.getServer(); ++ if ( this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.hasStarted && (!server.isRunning() || (currentTime > this.lastTick + this.earlyWarningEvery && !DISABLE_WATCHDOG) )) // Paper - add property to disable + { +- boolean isLongTimeout = currentTime > lastTick + timeoutTime; ++ boolean isLongTimeout = currentTime > lastTick + timeoutTime || (!server.isRunning() && !server.hasStopped() && currentTime > lastTick + 1000); + // Don't spam early warning dumps + if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue; +- if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... ++ if ( !isLongTimeout && server.hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... + lastEarlyWarning = currentTime; + if (isLongTimeout) { + // Paper end +@@ -136,9 +139,24 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa + + if ( isLongTimeout ) + { +- if ( this.restart && !MinecraftServer.getServer().hasStopped() ) ++ if ( !server.hasStopped() ) + { +- RestartCommand.restart(); ++ AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us ++ server.forceTicks = true; ++ if (restart) { ++ RestartCommand.addShutdownHook( SpigotConfig.restartScript ); ++ } ++ // try one last chance to safe shutdown on main incase it 'comes back' ++ server.abnormalExit = true; ++ server.safeShutdown(false, restart); ++ try { ++ Thread.sleep(1000); ++ } catch (InterruptedException e) { ++ e.printStackTrace(); ++ } ++ if (!server.hasStopped()) { ++ server.close(); ++ } + } + break; + } // Paper end +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index 32e64b3866bdd1489a90339bda2268adafbb15de..675cd61221e807aadf28322b46c3daa1370241b5 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -1,5 +1,5 @@ + +- ++ + + + diff --git a/patches/server/1003-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch b/patches/server/1003-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch deleted file mode 100644 index c4b823d114be..000000000000 --- a/patches/server/1003-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch +++ /dev/null @@ -1,89 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Mon, 5 Apr 2021 01:42:35 -0400 -Subject: [PATCH] Improve cancelling PreCreatureSpawnEvent with per player mob - spawns - - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 0d552d4b967687e2bfb92b1e5106071460082409..9037ba5197eed9d8e616fb65369f6b1a5ea9562c 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -306,8 +306,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - } - -+ // Paper start - per player mob count backoff -+ public void updateFailurePlayerMobTypeMap(int chunkX, int chunkZ, net.minecraft.world.entity.MobCategory mobCategory) { -+ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { -+ return; -+ } -+ int idx = mobCategory.ordinal(); -+ final com.destroystokyo.paper.util.maplist.ReferenceList inRange = -+ this.getNearbyPlayers().getPlayersByChunk(chunkX, chunkZ, io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE); -+ if (inRange == null) { -+ return; -+ } -+ final Object[] backingSet = inRange.getRawData(); -+ for (int i = 0, len = inRange.size(); i < len; i++) { -+ ++((ServerPlayer)backingSet[i]).mobBackoffCounts[idx]; -+ } -+ } -+ // Paper end - per player mob count backoff - public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) { -- return player.mobCounts[mobCategory.ordinal()]; -+ return player.mobCounts[mobCategory.ordinal()] + player.mobBackoffCounts[mobCategory.ordinal()]; // Paper - per player mob count backoff - } - // Paper end - Optional per player mob spawns - -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 059ab637adf1be576fa1fff36a91b6c5f1b5f035..5afbb5b307cc67d86dd916dc8f7521d5d021e056 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -524,7 +524,17 @@ public class ServerChunkCache extends ChunkSource { - if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled - // re-set mob counts - for (ServerPlayer player : this.level.players) { -- Arrays.fill(player.mobCounts, 0); -+ // Paper start - per player mob spawning backoff -+ for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) { -+ player.mobCounts[ii] = 0; -+ -+ int newBackoff = player.mobBackoffCounts[ii] - 1; // TODO make configurable bleed // TODO use nonlinear algorithm? -+ if (newBackoff < 0) { -+ newBackoff = 0; -+ } -+ player.mobBackoffCounts[ii] = newBackoff; -+ } -+ // Paper end - per player mob spawning backoff - } - spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); - } else { -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 4bfc12a50a262f49c0262b06b39faa8116b3807f..803ed79940af00b49aec4b1414ddfacb57ff4f3f 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -260,6 +260,7 @@ public class ServerPlayer extends Player { - public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length; - public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper - // Paper end - Optional per player mob spawns -+ public final int[] mobBackoffCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper - per player mob count backoff - - // CraftBukkit start - public String displayName; -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index da9f091e1c2dd8b025e806f53a4708b623ee4cd8..17e9f3a30e287faf210e08dc7eb177a70f049f43 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -277,6 +277,11 @@ public final class NaturalSpawner { - - // Paper start - PreCreatureSpawnEvent - PreSpawnStatus doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2); -+ // Paper start - per player mob count backoff -+ if (doSpawning == PreSpawnStatus.ABORT || doSpawning == PreSpawnStatus.CANCELLED) { -+ world.getChunkSource().chunkMap.updateFailurePlayerMobTypeMap(blockposition_mutableblockposition.getX() >> 4, blockposition_mutableblockposition.getZ() >> 4, group); -+ } -+ // Paper end - per player mob count backoff - if (doSpawning == PreSpawnStatus.ABORT) { - return j; // Paper - Optional per player mob spawns - } diff --git a/patches/server/1009-Optimize-Voxel-Shape-Merging.patch b/patches/server/1003-Optimize-Voxel-Shape-Merging.patch similarity index 100% rename from patches/server/1009-Optimize-Voxel-Shape-Merging.patch rename to patches/server/1003-Optimize-Voxel-Shape-Merging.patch diff --git a/patches/server/1004-Optimize-Collision-to-not-load-chunks.patch b/patches/server/1004-Optimize-Collision-to-not-load-chunks.patch deleted file mode 100644 index 88daae9ea462..000000000000 --- a/patches/server/1004-Optimize-Collision-to-not-load-chunks.patch +++ /dev/null @@ -1,111 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 2 Apr 2020 02:37:57 -0400 -Subject: [PATCH] Optimize Collision to not load chunks - -The collision code takes an AABB and generates a cuboid of checks rather -than a cylinder, so at high velocity this can generate a lot of chunk checks. - -Treat an unloaded chunk as a collision for entities, and also for players if -the "prevent moving into unloaded chunks" setting is enabled. - -If that serting is not enabled, collisions will be ignored for players, since -movement will load only the chunk the player enters anyways and avoids loading -massive amounts of surrounding chunks due to large AABB lookups. - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index eae6121a2f3fb33146b0a625cc82c8bce8efc91b..b71d7714fbaa7d4493ccddf877c9668f25b104bf 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -934,6 +934,7 @@ public abstract class PlayerList { - entityplayer1.setShiftKeyDown(false); - entityplayer1.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); - -+ worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper - while (avoidSuffocation && !worldserver1.noCollision((Entity) entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { - // CraftBukkit end - entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 95a590f4a379c1905e700036042b9bde1c0f3264..0fc522f27afb8a0ded061392d011ff67294b16b3 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -241,6 +241,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason - - public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper -+ public boolean collisionLoadChunks = false; // Paper - private CraftEntity bukkitEntity; - - public @org.jetbrains.annotations.Nullable net.minecraft.server.level.ChunkMap.TrackedEntity tracker; // Paper -diff --git a/src/main/java/net/minecraft/world/level/BlockCollisions.java b/src/main/java/net/minecraft/world/level/BlockCollisions.java -index f2c423154ed6a00882a46d93b69ed4f6ba73782c..a3eaf80b020c3bbc0306c5d17659ee661dfd275b 100644 ---- a/src/main/java/net/minecraft/world/level/BlockCollisions.java -+++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java -@@ -65,22 +65,41 @@ public class BlockCollisions extends AbstractIterator { - protected T computeNext() { - while(true) { - if (this.cursor.advance()) { -- int i = this.cursor.nextX(); -- int j = this.cursor.nextY(); -- int k = this.cursor.nextZ(); -+ int i = this.cursor.nextX(); final int x = i; // Paper -+ int j = this.cursor.nextY(); final int y = j; // Paper -+ int k = this.cursor.nextZ(); final int z = k; // Paper - int l = this.cursor.getNextType(); - if (l == 3) { - continue; - } -+ // Paper start - ensure we don't load chunks -+ final @Nullable Entity source = this.context instanceof net.minecraft.world.phys.shapes.EntityCollisionContext entityContext ? entityContext.getEntity() : null; -+ boolean far = source != null && io.papermc.paper.util.MCUtil.distanceSq(source.getX(), y, source.getZ(), x, y, z) > 14; -+ this.pos.set(x, y, z); - -- BlockGetter blockGetter = this.getChunk(i, k); -- if (blockGetter == null) { -+ BlockState blockState; -+ if (this.collisionGetter instanceof net.minecraft.server.level.WorldGenRegion) { -+ BlockGetter blockGetter = this.getChunk(x, z); -+ if (blockGetter == null) { -+ continue; -+ } -+ blockState = blockGetter.getBlockState(this.pos); -+ } else if ((!far && source instanceof net.minecraft.server.level.ServerPlayer) || (source != null && source.collisionLoadChunks)) { -+ blockState = this.collisionGetter.getBlockState(this.pos); -+ } else { -+ blockState = this.collisionGetter.getBlockStateIfLoaded(this.pos); -+ } -+ -+ if (blockState == null) { -+ if (!(source instanceof net.minecraft.server.level.ServerPlayer) || source.level().paperConfig().chunks.preventMovingIntoUnloadedChunks) { -+ return this.resultProvider.apply(new BlockPos.MutableBlockPos(x, y, z), Shapes.create(far ? source.getBoundingBox() : new AABB(new BlockPos(x, y, z)))); -+ } -+ // Paper end - continue; - } - -- this.pos.set(i, j, k); -- BlockState blockState = blockGetter.getBlockState(this.pos); -- if (this.onlySuffocatingBlocks && !blockState.isSuffocating(blockGetter, this.pos) || l == 1 && !blockState.hasLargeCollisionShape() || l == 2 && !blockState.is(Blocks.MOVING_PISTON)) { -+ // Paper - moved up -+ if (/*this.onlySuffocatingBlocks && (!blockState.isSuffocating(blockGetter, this.pos)) ||*/ l == 1 && !blockState.hasLargeCollisionShape() || l == 2 && !blockState.is(Blocks.MOVING_PISTON)) { // Paper - onlySuffocatingBlocks is only true on the client, so we don't care about it here - continue; - } - -diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java -index 140d10807a3a6806578cd203ba58383590c2f2c0..c476e37df8a75d77f5093b2a449e04f25ef2c2dd 100644 ---- a/src/main/java/net/minecraft/world/level/CollisionGetter.java -+++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java -@@ -44,11 +44,13 @@ public interface CollisionGetter extends BlockGetter { - } - - default boolean noCollision(@Nullable Entity entity, AABB box) { -+ try { if (entity != null) entity.collisionLoadChunks = true; // Paper - for(VoxelShape voxelShape : this.getBlockCollisions(entity, box)) { - if (!voxelShape.isEmpty()) { - return false; - } - } -+ } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper - - if (!this.getEntityCollisions(entity, box).isEmpty()) { - return false; diff --git a/patches/server/1010-Write-SavedData-IO-async.patch b/patches/server/1004-Write-SavedData-IO-async.patch similarity index 100% rename from patches/server/1010-Write-SavedData-IO-async.patch rename to patches/server/1004-Write-SavedData-IO-async.patch diff --git a/patches/server/1005-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch b/patches/server/1005-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch new file mode 100644 index 000000000000..03bc5f00bbbc --- /dev/null +++ b/patches/server/1005-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch @@ -0,0 +1,169 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 13 May 2020 23:01:26 -0400 +Subject: [PATCH] Protect Bedrock and End Portal/Frames from being destroyed + +This fixes exploits that let players destroy bedrock by Pistons, explosions +and Mushrooom/Tree generation. + +These blocks are designed to not be broken except by creative players/commands. +So protect them from a multitude of methods of destroying them. + +A config is provided if you rather let players use these exploits, and let +them destroy the worlds End Portals and get on top of the nether easy. + +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 32775780df3e6f34961119f10c81462c0f729045..6894366ebedc461e1e6703317d83f91fb8d4f09b 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -191,6 +191,7 @@ public class Explosion { + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { + BlockPos blockposition = BlockPos.containing(d4, d5, d6); + BlockState iblockdata = this.level.getBlockState(blockposition); ++ if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed + FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions + + if (!this.level.isInWorldBounds(blockposition)) { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 9149ddd3d950d1616ec3c85d54c494bcc6501e83..cb31b2f88e701dc9bb14ea5c568e4666f6cdc0b9 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -540,6 +540,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ BlockState type = getBlockState(pos); ++ if (!type.isDestroyable()) return false; ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + CraftBlockState blockstate = this.capturedBlockStates.get(pos); + if (blockstate == null) { + blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags); +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 6896d46fce2e466ebee23ac2dc00312ec1beefdb..b60a52788e73de3dcb086c1a4628466b25c9d3ef 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -90,6 +90,19 @@ public class Block extends BlockBehaviour implements ItemLike { + protected final StateDefinition stateDefinition; + private BlockState defaultBlockState; + // Paper start ++ public final boolean isDestroyable() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || ++ this != Blocks.BEDROCK && ++ this != Blocks.END_PORTAL_FRAME && ++ this != Blocks.END_PORTAL && ++ this != Blocks.END_GATEWAY && ++ this != Blocks.COMMAND_BLOCK && ++ this != Blocks.REPEATING_COMMAND_BLOCK && ++ this != Blocks.CHAIN_COMMAND_BLOCK && ++ this != Blocks.BARRIER && ++ this != Blocks.STRUCTURE_BLOCK && ++ this != Blocks.JIGSAW; ++ } + public co.aikar.timings.Timing timing; + public co.aikar.timings.Timing getTiming() { + if (timing == null) { +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index 3dfe79684f662ac7cae4583bfe03a633438b4df7..be74adc86f0ca467f3b59e7b57fd47a8f381d86e 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -212,6 +212,12 @@ public class PistonBaseBlock extends DirectionalBlock { + @Override + public boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { + Direction enumdirection = (Direction) state.getValue(PistonBaseBlock.FACING); ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; prevent retracting when we're facing the wrong way (we were replaced before retraction could occur) ++ Direction directionQueuedAs = Direction.from3DDataValue(data & 7); // Paper - copied from below ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits && enumdirection != directionQueuedAs) { ++ return false; ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + BlockState iblockdata1 = (BlockState) state.setValue(PistonBaseBlock.EXTENDED, true); + + if (!world.isClientSide) { +@@ -252,7 +258,7 @@ public class PistonBaseBlock extends DirectionalBlock { + } + // Paper end - Fix sticky pistons and BlockPistonRetractEvent + world.setBlock(pos, iblockdata2, 20); +- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); ++ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed; diff on change + world.blockUpdated(pos, iblockdata2.getBlock()); + iblockdata2.updateNeighbourShapes(world, pos, 2); + if (this.isSticky) { +@@ -288,7 +294,14 @@ public class PistonBaseBlock extends DirectionalBlock { + } + } + } else { +- world.removeBlock(pos.relative(enumdirection), false); ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; fix headless pistons breaking blocks ++ BlockPos headPos = pos.relative(enumdirection); ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || world.getBlockState(headPos) == Blocks.PISTON_HEAD.defaultBlockState().setValue(FACING, enumdirection)) { // double check to make sure we're not a headless piston. ++ world.removeBlock(headPos, false); ++ } else { ++ ((ServerLevel) world).getChunkSource().blockChanged(headPos); // ... fix client desync ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + } + + world.playSound((Player) null, pos, SoundEvents.PISTON_CONTRACT, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.15F + 0.6F); +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index e28ac8f7960f648099e5f3607530a406c72e5056..e493b34aa8726ed48f8e5db2ae8ea561cc5b1f75 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -189,7 +189,7 @@ public abstract class BlockBehaviour implements FeatureElement { + /** @deprecated */ + @Deprecated + public void onExplosionHit(BlockState state, Level world, BlockPos pos, Explosion explosion, BiConsumer stackMerger) { +- if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK) { ++ if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK && state.isDestroyable()) { // Paper - Protect Bedrock and End Portal/Frames from being destroyed + Block block = state.getBlock(); + boolean flag = explosion.getIndirectSourceEntity() instanceof Player; + +@@ -285,7 +285,7 @@ public abstract class BlockBehaviour implements FeatureElement { + /** @deprecated */ + @Deprecated + public boolean canBeReplaced(BlockState state, BlockPlaceContext context) { +- return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())); ++ return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed + } + + /** @deprecated */ +@@ -965,6 +965,12 @@ public abstract class BlockBehaviour implements FeatureElement { + return this.legacySolid; + } + ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ public final boolean isDestroyable() { ++ return getBlock().isDestroyable(); ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed ++ + public boolean isValidSpawn(BlockGetter world, BlockPos pos, EntityType type) { + return this.getBlock().properties.isValidSpawn.test(this.asState(), world, pos, type); + } +@@ -1068,7 +1074,7 @@ public abstract class BlockBehaviour implements FeatureElement { + } + + public PushReaction getPistonPushReaction() { +- return this.pushReaction; ++ return !this.isDestroyable() ? PushReaction.BLOCK : this.pushReaction; // Paper - Protect Bedrock and End Portal/Frames from being destroyed + } + + public boolean isSolidRender(BlockGetter world, BlockPos pos) { +diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +index afdd4ecbff21e2172b390bcbdf74f3c1bbddafcc..bb739f8584dd3847152314aa995800f51907da2f 100644 +--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java ++++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +@@ -221,6 +221,13 @@ public class PortalForcer { + for (int j = -1; j < 3; ++j) { + for (int k = -1; k < 4; ++k) { + temp.setWithOffset(pos, portalDirection.getStepX() * j + enumdirection1.getStepX() * distanceOrthogonalToPortal, k, portalDirection.getStepZ() * j + enumdirection1.getStepZ() * distanceOrthogonalToPortal); ++ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits) { ++ if (!this.level.getBlockState(temp).isDestroyable()) { ++ return false; ++ } ++ } ++ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed + if (k < 0 && !this.level.getBlockState(temp).isSolid()) { + return false; + } diff --git a/patches/server/1006-Use-distance-map-to-optimise-entity-tracker.patch b/patches/server/1006-Use-distance-map-to-optimise-entity-tracker.patch new file mode 100644 index 000000000000..4ec227e73719 --- /dev/null +++ b/patches/server/1006-Use-distance-map-to-optimise-entity-tracker.patch @@ -0,0 +1,341 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 5 May 2020 20:18:05 -0700 +Subject: [PATCH] Use distance map to optimise entity tracker + +Use the distance map to find candidate players for tracking. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 9037ba5197eed9d8e616fb65369f6b1a5ea9562c..7e5a8789e06a5ea1d2657ea8ee5c0460da92aaeb 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -146,6 +146,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + // Paper start - distance maps + private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); ++ // Paper start - use distance map to optimise tracker ++ public static boolean isLegacyTrackingEntity(Entity entity) { ++ return entity.isLegacyTrackingEntity; ++ } ++ ++ // inlined EnumMap, TrackingRange.TrackingRangeType ++ static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values(); ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps; ++ final int[] entityTrackerTrackRanges; ++ public final int getEntityTrackerRange(final int ordinal) { ++ return this.entityTrackerTrackRanges[ordinal]; ++ } ++ ++ private int convertSpigotRangeToVanilla(final int vanilla) { ++ return net.minecraft.server.MinecraftServer.getServer().getScaledTrackingDistance(vanilla); ++ } ++ // Paper end - use distance map to optimise tracker + + void addPlayerToDistanceMaps(ServerPlayer player) { + int chunkX = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getX()); +@@ -153,6 +170,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Note: players need to be explicitly added to distance maps before they can be updated + this.nearbyPlayers.addPlayer(player); + this.level.playerChunkLoader.addPlayer(player); // Paper - replace chunk loader ++ // Paper start - use distance map to optimise entity tracker ++ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { ++ com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; ++ int trackRange = this.entityTrackerTrackRanges[i]; ++ ++ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player))); ++ } ++ // Paper end - use distance map to optimise entity tracker + } + + void removePlayerFromDistanceMaps(ServerPlayer player) { +@@ -161,6 +186,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Note: players need to be explicitly added to distance maps before they can be updated + this.nearbyPlayers.removePlayer(player); + this.level.playerChunkLoader.removePlayer(player); // Paper - replace chunk loader ++ // Paper start - use distance map to optimise tracker ++ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { ++ this.playerEntityTrackerTrackMaps[i].remove(player); ++ } ++ // Paper end - use distance map to optimise tracker + } + + void updateMaps(ServerPlayer player) { +@@ -169,6 +199,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Note: players need to be explicitly added to distance maps before they can be updated + this.nearbyPlayers.tickPlayer(player); + this.level.playerChunkLoader.updatePlayer(player); // Paper - replace chunk loader ++ // Paper start - use distance map to optimise entity tracker ++ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { ++ com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; ++ int trackRange = this.entityTrackerTrackRanges[i]; ++ ++ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player))); ++ } ++ // Paper end - use distance map to optimise entity tracker + } + // Paper end + // Paper start +@@ -256,6 +294,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.regionManagers.add(this.dataRegionManager); + this.nearbyPlayers = new io.papermc.paper.util.player.NearbyPlayers(this.level); + // Paper end ++ // Paper start - use distance map to optimise entity tracker ++ this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length]; ++ this.entityTrackerTrackRanges = new int[TRACKING_RANGE_TYPES.length]; ++ ++ org.spigotmc.SpigotWorldConfig spigotWorldConfig = this.level.spigotConfig; ++ ++ for (int ordinal = 0, len = TRACKING_RANGE_TYPES.length; ordinal < len; ++ordinal) { ++ org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = TRACKING_RANGE_TYPES[ordinal]; ++ int configuredSpigotValue; ++ switch (trackingRangeType) { ++ case PLAYER: ++ configuredSpigotValue = spigotWorldConfig.playerTrackingRange; ++ break; ++ case ANIMAL: ++ configuredSpigotValue = spigotWorldConfig.animalTrackingRange; ++ break; ++ case MONSTER: ++ configuredSpigotValue = spigotWorldConfig.monsterTrackingRange; ++ break; ++ case MISC: ++ configuredSpigotValue = spigotWorldConfig.miscTrackingRange; ++ break; ++ case OTHER: ++ configuredSpigotValue = spigotWorldConfig.otherTrackingRange; ++ break; ++ case ENDERDRAGON: ++ configuredSpigotValue = EntityType.ENDER_DRAGON.clientTrackingRange() * 16; ++ break; ++ case DISPLAY: ++ configuredSpigotValue = spigotWorldConfig.displayTrackingRange; ++ break; ++ default: ++ throw new IllegalStateException("Missing case for enum " + trackingRangeType); ++ } ++ configuredSpigotValue = convertSpigotRangeToVanilla(configuredSpigotValue); ++ ++ int trackRange = (configuredSpigotValue >>> 4) + ((configuredSpigotValue & 15) != 0 ? 1 : 0); ++ this.entityTrackerTrackRanges[ordinal] = trackRange; ++ ++ this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); ++ } ++ // Paper end - use distance map to optimise entity tracker + } + + // Paper start +@@ -933,17 +1013,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public void move(ServerPlayer player) { +- ObjectIterator objectiterator = this.entityMap.values().iterator(); +- +- while (objectiterator.hasNext()) { +- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); +- +- if (playerchunkmap_entitytracker.entity == player) { +- playerchunkmap_entitytracker.updatePlayers(this.level.players()); +- } else { +- playerchunkmap_entitytracker.updatePlayer(player); +- } +- } ++ // Paper - delay this logic for the entity tracker tick, no need to duplicate it + + SectionPos sectionposition = player.getLastSectionPos(); + SectionPos sectionposition1 = SectionPos.of((EntityAccess) player); +@@ -1020,7 +1090,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker + this.entityMap.put(entity.getId(), playerchunkmap_entitytracker); +- playerchunkmap_entitytracker.updatePlayers(this.level.players()); ++ playerchunkmap_entitytracker.updatePlayers(entity.getPlayersInTrackRange()); // Paper - don't search all players + if (entity instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entity; + +@@ -1064,9 +1134,37 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + entity.tracker = null; // Paper - We're no longer tracked + } + +- protected void tick() { +- // Paper - replaced by PlayerChunkLoader ++ // Paper start - optimised tracker ++ private final void processTrackQueue() { ++ this.level.timings.tracker1.startTiming(); ++ try { ++ for (TrackedEntity tracker : this.entityMap.values()) { ++ // update tracker entry ++ tracker.updatePlayers(tracker.entity.getPlayersInTrackRange()); ++ } ++ } finally { ++ this.level.timings.tracker1.stopTiming(); ++ } ++ ++ ++ this.level.timings.tracker2.startTiming(); ++ try { ++ for (TrackedEntity tracker : this.entityMap.values()) { ++ tracker.serverEntity.sendChanges(); ++ } ++ } finally { ++ this.level.timings.tracker2.stopTiming(); ++ } ++ } ++ // Paper end - optimised tracker + ++ protected void tick() { ++ // Paper start - optimized tracker ++ if (true) { ++ this.processTrackQueue(); ++ return; ++ } ++ // Paper end - optimized tracker + List list = Lists.newArrayList(); + List list1 = this.level.players(); + ObjectIterator objectiterator = this.entityMap.values().iterator(); +@@ -1216,6 +1314,42 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.lastSectionPos = SectionPos.of((EntityAccess) entity); + } + ++ // Paper start - use distance map to optimise tracker ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet lastTrackerCandidates; ++ ++ final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newTrackerCandidates) { ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet oldTrackerCandidates = this.lastTrackerCandidates; ++ this.lastTrackerCandidates = newTrackerCandidates; ++ ++ if (newTrackerCandidates != null) { ++ Object[] rawData = newTrackerCandidates.getBackingSet(); ++ for (int i = 0, len = rawData.length; i < len; ++i) { ++ Object raw = rawData[i]; ++ if (!(raw instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer)raw; ++ this.updatePlayer(player); ++ } ++ } ++ ++ if (oldTrackerCandidates == newTrackerCandidates) { ++ // this is likely the case. ++ // means there has been no range changes, so we can just use the above for tracking. ++ return; ++ } ++ ++ // stuff could have been removed, so we need to check the trackedPlayers set ++ // for players that were removed ++ ++ for (ServerPlayerConnection conn : this.seenBy.toArray(new ServerPlayerConnection[0])) { // avoid CME ++ if (newTrackerCandidates == null || !newTrackerCandidates.contains(conn.getPlayer())) { ++ this.updatePlayer(conn.getPlayer()); ++ } ++ } ++ } ++ // Paper end - use distance map to optimise tracker ++ + public boolean equals(Object object) { + return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false; + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 79f6cb643ca97617e25b96c73695393aed8daa6e..c259b21f0f84974bb9ef13fa459890717c950d0b 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -56,6 +56,7 @@ import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; ++import io.papermc.paper.util.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; +@@ -468,6 +469,38 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + this.teleportTo(worldserver, null); + } + // Paper end - make end portalling safe ++ // Paper start - optimise entity tracking ++ final org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = org.spigotmc.TrackingRange.getTrackingRangeType(this); ++ ++ public boolean isLegacyTrackingEntity = false; ++ ++ public final void setLegacyTrackingEntity(final boolean isLegacyTrackingEntity) { ++ this.isLegacyTrackingEntity = isLegacyTrackingEntity; ++ } ++ ++ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getPlayersInTrackRange() { ++ // determine highest range of passengers ++ if (this.passengers.isEmpty()) { ++ return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()] ++ .getObjectsInRange(MCUtil.getCoordinateKey(this)); ++ } ++ Iterable passengers = this.getIndirectPassengers(); ++ net.minecraft.server.level.ChunkMap chunkMap = ((ServerLevel)this.level).getChunkSource().chunkMap; ++ org.spigotmc.TrackingRange.TrackingRangeType type = this.trackingRangeType; ++ int range = chunkMap.getEntityTrackerRange(type.ordinal()); ++ ++ for (Entity passenger : passengers) { ++ org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType; ++ int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal()); ++ if (passengerRange > range) { ++ type = passengerType; ++ range = passengerRange; ++ } ++ } ++ ++ return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this)); ++ } ++ // Paper end - optimise entity tracking + public float getBukkitYaw() { + return this.yRot; + } +diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java +index bb06f89a29f30144e7e2113e088a503db006a83c..e4425b242fe73d1fd2bd10c313aa16925432329f 100644 +--- a/src/main/java/org/spigotmc/TrackingRange.java ++++ b/src/main/java/org/spigotmc/TrackingRange.java +@@ -55,4 +55,48 @@ public class TrackingRange + return config.otherTrackingRange; + } + } ++ ++ // Paper start - optimise entity tracking ++ // copied from above, TODO check on update ++ public static TrackingRangeType getTrackingRangeType(Entity entity) ++ { ++ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return TrackingRangeType.ENDERDRAGON; // Paper - enderdragon is exempt ++ if ( entity instanceof ServerPlayer ) ++ { ++ return TrackingRangeType.PLAYER; ++ // Paper start - Simplify and set water mobs to animal tracking range ++ } ++ switch (entity.activationType) { ++ case RAIDER: ++ case MONSTER: ++ case FLYING_MONSTER: ++ return TrackingRangeType.MONSTER; ++ case WATER: ++ case VILLAGER: ++ case ANIMAL: ++ return TrackingRangeType.ANIMAL; ++ case MISC: ++ } ++ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) ++ // Paper end ++ { ++ return TrackingRangeType.MISC; ++ } else if (entity instanceof Display) { ++ return TrackingRangeType.DISPLAY; ++ } else ++ { ++ return TrackingRangeType.OTHER; ++ } ++ } ++ ++ public static enum TrackingRangeType { ++ PLAYER, ++ ANIMAL, ++ MONSTER, ++ MISC, ++ OTHER, ++ ENDERDRAGON, ++ DISPLAY; ++ } ++ // Paper end - optimise entity tracking + } diff --git a/patches/server/1007-Fix-and-optimise-world-force-upgrading.patch b/patches/server/1007-Fix-and-optimise-world-force-upgrading.patch deleted file mode 100644 index b9881782d6ad..000000000000 --- a/patches/server/1007-Fix-and-optimise-world-force-upgrading.patch +++ /dev/null @@ -1,382 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 20 May 2021 07:02:22 -0700 -Subject: [PATCH] Fix and optimise world force upgrading - -The WorldUpgrader class was incorrectly modified by -CB. It will store an IChunkLoader instance for all -dimension types in the world, but obviously with how -CB shifts around worlds only one dimension type exists -per world. But this would be OK if CB did this -change correctly. All IChunkLoader instances -will point to the same regionfiles. And all -IChunkLoader instances are going to be read from. - -This problem hasn't really been reported because -it relies on the persistent legacy data to be converted -as well to cause corruption. Why? Because the legacy -data is also shared, it will result in different -outputs from conversion (as once conversion for legacy -persistent data takes place, it is REMOVED - so the next -convert will _not_ have the data). Which means different -sizes on disk. Which means different regionfile sector -allocations. Which means there are 3 different possible -regionfile sector allocations in memory, and none of them -are going to be correct. - -I've fixed this by writing a world upgrader suited to -CB's changes to world folder format. It was brain dead -easy to add threading, so I did. - -== AT == -public net.minecraft.util.worldupdate.WorldUpgrader REGEX - -diff --git a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java -new file mode 100644 -index 0000000000000000000000000000000000000000..513833c2ea23df5b079d157bc5cb89d5c9754c0b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java -@@ -0,0 +1,209 @@ -+package io.papermc.paper.world; -+ -+import com.mojang.datafixers.DataFixer; -+import com.mojang.serialization.Codec; -+import net.minecraft.SharedConstants; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.resources.ResourceKey; -+import net.minecraft.util.worldupdate.WorldUpgrader; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.chunk.ChunkGenerator; -+import net.minecraft.world.level.chunk.storage.ChunkStorage; -+import net.minecraft.world.level.chunk.storage.RegionFileStorage; -+import net.minecraft.world.level.dimension.DimensionType; -+import net.minecraft.world.level.dimension.LevelStem; -+import net.minecraft.world.level.levelgen.WorldGenSettings; -+import net.minecraft.world.level.storage.DimensionDataStorage; -+import net.minecraft.world.level.storage.LevelStorageSource; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+import java.io.File; -+import java.io.IOException; -+import java.text.DecimalFormat; -+import java.util.Optional; -+import java.util.concurrent.ExecutorService; -+import java.util.concurrent.Executors; -+import java.util.concurrent.ThreadFactory; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.function.Supplier; -+ -+public class ThreadedWorldUpgrader { -+ -+ private static final Logger LOGGER = LogManager.getLogger(); -+ -+ private final ResourceKey dimensionType; -+ private final String worldName; -+ private final File worldDir; -+ private final ExecutorService threadPool; -+ private final DataFixer dataFixer; -+ private final Optional>> generatorKey; -+ private final boolean removeCaches; -+ -+ public ThreadedWorldUpgrader(final ResourceKey dimensionType, final String worldName, final File worldDir, final int threads, -+ final DataFixer dataFixer, final Optional>> generatorKey, final boolean removeCaches) { -+ this.dimensionType = dimensionType; -+ this.worldName = worldName; -+ this.worldDir = worldDir; -+ this.threadPool = Executors.newFixedThreadPool(Math.max(1, threads), new ThreadFactory() { -+ private final AtomicInteger threadCounter = new AtomicInteger(); -+ -+ @Override -+ public Thread newThread(final Runnable run) { -+ final Thread ret = new Thread(run); -+ -+ ret.setName("World upgrader thread for world " + ThreadedWorldUpgrader.this.worldName + " #" + this.threadCounter.getAndIncrement()); -+ ret.setUncaughtExceptionHandler((thread, throwable) -> { -+ LOGGER.fatal("Error upgrading world", throwable); -+ }); -+ -+ return ret; -+ } -+ }); -+ this.dataFixer = dataFixer; -+ this.generatorKey = generatorKey; -+ this.removeCaches = removeCaches; -+ } -+ -+ public void convert() { -+ final File worldFolder = LevelStorageSource.getStorageFolder(this.worldDir.toPath(), this.dimensionType).toFile(); -+ final DimensionDataStorage worldPersistentData = new DimensionDataStorage(new File(worldFolder, "data"), this.dataFixer); -+ -+ final File regionFolder = new File(worldFolder, "region"); -+ -+ LOGGER.info("Force upgrading " + this.worldName); -+ LOGGER.info("Counting regionfiles for " + this.worldName); -+ final File[] regionFiles = regionFolder.listFiles((final File dir, final String name) -> { -+ return WorldUpgrader.REGEX.matcher(name).matches(); -+ }); -+ if (regionFiles == null) { -+ LOGGER.info("Found no regionfiles to convert for world " + this.worldName); -+ return; -+ } -+ LOGGER.info("Found " + regionFiles.length + " regionfiles to convert"); -+ LOGGER.info("Starting conversion now for world " + this.worldName); -+ -+ final WorldInfo info = new WorldInfo(() -> worldPersistentData, -+ new ChunkStorage(regionFolder.toPath(), this.dataFixer, false), this.removeCaches, this.dimensionType, this.generatorKey); -+ -+ long expectedChunks = (long)regionFiles.length * (32L * 32L); -+ -+ for (final File regionFile : regionFiles) { -+ final ChunkPos regionPos = RegionFileStorage.getRegionFileCoordinates(regionFile.toPath()); -+ if (regionPos == null) { -+ expectedChunks -= (32L * 32L); -+ continue; -+ } -+ -+ this.threadPool.execute(new ConvertTask(info, regionPos.x >> 5, regionPos.z >> 5)); -+ } -+ this.threadPool.shutdown(); -+ -+ final DecimalFormat format = new DecimalFormat("#0.00"); -+ -+ final long start = System.nanoTime(); -+ -+ while (!this.threadPool.isTerminated()) { -+ final long current = info.convertedChunks.get(); -+ -+ LOGGER.info("{}% completed ({} / {} chunks)...", format.format((double)current / (double)expectedChunks * 100.0), current, expectedChunks); -+ -+ try { -+ Thread.sleep(1000L); -+ } catch (final InterruptedException ignore) {} -+ } -+ -+ final long end = System.nanoTime(); -+ -+ try { -+ info.loader.close(); -+ } catch (final IOException ex) { -+ LOGGER.fatal("Failed to close chunk loader", ex); -+ } -+ LOGGER.info("Completed conversion. Took {}s, {} out of {} chunks needed to be converted/modified ({}%)", -+ (int)Math.ceil((end - start) * 1.0e-9), info.modifiedChunks.get(), expectedChunks, format.format((double)info.modifiedChunks.get() / (double)expectedChunks * 100.0)); -+ } -+ -+ private static final class WorldInfo { -+ -+ public final Supplier persistentDataSupplier; -+ public final ChunkStorage loader; -+ public final boolean removeCaches; -+ public final ResourceKey worldKey; -+ public final Optional>> generatorKey; -+ public final AtomicLong convertedChunks = new AtomicLong(); -+ public final AtomicLong modifiedChunks = new AtomicLong(); -+ -+ private WorldInfo(final Supplier persistentDataSupplier, final ChunkStorage loader, final boolean removeCaches, -+ final ResourceKey worldKey, Optional>> generatorKey) { -+ this.persistentDataSupplier = persistentDataSupplier; -+ this.loader = loader; -+ this.removeCaches = removeCaches; -+ this.worldKey = worldKey; -+ this.generatorKey = generatorKey; -+ } -+ } -+ -+ private static final class ConvertTask implements Runnable { -+ -+ private final WorldInfo worldInfo; -+ private final int regionX; -+ private final int regionZ; -+ -+ public ConvertTask(final WorldInfo worldInfo, final int regionX, final int regionZ) { -+ this.worldInfo = worldInfo; -+ this.regionX = regionX; -+ this.regionZ = regionZ; -+ } -+ -+ @Override -+ public void run() { -+ final int regionCX = this.regionX << 5; -+ final int regionCZ = this.regionZ << 5; -+ -+ final Supplier persistentDataSupplier = this.worldInfo.persistentDataSupplier; -+ final ChunkStorage loader = this.worldInfo.loader; -+ final boolean removeCaches = this.worldInfo.removeCaches; -+ final ResourceKey worldKey = this.worldInfo.worldKey; -+ -+ for (int cz = regionCZ; cz < (regionCZ + 32); ++cz) { -+ for (int cx = regionCX; cx < (regionCX + 32); ++cx) { -+ final ChunkPos chunkPos = new ChunkPos(cx, cz); -+ try { -+ // no need to check the coordinate of the chunk, the regionfilecache does that for us -+ -+ CompoundTag chunkNBT = (loader.read(chunkPos).join()).orElse(null); -+ -+ if (chunkNBT == null) { -+ continue; -+ } -+ -+ final int versionBefore = ChunkStorage.getVersion(chunkNBT); -+ -+ chunkNBT = loader.upgradeChunkTag(worldKey, persistentDataSupplier, chunkNBT, this.worldInfo.generatorKey, chunkPos, null); -+ -+ boolean modified = versionBefore < SharedConstants.getCurrentVersion().getDataVersion().getVersion(); -+ -+ if (removeCaches) { -+ final CompoundTag level = chunkNBT.getCompound("Level"); -+ modified |= level.contains("Heightmaps"); -+ level.remove("Heightmaps"); -+ modified |= level.contains("isLightOn"); -+ level.remove("isLightOn"); -+ } -+ -+ if (modified) { -+ this.worldInfo.modifiedChunks.getAndIncrement(); -+ loader.write(chunkPos, chunkNBT); -+ } -+ } catch (final Exception ex) { -+ LOGGER.error("Error upgrading chunk {}", chunkPos, ex); -+ } finally { -+ this.worldInfo.convertedChunks.getAndIncrement(); -+ } -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index 72d013d06705b08ed696e3d3b6d631d65800c2c9..61840cfd64caba6595dfc99c91c76a195638d4ee 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -399,6 +399,15 @@ public class Main { - return new WorldLoader.InitConfig(worldloader_d, Commands.CommandSelection.DEDICATED, serverPropertiesHandler.functionPermissionLevel); - } - -+ // Paper start - fix and optimise world upgrading -+ public static void convertWorldButItWorks(net.minecraft.resources.ResourceKey dimensionType, net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess worldSession, -+ DataFixer dataFixer, Optional>> generatorKey, boolean removeCaches) { -+ int threads = Runtime.getRuntime().availableProcessors() * 3 / 8; -+ final io.papermc.paper.world.ThreadedWorldUpgrader worldUpgrader = new io.papermc.paper.world.ThreadedWorldUpgrader(dimensionType, worldSession.getLevelId(), worldSession.levelDirectory.path().toFile(), threads, dataFixer, generatorKey, removeCaches); -+ worldUpgrader.convert(); -+ } -+ // Paper end - fix and optimise world upgrading -+ - public static void forceUpgrade(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, boolean eraseCache, BooleanSupplier continueCheck, Registry dimensionOptionsRegistry) { - Main.LOGGER.info("Forcing world upgrade! {}", session.getLevelId()); // CraftBukkit - WorldUpgrader worldupgrader = new WorldUpgrader(session, dataFixer, dimensionOptionsRegistry, eraseCache); -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 79de3c639795cfc0bd86f842446e2bb3ab71d23a..1dfafbe508b4e4598339f412e5fb9d92717b5d26 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -584,11 +584,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -- return true; -- }, dimensions); -- } -+ // Paper - fix and optimise world upgrading; move down - - PrimaryLevelData iworlddataserver = worlddata; - boolean flag = worlddata.isDebugWorld(); -@@ -603,6 +599,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop worldKey = ResourceKey.create(Registries.DIMENSION, dimensionKey.location()); - - if (dimensionKey == LevelStem.OVERWORLD) { -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 927c7948e567764e8cf75c7ce486e1ea6c9a8d87..7694b7f299495a084ce71c5d04e5e690a75fe55b 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -181,6 +181,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions - public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here - -+ // Paper start - fix and optimise world upgrading -+ // copied from below -+ public static ResourceKey getDimensionKey(DimensionType manager) { -+ return ((org.bukkit.craftbukkit.CraftServer)org.bukkit.Bukkit.getServer()).getHandle().getServer().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.DIMENSION_TYPE).getResourceKey(manager).orElseThrow(() -> { -+ return new IllegalStateException("Unregistered dimension type: " + manager); -+ }); -+ } -+ // Paper end - fix and optimise world upgrading -+ - public CraftWorld getWorld() { - return this.world; - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index 0db8ee3b640e6d1268e9c1cccda85459bd447105..42d37bee3a459adcd46408596ccf93abbcff51fe 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -60,6 +60,29 @@ public class RegionFileStorage implements AutoCloseable { - } - - // Paper start -+ @Nullable -+ public static ChunkPos getRegionFileCoordinates(Path file) { -+ String fileName = file.getFileName().toString(); -+ if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) { -+ return null; -+ } -+ -+ String[] split = fileName.split("\\."); -+ -+ if (split.length != 4) { -+ return null; -+ } -+ -+ try { -+ int x = Integer.parseInt(split[1]); -+ int z = Integer.parseInt(split[2]); -+ -+ return new ChunkPos(x << 5, z << 5); -+ } catch (NumberFormatException ex) { -+ return null; -+ } -+ } -+ - public synchronized RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) { - return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ())); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index b8f05300a17d415505deb8da4a923357220ae1bd..4df3b94c8126f00188f5e125757411a0359728fa 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1345,9 +1345,7 @@ public final class CraftServer implements Server { - worlddata.checkName(name); - worlddata.setModdedInfo(this.console.getServerModName(), this.console.getModdedStatus().shouldReportAsModified()); - -- if (this.console.options.has("forceUpgrade")) { -- net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), this.console.options.has("eraseCache"), () -> true, iregistry); -- } -+ // Paper - fix and optimise world upgrading; move down - - long j = BiomeManager.obfuscateSeed(worlddata.worldGenOptions().seed()); // Paper - use world seed - List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); -@@ -1358,6 +1356,13 @@ public final class CraftServer implements Server { - biomeProvider = generator.getDefaultBiomeProvider(worldInfo); - } - -+ // Paper start - fix and optimise world upgrading -+ if (this.console.options.has("forceUpgrade")) { -+ net.minecraft.server.Main.convertWorldButItWorks( -+ actualDimension, worldSession, DataFixers.getDataFixer(), worlddimension.generator().getTypeNameForDataFixer(), this.console.options.has("eraseCache") -+ ); -+ } -+ // Paper end - fix and optimise world upgrading - ResourceKey worldKey; - String levelName = this.getServer().getProperties().levelName; - if (name.equals(levelName + "_nether")) { diff --git a/patches/server/1013-Optimize-Bit-Operations-by-inlining.patch b/patches/server/1007-Optimize-Bit-Operations-by-inlining.patch similarity index 100% rename from patches/server/1013-Optimize-Bit-Operations-by-inlining.patch rename to patches/server/1007-Optimize-Bit-Operations-by-inlining.patch diff --git a/patches/server/1008-Improved-Watchdog-Support.patch b/patches/server/1008-Improved-Watchdog-Support.patch deleted file mode 100644 index d1aa051eba4a..000000000000 --- a/patches/server/1008-Improved-Watchdog-Support.patch +++ /dev/null @@ -1,544 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 12 Apr 2020 15:50:48 -0400 -Subject: [PATCH] Improved Watchdog Support - -Forced Watchdog Crash support and Improve Async Shutdown - -If the request to shut down the server is received while we are in -a watchdog hang, immediately treat it as a crash and begin the shutdown -process. Shutdown process is now improved to also shutdown cleanly when -not using restart scripts either. - -If a server is deadlocked, a server owner can send SIGUP (or any other signal -the JVM understands to shut down as it currently does) and the watchdog -will no longer need to wait until the full timeout, allowing you to trigger -a close process and try to shut the server down gracefully, saving player and -world data. - -Previously there was no way to trigger this outside of waiting for a full watchdog -timeout, which may be set to a really long time... - -Additionally, fix everything to do with shutting the server down asynchronously. - -Previously, nearly everything about the process was fragile and unsafe. Main might -not have actually been frozen, and might still be manipulating state. - -Or, some reuest might ask main to do something in the shutdown but main is dead. - -Or worse, other things might start closing down items such as the Console or Thread Pool -before we are fully shutdown. - -This change tries to resolve all of these issues by moving everything into the stop -method and guaranteeing only one thread is stopping the server. - -We then issue Thread Death to the main thread of another thread initiates the stop process. -We have to ensure Thread Death propagates correctly though to stop main completely. - -This is to ensure that if main isn't truely stuck, it's not manipulating state we are trying to save. - -This also moves all plugins who register "delayed init" tasks to occur just before "Done" so they -are properly accounted for and wont trip watchdog on init. - -diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java -index 6aaed8e8bf8c721fc834da5c76ac72a4c3e92458..4b002e8b75d117b726b0de274a76d3596fce015b 100644 ---- a/src/main/java/com/destroystokyo/paper/Metrics.java -+++ b/src/main/java/com/destroystokyo/paper/Metrics.java -@@ -92,7 +92,12 @@ public class Metrics { - * Starts the Scheduler which submits our data every 30 minutes. - */ - private void startSubmitting() { -- final Runnable submitTask = this::submitData; -+ final Runnable submitTask = () -> { -+ if (MinecraftServer.getServer().hasStopped()) { -+ return; -+ } -+ submitData(); -+ }; - - // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution of requests on the - // bStats backend. To circumvent this problem, we introduce some randomness into the initial and second delay. -diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java -index 6f2452de76e8f5fcc1367066e0e753740764eb98..e047dee632022abfe05865d1e71838be8d5d053a 100644 ---- a/src/main/java/net/minecraft/CrashReport.java -+++ b/src/main/java/net/minecraft/CrashReport.java -@@ -234,6 +234,7 @@ public class CrashReport { - } - - public static CrashReport forThrowable(Throwable cause, String title) { -+ if (cause instanceof ThreadDeath) com.destroystokyo.paper.util.SneakyThrow.sneaky(cause); // Paper - while (cause instanceof CompletionException && cause.getCause() != null) { - cause = cause.getCause(); - } -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 1dfafbe508b4e4598339f412e5fb9d92717b5d26..97826afb097851f5736a64ae154d42147de55648 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -296,7 +296,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); - public int autosavePeriod; - public Commands vanillaCommandDispatcher; -- private boolean forceTicks; -+ public boolean forceTicks; // Paper - // CraftBukkit end - // Spigot start - public static final int TPS = 20; -@@ -309,6 +309,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { - AtomicReference atomicreference = new AtomicReference(); - Thread thread = new io.papermc.paper.util.TickThread(() -> { // Paper - rewrite chunk system -@@ -928,6 +931,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop {}; -+ } -+ // Paper end - return new TickTask(this.tickCount, runnable); - } - -@@ -2111,7 +2167,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements Profiler - try { - task.run(); - } catch (Exception var3) { -+ if (var3.getCause() instanceof ThreadDeath) throw var3; // Paper - LOGGER.error(LogUtils.FATAL_MARKER, "Error executing task on {}", this.name(), var3); - } - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 7694b7f299495a084ce71c5d04e5e690a75fe55b..9149ddd3d950d1616ec3c85d54c494bcc6501e83 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -935,6 +935,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - try { - tickConsumer.accept(entity); - } catch (Throwable throwable) { -+ if (throwable instanceof ThreadDeath) throw throwable; // Paper - // Paper start - Prevent block entity and entity crashes - final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); - MinecraftServer.LOGGER.error(msg, throwable); -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 2eeb0c78f2b717b59542b6b668371558ae2fcc25..6ec3fc801453fd54c25b642e6fa71c19b463311d 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -1176,6 +1176,7 @@ public class LevelChunk extends ChunkAccess { - - gameprofilerfiller.pop(); - } catch (Throwable throwable) { -+ if (throwable instanceof ThreadDeath) throw throwable; // Paper - // Paper start - Prevent block entity and entity crashes - final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ()); - net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index d931a4c6e633bf9c1b3e5e18e880e2ddbfe4aa2a..ce341f42b3a5e17fb6d1f7de8057e73137ae2a6e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -186,6 +186,36 @@ public class Main { - - OptionSet options = null; - -+ // Paper start - preload logger classes to avoid plugins mixing versions -+ tryPreloadClass("org.apache.logging.log4j.core.Core"); -+ tryPreloadClass("org.apache.logging.log4j.core.appender.AsyncAppender"); -+ tryPreloadClass("org.apache.logging.log4j.core.Appender"); -+ tryPreloadClass("org.apache.logging.log4j.core.ContextDataInjector"); -+ tryPreloadClass("org.apache.logging.log4j.core.Filter"); -+ tryPreloadClass("org.apache.logging.log4j.core.ErrorHandler"); -+ tryPreloadClass("org.apache.logging.log4j.core.LogEvent"); -+ tryPreloadClass("org.apache.logging.log4j.core.Logger"); -+ tryPreloadClass("org.apache.logging.log4j.core.LoggerContext"); -+ tryPreloadClass("org.apache.logging.log4j.core.LogEventListener"); -+ tryPreloadClass("org.apache.logging.log4j.core.AbstractLogEvent"); -+ tryPreloadClass("org.apache.logging.log4j.message.AsynchronouslyFormattable"); -+ tryPreloadClass("org.apache.logging.log4j.message.FormattedMessage"); -+ tryPreloadClass("org.apache.logging.log4j.message.ParameterizedMessage"); -+ tryPreloadClass("org.apache.logging.log4j.message.Message"); -+ tryPreloadClass("org.apache.logging.log4j.message.MessageFactory"); -+ tryPreloadClass("org.apache.logging.log4j.message.TimestampMessage"); -+ tryPreloadClass("org.apache.logging.log4j.message.SimpleMessage"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLogger"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerContext"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncQueueFullPolicy"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerDisruptor"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEvent"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.DisruptorUtil"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEventHandler"); -+ tryPreloadClass("org.apache.logging.log4j.core.impl.ThrowableProxy"); -+ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedClassInfo"); -+ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedStackTraceElement"); -+ // Paper end - try { - options = parser.parse(args); - } catch (joptsimple.OptionException ex) { -@@ -297,8 +327,65 @@ public class Main { - } catch (Throwable t) { - t.printStackTrace(); - } -+ // Paper start -+ // load some required classes to avoid errors during shutdown if jar is replaced -+ // also to guarantee our version loads over plugins -+ tryPreloadClass("com.destroystokyo.paper.util.SneakyThrow"); -+ tryPreloadClass("com.google.common.collect.Iterators$PeekingImpl"); -+ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$Values"); -+ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$ValueIterator"); -+ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$WriteThroughEntry"); -+ tryPreloadClass("com.google.common.collect.Iterables"); -+ for (int i = 1; i <= 15; i++) { -+ tryPreloadClass("com.google.common.collect.Iterables$" + i, false); -+ } -+ tryPreloadClass("org.apache.commons.lang3.mutable.MutableBoolean"); -+ tryPreloadClass("org.apache.commons.lang3.mutable.MutableInt"); -+ tryPreloadClass("org.jline.terminal.impl.MouseSupport"); -+ tryPreloadClass("org.jline.terminal.impl.MouseSupport$1"); -+ tryPreloadClass("org.jline.terminal.Terminal$MouseTracking"); -+ tryPreloadClass("co.aikar.timings.TimingHistory"); -+ tryPreloadClass("co.aikar.timings.TimingHistory$MinuteReport"); -+ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext"); -+ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$11"); -+ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$12"); -+ tryPreloadClass("io.netty.channel.AbstractChannel$AbstractUnsafe$8"); -+ tryPreloadClass("io.netty.util.concurrent.DefaultPromise"); -+ tryPreloadClass("io.netty.util.concurrent.DefaultPromise$1"); -+ tryPreloadClass("io.netty.util.internal.PromiseNotificationUtil"); -+ tryPreloadClass("io.netty.util.internal.SystemPropertyUtil"); -+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler"); -+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$1"); -+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$2"); -+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$3"); -+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$4"); -+ tryPreloadClass("org.slf4j.helpers.MessageFormatter"); -+ tryPreloadClass("org.slf4j.helpers.FormattingTuple"); -+ tryPreloadClass("org.slf4j.helpers.BasicMarker"); -+ tryPreloadClass("org.slf4j.helpers.Util"); -+ tryPreloadClass("com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent"); -+ tryPreloadClass("com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent"); -+ // Minecraft, seen during saving -+ tryPreloadClass(net.minecraft.world.level.lighting.LayerLightEventListener.DummyLightLayerEventListener.class.getName()); -+ tryPreloadClass(net.minecraft.world.level.lighting.LayerLightEventListener.class.getName()); -+ tryPreloadClass(net.minecraft.util.ExceptionCollector.class.getName()); -+ tryPreloadClass(io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.PlayerChunkLoaderData.class.getName()); -+ // Paper end -+ } -+ } -+ -+ // Paper start -+ private static void tryPreloadClass(String className) { -+ tryPreloadClass(className, true); -+ } -+ private static void tryPreloadClass(String className, boolean printError) { -+ try { -+ Class.forName(className); -+ } catch (ClassNotFoundException e) { -+ if (printError) System.err.println("An expected class " + className + " was not found for preloading: " + e.getMessage()); - } - } -+ // Paper end - - private static List asList(String... params) { - return Arrays.asList(params); -diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java -index c6e8441e299f477ddb22c1ce2618710763978f1a..e8e93538dfd71de86515d9405f728db1631e949a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java -@@ -12,11 +12,27 @@ public class ServerShutdownThread extends Thread { - @Override - public void run() { - try { -+ // Paper start - try to shutdown on main -+ server.safeShutdown(false, false); -+ for (int i = 1000; i > 0 && !server.hasStopped(); i -= 100) { -+ Thread.sleep(100); -+ } -+ if (server.hasStopped()) { -+ while (!server.hasFullyShutdown) Thread.sleep(1000); -+ return; -+ } -+ // Looks stalled, close async - org.spigotmc.AsyncCatcher.enabled = false; // Spigot -+ server.forceTicks = true; - this.server.close(); -+ while (!server.hasFullyShutdown) Thread.sleep(1000); -+ } catch (InterruptedException e) { -+ e.printStackTrace(); -+ // Paper end - } finally { -+ org.apache.logging.log4j.LogManager.shutdown(); // Paper - try { -- net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender -+ //net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Move into stop - } catch (Exception e) { - } - } -diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java -index e3b262add194a126e731c68e68f3139a00cacacb..da7d5efd76c9ef92e9ce22860fec791890a687be 100644 ---- a/src/main/java/org/spigotmc/RestartCommand.java -+++ b/src/main/java/org/spigotmc/RestartCommand.java -@@ -138,7 +138,7 @@ public class RestartCommand extends Command - // Paper end - - // Paper start - copied from above and modified to return if the hook registered -- private static boolean addShutdownHook(String restartScript) -+ public static boolean addShutdownHook(String restartScript) - { - String[] split = restartScript.split( " " ); - if ( split.length > 0 && new File( split[0] ).isFile() ) -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index a284d3b8526a743ba4389ec5b44d80af6d0e5a5f..0234555978d1b13051f876a257e47bafad37b0f8 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -11,6 +11,7 @@ import org.bukkit.Bukkit; - public final class WatchdogThread extends io.papermc.paper.util.TickThread // Paper - rewrite chunk system - { - -+ public static final boolean DISABLE_WATCHDOG = Boolean.getBoolean("disable.watchdog"); // Paper - private static WatchdogThread instance; - private long timeoutTime; - private boolean restart; -@@ -39,6 +40,7 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa - { - if ( WatchdogThread.instance == null ) - { -+ if (timeoutTime <= 0) timeoutTime = 300; // Paper - WatchdogThread.instance = new WatchdogThread( timeoutTime * 1000L, restart ); - WatchdogThread.instance.start(); - } else -@@ -70,12 +72,13 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa - // Paper start - Logger log = Bukkit.getServer().getLogger(); - long currentTime = WatchdogThread.monotonicMillis(); -- if ( this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable -+ MinecraftServer server = MinecraftServer.getServer(); -+ if ( this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.hasStarted && (!server.isRunning() || (currentTime > this.lastTick + this.earlyWarningEvery && !DISABLE_WATCHDOG) )) // Paper - add property to disable - { -- boolean isLongTimeout = currentTime > lastTick + timeoutTime; -+ boolean isLongTimeout = currentTime > lastTick + timeoutTime || (!server.isRunning() && !server.hasStopped() && currentTime > lastTick + 1000); - // Don't spam early warning dumps - if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue; -- if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... -+ if ( !isLongTimeout && server.hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... - lastEarlyWarning = currentTime; - if (isLongTimeout) { - // Paper end -@@ -136,9 +139,24 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa - - if ( isLongTimeout ) - { -- if ( this.restart && !MinecraftServer.getServer().hasStopped() ) -+ if ( !server.hasStopped() ) - { -- RestartCommand.restart(); -+ AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us -+ server.forceTicks = true; -+ if (restart) { -+ RestartCommand.addShutdownHook( SpigotConfig.restartScript ); -+ } -+ // try one last chance to safe shutdown on main incase it 'comes back' -+ server.abnormalExit = true; -+ server.safeShutdown(false, restart); -+ try { -+ Thread.sleep(1000); -+ } catch (InterruptedException e) { -+ e.printStackTrace(); -+ } -+ if (!server.hasStopped()) { -+ server.close(); -+ } - } - break; - } // Paper end -diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml -index 32e64b3866bdd1489a90339bda2268adafbb15de..675cd61221e807aadf28322b46c3daa1370241b5 100644 ---- a/src/main/resources/log4j2.xml -+++ b/src/main/resources/log4j2.xml -@@ -1,5 +1,5 @@ - -- -+ - - - diff --git a/patches/server/1014-Remove-streams-from-hot-code.patch b/patches/server/1008-Remove-streams-from-hot-code.patch similarity index 100% rename from patches/server/1014-Remove-streams-from-hot-code.patch rename to patches/server/1008-Remove-streams-from-hot-code.patch diff --git a/patches/server/1015-Eigencraft-redstone-implementation.patch b/patches/server/1009-Eigencraft-redstone-implementation.patch similarity index 100% rename from patches/server/1015-Eigencraft-redstone-implementation.patch rename to patches/server/1009-Eigencraft-redstone-implementation.patch diff --git a/patches/server/1016-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch b/patches/server/1010-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch similarity index 100% rename from patches/server/1016-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch rename to patches/server/1010-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch diff --git a/patches/server/1017-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch b/patches/server/1011-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch similarity index 100% rename from patches/server/1017-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch rename to patches/server/1011-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch diff --git a/patches/server/1011-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch b/patches/server/1011-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch deleted file mode 100644 index 47097c6ff44a..000000000000 --- a/patches/server/1011-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch +++ /dev/null @@ -1,169 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 13 May 2020 23:01:26 -0400 -Subject: [PATCH] Protect Bedrock and End Portal/Frames from being destroyed - -This fixes exploits that let players destroy bedrock by Pistons, explosions -and Mushrooom/Tree generation. - -These blocks are designed to not be broken except by creative players/commands. -So protect them from a multitude of methods of destroying them. - -A config is provided if you rather let players use these exploits, and let -them destroy the worlds End Portals and get on top of the nether easy. - -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index 34159798e6617ce13b3ac8aae07d24d9bca6ee36..f54219d2b973136ad00a0f03cbd99f6b82ecee65 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -191,6 +191,7 @@ public class Explosion { - for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { - BlockPos blockposition = BlockPos.containing(d4, d5, d6); - BlockState iblockdata = this.level.getBlockState(blockposition); -+ if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed - FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions - - if (!this.level.isInWorldBounds(blockposition)) { -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 9149ddd3d950d1616ec3c85d54c494bcc6501e83..cb31b2f88e701dc9bb14ea5c568e4666f6cdc0b9 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -540,6 +540,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { - // CraftBukkit start - tree generation - if (this.captureTreeGeneration) { -+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed -+ BlockState type = getBlockState(pos); -+ if (!type.isDestroyable()) return false; -+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed - CraftBlockState blockstate = this.capturedBlockStates.get(pos); - if (blockstate == null) { - blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags); -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 6896d46fce2e466ebee23ac2dc00312ec1beefdb..b60a52788e73de3dcb086c1a4628466b25c9d3ef 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -90,6 +90,19 @@ public class Block extends BlockBehaviour implements ItemLike { - protected final StateDefinition stateDefinition; - private BlockState defaultBlockState; - // Paper start -+ public final boolean isDestroyable() { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || -+ this != Blocks.BEDROCK && -+ this != Blocks.END_PORTAL_FRAME && -+ this != Blocks.END_PORTAL && -+ this != Blocks.END_GATEWAY && -+ this != Blocks.COMMAND_BLOCK && -+ this != Blocks.REPEATING_COMMAND_BLOCK && -+ this != Blocks.CHAIN_COMMAND_BLOCK && -+ this != Blocks.BARRIER && -+ this != Blocks.STRUCTURE_BLOCK && -+ this != Blocks.JIGSAW; -+ } - public co.aikar.timings.Timing timing; - public co.aikar.timings.Timing getTiming() { - if (timing == null) { -diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -index 3dfe79684f662ac7cae4583bfe03a633438b4df7..be74adc86f0ca467f3b59e7b57fd47a8f381d86e 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -@@ -212,6 +212,12 @@ public class PistonBaseBlock extends DirectionalBlock { - @Override - public boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { - Direction enumdirection = (Direction) state.getValue(PistonBaseBlock.FACING); -+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; prevent retracting when we're facing the wrong way (we were replaced before retraction could occur) -+ Direction directionQueuedAs = Direction.from3DDataValue(data & 7); // Paper - copied from below -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits && enumdirection != directionQueuedAs) { -+ return false; -+ } -+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed - BlockState iblockdata1 = (BlockState) state.setValue(PistonBaseBlock.EXTENDED, true); - - if (!world.isClientSide) { -@@ -252,7 +258,7 @@ public class PistonBaseBlock extends DirectionalBlock { - } - // Paper end - Fix sticky pistons and BlockPistonRetractEvent - world.setBlock(pos, iblockdata2, 20); -- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); -+ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed; diff on change - world.blockUpdated(pos, iblockdata2.getBlock()); - iblockdata2.updateNeighbourShapes(world, pos, 2); - if (this.isSticky) { -@@ -288,7 +294,14 @@ public class PistonBaseBlock extends DirectionalBlock { - } - } - } else { -- world.removeBlock(pos.relative(enumdirection), false); -+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed; fix headless pistons breaking blocks -+ BlockPos headPos = pos.relative(enumdirection); -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || world.getBlockState(headPos) == Blocks.PISTON_HEAD.defaultBlockState().setValue(FACING, enumdirection)) { // double check to make sure we're not a headless piston. -+ world.removeBlock(headPos, false); -+ } else { -+ ((ServerLevel) world).getChunkSource().blockChanged(headPos); // ... fix client desync -+ } -+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed - } - - world.playSound((Player) null, pos, SoundEvents.PISTON_CONTRACT, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.15F + 0.6F); -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index e28ac8f7960f648099e5f3607530a406c72e5056..e493b34aa8726ed48f8e5db2ae8ea561cc5b1f75 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -189,7 +189,7 @@ public abstract class BlockBehaviour implements FeatureElement { - /** @deprecated */ - @Deprecated - public void onExplosionHit(BlockState state, Level world, BlockPos pos, Explosion explosion, BiConsumer stackMerger) { -- if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK) { -+ if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK && state.isDestroyable()) { // Paper - Protect Bedrock and End Portal/Frames from being destroyed - Block block = state.getBlock(); - boolean flag = explosion.getIndirectSourceEntity() instanceof Player; - -@@ -285,7 +285,7 @@ public abstract class BlockBehaviour implements FeatureElement { - /** @deprecated */ - @Deprecated - public boolean canBeReplaced(BlockState state, BlockPlaceContext context) { -- return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())); -+ return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed - } - - /** @deprecated */ -@@ -965,6 +965,12 @@ public abstract class BlockBehaviour implements FeatureElement { - return this.legacySolid; - } - -+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed -+ public final boolean isDestroyable() { -+ return getBlock().isDestroyable(); -+ } -+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed -+ - public boolean isValidSpawn(BlockGetter world, BlockPos pos, EntityType type) { - return this.getBlock().properties.isValidSpawn.test(this.asState(), world, pos, type); - } -@@ -1068,7 +1074,7 @@ public abstract class BlockBehaviour implements FeatureElement { - } - - public PushReaction getPistonPushReaction() { -- return this.pushReaction; -+ return !this.isDestroyable() ? PushReaction.BLOCK : this.pushReaction; // Paper - Protect Bedrock and End Portal/Frames from being destroyed - } - - public boolean isSolidRender(BlockGetter world, BlockPos pos) { -diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -index afdd4ecbff21e2172b390bcbdf74f3c1bbddafcc..bb739f8584dd3847152314aa995800f51907da2f 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -@@ -221,6 +221,13 @@ public class PortalForcer { - for (int j = -1; j < 3; ++j) { - for (int k = -1; k < 4; ++k) { - temp.setWithOffset(pos, portalDirection.getStepX() * j + enumdirection1.getStepX() * distanceOrthogonalToPortal, k, portalDirection.getStepZ() * j + enumdirection1.getStepZ() * distanceOrthogonalToPortal); -+ // Paper start - Protect Bedrock and End Portal/Frames from being destroyed -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits) { -+ if (!this.level.getBlockState(temp).isDestroyable()) { -+ return false; -+ } -+ } -+ // Paper end - Protect Bedrock and End Portal/Frames from being destroyed - if (k < 0 && !this.level.getBlockState(temp).isSolid()) { - return false; - } diff --git a/patches/server/1012-Improve-boat-collision-performance.patch b/patches/server/1012-Improve-boat-collision-performance.patch new file mode 100644 index 000000000000..cf6af1a4d2d8 --- /dev/null +++ b/patches/server/1012-Improve-boat-collision-performance.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 2 Aug 2021 10:10:40 +0200 +Subject: [PATCH] Improve boat collision performance + + +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index edf937591abf62416bd692e40b7b7a6badbe877d..b40864e41e1506884fdefefbf3cf4833a8f706c3 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -121,6 +121,7 @@ public class Util { + }).findFirst().orElseThrow(() -> { + return new IllegalStateException("No jar file system provider found"); + }); ++ public static final double COLLISION_EPSILON = 1.0E-7; // Paper - Improve boat collision performance + private static Consumer thePauser = (message) -> { + }; + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index b64d6cd9899ab91895faeb090c5afadbbc90f09e..9f7cb7a2780836c4ce5a6971e9f6004a91509490 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1438,7 +1438,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + if (!source.is(DamageTypeTags.IS_PROJECTILE)) { + Entity entity = source.getDirectEntity(); + +- if (entity instanceof LivingEntity) { ++ if (entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Improve boat collision performance + LivingEntity entityliving = (LivingEntity) entity; + + this.blockUsingShield(entityliving); +@@ -1532,11 +1532,12 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + if (entity1 != null && !source.is(DamageTypeTags.NO_KNOCKBACK)) { +- double d0 = entity1.getX() - this.getX(); ++ final boolean far = entity1.distanceToSqr(this) > (200.0 * 200.0); // Paper - Improve boat collision performance ++ double d0 = far ? (Math.random() - Math.random()) : entity1.getX() - this.getX(); // Paper - Improve boat collision performance + + double d1; + +- for (d1 = entity1.getZ() - this.getZ(); d0 * d0 + d1 * d1 < 1.0E-4D; d1 = (Math.random() - Math.random()) * 0.01D) { ++ for (d1 = far ? Math.random() - Math.random() : entity1.getZ() - this.getZ(); d0 * d0 + d1 * d1 < 1.0E-4D; d1 = (Math.random() - Math.random()) * 0.01D) { // Paper - Improve boat collision performance + d0 = (Math.random() - Math.random()) * 0.01D; + } + +@@ -2279,7 +2280,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING)); + Entity entity = damagesource.getDirectEntity(); + +- if (entity instanceof LivingEntity) { ++ if (entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Improve boat collision performance + this.blockUsingShield((LivingEntity) entity); + } + } +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +index 1ced6d60a74fac028804b3c2d938e89af4706823..db6aa75d642f4a7258f197933671907faf79c8f2 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +@@ -689,7 +689,7 @@ public class Boat extends VehicleEntity implements VariantHolder { + this.invFriction = 0.05F; + if (this.oldStatus == Boat.Status.IN_AIR && this.status != Boat.Status.IN_AIR && this.status != Boat.Status.ON_LAND) { + this.waterLevel = this.getY(1.0D); +- this.setPos(this.getX(), (double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D, this.getZ()); ++ this.move(MoverType.SELF, new Vec3(0.0, ((double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D) - this.getY(), 0.0)); // Paper - Improve boat collision performance + this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D)); + this.lastYd = 0.0D; + this.status = Boat.Status.IN_WATER; diff --git a/patches/server/1012-Use-distance-map-to-optimise-entity-tracker.patch b/patches/server/1012-Use-distance-map-to-optimise-entity-tracker.patch deleted file mode 100644 index 891a4e5d2191..000000000000 --- a/patches/server/1012-Use-distance-map-to-optimise-entity-tracker.patch +++ /dev/null @@ -1,341 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 5 May 2020 20:18:05 -0700 -Subject: [PATCH] Use distance map to optimise entity tracker - -Use the distance map to find candidate players for tracking. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 9037ba5197eed9d8e616fb65369f6b1a5ea9562c..7e5a8789e06a5ea1d2657ea8ee5c0460da92aaeb 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -146,6 +146,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - // Paper start - distance maps - private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); -+ // Paper start - use distance map to optimise tracker -+ public static boolean isLegacyTrackingEntity(Entity entity) { -+ return entity.isLegacyTrackingEntity; -+ } -+ -+ // inlined EnumMap, TrackingRange.TrackingRangeType -+ static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values(); -+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps; -+ final int[] entityTrackerTrackRanges; -+ public final int getEntityTrackerRange(final int ordinal) { -+ return this.entityTrackerTrackRanges[ordinal]; -+ } -+ -+ private int convertSpigotRangeToVanilla(final int vanilla) { -+ return net.minecraft.server.MinecraftServer.getServer().getScaledTrackingDistance(vanilla); -+ } -+ // Paper end - use distance map to optimise tracker - - void addPlayerToDistanceMaps(ServerPlayer player) { - int chunkX = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getX()); -@@ -153,6 +170,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Note: players need to be explicitly added to distance maps before they can be updated - this.nearbyPlayers.addPlayer(player); - this.level.playerChunkLoader.addPlayer(player); // Paper - replace chunk loader -+ // Paper start - use distance map to optimise entity tracker -+ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { -+ com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; -+ int trackRange = this.entityTrackerTrackRanges[i]; -+ -+ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player))); -+ } -+ // Paper end - use distance map to optimise entity tracker - } - - void removePlayerFromDistanceMaps(ServerPlayer player) { -@@ -161,6 +186,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Note: players need to be explicitly added to distance maps before they can be updated - this.nearbyPlayers.removePlayer(player); - this.level.playerChunkLoader.removePlayer(player); // Paper - replace chunk loader -+ // Paper start - use distance map to optimise tracker -+ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { -+ this.playerEntityTrackerTrackMaps[i].remove(player); -+ } -+ // Paper end - use distance map to optimise tracker - } - - void updateMaps(ServerPlayer player) { -@@ -169,6 +199,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Note: players need to be explicitly added to distance maps before they can be updated - this.nearbyPlayers.tickPlayer(player); - this.level.playerChunkLoader.updatePlayer(player); // Paper - replace chunk loader -+ // Paper start - use distance map to optimise entity tracker -+ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { -+ com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; -+ int trackRange = this.entityTrackerTrackRanges[i]; -+ -+ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player))); -+ } -+ // Paper end - use distance map to optimise entity tracker - } - // Paper end - // Paper start -@@ -256,6 +294,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.regionManagers.add(this.dataRegionManager); - this.nearbyPlayers = new io.papermc.paper.util.player.NearbyPlayers(this.level); - // Paper end -+ // Paper start - use distance map to optimise entity tracker -+ this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length]; -+ this.entityTrackerTrackRanges = new int[TRACKING_RANGE_TYPES.length]; -+ -+ org.spigotmc.SpigotWorldConfig spigotWorldConfig = this.level.spigotConfig; -+ -+ for (int ordinal = 0, len = TRACKING_RANGE_TYPES.length; ordinal < len; ++ordinal) { -+ org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = TRACKING_RANGE_TYPES[ordinal]; -+ int configuredSpigotValue; -+ switch (trackingRangeType) { -+ case PLAYER: -+ configuredSpigotValue = spigotWorldConfig.playerTrackingRange; -+ break; -+ case ANIMAL: -+ configuredSpigotValue = spigotWorldConfig.animalTrackingRange; -+ break; -+ case MONSTER: -+ configuredSpigotValue = spigotWorldConfig.monsterTrackingRange; -+ break; -+ case MISC: -+ configuredSpigotValue = spigotWorldConfig.miscTrackingRange; -+ break; -+ case OTHER: -+ configuredSpigotValue = spigotWorldConfig.otherTrackingRange; -+ break; -+ case ENDERDRAGON: -+ configuredSpigotValue = EntityType.ENDER_DRAGON.clientTrackingRange() * 16; -+ break; -+ case DISPLAY: -+ configuredSpigotValue = spigotWorldConfig.displayTrackingRange; -+ break; -+ default: -+ throw new IllegalStateException("Missing case for enum " + trackingRangeType); -+ } -+ configuredSpigotValue = convertSpigotRangeToVanilla(configuredSpigotValue); -+ -+ int trackRange = (configuredSpigotValue >>> 4) + ((configuredSpigotValue & 15) != 0 ? 1 : 0); -+ this.entityTrackerTrackRanges[ordinal] = trackRange; -+ -+ this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); -+ } -+ // Paper end - use distance map to optimise entity tracker - } - - // Paper start -@@ -933,17 +1013,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - public void move(ServerPlayer player) { -- ObjectIterator objectiterator = this.entityMap.values().iterator(); -- -- while (objectiterator.hasNext()) { -- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); -- -- if (playerchunkmap_entitytracker.entity == player) { -- playerchunkmap_entitytracker.updatePlayers(this.level.players()); -- } else { -- playerchunkmap_entitytracker.updatePlayer(player); -- } -- } -+ // Paper - delay this logic for the entity tracker tick, no need to duplicate it - - SectionPos sectionposition = player.getLastSectionPos(); - SectionPos sectionposition1 = SectionPos.of((EntityAccess) player); -@@ -1020,7 +1090,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker - this.entityMap.put(entity.getId(), playerchunkmap_entitytracker); -- playerchunkmap_entitytracker.updatePlayers(this.level.players()); -+ playerchunkmap_entitytracker.updatePlayers(entity.getPlayersInTrackRange()); // Paper - don't search all players - if (entity instanceof ServerPlayer) { - ServerPlayer entityplayer = (ServerPlayer) entity; - -@@ -1064,9 +1134,37 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - entity.tracker = null; // Paper - We're no longer tracked - } - -- protected void tick() { -- // Paper - replaced by PlayerChunkLoader -+ // Paper start - optimised tracker -+ private final void processTrackQueue() { -+ this.level.timings.tracker1.startTiming(); -+ try { -+ for (TrackedEntity tracker : this.entityMap.values()) { -+ // update tracker entry -+ tracker.updatePlayers(tracker.entity.getPlayersInTrackRange()); -+ } -+ } finally { -+ this.level.timings.tracker1.stopTiming(); -+ } -+ -+ -+ this.level.timings.tracker2.startTiming(); -+ try { -+ for (TrackedEntity tracker : this.entityMap.values()) { -+ tracker.serverEntity.sendChanges(); -+ } -+ } finally { -+ this.level.timings.tracker2.stopTiming(); -+ } -+ } -+ // Paper end - optimised tracker - -+ protected void tick() { -+ // Paper start - optimized tracker -+ if (true) { -+ this.processTrackQueue(); -+ return; -+ } -+ // Paper end - optimized tracker - List list = Lists.newArrayList(); - List list1 = this.level.players(); - ObjectIterator objectiterator = this.entityMap.values().iterator(); -@@ -1216,6 +1314,42 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.lastSectionPos = SectionPos.of((EntityAccess) entity); - } - -+ // Paper start - use distance map to optimise tracker -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet lastTrackerCandidates; -+ -+ final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newTrackerCandidates) { -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet oldTrackerCandidates = this.lastTrackerCandidates; -+ this.lastTrackerCandidates = newTrackerCandidates; -+ -+ if (newTrackerCandidates != null) { -+ Object[] rawData = newTrackerCandidates.getBackingSet(); -+ for (int i = 0, len = rawData.length; i < len; ++i) { -+ Object raw = rawData[i]; -+ if (!(raw instanceof ServerPlayer)) { -+ continue; -+ } -+ ServerPlayer player = (ServerPlayer)raw; -+ this.updatePlayer(player); -+ } -+ } -+ -+ if (oldTrackerCandidates == newTrackerCandidates) { -+ // this is likely the case. -+ // means there has been no range changes, so we can just use the above for tracking. -+ return; -+ } -+ -+ // stuff could have been removed, so we need to check the trackedPlayers set -+ // for players that were removed -+ -+ for (ServerPlayerConnection conn : this.seenBy.toArray(new ServerPlayerConnection[0])) { // avoid CME -+ if (newTrackerCandidates == null || !newTrackerCandidates.contains(conn.getPlayer())) { -+ this.updatePlayer(conn.getPlayer()); -+ } -+ } -+ } -+ // Paper end - use distance map to optimise tracker -+ - public boolean equals(Object object) { - return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false; - } -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 0fc522f27afb8a0ded061392d011ff67294b16b3..c206adff916594ec573459d193cc7f3eda4b4957 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -56,6 +56,7 @@ import net.minecraft.network.syncher.EntityDataSerializers; - import net.minecraft.network.syncher.SynchedEntityData; - import net.minecraft.resources.ResourceKey; - import net.minecraft.resources.ResourceLocation; -+import io.papermc.paper.util.MCUtil; - import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; -@@ -468,6 +469,38 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - this.teleportTo(worldserver, null); - } - // Paper end - make end portalling safe -+ // Paper start - optimise entity tracking -+ final org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = org.spigotmc.TrackingRange.getTrackingRangeType(this); -+ -+ public boolean isLegacyTrackingEntity = false; -+ -+ public final void setLegacyTrackingEntity(final boolean isLegacyTrackingEntity) { -+ this.isLegacyTrackingEntity = isLegacyTrackingEntity; -+ } -+ -+ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getPlayersInTrackRange() { -+ // determine highest range of passengers -+ if (this.passengers.isEmpty()) { -+ return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()] -+ .getObjectsInRange(MCUtil.getCoordinateKey(this)); -+ } -+ Iterable passengers = this.getIndirectPassengers(); -+ net.minecraft.server.level.ChunkMap chunkMap = ((ServerLevel)this.level).getChunkSource().chunkMap; -+ org.spigotmc.TrackingRange.TrackingRangeType type = this.trackingRangeType; -+ int range = chunkMap.getEntityTrackerRange(type.ordinal()); -+ -+ for (Entity passenger : passengers) { -+ org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType; -+ int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal()); -+ if (passengerRange > range) { -+ type = passengerType; -+ range = passengerRange; -+ } -+ } -+ -+ return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this)); -+ } -+ // Paper end - optimise entity tracking - public float getBukkitYaw() { - return this.yRot; - } -diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java -index bb06f89a29f30144e7e2113e088a503db006a83c..e4425b242fe73d1fd2bd10c313aa16925432329f 100644 ---- a/src/main/java/org/spigotmc/TrackingRange.java -+++ b/src/main/java/org/spigotmc/TrackingRange.java -@@ -55,4 +55,48 @@ public class TrackingRange - return config.otherTrackingRange; - } - } -+ -+ // Paper start - optimise entity tracking -+ // copied from above, TODO check on update -+ public static TrackingRangeType getTrackingRangeType(Entity entity) -+ { -+ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return TrackingRangeType.ENDERDRAGON; // Paper - enderdragon is exempt -+ if ( entity instanceof ServerPlayer ) -+ { -+ return TrackingRangeType.PLAYER; -+ // Paper start - Simplify and set water mobs to animal tracking range -+ } -+ switch (entity.activationType) { -+ case RAIDER: -+ case MONSTER: -+ case FLYING_MONSTER: -+ return TrackingRangeType.MONSTER; -+ case WATER: -+ case VILLAGER: -+ case ANIMAL: -+ return TrackingRangeType.ANIMAL; -+ case MISC: -+ } -+ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) -+ // Paper end -+ { -+ return TrackingRangeType.MISC; -+ } else if (entity instanceof Display) { -+ return TrackingRangeType.DISPLAY; -+ } else -+ { -+ return TrackingRangeType.OTHER; -+ } -+ } -+ -+ public static enum TrackingRangeType { -+ PLAYER, -+ ANIMAL, -+ MONSTER, -+ MISC, -+ OTHER, -+ ENDERDRAGON, -+ DISPLAY; -+ } -+ // Paper end - optimise entity tracking - } diff --git a/patches/server/1019-Optimise-general-POI-access.patch b/patches/server/1013-Optimise-general-POI-access.patch similarity index 100% rename from patches/server/1019-Optimise-general-POI-access.patch rename to patches/server/1013-Optimise-general-POI-access.patch diff --git a/patches/server/1020-Custom-table-implementation-for-blockstate-state-loo.patch b/patches/server/1014-Custom-table-implementation-for-blockstate-state-loo.patch similarity index 100% rename from patches/server/1020-Custom-table-implementation-for-blockstate-state-loo.patch rename to patches/server/1014-Custom-table-implementation-for-blockstate-state-loo.patch diff --git a/patches/server/1021-Execute-chunk-tasks-mid-tick.patch b/patches/server/1015-Execute-chunk-tasks-mid-tick.patch similarity index 100% rename from patches/server/1021-Execute-chunk-tasks-mid-tick.patch rename to patches/server/1015-Execute-chunk-tasks-mid-tick.patch diff --git a/patches/server/1016-Optimise-random-block-ticking.patch b/patches/server/1016-Optimise-random-block-ticking.patch new file mode 100644 index 000000000000..65d5fd32022a --- /dev/null +++ b/patches/server/1016-Optimise-random-block-ticking.patch @@ -0,0 +1,456 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 20 Jun 2021 16:19:26 -0700 +Subject: [PATCH] Optimise random block ticking + +Massive performance improvement for random block ticking. +The performance increase comes from the fact that the vast +majority of attempted block ticks (~95% in my testing) fail +because the randomly selected block is not tickable. + +Now only tickable blocks are targeted, however this means that +the maximum number of block ticks occurs per chunk. However, +not all chunks are going to be targeted. The percent chance +of a chunk being targeted is based on how many tickable blocks +are in the chunk. +This means that while block ticks are spread out less, the +total number of blocks ticked per world tick remains the same. +Therefore, the chance of a random tickable block being ticked +remains the same. + +diff --git a/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7d93652c1abbb6aee6eb7c26cf35d4d032ef7b69 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java +@@ -0,0 +1,65 @@ ++package io.papermc.paper.util.math; ++ ++import net.minecraft.util.RandomSource; ++import net.minecraft.world.level.levelgen.LegacyRandomSource; ++import net.minecraft.world.level.levelgen.PositionalRandomFactory; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class ThreadUnsafeRandom extends LegacyRandomSource { ++ ++ // See javadoc and internal comments for java.util.Random where these values come from, how they are used, and the author for them. ++ private static final long multiplier = 0x5DEECE66DL; ++ private static final long addend = 0xBL; ++ private static final long mask = (1L << 48) - 1; ++ ++ private static long initialScramble(long seed) { ++ return (seed ^ multiplier) & mask; ++ } ++ ++ private long seed; ++ ++ public ThreadUnsafeRandom(long seed) { ++ super(seed); ++ } ++ ++ @Override ++ public RandomSource fork() { ++ return new ThreadUnsafeRandom(this.nextLong()); ++ } ++ ++ @Override ++ public PositionalRandomFactory forkPositional() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setSeed(long seed) { ++ // note: called by Random constructor ++ this.seed = initialScramble(seed); ++ } ++ ++ @Override ++ public int next(int bits) { ++ // avoid the expensive CAS logic used by superclass ++ return (int) (((this.seed = this.seed * multiplier + addend) & mask) >>> (48 - bits)); ++ } ++ ++ // Taken from ++ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ ++ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c ++ // Original license is public domain ++ public static int fastRandomBounded(final long randomInteger, final long limit) { ++ // randomInteger must be [0, pow(2, 32)) ++ // limit must be [0, pow(2, 32)) ++ return (int)((randomInteger * limit) >>> 32); ++ } ++ ++ @Override ++ public int nextInt(int bound) { ++ // yes this breaks random's spec ++ // however there's nothing that uses this class that relies on it ++ return fastRandomBounded(this.next(32) & 0xFFFFFFFFL, bound); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 3535f86b92c4e61fd84defbbf37e074690a30019..bac2e7c8178696859ff2d38f1e095d86557fc306 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -855,6 +855,10 @@ public class ServerLevel extends Level implements WorldGenLevel { + entityplayer.stopSleepInBed(false, false); + }); + } ++ // Paper start - optimise random block ticking ++ private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos(); ++ private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(this.random.nextLong()); ++ // Paper end + + public void tickChunk(LevelChunk chunk, int randomTickSpeed) { + ChunkPos chunkcoordintpair = chunk.getPos(); +@@ -864,8 +868,10 @@ public class ServerLevel extends Level implements WorldGenLevel { + ProfilerFiller gameprofilerfiller = this.getProfiler(); + + gameprofilerfiller.push("thunder"); ++ final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change ++ + if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder +- BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); ++ blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper + + if (this.isRainingAt(blockposition)) { + DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); +@@ -897,7 +903,10 @@ public class ServerLevel extends Level implements WorldGenLevel { + if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow + for (int l = 0; l < randomTickSpeed; ++l) { + if (this.random.nextInt(48) == 0) { +- this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15)); ++ // Paper start ++ this.getRandomBlockPosition(j, 0, k, 15, blockposition); ++ this.tickPrecipitation(blockposition, chunk); ++ // Paper end + } + } + } // Paper - Option to disable ice and snow +@@ -905,36 +914,37 @@ public class ServerLevel extends Level implements WorldGenLevel { + gameprofilerfiller.popPush("tickBlocks"); + timings.chunkTicksBlocks.startTiming(); // Paper + if (randomTickSpeed > 0) { +- LevelChunkSection[] achunksection = chunk.getSections(); +- +- for (int i1 = 0; i1 < achunksection.length; ++i1) { +- LevelChunkSection chunksection = achunksection[i1]; +- +- if (chunksection.isRandomlyTicking()) { +- int j1 = chunk.getSectionYFromSectionIndex(i1); +- int k1 = SectionPos.sectionToBlockCoord(j1); +- +- for (int l1 = 0; l1 < randomTickSpeed; ++l1) { +- BlockPos blockposition1 = this.getBlockRandomPos(j, k1, k, 15); +- +- gameprofilerfiller.push("randomTick"); +- BlockState iblockdata = chunksection.getBlockState(blockposition1.getX() - j, blockposition1.getY() - k1, blockposition1.getZ() - k); +- +- if (iblockdata.isRandomlyTicking()) { +- iblockdata.randomTick(this, blockposition1, this.random); +- } ++ // Paper start - optimize random block ticking ++ LevelChunkSection[] sections = chunk.getSections(); ++ final int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this); ++ for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) { ++ LevelChunkSection section = sections[sectionIndex]; ++ if (section == null || section.tickingList.size() == 0) continue; ++ ++ int yPos = (sectionIndex + minSection) << 4; ++ for (int a = 0; a < randomTickSpeed; ++a) { ++ int tickingBlocks = section.tickingList.size(); ++ int index = this.randomTickRandom.nextInt(16 * 16 * 16); ++ if (index >= tickingBlocks) { ++ continue; ++ } + +- FluidState fluid = iblockdata.getFluidState(); ++ long raw = section.tickingList.getRaw(index); ++ int location = com.destroystokyo.paper.util.maplist.IBlockDataList.getLocationFromRaw(raw); ++ int randomX = location & 15; ++ int randomY = ((location >>> (4 + 4)) & 255) | yPos; ++ int randomZ = (location >>> 4) & 15; + +- if (fluid.isRandomlyTicking()) { +- fluid.randomTick(this, blockposition1, this.random); +- } ++ BlockPos blockposition2 = blockposition.set(j + randomX, randomY, k + randomZ); ++ BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw); + +- gameprofilerfiller.pop(); +- } ++ iblockdata.randomTick(this, blockposition2, this.randomTickRandom); + } ++ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock). ++ // TODO CHECK ON UPDATE (ping the Canadian) + } + } ++ // Paper end - optimise random block ticking + + timings.chunkTicksBlocks.stopTiming(); // Paper + gameprofilerfiller.pop(); +@@ -942,17 +952,25 @@ public class ServerLevel extends Level implements WorldGenLevel { + + @VisibleForTesting + public void tickPrecipitation(BlockPos pos) { +- BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); +- BlockPos blockposition2 = blockposition1.below(); ++ // Paper start - optimise chunk ticking ++ tickPrecipitation(pos.mutable(), this.getChunkAt(pos)); ++ } ++ public void tickPrecipitation(BlockPos.MutableBlockPos blockposition1, final LevelChunk chunk) { ++ int normalY = chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, blockposition1.getX() & 15, blockposition1.getZ() & 15) + 1; ++ int downY = normalY - 1; ++ blockposition1.setY(normalY); ++ // Paper end - optimise chunk ticking + Biome biomebase = (Biome) this.getBiome(blockposition1).value(); + +- if (biomebase.shouldFreeze(this, blockposition2)) { +- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition2, Blocks.ICE.defaultBlockState(), null); // CraftBukkit ++ blockposition1.setY(downY); ++ if (biomebase.shouldFreeze(this, blockposition1)) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit + } + + if (this.isRaining()) { + int i = this.getGameRules().getInt(GameRules.RULE_SNOW_ACCUMULATION_HEIGHT); + ++ blockposition1.setY(normalY); // Paper - optimise chunk ticking + if (i > 0 && biomebase.shouldSnow(this, blockposition1)) { + BlockState iblockdata = this.getBlockState(blockposition1); + +@@ -970,12 +988,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + } + +- Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitationAt(blockposition2); ++ blockposition1.setY(downY); // Paper - optimise chunk ticking ++ Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitationAt(blockposition1); // Paper - optimise chunk ticking + + if (biomebase_precipitation != Biome.Precipitation.NONE) { +- BlockState iblockdata2 = this.getBlockState(blockposition2); ++ BlockState iblockdata2 = this.getBlockState(blockposition1); // Paper - optimise chunk ticking + +- iblockdata2.getBlock().handlePrecipitation(iblockdata2, this, blockposition2, biomebase_precipitation); ++ iblockdata2.getBlock().handlePrecipitation(iblockdata2, this, blockposition1, biomebase_precipitation); // Paper - optimise chunk ticking + } + } + +diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java +index 68648c5a5e3ff079f832092af0f2f801c42d1ede..8bafd5fd7499ba4a04bf706cfd1e156073716e21 100644 +--- a/src/main/java/net/minecraft/util/BitStorage.java ++++ b/src/main/java/net/minecraft/util/BitStorage.java +@@ -20,4 +20,15 @@ public interface BitStorage { + void unpack(int[] out); + + BitStorage copy(); ++ ++ // Paper start ++ void forEach(DataBitConsumer consumer); ++ ++ @FunctionalInterface ++ interface DataBitConsumer { ++ ++ void accept(int location, int data); ++ ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java +index acd820b19aff4e093536cc47002a899498b3fd6b..453c1d7e01970fd817d27f59c3b00ffc70e8ca0c 100644 +--- a/src/main/java/net/minecraft/util/SimpleBitStorage.java ++++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java +@@ -124,6 +124,28 @@ public class SimpleBitStorage implements BitStorage { + return this.bits; + } + ++ // Paper start ++ @Override ++ public final void forEach(DataBitConsumer consumer) { ++ int i = 0; ++ long[] along = this.data; ++ int j = along.length; ++ ++ for (int k = 0; k < j; ++k) { ++ long l = along[k]; ++ ++ for (int i1 = 0; i1 < this.valuesPerLong; ++i1) { ++ consumer.accept(i, (int) (l & this.mask)); ++ l >>= this.bits; ++ ++i; ++ if (i >= this.size) { ++ return; ++ } ++ } ++ } ++ } ++ // Paper end ++ + @Override + public void getAll(IntConsumer action) { + int i = 0; +diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java +index b7a3f15dc1ed9e9322a86921052984c7cbd3262a..f8de91393564b3691c17339ac9196cc0fc1cf748 100644 +--- a/src/main/java/net/minecraft/util/ZeroBitStorage.java ++++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java +@@ -46,6 +46,15 @@ public class ZeroBitStorage implements BitStorage { + return 0; + } + ++ // Paper start ++ @Override ++ public void forEach(DataBitConsumer consumer) { ++ for(int i = 0; i < this.size; ++i) { ++ consumer.accept(i, 0); ++ } ++ } ++ // Paper end ++ + @Override + public void getAll(IntConsumer action) { + for(int i = 0; i < this.size; ++i) { +diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +index 6a98f66b7701e8af389ca9a1e9eb230a6100c838..dbdb6c432448b151fa4421f14235f8bad23dc720 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +@@ -87,7 +87,7 @@ public class Turtle extends Animal { + } + + public void setHomePos(BlockPos pos) { +- this.entityData.set(Turtle.HOME_POS, pos); ++ this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos... + } + + public BlockPos getHomePos() { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 43052e3194812fe8d7aa6569c1c1c49d8ba25446..1712cf22d987a87c427f042a89a9fff90203b079 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1395,10 +1395,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public abstract RecipeManager getRecipeManager(); + + public BlockPos getBlockRandomPos(int x, int y, int z, int l) { ++ // Paper start - allow use of mutable pos ++ BlockPos.MutableBlockPos ret = new BlockPos.MutableBlockPos(); ++ this.getRandomBlockPosition(x, y, z, l, ret); ++ return ret.immutable(); ++ } ++ public final BlockPos.MutableBlockPos getRandomBlockPosition(int x, int y, int z, int l, BlockPos.MutableBlockPos out) { ++ // Paper end + this.randValue = this.randValue * 3 + 1013904223; + int i1 = this.randValue >> 2; + +- return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); ++ out.set(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); // Paper - change to setValues call ++ return out; // Paper + } + + public boolean noSave() { +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index 8852263cb6faec1b68326145aa30e5cd36d066e7..eb05c01e85825cbd5b7cf43bc6d261db0b871b92 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -25,6 +25,7 @@ public class LevelChunkSection { + public final PalettedContainer states; + // CraftBukkit start - read/write + private PalettedContainer> biomes; ++ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper + + public LevelChunkSection(PalettedContainer datapaletteblock, PalettedContainer> palettedcontainerro) { + // CraftBukkit end +@@ -77,6 +78,9 @@ public class LevelChunkSection { + --this.nonEmptyBlockCount; + if (iblockdata1.isRandomlyTicking()) { + --this.tickingBlockCount; ++ // Paper start ++ this.tickingList.remove(x, y, z); ++ // Paper end + } + } + +@@ -88,6 +92,9 @@ public class LevelChunkSection { + ++this.nonEmptyBlockCount; + if (state.isRandomlyTicking()) { + ++this.tickingBlockCount; ++ // Paper start ++ this.tickingList.add(x, y, z, state); ++ // Paper end + } + } + +@@ -115,40 +122,34 @@ public class LevelChunkSection { + } + + public void recalcBlockCounts() { +- class a implements PalettedContainer.CountConsumer { +- +- public int nonEmptyBlockCount; +- public int tickingBlockCount; +- public int tickingFluidCount; +- +- a() {} +- +- public void accept(BlockState iblockdata, int i) { ++ // Paper start - unfuck this ++ this.tickingList.clear(); ++ this.nonEmptyBlockCount = 0; ++ this.tickingBlockCount = 0; ++ this.tickingFluidCount = 0; ++ // Don't run this on clearly empty sections ++ if (this.maybeHas((BlockState state) -> !state.isAir() || !state.getFluidState().isEmpty())) { ++ this.states.forEachLocation((BlockState iblockdata, int i) -> { + FluidState fluid = iblockdata.getFluidState(); + + if (!iblockdata.isAir()) { +- this.nonEmptyBlockCount += i; ++ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); + if (iblockdata.isRandomlyTicking()) { +- this.tickingBlockCount += i; ++ this.tickingBlockCount = (short)(this.tickingBlockCount + 1); ++ this.tickingList.add(i, iblockdata); + } + } + + if (!fluid.isEmpty()) { +- this.nonEmptyBlockCount += i; ++ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); + if (fluid.isRandomlyTicking()) { +- this.tickingFluidCount += i; ++ this.tickingFluidCount = (short) (this.tickingFluidCount + 1); + } + } + +- } ++ }); + } +- +- a a0 = new a(); +- +- this.states.count(a0); +- this.nonEmptyBlockCount = (short) a0.nonEmptyBlockCount; +- this.tickingBlockCount = (short) a0.tickingBlockCount; +- this.tickingFluidCount = (short) a0.tickingFluidCount; ++ // Paper end + } + + public PalettedContainer getStates() { +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 0f930f8355ea99d1cb1a8d27edc1c224588f852f..983799520ce052d98c9231f4f7925492d4f7d5c9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -386,6 +386,14 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + } + ++ // Paper start ++ public void forEachLocation(PalettedContainer.CountConsumer consumer) { ++ this.data.storage.forEach((int location, int data) -> { ++ consumer.accept(this.data.palette.valueFor(data), location); ++ }); ++ } ++ // Paper end ++ + @FunctionalInterface + public interface CountConsumer { + void accept(T object, int count); diff --git a/patches/server/1017-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch b/patches/server/1017-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch new file mode 100644 index 000000000000..b294b05766ba --- /dev/null +++ b/patches/server/1017-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch @@ -0,0 +1,777 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 2 Feb 2020 02:25:10 -0800 +Subject: [PATCH] Attempt to recalculate regionfile header if it is corrupt + +Instead of trying to relocate the chunk, which is seems to never +be the correct choice, so we end up duplicating or swapping chunks, +we instead drop the current regionfile header and recalculate - +hoping that at least then we don't swap chunks, and maybe recover +them all. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 539b36bde9cba3a44184eba36df9aa4c345a5b84..d53c4f3d47a8728d56fbd9b5e12be51885560d52 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -70,6 +70,18 @@ import net.minecraft.world.ticks.ProtoChunkTicks; + import org.slf4j.Logger; + + public class ChunkSerializer { ++ // Paper start - Attempt to recalculate regionfile header if it is corrupt ++ // TODO: Check on update ++ public static long getLastWorldSaveTime(CompoundTag chunkData) { ++ final int dataVersion = ChunkStorage.getVersion(chunkData); ++ if (dataVersion < 2842) { // Level tag is removed after this version ++ final CompoundTag levelData = chunkData.getCompound("Level"); ++ return levelData.getLong("LastUpdate"); ++ } else { ++ return chunkData.getLong("LastUpdate"); ++ } ++ } ++ // Paper end - Attempt to recalculate regionfile header if it is corrupt + + public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -455,7 +467,7 @@ public class ChunkSerializer { + nbttagcompound.putInt("xPos", chunkcoordintpair.x); + nbttagcompound.putInt("yPos", chunk.getMinSection()); + nbttagcompound.putInt("zPos", chunkcoordintpair.z); +- nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading ++ nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading // Paper - diff on change + nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime()); + nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString()); + BlendingData blendingdata = chunk.getBlendingData(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index 645a1773237f2002c233ec1f3ff6f0ca46ca4024..d16d7c2fed89fb1347df7ddd95856e7f08c22e8a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -38,7 +38,7 @@ public class ChunkStorage implements AutoCloseable { + + public ChunkStorage(Path directory, DataFixer dataFixer, boolean dsync) { + this.fixerUpper = dataFixer; +- this.regionFileCache = new RegionFileStorage(directory, dsync); // Paper - rewrite chunk system; async chunk IO ++ this.regionFileCache = new RegionFileStorage(directory, dsync, true); // Paper - rewrite chunk system; async chunk IO & Attempt to recalculate regionfile header if it is corrupt + } + + public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java +index c8298a597818227de33a4afce4698ec0666cf758..6762b0f71ea9e369bb77103b7f1938983cb77a44 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java +@@ -9,6 +9,27 @@ import java.util.BitSet; + public class RegionBitmap { + private final BitSet used = new BitSet(); + ++ // Paper start - Attempt to recalculate regionfile header if it is corrupt ++ public final void copyFrom(RegionBitmap other) { ++ BitSet thisBitset = this.used; ++ BitSet otherBitset = other.used; ++ ++ for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) { ++ thisBitset.set(i, otherBitset.get(i)); ++ } ++ } ++ ++ public final boolean tryAllocate(int from, int length) { ++ BitSet bitset = this.used; ++ int firstSet = bitset.nextSetBit(from); ++ if (firstSet > 0 && firstSet < (from + length)) { ++ return false; ++ } ++ bitset.set(from, from + length); ++ return true; ++ } ++ // Paper end - Attempt to recalculate regionfile header if it is corrupt ++ + public void force(int start, int size) { + this.used.set(start, start + size); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +index 92ba75254f6ffca40abd5485dbb4789de59edebd..6cf83502a954cce9c562ec036bfeddb477d38b73 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -50,6 +50,355 @@ public class RegionFile implements AutoCloseable { + public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // Paper + public final Path regionFile; // Paper + ++ // Paper start - Attempt to recalculate regionfile header if it is corrupt ++ private static long roundToSectors(long bytes) { ++ long sectors = bytes >>> 12; // 4096 = 2^12 ++ long remainingBytes = bytes & 4095; ++ long sign = -remainingBytes; // sign is 1 if nonzero ++ return sectors + (sign >>> 63); ++ } ++ ++ private static final CompoundTag OVERSIZED_COMPOUND = new CompoundTag(); ++ ++ private CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException { ++ try { ++ if (chunkDataLength < 0) { ++ return null; ++ } ++ ++ long offset = sector * 4096L + 4L; // offset for chunk data ++ ++ if ((offset + chunkDataLength) > fileLength) { ++ return null; ++ } ++ ++ ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength); ++ if (chunkDataLength != this.file.read(chunkData, offset)) { ++ return null; ++ } ++ ++ ((java.nio.Buffer)chunkData).flip(); ++ ++ byte compressionType = chunkData.get(); ++ if (compressionType < 0) { // compressionType & 128 != 0 ++ // oversized chunk ++ return OVERSIZED_COMPOUND; ++ } ++ ++ RegionFileVersion compression = RegionFileVersion.fromId(compressionType); ++ if (compression == null) { ++ return null; ++ } ++ ++ InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position())); ++ ++ return NbtIo.read(new DataInputStream(input)); ++ } catch (Exception ex) { ++ return null; ++ } ++ } ++ ++ private int getLength(long sector) throws IOException { ++ ByteBuffer length = ByteBuffer.allocate(4); ++ if (4 != this.file.read(length, sector * 4096L)) { ++ return -1; ++ } ++ ++ return length.getInt(0); ++ } ++ ++ private void backupRegionFile() { ++ Path backup = this.regionFile.getParent().resolve(this.regionFile.getFileName() + "." + new java.util.Random().nextLong() + ".backup"); ++ this.backupRegionFile(backup); ++ } ++ ++ private void backupRegionFile(Path to) { ++ try { ++ this.file.force(true); ++ LOGGER.warn("Backing up regionfile \"" + this.regionFile.toAbsolutePath() + "\" to " + to.toAbsolutePath()); ++ java.nio.file.Files.copy(this.regionFile, to, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES); ++ LOGGER.warn("Backed up the regionfile to " + to.toAbsolutePath()); ++ } catch (IOException ex) { ++ LOGGER.error("Failed to backup to " + to.toAbsolutePath(), ex); ++ } ++ } ++ ++ private static boolean inSameRegionfile(ChunkPos first, ChunkPos second) { ++ return (first.x & ~31) == (second.x & ~31) && (first.z & ~31) == (second.z & ~31); ++ } ++ ++ // note: only call for CHUNK regionfiles ++ boolean recalculateHeader() throws IOException { ++ if (!this.canRecalcHeader) { ++ return false; ++ } ++ ChunkPos ourLowerLeftPosition = RegionFileStorage.getRegionFileCoordinates(this.regionFile); ++ if (ourLowerLeftPosition == null) { ++ LOGGER.error("Unable to get chunk location of regionfile " + this.regionFile.toAbsolutePath() + ", cannot recover header"); ++ return false; ++ } ++ synchronized (this) { ++ LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile " + this.regionFile.toAbsolutePath(), new Throwable()); ++ ++ // try to backup file so maybe it could be sent to us for further investigation ++ ++ this.backupRegionFile(); ++ CompoundTag[] compounds = new CompoundTag[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data) ++ int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes ++ int[] sectorOffsets = new int[32 * 32]; // in sectors ++ boolean[] hasAikarOversized = new boolean[32 * 32]; ++ ++ long fileLength = this.file.size(); ++ long totalSectors = roundToSectors(fileLength); ++ ++ // search the regionfile from start to finish for the most up-to-date chunk data ++ ++ for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip ++ int chunkDataLength = this.getLength(i); ++ CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength); ++ if (compound == null || compound == OVERSIZED_COMPOUND) { ++ continue; ++ } ++ ++ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(compound); ++ if (!inSameRegionfile(ourLowerLeftPosition, chunkPos)) { ++ LOGGER.error("Ignoring absolute chunk " + chunkPos + " in regionfile as it is not contained in the bounds of the regionfile '" + this.regionFile.toAbsolutePath() + "'. It should be in regionfile (" + (chunkPos.x >> 5) + "," + (chunkPos.z >> 5) + ")"); ++ continue; ++ } ++ int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5); ++ ++ CompoundTag otherCompound = compounds[location]; ++ ++ if (otherCompound != null && ChunkSerializer.getLastWorldSaveTime(otherCompound) > ChunkSerializer.getLastWorldSaveTime(compound)) { ++ continue; // don't overwrite newer data. ++ } ++ ++ // aikar oversized? ++ Path aikarOversizedFile = this.getOversizedFile(chunkPos.x, chunkPos.z); ++ boolean isAikarOversized = false; ++ if (Files.exists(aikarOversizedFile)) { ++ try { ++ CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z); ++ if (ChunkSerializer.getLastWorldSaveTime(compound) == ChunkSerializer.getLastWorldSaveTime(aikarOversizedCompound)) { ++ // best we got for an id. hope it's good enough ++ isAikarOversized = true; ++ } ++ } catch (Exception ex) { ++ LOGGER.error("Failed to read aikar oversized data for absolute chunk (" + chunkPos.x + "," + chunkPos.z + ") in regionfile " + this.regionFile.toAbsolutePath() + ", oversized data for this chunk will be lost", ex); ++ // fall through, if we can't read aikar oversized we can't risk corrupting chunk data ++ } ++ } ++ ++ hasAikarOversized[location] = isAikarOversized; ++ compounds[location] = compound; ++ rawLengths[location] = chunkDataLength + 4; ++ sectorOffsets[location] = (int)i; ++ ++ int chunkSectorLength = (int)roundToSectors(rawLengths[location]); ++ i += chunkSectorLength; ++ --i; // gets incremented next iteration ++ } ++ ++ // forge style oversized data is already handled by the local search, and aikar data we just hope ++ // we get it right as aikar data has no identifiers we could use to try and find its corresponding ++ // local data compound ++ ++ java.nio.file.Path containingFolder = this.externalFileDir; ++ Path[] regionFiles = Files.list(containingFolder).toArray(Path[]::new); ++ boolean[] oversized = new boolean[32 * 32]; ++ RegionFileVersion[] oversizedCompressionTypes = new RegionFileVersion[32 * 32]; ++ ++ if (regionFiles != null) { ++ int lowerXBound = ourLowerLeftPosition.x; // inclusive ++ int lowerZBound = ourLowerLeftPosition.z; // inclusive ++ int upperXBound = lowerXBound + 32 - 1; // inclusive ++ int upperZBound = lowerZBound + 32 - 1; // inclusive ++ ++ // read mojang oversized data ++ for (Path regionFile : regionFiles) { ++ ChunkPos oversizedCoords = getOversizedChunkPair(regionFile); ++ if (oversizedCoords == null) { ++ continue; ++ } ++ ++ if ((oversizedCoords.x < lowerXBound || oversizedCoords.x > upperXBound) || (oversizedCoords.z < lowerZBound || oversizedCoords.z > upperZBound)) { ++ continue; // not in our regionfile ++ } ++ ++ // ensure oversized data is valid & is newer than data in the regionfile ++ ++ int location = (oversizedCoords.x & 31) | ((oversizedCoords.z & 31) << 5); ++ ++ byte[] chunkData; ++ try { ++ chunkData = Files.readAllBytes(regionFile); ++ } catch (Exception ex) { ++ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", data will be lost", ex); ++ continue; ++ } ++ ++ CompoundTag compound = null; ++ ++ // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them ++ RegionFileVersion compression = null; ++ for (RegionFileVersion compressionType : RegionFileVersion.VERSIONS.values()) { ++ try { ++ DataInputStream in = new DataInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData))); // typical java ++ compound = NbtIo.read((java.io.DataInput)in); ++ compression = compressionType; ++ break; // reaches here iff readNBT does not throw ++ } catch (Exception ex) { ++ continue; ++ } ++ } ++ ++ if (compound == null) { ++ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", it's corrupt. Its data will be lost"); ++ continue; ++ } ++ ++ if (!ChunkSerializer.getChunkCoordinate(compound).equals(oversizedCoords)) { ++ LOGGER.error("Can't use oversized chunk stored in " + regionFile.toAbsolutePath() + ", got absolute chunkpos: " + ChunkSerializer.getChunkCoordinate(compound) + ", expected " + oversizedCoords); ++ continue; ++ } ++ ++ if (compounds[location] == null || ChunkSerializer.getLastWorldSaveTime(compound) > ChunkSerializer.getLastWorldSaveTime(compounds[location])) { ++ oversized[location] = true; ++ oversizedCompressionTypes[location] = compression; ++ } ++ } ++ } ++ ++ // now we need to calculate a new offset header ++ ++ int[] calculatedOffsets = new int[32 * 32]; ++ RegionBitmap newSectorAllocations = new RegionBitmap(); ++ newSectorAllocations.force(0, 2); // make space for header ++ ++ // allocate sectors for normal chunks ++ ++ for (int chunkX = 0; chunkX < 32; ++chunkX) { ++ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { ++ int location = chunkX | (chunkZ << 5); ++ ++ if (oversized[location]) { ++ continue; ++ } ++ ++ int rawLength = rawLengths[location]; // bytes ++ int sectorOffset = sectorOffsets[location]; // sectors ++ int sectorLength = (int)roundToSectors(rawLength); ++ ++ if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) { ++ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized ++ } else { ++ LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + ", chunk will be regenerated"); ++ } ++ } ++ } ++ ++ // allocate sectors for oversized chunks ++ ++ for (int chunkX = 0; chunkX < 32; ++chunkX) { ++ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { ++ int location = chunkX | (chunkZ << 5); ++ ++ if (!oversized[location]) { ++ continue; ++ } ++ ++ int sectorOffset = newSectorAllocations.allocate(1); ++ int sectorLength = 1; ++ ++ try { ++ this.file.write(this.createExternalStub(oversizedCompressionTypes[location]), sectorOffset * 4096); ++ // only allocate in the new offsets if the write succeeds ++ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized ++ } catch (IOException ex) { ++ newSectorAllocations.free(sectorOffset, sectorLength); ++ LOGGER.error("Failed to write new oversized chunk data holder, local chunk at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + " will be regenerated"); ++ } ++ } ++ } ++ ++ // rewrite aikar oversized data ++ ++ this.oversizedCount = 0; ++ for (int chunkX = 0; chunkX < 32; ++chunkX) { ++ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { ++ int location = chunkX | (chunkZ << 5); ++ int isAikarOversized = hasAikarOversized[location] ? 1 : 0; ++ ++ this.oversizedCount += isAikarOversized; ++ this.oversized[location] = (byte)isAikarOversized; ++ } ++ } ++ ++ if (this.oversizedCount > 0) { ++ try { ++ this.writeOversizedMeta(); ++ } catch (Exception ex) { ++ LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile " + this.regionFile.toAbsolutePath(), ex); ++ Files.deleteIfExists(this.getOversizedMetaFile()); ++ } ++ } else { ++ Files.deleteIfExists(this.getOversizedMetaFile()); ++ } ++ ++ this.usedSectors.copyFrom(newSectorAllocations); ++ ++ // before we overwrite the old sectors, print a summary of the chunks that got changed. ++ ++ LOGGER.info("Starting summary of changes for regionfile " + this.regionFile.toAbsolutePath()); ++ ++ for (int chunkX = 0; chunkX < 32; ++chunkX) { ++ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { ++ int location = chunkX | (chunkZ << 5); ++ ++ int oldOffset = this.offsets.get(location); ++ int newOffset = calculatedOffsets[location]; ++ ++ if (oldOffset == newOffset) { ++ continue; ++ } ++ ++ this.offsets.put(location, newOffset); // overwrite incorrect offset ++ ++ if (oldOffset == 0) { ++ // found lost data ++ LOGGER.info("Found missing data for local chunk (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath()); ++ } else if (newOffset == 0) { ++ LOGGER.warn("Data for local chunk (" + chunkX + "," + chunkZ + ") could not be recovered in regionfile " + this.regionFile.toAbsolutePath() + ", it will be regenerated"); ++ } else { ++ LOGGER.info("Local chunk (" + chunkX + "," + chunkZ + ") changed to point to newer data or correct chunk in regionfile " + this.regionFile.toAbsolutePath()); ++ } ++ } ++ } ++ ++ LOGGER.info("End of change summary for regionfile " + this.regionFile.toAbsolutePath()); ++ ++ // simply destroy the timestamp header, it's not used ++ ++ for (int i = 0; i < 32 * 32; ++i) { ++ this.timestamps.put(i, calculatedOffsets[i] != 0 ? (int)System.currentTimeMillis() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this ++ } ++ ++ // write new header ++ try { ++ this.flush(); ++ this.file.force(true); // try to ensure it goes through... ++ LOGGER.info("Successfully wrote new header to disk for regionfile " + this.regionFile.toAbsolutePath()); ++ } catch (IOException ex) { ++ LOGGER.error("Failed to write new header to disk for regionfile " + this.regionFile.toAbsolutePath(), ex); ++ } ++ } ++ ++ return true; ++ } ++ ++ final boolean canRecalcHeader; // final forces compile fail on new constructor ++ // Paper end - Attempt to recalculate regionfile header if it is corrupt ++ + // Paper start - Cache chunk status + private final net.minecraft.world.level.chunk.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.ChunkStatus[32 * 32]; + +@@ -77,8 +426,19 @@ public class RegionFile implements AutoCloseable { + public RegionFile(Path file, Path directory, boolean dsync) throws IOException { + this(file, directory, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format + } ++ // Paper start - add can recalc flag ++ public RegionFile(Path file, Path directory, boolean dsync, boolean canRecalcHeader) throws IOException { ++ this(file, directory, RegionFileVersion.getCompressionFormat(), dsync, canRecalcHeader); ++ } ++ // Paper end - add can recalc flag + + public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException { ++ // Paper start - add can recalc flag ++ this(file, directory, outputChunkStreamVersion, dsync, false); ++ } ++ public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync, boolean canRecalcHeader) throws IOException { ++ this.canRecalcHeader = canRecalcHeader; ++ // Paper end - add can recalc flag + this.header = ByteBuffer.allocateDirect(8192); + this.regionFile = file; // Paper + initOversizedState(); // Paper +@@ -107,14 +467,16 @@ public class RegionFile implements AutoCloseable { + RegionFile.LOGGER.warn("Region file {} has truncated header: {}", file, i); + } + +- long j = Files.size(file); ++ final long j = Files.size(file); final long regionFileSize = j; // Paper - recalculate header on header corruption + +- for (int k = 0; k < 1024; ++k) { +- int l = this.offsets.get(k); ++ boolean needsHeaderRecalc = false; // Paper - recalculate header on header corruption ++ boolean hasBackedUp = false; // Paper - recalculate header on header corruption ++ for (int k = 0; k < 1024; ++k) { final int headerLocation = k; // Paper - we expect this to be the header location ++ final int l = this.offsets.get(k); + + if (l != 0) { +- int i1 = RegionFile.getSectorNumber(l); +- int j1 = RegionFile.getNumSectors(l); ++ final int i1 = RegionFile.getSectorNumber(l); final int offset = i1; // Paper - we expect this to be offset in file in sectors ++ int j1 = RegionFile.getNumSectors(l); final int sectorLength; // Paper - diff on change, we expect this to be sector length of region - watch out for reassignments + // Spigot start + if (j1 == 255) { + // We're maxed out, so we need to read the proper length from the section +@@ -123,32 +485,102 @@ public class RegionFile implements AutoCloseable { + j1 = (realLen.getInt(0) + 4) / 4096 + 1; + } + // Spigot end ++ sectorLength = j1; // Paper - diff on change, we expect this to be sector length of region + + if (i1 < 2) { + RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{file, k, i1}); +- this.offsets.put(k, 0); ++ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change + } else if (j1 == 0) { + RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", file, k); +- this.offsets.put(k, 0); ++ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change + } else if ((long) i1 * 4096L > j) { + RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", new Object[]{file, k, i1}); +- this.offsets.put(k, 0); ++ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change + } else { +- this.usedSectors.force(i1, j1); ++ //this.usedSectors.force(i1, j1); // Paper - move this down so we can check if it fails to allocate ++ } ++ // Paper start - recalculate header on header corruption ++ if (offset < 2 || sectorLength <= 0 || ((long)offset * 4096L) > regionFileSize) { ++ if (canRecalcHeader) { ++ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + "! Recalculating header..."); ++ needsHeaderRecalc = true; ++ break; ++ } else { ++ // location = chunkX | (chunkZ << 5); ++ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + ++ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header"); ++ if (!hasBackedUp) { ++ hasBackedUp = true; ++ this.backupRegionFile(); ++ } ++ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too ++ this.offsets.put(headerLocation, 0); // delete the entry from header ++ continue; ++ } ++ } ++ boolean failedToAllocate = !this.usedSectors.tryAllocate(offset, sectorLength); ++ if (failedToAllocate) { ++ LOGGER.error("Overlapping allocation by local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") in regionfile " + this.regionFile.toAbsolutePath()); + } ++ if (failedToAllocate & !canRecalcHeader) { ++ // location = chunkX | (chunkZ << 5); ++ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + ++ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header"); ++ if (!hasBackedUp) { ++ hasBackedUp = true; ++ this.backupRegionFile(); ++ } ++ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too ++ this.offsets.put(headerLocation, 0); // delete the entry from header ++ continue; ++ } ++ needsHeaderRecalc |= failedToAllocate; ++ // Paper end - recalculate header on header corruption + } + } ++ // Paper start - recalculate header on header corruption ++ // we move the recalc here so comparison to old header is correct when logging to console ++ if (needsHeaderRecalc) { // true if header gave us overlapping allocations or had other issues ++ LOGGER.error("Recalculating regionfile " + this.regionFile.toAbsolutePath() + ", header gave erroneous offsets & locations"); ++ this.recalculateHeader(); ++ } ++ // Paper end + } + + } + } + + private Path getExternalChunkPath(ChunkPos chunkPos) { +- String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; ++ String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; // Paper - diff on change + + return this.externalFileDir.resolve(s); + } + ++ // Paper start ++ private static ChunkPos getOversizedChunkPair(Path file) { ++ String fileName = file.getFileName().toString(); ++ ++ if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) { ++ return null; ++ } ++ ++ String[] split = fileName.split("\\."); ++ ++ if (split.length != 4) { ++ return null; ++ } ++ ++ try { ++ int x = Integer.parseInt(split[1]); ++ int z = Integer.parseInt(split[2]); ++ ++ return new ChunkPos(x, z); ++ } catch (NumberFormatException ex) { ++ return null; ++ } ++ } ++ // Paper end ++ + @Nullable + public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException { + int i = this.getOffset(pos); +@@ -172,6 +604,11 @@ public class RegionFile implements AutoCloseable { + ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error + if (bytebuffer.remaining() < 5) { + RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", new Object[]{pos, l, bytebuffer.remaining()}); ++ // Paper start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ // Paper end - recalculate header on regionfile corruption + return null; + } else { + int i1 = bytebuffer.getInt(); +@@ -179,6 +616,11 @@ public class RegionFile implements AutoCloseable { + + if (i1 == 0) { + RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", pos); ++ // Paper start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ // Paper end - recalculate header on regionfile corruption + return null; + } else { + int j1 = i1 - 1; +@@ -186,17 +628,44 @@ public class RegionFile implements AutoCloseable { + if (RegionFile.isExternalStreamChunk(b0)) { + if (j1 != 0) { + RegionFile.LOGGER.warn("Chunk has both internal and external streams"); ++ // Paper start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ // Paper end - recalculate header on regionfile corruption + } + +- return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0)); ++ // Paper start - recalculate header on regionfile corruption ++ final DataInputStream ret = this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0)); ++ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ return ret; ++ // Paper end - recalculate header on regionfile corruption + } else if (j1 > bytebuffer.remaining()) { + RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", new Object[]{pos, j1, bytebuffer.remaining()}); ++ // Paper start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ // Paper end - recalculate header on regionfile corruption + return null; + } else if (j1 < 0) { + RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, pos); ++ // Paper start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ // Paper end - recalculate header on regionfile corruption + return null; + } else { +- return this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1)); ++ // Paper start - recalculate header on regionfile corruption ++ final DataInputStream ret = this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1)); ++ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ return ret; ++ // Paper end - recalculate header on regionfile corruption + } + } + } +@@ -371,10 +840,15 @@ public class RegionFile implements AutoCloseable { + } + + private ByteBuffer createExternalStub() { ++ // Paper start - add version param ++ return this.createExternalStub(this.version); ++ } ++ private ByteBuffer createExternalStub(RegionFileVersion version) { ++ // Paper end - add version param + ByteBuffer bytebuffer = ByteBuffer.allocate(5); + + bytebuffer.putInt(1); +- bytebuffer.put((byte) (this.version.getId() | 128)); ++ bytebuffer.put((byte) (version.getId() | 128)); // Paper - replace with version param + ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error + return bytebuffer; + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 42d37bee3a459adcd46408596ccf93abbcff51fe..fe312b1aef579cb4bf81bdd967cf72ff880d7505 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -24,6 +24,7 @@ public class RegionFileStorage implements AutoCloseable { + public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap(); + private final Path folder; + private final boolean sync; ++ private final boolean isChunkData; // Paper + + // Paper start - cache regionfile does not exist state + static final int MAX_NON_EXISTING_CACHE = 1024 * 64; +@@ -55,6 +56,12 @@ public class RegionFileStorage implements AutoCloseable { + // Paper end - cache regionfile does not exist state + + protected RegionFileStorage(Path directory, boolean dsync) { // Paper - protected constructor ++ // Paper start - add isChunkData param ++ this(directory, dsync, false); ++ } ++ RegionFileStorage(Path directory, boolean dsync, boolean isChunkData) { ++ this.isChunkData = isChunkData; ++ // Paper end - add isChunkData param + this.folder = directory; + this.sync = dsync; + } +@@ -122,7 +129,7 @@ public class RegionFileStorage implements AutoCloseable { + // Paper - only create directory if not existing only - moved down + Path path = this.folder; + int j = chunkcoordintpair.getRegionX(); +- Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); ++ Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Paper - diff on change + if (existingOnly && !java.nio.file.Files.exists(path1)) { // Paper start - cache regionfile does not exist state + this.markNonExisting(regionPos); + return null; // CraftBukkit +@@ -131,7 +138,7 @@ public class RegionFileStorage implements AutoCloseable { + } + // Paper end - cache regionfile does not exist state + FileUtil.createDirectoriesSafe(this.folder); // Paper - only create directory if not existing only - moved from above +- RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync); ++ RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header + + this.regionCache.putAndMoveToFirst(i, regionfile1); + // Paper start +@@ -188,6 +195,13 @@ public class RegionFileStorage implements AutoCloseable { + if (regionfile == null) { + return null; + } ++ // Paper start - Add regionfile parameter ++ return this.read(pos, regionfile); ++ } ++ public CompoundTag read(ChunkPos pos, RegionFile regionfile) throws IOException { ++ // We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile ++ // if we decide to re-read ++ // Paper end + // CraftBukkit end + try { // Paper + DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos); +@@ -204,6 +218,20 @@ public class RegionFileStorage implements AutoCloseable { + try { + if (datainputstream != null) { + nbttagcompound = NbtIo.read((DataInput) datainputstream); ++ // Paper start - recover from corrupt regionfile header ++ if (this.isChunkData) { ++ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(nbttagcompound); ++ if (!chunkPos.equals(pos)) { ++ net.minecraft.server.MinecraftServer.LOGGER.error("Attempting to read chunk data at " + pos + " but got chunk data for " + chunkPos + " instead! Attempting regionfile recalculation for regionfile " + regionfile.regionFile.toAbsolutePath()); ++ if (regionfile.recalculateHeader()) { ++ regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once. ++ return this.read(pos, regionfile); ++ } ++ net.minecraft.server.MinecraftServer.LOGGER.error("Can't recalculate regionfile header, regenerating chunk " + pos + " for " + regionfile.regionFile.toAbsolutePath()); ++ return null; ++ } ++ } ++ // Paper end - recover from corrupt regionfile header + break label43; + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java +index 374ff77f15e339500714580673ae8077482ba247..6210a202d27788b1304e749b5bc2d9e2b88f5a63 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java +@@ -14,7 +14,7 @@ import javax.annotation.Nullable; + import net.minecraft.util.FastBufferedInputStream; + + public class RegionFileVersion { +- private static final Int2ObjectMap VERSIONS = new Int2ObjectOpenHashMap<>(); ++ public static final Int2ObjectMap VERSIONS = new Int2ObjectOpenHashMap<>(); // Paper - private -> public + public static final RegionFileVersion VERSION_GZIP = register(new RegionFileVersion(1, (stream) -> { + return new FastBufferedInputStream(new GZIPInputStream(stream)); + }, (stream) -> { diff --git a/patches/server/1018-Improve-boat-collision-performance.patch b/patches/server/1018-Improve-boat-collision-performance.patch deleted file mode 100644 index 4f4144d00555..000000000000 --- a/patches/server/1018-Improve-boat-collision-performance.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 2 Aug 2021 10:10:40 +0200 -Subject: [PATCH] Improve boat collision performance - - -diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java -index edf937591abf62416bd692e40b7b7a6badbe877d..b40864e41e1506884fdefefbf3cf4833a8f706c3 100644 ---- a/src/main/java/net/minecraft/Util.java -+++ b/src/main/java/net/minecraft/Util.java -@@ -121,6 +121,7 @@ public class Util { - }).findFirst().orElseThrow(() -> { - return new IllegalStateException("No jar file system provider found"); - }); -+ public static final double COLLISION_EPSILON = 1.0E-7; // Paper - Improve boat collision performance - private static Consumer thePauser = (message) -> { - }; - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 2b77cc316a8ca5bf75b4aa7f5e881d920bef094c..7d9890670b47a51839784914b0b96a994c1c6ace 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1437,7 +1437,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (!source.is(DamageTypeTags.IS_PROJECTILE)) { - Entity entity = source.getDirectEntity(); - -- if (entity instanceof LivingEntity) { -+ if (entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Improve boat collision performance - LivingEntity entityliving = (LivingEntity) entity; - - this.blockUsingShield(entityliving); -@@ -1531,11 +1531,12 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - if (entity1 != null && !source.is(DamageTypeTags.NO_KNOCKBACK)) { -- double d0 = entity1.getX() - this.getX(); -+ final boolean far = entity1.distanceToSqr(this) > (200.0 * 200.0); // Paper - Improve boat collision performance -+ double d0 = far ? (Math.random() - Math.random()) : entity1.getX() - this.getX(); // Paper - Improve boat collision performance - - double d1; - -- for (d1 = entity1.getZ() - this.getZ(); d0 * d0 + d1 * d1 < 1.0E-4D; d1 = (Math.random() - Math.random()) * 0.01D) { -+ for (d1 = far ? Math.random() - Math.random() : entity1.getZ() - this.getZ(); d0 * d0 + d1 * d1 < 1.0E-4D; d1 = (Math.random() - Math.random()) * 0.01D) { // Paper - Improve boat collision performance - d0 = (Math.random() - Math.random()) * 0.01D; - } - -@@ -2266,7 +2267,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING)); - Entity entity = damagesource.getDirectEntity(); - -- if (entity instanceof LivingEntity) { -+ if (entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - Improve boat collision performance - this.blockUsingShield((LivingEntity) entity); - } - } -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -index 1ced6d60a74fac028804b3c2d938e89af4706823..db6aa75d642f4a7258f197933671907faf79c8f2 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -@@ -689,7 +689,7 @@ public class Boat extends VehicleEntity implements VariantHolder { - this.invFriction = 0.05F; - if (this.oldStatus == Boat.Status.IN_AIR && this.status != Boat.Status.IN_AIR && this.status != Boat.Status.ON_LAND) { - this.waterLevel = this.getY(1.0D); -- this.setPos(this.getX(), (double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D, this.getZ()); -+ this.move(MoverType.SELF, new Vec3(0.0, ((double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D) - this.getY(), 0.0)); // Paper - Improve boat collision performance - this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D)); - this.lastYd = 0.0D; - this.status = Boat.Status.IN_WATER; diff --git a/patches/server/1018-Use-Velocity-compression-and-cipher-natives.patch b/patches/server/1018-Use-Velocity-compression-and-cipher-natives.patch new file mode 100644 index 000000000000..ed8b42ddf61d --- /dev/null +++ b/patches/server/1018-Use-Velocity-compression-and-cipher-natives.patch @@ -0,0 +1,360 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 26 Jul 2021 02:15:17 -0400 +Subject: [PATCH] Use Velocity compression and cipher natives + + +diff --git a/build.gradle.kts b/build.gradle.kts +index 7c563ef33d12b227856e65392905bffa5289285a..376e8983fdfdbb6c3e5fd8ad0f6a05e655b622bf 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -40,6 +40,11 @@ dependencies { + runtimeOnly("org.xerial:sqlite-jdbc:3.42.0.1") + runtimeOnly("com.mysql:mysql-connector-j:8.2.0") + runtimeOnly("com.lmax:disruptor:3.4.4") // Paper ++ // Paper start - Use Velocity cipher ++ implementation("com.velocitypowered:velocity-native:3.1.2-SNAPSHOT") { ++ isTransitive = false ++ } ++ // Paper end - Use Velocity cipher + + runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.6") + runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") +diff --git a/src/main/java/net/minecraft/network/CipherDecoder.java b/src/main/java/net/minecraft/network/CipherDecoder.java +index 778beb445eac5769b9e4e07b4d1294c50ae2602b..7b895b6a626da6297f07582e96d98ecfb6c8c951 100644 +--- a/src/main/java/net/minecraft/network/CipherDecoder.java ++++ b/src/main/java/net/minecraft/network/CipherDecoder.java +@@ -7,13 +7,29 @@ import java.util.List; + import javax.crypto.Cipher; + + public class CipherDecoder extends MessageToMessageDecoder { +- private final CipherBase cipher; ++ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper - Use Velocity cipher + +- public CipherDecoder(Cipher cipher) { +- this.cipher = new CipherBase(cipher); ++ public CipherDecoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper - Use Velocity cipher ++ this.cipher = cipher; // Paper - Use Velocity cipher + } + + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { +- list.add(this.cipher.decipher(channelHandlerContext, byteBuf)); ++ // Paper start - Use Velocity cipher ++ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf); ++ try { ++ cipher.process(compatible); ++ list.add(compatible); ++ } catch (Exception e) { ++ compatible.release(); // compatible will never be used if we throw an exception ++ throw e; ++ } ++ // Paper end - Use Velocity cipher + } ++ ++ // Paper start - Use Velocity cipher ++ @Override ++ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { ++ cipher.close(); ++ } ++ // Paper end - Use Velocity cipher + } +diff --git a/src/main/java/net/minecraft/network/CipherEncoder.java b/src/main/java/net/minecraft/network/CipherEncoder.java +index 0f3d502a9680006bcdcd7d272240a2e5c3b46790..ffa1c48585fbbc1d30826d435043527f6183a3ee 100644 +--- a/src/main/java/net/minecraft/network/CipherEncoder.java ++++ b/src/main/java/net/minecraft/network/CipherEncoder.java +@@ -4,15 +4,32 @@ import io.netty.buffer.ByteBuf; + import io.netty.channel.ChannelHandlerContext; + import io.netty.handler.codec.MessageToByteEncoder; + import javax.crypto.Cipher; ++import java.util.List; + +-public class CipherEncoder extends MessageToByteEncoder { +- private final CipherBase cipher; ++public class CipherEncoder extends io.netty.handler.codec.MessageToMessageEncoder { // Paper - Use Velocity cipher; change superclass ++ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper - Use Velocity cipher + +- public CipherEncoder(Cipher cipher) { +- this.cipher = new CipherBase(cipher); ++ public CipherEncoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper - Use Velocity cipher ++ this.cipher = cipher; // Paper - Use Velocity cipher + } + +- protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { +- this.cipher.encipher(byteBuf, byteBuf2); ++ protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { ++ // Paper start - Use Velocity cipher ++ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf); ++ try { ++ cipher.process(compatible); ++ list.add(compatible); ++ } catch (Exception e) { ++ compatible.release(); // compatible will never be used if we throw an exception ++ throw e; ++ } ++ // Paper end - Use Velocity cipher + } ++ ++ // Paper start - Use Velocity cipher ++ @Override ++ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { ++ cipher.close(); ++ } ++ // Paper end - Use Velocity cipher + } +diff --git a/src/main/java/net/minecraft/network/CompressionDecoder.java b/src/main/java/net/minecraft/network/CompressionDecoder.java +index 2758c257cb4e2b0497bd9243c635f8fe3dbc414c..2763052eb2d5b8eb432022ab00a417976f4b2927 100644 +--- a/src/main/java/net/minecraft/network/CompressionDecoder.java ++++ b/src/main/java/net/minecraft/network/CompressionDecoder.java +@@ -13,13 +13,20 @@ public class CompressionDecoder extends ByteToMessageDecoder { + public static final int MAXIMUM_COMPRESSED_LENGTH = 2097152; + public static final int MAXIMUM_UNCOMPRESSED_LENGTH = 8388608; + private final Inflater inflater; ++ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - Use Velocity cipher + private int threshold; + private boolean validateDecompressed; + ++ // Paper start - Use Velocity cipher + public CompressionDecoder(int compressionThreshold, boolean rejectsBadPackets) { ++ this(null, compressionThreshold, rejectsBadPackets); ++ } ++ public CompressionDecoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold, boolean rejectsBadPackets) { + this.threshold = compressionThreshold; + this.validateDecompressed = rejectsBadPackets; +- this.inflater = new Inflater(); ++ this.inflater = compressor == null ? new Inflater() : null; ++ this.compressor = compressor; ++ // Paper end - Use Velocity cipher + } + + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { +@@ -38,14 +45,42 @@ public class CompressionDecoder extends ByteToMessageDecoder { + } + } + ++ if (inflater != null) { // Paper - Use Velocity cipher; fallback to vanilla inflater + this.setupInflaterInput(byteBuf); + ByteBuf byteBuf2 = this.inflate(channelHandlerContext, i); + this.inflater.reset(); + list.add(byteBuf2); ++ return; // Paper - Use Velocity cipher ++ } // Paper - use velocity compression ++ ++ // Paper start - Use Velocity cipher ++ int claimedUncompressedSize = i; // OBFHELPER ++ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf); ++ ByteBuf uncompressed = com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(channelHandlerContext.alloc(), this.compressor, claimedUncompressedSize); ++ try { ++ this.compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize); ++ list.add(uncompressed); ++ byteBuf.clear(); ++ } catch (Exception e) { ++ uncompressed.release(); ++ throw e; ++ } finally { ++ compatibleIn.release(); ++ } ++ // Paper end - Use Velocity cipher + } + } + } + ++ // Paper start - Use Velocity cipher ++ @Override ++ public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { ++ if (this.compressor != null) { ++ this.compressor.close(); ++ } ++ } ++ // Paper end - Use Velocity cipher ++ + private void setupInflaterInput(ByteBuf buf) { + ByteBuffer byteBuffer; + if (buf.nioBufferCount() > 0) { +diff --git a/src/main/java/net/minecraft/network/CompressionEncoder.java b/src/main/java/net/minecraft/network/CompressionEncoder.java +index 859af8c845bae9781a62fa4acae56c6e2d449e10..f67f59f287d9a5cdd685b6b56ed1daf3f091e099 100644 +--- a/src/main/java/net/minecraft/network/CompressionEncoder.java ++++ b/src/main/java/net/minecraft/network/CompressionEncoder.java +@@ -6,21 +6,36 @@ import io.netty.handler.codec.MessageToByteEncoder; + import java.util.zip.Deflater; + + public class CompressionEncoder extends MessageToByteEncoder { +- private final byte[] encodeBuf = new byte[8192]; ++ private final byte[] encodeBuf; // Paper - Use Velocity cipher + private final Deflater deflater; ++ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - Use Velocity cipher + private int threshold; + ++ // Paper start - Use Velocity cipher + public CompressionEncoder(int compressionThreshold) { ++ this(null, compressionThreshold); ++ } ++ public CompressionEncoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold) { + this.threshold = compressionThreshold; +- this.deflater = new Deflater(); ++ if (compressor == null) { ++ this.encodeBuf = new byte[8192]; ++ this.deflater = new Deflater(); ++ } else { ++ this.encodeBuf = null; ++ this.deflater = null; ++ } ++ this.compressor = compressor; ++ // Paper end - Use Velocity cipher + } + +- protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) { ++ protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { // Paper - Use Velocity cipher + int i = byteBuf.readableBytes(); + if (i < this.threshold) { + VarInt.write(byteBuf2, 0); + byteBuf2.writeBytes(byteBuf); + } else { ++ // Paper start - Use Velocity cipher ++ if (this.deflater != null) { + byte[] bs = new byte[i]; + byteBuf.readBytes(bs); + VarInt.write(byteBuf2, bs.length); +@@ -33,10 +48,48 @@ public class CompressionEncoder extends MessageToByteEncoder { + } + + this.deflater.reset(); ++ return; ++ } ++ ++ VarInt.write(byteBuf2, i); ++ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf); ++ try { ++ this.compressor.deflate(compatibleIn, byteBuf2); ++ } finally { ++ compatibleIn.release(); ++ } ++ // Paper end - Use Velocity cipher + } + + } + ++ // Paper start - Use Velocity cipher ++ @Override ++ protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception{ ++ if (this.compressor != null) { ++ // We allocate bytes to be compressed plus 1 byte. This covers two cases: ++ // ++ // - Compression ++ // According to https://github.com/ebiggers/libdeflate/blob/master/libdeflate.h#L103, ++ // if the data compresses well (and we do not have some pathological case) then the maximum ++ // size the compressed size will ever be is the input size minus one. ++ // - Uncompressed ++ // This is fairly obvious - we will then have one more than the uncompressed size. ++ int initialBufferSize = msg.readableBytes() + 1; ++ return com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(ctx.alloc(), this.compressor, initialBufferSize); ++ } ++ ++ return super.allocateBuffer(ctx, msg, preferDirect); ++ } ++ ++ @Override ++ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { ++ if (this.compressor != null) { ++ this.compressor.close(); ++ } ++ } ++ // Paper end - Use Velocity cipher ++ + public int getThreshold() { + return this.threshold; + } +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 7f2aa5e17fe675f3404d67b1794d2ca68b188eb9..fff375fd50fa1a804636a92ded1ae55cff42977d 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -734,11 +734,28 @@ public class Connection extends SimpleChannelInboundHandler> { + return networkmanager; + } + +- public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) { +- this.encrypted = true; +- this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher)); +- this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher)); ++ // Paper start - Use Velocity cipher ++// public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) { ++// this.encrypted = true; ++// this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher)); ++// this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher)); ++// } ++ ++ public void setupEncryption(javax.crypto.SecretKey key) throws net.minecraft.util.CryptException { ++ if (!this.encrypted) { ++ try { ++ com.velocitypowered.natives.encryption.VelocityCipher decryption = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key); ++ com.velocitypowered.natives.encryption.VelocityCipher encryption = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key); ++ ++ this.encrypted = true; ++ this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryption)); ++ this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryption)); ++ } catch (java.security.GeneralSecurityException e) { ++ throw new net.minecraft.util.CryptException(e); ++ } ++ } + } ++ // Paper end - Use Velocity cipher + + public boolean isEncrypted() { + return this.encrypted; +@@ -771,16 +788,17 @@ public class Connection extends SimpleChannelInboundHandler> { + + public void setupCompression(int compressionThreshold, boolean rejectsBadPackets) { + if (compressionThreshold >= 0) { ++ com.velocitypowered.natives.compression.VelocityCompressor compressor = com.velocitypowered.natives.util.Natives.compress.get().create(io.papermc.paper.configuration.GlobalConfiguration.get().misc.compressionLevel.or(-1)); // Paper - Use Velocity cipher + if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) { + ((CompressionDecoder) this.channel.pipeline().get("decompress")).setThreshold(compressionThreshold, rejectsBadPackets); + } else { +- this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressionThreshold, rejectsBadPackets)); ++ this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressor, compressionThreshold, rejectsBadPackets)); // Paper - Use Velocity cipher + } + + if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) { + ((CompressionEncoder) this.channel.pipeline().get("compress")).setThreshold(compressionThreshold); + } else { +- this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressionThreshold)); ++ this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressor, compressionThreshold)); // Paper - Use Velocity cipher + } + this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners + } else { +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index a4a29a7ea0035ecf4c61ee8547a9eb24acb667d0..586521a2cbb1d4dcfb912029f65e4363ec7674a7 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -106,6 +106,11 @@ public class ServerConnectionListener { + ServerConnectionListener.LOGGER.info("Using default channel type"); + } + ++ // Paper start - Use Velocity cipher ++ ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.compress.getLoadedVariant() + " compression from Velocity."); ++ ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity."); ++ // Paper end - Use Velocity cipher ++ + this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer() { + protected void initChannel(Channel channel) { + Connection.setInitialProtocolAttributes(channel); +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index b8d8f14c30786321949901ca5184c43bee716355..c5fa9f4d28f9a7f64a50a902ee5e631bfc00119c 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -235,12 +235,14 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + } + + SecretKey secretkey = packet.getSecretKey(privatekey); +- Cipher cipher = Crypt.getCipher(2, secretkey); +- Cipher cipher1 = Crypt.getCipher(1, secretkey); ++ // Paper start - Use Velocity cipher ++// Cipher cipher = Crypt.getCipher(2, secretkey); ++// Cipher cipher1 = Crypt.getCipher(1, secretkey); ++ // Paper end - Use Velocity cipher + + s = (new BigInteger(Crypt.digestData("", this.server.getKeyPair().getPublic(), secretkey))).toString(16); + this.state = ServerLoginPacketListenerImpl.State.AUTHENTICATING; +- this.connection.setEncryptionKey(cipher, cipher1); ++ this.connection.setupEncryption(secretkey); // Paper - Use Velocity cipher + } catch (CryptException cryptographyexception) { + throw new IllegalStateException("Protocol error", cryptographyexception); + } diff --git a/patches/server/1019-Detail-more-information-in-watchdog-dumps.patch b/patches/server/1019-Detail-more-information-in-watchdog-dumps.patch new file mode 100644 index 000000000000..a132619b902d --- /dev/null +++ b/patches/server/1019-Detail-more-information-in-watchdog-dumps.patch @@ -0,0 +1,296 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 26 Mar 2020 21:59:32 -0700 +Subject: [PATCH] Detail more information in watchdog dumps + +- Dump position, world, velocity, and uuid for currently ticking entities +- Dump player name, player uuid, position, and world for packet handling + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index fff375fd50fa1a804636a92ded1ae55cff42977d..4716f8bd8a64d4f20f0d5957c1e7fabf63020f43 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -586,7 +586,13 @@ public class Connection extends SimpleChannelInboundHandler> { + if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener) + || loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING + || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) { ++ // Paper start - detailed watchdog information ++ net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener); ++ try { // Paper end - detailed watchdog information + tickablepacketlistener.tick(); ++ } finally { // Paper start - detailed watchdog information ++ net.minecraft.network.protocol.PacketUtils.packetProcessing.pop(); ++ } // Paper end - detailed watchdog information + } + // Paper end - Buffer joins to world + } +diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java +index 454d0187ff8370a0d99cca051ee0a8c50b39cfb7..3e2d5dcd62775b6ed7c0ce0ba51a71b635b1d644 100644 +--- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java ++++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java +@@ -18,6 +18,24 @@ public class PacketUtils { + + private static final Logger LOGGER = LogUtils.getLogger(); + ++ // Paper start - detailed watchdog information ++ public static final java.util.concurrent.ConcurrentLinkedDeque packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>(); ++ static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong(); ++ ++ public static long getTotalProcessedPackets() { ++ return totalMainThreadPacketsProcessed.get(); ++ } ++ ++ public static java.util.List getCurrentPacketProcessors() { ++ java.util.List ret = new java.util.ArrayList<>(4); ++ for (PacketListener listener : packetProcessing) { ++ ret.add(listener); ++ } ++ ++ return ret; ++ } ++ // Paper end - detailed watchdog information ++ + public PacketUtils() {} + + public static void ensureRunningOnSameThread(Packet packet, T listener, ServerLevel world) throws RunningOnDifferentThreadException { +@@ -27,6 +45,8 @@ public class PacketUtils { + public static void ensureRunningOnSameThread(Packet packet, T listener, BlockableEventLoop engine) throws RunningOnDifferentThreadException { + if (!engine.isSameThread()) { + engine.execute(() -> { // Paper - Fix preemptive player kick on a server shutdown ++ packetProcessing.push(listener); // Paper - detailed watchdog information ++ try { // Paper - detailed watchdog information + if (MinecraftServer.getServer().hasStopped() || (listener instanceof ServerCommonPacketListenerImpl && ((ServerCommonPacketListenerImpl) listener).processedDisconnect)) return; // CraftBukkit, MC-142590 + if (listener.shouldHandleMessage(packet)) { + co.aikar.timings.Timing timing = co.aikar.timings.MinecraftTimings.getPacketTiming(packet); // Paper - timings +@@ -64,6 +84,12 @@ public class PacketUtils { + } else { + PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: {}", packet); + } ++ // Paper start - detailed watchdog information ++ } finally { ++ totalMainThreadPacketsProcessed.getAndIncrement(); ++ packetProcessing.pop(); ++ } ++ // Paper end - detailed watchdog information + + }); + throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD; +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index bac2e7c8178696859ff2d38f1e095d86557fc306..5eaf8585df1f885f4a08fdd06ff4bb730961e400 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1239,7 +1239,26 @@ public class ServerLevel extends Level implements WorldGenLevel { + + } + ++ // Paper start - log detailed entity tick information ++ // TODO replace with varhandle ++ static final java.util.concurrent.atomic.AtomicReference currentlyTickingEntity = new java.util.concurrent.atomic.AtomicReference<>(); ++ ++ public static List getCurrentlyTickingEntities() { ++ Entity ticking = currentlyTickingEntity.get(); ++ List ret = java.util.Arrays.asList(ticking == null ? new Entity[0] : new Entity[] { ticking }); ++ ++ return ret; ++ } ++ // Paper end - log detailed entity tick information ++ + public void tickNonPassenger(Entity entity) { ++ // Paper start - log detailed entity tick information ++ io.papermc.paper.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); ++ try { ++ if (currentlyTickingEntity.get() == null) { ++ currentlyTickingEntity.lazySet(entity); ++ } ++ // Paper end - log detailed entity tick information + ++TimingHistory.entityTicks; // Paper - timings + // Spigot start + co.aikar.timings.Timing timer; // Paper +@@ -1279,7 +1298,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + this.tickPassenger(entity, entity1); + } + // } finally { timer.stopTiming(); } // Paper - timings - move up +- ++ // Paper start - log detailed entity tick information ++ } finally { ++ if (currentlyTickingEntity.get() == entity) { ++ currentlyTickingEntity.lazySet(null); ++ } ++ } ++ // Paper end - log detailed entity tick information + } + + private void tickPassenger(Entity vehicle, Entity passenger) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index dea94b8987d77bcb4c2415ed2fa14b2847f11e09..ecf53d27a924cccb7d773cea3ce0f68a41ff52f5 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1050,8 +1050,43 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + return this.onGround; + } + ++ // Paper start - detailed watchdog information ++ public final Object posLock = new Object(); // Paper - log detailed entity tick information ++ ++ private Vec3 moveVector; ++ private double moveStartX; ++ private double moveStartY; ++ private double moveStartZ; ++ ++ public final Vec3 getMoveVector() { ++ return this.moveVector; ++ } ++ ++ public final double getMoveStartX() { ++ return this.moveStartX; ++ } ++ ++ public final double getMoveStartY() { ++ return this.moveStartY; ++ } ++ ++ public final double getMoveStartZ() { ++ return this.moveStartZ; ++ } ++ // Paper end - detailed watchdog information ++ + public void move(MoverType movementType, Vec3 movement) { + final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity ++ // Paper start - detailed watchdog information ++ io.papermc.paper.util.TickThread.ensureTickThread("Cannot move an entity off-main"); ++ synchronized (this.posLock) { ++ this.moveStartX = this.getX(); ++ this.moveStartY = this.getY(); ++ this.moveStartZ = this.getZ(); ++ this.moveVector = movement; ++ } ++ try { ++ // Paper end - detailed watchdog information + if (this.noPhysics) { + this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); + } else { +@@ -1221,6 +1256,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + this.level().getProfiler().pop(); + } + } ++ // Paper start - detailed watchdog information ++ } finally { ++ synchronized (this.posLock) { // Paper ++ this.moveVector = null; ++ } // Paper ++ } ++ // Paper end - detailed watchdog information + } + + private boolean isStateClimbable(BlockState state) { +@@ -4363,7 +4405,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + public void setDeltaMovement(Vec3 velocity) { ++ synchronized (this.posLock) { // Paper + this.deltaMovement = velocity; ++ } // Paper + } + + public void addDeltaMovement(Vec3 velocity) { +@@ -4466,7 +4510,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + // Paper end - Fix MC-4 + if (this.position.x != x || this.position.y != y || this.position.z != z) { ++ synchronized (this.posLock) { // Paper + this.position = new Vec3(x, y, z); ++ } // Paper + int i = Mth.floor(x); + int j = Mth.floor(y); + int k = Mth.floor(z); +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 0234555978d1b13051f876a257e47bafad37b0f8..9e638f72f180ff5ef63ec3dd6cf548c53f7bd4a5 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -22,6 +22,78 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa + private volatile long lastTick; + private volatile boolean stopping; + ++ // Paper start - log detailed tick information ++ private void dumpEntity(net.minecraft.world.entity.Entity entity) { ++ Logger log = Bukkit.getServer().getLogger(); ++ double posX, posY, posZ; ++ net.minecraft.world.phys.Vec3 mot; ++ double moveStartX, moveStartY, moveStartZ; ++ net.minecraft.world.phys.Vec3 moveVec; ++ synchronized (entity.posLock) { ++ posX = entity.getX(); ++ posY = entity.getY(); ++ posZ = entity.getZ(); ++ mot = entity.getDeltaMovement(); ++ moveStartX = entity.getMoveStartX(); ++ moveStartY = entity.getMoveStartY(); ++ moveStartZ = entity.getMoveStartZ(); ++ moveVec = entity.getMoveVector(); ++ } ++ ++ String entityType = net.minecraft.world.entity.EntityType.getKey(entity.getType()).toString(); ++ java.util.UUID entityUUID = entity.getUUID(); ++ net.minecraft.world.level.Level world = entity.level(); ++ ++ log.log(Level.SEVERE, "Ticking entity: " + entityType + ", entity class: " + entity.getClass().getName()); ++ log.log(Level.SEVERE, "Entity status: removed: " + entity.isRemoved() + ", valid: " + entity.valid + ", alive: " + entity.isAlive() + ", is passenger: " + entity.isPassenger()); ++ log.log(Level.SEVERE, "Entity UUID: " + entityUUID); ++ log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")"); ++ log.log(Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)"); ++ log.log(Level.SEVERE, "Entity AABB: " + entity.getBoundingBox()); ++ if (moveVec != null) { ++ log.log(Level.SEVERE, "Move call information: "); ++ log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")"); ++ log.log(Level.SEVERE, "Move vector: " + moveVec.toString()); ++ } ++ } ++ ++ private void dumpTickingInfo() { ++ Logger log = Bukkit.getServer().getLogger(); ++ ++ // ticking entities ++ for (net.minecraft.world.entity.Entity entity : net.minecraft.server.level.ServerLevel.getCurrentlyTickingEntities()) { ++ this.dumpEntity(entity); ++ net.minecraft.world.entity.Entity vehicle = entity.getVehicle(); ++ if (vehicle != null) { ++ log.log(Level.SEVERE, "Detailing vehicle for above entity:"); ++ this.dumpEntity(vehicle); ++ } ++ } ++ ++ // packet processors ++ for (net.minecraft.network.PacketListener packetListener : net.minecraft.network.protocol.PacketUtils.getCurrentPacketProcessors()) { ++ if (packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl) { ++ net.minecraft.server.level.ServerPlayer player = ((net.minecraft.server.network.ServerGamePacketListenerImpl)packetListener).player; ++ long totalPackets = net.minecraft.network.protocol.PacketUtils.getTotalProcessedPackets(); ++ if (player == null) { ++ log.log(Level.SEVERE, "Handling packet for player connection or ticking player connection (null player): " + packetListener); ++ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); ++ } else { ++ this.dumpEntity(player); ++ net.minecraft.world.entity.Entity vehicle = player.getVehicle(); ++ if (vehicle != null) { ++ log.log(Level.SEVERE, "Detailing vehicle for above entity:"); ++ this.dumpEntity(vehicle); ++ } ++ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); ++ } ++ } else { ++ log.log(Level.SEVERE, "Handling packet for connection: " + packetListener); ++ } ++ } ++ } ++ // Paper end - log detailed tick information ++ + private WatchdogThread(long timeoutTime, boolean restart) + { + super( "Paper Watchdog Thread" ); +@@ -119,6 +191,7 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper + io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(isLongTimeout); // Paper - rewrite chunk system ++ this.dumpTickingInfo(); // Paper - log detailed tick information + WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // diff --git a/patches/server/1020-Collision-optimisations.patch b/patches/server/1020-Collision-optimisations.patch new file mode 100644 index 000000000000..a849d42f7be2 --- /dev/null +++ b/patches/server/1020-Collision-optimisations.patch @@ -0,0 +1,4673 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 4 May 2020 10:06:24 -0700 +Subject: [PATCH] Collision optimisations + +The collision patch has been designed with the assumption that +most shapes are either a single AABB or an ArrayVoxelShape +(typical voxel bitset representation). Like previously, +single AABB shapes are treated as AABBs. Unlike previously, the +VoxelShape class has been changed to carry shape data that +ArrayVoxelShape would, except in a discrete manner rather +than abstracted away (not hidden behind DoubleList and +the poorly named DiscreteVoxelShape). + +VoxelShape now carries three important states: + 1. The voxel bitset + its sizes for the X, Y, and Z axis + 2. The voxel coordinates (represented as an array and an offset per axis) + 3. Single AABB representation, if possible + +Note that if the single AABB representation is present, +it is used instead of the voxel bitset representation as +the single AABB representation is a special case of the +voxel bitset representation and can be optimised as such. + +This effectively turns every VoxelShape instance, regardless of +actual class, into a typical voxel bitset representation. +This allows all VoxelShape operations to be optimised +for voxel bitset representations without dealing with the +abstraction and indirection that was imposed on VoxelShape +by Mojang. The patch now effectively optimises all VoxelShape +operations. Below is a list of some of the operations optimised: + - Shape merging/ORing + - Shape optimisation + - Occlusion checking + - Non-single AABB VoxelShape collisions/intersection + - Shape raytracing + - Empty VoxelShape testing + +This patch also includes optimisations for raytracing, +which mostly boil down to removing indirection caused by the +interface BlockGetter which allows chunk caching. + +diff --git a/src/main/java/io/papermc/paper/util/CachedLists.java b/src/main/java/io/papermc/paper/util/CachedLists.java +index be668387f65a633c6ac497fca632a4767a1bf3a2..e08f4e39db4ee3fed62e37364d17dcc5c5683504 100644 +--- a/src/main/java/io/papermc/paper/util/CachedLists.java ++++ b/src/main/java/io/papermc/paper/util/CachedLists.java +@@ -1,8 +1,57 @@ + package io.papermc.paper.util; + ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.phys.AABB; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.util.UnsafeList; ++import java.util.List; ++ + public final class CachedLists { + +- public static void reset() { ++ // Paper start - optimise collisions ++ static final UnsafeList TEMP_COLLISION_LIST = new UnsafeList<>(1024); ++ static boolean tempCollisionListInUse; ++ ++ public static UnsafeList getTempCollisionList() { ++ if (!Bukkit.isPrimaryThread() || tempCollisionListInUse) { ++ return new UnsafeList<>(16); ++ } ++ tempCollisionListInUse = true; ++ return TEMP_COLLISION_LIST; ++ } ++ ++ public static void returnTempCollisionList(List list) { ++ if (list != TEMP_COLLISION_LIST) { ++ return; ++ } ++ ((UnsafeList)list).setSize(0); ++ tempCollisionListInUse = false; ++ } + ++ static final UnsafeList TEMP_GET_ENTITIES_LIST = new UnsafeList<>(1024); ++ static boolean tempGetEntitiesListInUse; ++ ++ public static UnsafeList getTempGetEntitiesList() { ++ if (!Bukkit.isPrimaryThread() || tempGetEntitiesListInUse) { ++ return new UnsafeList<>(16); ++ } ++ tempGetEntitiesListInUse = true; ++ return TEMP_GET_ENTITIES_LIST; ++ } ++ ++ public static void returnTempGetEntitiesList(List list) { ++ if (list != TEMP_GET_ENTITIES_LIST) { ++ return; ++ } ++ ((UnsafeList)list).setSize(0); ++ tempGetEntitiesListInUse = false; ++ } ++ // Paper end - optimise collisions ++ ++ public static void reset() { ++ // Paper start - optimise collisions ++ TEMP_COLLISION_LIST.completeReset(); ++ TEMP_GET_ENTITIES_LIST.completeReset(); ++ // Paper end - optimise collisions + } + } +diff --git a/src/main/java/io/papermc/paper/util/CollisionUtil.java b/src/main/java/io/papermc/paper/util/CollisionUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ee0331a6bc40cdde08d926fd8eb1dc642630c2e5 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/CollisionUtil.java +@@ -0,0 +1,1851 @@ ++package io.papermc.paper.util; ++ ++import io.papermc.paper.util.collisions.CachedShapeData; ++import it.unimi.dsi.fastutil.doubles.DoubleArrayList; ++import it.unimi.dsi.fastutil.doubles.DoubleList; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.level.CollisionGetter; ++import net.minecraft.world.level.EntityGetter; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.border.WorldBorder; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.PalettedContainer; ++import net.minecraft.world.level.material.FluidState; ++import net.minecraft.world.phys.AABB; ++import net.minecraft.world.phys.Vec3; ++import net.minecraft.world.phys.shapes.ArrayVoxelShape; ++import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape; ++import net.minecraft.world.phys.shapes.BooleanOp; ++import net.minecraft.world.phys.shapes.CollisionContext; ++import net.minecraft.world.phys.shapes.DiscreteVoxelShape; ++import net.minecraft.world.phys.shapes.EntityCollisionContext; ++import net.minecraft.world.phys.shapes.OffsetDoubleList; ++import net.minecraft.world.phys.shapes.Shapes; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import java.util.Arrays; ++import java.util.List; ++import java.util.function.BiPredicate; ++import java.util.function.Predicate; ++ ++public final class CollisionUtil { ++ ++ public static final double COLLISION_EPSILON = 1.0E-7; ++ public static final DoubleArrayList ZERO_ONE = DoubleArrayList.wrap(new double[] { 0.0, 1.0 }); ++ ++ public static boolean isSpecialCollidingBlock(final net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase block) { ++ return block.hasLargeCollisionShape() || block.getBlock() == Blocks.MOVING_PISTON; ++ } ++ ++ public static boolean isEmpty(final AABB aabb) { ++ return (aabb.maxX - aabb.minX) < COLLISION_EPSILON || (aabb.maxY - aabb.minY) < COLLISION_EPSILON || (aabb.maxZ - aabb.minZ) < COLLISION_EPSILON; ++ } ++ ++ public static boolean isEmpty(final double minX, final double minY, final double minZ, ++ final double maxX, final double maxY, final double maxZ) { ++ return (maxX - minX) < COLLISION_EPSILON || (maxY - minY) < COLLISION_EPSILON || (maxZ - minZ) < COLLISION_EPSILON; ++ } ++ ++ public static AABB getBoxForChunk(final int chunkX, final int chunkZ) { ++ double x = (double)(chunkX << 4); ++ double z = (double)(chunkZ << 4); ++ // use a bounding box bigger than the chunk to prevent entities from entering it on move ++ return new AABB(x - 3*COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*COLLISION_EPSILON, ++ x + (16.0 + 3*COLLISION_EPSILON), Double.POSITIVE_INFINITY, z + (16.0 + 3*COLLISION_EPSILON), false); ++ } ++ ++ /* ++ A couple of rules for VoxelShape collisions: ++ Two shapes only intersect if they are actually more than EPSILON units into each other. This also applies to movement ++ checks. ++ If the two shapes strictly collide, then the return value of a collide call will return a value in the opposite ++ direction of the source move. However, this value will not be greater in magnitude than EPSILON. Collision code ++ will automatically round it to 0. ++ */ ++ ++ public static boolean voxelShapeIntersect(final double minX1, final double minY1, final double minZ1, final double maxX1, ++ final double maxY1, final double maxZ1, final double minX2, final double minY2, ++ final double minZ2, final double maxX2, final double maxY2, final double maxZ2) { ++ return (minX1 - maxX2) < -COLLISION_EPSILON && (maxX1 - minX2) > COLLISION_EPSILON && ++ (minY1 - maxY2) < -COLLISION_EPSILON && (maxY1 - minY2) > COLLISION_EPSILON && ++ (minZ1 - maxZ2) < -COLLISION_EPSILON && (maxZ1 - minZ2) > COLLISION_EPSILON; ++ } ++ ++ public static boolean voxelShapeIntersect(final AABB box, final double minX, final double minY, final double minZ, ++ final double maxX, final double maxY, final double maxZ) { ++ return (box.minX - maxX) < -COLLISION_EPSILON && (box.maxX - minX) > COLLISION_EPSILON && ++ (box.minY - maxY) < -COLLISION_EPSILON && (box.maxY - minY) > COLLISION_EPSILON && ++ (box.minZ - maxZ) < -COLLISION_EPSILON && (box.maxZ - minZ) > COLLISION_EPSILON; ++ } ++ ++ public static boolean voxelShapeIntersect(final AABB box1, final AABB box2) { ++ return (box1.minX - box2.maxX) < -COLLISION_EPSILON && (box1.maxX - box2.minX) > COLLISION_EPSILON && ++ (box1.minY - box2.maxY) < -COLLISION_EPSILON && (box1.maxY - box2.minY) > COLLISION_EPSILON && ++ (box1.minZ - box2.maxZ) < -COLLISION_EPSILON && (box1.maxZ - box2.minZ) > COLLISION_EPSILON; ++ } ++ ++ // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON ++ public static double collideX(final AABB target, final AABB source, final double source_move) { ++ if ((source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON && ++ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { ++ if (source_move >= 0.0) { ++ final double max_move = target.minX - source.maxX; // < 0.0 if no strict collision ++ if (max_move < -COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.min(max_move, source_move); ++ } else { ++ final double max_move = target.maxX - source.minX; // > 0.0 if no strict collision ++ if (max_move > COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.max(max_move, source_move); ++ } ++ } ++ return source_move; ++ } ++ ++ // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON ++ public static double collideY(final AABB target, final AABB source, final double source_move) { ++ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && ++ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { ++ if (source_move >= 0.0) { ++ final double max_move = target.minY - source.maxY; // < 0.0 if no strict collision ++ if (max_move < -COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.min(max_move, source_move); ++ } else { ++ final double max_move = target.maxY - source.minY; // > 0.0 if no strict collision ++ if (max_move > COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.max(max_move, source_move); ++ } ++ } ++ return source_move; ++ } ++ ++ // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON ++ public static double collideZ(final AABB target, final AABB source, final double source_move) { ++ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && ++ (source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON) { ++ if (source_move >= 0.0) { ++ final double max_move = target.minZ - source.maxZ; // < 0.0 if no strict collision ++ if (max_move < -COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.min(max_move, source_move); ++ } else { ++ final double max_move = target.maxZ - source.minZ; // > 0.0 if no strict collision ++ if (max_move > COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.max(max_move, source_move); ++ } ++ } ++ return source_move; ++ } ++ ++ // startIndex and endIndex inclusive ++ // assumes indices are in range of array ++ private static int findFloor(final double[] values, final double value, int startIndex, int endIndex) { ++ do { ++ final int middle = (startIndex + endIndex) >>> 1; ++ final double middleVal = values[middle]; ++ ++ if (value < middleVal) { ++ endIndex = middle - 1; ++ } else { ++ startIndex = middle + 1; ++ } ++ } while (startIndex <= endIndex); ++ ++ return startIndex - 1; ++ } ++ ++ public static boolean voxelShapeIntersectNoEmpty(final VoxelShape voxel, final AABB aabb) { ++ if (voxel.isEmpty()) { ++ return false; ++ } ++ ++ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true ++ ++ // offsets that should be applied to coords ++ final double off_x = voxel.offsetX(); ++ final double off_y = voxel.offsetY(); ++ final double off_z = voxel.offsetZ(); ++ ++ final double[] coords_x = voxel.rootCoordinatesX(); ++ final double[] coords_y = voxel.rootCoordinatesY(); ++ final double[] coords_z = voxel.rootCoordinatesZ(); ++ ++ final CachedShapeData cached_shape_data = voxel.getCachedVoxelData(); ++ ++ // note: size = coords.length - 1 ++ final int size_x = cached_shape_data.sizeX(); ++ final int size_y = cached_shape_data.sizeY(); ++ final int size_z = cached_shape_data.sizeZ(); ++ ++ // note: voxel bitset with set index (x, y, z) indicates that ++ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1]) ++ // is collidable. this is the fundamental principle of operation for the voxel collision operation ++ ++ // note: we should be offsetting coords, but we can also just subtract from source as well - which is ++ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that) ++ // note: for intersection, one we find the floor of the min we can use that as the start index ++ // for the next check as source max >= source min ++ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size, ++ // as this implies that coords[coords.length - 1] < source min ++ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max ++ ++ final int floor_min_x = Math.max( ++ 0, ++ findFloor(coords_x, (aabb.minX - off_x) + COLLISION_EPSILON, 0, size_x) ++ ); ++ if (floor_min_x >= size_x) { ++ // cannot intersect ++ return false; ++ } ++ ++ final int ceil_max_x = Math.min( ++ size_x, ++ findFloor(coords_x, (aabb.maxX - off_x) - COLLISION_EPSILON, floor_min_x, size_x) + 1 ++ ); ++ if (floor_min_x >= ceil_max_x) { ++ // cannot intersect ++ return false; ++ } ++ ++ final int floor_min_y = Math.max( ++ 0, ++ findFloor(coords_y, (aabb.minY - off_y) + COLLISION_EPSILON, 0, size_y) ++ ); ++ if (floor_min_y >= size_y) { ++ // cannot intersect ++ return false; ++ } ++ ++ final int ceil_max_y = Math.min( ++ size_y, ++ findFloor(coords_y, (aabb.maxY - off_y) - COLLISION_EPSILON, floor_min_y, size_y) + 1 ++ ); ++ if (floor_min_y >= ceil_max_y) { ++ // cannot intersect ++ return false; ++ } ++ ++ final int floor_min_z = Math.max( ++ 0, ++ findFloor(coords_z, (aabb.minZ - off_z) + COLLISION_EPSILON, 0, size_z) ++ ); ++ if (floor_min_z >= size_z) { ++ // cannot intersect ++ return false; ++ } ++ ++ final int ceil_max_z = Math.min( ++ size_z, ++ findFloor(coords_z, (aabb.maxZ - off_z) - COLLISION_EPSILON, floor_min_z, size_z) + 1 ++ ); ++ if (floor_min_z >= ceil_max_z) { ++ // cannot intersect ++ return false; ++ } ++ ++ final long[] bitset = cached_shape_data.voxelSet(); ++ ++ // check bitset to check if any shapes in range are full ++ ++ final int mul_x = size_y*size_z; ++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { ++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { ++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return true; ++ } ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ // assume !target.isEmpty() && abs(source_move) >= COLLISION_EPSILON ++ public static double collideX(final VoxelShape target, final AABB source, final double source_move) { ++ final AABB single_aabb = target.getSingleAABBRepresentation(); ++ if (single_aabb != null) { ++ return collideX(single_aabb, source, source_move); ++ } ++ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true ++ ++ // offsets that should be applied to coords ++ final double off_x = target.offsetX(); ++ final double off_y = target.offsetY(); ++ final double off_z = target.offsetZ(); ++ ++ final double[] coords_x = target.rootCoordinatesX(); ++ final double[] coords_y = target.rootCoordinatesY(); ++ final double[] coords_z = target.rootCoordinatesZ(); ++ ++ final CachedShapeData cached_shape_data = target.getCachedVoxelData(); ++ ++ // note: size = coords.length - 1 ++ final int size_x = cached_shape_data.sizeX(); ++ final int size_y = cached_shape_data.sizeY(); ++ final int size_z = cached_shape_data.sizeZ(); ++ ++ // note: voxel bitset with set index (x, y, z) indicates that ++ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1]) ++ // is collidable. this is the fundamental principle of operation for the voxel collision operation ++ ++ ++ // note: we should be offsetting coords, but we can also just subtract from source as well - which is ++ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that) ++ // note: for intersection, one we find the floor of the min we can use that as the start index ++ // for the next check as source max >= source min ++ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size, ++ // as this implies that coords[coords.length - 1] < source min ++ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max ++ ++ final int floor_min_y = Math.max( ++ 0, ++ findFloor(coords_y, (source.minY - off_y) + COLLISION_EPSILON, 0, size_y) ++ ); ++ if (floor_min_y >= size_y) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int ceil_max_y = Math.min( ++ size_y, ++ findFloor(coords_y, (source.maxY - off_y) - COLLISION_EPSILON, floor_min_y, size_y) + 1 ++ ); ++ if (floor_min_y >= ceil_max_y) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int floor_min_z = Math.max( ++ 0, ++ findFloor(coords_z, (source.minZ - off_z) + COLLISION_EPSILON, 0, size_z) ++ ); ++ if (floor_min_z >= size_z) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int ceil_max_z = Math.min( ++ size_z, ++ findFloor(coords_z, (source.maxZ - off_z) - COLLISION_EPSILON, floor_min_z, size_z) + 1 ++ ); ++ if (floor_min_z >= ceil_max_z) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ // index = z + y*size_z + x*(size_z*size_y) ++ ++ final long[] bitset = cached_shape_data.voxelSet(); ++ ++ if (source_move > 0.0) { ++ final double source_max = source.maxX - off_x; ++ final int ceil_max_x = findFloor( ++ coords_x, source_max - COLLISION_EPSILON, 0, size_x ++ ) + 1; // add one, we are not interested in (coords[i] + COLLISION_EPSILON) < max ++ ++ // note: only the order of the first loop matters ++ ++ // note: we cannot collide with the face at index size on the collision axis for forward movement ++ ++ final int mul_x = size_y*size_z; ++ for (int curr_x = ceil_max_x; curr_x < size_x; ++curr_x) { ++ double max_dist = coords_x[curr_x] - source_max; ++ if (max_dist >= source_move) { ++ // if we reach here, then we will never have a case where ++ // coords[curr + n] - source_max < source_move, as coords[curr + n] < coords[curr + n + 1] ++ // thus, we can return immediately ++ ++ // this optimization is important since this loop is bounded by size, and _not_ by ++ // a calculated max index based off of source_move - so it would be possible to check ++ // the whole intersected shape for collisions when we didn't need to! ++ return source_move; ++ } ++ if (max_dist >= -COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON ++ max_dist = Math.min(max_dist, source_move); ++ } ++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { ++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return max_dist; ++ } ++ } ++ } ++ } ++ ++ return source_move; ++ } else { ++ final double source_min = source.minX - off_x; ++ final int floor_min_x = findFloor( ++ coords_x, source_min + COLLISION_EPSILON, 0, size_x ++ ); ++ ++ // note: only the order of the first loop matters ++ ++ // note: we cannot collide with the face at index 0 on the collision axis for backwards movement ++ ++ // note: we offset the collision axis by - 1 for the voxel bitset index, but use + 1 for the ++ // coordinate index as the voxelset stores whether the shape is solid for [index, index + 1] ++ // thus, we need to use the voxel index i-1 if we want to check that the face at index i is solid ++ final int mul_x = size_y*size_z; ++ for (int curr_x = floor_min_x - 1; curr_x >= 0; --curr_x) { ++ double max_dist = coords_x[curr_x + 1] - source_min; ++ if (max_dist <= source_move) { ++ // if we reach here, then we will never have a case where ++ // coords[curr + n] - source_max > source_move, as coords[curr + n] > coords[curr + n - 1] ++ // thus, we can return immediately ++ ++ // this optimization is important since this loop is possibly bounded by size, and _not_ by ++ // a calculated max index based off of source_move - so it would be possible to check ++ // the whole intersected shape for collisions when we didn't need to! ++ return source_move; ++ } ++ if (max_dist <= COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON ++ max_dist = Math.max(max_dist, source_move); ++ } ++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { ++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return max_dist; ++ } ++ } ++ } ++ } ++ ++ return source_move; ++ } ++ } ++ ++ public static double collideY(final VoxelShape target, final AABB source, final double source_move) { ++ final AABB single_aabb = target.getSingleAABBRepresentation(); ++ if (single_aabb != null) { ++ return collideY(single_aabb, source, source_move); ++ } ++ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true ++ ++ // offsets that should be applied to coords ++ final double off_x = target.offsetX(); ++ final double off_y = target.offsetY(); ++ final double off_z = target.offsetZ(); ++ ++ final double[] coords_x = target.rootCoordinatesX(); ++ final double[] coords_y = target.rootCoordinatesY(); ++ final double[] coords_z = target.rootCoordinatesZ(); ++ ++ final CachedShapeData cached_shape_data = target.getCachedVoxelData(); ++ ++ // note: size = coords.length - 1 ++ final int size_x = cached_shape_data.sizeX(); ++ final int size_y = cached_shape_data.sizeY(); ++ final int size_z = cached_shape_data.sizeZ(); ++ ++ // note: voxel bitset with set index (x, y, z) indicates that ++ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1]) ++ // is collidable. this is the fundamental principle of operation for the voxel collision operation ++ ++ ++ // note: we should be offsetting coords, but we can also just subtract from source as well - which is ++ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that) ++ // note: for intersection, one we find the floor of the min we can use that as the start index ++ // for the next check as source max >= source min ++ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size, ++ // as this implies that coords[coords.length - 1] < source min ++ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max ++ ++ final int floor_min_x = Math.max( ++ 0, ++ findFloor(coords_x, (source.minX - off_x) + COLLISION_EPSILON, 0, size_x) ++ ); ++ if (floor_min_x >= size_x) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int ceil_max_x = Math.min( ++ size_x, ++ findFloor(coords_x, (source.maxX - off_x) - COLLISION_EPSILON, floor_min_x, size_x) + 1 ++ ); ++ if (floor_min_x >= ceil_max_x) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int floor_min_z = Math.max( ++ 0, ++ findFloor(coords_z, (source.minZ - off_z) + COLLISION_EPSILON, 0, size_z) ++ ); ++ if (floor_min_z >= size_z) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int ceil_max_z = Math.min( ++ size_z, ++ findFloor(coords_z, (source.maxZ - off_z) - COLLISION_EPSILON, floor_min_z, size_z) + 1 ++ ); ++ if (floor_min_z >= ceil_max_z) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ // index = z + y*size_z + x*(size_z*size_y) ++ ++ final long[] bitset = cached_shape_data.voxelSet(); ++ ++ if (source_move > 0.0) { ++ final double source_max = source.maxY - off_y; ++ final int ceil_max_y = findFloor( ++ coords_y, source_max - COLLISION_EPSILON, 0, size_y ++ ) + 1; // add one, we are not interested in (coords[i] + COLLISION_EPSILON) < max ++ ++ // note: only the order of the first loop matters ++ ++ // note: we cannot collide with the face at index size on the collision axis for forward movement ++ ++ final int mul_x = size_y*size_z; ++ for (int curr_y = ceil_max_y; curr_y < size_y; ++curr_y) { ++ double max_dist = coords_y[curr_y] - source_max; ++ if (max_dist >= source_move) { ++ // if we reach here, then we will never have a case where ++ // coords[curr + n] - source_max < source_move, as coords[curr + n] < coords[curr + n + 1] ++ // thus, we can return immediately ++ ++ // this optimization is important since this loop is bounded by size, and _not_ by ++ // a calculated max index based off of source_move - so it would be possible to check ++ // the whole intersected shape for collisions when we didn't need to! ++ return source_move; ++ } ++ if (max_dist >= -COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON ++ max_dist = Math.min(max_dist, source_move); ++ } ++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { ++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return max_dist; ++ } ++ } ++ } ++ } ++ ++ return source_move; ++ } else { ++ final double source_min = source.minY - off_y; ++ final int floor_min_y = findFloor( ++ coords_y, source_min + COLLISION_EPSILON, 0, size_y ++ ); ++ ++ // note: only the order of the first loop matters ++ ++ // note: we cannot collide with the face at index 0 on the collision axis for backwards movement ++ ++ // note: we offset the collision axis by - 1 for the voxel bitset index, but use + 1 for the ++ // coordinate index as the voxelset stores whether the shape is solid for [index, index + 1] ++ // thus, we need to use the voxel index i-1 if we want to check that the face at index i is solid ++ final int mul_x = size_y*size_z; ++ for (int curr_y = floor_min_y - 1; curr_y >= 0; --curr_y) { ++ double max_dist = coords_y[curr_y + 1] - source_min; ++ if (max_dist <= source_move) { ++ // if we reach here, then we will never have a case where ++ // coords[curr + n] - source_max > source_move, as coords[curr + n] > coords[curr + n - 1] ++ // thus, we can return immediately ++ ++ // this optimization is important since this loop is possibly bounded by size, and _not_ by ++ // a calculated max index based off of source_move - so it would be possible to check ++ // the whole intersected shape for collisions when we didn't need to! ++ return source_move; ++ } ++ if (max_dist <= COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON ++ max_dist = Math.max(max_dist, source_move); ++ } ++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { ++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return max_dist; ++ } ++ } ++ } ++ } ++ ++ return source_move; ++ } ++ } ++ ++ public static double collideZ(final VoxelShape target, final AABB source, final double source_move) { ++ final AABB single_aabb = target.getSingleAABBRepresentation(); ++ if (single_aabb != null) { ++ return collideZ(single_aabb, source, source_move); ++ } ++ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true ++ ++ // offsets that should be applied to coords ++ final double off_x = target.offsetX(); ++ final double off_y = target.offsetY(); ++ final double off_z = target.offsetZ(); ++ ++ final double[] coords_x = target.rootCoordinatesX(); ++ final double[] coords_y = target.rootCoordinatesY(); ++ final double[] coords_z = target.rootCoordinatesZ(); ++ ++ final CachedShapeData cached_shape_data = target.getCachedVoxelData(); ++ ++ // note: size = coords.length - 1 ++ final int size_x = cached_shape_data.sizeX(); ++ final int size_y = cached_shape_data.sizeY(); ++ final int size_z = cached_shape_data.sizeZ(); ++ ++ // note: voxel bitset with set index (x, y, z) indicates that ++ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1]) ++ // is collidable. this is the fundamental principle of operation for the voxel collision operation ++ ++ ++ // note: we should be offsetting coords, but we can also just subtract from source as well - which is ++ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that) ++ // note: for intersection, one we find the floor of the min we can use that as the start index ++ // for the next check as source max >= source min ++ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size, ++ // as this implies that coords[coords.length - 1] < source min ++ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max ++ ++ final int floor_min_x = Math.max( ++ 0, ++ findFloor(coords_x, (source.minX - off_x) + COLLISION_EPSILON, 0, size_x) ++ ); ++ if (floor_min_x >= size_x) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int ceil_max_x = Math.min( ++ size_x, ++ findFloor(coords_x, (source.maxX - off_x) - COLLISION_EPSILON, floor_min_x, size_x) + 1 ++ ); ++ if (floor_min_x >= ceil_max_x) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int floor_min_y = Math.max( ++ 0, ++ findFloor(coords_y, (source.minY - off_y) + COLLISION_EPSILON, 0, size_y) ++ ); ++ if (floor_min_y >= size_y) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int ceil_max_y = Math.min( ++ size_y, ++ findFloor(coords_y, (source.maxY - off_y) - COLLISION_EPSILON, floor_min_y, size_y) + 1 ++ ); ++ if (floor_min_y >= ceil_max_y) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ // index = z + y*size_z + x*(size_z*size_y) ++ ++ final long[] bitset = cached_shape_data.voxelSet(); ++ ++ if (source_move > 0.0) { ++ final double source_max = source.maxZ - off_z; ++ final int ceil_max_z = findFloor( ++ coords_z, source_max - COLLISION_EPSILON, 0, size_z ++ ) + 1; // add one, we are not interested in (coords[i] + COLLISION_EPSILON) < max ++ ++ // note: only the order of the first loop matters ++ ++ // note: we cannot collide with the face at index size on the collision axis for forward movement ++ ++ final int mul_x = size_y*size_z; ++ for (int curr_z = ceil_max_z; curr_z < size_z; ++curr_z) { ++ double max_dist = coords_z[curr_z] - source_max; ++ if (max_dist >= source_move) { ++ // if we reach here, then we will never have a case where ++ // coords[curr + n] - source_max < source_move, as coords[curr + n] < coords[curr + n + 1] ++ // thus, we can return immediately ++ ++ // this optimization is important since this loop is bounded by size, and _not_ by ++ // a calculated max index based off of source_move - so it would be possible to check ++ // the whole intersected shape for collisions when we didn't need to! ++ return source_move; ++ } ++ if (max_dist >= -COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON ++ max_dist = Math.min(max_dist, source_move); ++ } ++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { ++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return max_dist; ++ } ++ } ++ } ++ } ++ ++ return source_move; ++ } else { ++ final double source_min = source.minZ - off_z; ++ final int floor_min_z = findFloor( ++ coords_z, source_min + COLLISION_EPSILON, 0, size_z ++ ); ++ ++ // note: only the order of the first loop matters ++ ++ // note: we cannot collide with the face at index 0 on the collision axis for backwards movement ++ ++ // note: we offset the collision axis by - 1 for the voxel bitset index, but use + 1 for the ++ // coordinate index as the voxelset stores whether the shape is solid for [index, index + 1] ++ // thus, we need to use the voxel index i-1 if we want to check that the face at index i is solid ++ final int mul_x = size_y*size_z; ++ for (int curr_z = floor_min_z - 1; curr_z >= 0; --curr_z) { ++ double max_dist = coords_z[curr_z + 1] - source_min; ++ if (max_dist <= source_move) { ++ // if we reach here, then we will never have a case where ++ // coords[curr + n] - source_max > source_move, as coords[curr + n] > coords[curr + n - 1] ++ // thus, we can return immediately ++ ++ // this optimization is important since this loop is possibly bounded by size, and _not_ by ++ // a calculated max index based off of source_move - so it would be possible to check ++ // the whole intersected shape for collisions when we didn't need to! ++ return source_move; ++ } ++ if (max_dist <= COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON ++ max_dist = Math.max(max_dist, source_move); ++ } ++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { ++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return max_dist; ++ } ++ } ++ } ++ } ++ ++ return source_move; ++ } ++ } ++ ++ // does not use epsilon ++ public static boolean strictlyContains(final VoxelShape voxel, final Vec3 point) { ++ return strictlyContains(voxel, point.x, point.y, point.z); ++ } ++ ++ // does not use epsilon ++ public static boolean strictlyContains(final VoxelShape voxel, double x, double y, double z) { ++ final AABB single_aabb = voxel.getSingleAABBRepresentation(); ++ if (single_aabb != null) { ++ return single_aabb.contains(x, y, z); ++ } ++ ++ if (voxel.isEmpty()) { ++ // bitset is clear, no point in searching ++ return false; ++ } ++ ++ // offset input ++ x -= voxel.offsetX(); ++ y -= voxel.offsetY(); ++ z -= voxel.offsetZ(); ++ ++ final double[] coords_x = voxel.rootCoordinatesX(); ++ final double[] coords_y = voxel.rootCoordinatesY(); ++ final double[] coords_z = voxel.rootCoordinatesZ(); ++ ++ final CachedShapeData cached_shape_data = voxel.getCachedVoxelData(); ++ ++ // note: size = coords.length - 1 ++ final int size_x = cached_shape_data.sizeX(); ++ final int size_y = cached_shape_data.sizeY(); ++ final int size_z = cached_shape_data.sizeZ(); ++ ++ // note: should mirror AABB#contains, which is that for any point X that X >= min and X < max. ++ // specifically, it cannot collide on the max bounds of the shape ++ ++ final int index_x = findFloor(coords_x, x, 0, size_x); ++ if (index_x < 0 || index_x >= size_x) { ++ return false; ++ } ++ ++ final int index_y = findFloor(coords_y, y, 0, size_y); ++ if (index_y < 0 || index_y >= size_y) { ++ return false; ++ } ++ ++ final int index_z = findFloor(coords_z, z, 0, size_z); ++ if (index_z < 0 || index_z >= size_z) { ++ return false; ++ } ++ ++ // index = z + y*size_z + x*(size_z*size_y) ++ ++ final int index = index_z + index_y*size_z + index_x*(size_z*size_y); ++ ++ final long[] bitset = cached_shape_data.voxelSet(); ++ ++ return (bitset[index >>> 6] & (1L << index)) != 0L; ++ } ++ ++ private static int makeBitset(final boolean ft, final boolean tf, final boolean tt) { ++ // idx ff -> 0 ++ // idx ft -> 1 ++ // idx tf -> 2 ++ // idx tt -> 3 ++ return ((ft ? 1 : 0) << 1) | ((tf ? 1 : 0) << 2) | ((tt ? 1 : 0) << 3); ++ } ++ ++ private static BitSetDiscreteVoxelShape merge(final CachedShapeData shapeDataFirst, final CachedShapeData shapeDataSecond, ++ final MergedVoxelCoordinateList mergedX, final MergedVoxelCoordinateList mergedY, ++ final MergedVoxelCoordinateList mergedZ, ++ final int booleanOp) { ++ final int sizeX = mergedX.voxels; ++ final int sizeY = mergedY.voxels; ++ final int sizeZ = mergedZ.voxels; ++ ++ final long[] s1Voxels = shapeDataFirst.voxelSet(); ++ final long[] s2Voxels = shapeDataSecond.voxelSet(); ++ ++ final int s1Mul1 = shapeDataFirst.sizeZ(); ++ final int s1Mul2 = s1Mul1 * shapeDataFirst.sizeY(); ++ ++ final int s2Mul1 = shapeDataSecond.sizeZ(); ++ final int s2Mul2 = s2Mul1 * shapeDataSecond.sizeY(); ++ ++ // note: indices may contain -1, but nothing > size ++ final BitSetDiscreteVoxelShape ret = new BitSetDiscreteVoxelShape(sizeX, sizeY, sizeZ); ++ ++ boolean empty = true; ++ ++ int mergedIdx = 0; ++ for (int idxX = 0; idxX < sizeX; ++idxX) { ++ final int s1x = mergedX.firstIndices[idxX]; ++ final int s2x = mergedX.secondIndices[idxX]; ++ boolean setX = false; ++ for (int idxY = 0; idxY < sizeY; ++idxY) { ++ final int s1y = mergedY.firstIndices[idxY]; ++ final int s2y = mergedY.secondIndices[idxY]; ++ boolean setY = false; ++ for (int idxZ = 0; idxZ < sizeZ; ++idxZ) { ++ final int s1z = mergedZ.firstIndices[idxZ]; ++ final int s2z = mergedZ.secondIndices[idxZ]; ++ ++ int idx; ++ ++ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L); ++ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L); ++ ++ // idx ff -> 0 ++ // idx ft -> 1 ++ // idx tf -> 2 ++ // idx tt -> 3 ++ ++ final boolean res = (booleanOp & (1 << (isS2Full | (isS1Full << 1)))) != 0; ++ setY |= res; ++ setX |= res; ++ ++ if (res) { ++ empty = false; ++ // inline and optimize fill operation ++ ret.zMin = Math.min(ret.zMin, idxZ); ++ ret.zMax = Math.max(ret.zMax, idxZ + 1); ++ ret.storage.set(mergedIdx); ++ } ++ ++ ++mergedIdx; ++ } ++ if (setY) { ++ ret.yMin = Math.min(ret.yMin, idxY); ++ ret.yMax = Math.max(ret.yMax, idxY + 1); ++ } ++ } ++ if (setX) { ++ ret.xMin = Math.min(ret.xMin, idxX); ++ ret.xMax = Math.max(ret.xMax, idxX + 1); ++ } ++ } ++ ++ return empty ? null : ret; ++ } ++ ++ private static boolean isMergeEmpty(final CachedShapeData shapeDataFirst, final CachedShapeData shapeDataSecond, ++ final MergedVoxelCoordinateList mergedX, final MergedVoxelCoordinateList mergedY, ++ final MergedVoxelCoordinateList mergedZ, ++ final int booleanOp) { ++ final int sizeX = mergedX.voxels; ++ final int sizeY = mergedY.voxels; ++ final int sizeZ = mergedZ.voxels; ++ ++ final long[] s1Voxels = shapeDataFirst.voxelSet(); ++ final long[] s2Voxels = shapeDataSecond.voxelSet(); ++ ++ final int s1Mul1 = shapeDataFirst.sizeZ(); ++ final int s1Mul2 = s1Mul1 * shapeDataFirst.sizeY(); ++ ++ final int s2Mul1 = shapeDataSecond.sizeZ(); ++ final int s2Mul2 = s2Mul1 * shapeDataSecond.sizeY(); ++ ++ // note: indices may contain -1, but nothing > size ++ for (int idxX = 0; idxX < sizeX; ++idxX) { ++ final int s1x = mergedX.firstIndices[idxX]; ++ final int s2x = mergedX.secondIndices[idxX]; ++ for (int idxY = 0; idxY < sizeY; ++idxY) { ++ final int s1y = mergedY.firstIndices[idxY]; ++ final int s2y = mergedY.secondIndices[idxY]; ++ for (int idxZ = 0; idxZ < sizeZ; ++idxZ) { ++ final int s1z = mergedZ.firstIndices[idxZ]; ++ final int s2z = mergedZ.secondIndices[idxZ]; ++ ++ int idx; ++ ++ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L); ++ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L); ++ ++ // idx ff -> 0 ++ // idx ft -> 1 ++ // idx tf -> 2 ++ // idx tt -> 3 ++ ++ final boolean res = (booleanOp & (1 << (isS2Full | (isS1Full << 1)))) != 0; ++ ++ if (res) { ++ return false; ++ } ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ public static VoxelShape joinOptimized(final VoxelShape first, final VoxelShape second, final BooleanOp operator) { ++ return joinUnoptimized(first, second, operator).optimize(); ++ } ++ ++ public static VoxelShape joinUnoptimized(final VoxelShape first, final VoxelShape second, final BooleanOp operator) { ++ final boolean ff = operator.apply(false, false); ++ if (ff) { ++ // technically, should be an infinite box but that's clearly an error ++ throw new UnsupportedOperationException("Ambiguous operator: (false, false) -> true"); ++ } ++ ++ final boolean tt = operator.apply(true, true); ++ ++ if (first == second) { ++ return tt ? first : Shapes.empty(); ++ } ++ ++ final boolean ft = operator.apply(false, true); ++ final boolean tf = operator.apply(true, false); ++ ++ if (first.isEmpty()) { ++ return ft ? second : Shapes.empty(); ++ } ++ if (second.isEmpty()) { ++ return tf ? first : Shapes.empty(); ++ } ++ ++ if (!tt) { ++ // try to check for no intersection, since tt = false ++ final AABB aabbF = first.getSingleAABBRepresentation(); ++ final AABB aabbS = second.getSingleAABBRepresentation(); ++ ++ final boolean intersect; ++ ++ final boolean hasAABBF = aabbF != null; ++ final boolean hasAABBS = aabbS != null; ++ if (hasAABBF | hasAABBS) { ++ if (hasAABBF & hasAABBS) { ++ intersect = voxelShapeIntersect(aabbF, aabbS); ++ } else if (hasAABBF) { ++ intersect = voxelShapeIntersectNoEmpty(second, aabbF); ++ } else { ++ intersect = voxelShapeIntersectNoEmpty(first, aabbS); ++ } ++ } else { ++ // expect cached bounds ++ intersect = voxelShapeIntersect(first.bounds(), second.bounds()); ++ } ++ ++ if (!intersect) { ++ if (!tf & !ft) { ++ return Shapes.empty(); ++ } ++ if (!tf | !ft) { ++ return tf ? first : second; ++ } ++ } ++ } ++ ++ final MergedVoxelCoordinateList mergedX = MergedVoxelCoordinateList.merge( ++ first.rootCoordinatesX(), first.offsetX(), ++ second.rootCoordinatesX(), second.offsetX(), ++ ft, tf ++ ); ++ if (mergedX == MergedVoxelCoordinateList.EMPTY) { ++ return Shapes.empty(); ++ } ++ final MergedVoxelCoordinateList mergedY = MergedVoxelCoordinateList.merge( ++ first.rootCoordinatesY(), first.offsetY(), ++ second.rootCoordinatesY(), second.offsetY(), ++ ft, tf ++ ); ++ if (mergedY == MergedVoxelCoordinateList.EMPTY) { ++ return Shapes.empty(); ++ } ++ final MergedVoxelCoordinateList mergedZ = MergedVoxelCoordinateList.merge( ++ first.rootCoordinatesZ(), first.offsetZ(), ++ second.rootCoordinatesZ(), second.offsetZ(), ++ ft, tf ++ ); ++ if (mergedZ == MergedVoxelCoordinateList.EMPTY) { ++ return Shapes.empty(); ++ } ++ ++ final CachedShapeData shapeDataFirst = first.getCachedVoxelData(); ++ final CachedShapeData shapeDataSecond = second.getCachedVoxelData(); ++ ++ final BitSetDiscreteVoxelShape mergedShape = merge( ++ shapeDataFirst, shapeDataSecond, ++ mergedX, mergedY, mergedZ, ++ makeBitset(ft, tf, tt) ++ ); ++ ++ if (mergedShape == null) { ++ return Shapes.empty(); ++ } ++ ++ return new ArrayVoxelShape( ++ mergedShape, mergedX.wrapCoords(), mergedY.wrapCoords(), mergedZ.wrapCoords() ++ ); ++ } ++ ++ public static boolean isJoinNonEmpty(final VoxelShape first, final VoxelShape second, final BooleanOp operator) { ++ final boolean ff = operator.apply(false, false); ++ if (ff) { ++ // technically, should be an infinite box but that's clearly an error ++ throw new UnsupportedOperationException("Ambiguous operator: (false, false) -> true"); ++ } ++ final boolean firstEmpty = first.isEmpty(); ++ final boolean secondEmpty = second.isEmpty(); ++ if (firstEmpty | secondEmpty) { ++ return operator.apply(!firstEmpty, !secondEmpty); ++ } ++ ++ final boolean tt = operator.apply(true, true); ++ ++ if (first == second) { ++ return tt; ++ } ++ ++ final boolean ft = operator.apply(false, true); ++ final boolean tf = operator.apply(true, false); ++ ++ // try to check intersection ++ final AABB aabbF = first.getSingleAABBRepresentation(); ++ final AABB aabbS = second.getSingleAABBRepresentation(); ++ ++ final boolean intersect; ++ ++ final boolean hasAABBF = aabbF != null; ++ final boolean hasAABBS = aabbS != null; ++ if (hasAABBF | hasAABBS) { ++ if (hasAABBF & hasAABBS) { ++ intersect = voxelShapeIntersect(aabbF, aabbS); ++ } else if (hasAABBF) { ++ intersect = voxelShapeIntersectNoEmpty(second, aabbF); ++ } else { ++ // hasAABBS -> true ++ intersect = voxelShapeIntersectNoEmpty(first, aabbS); ++ } ++ ++ if (!intersect) { ++ // is only non-empty if we take from first or second, as there is no overlap AND both shapes are non-empty ++ return tf | ft; ++ } else if (tt) { ++ // intersect = true && tt = true -> non-empty merged shape ++ return true; ++ } ++ } else { ++ // expect cached bounds ++ intersect = voxelShapeIntersect(first.bounds(), second.bounds()); ++ if (!intersect) { ++ // is only non-empty if we take from first or second, as there is no intersection ++ return tf | ft; ++ } ++ } ++ ++ final MergedVoxelCoordinateList mergedX = MergedVoxelCoordinateList.merge( ++ first.rootCoordinatesX(), first.offsetX(), ++ second.rootCoordinatesX(), second.offsetX(), ++ ft, tf ++ ); ++ if (mergedX == MergedVoxelCoordinateList.EMPTY) { ++ return false; ++ } ++ final MergedVoxelCoordinateList mergedY = MergedVoxelCoordinateList.merge( ++ first.rootCoordinatesY(), first.offsetY(), ++ second.rootCoordinatesY(), second.offsetY(), ++ ft, tf ++ ); ++ if (mergedY == MergedVoxelCoordinateList.EMPTY) { ++ return false; ++ } ++ final MergedVoxelCoordinateList mergedZ = MergedVoxelCoordinateList.merge( ++ first.rootCoordinatesZ(), first.offsetZ(), ++ second.rootCoordinatesZ(), second.offsetZ(), ++ ft, tf ++ ); ++ if (mergedZ == MergedVoxelCoordinateList.EMPTY) { ++ return false; ++ } ++ ++ final CachedShapeData shapeDataFirst = first.getCachedVoxelData(); ++ final CachedShapeData shapeDataSecond = second.getCachedVoxelData(); ++ ++ return !isMergeEmpty( ++ shapeDataFirst, shapeDataSecond, ++ mergedX, mergedY, mergedZ, ++ makeBitset(ft, tf, tt) ++ ); ++ } ++ ++ private static final class MergedVoxelCoordinateList { ++ ++ private static final int[][] SIMPLE_INDICES_CACHE = new int[64][]; ++ static { ++ for (int i = 0; i < SIMPLE_INDICES_CACHE.length; ++i) { ++ SIMPLE_INDICES_CACHE[i] = getIndices(i); ++ } ++ } ++ ++ private static final MergedVoxelCoordinateList EMPTY = new MergedVoxelCoordinateList( ++ new double[] { 0.0 }, 0.0, new int[0], new int[0], 0 ++ ); ++ ++ private static int[] getIndices(final int length) { ++ final int[] ret = new int[length]; ++ ++ for (int i = 1; i < length; ++i) { ++ ret[i] = i; ++ } ++ ++ return ret; ++ } ++ ++ // indices above voxel size are always set to -1 ++ public final double[] coordinates; ++ public final double coordinateOffset; ++ public final int[] firstIndices; ++ public final int[] secondIndices; ++ public final int voxels; ++ ++ private MergedVoxelCoordinateList(final double[] coordinates, final double coordinateOffset, ++ final int[] firstIndices, final int[] secondIndices, final int voxels) { ++ this.coordinates = coordinates; ++ this.coordinateOffset = coordinateOffset; ++ this.firstIndices = firstIndices; ++ this.secondIndices = secondIndices; ++ this.voxels = voxels; ++ } ++ ++ public DoubleList wrapCoords() { ++ if (this.coordinateOffset == 0.0) { ++ return DoubleArrayList.wrap(this.coordinates, this.voxels + 1); ++ } ++ return new OffsetDoubleList(DoubleArrayList.wrap(this.coordinates, this.voxels + 1), this.coordinateOffset); ++ } ++ ++ // assume coordinates.length > 1 ++ public static MergedVoxelCoordinateList getForSingle(final double[] coordinates, final double offset) { ++ final int voxels = coordinates.length - 1; ++ final int[] indices = voxels < SIMPLE_INDICES_CACHE.length ? SIMPLE_INDICES_CACHE[voxels] : getIndices(voxels); ++ ++ return new MergedVoxelCoordinateList(coordinates, offset, indices, indices, voxels); ++ } ++ ++ // assume coordinates.length > 1 ++ public static MergedVoxelCoordinateList merge(final double[] firstCoordinates, final double firstOffset, ++ final double[] secondCoordinates, final double secondOffset, ++ final boolean ft, final boolean tf) { ++ if (firstCoordinates == secondCoordinates && firstOffset == secondOffset) { ++ return getForSingle(firstCoordinates, firstOffset); ++ } ++ ++ final int firstCount = firstCoordinates.length; ++ final int secondCount = secondCoordinates.length; ++ ++ final int voxelsFirst = firstCount - 1; ++ final int voxelsSecond = secondCount - 1; ++ ++ final int maxCount = firstCount + secondCount; ++ ++ final double[] coordinates = new double[maxCount]; ++ final int[] firstIndices = new int[maxCount]; ++ final int[] secondIndices = new int[maxCount]; ++ ++ final boolean notTF = !tf; ++ final boolean notFT = !ft; ++ ++ int firstIndex = 0; ++ int secondIndex = 0; ++ int resultSize = 0; ++ ++ // note: operations on NaN are false ++ double last = Double.NaN; ++ ++ for (;;) { ++ final boolean noneLeftFirst = firstIndex >= firstCount; ++ final boolean noneLeftSecond = secondIndex >= secondCount; ++ ++ if ((noneLeftFirst & noneLeftSecond) | (noneLeftSecond & notTF) | (noneLeftFirst & notFT)) { ++ break; ++ } ++ ++ final boolean firstZero = firstIndex == 0; ++ final boolean secondZero = secondIndex == 0; ++ ++ final double select; ++ ++ if (noneLeftFirst) { ++ // noneLeftSecond -> false ++ // notFT -> false ++ select = secondCoordinates[secondIndex] + secondOffset; ++ ++secondIndex; ++ } else if (noneLeftSecond) { ++ // noneLeftFirst -> false ++ // notTF -> false ++ select = firstCoordinates[firstIndex] + firstOffset; ++ ++firstIndex; ++ } else { ++ // noneLeftFirst | noneLeftSecond -> false ++ // notTF -> ?? ++ // notFT -> ?? ++ final boolean breakFirst = notTF & secondZero; ++ final boolean breakSecond = notFT & firstZero; ++ ++ final double first = firstCoordinates[firstIndex] + firstOffset; ++ final double second = secondCoordinates[secondIndex] + secondOffset; ++ final boolean useFirst = first < (second + COLLISION_EPSILON); ++ final boolean cont = (useFirst & breakFirst) | (!useFirst & breakSecond); ++ ++ select = useFirst ? first : second; ++ firstIndex += useFirst ? 1 : 0; ++ secondIndex += 1 ^ (useFirst ? 1 : 0); ++ ++ if (cont) { ++ continue; ++ } ++ } ++ ++ int prevFirst = firstIndex - 1; ++ prevFirst = prevFirst >= voxelsFirst ? -1 : prevFirst; ++ int prevSecond = secondIndex - 1; ++ prevSecond = prevSecond >= voxelsSecond ? -1 : prevSecond; ++ ++ if (last >= (select - COLLISION_EPSILON)) { ++ // note: any operations on NaN is false ++ firstIndices[resultSize - 1] = prevFirst; ++ secondIndices[resultSize - 1] = prevSecond; ++ } else { ++ firstIndices[resultSize] = prevFirst; ++ secondIndices[resultSize] = prevSecond; ++ coordinates[resultSize] = select; ++ ++ ++resultSize; ++ last = select; ++ } ++ } ++ ++ return resultSize <= 1 ? EMPTY : new MergedVoxelCoordinateList(coordinates, 0.0, firstIndices, secondIndices, resultSize - 1); ++ } ++ } ++ ++ public static AABB offsetX(final AABB box, final double dx) { ++ return new AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); ++ } ++ ++ public static AABB offsetY(final AABB box, final double dy) { ++ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); ++ } ++ ++ public static AABB offsetZ(final AABB box, final double dz) { ++ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz, false); ++ } ++ ++ public static AABB expandRight(final AABB box, final double dx) { // dx > 0.0 ++ return new AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); ++ } ++ ++ public static AABB expandLeft(final AABB box, final double dx) { // dx < 0.0 ++ return new AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); ++ } ++ ++ public static AABB expandUpwards(final AABB box, final double dy) { // dy > 0.0 ++ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); ++ } ++ ++ public static AABB expandDownwards(final AABB box, final double dy) { // dy < 0.0 ++ return new AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ, false); ++ } ++ ++ public static AABB expandForwards(final AABB box, final double dz) { // dz > 0.0 ++ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz, false); ++ } ++ ++ public static AABB expandBackwards(final AABB box, final double dz) { // dz < 0.0 ++ return new AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ, false); ++ } ++ ++ public static AABB cutRight(final AABB box, final double dx) { // dx > 0.0 ++ return new AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); ++ } ++ ++ public static AABB cutLeft(final AABB box, final double dx) { // dx < 0.0 ++ return new AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ, false); ++ } ++ ++ public static AABB cutUpwards(final AABB box, final double dy) { // dy > 0.0 ++ return new AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); ++ } ++ ++ public static AABB cutDownwards(final AABB box, final double dy) { // dy < 0.0 ++ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ, false); ++ } ++ ++ public static AABB cutForwards(final AABB box, final double dz) { // dz > 0.0 ++ return new AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz, false); ++ } ++ ++ public static AABB cutBackwards(final AABB box, final double dz) { // dz < 0.0 ++ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ, false); ++ } ++ ++ public static double performAABBCollisionsX(final AABB currentBoundingBox, double value, final List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ if (Math.abs(value) < COLLISION_EPSILON) { ++ return 0.0; ++ } ++ final AABB target = potentialCollisions.get(i); ++ value = collideX(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static double performAABBCollisionsY(final AABB currentBoundingBox, double value, final List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ if (Math.abs(value) < COLLISION_EPSILON) { ++ return 0.0; ++ } ++ final AABB target = potentialCollisions.get(i); ++ value = collideY(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static double performAABBCollisionsZ(final AABB currentBoundingBox, double value, final List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ if (Math.abs(value) < COLLISION_EPSILON) { ++ return 0.0; ++ } ++ final AABB target = potentialCollisions.get(i); ++ value = collideZ(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static double performVoxelCollisionsX(final AABB currentBoundingBox, double value, final List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ if (Math.abs(value) < COLLISION_EPSILON) { ++ return 0.0; ++ } ++ final VoxelShape target = potentialCollisions.get(i); ++ value = collideX(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static double performVoxelCollisionsY(final AABB currentBoundingBox, double value, final List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ if (Math.abs(value) < COLLISION_EPSILON) { ++ return 0.0; ++ } ++ final VoxelShape target = potentialCollisions.get(i); ++ value = collideY(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static double performVoxelCollisionsZ(final AABB currentBoundingBox, double value, final List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ if (Math.abs(value) < COLLISION_EPSILON) { ++ return 0.0; ++ } ++ final VoxelShape target = potentialCollisions.get(i); ++ value = collideZ(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static Vec3 performVoxelCollisions(final Vec3 moveVector, AABB axisalignedbb, final List potentialCollisions) { ++ double x = moveVector.x; ++ double y = moveVector.y; ++ double z = moveVector.z; ++ ++ if (y != 0.0) { ++ y = performVoxelCollisionsY(axisalignedbb, y, potentialCollisions); ++ if (y != 0.0) { ++ axisalignedbb = offsetY(axisalignedbb, y); ++ } ++ } ++ ++ final boolean xSmaller = Math.abs(x) < Math.abs(z); ++ ++ if (xSmaller && z != 0.0) { ++ z = performVoxelCollisionsZ(axisalignedbb, z, potentialCollisions); ++ if (z != 0.0) { ++ axisalignedbb = offsetZ(axisalignedbb, z); ++ } ++ } ++ ++ if (x != 0.0) { ++ x = performVoxelCollisionsX(axisalignedbb, x, potentialCollisions); ++ if (!xSmaller && x != 0.0) { ++ axisalignedbb = offsetX(axisalignedbb, x); ++ } ++ } ++ ++ if (!xSmaller && z != 0.0) { ++ z = performVoxelCollisionsZ(axisalignedbb, z, potentialCollisions); ++ } ++ ++ return new Vec3(x, y, z); ++ } ++ ++ public static Vec3 performAABBCollisions(final Vec3 moveVector, AABB axisalignedbb, final List potentialCollisions) { ++ double x = moveVector.x; ++ double y = moveVector.y; ++ double z = moveVector.z; ++ ++ if (y != 0.0) { ++ y = performAABBCollisionsY(axisalignedbb, y, potentialCollisions); ++ if (y != 0.0) { ++ axisalignedbb = offsetY(axisalignedbb, y); ++ } ++ } ++ ++ final boolean xSmaller = Math.abs(x) < Math.abs(z); ++ ++ if (xSmaller && z != 0.0) { ++ z = performAABBCollisionsZ(axisalignedbb, z, potentialCollisions); ++ if (z != 0.0) { ++ axisalignedbb = offsetZ(axisalignedbb, z); ++ } ++ } ++ ++ if (x != 0.0) { ++ x = performAABBCollisionsX(axisalignedbb, x, potentialCollisions); ++ if (!xSmaller && x != 0.0) { ++ axisalignedbb = offsetX(axisalignedbb, x); ++ } ++ } ++ ++ if (!xSmaller && z != 0.0) { ++ z = performAABBCollisionsZ(axisalignedbb, z, potentialCollisions); ++ } ++ ++ return new Vec3(x, y, z); ++ } ++ ++ public static Vec3 performCollisions(final Vec3 moveVector, AABB axisalignedbb, ++ final List voxels, ++ final List aabbs) { ++ if (voxels.isEmpty()) { ++ // fast track only AABBs ++ return performAABBCollisions(moveVector, axisalignedbb, aabbs); ++ } ++ ++ double x = moveVector.x; ++ double y = moveVector.y; ++ double z = moveVector.z; ++ ++ if (y != 0.0) { ++ y = performAABBCollisionsY(axisalignedbb, y, aabbs); ++ y = performVoxelCollisionsY(axisalignedbb, y, voxels); ++ if (y != 0.0) { ++ axisalignedbb = offsetY(axisalignedbb, y); ++ } ++ } ++ ++ final boolean xSmaller = Math.abs(x) < Math.abs(z); ++ ++ if (xSmaller && z != 0.0) { ++ z = performAABBCollisionsZ(axisalignedbb, z, aabbs); ++ z = performVoxelCollisionsZ(axisalignedbb, z, voxels); ++ if (z != 0.0) { ++ axisalignedbb = offsetZ(axisalignedbb, z); ++ } ++ } ++ ++ if (x != 0.0) { ++ x = performAABBCollisionsX(axisalignedbb, x, aabbs); ++ x = performVoxelCollisionsX(axisalignedbb, x, voxels); ++ if (!xSmaller && x != 0.0) { ++ axisalignedbb = offsetX(axisalignedbb, x); ++ } ++ } ++ ++ if (!xSmaller && z != 0.0) { ++ z = performAABBCollisionsZ(axisalignedbb, z, aabbs); ++ z = performVoxelCollisionsZ(axisalignedbb, z, voxels); ++ } ++ ++ return new Vec3(x, y, z); ++ } ++ ++ public static boolean isCollidingWithBorder(final WorldBorder worldborder, final AABB boundingBox) { ++ return isCollidingWithBorder(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); ++ } ++ ++ public static boolean isCollidingWithBorder(final WorldBorder worldborder, final double boxMinX, final double boxMaxX, ++ final double boxMinZ, final double boxMaxZ) { ++ // border size is rounded like the collide voxel shape of the border ++ final double borderMinX = Math.floor(worldborder.getMinX()); // -X ++ final double borderMaxX = Math.ceil(worldborder.getMaxX()); // +X ++ ++ final double borderMinZ = Math.floor(worldborder.getMinZ()); // -Z ++ final double borderMaxZ = Math.ceil(worldborder.getMaxZ()); // +Z ++ ++ // inverted check for world border enclosing the specified box expanded by -EPSILON ++ return (borderMinX - boxMinX) > CollisionUtil.COLLISION_EPSILON || (borderMaxX - boxMaxX) < -CollisionUtil.COLLISION_EPSILON || ++ (borderMinZ - boxMinZ) > CollisionUtil.COLLISION_EPSILON || (borderMaxZ - boxMaxZ) < -CollisionUtil.COLLISION_EPSILON; ++ } ++ ++ /* Math.max/min specify that any NaN argument results in a NaN return, unlike these functions */ ++ private static double min(final double x, final double y) { ++ return x < y ? x : y; ++ } ++ ++ private static double max(final double x, final double y) { ++ return x > y ? x : y; ++ } ++ ++ public static final int COLLISION_FLAG_LOAD_CHUNKS = 1 << 0; ++ public static final int COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS = 1 << 1; ++ public static final int COLLISION_FLAG_CHECK_BORDER = 1 << 2; ++ public static final int COLLISION_FLAG_CHECK_ONLY = 1 << 3; ++ ++ public static boolean getCollisionsForBlocksOrWorldBorder(final Level world, final Entity entity, final AABB aabb, ++ final List intoVoxel, final List intoAABB, ++ final int collisionFlags, final BiPredicate predicate) { ++ final boolean checkOnly = (collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0; ++ boolean ret = false; ++ ++ if ((collisionFlags & COLLISION_FLAG_CHECK_BORDER) != 0) { ++ final WorldBorder worldBorder = world.getWorldBorder(); ++ if (CollisionUtil.isCollidingWithBorder(worldBorder, aabb) && entity != null && worldBorder.isInsideCloseToBorder(entity, aabb)) { ++ if (checkOnly) { ++ return true; ++ } else { ++ final VoxelShape borderShape = worldBorder.getCollisionShape(); ++ intoVoxel.add(borderShape); ++ ret = true; ++ } ++ } ++ } ++ ++ final int minSection = world.minSection; ++ ++ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; ++ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; ++ ++ final int minBlockY = Math.max((minSection << 4) - 1, Mth.floor(aabb.minY - COLLISION_EPSILON) - 1); ++ final int maxBlockY = Math.min((world.maxSection << 4) + 16, Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1); ++ ++ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; ++ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1; ++ ++ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); ++ final CollisionContext collisionShape = new LazyEntityCollisionContext(entity); ++ ++ // special cases: ++ if (minBlockY > maxBlockY) { ++ // no point in checking ++ return ret; ++ } ++ ++ final int minChunkX = minBlockX >> 4; ++ final int maxChunkX = maxBlockX >> 4; ++ ++ final int minChunkY = minBlockY >> 4; ++ final int maxChunkY = maxBlockY >> 4; ++ ++ final int minChunkZ = minBlockZ >> 4; ++ final int maxChunkZ = maxBlockZ >> 4; ++ ++ final boolean loadChunks = (collisionFlags & COLLISION_FLAG_LOAD_CHUNKS) != 0; ++ final ServerChunkCache chunkSource = (ServerChunkCache)world.getChunkSource(); ++ ++ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { ++ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { ++ final ChunkAccess chunk = loadChunks ? chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, true) : chunkSource.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); ++ ++ if (chunk == null) { ++ if ((collisionFlags & COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS) != 0) { ++ if (checkOnly) { ++ return true; ++ } else { ++ intoAABB.add(getBoxForChunk(currChunkX, currChunkZ)); ++ ret = true; ++ } ++ } ++ continue; ++ } ++ ++ final LevelChunkSection[] sections = chunk.getSections(); ++ ++ // bound y ++ for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) { ++ final int sectionIdx = currChunkY - minSection; ++ if (sectionIdx < 0 || sectionIdx >= sections.length) { ++ continue; ++ } ++ final LevelChunkSection section = sections[sectionIdx]; ++ if (section == null || section.hasOnlyAir()) { ++ // empty ++ continue; ++ } ++ ++ final boolean hasSpecial = section.getSpecialCollidingBlocks() != 0; ++ final int sectionAdjust = !hasSpecial ? 1 : 0; ++ ++ final PalettedContainer blocks = section.states; ++ ++ final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) + sectionAdjust : 0; ++ final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) - sectionAdjust : 15; ++ final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) + sectionAdjust : 0; ++ final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) - sectionAdjust : 15; ++ final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) + sectionAdjust : 0; ++ final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) - sectionAdjust : 15; ++ ++ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { ++ final int blockY = currY | (currChunkY << 4); ++ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) { ++ final int blockZ = currZ | (currChunkZ << 4); ++ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { ++ final int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8); ++ final int blockX = currX | (currChunkX << 4); ++ ++ final int edgeCount = hasSpecial ? ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + ++ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + ++ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0) : 0; ++ if (edgeCount == 3) { ++ continue; ++ } ++ ++ final BlockState blockData = blocks.get(localBlockIndex); ++ ++ if (blockData.emptyCollisionShape()) { ++ continue; ++ } ++ ++ if (edgeCount == 0 || ((edgeCount != 1 || blockData.hasLargeCollisionShape()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON))) { ++ VoxelShape blockCollision = blockData.getConstantCollisionShape(); ++ ++ if (blockCollision == null) { ++ mutablePos.set(blockX, blockY, blockZ); ++ blockCollision = blockData.getCollisionShape(world, mutablePos, collisionShape); ++ } ++ ++ AABB singleAABB = blockCollision.getSingleAABBRepresentation(); ++ if (singleAABB != null) { ++ singleAABB = singleAABB.move((double)blockX, (double)blockY, (double)blockZ); ++ if (!voxelShapeIntersect(aabb, singleAABB)) { ++ continue; ++ } ++ ++ if (predicate != null) { ++ mutablePos.set(blockX, blockY, blockZ); ++ if (!predicate.test(blockData, mutablePos)) { ++ continue; ++ } ++ } ++ ++ if (checkOnly) { ++ return true; ++ } else { ++ ret = true; ++ intoAABB.add(singleAABB); ++ continue; ++ } ++ } ++ ++ if (blockCollision.isEmpty()) { ++ continue; ++ } ++ ++ final VoxelShape blockCollisionOffset = blockCollision.move((double)blockX, (double)blockY, (double)blockZ); ++ ++ if (!voxelShapeIntersectNoEmpty(blockCollisionOffset, aabb)) { ++ continue; ++ } ++ ++ if (predicate != null) { ++ mutablePos.set(blockX, blockY, blockZ); ++ if (!predicate.test(blockData, mutablePos)) { ++ continue; ++ } ++ } ++ ++ if (checkOnly) { ++ return true; ++ } else { ++ ret = true; ++ intoVoxel.add(blockCollisionOffset); ++ continue; ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ public static boolean getEntityHardCollisions(final CollisionGetter getter, final Entity entity, AABB aabb, ++ final List into, final int collisionFlags, final Predicate predicate) { ++ final boolean checkOnly = (collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0; ++ if (!(getter instanceof EntityGetter entityGetter)) { ++ return false; ++ } ++ ++ boolean ret = false; ++ ++ // to comply with vanilla intersection rules, expand by -epsilon so that we only get stuff we definitely collide with. ++ // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems ++ // specifically with boat collisions. ++ aabb = aabb.inflate(-COLLISION_EPSILON, -COLLISION_EPSILON, -COLLISION_EPSILON); ++ final List entities; ++ if (entity != null && entity.hardCollides()) { ++ entities = entityGetter.getEntities(entity, aabb, predicate); ++ } else { ++ entities = entityGetter.getHardCollidingEntities(entity, aabb, predicate); ++ } ++ ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ final Entity otherEntity = entities.get(i); ++ ++ if (otherEntity.isSpectator()) { ++ continue; ++ } ++ ++ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) { ++ if (checkOnly) { ++ return true; ++ } else { ++ into.add(otherEntity.getBoundingBox()); ++ ret = true; ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ public static boolean getCollisions(final Level world, final Entity entity, final AABB aabb, ++ final List intoVoxel, final List intoAABB, final int collisionFlags, ++ final BiPredicate blockPredicate, ++ final Predicate entityPredicate) { ++ if ((collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0) { ++ return getCollisionsForBlocksOrWorldBorder(world, entity, aabb, intoVoxel, intoAABB, collisionFlags, blockPredicate) ++ || getEntityHardCollisions(world, entity, aabb, intoAABB, collisionFlags, entityPredicate); ++ } else { ++ return getCollisionsForBlocksOrWorldBorder(world, entity, aabb, intoVoxel, intoAABB, collisionFlags, blockPredicate) ++ | getEntityHardCollisions(world, entity, aabb, intoAABB, collisionFlags, entityPredicate); ++ } ++ } ++ ++ public static final class LazyEntityCollisionContext extends EntityCollisionContext { ++ ++ private CollisionContext delegate; ++ private boolean delegated; ++ ++ public LazyEntityCollisionContext(final Entity entity) { ++ super(false, 0.0, null, null, entity); ++ } ++ ++ public boolean isDelegated() { ++ final boolean delegated = this.delegated; ++ this.delegated = false; ++ return delegated; ++ } ++ ++ public CollisionContext getDelegate() { ++ this.delegated = true; ++ final Entity entity = this.getEntity(); ++ return this.delegate == null ? this.delegate = (entity == null ? CollisionContext.empty() : CollisionContext.of(entity)) : this.delegate; ++ } ++ ++ @Override ++ public boolean isDescending() { ++ return this.getDelegate().isDescending(); ++ } ++ ++ @Override ++ public boolean isAbove(final VoxelShape shape, final BlockPos pos, final boolean defaultValue) { ++ return this.getDelegate().isAbove(shape, pos, defaultValue); ++ } ++ ++ @Override ++ public boolean isHoldingItem(final Item item) { ++ return this.getDelegate().isHoldingItem(item); ++ } ++ ++ @Override ++ public boolean canStandOnFluid(final FluidState state, final FluidState fluidState) { ++ return this.getDelegate().canStandOnFluid(state, fluidState); ++ } ++ } ++ ++ private CollisionUtil() { ++ throw new RuntimeException(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/util/collisions/CachedShapeData.java b/src/main/java/io/papermc/paper/util/collisions/CachedShapeData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1cb96b09375770f92f3e494ce2f28d9ff8699581 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/collisions/CachedShapeData.java +@@ -0,0 +1,10 @@ ++package io.papermc.paper.util.collisions; ++ ++public record CachedShapeData( ++ int sizeX, int sizeY, int sizeZ, ++ long[] voxelSet, ++ int minFullX, int minFullY, int minFullZ, ++ int maxFullX, int maxFullY, int maxFullZ, ++ boolean isEmpty, boolean hasSingleAABB ++) { ++} +diff --git a/src/main/java/io/papermc/paper/util/collisions/CachedToAABBs.java b/src/main/java/io/papermc/paper/util/collisions/CachedToAABBs.java +new file mode 100644 +index 0000000000000000000000000000000000000000..85c448a775f60ca4b4a4f2baf17487ef45bdd383 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/collisions/CachedToAABBs.java +@@ -0,0 +1,39 @@ ++package io.papermc.paper.util.collisions; ++ ++import net.minecraft.world.phys.AABB; ++import java.util.ArrayList; ++import java.util.List; ++ ++public record CachedToAABBs( ++ List aabbs, ++ boolean isOffset, ++ double offX, double offY, double offZ ++) { ++ ++ public CachedToAABBs removeOffset() { ++ final List toOffset = this.aabbs; ++ final double offX = this.offX; ++ final double offY = this.offY; ++ final double offZ = this.offZ; ++ ++ final List ret = new ArrayList<>(toOffset.size()); ++ ++ for (int i = 0, len = toOffset.size(); i < len; ++i) { ++ ret.add(toOffset.get(i).move(offX, offY, offZ)); ++ } ++ ++ return new CachedToAABBs(ret, false, 0.0, 0.0, 0.0); ++ } ++ ++ public static CachedToAABBs offset(final CachedToAABBs cache, final double offX, final double offY, final double offZ) { ++ if (offX == 0.0 && offY == 0.0 && offZ == 0.0) { ++ return cache; ++ } ++ ++ final double resX = cache.offX + offX; ++ final double resY = cache.offY + offY; ++ final double resZ = cache.offZ + offZ; ++ ++ return new CachedToAABBs(cache.aabbs, true, resX, resY, resZ); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/util/collisions/FlatBitsetUtil.java b/src/main/java/io/papermc/paper/util/collisions/FlatBitsetUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ff9d2dad39dcc02b2371458b7b5f64c6090e8012 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/collisions/FlatBitsetUtil.java +@@ -0,0 +1,109 @@ ++package io.papermc.paper.util.collisions; ++ ++import java.util.Objects; ++ ++public final class FlatBitsetUtil { ++ ++ private static final int LOG2_LONG = 6; ++ private static final long ALL_SET = -1L; ++ private static final int BITS_PER_LONG = Long.SIZE; ++ ++ // from inclusive ++ // to exclusive ++ public static int firstSet(final long[] bitset, final int from, final int to) { ++ if ((from | to | (to - from)) < 0) { ++ throw new IndexOutOfBoundsException(); ++ } ++ ++ int bitsetIdx = from >>> LOG2_LONG; ++ int bitIdx = from & ~(BITS_PER_LONG - 1); ++ ++ long tmp = bitset[bitsetIdx] & (ALL_SET << from); ++ for (;;) { ++ if (tmp != 0L) { ++ final int ret = bitIdx | Long.numberOfTrailingZeros(tmp); ++ return ret >= to ? -1 : ret; ++ } ++ ++ bitIdx += BITS_PER_LONG; ++ ++ if (bitIdx >= to) { ++ return -1; ++ } ++ ++ tmp = bitset[++bitsetIdx]; ++ } ++ } ++ ++ // from inclusive ++ // to exclusive ++ public static int firstClear(final long[] bitset, final int from, final int to) { ++ if ((from | to | (to - from)) < 0) { ++ throw new IndexOutOfBoundsException(); ++ } ++ // like firstSet, but invert the bitset ++ ++ int bitsetIdx = from >>> LOG2_LONG; ++ int bitIdx = from & ~(BITS_PER_LONG - 1); ++ ++ long tmp = (~bitset[bitsetIdx]) & (ALL_SET << from); ++ for (;;) { ++ if (tmp != 0L) { ++ final int ret = bitIdx | Long.numberOfTrailingZeros(tmp); ++ return ret >= to ? -1 : ret; ++ } ++ ++ bitIdx += BITS_PER_LONG; ++ ++ if (bitIdx >= to) { ++ return -1; ++ } ++ ++ tmp = ~bitset[++bitsetIdx]; ++ } ++ } ++ ++ // from inclusive ++ // to exclusive ++ public static void clearRange(final long[] bitset, final int from, int to) { ++ if ((from | to | (to - from)) < 0) { ++ throw new IndexOutOfBoundsException(); ++ } ++ ++ if (from == to) { ++ return; ++ } ++ ++ --to; ++ ++ final int fromBitsetIdx = from >>> LOG2_LONG; ++ final int toBitsetIdx = to >>> LOG2_LONG; ++ ++ final long keepFirst = ~(ALL_SET << from); ++ final long keepLast = ~(ALL_SET >>> ((BITS_PER_LONG - 1) ^ to)); ++ ++ Objects.checkFromToIndex(fromBitsetIdx, toBitsetIdx, bitset.length); ++ ++ if (fromBitsetIdx == toBitsetIdx) { ++ // special case: need to keep both first and last ++ bitset[fromBitsetIdx] &= (keepFirst | keepLast); ++ } else { ++ bitset[fromBitsetIdx] &= keepFirst; ++ ++ for (int i = fromBitsetIdx + 1; i < toBitsetIdx; ++i) { ++ bitset[i] = 0L; ++ } ++ ++ bitset[toBitsetIdx] &= keepLast; ++ } ++ } ++ ++ // from inclusive ++ // to exclusive ++ public static boolean isRangeSet(final long[] bitset, final int from, final int to) { ++ return firstClear(bitset, from, to) == -1; ++ } ++ ++ ++ private FlatBitsetUtil() {} ++} +diff --git a/src/main/java/io/papermc/paper/util/collisions/MergedORCache.java b/src/main/java/io/papermc/paper/util/collisions/MergedORCache.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1f42bdfdb052056e62a939ab0d1944f8a064fe6c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/collisions/MergedORCache.java +@@ -0,0 +1,10 @@ ++package io.papermc.paper.util.collisions; ++ ++import net.minecraft.world.phys.shapes.VoxelShape; ++ ++public record MergedORCache( ++ VoxelShape key, ++ VoxelShape result ++) { ++ ++} +diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java +index 073c717bb676b9e99aada00c349fb7eee91df1e7..2a9fc1f1dfc0c5894c1e74dad5a79ae9b02ac74f 100644 +--- a/src/main/java/net/minecraft/core/Direction.java ++++ b/src/main/java/net/minecraft/core/Direction.java +@@ -57,6 +57,21 @@ public enum Direction implements StringRepresentable { + private final int adjY; + private final int adjZ; + // Paper end - Perf: Inline shift direction fields ++ // Paper start - optimise collisions ++ private static final int RANDOM_OFFSET = 2017601568; ++ private Direction opposite; ++ static { ++ for (final Direction direction : VALUES) { ++ direction.opposite = from3DDataValue(direction.oppositeIndex); ++ } ++ } ++ ++ private final int id = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(this.ordinal() + RANDOM_OFFSET) + RANDOM_OFFSET); ++ ++ public final int uniqueId() { ++ return this.id; ++ } ++ // Paper end - optimise collisions + + private Direction(int id, int idOpposite, int idHorizontal, String name, Direction.AxisDirection direction, Direction.Axis axis, Vec3i vector) { + this.data3d = id; +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index acc9858e0cf10cb2aae0554037096411a208bd05..c99d2f2d64b73179e4e27b63030e26a07953041b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -496,7 +496,7 @@ public class ServerPlayer extends Player { + + if (blockposition1 != null) { + this.moveTo(blockposition1, world.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored +- if (world.noCollision((Entity) this)) { ++ if (world.noCollision(this, this.getBoundingBox(), true)) { // Paper - make sure this loads chunks, we default to NOT loading now + break; + } + } +@@ -504,7 +504,7 @@ public class ServerPlayer extends Player { + } else { + this.moveTo(blockposition, world.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored + +- while (!world.noCollision((Entity) this) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { ++ while (!world.noCollision(this, this.getBoundingBox(), true) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { // Paper - make sure this loads chunks, we default to NOT loading now + this.setPos(this.getX(), this.getY() + 1.0D, this.getZ()); + } + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index cb81990877f918a5305478b5e8ac7dde6a78f238..4816897a82c569717bf7ea139a55ab3fd931a63e 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -935,7 +935,7 @@ public abstract class PlayerList { + entityplayer1.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + + worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper +- while (avoidSuffocation && !worldserver1.noCollision((Entity) entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { ++ while (avoidSuffocation && !worldserver1.noCollision(entityplayer1, entityplayer1.getBoundingBox(), true) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { // Paper - make sure this loads chunks, we default to NOT loading now + // CraftBukkit end + entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index ecf53d27a924cccb7d773cea3ce0f68a41ff52f5..906eded9a2ab61737a30cfe89292a71237ce4eb7 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1237,9 +1237,44 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + float f = this.getBlockSpeedFactor(); + + this.setDeltaMovement(this.getDeltaMovement().multiply((double) f, 1.0D, (double) f)); +- if (this.level().getBlockStatesIfLoaded(this.getBoundingBox().deflate(1.0E-6D)).noneMatch((iblockdata2) -> { +- return iblockdata2.is(BlockTags.FIRE) || iblockdata2.is(Blocks.LAVA); +- })) { ++ // Paper start - remove expensive streams from here ++ boolean noneMatch = true; ++ AABB fireSearchBox = this.getBoundingBox().deflate(1.0E-6D); ++ { ++ int minX = Mth.floor(fireSearchBox.minX); ++ int minY = Mth.floor(fireSearchBox.minY); ++ int minZ = Mth.floor(fireSearchBox.minZ); ++ int maxX = Mth.floor(fireSearchBox.maxX); ++ int maxY = Mth.floor(fireSearchBox.maxY); ++ int maxZ = Mth.floor(fireSearchBox.maxZ); ++ fire_search_loop: ++ for (int fz = minZ; fz <= maxZ; ++fz) { ++ for (int fx = minX; fx <= maxX; ++fx) { ++ for (int fy = minY; fy <= maxY; ++fy) { ++ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)this.level.getChunkIfLoadedImmediately(fx >> 4, fz >> 4); ++ if (chunk == null) { ++ // Vanilla rets an empty stream if all the chunks are not loaded, so noneMatch will be true ++ // even if we're in lava/fire ++ noneMatch = true; ++ break fire_search_loop; ++ } ++ if (!noneMatch) { ++ // don't do get type, we already know we're in fire - we just need to check the chunks ++ // loaded state ++ continue; ++ } ++ ++ BlockState type = chunk.getBlockStateFinal(fx, fy, fz); ++ if (type.is(BlockTags.FIRE) || type.is(Blocks.LAVA)) { ++ noneMatch = false; ++ // can't break, we need to retain vanilla behavior by ensuring ALL chunks are loaded ++ } ++ } ++ } ++ } ++ } ++ if (noneMatch) { ++ // Paper end - remove expensive streams from here + if (this.remainingFireTicks <= 0) { + this.setRemainingFireTicks(-this.getFireImmuneTicks()); + } +@@ -1419,32 +1454,82 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + } + + private Vec3 collide(Vec3 movement) { +- AABB axisalignedbb = this.getBoundingBox(); +- List list = this.level().getEntityCollisions(this, axisalignedbb.expandTowards(movement)); +- Vec3 vec3d1 = movement.lengthSqr() == 0.0D ? movement : Entity.collideBoundingBox(this, movement, axisalignedbb, this.level(), list); +- boolean flag = movement.x != vec3d1.x; +- boolean flag1 = movement.y != vec3d1.y; +- boolean flag2 = movement.z != vec3d1.z; +- boolean flag3 = this.onGround() || flag1 && movement.y < 0.0D; ++ // Paper start - optimise collisions ++ final boolean xZero = movement.x == 0.0; ++ final boolean yZero = movement.y == 0.0; ++ final boolean zZero = movement.z == 0.0; ++ if (xZero & yZero & zZero) { ++ return movement; ++ } ++ ++ final Level world = this.level; ++ final AABB currBoundingBox = this.getBoundingBox(); ++ ++ if (io.papermc.paper.util.CollisionUtil.isEmpty(currBoundingBox)) { ++ return movement; ++ } ++ ++ final List potentialCollisionsBB = new java.util.ArrayList<>(); ++ final List potentialCollisionsVoxel = new java.util.ArrayList<>(); ++ final double stepHeight = (double)this.maxUpStep(); ++ final AABB collisionBox; ++ final boolean onGround = this.onGround; ++ ++ if (xZero & zZero) { ++ if (movement.y > 0.0) { ++ collisionBox = io.papermc.paper.util.CollisionUtil.cutUpwards(currBoundingBox, movement.y); ++ } else { ++ collisionBox = io.papermc.paper.util.CollisionUtil.cutDownwards(currBoundingBox, movement.y); ++ } ++ } else { ++ // note: xZero == false or zZero == false ++ if (stepHeight > 0.0 && (onGround || (movement.y < 0.0))) { ++ // don't bother getting the collisions if we don't need them. ++ if (movement.y <= 0.0) { ++ collisionBox = io.papermc.paper.util.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(movement.x, movement.y, movement.z), stepHeight); ++ } else { ++ collisionBox = currBoundingBox.expandTowards(movement.x, Math.max(stepHeight, movement.y), movement.z); ++ } ++ } else { ++ collisionBox = currBoundingBox.expandTowards(movement.x, movement.y, movement.z); ++ } ++ } ++ ++ io.papermc.paper.util.CollisionUtil.getCollisions( ++ world, this, collisionBox, potentialCollisionsVoxel, potentialCollisionsBB, ++ io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, ++ null, null ++ ); ++ ++ if (potentialCollisionsVoxel.isEmpty() && potentialCollisionsBB.isEmpty()) { ++ return movement; ++ } + +- if (this.maxUpStep() > 0.0F && flag3 && (flag || flag2)) { +- Vec3 vec3d2 = Entity.collideBoundingBox(this, new Vec3(movement.x, (double) this.maxUpStep(), movement.z), axisalignedbb, this.level(), list); +- Vec3 vec3d3 = Entity.collideBoundingBox(this, new Vec3(0.0D, (double) this.maxUpStep(), 0.0D), axisalignedbb.expandTowards(movement.x, 0.0D, movement.z), this.level(), list); ++ final Vec3 limitedMoveVector = io.papermc.paper.util.CollisionUtil.performCollisions(movement, currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); + +- if (vec3d3.y < (double) this.maxUpStep()) { +- Vec3 vec3d4 = Entity.collideBoundingBox(this, new Vec3(movement.x, 0.0D, movement.z), axisalignedbb.move(vec3d3), this.level(), list).add(vec3d3); ++ if (stepHeight > 0.0 ++ && (onGround || (limitedMoveVector.y != movement.y && movement.y < 0.0)) ++ && (limitedMoveVector.x != movement.x || limitedMoveVector.z != movement.z)) { ++ Vec3 vec3d2 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, stepHeight, movement.z), currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); ++ final Vec3 vec3d3 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(movement.x, 0.0, movement.z), potentialCollisionsVoxel, potentialCollisionsBB); ++ ++ if (vec3d3.y < stepHeight) { ++ final Vec3 vec3d4 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, 0.0D, movement.z), currBoundingBox.move(vec3d3), potentialCollisionsVoxel, potentialCollisionsBB).add(vec3d3); + + if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) { + vec3d2 = vec3d4; + } + } + +- if (vec3d2.horizontalDistanceSqr() > vec3d1.horizontalDistanceSqr()) { +- return vec3d2.add(Entity.collideBoundingBox(this, new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), axisalignedbb.move(vec3d2), this.level(), list)); ++ if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) { ++ return vec3d2.add(io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisionsVoxel, potentialCollisionsBB)); + } +- } + +- return vec3d1; ++ return limitedMoveVector; ++ } else { ++ return limitedMoveVector; ++ } ++ // Paper end - optimise collisions + } + + public static Vec3 collideBoundingBox(@Nullable Entity entity, Vec3 movement, AABB entityBoundingBox, Level world, List collisions) { +@@ -2691,11 +2776,70 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S + float f = this.dimensions.width * 0.8F; + AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f); + +- return BlockPos.betweenClosedStream(axisalignedbb).anyMatch((blockposition) -> { +- BlockState iblockdata = this.level().getBlockState(blockposition); ++ // Paper start - optimise collisions ++ if (io.papermc.paper.util.CollisionUtil.isEmpty(axisalignedbb)) { ++ return false; ++ } + +- return !iblockdata.isAir() && iblockdata.isSuffocating(this.level(), blockposition) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level(), blockposition).move((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()), Shapes.create(axisalignedbb), BooleanOp.AND); +- }); ++ final BlockPos.MutableBlockPos tempPos = new BlockPos.MutableBlockPos(); ++ ++ final int minX = Mth.floor(axisalignedbb.minX); ++ final int minY = Mth.floor(axisalignedbb.minY); ++ final int minZ = Mth.floor(axisalignedbb.minZ); ++ final int maxX = Mth.floor(axisalignedbb.maxX); ++ final int maxY = Mth.floor(axisalignedbb.maxY); ++ final int maxZ = Mth.floor(axisalignedbb.maxZ); ++ ++ final net.minecraft.server.level.ServerChunkCache chunkProvider = (net.minecraft.server.level.ServerChunkCache)this.level.getChunkSource(); ++ ++ long lastChunkKey = ChunkPos.INVALID_CHUNK_POS; ++ net.minecraft.world.level.chunk.LevelChunk lastChunk = null; ++ for (int fz = minZ; fz <= maxZ; ++fz) { ++ tempPos.setZ(fz); ++ for (int fx = minX; fx <= maxX; ++fx) { ++ final int newChunkX = fx >> 4; ++ final int newChunkZ = fz >> 4; ++ final net.minecraft.world.level.chunk.LevelChunk chunk = lastChunkKey == (lastChunkKey = io.papermc.paper.util.CoordinateUtils.getChunkKey(newChunkX, newChunkZ)) ? ++ lastChunk : (lastChunk = chunkProvider.getChunkAtIfLoadedImmediately(newChunkX, newChunkZ)); ++ tempPos.setX(fx); ++ if (chunk == null) { ++ continue; ++ } ++ for (int fy = minY; fy <= maxY; ++fy) { ++ tempPos.setY(fy); ++ ++ final BlockState state = chunk.getBlockState(tempPos); ++ ++ if (state.emptyCollisionShape() || !state.isSuffocating(this.level, tempPos)) { ++ continue; ++ } ++ ++ // Yes, it does not use the Entity context stuff. ++ final VoxelShape collisionShape = state.getCollisionShape(this.level, tempPos); ++ ++ if (collisionShape.isEmpty()) { ++ continue; ++ } ++ ++ final AABB toCollide = axisalignedbb.move(-(double)fx, -(double)fy, -(double)fz); ++ ++ final AABB singleAABB = collisionShape.getSingleAABBRepresentation(); ++ if (singleAABB != null) { ++ if (io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(singleAABB, toCollide)) { ++ return true; ++ } ++ continue; ++ } ++ ++ if (io.papermc.paper.util.CollisionUtil.voxelShapeIntersectNoEmpty(collisionShape, toCollide)) { ++ return true; ++ } ++ continue; ++ } ++ } ++ } ++ // Paper end - optimise collisions ++ return false; + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 6dfcc296ff7e59ecbebc5446973fabc9eff3cb43..94a30a0c1266bf919d1dc4ca2b19489edd54a7fa 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -353,7 +353,7 @@ public class ArmorStand extends LivingEntity { + @Override + protected void pushEntities() { + if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups +- List list = this.level().getEntities((Entity) this, this.getBoundingBox(), ArmorStand.RIDABLE_MINECARTS); ++ List list = this.level().getEntitiesOfClass(AbstractMinecart.class, this.getBoundingBox(), ArmorStand.RIDABLE_MINECARTS); // Paper - optimise collisions + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java +index ffa4f34d964fbcc53e2dfe11677832db21a6eb93..7618364e5373fe17cfe45a5a4ee9ab25e591581c 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Spider.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java +@@ -86,7 +86,7 @@ public class Spider extends Monster { + public void tick() { + super.tick(); + if (!this.level().isClientSide) { +- this.setClimbing(this.horizontalCollision && (this.level().paperConfig().entities.behavior.allowSpiderWorldBorderClimbing)); // Paper - Add config option for spider worldborder climbing ++ this.setClimbing(this.horizontalCollision && (this.level().paperConfig().entities.behavior.allowSpiderWorldBorderClimbing || !io.papermc.paper.util.CollisionUtil.isCollidingWithBorder(this.level().getWorldBorder(), this.getBoundingBox().inflate(io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)))); // Paper - Add config option for spider worldborder climbing & Inflate by +EPSILON as collision will just barely place us outside border + } + + } +diff --git a/src/main/java/net/minecraft/world/level/BlockCollisions.java b/src/main/java/net/minecraft/world/level/BlockCollisions.java +index a3eaf80b020c3bbc0306c5d17659ee661dfd275b..1b6f72932fbdd567a1534bcf15e8a610b00f974d 100644 +--- a/src/main/java/net/minecraft/world/level/BlockCollisions.java ++++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java +@@ -105,7 +105,7 @@ public class BlockCollisions extends AbstractIterator { + + VoxelShape voxelShape = blockState.getCollisionShape(this.collisionGetter, this.pos, this.context); + if (voxelShape == Shapes.block()) { +- if (!this.box.intersects((double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) { ++ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(this.box, (double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) { // Paper - keep vanilla behavior for voxelshape intersection - See comment in CollisionUtil + continue; + } + +diff --git a/src/main/java/net/minecraft/world/level/ClipContext.java b/src/main/java/net/minecraft/world/level/ClipContext.java +index 86a4f30c8784c602436ecf1c78efb0bdca4b7089..b0bea28e9261767c60d30fb0e76f4f3af8a5634e 100644 +--- a/src/main/java/net/minecraft/world/level/ClipContext.java ++++ b/src/main/java/net/minecraft/world/level/ClipContext.java +@@ -17,8 +17,8 @@ public class ClipContext { + + private final Vec3 from; + private final Vec3 to; +- private final ClipContext.Block block; +- private final ClipContext.Fluid fluid; ++ public final ClipContext.Block block; // Paper - optimise collisions - public ++ public final ClipContext.Fluid fluid; // Paper - optimise collisions - public + private final CollisionContext collisionContext; + + public ClipContext(Vec3 start, Vec3 end, ClipContext.Block shapeType, ClipContext.Fluid fluidHandling, Entity entity) { +diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java +index c476e37df8a75d77f5093b2a449e04f25ef2c2dd..5d66aadae51db1ae760812849bfc8740b82af9a9 100644 +--- a/src/main/java/net/minecraft/world/level/CollisionGetter.java ++++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java +@@ -35,6 +35,12 @@ public interface CollisionGetter extends BlockGetter { + return this.isUnobstructed(entity, Shapes.create(entity.getBoundingBox())); + } + ++ // Paper start - optimise collisions ++ default boolean noCollision(Entity entity, AABB box, boolean loadChunks) { ++ return this.noCollision(entity, box); ++ } ++ // Paper end - optimise collisions ++ + default boolean noCollision(AABB box) { + return this.noCollision((Entity)null, box); + } +diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java +index cc888bbcd6a50124fa553bc4a8ffd1e8885d3856..f42dd9602805e9d538506ee4e3eac7e2811a3da6 100644 +--- a/src/main/java/net/minecraft/world/level/EntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +@@ -45,17 +45,36 @@ public interface EntityGetter { + } + + default boolean isUnobstructed(@Nullable Entity except, VoxelShape shape) { ++ // Paper start - optimise collisions + if (shape.isEmpty()) { +- return true; +- } else { +- for(Entity entity : this.getEntities(except, shape.bounds())) { +- if (!entity.isRemoved() && entity.blocksBuilding && (except == null || !entity.isPassengerOfSameVehicle(except)) && Shapes.joinIsNotEmpty(shape, Shapes.create(entity.getBoundingBox()), BooleanOp.AND)) { +- return false; ++ return false; ++ } ++ ++ final AABB singleAABB = shape.getSingleAABBRepresentation(); ++ final List entities = this.getEntities( ++ except, ++ singleAABB == null ? shape.bounds() : singleAABB.inflate(-io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON, -io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON, -io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) ++ ); ++ ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ final Entity otherEntity = entities.get(i); ++ ++ if (otherEntity.isRemoved() || !otherEntity.blocksBuilding || (except != null && otherEntity.isPassengerOfSameVehicle(except))) { ++ continue; ++ } ++ ++ if (singleAABB == null) { ++ final AABB entityBB = otherEntity.getBoundingBox(); ++ if (io.papermc.paper.util.CollisionUtil.isEmpty(entityBB) || !io.papermc.paper.util.CollisionUtil.voxelShapeIntersectNoEmpty(shape, entityBB)) { ++ continue; + } + } + +- return true; ++ return false; + } ++ ++ return true; ++ // Paper end - optimise collisions + } + + default List getEntitiesOfClass(Class entityClass, AABB box) { +@@ -63,23 +82,41 @@ public interface EntityGetter { + } + + default List getEntityCollisions(@Nullable Entity entity, AABB box) { +- if (box.getSize() < 1.0E-7D) { +- return List.of(); ++ // Paper start - optimise collisions ++ // first behavior change is to correctly check for empty AABB ++ if (io.papermc.paper.util.CollisionUtil.isEmpty(box)) { ++ // reduce indirection by always returning type with same class ++ return new java.util.ArrayList<>(); ++ } ++ ++ // to comply with vanilla intersection rules, expand by -epsilon so that we only get stuff we definitely collide with. ++ // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems ++ // specifically with boat collisions. ++ box = box.inflate(-io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON, -io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON, -io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON); ++ ++ final List entities; ++ if (entity != null && entity.hardCollides()) { ++ entities = this.getEntities(entity, box, null); + } else { +- Predicate predicate = entity == null ? EntitySelector.CAN_BE_COLLIDED_WITH : EntitySelector.NO_SPECTATORS.and(entity::canCollideWith); +- List list = this.getEntities(entity, box.inflate(1.0E-7D), predicate); +- if (list.isEmpty()) { +- return List.of(); +- } else { +- ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(list.size()); +- +- for(Entity entity2 : list) { +- builder.add(Shapes.create(entity2.getBoundingBox())); +- } ++ entities = this.getHardCollidingEntities(entity, box, null); ++ } + +- return builder.build(); ++ final List ret = new java.util.ArrayList<>(Math.min(25, entities.size())); ++ ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ final Entity otherEntity = entities.get(i); ++ ++ if (otherEntity.isSpectator()) { ++ continue; ++ } ++ ++ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) { ++ ret.add(Shapes.create(otherEntity.getBoundingBox())); + } + } ++ ++ return ret; ++ // Paper end - optimise collisions + } + + // Paper start - Affects Spawning API +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 1712cf22d987a87c427f042a89a9fff90203b079..f21f1b3fcab47f18cbf26a9797b1b7b9a5dccfc9 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -294,6 +294,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime); + this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); + this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray ++ // Paper start - optimise collisions ++ this.minSection = io.papermc.paper.util.WorldUtil.getMinSection(this); ++ this.maxSection = io.papermc.paper.util.WorldUtil.getMaxSection(this); ++ // Paper end - optimise collisions + } + + // Paper start - Cancel hit for vanished players +@@ -335,6 +339,366 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return true; + } + // Paper end - Cancel hit for vanished players ++ // Paper start - optimise collisions ++ public final int minSection; ++ public final int maxSection; ++ ++ @Override ++ public final boolean isUnobstructed(final Entity entity) { ++ final AABB boundingBox = entity.getBoundingBox(); ++ if (io.papermc.paper.util.CollisionUtil.isEmpty(boundingBox)) { ++ return false; ++ } ++ ++ final List entities = this.getEntities( ++ entity, ++ boundingBox.inflate(-io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON, -io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON, -io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON), ++ null ++ ); ++ ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ final Entity otherEntity = entities.get(i); ++ ++ if (otherEntity.isSpectator() || otherEntity.isRemoved() || !otherEntity.blocksBuilding || otherEntity.isPassengerOfSameVehicle(entity)) { ++ continue; ++ } ++ ++ return false; ++ } ++ ++ return true; ++ } ++ ++ private static net.minecraft.world.phys.BlockHitResult miss(final ClipContext clipContext) { ++ final Vec3 to = clipContext.getTo(); ++ final Vec3 from = clipContext.getFrom(); ++ ++ return net.minecraft.world.phys.BlockHitResult.miss(to, Direction.getNearest(from.x - to.x, from.y - to.y, from.z - to.z), BlockPos.containing(to.x, to.y, to.z)); ++ } ++ ++ private static final FluidState AIR_FLUIDSTATE = Fluids.EMPTY.defaultFluidState(); ++ ++ private static net.minecraft.world.phys.BlockHitResult fastClip(final Vec3 from, final Vec3 to, final Level level, ++ final ClipContext clipContext) { ++ final double adjX = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.x - to.x); ++ final double adjY = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.y - to.y); ++ final double adjZ = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.z - to.z); ++ ++ if (adjX == 0.0 && adjY == 0.0 && adjZ == 0.0) { ++ return miss(clipContext); ++ } ++ ++ final double toXAdj = to.x - adjX; ++ final double toYAdj = to.y - adjY; ++ final double toZAdj = to.z - adjZ; ++ final double fromXAdj = from.x + adjX; ++ final double fromYAdj = from.y + adjY; ++ final double fromZAdj = from.z + adjZ; ++ ++ int currX = Mth.floor(fromXAdj); ++ int currY = Mth.floor(fromYAdj); ++ int currZ = Mth.floor(fromZAdj); ++ ++ final BlockPos.MutableBlockPos currPos = new BlockPos.MutableBlockPos(); ++ ++ final double diffX = toXAdj - fromXAdj; ++ final double diffY = toYAdj - fromYAdj; ++ final double diffZ = toZAdj - fromZAdj; ++ ++ final double dxDouble = Math.signum(diffX); ++ final double dyDouble = Math.signum(diffY); ++ final double dzDouble = Math.signum(diffZ); ++ ++ final int dx = (int)dxDouble; ++ final int dy = (int)dyDouble; ++ final int dz = (int)dzDouble; ++ ++ final double normalizedDiffX = diffX == 0.0 ? Double.MAX_VALUE : dxDouble / diffX; ++ final double normalizedDiffY = diffY == 0.0 ? Double.MAX_VALUE : dyDouble / diffY; ++ final double normalizedDiffZ = diffZ == 0.0 ? Double.MAX_VALUE : dzDouble / diffZ; ++ ++ double normalizedCurrX = normalizedDiffX * (diffX > 0.0 ? (1.0 - Mth.frac(fromXAdj)) : Mth.frac(fromXAdj)); ++ double normalizedCurrY = normalizedDiffY * (diffY > 0.0 ? (1.0 - Mth.frac(fromYAdj)) : Mth.frac(fromYAdj)); ++ double normalizedCurrZ = normalizedDiffZ * (diffZ > 0.0 ? (1.0 - Mth.frac(fromZAdj)) : Mth.frac(fromZAdj)); ++ ++ net.minecraft.world.level.chunk.LevelChunkSection[] lastChunk = null; ++ net.minecraft.world.level.chunk.PalettedContainer lastSection = null; ++ int lastChunkX = Integer.MIN_VALUE; ++ int lastChunkY = Integer.MIN_VALUE; ++ int lastChunkZ = Integer.MIN_VALUE; ++ ++ final int minSection = level.minSection; ++ final net.minecraft.server.level.ServerChunkCache chunkProvider = (net.minecraft.server.level.ServerChunkCache)level.getChunkSource(); ++ ++ for (;;) { ++ currPos.set(currX, currY, currZ); ++ ++ final int newChunkX = currX >> 4; ++ final int newChunkY = currY >> 4; ++ final int newChunkZ = currZ >> 4; ++ ++ final int chunkDiff = ((newChunkX ^ lastChunkX) | (newChunkZ ^ lastChunkZ)); ++ final int chunkYDiff = newChunkY ^ lastChunkY; ++ ++ if ((chunkDiff | chunkYDiff) != 0) { ++ if (chunkDiff != 0) { ++ LevelChunk chunk = chunkProvider.getChunkAtIfLoadedImmediately(newChunkX, newChunkZ); ++ lastChunk = chunk == null ? null : chunk.getSections(); // diff: don't load chunks for this ++ } ++ final int sectionY = newChunkY - minSection; ++ lastSection = lastChunk != null && sectionY >= 0 && sectionY < lastChunk.length ? lastChunk[sectionY].states : null; ++ ++ lastChunkX = newChunkX; ++ lastChunkY = newChunkY; ++ lastChunkZ = newChunkZ; ++ } ++ ++ final BlockState blockState; ++ if (lastSection != null && !(blockState = lastSection.get((currX & 15) | ((currZ & 15) << 4) | ((currY & 15) << (4+4)))).isAir()) { ++ final net.minecraft.world.phys.shapes.VoxelShape blockCollision = clipContext.getBlockShape(blockState, level, currPos); ++ ++ final net.minecraft.world.phys.BlockHitResult blockHit = blockCollision.isEmpty() ? null : level.clipWithInteractionOverride(from, to, currPos, blockCollision, blockState); ++ ++ final net.minecraft.world.phys.shapes.VoxelShape fluidCollision; ++ final FluidState fluidState; ++ if (clipContext.fluid != ClipContext.Fluid.NONE && (fluidState = blockState.getFluidState()) != AIR_FLUIDSTATE) { ++ fluidCollision = clipContext.getFluidShape(fluidState, level, currPos); ++ ++ final net.minecraft.world.phys.BlockHitResult fluidHit = fluidCollision.clip(from, to, currPos); ++ ++ if (fluidHit != null) { ++ if (blockHit == null) { ++ return fluidHit; ++ } ++ ++ return from.distanceToSqr(blockHit.getLocation()) <= from.distanceToSqr(fluidHit.getLocation()) ? blockHit : fluidHit; ++ } ++ } ++ ++ if (blockHit != null) { ++ return blockHit; ++ } ++ } // else: usually fall here ++ ++ if (normalizedCurrX > 1.0 && normalizedCurrY > 1.0 && normalizedCurrZ > 1.0) { ++ return miss(clipContext); ++ } ++ ++ // inc the smallest normalized coordinate ++ ++ if (normalizedCurrX < normalizedCurrY) { ++ if (normalizedCurrX < normalizedCurrZ) { ++ currX += dx; ++ normalizedCurrX += normalizedDiffX; ++ } else { ++ // x < y && x >= z <--> z < y && z <= x ++ currZ += dz; ++ normalizedCurrZ += normalizedDiffZ; ++ } ++ } else if (normalizedCurrY < normalizedCurrZ) { ++ // y <= x && y < z ++ currY += dy; ++ normalizedCurrY += normalizedDiffY; ++ } else { ++ // y <= x && z <= y <--> z <= y && z <= x ++ currZ += dz; ++ normalizedCurrZ += normalizedDiffZ; ++ } ++ } ++ } ++ ++ @Override ++ public final net.minecraft.world.phys.BlockHitResult clip(final ClipContext clipContext) { ++ // can only do this in this class, as not everything that implements BlockGetter can retrieve chunks ++ return fastClip(clipContext.getFrom(), clipContext.getTo(), this, clipContext); ++ } ++ ++ @Override ++ public final boolean noCollision(final Entity entity, final AABB box, final boolean loadChunks) { ++ int flags = io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_ONLY; ++ if (entity != null) { ++ flags |= io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_BORDER; ++ } ++ if (loadChunks) { ++ flags |= io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_LOAD_CHUNKS; ++ } ++ if (io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, null, flags, null)) { ++ return false; ++ } ++ ++ return !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, flags, null); ++ } ++ ++ @Override ++ public final boolean collidesWithSuffocatingBlock(final Entity entity, final AABB box) { ++ return io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, null, ++ io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_ONLY, ++ (final BlockState state, final BlockPos pos) -> { ++ return state.isSuffocating(Level.this, pos); ++ } ++ ); ++ } ++ ++ private static net.minecraft.world.phys.shapes.VoxelShape inflateAABBToVoxel(final AABB aabb, final double x, final double y, final double z) { ++ return net.minecraft.world.phys.shapes.Shapes.create( ++ aabb.minX - x, ++ aabb.minY - y, ++ aabb.minZ - z, ++ ++ aabb.maxX + x, ++ aabb.maxY + y, ++ aabb.maxZ + z ++ ); ++ } ++ ++ @Override ++ public final java.util.Optional findFreePosition(final Entity entity, final net.minecraft.world.phys.shapes.VoxelShape boundsShape, final Vec3 fromPosition, ++ final double rangeX, final double rangeY, final double rangeZ) { ++ if (boundsShape.isEmpty()) { ++ return java.util.Optional.empty(); ++ } ++ ++ final double expandByX = rangeX * 0.5; ++ final double expandByY = rangeY * 0.5; ++ final double expandByZ = rangeZ * 0.5; ++ ++ // note: it is useless to look at shapes outside of range / 2.0 ++ final AABB collectionVolume = boundsShape.bounds().inflate(expandByX, expandByY, expandByZ); ++ ++ final List aabbs = new java.util.ArrayList<>(); ++ final List voxels = new java.util.ArrayList<>(); ++ ++ io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder( ++ this, entity, collectionVolume, voxels, aabbs, ++ io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, ++ null ++ ); ++ ++ // push voxels into aabbs ++ for (int i = 0, len = voxels.size(); i < len; ++i) { ++ aabbs.addAll(voxels.get(i).toAabbs()); ++ } ++ ++ // expand AABBs ++ final net.minecraft.world.phys.shapes.VoxelShape first = aabbs.isEmpty() ? net.minecraft.world.phys.shapes.Shapes.empty() : inflateAABBToVoxel(aabbs.get(0), expandByX, expandByY, expandByZ); ++ final net.minecraft.world.phys.shapes.VoxelShape[] rest = new net.minecraft.world.phys.shapes.VoxelShape[Math.max(0, aabbs.size() - 1)]; ++ ++ for (int i = 1, len = aabbs.size(); i < len; ++i) { ++ rest[i - 1] = inflateAABBToVoxel(aabbs.get(i), expandByX, expandByY, expandByZ); ++ } ++ ++ // use optimized implementation of ORing the shapes together ++ final net.minecraft.world.phys.shapes.VoxelShape joined = net.minecraft.world.phys.shapes.Shapes.or(first, rest); ++ ++ // find free space ++ // can use unoptimized join here (instead of join()), as closestPointTo uses toAabbs() ++ final net.minecraft.world.phys.shapes.VoxelShape freeSpace = net.minecraft.world.phys.shapes.Shapes.joinUnoptimized( ++ boundsShape, joined, net.minecraft.world.phys.shapes.BooleanOp.ONLY_FIRST ++ ); ++ ++ return freeSpace.closestPointTo(fromPosition); ++ } ++ ++ @Override ++ public final java.util.Optional findSupportingBlock(final Entity entity, final AABB aabb) { ++ final int minBlockX = Mth.floor(aabb.minX - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) - 1; ++ final int maxBlockX = Mth.floor(aabb.maxX + io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) + 1; ++ ++ final int minBlockY = Mth.floor(aabb.minY - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) - 1; ++ final int maxBlockY = Mth.floor(aabb.maxY + io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) + 1; ++ ++ final int minBlockZ = Mth.floor(aabb.minZ - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) - 1; ++ final int maxBlockZ = Mth.floor(aabb.maxZ + io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) + 1; ++ ++ io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext collisionContext = null; ++ ++ final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); ++ BlockPos selected = null; ++ double selectedDistance = Double.MAX_VALUE; ++ ++ final Vec3 entityPos = entity.position(); ++ ++ LevelChunk lastChunk = null; ++ int lastChunkX = Integer.MIN_VALUE; ++ int lastChunkZ = Integer.MIN_VALUE; ++ ++ final net.minecraft.server.level.ServerChunkCache chunkProvider = (net.minecraft.server.level.ServerChunkCache)this.getChunkSource(); ++ ++ for (int currZ = minBlockZ; currZ <= maxBlockZ; ++currZ) { ++ pos.setZ(currZ); ++ for (int currX = minBlockX; currX <= maxBlockX; ++currX) { ++ pos.setX(currX); ++ ++ final int newChunkX = currX >> 4; ++ final int newChunkZ = currZ >> 4; ++ ++ final int chunkDiff = ((newChunkX ^ lastChunkX) | (newChunkZ ^ lastChunkZ)); ++ ++ if (chunkDiff != 0) { ++ lastChunk = chunkProvider.getChunkAtIfLoadedImmediately(newChunkX, newChunkZ); ++ } ++ ++ if (lastChunk == null) { ++ continue; ++ } ++ for (int currY = minBlockY; currY <= maxBlockY; ++currY) { ++ int edgeCount = ((currX == minBlockX || currX == maxBlockX) ? 1 : 0) + ++ ((currY == minBlockY || currY == maxBlockY) ? 1 : 0) + ++ ((currZ == minBlockZ || currZ == maxBlockZ) ? 1 : 0); ++ if (edgeCount == 3) { ++ continue; ++ } ++ ++ pos.setY(currY); ++ ++ final double distance = pos.distToCenterSqr(entityPos); ++ if (distance > selectedDistance || (distance == selectedDistance && selected.compareTo(pos) >= 0)) { ++ continue; ++ } ++ ++ final BlockState state = lastChunk.getBlockState(currX, currY, currZ); ++ if (state.emptyCollisionShape()) { ++ continue; ++ } ++ ++ if ((edgeCount != 1 || state.hasLargeCollisionShape()) && (edgeCount != 2 || state.getBlock() == Blocks.MOVING_PISTON)) { ++ if (collisionContext == null) { ++ collisionContext = new io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext(entity); ++ } ++ final net.minecraft.world.phys.shapes.VoxelShape blockCollision = state.getCollisionShape(lastChunk, pos, collisionContext); ++ if (blockCollision.isEmpty()) { ++ continue; ++ } ++ ++ // avoid VoxelShape#move by shifting the entity collision shape instead ++ final AABB shiftedAABB = aabb.move(-(double)currX, -(double)currY, -(double)currZ); ++ ++ final AABB singleAABB = blockCollision.getSingleAABBRepresentation(); ++ if (singleAABB != null) { ++ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(singleAABB, shiftedAABB)) { ++ continue; ++ } ++ ++ selected = pos.immutable(); ++ selectedDistance = distance; ++ continue; ++ } ++ ++ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersectNoEmpty(blockCollision, shiftedAABB)) { ++ continue; ++ } ++ ++ selected = pos.immutable(); ++ selectedDistance = distance; ++ continue; ++ } ++ } ++ } ++ } ++ ++ return java.util.Optional.ofNullable(selected); ++ } ++ // Paper end - optimise collisions + @Override + public boolean isClientSide() { + return this.isClientSide; +@@ -958,7 +1322,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + @Override + public boolean noCollision(@Nullable Entity entity, AABB box) { + if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return false; +- return LevelAccessor.super.noCollision(entity, box); ++ // Paper start - optimise collisions ++ int flags = io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_ONLY; ++ if (entity != null) { ++ flags |= io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_BORDER; ++ } ++ if (io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, null, flags, null)) { ++ return false; ++ } ++ ++ return !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, flags, null); ++ // Paper end - optimise collisions + } + // Paper end - Option to prevent armor stands from doing entity lookups + +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 b60a52788e73de3dcb086c1a4628466b25c9d3ef..22036ed3ea0629bc12981a8d91a03e55cc2117d6 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -284,7 +284,7 @@ public class Block extends BlockBehaviour implements ItemLike { + } + + public static boolean isShapeFullBlock(VoxelShape shape) { +- return (Boolean) Block.SHAPE_FULL_BLOCK_CACHE.getUnchecked(shape); ++ return shape.isFullBlock(); // Paper - optimise collisions + } + + public boolean propagatesSkylightDown(BlockState state, BlockGetter world, BlockPos pos) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index e493b34aa8726ed48f8e5db2ae8ea561cc5b1f75..2892e586146cbc560f0bcf4b9af6d0575cb0a82e 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -882,6 +882,10 @@ public abstract class BlockBehaviour implements FeatureElement { + this.instrument = blockbase_info.instrument; + this.replaceable = blockbase_info.replaceable; + this.conditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion; // Paper ++ // Paper start - optimise collisions ++ this.id1 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET); ++ this.id2 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET); ++ // Paper end - optimise collisions + } + // Paper start - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time + private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; +@@ -930,6 +934,52 @@ public abstract class BlockBehaviour implements FeatureElement { + return this.conditionallyFullOpaque; + } + // Paper end - starlight ++ // Paper start - optimise collisions ++ private static final int RANDOM_OFFSET = 704237939; ++ private static final Direction[] DIRECTIONS_CACHED = Direction.values(); ++ private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger(); ++ private final int id1, id2; ++ private boolean occludesFullBlock; ++ private boolean emptyCollisionShape; ++ private VoxelShape constantCollisionShape; ++ private AABB constantAABBCollision; ++ private static void initCaches(final VoxelShape shape) { ++ shape.isFullBlock(); ++ shape.occludesFullBlock(); ++ shape.toAabbs(); ++ if (!shape.isEmpty()) { ++ shape.bounds(); ++ } ++ } ++ ++ public final boolean hasCache() { ++ return this.cache != null; ++ } ++ ++ public final boolean occludesFullBlock() { ++ return this.occludesFullBlock; ++ } ++ ++ public final boolean emptyCollisionShape() { ++ return this.emptyCollisionShape; ++ } ++ ++ public final int uniqueId1() { ++ return this.id1; ++ } ++ ++ public final int uniqueId2() { ++ return this.id2; ++ } ++ ++ public final VoxelShape getConstantCollisionShape() { ++ return this.constantCollisionShape; ++ } ++ ++ public final AABB getConstantCollisionAABB() { ++ return this.constantAABBCollision; ++ } ++ // Paper end - optimise collisions + + public void initCache() { + this.fluidState = ((Block) this.owner).getFluidState(this.asState()); +@@ -941,6 +991,39 @@ public abstract class BlockBehaviour implements FeatureElement { + this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - starlight - cache opacity for light + + this.legacySolid = this.calculateSolid(); ++ // Paper start - optimise collisions ++ if (this.cache != null) { ++ final VoxelShape collisionShape = this.cache.collisionShape; ++ try { ++ this.constantCollisionShape = this.getCollisionShape(null, null, null); ++ this.constantAABBCollision = this.constantCollisionShape == null ? null : this.constantCollisionShape.getSingleAABBRepresentation(); ++ } catch (final Throwable throwable) { ++ this.constantCollisionShape = null; ++ this.constantAABBCollision = null; ++ } ++ this.occludesFullBlock = collisionShape.occludesFullBlock(); ++ this.emptyCollisionShape = collisionShape.isEmpty(); ++ // init caches ++ initCaches(collisionShape); ++ if (collisionShape != Shapes.empty() && collisionShape != Shapes.block()) { ++ for (final Direction direction : DIRECTIONS_CACHED) { ++ // initialise the directional face shape cache as well ++ final VoxelShape shape = Shapes.getFaceShape(collisionShape, direction); ++ initCaches(shape); ++ } ++ } ++ if (this.cache.occlusionShapes != null) { ++ for (final VoxelShape shape : this.cache.occlusionShapes) { ++ initCaches(shape); ++ } ++ } ++ } else { ++ this.occludesFullBlock = false; ++ this.emptyCollisionShape = false; ++ this.constantCollisionShape = null; ++ this.constantAABBCollision = null; ++ } ++ // Paper end - optimise collisions + } + + public Block getBlock() { +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index eb05c01e85825cbd5b7cf43bc6d261db0b871b92..796bbef3544e06b8e7aac7e8ac5f740a2613f4bd 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -26,6 +26,22 @@ public class LevelChunkSection { + // CraftBukkit start - read/write + private PalettedContainer> biomes; + public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper ++ // Paper start - optimise collisions ++ private int specialCollidingBlocks; ++ ++ private void updateBlockCallback(final int x, final int y, final int z, final BlockState oldState, final BlockState newState) { ++ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(newState)) { ++ ++this.specialCollidingBlocks; ++ } ++ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(oldState)) { ++ --this.specialCollidingBlocks; ++ } ++ } ++ ++ public final int getSpecialCollidingBlocks() { ++ return this.specialCollidingBlocks; ++ } ++ // Paper end - optimise collisions + + public LevelChunkSection(PalettedContainer datapaletteblock, PalettedContainer> palettedcontainerro) { + // CraftBukkit end +@@ -62,8 +78,8 @@ public class LevelChunkSection { + return this.setBlockState(x, y, z, state, true); + } + +- public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) { +- BlockState iblockdata1; ++ public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) { // Paper - state -> new state ++ BlockState iblockdata1; // Paper - iblockdata1 -> oldState + + if (lock) { + iblockdata1 = (BlockState) this.states.getAndSet(x, y, z, state); +@@ -102,6 +118,7 @@ public class LevelChunkSection { + ++this.tickingFluidCount; + } + ++ this.updateBlockCallback(x, y, z, iblockdata1, state); // Paper - optimise collisions + return iblockdata1; + } + +@@ -147,6 +164,11 @@ public class LevelChunkSection { + } + } + ++ // Paper start - optimise collisions ++ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(iblockdata)) { ++ ++this.specialCollidingBlocks; ++ } ++ // Paper end - optimise collisions + }); + } + // Paper end +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 a98ab20814cc29a25e9d29adfbb7e70d46768df2..6d8ff6c06af5545634f255ed17dc1e489ece2548 100644 +--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java ++++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +@@ -240,6 +240,17 @@ public abstract class FlowingFluid extends Fluid { + } + + private boolean canPassThroughWall(Direction face, BlockGetter world, BlockPos pos, BlockState state, BlockPos fromPos, BlockState fromState) { ++ // Paper start - optimise collisions ++ if (state.emptyCollisionShape() & fromState.emptyCollisionShape()) { ++ // don't even try to cache simple cases ++ return true; ++ } ++ ++ if (state.occludesFullBlock() | fromState.occludesFullBlock()) { ++ // don't even try to cache simple cases ++ return false; ++ } ++ // Paper end - optimise collisions + Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap; + + if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) { +diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java +index b8443953de15066f32f629c0dd7e24bad750f558..67d595f75e0c3bffdb27b85b25ccd1f0bf1427d5 100644 +--- a/src/main/java/net/minecraft/world/phys/AABB.java ++++ b/src/main/java/net/minecraft/world/phys/AABB.java +@@ -25,6 +25,17 @@ public class AABB { + this.maxZ = Math.max(z1, z2); + } + ++ // Paper start ++ public AABB(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, boolean dummy) { ++ this.minX = minX; ++ this.minY = minY; ++ this.minZ = minZ; ++ this.maxX = maxX; ++ this.maxY = maxY; ++ this.maxZ = maxZ; ++ } ++ // Paper end ++ + public AABB(BlockPos pos) { + this((double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)(pos.getX() + 1), (double)(pos.getY() + 1), (double)(pos.getZ() + 1)); + } +@@ -305,7 +316,7 @@ public class AABB { + } + + @Nullable +- private static Direction getDirection(AABB box, Vec3 intersectingVector, double[] traceDistanceResult, @Nullable Direction approachDirection, double deltaX, double deltaY, double deltaZ) { ++ public static Direction getDirection(AABB box, Vec3 intersectingVector, double[] traceDistanceResult, @Nullable Direction approachDirection, double deltaX, double deltaY, double deltaZ) { // Paper - optimise collisions - public + if (deltaX > 1.0E-7D) { + approachDirection = clipPoint(traceDistanceResult, approachDirection, deltaX, deltaY, deltaZ, box.minX, box.minY, box.maxY, box.minZ, box.maxZ, Direction.WEST, intersectingVector.x, intersectingVector.y, intersectingVector.z); + } else if (deltaX < -1.0E-7D) { +diff --git a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java +index 9d627b8e6bf3140b894d38b9a720896e2d776369..a232b9396a41c11579a4d691b05717b16473513e 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java +@@ -15,7 +15,7 @@ public class ArrayVoxelShape extends VoxelShape { + this(shape, (DoubleList)DoubleArrayList.wrap(Arrays.copyOf(xPoints, shape.getXSize() + 1)), (DoubleList)DoubleArrayList.wrap(Arrays.copyOf(yPoints, shape.getYSize() + 1)), (DoubleList)DoubleArrayList.wrap(Arrays.copyOf(zPoints, shape.getZSize() + 1))); + } + +- ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) { ++ public ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) { // Paper - optimise collisions - public + super(shape); + int i = shape.getXSize() + 1; + int j = shape.getYSize() + 1; +@@ -27,6 +27,7 @@ public class ArrayVoxelShape extends VoxelShape { + } else { + throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape.")); + } ++ this.initCache(); // Paper - optimise collisions + } + + @Override +@@ -42,4 +43,5 @@ public class ArrayVoxelShape extends VoxelShape { + throw new IllegalArgumentException(); + } + } ++ + } +diff --git a/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java +index c25f409d63a50c5de1434db1d6b298935f106221..6f532d9aa613ecb0f5695b108ec6d7ed3598ca82 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java +@@ -4,13 +4,13 @@ import java.util.BitSet; + import net.minecraft.core.Direction; + + public final class BitSetDiscreteVoxelShape extends DiscreteVoxelShape { +- private final BitSet storage; +- private int xMin; +- private int yMin; +- private int zMin; +- private int xMax; +- private int yMax; +- private int zMax; ++ public final BitSet storage; // Paper - optimise collisions - public ++ public int xMin; // Paper - optimise collisions - public ++ public int yMin; // Paper - optimise collisions - public ++ public int zMin; // Paper - optimise collisions - public ++ public int xMax; // Paper - optimise collisions - public ++ public int yMax; // Paper - optimise collisions - public ++ public int zMax; // Paper - optimise collisions - public + + public BitSetDiscreteVoxelShape(int sizeX, int sizeY, int sizeZ) { + super(sizeX, sizeY, sizeZ); +@@ -150,46 +150,106 @@ public final class BitSetDiscreteVoxelShape extends DiscreteVoxelShape { + } + + protected static void forAllBoxes(DiscreteVoxelShape voxelSet, DiscreteVoxelShape.IntLineConsumer callback, boolean coalesce) { +- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = new BitSetDiscreteVoxelShape(voxelSet); ++ // Paper start - optimise collisions ++ // called with the shape of a VoxelShape, so we can expect the cache to exist ++ final io.papermc.paper.util.collisions.CachedShapeData cache = voxelSet.getOrCreateCachedShapeData(); ++ ++ final int sizeX = cache.sizeX(); ++ final int sizeY = cache.sizeY(); ++ final int sizeZ = cache.sizeZ(); ++ ++ int indexX; ++ int indexY = 0; ++ int indexZ; ++ ++ int incY = sizeZ; ++ int incX = sizeZ*sizeY; ++ ++ long[] bitset = cache.voxelSet(); ++ ++ // index = z + y*size_z + x*(size_z*size_y) ++ ++ if (!coalesce) { ++ // due to the odd selection of loop order (which does affect behavior, unfortunately) we can't simply ++ // increment an index in the Z loop, and have to perform this trash (keeping track of 3 counters) to avoid ++ // the multiplication ++ for (int y = 0; y < sizeY; ++y, indexY += incY) { ++ indexX = indexY; ++ for (int x = 0; x < sizeX; ++x, indexX += incX) { ++ indexZ = indexX; ++ for (int z = 0; z < sizeZ; ++z, ++indexZ) { ++ if ((bitset[indexZ >>> 6] & (1L << indexZ)) != 0L) { ++ callback.consume(x, y, z, x + 1, y + 1, z + 1); ++ } ++ } ++ } ++ } ++ } else { ++ // same notes about loop order as the above ++ // this branch is actually important to optimise, as it affects uncached toAabbs() (which affects optimize()) + +- for(int i = 0; i < bitSetDiscreteVoxelShape.ySize; ++i) { +- for(int j = 0; j < bitSetDiscreteVoxelShape.xSize; ++j) { +- int k = -1; ++ // only clone when we may write to it ++ bitset = bitset.clone(); + +- for(int l = 0; l <= bitSetDiscreteVoxelShape.zSize; ++l) { +- if (bitSetDiscreteVoxelShape.isFullWide(j, i, l)) { +- if (coalesce) { +- if (k == -1) { +- k = l; +- } +- } else { +- callback.consume(j, i, l, j + 1, i + 1, l + 1); ++ for (int y = 0; y < sizeY; ++y, indexY += incY) { ++ indexX = indexY; ++ for (int x = 0; x < sizeX; ++x, indexX += incX) { ++ for (int zIdx = indexX, endIndex = indexX + sizeZ; zIdx < endIndex;) { ++ final int firstSetZ = io.papermc.paper.util.collisions.FlatBitsetUtil.firstSet(bitset, zIdx, endIndex); ++ ++ if (firstSetZ == -1) { ++ break; + } +- } else if (k != -1) { +- int m = j; +- int n = i; +- bitSetDiscreteVoxelShape.clearZStrip(k, l, j, i); +- +- while(bitSetDiscreteVoxelShape.isZStripFull(k, l, m + 1, i)) { +- bitSetDiscreteVoxelShape.clearZStrip(k, l, m + 1, i); +- ++m; ++ ++ int lastSetZ = io.papermc.paper.util.collisions.FlatBitsetUtil.firstClear(bitset, firstSetZ, endIndex); ++ if (lastSetZ == -1) { ++ lastSetZ = endIndex; + } + +- while(bitSetDiscreteVoxelShape.isXZRectangleFull(j, m + 1, k, l, n + 1)) { +- for(int o = j; o <= m; ++o) { +- bitSetDiscreteVoxelShape.clearZStrip(k, l, o, n + 1); ++ io.papermc.paper.util.collisions.FlatBitsetUtil.clearRange(bitset, firstSetZ, lastSetZ); ++ ++ // try to merge neighbouring on the X axis ++ int endX = x + 1; // exclusive ++ for (int neighbourIdxStart = firstSetZ + incX, neighbourIdxEnd = lastSetZ + incX; ++ endX < sizeX && io.papermc.paper.util.collisions.FlatBitsetUtil.isRangeSet(bitset, neighbourIdxStart, neighbourIdxEnd); ++ neighbourIdxStart += incX, neighbourIdxEnd += incX) { ++ ++ ++endX; ++ io.papermc.paper.util.collisions.FlatBitsetUtil.clearRange(bitset, neighbourIdxStart, neighbourIdxEnd); ++ } ++ ++ // try to merge neighbouring on the Y axis ++ ++ int endY; // exclusive ++ int firstSetZY, lastSetZY; ++ y_merge: ++ for (endY = y + 1, firstSetZY = firstSetZ + incY, lastSetZY = lastSetZ + incY; endY < sizeY; ++ firstSetZY += incY, lastSetZY += incY) { ++ ++ // test the whole XZ range ++ for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX; ++ ++testX, start += incX, end += incX) { ++ if (!io.papermc.paper.util.collisions.FlatBitsetUtil.isRangeSet(bitset, start, end)) { ++ break y_merge; ++ } + } + +- ++n; ++ ++endY; ++ ++ // passed, so we can clear it ++ for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX; ++ ++testX, start += incX, end += incX) { ++ io.papermc.paper.util.collisions.FlatBitsetUtil.clearRange(bitset, start, end); ++ } + } + +- callback.consume(j, i, k, m + 1, n + 1, l); +- k = -1; ++ callback.consume(x, y, firstSetZ - indexX, endX, endY, lastSetZ - indexX); ++ zIdx = lastSetZ; + } + } + } + } +- ++ // Paper end - optimise collisions + } + + private boolean isZStripFull(int z1, int z2, int x, int y) { +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 68e89dbd79171627046e89699057964e44c40e7d..110405e6e70d980d3e09f04d79562b32a7413071 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java +@@ -7,6 +7,7 @@ import net.minecraft.util.Mth; + public final class CubeVoxelShape extends VoxelShape { + protected CubeVoxelShape(DiscreteVoxelShape voxels) { + super(voxels); ++ this.initCache(); // Paper - optimise collisions + } + + @Override +diff --git a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java +index b27ed92b2a87d4c20c1aa300202adfab896c99ea..d0a4547f95ee24283af77cfa130979d05915292f 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java +@@ -9,6 +9,71 @@ public abstract class DiscreteVoxelShape { + protected final int ySize; + protected final int zSize; + ++ // Paper start - optimise collisions ++ private io.papermc.paper.util.collisions.CachedShapeData cachedShapeData; ++ ++ public final io.papermc.paper.util.collisions.CachedShapeData getOrCreateCachedShapeData() { ++ if (this.cachedShapeData != null) { ++ return this.cachedShapeData; ++ } ++ ++ final DiscreteVoxelShape discreteVoxelShape = (DiscreteVoxelShape)(Object)this; ++ ++ final int sizeX = discreteVoxelShape.getXSize(); ++ final int sizeY = discreteVoxelShape.getYSize(); ++ final int sizeZ = discreteVoxelShape.getZSize(); ++ ++ final int maxIndex = sizeX * sizeY * sizeZ; // exclusive ++ ++ final int longsRequired = (maxIndex + (Long.SIZE - 1)) >>> 6; ++ long[] voxelSet; ++ ++ final boolean isEmpty = discreteVoxelShape.isEmpty(); ++ ++ if (discreteVoxelShape instanceof BitSetDiscreteVoxelShape bitsetShape) { ++ voxelSet = bitsetShape.storage.toLongArray(); ++ if (voxelSet.length < longsRequired) { ++ // happens when the later long values are 0L, so we need to resize ++ voxelSet = java.util.Arrays.copyOf(voxelSet, longsRequired); ++ } ++ } else { ++ voxelSet = new long[longsRequired]; ++ if (!isEmpty) { ++ final int mulX = sizeZ * sizeY; ++ for (int x = 0; x < sizeX; ++x) { ++ for (int y = 0; y < sizeY; ++y) { ++ for (int z = 0; z < sizeZ; ++z) { ++ if (discreteVoxelShape.isFull(x, y, z)) { ++ // index = z + y*size_z + x*(size_z*size_y) ++ final int index = z + y * sizeZ + x * mulX; ++ ++ voxelSet[index >>> 6] |= 1L << index; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ final boolean hasSingleAABB = sizeX == 1 && sizeY == 1 && sizeZ == 1 && !isEmpty && discreteVoxelShape.isFull(0, 0, 0); ++ ++ final int minFullX = discreteVoxelShape.firstFull(Direction.Axis.X); ++ final int minFullY = discreteVoxelShape.firstFull(Direction.Axis.Y); ++ final int minFullZ = discreteVoxelShape.firstFull(Direction.Axis.Z); ++ ++ final int maxFullX = discreteVoxelShape.lastFull(Direction.Axis.X); ++ final int maxFullY = discreteVoxelShape.lastFull(Direction.Axis.Y); ++ final int maxFullZ = discreteVoxelShape.lastFull(Direction.Axis.Z); ++ ++ return this.cachedShapeData = new io.papermc.paper.util.collisions.CachedShapeData( ++ sizeX, sizeY, sizeZ, voxelSet, ++ minFullX, minFullY, minFullZ, ++ maxFullX, maxFullY, maxFullZ, ++ isEmpty, hasSingleAABB ++ ); ++ } ++ // Paper end - optimise collisions ++ + protected DiscreteVoxelShape(int sizeX, int sizeY, int sizeZ) { + if (sizeX >= 0 && sizeY >= 0 && sizeZ >= 0) { + this.xSize = sizeX; +diff --git a/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java b/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java +index 7ec02a7849437a18860aa0df7d9ddd71b2447d4c..5e45e49ab09344cb95736f4124b1c6e002ef5b82 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java +@@ -4,8 +4,8 @@ import it.unimi.dsi.fastutil.doubles.AbstractDoubleList; + import it.unimi.dsi.fastutil.doubles.DoubleList; + + public class OffsetDoubleList extends AbstractDoubleList { +- private final DoubleList delegate; +- private final double offset; ++ public final DoubleList delegate; // Paper - optimise collisions - public ++ public final double offset; // Paper - optimise collisions - public + + public OffsetDoubleList(DoubleList oldList, double offset) { + this.delegate = oldList; +diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +index 9176735c08a75854209f24113b0e78332249dc4d..17785f7c709073a01928e8e6a57702f8357900f4 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +@@ -16,13 +16,43 @@ public final class Shapes { + public static final double EPSILON = 1.0E-7D; + public static final double BIG_EPSILON = 1.0E-6D; + private static final VoxelShape BLOCK = Util.make(() -> { +- DiscreteVoxelShape discreteVoxelShape = new BitSetDiscreteVoxelShape(1, 1, 1); +- discreteVoxelShape.fill(0, 0, 0); +- return new CubeVoxelShape(discreteVoxelShape); ++ // Paper start - optimise collisions - force arrayvoxelshape ++ final DiscreteVoxelShape shape = new BitSetDiscreteVoxelShape(1, 1, 1); ++ shape.fill(0, 0, 0); ++ ++ return new ArrayVoxelShape( ++ shape, ++ io.papermc.paper.util.CollisionUtil.ZERO_ONE, io.papermc.paper.util.CollisionUtil.ZERO_ONE, io.papermc.paper.util.CollisionUtil.ZERO_ONE ++ ); ++ // Paper end - optimise collisions - force arrayvoxelshape + }); + public static final VoxelShape INFINITY = box(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + private static final VoxelShape EMPTY = new ArrayVoxelShape(new BitSetDiscreteVoxelShape(0, 0, 0), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D}))); + ++ // Paper start - optimise collisions - force arrayvoxelshape ++ private static final DoubleArrayList[] PARTS_BY_BITS = new DoubleArrayList[] { ++ DoubleArrayList.wrap(generateCubeParts(1 << 0)), ++ DoubleArrayList.wrap(generateCubeParts(1 << 1)), ++ DoubleArrayList.wrap(generateCubeParts(1 << 2)), ++ DoubleArrayList.wrap(generateCubeParts(1 << 3)) ++ }; ++ ++ private static double[] generateCubeParts(final int parts) { ++ // note: parts is a power of two, so we do not need to worry about loss of precision here ++ // note: parts is from [2^0, 2^3] ++ final double inc = 1.0 / (double)parts; ++ ++ final double[] ret = new double[parts + 1]; ++ double val = 0.0; ++ for (int i = 0; i <= parts; ++i) { ++ ret[i] = val; ++ val += inc; ++ } ++ ++ return ret; ++ } ++ // Paper end - optimise collisions - force arrayvoxelshape ++ + public static VoxelShape empty() { + return EMPTY; + } +@@ -41,22 +71,39 @@ public final class Shapes { + + public static VoxelShape create(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { + if (!(maxX - minX < 1.0E-7D) && !(maxY - minY < 1.0E-7D) && !(maxZ - minZ < 1.0E-7D)) { +- int i = findBits(minX, maxX); +- int j = findBits(minY, maxY); +- int k = findBits(minZ, maxZ); +- if (i >= 0 && j >= 0 && k >= 0) { +- if (i == 0 && j == 0 && k == 0) { +- return block(); ++ // Paper start - optimise collisions ++ // force ArrayVoxelShape in every case ++ final int bitsX = findBits(minX, maxX); ++ final int bitsY = findBits(minY, maxY); ++ final int bitsZ = findBits(minZ, maxZ); ++ if (bitsX >= 0 && bitsY >= 0 && bitsZ >= 0) { ++ if (bitsX == 0 && bitsY == 0 && bitsZ == 0) { ++ return BLOCK; + } else { +- int l = 1 << i; +- int m = 1 << j; +- int n = 1 << k; +- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.withFilledBounds(l, m, n, (int)Math.round(minX * (double)l), (int)Math.round(minY * (double)m), (int)Math.round(minZ * (double)n), (int)Math.round(maxX * (double)l), (int)Math.round(maxY * (double)m), (int)Math.round(maxZ * (double)n)); +- return new CubeVoxelShape(bitSetDiscreteVoxelShape); ++ final int sizeX = 1 << bitsX; ++ final int sizeY = 1 << bitsY; ++ final int sizeZ = 1 << bitsZ; ++ final BitSetDiscreteVoxelShape shape = BitSetDiscreteVoxelShape.withFilledBounds( ++ sizeX, sizeY, sizeZ, ++ (int)Math.round(minX * (double)sizeX), (int)Math.round(minY * (double)sizeY), (int)Math.round(minZ * (double)sizeZ), ++ (int)Math.round(maxX * (double)sizeX), (int)Math.round(maxY * (double)sizeY), (int)Math.round(maxZ * (double)sizeZ) ++ ); ++ return new ArrayVoxelShape( ++ shape, ++ PARTS_BY_BITS[bitsX], ++ PARTS_BY_BITS[bitsY], ++ PARTS_BY_BITS[bitsZ] ++ ); + } + } else { +- return new ArrayVoxelShape(BLOCK.shape, (DoubleList)DoubleArrayList.wrap(new double[]{minX, maxX}), (DoubleList)DoubleArrayList.wrap(new double[]{minY, maxY}), (DoubleList)DoubleArrayList.wrap(new double[]{minZ, maxZ})); ++ return new ArrayVoxelShape( ++ BLOCK.shape, ++ minX == 0.0 && maxX == 1.0 ? io.papermc.paper.util.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minX, maxX }), ++ minY == 0.0 && maxY == 1.0 ? io.papermc.paper.util.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minY, maxY }), ++ minZ == 0.0 && maxZ == 1.0 ? io.papermc.paper.util.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minZ, maxZ }) ++ ); + } ++ // Paper end - optimise collisions + } else { + return empty(); + } +@@ -95,67 +142,53 @@ public final class Shapes { + } + + public static VoxelShape or(VoxelShape first, VoxelShape... others) { +- return Arrays.stream(others).reduce(first, Shapes::or); ++ // Paper start - optimise collisions ++ int size = others.length; ++ if (size == 0) { ++ return first; ++ } ++ ++ // reduce complexity of joins by splitting the merges ++ ++ // add extra slot for first shape ++ ++size; ++ final VoxelShape[] tmp = Arrays.copyOf(others, size); ++ // insert first shape ++ tmp[size - 1] = first; ++ ++ while (size > 1) { ++ int newSize = 0; ++ for (int i = 0; i < size; i += 2) { ++ final int next = i + 1; ++ if (next >= size) { ++ // nothing to merge with, so leave it for next iteration ++ tmp[newSize++] = tmp[i]; ++ break; ++ } else { ++ // merge with adjacent ++ final VoxelShape one = tmp[i]; ++ final VoxelShape second = tmp[next]; ++ ++ tmp[newSize++] = Shapes.or(one, second); ++ } ++ } ++ size = newSize; ++ } ++ ++ return tmp[0]; ++ // Paper end - optimise collisions + } + + public static VoxelShape join(VoxelShape first, VoxelShape second, BooleanOp function) { +- return joinUnoptimized(first, second, function).optimize(); ++ return io.papermc.paper.util.CollisionUtil.joinOptimized(first, second, function); // Paper - optimise collisions + } + + public static VoxelShape joinUnoptimized(VoxelShape one, VoxelShape two, BooleanOp function) { +- if (function.apply(false, false)) { +- throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException()); +- } else if (one == two) { +- return function.apply(true, true) ? one : empty(); +- } else { +- boolean bl = function.apply(true, false); +- boolean bl2 = function.apply(false, true); +- if (one.isEmpty()) { +- return bl2 ? two : empty(); +- } else if (two.isEmpty()) { +- return bl ? one : empty(); +- } else { +- IndexMerger indexMerger = createIndexMerger(1, one.getCoords(Direction.Axis.X), two.getCoords(Direction.Axis.X), bl, bl2); +- IndexMerger indexMerger2 = createIndexMerger(indexMerger.size() - 1, one.getCoords(Direction.Axis.Y), two.getCoords(Direction.Axis.Y), bl, bl2); +- IndexMerger indexMerger3 = createIndexMerger((indexMerger.size() - 1) * (indexMerger2.size() - 1), one.getCoords(Direction.Axis.Z), two.getCoords(Direction.Axis.Z), bl, bl2); +- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.join(one.shape, two.shape, indexMerger, indexMerger2, indexMerger3, function); +- return (VoxelShape)(indexMerger instanceof DiscreteCubeMerger && indexMerger2 instanceof DiscreteCubeMerger && indexMerger3 instanceof DiscreteCubeMerger ? new CubeVoxelShape(bitSetDiscreteVoxelShape) : new ArrayVoxelShape(bitSetDiscreteVoxelShape, indexMerger.getList(), indexMerger2.getList(), indexMerger3.getList())); +- } +- } ++ return io.papermc.paper.util.CollisionUtil.joinUnoptimized(one, two, function); // Paper - optimise collisions + } + + public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) { +- if (predicate.apply(false, false)) { +- throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException()); +- } else { +- boolean bl = shape1.isEmpty(); +- boolean bl2 = shape2.isEmpty(); +- if (!bl && !bl2) { +- if (shape1 == shape2) { +- return predicate.apply(true, true); +- } else { +- boolean bl3 = predicate.apply(true, false); +- boolean bl4 = predicate.apply(false, true); +- +- for(Direction.Axis axis : AxisCycle.AXIS_VALUES) { +- if (shape1.max(axis) < shape2.min(axis) - 1.0E-7D) { +- return bl3 || bl4; +- } +- +- if (shape2.max(axis) < shape1.min(axis) - 1.0E-7D) { +- return bl3 || bl4; +- } +- } +- +- IndexMerger indexMerger = createIndexMerger(1, shape1.getCoords(Direction.Axis.X), shape2.getCoords(Direction.Axis.X), bl3, bl4); +- IndexMerger indexMerger2 = createIndexMerger(indexMerger.size() - 1, shape1.getCoords(Direction.Axis.Y), shape2.getCoords(Direction.Axis.Y), bl3, bl4); +- IndexMerger indexMerger3 = createIndexMerger((indexMerger.size() - 1) * (indexMerger2.size() - 1), shape1.getCoords(Direction.Axis.Z), shape2.getCoords(Direction.Axis.Z), bl3, bl4); +- return joinIsNotEmpty(indexMerger, indexMerger2, indexMerger3, shape1.shape, shape2.shape, predicate); +- } +- } else { +- return predicate.apply(!bl, !bl2); +- } +- } ++ return io.papermc.paper.util.CollisionUtil.isJoinNonEmpty(shape1, shape2, predicate); // Paper - optimise collisions + } + + private static boolean joinIsNotEmpty(IndexMerger mergedX, IndexMerger mergedY, IndexMerger mergedZ, DiscreteVoxelShape shape1, DiscreteVoxelShape shape2, BooleanOp predicate) { +@@ -181,69 +214,119 @@ public final class Shapes { + } + + public static boolean blockOccudes(VoxelShape shape, VoxelShape neighbor, Direction direction) { +- if (shape == block() && neighbor == block()) { ++ // Paper start - optimise collisions ++ final boolean firstBlock = shape == BLOCK; ++ final boolean secondBlock = neighbor == BLOCK; ++ ++ if (firstBlock & secondBlock) { + return true; +- } else if (neighbor.isEmpty()) { ++ } ++ ++ if (shape.isEmpty() | neighbor.isEmpty()) { + return false; +- } else { +- Direction.Axis axis = direction.getAxis(); +- Direction.AxisDirection axisDirection = direction.getAxisDirection(); +- VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? shape : neighbor; +- VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? neighbor : shape; +- BooleanOp booleanOp = axisDirection == Direction.AxisDirection.POSITIVE ? BooleanOp.ONLY_FIRST : BooleanOp.ONLY_SECOND; +- return DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0D, 1.0E-7D) && DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0D, 1.0E-7D) && !joinIsNotEmpty(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), booleanOp); + } ++ ++ // we optimise getOpposite, so we can use it ++ // secondly, use our cache to retrieve sliced shape ++ final VoxelShape newFirst = shape.getFaceShapeClamped(direction); ++ if (newFirst.isEmpty()) { ++ return false; ++ } ++ final VoxelShape newSecond = neighbor.getFaceShapeClamped(direction.getOpposite()); ++ if (newSecond.isEmpty()) { ++ return false; ++ } ++ ++ return !joinIsNotEmpty(newFirst, newSecond, BooleanOp.ONLY_FIRST); ++ // Paper end - optimise collisions + } + + public static VoxelShape getFaceShape(VoxelShape shape, Direction direction) { +- if (shape == block()) { +- return block(); +- } else { +- Direction.Axis axis = direction.getAxis(); +- boolean bl; +- int i; +- if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) { +- bl = DoubleMath.fuzzyEquals(shape.max(axis), 1.0D, 1.0E-7D); +- i = shape.shape.getSize(axis) - 1; +- } else { +- bl = DoubleMath.fuzzyEquals(shape.min(axis), 0.0D, 1.0E-7D); +- i = 0; +- } ++ return shape.getFaceShapeClamped(direction); // Paper - optimise collisions ++ } + +- return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i)); +- } ++ // Paper start - optimise collisions ++ private static boolean mergedMayOccludeBlock(final VoxelShape shape1, final VoxelShape shape2) { ++ // if the combined bounds of the two shapes cannot occlude, then neither can the merged ++ final AABB bounds1 = shape1.bounds(); ++ final AABB bounds2 = shape2.bounds(); ++ ++ final double minX = Math.min(bounds1.minX, bounds2.minX); ++ final double minY = Math.min(bounds1.minY, bounds2.minY); ++ final double minZ = Math.min(bounds1.minZ, bounds2.minZ); ++ ++ final double maxX = Math.max(bounds1.maxX, bounds2.maxX); ++ final double maxY = Math.max(bounds1.maxY, bounds2.maxY); ++ final double maxZ = Math.max(bounds1.maxZ, bounds2.maxZ); ++ ++ return (minX <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && maxX >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) && ++ (minY <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && maxY >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) && ++ (minZ <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && maxZ >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)); + } ++ // Paper end - optimise collisions + + public static boolean mergedFaceOccludes(VoxelShape one, VoxelShape two, Direction direction) { +- if (one != block() && two != block()) { +- Direction.Axis axis = direction.getAxis(); +- Direction.AxisDirection axisDirection = direction.getAxisDirection(); +- VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? one : two; +- VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? two : one; +- if (!DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0D, 1.0E-7D)) { +- voxelShape = empty(); +- } ++ // Paper start - optimise collisions ++ // see if any of the shapes on their own occludes, only if cached ++ if (one.occludesFullBlockIfCached() || two.occludesFullBlockIfCached()) { ++ return true; ++ } + +- if (!DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0D, 1.0E-7D)) { +- voxelShape2 = empty(); +- } ++ if (one.isEmpty() & two.isEmpty()) { ++ return false; ++ } + +- return !joinIsNotEmpty(block(), joinUnoptimized(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), BooleanOp.OR), BooleanOp.ONLY_FIRST); +- } else { ++ // we optimise getOpposite, so we can use it ++ // secondly, use our cache to retrieve sliced shape ++ final VoxelShape newFirst = one.getFaceShapeClamped(direction); ++ final VoxelShape newSecond = two.getFaceShapeClamped(direction.getOpposite()); ++ ++ // see if any of the shapes on their own occludes, only if cached ++ if (newFirst.occludesFullBlockIfCached() || newSecond.occludesFullBlockIfCached()) { + return true; + } ++ ++ final boolean firstEmpty = newFirst.isEmpty(); ++ final boolean secondEmpty = newSecond.isEmpty(); ++ ++ if (firstEmpty & secondEmpty) { ++ return false; ++ } ++ ++ if (firstEmpty | secondEmpty) { ++ return secondEmpty ? newFirst.occludesFullBlock() : newSecond.occludesFullBlock(); ++ } ++ ++ if (newFirst == newSecond) { ++ return newFirst.occludesFullBlock(); ++ } ++ ++ return mergedMayOccludeBlock(newFirst, newSecond) && newFirst.orUnoptimized(newSecond).occludesFullBlock(); ++ // Paper end - optimise collisions + } + + public static boolean faceShapeOccludes(VoxelShape one, VoxelShape two) { +- if (one != block() && two != block()) { +- if (one.isEmpty() && two.isEmpty()) { +- return false; +- } else { +- return !joinIsNotEmpty(block(), joinUnoptimized(one, two, BooleanOp.OR), BooleanOp.ONLY_FIRST); +- } +- } else { ++ // Paper start - optimise collisions ++ if (one.occludesFullBlockIfCached() || two.occludesFullBlockIfCached()) { + return true; + } ++ ++ final boolean s1Empty = one.isEmpty(); ++ final boolean s2Empty = two.isEmpty(); ++ if (s1Empty & s2Empty) { ++ return false; ++ } ++ ++ if (s1Empty | s2Empty) { ++ return s2Empty ? one.occludesFullBlock() : two.occludesFullBlock(); ++ } ++ ++ if (one == two) { ++ return one.occludesFullBlock(); ++ } ++ ++ return mergedMayOccludeBlock(one, two) && (one.orUnoptimized(two)).occludesFullBlock(); ++ // Paper end - optimise collisions + } + + @VisibleForTesting +diff --git a/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java b/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java +index cf469f9daa81da8bc330c9cac7e813db87f9f9af..d9256710e815a5cb55409a80d59df2029b98c0d7 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java +@@ -12,6 +12,7 @@ public class SliceShape extends VoxelShape { + super(makeSlice(shape.shape, axis, sliceWidth)); + this.delegate = shape; + this.axis = axis; ++ this.initCache(); // Paper - optimise collisions + } + + private static DiscreteVoxelShape makeSlice(DiscreteVoxelShape voxelSet, Direction.Axis axis, int sliceWidth) { +diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +index 15e2dfa9a17b4f19768c0cde0ad8031f0122cd33..6bd6385ad82481a099f3556ed2dbd3744888fc34 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +@@ -16,30 +16,438 @@ import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.Vec3; + + public abstract class VoxelShape { +- protected final DiscreteVoxelShape shape; ++ public final DiscreteVoxelShape shape; // Paper - optimise collisions - public + @Nullable + private VoxelShape[] faces; + +- VoxelShape(DiscreteVoxelShape voxels) { ++ // Paper start - optimise collisions ++ private double offsetX; ++ private double offsetY; ++ private double offsetZ; ++ private AABB singleAABBRepresentation; ++ private double[] rootCoordinatesX; ++ private double[] rootCoordinatesY; ++ private double[] rootCoordinatesZ; ++ ++ private io.papermc.paper.util.collisions.CachedShapeData cachedShapeData; ++ private boolean isEmpty; ++ ++ private io.papermc.paper.util.collisions.CachedToAABBs cachedToAABBs; ++ private AABB cachedBounds; ++ ++ private Boolean isFullBlock; ++ ++ private Boolean occludesFullBlock; ++ ++ // must be power of two ++ private static final int MERGED_CACHE_SIZE = 16; ++ ++ private io.papermc.paper.util.collisions.MergedORCache[] mergedORCache; ++ ++ public final double offsetX() { ++ return this.offsetX; ++ } ++ ++ public final double offsetY() { ++ return this.offsetY; ++ } ++ ++ public final double offsetZ() { ++ return this.offsetZ; ++ } ++ ++ public final AABB getSingleAABBRepresentation() { ++ return this.singleAABBRepresentation; ++ } ++ ++ public final double[] rootCoordinatesX() { ++ return this.rootCoordinatesX; ++ } ++ ++ public final double[] rootCoordinatesY() { ++ return this.rootCoordinatesY; ++ } ++ ++ public final double[] rootCoordinatesZ() { ++ return this.rootCoordinatesZ; ++ } ++ ++ private static double[] extractRawArray(final DoubleList list) { ++ if (list instanceof it.unimi.dsi.fastutil.doubles.DoubleArrayList rawList) { ++ final double[] raw = rawList.elements(); ++ final int expected = rawList.size(); ++ if (raw.length == expected) { ++ return raw; ++ } else { ++ return java.util.Arrays.copyOf(raw, expected); ++ } ++ } else { ++ return list.toDoubleArray(); ++ } ++ } ++ ++ public final void initCache() { ++ this.cachedShapeData = this.shape.getOrCreateCachedShapeData(); ++ this.isEmpty = this.cachedShapeData.isEmpty(); ++ ++ final DoubleList xList = this.getCoords(Direction.Axis.X); ++ final DoubleList yList = this.getCoords(Direction.Axis.Y); ++ final DoubleList zList = this.getCoords(Direction.Axis.Z); ++ ++ if (xList instanceof OffsetDoubleList offsetDoubleList) { ++ this.offsetX = offsetDoubleList.offset; ++ this.rootCoordinatesX = extractRawArray(offsetDoubleList.delegate); ++ } else { ++ this.rootCoordinatesX = extractRawArray(xList); ++ } ++ ++ if (yList instanceof OffsetDoubleList offsetDoubleList) { ++ this.offsetY = offsetDoubleList.offset; ++ this.rootCoordinatesY = extractRawArray(offsetDoubleList.delegate); ++ } else { ++ this.rootCoordinatesY = extractRawArray(yList); ++ } ++ ++ if (zList instanceof OffsetDoubleList offsetDoubleList) { ++ this.offsetZ = offsetDoubleList.offset; ++ this.rootCoordinatesZ = extractRawArray(offsetDoubleList.delegate); ++ } else { ++ this.rootCoordinatesZ = extractRawArray(zList); ++ } ++ ++ if (this.cachedShapeData.hasSingleAABB()) { ++ this.singleAABBRepresentation = new AABB( ++ this.rootCoordinatesX[0] + this.offsetX, this.rootCoordinatesY[0] + this.offsetY, this.rootCoordinatesZ[0] + this.offsetZ, ++ this.rootCoordinatesX[1] + this.offsetX, this.rootCoordinatesY[1] + this.offsetY, this.rootCoordinatesZ[1] + this.offsetZ ++ ); ++ this.cachedBounds = this.singleAABBRepresentation; ++ } ++ } ++ ++ public final io.papermc.paper.util.collisions.CachedShapeData getCachedVoxelData() { ++ return this.cachedShapeData; ++ } ++ ++ private VoxelShape[] faceShapeClampedCache; ++ ++ public final VoxelShape getFaceShapeClamped(final Direction direction) { ++ if (this.isEmpty) { ++ return (VoxelShape)(Object)this; ++ } ++ if ((VoxelShape)(Object)this == Shapes.block()) { ++ return (VoxelShape)(Object)this; ++ } ++ ++ VoxelShape[] cache = this.faceShapeClampedCache; ++ if (cache != null) { ++ final VoxelShape ret = cache[direction.ordinal()]; ++ if (ret != null) { ++ return ret; ++ } ++ } ++ ++ ++ if (cache == null) { ++ this.faceShapeClampedCache = cache = new VoxelShape[6]; ++ } ++ ++ final Direction.Axis axis = direction.getAxis(); ++ ++ final VoxelShape ret; ++ ++ if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) { ++ if (DoubleMath.fuzzyEquals(this.max(axis), 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) { ++ ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, this.shape.getSize(axis) - 1)); ++ } else { ++ ret = Shapes.empty(); ++ } ++ } else { ++ if (DoubleMath.fuzzyEquals(this.min(axis), 0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) { ++ ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, 0)); ++ } else { ++ ret = Shapes.empty(); ++ } ++ } ++ ++ cache[direction.ordinal()] = ret; ++ ++ return ret; ++ } ++ ++ private static VoxelShape tryForceBlock(final VoxelShape other) { ++ if (other == Shapes.block()) { ++ return other; ++ } ++ ++ final AABB otherAABB = other.getSingleAABBRepresentation(); ++ if (otherAABB == null) { ++ return other; ++ } ++ ++ if (Shapes.block().getSingleAABBRepresentation().equals(otherAABB)) { ++ return Shapes.block(); ++ } ++ ++ return other; ++ } ++ ++ private boolean computeOccludesFullBlock() { ++ if (this.isEmpty) { ++ this.occludesFullBlock = Boolean.FALSE; ++ return false; ++ } ++ ++ if (this.isFullBlock()) { ++ this.occludesFullBlock = Boolean.TRUE; ++ return true; ++ } ++ ++ final AABB singleAABB = this.singleAABBRepresentation; ++ if (singleAABB != null) { ++ // check if the bounding box encloses the full cube ++ final boolean ret = ++ (singleAABB.minY <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && singleAABB.maxY >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) && ++ (singleAABB.minX <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && singleAABB.maxX >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) && ++ (singleAABB.minZ <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && singleAABB.maxZ >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)); ++ this.occludesFullBlock = Boolean.valueOf(ret); ++ return ret; ++ } ++ ++ final boolean ret = !Shapes.joinIsNotEmpty(Shapes.block(), ((VoxelShape)(Object)this), BooleanOp.ONLY_FIRST); ++ this.occludesFullBlock = Boolean.valueOf(ret); ++ return ret; ++ } ++ ++ public final boolean occludesFullBlock() { ++ final Boolean ret = this.occludesFullBlock; ++ if (ret != null) { ++ return ret.booleanValue(); ++ } ++ ++ return this.computeOccludesFullBlock(); ++ } ++ ++ public final boolean occludesFullBlockIfCached() { ++ final Boolean ret = this.occludesFullBlock; ++ return ret != null ? ret.booleanValue() : false; ++ } ++ ++ private static int hash(final VoxelShape key) { ++ return it.unimi.dsi.fastutil.HashCommon.mix(System.identityHashCode(key)); ++ } ++ ++ public final VoxelShape orUnoptimized(final VoxelShape other) { ++ // don't cache simple cases ++ if (((VoxelShape)(Object)this) == other) { ++ return other; ++ } ++ ++ if (this.isEmpty) { ++ return other; ++ } ++ ++ if (other.isEmpty()) { ++ return (VoxelShape)(Object)this; ++ } ++ ++ // try this cache first ++ final int thisCacheKey = hash(other) & (MERGED_CACHE_SIZE - 1); ++ final io.papermc.paper.util.collisions.MergedORCache cached = this.mergedORCache == null ? null : this.mergedORCache[thisCacheKey]; ++ if (cached != null && cached.key() == other) { ++ return cached.result(); ++ } ++ ++ // try other cache ++ final int otherCacheKey = hash(this) & (MERGED_CACHE_SIZE - 1); ++ final io.papermc.paper.util.collisions.MergedORCache otherCache = other.mergedORCache == null ? null : other.mergedORCache[otherCacheKey]; ++ if (otherCache != null && otherCache.key() == this) { ++ return otherCache.result(); ++ } ++ ++ // note: unsure if joinUnoptimized(1, 2, OR) == joinUnoptimized(2, 1, OR) for all cases ++ final VoxelShape result = Shapes.joinUnoptimized(this, other, BooleanOp.OR); ++ ++ if (cached != null && otherCache == null) { ++ // try to use second cache instead of replacing an entry in this cache ++ if (other.mergedORCache == null) { ++ other.mergedORCache = new io.papermc.paper.util.collisions.MergedORCache[MERGED_CACHE_SIZE]; ++ } ++ other.mergedORCache[otherCacheKey] = new io.papermc.paper.util.collisions.MergedORCache(this, result); ++ } else { ++ // line is not occupied or other cache line is full ++ // always bias to replace this cache, as this cache is the first we check ++ if (this.mergedORCache == null) { ++ this.mergedORCache = new io.papermc.paper.util.collisions.MergedORCache[MERGED_CACHE_SIZE]; ++ } ++ this.mergedORCache[thisCacheKey] = new io.papermc.paper.util.collisions.MergedORCache(other, result); ++ } ++ ++ return result; ++ } ++ ++ private boolean computeFullBlock() { ++ Boolean ret; ++ if (this.isEmpty) { ++ ret = Boolean.FALSE; ++ } else if ((VoxelShape)(Object)this == Shapes.block()) { ++ ret = Boolean.TRUE; ++ } else { ++ final AABB singleAABB = this.singleAABBRepresentation; ++ if (singleAABB == null) { ++ final io.papermc.paper.util.collisions.CachedShapeData shapeData = this.cachedShapeData; ++ final int sMinX = shapeData.minFullX(); ++ final int sMinY = shapeData.minFullY(); ++ final int sMinZ = shapeData.minFullZ(); ++ ++ final int sMaxX = shapeData.maxFullX(); ++ final int sMaxY = shapeData.maxFullY(); ++ final int sMaxZ = shapeData.maxFullZ(); ++ ++ if (Math.abs(this.rootCoordinatesX[sMinX] + this.offsetX) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(this.rootCoordinatesY[sMinY] + this.offsetY) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(this.rootCoordinatesZ[sMinZ] + this.offsetZ) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && ++ ++ Math.abs(1.0 - (this.rootCoordinatesX[sMaxX] + this.offsetX)) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(1.0 - (this.rootCoordinatesY[sMaxY] + this.offsetY)) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(1.0 - (this.rootCoordinatesZ[sMaxZ] + this.offsetZ)) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) { ++ ++ // index = z + y*sizeZ + x*(sizeZ*sizeY) ++ ++ final int sizeY = shapeData.sizeY(); ++ final int sizeZ = shapeData.sizeZ(); ++ ++ final long[] bitset = shapeData.voxelSet(); ++ ++ ret = Boolean.TRUE; ++ ++ check_full: ++ for (int x = sMinX; x < sMaxX; ++x) { ++ for (int y = sMinY; y < sMaxY; ++y) { ++ final int baseIndex = y*sizeZ + x*(sizeZ*sizeY); ++ if (!io.papermc.paper.util.collisions.FlatBitsetUtil.isRangeSet(bitset, baseIndex + sMinZ, baseIndex + sMaxZ)) { ++ ret = Boolean.FALSE; ++ break check_full; ++ } ++ } ++ } ++ } else { ++ ret = Boolean.FALSE; ++ } ++ } else { ++ ret = Boolean.valueOf( ++ Math.abs(singleAABB.minX) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(singleAABB.minY) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(singleAABB.minZ) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && ++ ++ Math.abs(1.0 - singleAABB.maxX) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(1.0 - singleAABB.maxY) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(1.0 - singleAABB.maxZ) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON ++ ); ++ } ++ } ++ ++ this.isFullBlock = ret; ++ ++ return ret.booleanValue(); ++ } ++ ++ public boolean isFullBlock() { ++ final Boolean ret = this.isFullBlock; ++ ++ if (ret != null) { ++ return ret.booleanValue(); ++ } ++ ++ return this.computeFullBlock(); ++ } ++ // Paper end - optimise collisions ++ ++ protected VoxelShape(DiscreteVoxelShape voxels) { // Paper - protected + this.shape = voxels; + } + + public double min(Direction.Axis axis) { +- int i = this.shape.firstFull(axis); +- return i >= this.shape.getSize(axis) ? Double.POSITIVE_INFINITY : this.get(axis, i); ++ // Paper start - optimise collisions ++ final io.papermc.paper.util.collisions.CachedShapeData shapeData = this.cachedShapeData; ++ switch (axis) { ++ case X: { ++ final int idx = shapeData.minFullX(); ++ return idx >= shapeData.sizeX() ? Double.POSITIVE_INFINITY : (this.rootCoordinatesX[idx] + this.offsetX); ++ } ++ case Y: { ++ final int idx = shapeData.minFullY(); ++ return idx >= shapeData.sizeY() ? Double.POSITIVE_INFINITY : (this.rootCoordinatesY[idx] + this.offsetY); ++ } ++ case Z: { ++ final int idx = shapeData.minFullZ(); ++ return idx >= shapeData.sizeZ() ? Double.POSITIVE_INFINITY : (this.rootCoordinatesZ[idx] + this.offsetZ); ++ } ++ default: { ++ // should never get here ++ return Double.POSITIVE_INFINITY; ++ } ++ } ++ // Paper end - optimise collisions + } + + public double max(Direction.Axis axis) { +- int i = this.shape.lastFull(axis); +- return i <= 0 ? Double.NEGATIVE_INFINITY : this.get(axis, i); ++ // Paper start - optimise collisions ++ final io.papermc.paper.util.collisions.CachedShapeData shapeData = this.cachedShapeData; ++ switch (axis) { ++ case X: { ++ final int idx = shapeData.maxFullX(); ++ return idx <= 0 ? Double.NEGATIVE_INFINITY : (this.rootCoordinatesX[idx] + this.offsetX); ++ } ++ case Y: { ++ final int idx = shapeData.maxFullY(); ++ return idx <= 0 ? Double.NEGATIVE_INFINITY : (this.rootCoordinatesY[idx] + this.offsetY); ++ } ++ case Z: { ++ final int idx = shapeData.maxFullZ(); ++ return idx <= 0 ? Double.NEGATIVE_INFINITY : (this.rootCoordinatesZ[idx] + this.offsetZ); ++ } ++ default: { ++ // should never get here ++ return Double.NEGATIVE_INFINITY; ++ } ++ } ++ // Paper end - optimise collisions + } + + public AABB bounds() { +- if (this.isEmpty()) { +- throw (UnsupportedOperationException)Util.pauseInIde(new UnsupportedOperationException("No bounds for empty shape.")); +- } else { +- return new AABB(this.min(Direction.Axis.X), this.min(Direction.Axis.Y), this.min(Direction.Axis.Z), this.max(Direction.Axis.X), this.max(Direction.Axis.Y), this.max(Direction.Axis.Z)); ++ // Paper start - optimise collisions ++ if (this.isEmpty) { ++ throw Util.pauseInIde(new UnsupportedOperationException("No bounds for empty shape.")); + } ++ AABB cached = this.cachedBounds; ++ if (cached != null) { ++ return cached; ++ } ++ ++ final io.papermc.paper.util.collisions.CachedShapeData shapeData = this.cachedShapeData; ++ ++ final double[] coordsX = this.rootCoordinatesX; ++ final double[] coordsY = this.rootCoordinatesY; ++ final double[] coordsZ = this.rootCoordinatesZ; ++ ++ final double offX = this.offsetX; ++ final double offY = this.offsetY; ++ final double offZ = this.offsetZ; ++ ++ // note: if not empty, then there is one full AABB so no bounds checks are needed on the minFull/maxFull indices ++ cached = new AABB( ++ coordsX[shapeData.minFullX()] + offX, ++ coordsY[shapeData.minFullY()] + offY, ++ coordsZ[shapeData.minFullZ()] + offZ, ++ ++ coordsX[shapeData.maxFullX()] + offX, ++ coordsY[shapeData.maxFullY()] + offY, ++ coordsZ[shapeData.maxFullZ()] + offZ ++ ); ++ ++ this.cachedBounds = cached; ++ return cached; ++ // Paper end - optimise collisions + } + + public VoxelShape singleEncompassing() { +@@ -53,19 +461,106 @@ public abstract class VoxelShape { + protected abstract DoubleList getCoords(Direction.Axis axis); + + public boolean isEmpty() { +- return this.shape.isEmpty(); ++ return this.isEmpty; // Paper - optimise collisions + } + ++ // Paper start - optimise collisions ++ private static DoubleList offsetList(final DoubleList src, final double by) { ++ if (src instanceof OffsetDoubleList offsetDoubleList) { ++ return new OffsetDoubleList(offsetDoubleList.delegate, by + offsetDoubleList.offset); ++ } ++ return new OffsetDoubleList(src, by); ++ } ++ // Paper end - optimise collisions ++ + public VoxelShape move(double x, double y, double z) { +- return (VoxelShape)(this.isEmpty() ? Shapes.empty() : new ArrayVoxelShape(this.shape, (DoubleList)(new OffsetDoubleList(this.getCoords(Direction.Axis.X), x)), (DoubleList)(new OffsetDoubleList(this.getCoords(Direction.Axis.Y), y)), (DoubleList)(new OffsetDoubleList(this.getCoords(Direction.Axis.Z), z)))); ++ // Paper start - optimise collisions ++ if (this.isEmpty) { ++ return Shapes.empty(); ++ } ++ ++ final ArrayVoxelShape ret = new ArrayVoxelShape( ++ this.shape, ++ offsetList(this.getCoords(Direction.Axis.X), x), ++ offsetList(this.getCoords(Direction.Axis.Y), y), ++ offsetList(this.getCoords(Direction.Axis.Z), z) ++ ); ++ ++ final io.papermc.paper.util.collisions.CachedToAABBs cachedToAABBs = this.cachedToAABBs; ++ if (cachedToAABBs != null) { ++ ((VoxelShape)ret).cachedToAABBs = io.papermc.paper.util.collisions.CachedToAABBs.offset(cachedToAABBs, x, y, z); ++ } ++ ++ return ret; ++ // Paper end - optimise collisions + } + + public VoxelShape optimize() { +- VoxelShape[] voxelShapes = new VoxelShape[]{Shapes.empty()}; +- this.forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> { +- voxelShapes[0] = Shapes.joinUnoptimized(voxelShapes[0], Shapes.box(minX, minY, minZ, maxX, maxY, maxZ), BooleanOp.OR); +- }); +- return voxelShapes[0]; ++ // Paper start - optimise collisions ++ // Optimise merge strategy to increase the number of simple joins, and additionally forward the toAabbs cache ++ // to result ++ if (this.isEmpty) { ++ return Shapes.empty(); ++ } ++ ++ if (this.singleAABBRepresentation != null) { ++ // note: the isFullBlock() is fuzzy, and Shapes.create() is also fuzzy which would return block() ++ return this.isFullBlock() ? Shapes.block() : this; ++ } ++ ++ final List aabbs = this.toAabbs(); ++ ++ if (aabbs.size() == 1) { ++ final AABB singleAABB = aabbs.get(0); ++ final VoxelShape ret = Shapes.create(singleAABB); ++ ++ // forward AABB cache ++ if (ret.cachedToAABBs == null) { ++ ret.cachedToAABBs = this.cachedToAABBs; ++ } ++ ++ return ret; ++ } else { ++ // reduce complexity of joins by splitting the merges (old complexity: n^2, new: nlogn) ++ ++ // set up flat array so that this merge is done in-place ++ final VoxelShape[] tmp = new VoxelShape[aabbs.size()]; ++ ++ // initialise as unmerged ++ for (int i = 0, len = aabbs.size(); i < len; ++i) { ++ tmp[i] = Shapes.create(aabbs.get(i)); ++ } ++ ++ int size = aabbs.size(); ++ while (size > 1) { ++ int newSize = 0; ++ for (int i = 0; i < size; i += 2) { ++ final int next = i + 1; ++ if (next >= size) { ++ // nothing to merge with, so leave it for next iteration ++ tmp[newSize++] = tmp[i]; ++ break; ++ } else { ++ // merge with adjacent ++ final VoxelShape first = tmp[i]; ++ final VoxelShape second = tmp[next]; ++ ++ tmp[newSize++] = Shapes.joinUnoptimized(first, second, BooleanOp.OR); ++ } ++ } ++ size = newSize; ++ } ++ ++ final VoxelShape ret = tmp[0]; ++ ++ // forward AABB cache ++ if (ret.cachedToAABBs == null) { ++ ret.cachedToAABBs = this.cachedToAABBs; ++ } ++ ++ return ret; ++ } ++ // Paper end - optimise collisions + } + + public void forAllEdges(Shapes.DoubleLineConsumer consumer) { +@@ -83,12 +578,43 @@ public abstract class VoxelShape { + }, true); + } + ++ // Paper start - optimise collisions ++ private List toAabbsUncached() { ++ final List ret = new java.util.ArrayList<>(); ++ if (this.singleAABBRepresentation != null) { ++ ret.add(this.singleAABBRepresentation); ++ } else { ++ this.forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> { ++ ret.add(new AABB(minX, minY, minZ, maxX, maxY, maxZ)); ++ }); ++ } ++ ++ // cache result ++ this.cachedToAABBs = new io.papermc.paper.util.collisions.CachedToAABBs(ret, false, 0.0, 0.0, 0.0); ++ ++ return ret; ++ } ++ // Paper end - optimise collisions ++ + public List toAabbs() { +- List list = Lists.newArrayList(); +- this.forAllBoxes((x1, y1, z1, x2, y2, z2) -> { +- list.add(new AABB(x1, y1, z1, x2, y2, z2)); +- }); +- return list; ++ // Paper start - optimise collisions ++ io.papermc.paper.util.collisions.CachedToAABBs cachedToAABBs = this.cachedToAABBs; ++ if (cachedToAABBs != null) { ++ if (!cachedToAABBs.isOffset()) { ++ return cachedToAABBs.aabbs(); ++ } ++ ++ // all we need to do is offset the cache ++ cachedToAABBs = cachedToAABBs.removeOffset(); ++ // update cache ++ this.cachedToAABBs = cachedToAABBs; ++ ++ return cachedToAABBs.aabbs(); ++ } ++ ++ // make new cache ++ return this.toAabbsUncached(); ++ // Paper end - optimise collisions + } + + public double min(Direction.Axis axis, double from, double to) { +@@ -115,37 +641,85 @@ public abstract class VoxelShape { + }) - 1; + } + ++ // Paper start - optimise collisions ++ /** ++ * Copy of AABB#clip but for one AABB ++ */ ++ private static BlockHitResult clip(final AABB aabb, final Vec3 from, final Vec3 to, final BlockPos offset) { ++ final double[] minDistanceArr = new double[] { 1.0 }; ++ final double diffX = to.x - from.x; ++ final double diffY = to.y - from.y; ++ final double diffZ = to.z - from.z; ++ ++ final Direction direction = AABB.getDirection(aabb.move(offset), from, minDistanceArr, null, diffX, diffY, diffZ); ++ ++ if (direction == null) { ++ return null; ++ } ++ ++ final double minDistance = minDistanceArr[0]; ++ return new BlockHitResult(from.add(minDistance * diffX, minDistance * diffY, minDistance * diffZ), direction, offset, false); ++ } ++ // Paper end - optimise collisions ++ + @Nullable + public BlockHitResult clip(Vec3 start, Vec3 end, BlockPos pos) { +- if (this.isEmpty()) { ++ // Paper start - optimise collisions ++ if (this.isEmpty) { + return null; +- } else { +- Vec3 vec3 = end.subtract(start); +- if (vec3.lengthSqr() < 1.0E-7D) { +- return null; +- } else { +- Vec3 vec32 = start.add(vec3.scale(0.001D)); +- return this.shape.isFullWide(this.findIndex(Direction.Axis.X, vec32.x - (double)pos.getX()), this.findIndex(Direction.Axis.Y, vec32.y - (double)pos.getY()), this.findIndex(Direction.Axis.Z, vec32.z - (double)pos.getZ())) ? new BlockHitResult(vec32, Direction.getNearest(vec3.x, vec3.y, vec3.z).getOpposite(), pos, true) : AABB.clip(this.toAabbs(), start, end, pos); ++ } ++ ++ final Vec3 directionOpposite = end.subtract(start); ++ if (directionOpposite.lengthSqr() < io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) { ++ return null; ++ } ++ ++ final Vec3 fromBehind = start.add(directionOpposite.scale(0.001)); ++ final double fromBehindOffsetX = fromBehind.x - (double)pos.getX(); ++ final double fromBehindOffsetY = fromBehind.y - (double)pos.getY(); ++ final double fromBehindOffsetZ = fromBehind.z - (double)pos.getZ(); ++ ++ final AABB singleAABB = this.singleAABBRepresentation; ++ if (singleAABB != null) { ++ if (singleAABB.contains(fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) { ++ return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), pos, true); + } ++ return clip(singleAABB, start, end, pos); + } ++ ++ if (io.papermc.paper.util.CollisionUtil.strictlyContains(this, fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) { ++ return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), pos, true); ++ } ++ ++ return AABB.clip(this.toAabbs(), start, end, pos); ++ // Paper end - optimise collisions + } + + public Optional closestPointTo(Vec3 target) { +- if (this.isEmpty()) { ++ // Paper start - optimise collisions ++ if (this.isEmpty) { + return Optional.empty(); +- } else { +- Vec3[] vec3s = new Vec3[1]; +- this.forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> { +- double d = Mth.clamp(target.x(), minX, maxX); +- double e = Mth.clamp(target.y(), minY, maxY); +- double f = Mth.clamp(target.z(), minZ, maxZ); +- if (vec3s[0] == null || target.distanceToSqr(d, e, f) < target.distanceToSqr(vec3s[0])) { +- vec3s[0] = new Vec3(d, e, f); +- } ++ } + +- }); +- return Optional.of(vec3s[0]); ++ Vec3 ret = null; ++ double retDistance = Double.MAX_VALUE; ++ ++ final List aabbs = this.toAabbs(); ++ for (int i = 0, len = aabbs.size(); i < len; ++i) { ++ final AABB aabb = aabbs.get(i); ++ final double x = Mth.clamp(target.x, aabb.minX, aabb.maxX); ++ final double y = Mth.clamp(target.y, aabb.minY, aabb.maxY); ++ final double z = Mth.clamp(target.z, aabb.minZ, aabb.maxZ); ++ ++ double dist = target.distanceToSqr(x, y, z); ++ if (dist < retDistance) { ++ ret = new Vec3(x, y, z); ++ retDistance = dist; ++ } + } ++ ++ return Optional.ofNullable(ret); ++ // Paper end - optimise collisions + } + + public VoxelShape getFaceShape(Direction facing) { +@@ -180,7 +754,28 @@ public abstract class VoxelShape { + } + + public double collide(Direction.Axis axis, AABB box, double maxDist) { +- return this.collideX(AxisCycle.between(axis, Direction.Axis.X), box, maxDist); ++ // Paper start - optimise collisions ++ if (this.isEmpty) { ++ return maxDist; ++ } ++ if (Math.abs(maxDist) < io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) { ++ return 0.0; ++ } ++ switch (axis) { ++ case X: { ++ return io.papermc.paper.util.CollisionUtil.collideX(this, box, maxDist); ++ } ++ case Y: { ++ return io.papermc.paper.util.CollisionUtil.collideY(this, box, maxDist); ++ } ++ case Z: { ++ return io.papermc.paper.util.CollisionUtil.collideZ(this, box, maxDist); ++ } ++ default: { ++ throw new RuntimeException("Unknown axis: " + axis); ++ } ++ } ++ // Paper end - optimise collisions + } + + protected double collideX(AxisCycle axisCycle, AABB box, double maxDist) { diff --git a/patches/server/1021-Optimise-collision-checking-in-player-move-packet-ha.patch b/patches/server/1021-Optimise-collision-checking-in-player-move-packet-ha.patch new file mode 100644 index 000000000000..165a84935b23 --- /dev/null +++ b/patches/server/1021-Optimise-collision-checking-in-player-move-packet-ha.patch @@ -0,0 +1,170 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 2 Jul 2020 12:02:43 -0700 +Subject: [PATCH] Optimise collision checking in player move packet handling + +Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision + +CHECK ME + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f59b49a7e9884def9bc9f30dbd4c72dac2915c76..9a3d38365754cf40d8a18aabd1ad1cf27c576599 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -552,7 +552,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + return; + } + +- boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); ++ AABB oldBox = entity.getBoundingBox(); // Paper - copy from player movement packet + + d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above + d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; // Paper - diff on change, used for checking large move vectors above +@@ -568,6 +568,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + entity.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); ++ boolean didCollide = toX != entity.getX() || toY != entity.getY() || toZ != entity.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be... + double d11 = d7; + + d6 = d3 - entity.getX(); +@@ -581,15 +582,23 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + boolean flag2 = false; + + if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot +- flag2 = true; ++ flag2 = true; // Paper - diff on change, this should be moved wrongly + ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", new Object[]{entity.getName().getString(), this.player.getName().getString(), Math.sqrt(d10)}); + } + + entity.absMoveTo(d3, d4, d5, f, f1); + this.player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit +- boolean flag3 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); + +- if (flag && (flag2 || !flag3)) { ++ // Paper start - optimise out extra getCubes ++ boolean teleportBack = flag2; // violating this is always a fail ++ if (!teleportBack) { ++ // note: only call after setLocation, or else getBoundingBox is wrong ++ AABB newBox = entity.getBoundingBox(); ++ if (didCollide || !oldBox.equals(newBox)) { ++ teleportBack = this.hasNewCollision(worldserver, entity, oldBox, newBox); ++ } // else: no collision at all detected, why do we care? ++ } ++ if (teleportBack) { // Paper end - optimise out extra getCubes + entity.absMoveTo(d0, d1, d2, f, f1); + this.player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit + this.send(new ClientboundMoveVehiclePacket(entity)); +@@ -668,7 +677,32 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + private boolean noBlocksAround(Entity entity) { +- return entity.level().getBlockStates(entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D)).allMatch(BlockBehaviour.BlockStateBase::isAir); ++ // Paper start - stop using streams, this is already a known fixed problem in Entity#move ++ AABB box = entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D); ++ int minX = Mth.floor(box.minX); ++ int minY = Mth.floor(box.minY); ++ int minZ = Mth.floor(box.minZ); ++ int maxX = Mth.floor(box.maxX); ++ int maxY = Mth.floor(box.maxY); ++ int maxZ = Mth.floor(box.maxZ); ++ ++ Level world = entity.level(); ++ BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); ++ ++ for (int y = minY; y <= maxY; ++y) { ++ for (int z = minZ; z <= maxZ; ++z) { ++ for (int x = minX; x <= maxX; ++x) { ++ pos.set(x, y, z); ++ BlockState type = world.getBlockStateIfLoaded(pos); ++ if (type != null && !type.isAir()) { ++ return false; ++ } ++ } ++ } ++ } ++ ++ return true; ++ // Paper end - stop using streams, this is already a known fixed problem in Entity#move + } + + @Override +@@ -1282,7 +1316,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + if (this.awaitingPositionFromClient != null) { +- if (this.tickCount - this.awaitingTeleportTime > 20) { ++ if (false && this.tickCount - this.awaitingTeleportTime > 20) { // Paper - this will greatly screw with clients with > 1000ms RTT + this.awaitingTeleportTime = this.tickCount; + this.teleport(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); + } +@@ -1389,7 +1423,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + } + +- AABB axisalignedbb = this.player.getBoundingBox(); ++ AABB axisalignedbb = this.player.getBoundingBox(); // Paper - diff on change, should be old AABB + + d6 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above + d7 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above +@@ -1431,6 +1465,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + this.player.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); + this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move ++ boolean didCollide = toX != this.player.getX() || toY != this.player.getY() || toZ != this.player.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be... + // Paper start - prevent position desync + if (this.awaitingPositionFromClient != null) { + return; // ... thanks Mojang for letting move calls teleport across dimensions. +@@ -1459,7 +1494,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + } + +- boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2)); ++ // Paper start - optimise out extra getCubes ++ boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && movedWrongly; ++ this.player.absMoveTo(d0, d1, d2, f, f1); // prevent desync by tping to the set position, dropped for unknown reasons by mojang ++ if (!this.player.noPhysics && !this.player.isSleeping() && !teleportBack) { ++ AABB newBox = this.player.getBoundingBox(); ++ if (didCollide || !axisalignedbb.equals(newBox)) { ++ // note: only call after setLocation, or else getBoundingBox is wrong ++ teleportBack = this.hasNewCollision(worldserver, this.player, axisalignedbb, newBox); ++ } // else: no collision at all detected, why do we care? ++ } ++ // Paper end - optimise out extra getCubes + if (teleportBack) { + io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK, + toX, toY, toZ, toYaw, toPitch, false); +@@ -1559,6 +1604,33 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + } + ++ // Paper start - optimise out extra getCubes ++ private boolean hasNewCollision(final ServerLevel world, final Entity entity, final AABB oldBox, final AABB newBox) { ++ final List collisionsBB = new java.util.ArrayList<>(); ++ final List collisionsVoxel = new java.util.ArrayList<>(); ++ io.papermc.paper.util.CollisionUtil.getCollisions( ++ world, entity, newBox, collisionsVoxel, collisionsBB, ++ io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS | io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, ++ null, null ++ ); ++ ++ for (int i = 0, len = collisionsBB.size(); i < len; ++i) { ++ final AABB box = collisionsBB.get(i); ++ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(box, oldBox)) { ++ return true; ++ } ++ } ++ ++ for (int i = 0, len = collisionsVoxel.size(); i < len; ++i) { ++ final VoxelShape voxel = collisionsVoxel.get(i); ++ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersectNoEmpty(voxel, oldBox)) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ // Paper end - optimise out extra getCubes + private boolean isPlayerCollidingWithAnythingNew(LevelReader world, AABB box, double newX, double newY, double newZ) { + AABB axisalignedbb1 = this.player.getBoundingBox().move(newX - this.player.getX(), newY - this.player.getY(), newZ - this.player.getZ()); + Iterable iterable = world.getCollisions(this.player, axisalignedbb1.deflate(9.999999747378752E-6D)); diff --git a/patches/server/1028-Fix-tripwire-state-inconsistency.patch b/patches/server/1022-Fix-tripwire-state-inconsistency.patch similarity index 100% rename from patches/server/1028-Fix-tripwire-state-inconsistency.patch rename to patches/server/1022-Fix-tripwire-state-inconsistency.patch diff --git a/patches/server/1022-Optimise-random-block-ticking.patch b/patches/server/1022-Optimise-random-block-ticking.patch deleted file mode 100644 index f97afe35f89b..000000000000 --- a/patches/server/1022-Optimise-random-block-ticking.patch +++ /dev/null @@ -1,456 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 20 Jun 2021 16:19:26 -0700 -Subject: [PATCH] Optimise random block ticking - -Massive performance improvement for random block ticking. -The performance increase comes from the fact that the vast -majority of attempted block ticks (~95% in my testing) fail -because the randomly selected block is not tickable. - -Now only tickable blocks are targeted, however this means that -the maximum number of block ticks occurs per chunk. However, -not all chunks are going to be targeted. The percent chance -of a chunk being targeted is based on how many tickable blocks -are in the chunk. -This means that while block ticks are spread out less, the -total number of blocks ticked per world tick remains the same. -Therefore, the chance of a random tickable block being ticked -remains the same. - -diff --git a/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7d93652c1abbb6aee6eb7c26cf35d4d032ef7b69 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java -@@ -0,0 +1,65 @@ -+package io.papermc.paper.util.math; -+ -+import net.minecraft.util.RandomSource; -+import net.minecraft.world.level.levelgen.LegacyRandomSource; -+import net.minecraft.world.level.levelgen.PositionalRandomFactory; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public final class ThreadUnsafeRandom extends LegacyRandomSource { -+ -+ // See javadoc and internal comments for java.util.Random where these values come from, how they are used, and the author for them. -+ private static final long multiplier = 0x5DEECE66DL; -+ private static final long addend = 0xBL; -+ private static final long mask = (1L << 48) - 1; -+ -+ private static long initialScramble(long seed) { -+ return (seed ^ multiplier) & mask; -+ } -+ -+ private long seed; -+ -+ public ThreadUnsafeRandom(long seed) { -+ super(seed); -+ } -+ -+ @Override -+ public RandomSource fork() { -+ return new ThreadUnsafeRandom(this.nextLong()); -+ } -+ -+ @Override -+ public PositionalRandomFactory forkPositional() { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setSeed(long seed) { -+ // note: called by Random constructor -+ this.seed = initialScramble(seed); -+ } -+ -+ @Override -+ public int next(int bits) { -+ // avoid the expensive CAS logic used by superclass -+ return (int) (((this.seed = this.seed * multiplier + addend) & mask) >>> (48 - bits)); -+ } -+ -+ // Taken from -+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ -+ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c -+ // Original license is public domain -+ public static int fastRandomBounded(final long randomInteger, final long limit) { -+ // randomInteger must be [0, pow(2, 32)) -+ // limit must be [0, pow(2, 32)) -+ return (int)((randomInteger * limit) >>> 32); -+ } -+ -+ @Override -+ public int nextInt(int bound) { -+ // yes this breaks random's spec -+ // however there's nothing that uses this class that relies on it -+ return fastRandomBounded(this.next(32) & 0xFFFFFFFFL, bound); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 3535f86b92c4e61fd84defbbf37e074690a30019..bac2e7c8178696859ff2d38f1e095d86557fc306 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -855,6 +855,10 @@ public class ServerLevel extends Level implements WorldGenLevel { - entityplayer.stopSleepInBed(false, false); - }); - } -+ // Paper start - optimise random block ticking -+ private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos(); -+ private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(this.random.nextLong()); -+ // Paper end - - public void tickChunk(LevelChunk chunk, int randomTickSpeed) { - ChunkPos chunkcoordintpair = chunk.getPos(); -@@ -864,8 +868,10 @@ public class ServerLevel extends Level implements WorldGenLevel { - ProfilerFiller gameprofilerfiller = this.getProfiler(); - - gameprofilerfiller.push("thunder"); -+ final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change -+ - if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder -- BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); -+ blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper - - if (this.isRainingAt(blockposition)) { - DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); -@@ -897,7 +903,10 @@ public class ServerLevel extends Level implements WorldGenLevel { - if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow - for (int l = 0; l < randomTickSpeed; ++l) { - if (this.random.nextInt(48) == 0) { -- this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15)); -+ // Paper start -+ this.getRandomBlockPosition(j, 0, k, 15, blockposition); -+ this.tickPrecipitation(blockposition, chunk); -+ // Paper end - } - } - } // Paper - Option to disable ice and snow -@@ -905,36 +914,37 @@ public class ServerLevel extends Level implements WorldGenLevel { - gameprofilerfiller.popPush("tickBlocks"); - timings.chunkTicksBlocks.startTiming(); // Paper - if (randomTickSpeed > 0) { -- LevelChunkSection[] achunksection = chunk.getSections(); -- -- for (int i1 = 0; i1 < achunksection.length; ++i1) { -- LevelChunkSection chunksection = achunksection[i1]; -- -- if (chunksection.isRandomlyTicking()) { -- int j1 = chunk.getSectionYFromSectionIndex(i1); -- int k1 = SectionPos.sectionToBlockCoord(j1); -- -- for (int l1 = 0; l1 < randomTickSpeed; ++l1) { -- BlockPos blockposition1 = this.getBlockRandomPos(j, k1, k, 15); -- -- gameprofilerfiller.push("randomTick"); -- BlockState iblockdata = chunksection.getBlockState(blockposition1.getX() - j, blockposition1.getY() - k1, blockposition1.getZ() - k); -- -- if (iblockdata.isRandomlyTicking()) { -- iblockdata.randomTick(this, blockposition1, this.random); -- } -+ // Paper start - optimize random block ticking -+ LevelChunkSection[] sections = chunk.getSections(); -+ final int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this); -+ for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) { -+ LevelChunkSection section = sections[sectionIndex]; -+ if (section == null || section.tickingList.size() == 0) continue; -+ -+ int yPos = (sectionIndex + minSection) << 4; -+ for (int a = 0; a < randomTickSpeed; ++a) { -+ int tickingBlocks = section.tickingList.size(); -+ int index = this.randomTickRandom.nextInt(16 * 16 * 16); -+ if (index >= tickingBlocks) { -+ continue; -+ } - -- FluidState fluid = iblockdata.getFluidState(); -+ long raw = section.tickingList.getRaw(index); -+ int location = com.destroystokyo.paper.util.maplist.IBlockDataList.getLocationFromRaw(raw); -+ int randomX = location & 15; -+ int randomY = ((location >>> (4 + 4)) & 255) | yPos; -+ int randomZ = (location >>> 4) & 15; - -- if (fluid.isRandomlyTicking()) { -- fluid.randomTick(this, blockposition1, this.random); -- } -+ BlockPos blockposition2 = blockposition.set(j + randomX, randomY, k + randomZ); -+ BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw); - -- gameprofilerfiller.pop(); -- } -+ iblockdata.randomTick(this, blockposition2, this.randomTickRandom); - } -+ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock). -+ // TODO CHECK ON UPDATE (ping the Canadian) - } - } -+ // Paper end - optimise random block ticking - - timings.chunkTicksBlocks.stopTiming(); // Paper - gameprofilerfiller.pop(); -@@ -942,17 +952,25 @@ public class ServerLevel extends Level implements WorldGenLevel { - - @VisibleForTesting - public void tickPrecipitation(BlockPos pos) { -- BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); -- BlockPos blockposition2 = blockposition1.below(); -+ // Paper start - optimise chunk ticking -+ tickPrecipitation(pos.mutable(), this.getChunkAt(pos)); -+ } -+ public void tickPrecipitation(BlockPos.MutableBlockPos blockposition1, final LevelChunk chunk) { -+ int normalY = chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, blockposition1.getX() & 15, blockposition1.getZ() & 15) + 1; -+ int downY = normalY - 1; -+ blockposition1.setY(normalY); -+ // Paper end - optimise chunk ticking - Biome biomebase = (Biome) this.getBiome(blockposition1).value(); - -- if (biomebase.shouldFreeze(this, blockposition2)) { -- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition2, Blocks.ICE.defaultBlockState(), null); // CraftBukkit -+ blockposition1.setY(downY); -+ if (biomebase.shouldFreeze(this, blockposition1)) { -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit - } - - if (this.isRaining()) { - int i = this.getGameRules().getInt(GameRules.RULE_SNOW_ACCUMULATION_HEIGHT); - -+ blockposition1.setY(normalY); // Paper - optimise chunk ticking - if (i > 0 && biomebase.shouldSnow(this, blockposition1)) { - BlockState iblockdata = this.getBlockState(blockposition1); - -@@ -970,12 +988,13 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - } - -- Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitationAt(blockposition2); -+ blockposition1.setY(downY); // Paper - optimise chunk ticking -+ Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitationAt(blockposition1); // Paper - optimise chunk ticking - - if (biomebase_precipitation != Biome.Precipitation.NONE) { -- BlockState iblockdata2 = this.getBlockState(blockposition2); -+ BlockState iblockdata2 = this.getBlockState(blockposition1); // Paper - optimise chunk ticking - -- iblockdata2.getBlock().handlePrecipitation(iblockdata2, this, blockposition2, biomebase_precipitation); -+ iblockdata2.getBlock().handlePrecipitation(iblockdata2, this, blockposition1, biomebase_precipitation); // Paper - optimise chunk ticking - } - } - -diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java -index 68648c5a5e3ff079f832092af0f2f801c42d1ede..8bafd5fd7499ba4a04bf706cfd1e156073716e21 100644 ---- a/src/main/java/net/minecraft/util/BitStorage.java -+++ b/src/main/java/net/minecraft/util/BitStorage.java -@@ -20,4 +20,15 @@ public interface BitStorage { - void unpack(int[] out); - - BitStorage copy(); -+ -+ // Paper start -+ void forEach(DataBitConsumer consumer); -+ -+ @FunctionalInterface -+ interface DataBitConsumer { -+ -+ void accept(int location, int data); -+ -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java -index acd820b19aff4e093536cc47002a899498b3fd6b..453c1d7e01970fd817d27f59c3b00ffc70e8ca0c 100644 ---- a/src/main/java/net/minecraft/util/SimpleBitStorage.java -+++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java -@@ -124,6 +124,28 @@ public class SimpleBitStorage implements BitStorage { - return this.bits; - } - -+ // Paper start -+ @Override -+ public final void forEach(DataBitConsumer consumer) { -+ int i = 0; -+ long[] along = this.data; -+ int j = along.length; -+ -+ for (int k = 0; k < j; ++k) { -+ long l = along[k]; -+ -+ for (int i1 = 0; i1 < this.valuesPerLong; ++i1) { -+ consumer.accept(i, (int) (l & this.mask)); -+ l >>= this.bits; -+ ++i; -+ if (i >= this.size) { -+ return; -+ } -+ } -+ } -+ } -+ // Paper end -+ - @Override - public void getAll(IntConsumer action) { - int i = 0; -diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java -index b7a3f15dc1ed9e9322a86921052984c7cbd3262a..f8de91393564b3691c17339ac9196cc0fc1cf748 100644 ---- a/src/main/java/net/minecraft/util/ZeroBitStorage.java -+++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java -@@ -46,6 +46,15 @@ public class ZeroBitStorage implements BitStorage { - return 0; - } - -+ // Paper start -+ @Override -+ public void forEach(DataBitConsumer consumer) { -+ for(int i = 0; i < this.size; ++i) { -+ consumer.accept(i, 0); -+ } -+ } -+ // Paper end -+ - @Override - public void getAll(IntConsumer action) { - for(int i = 0; i < this.size; ++i) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 379990c0ea9d10fd3c627ffff2198cb554780eb0..d595f1590619b24d460fc2c10a5412844f62cba0 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -87,7 +87,7 @@ public class Turtle extends Animal { - } - - public void setHomePos(BlockPos pos) { -- this.entityData.set(Turtle.HOME_POS, pos); -+ this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos... - } - - public BlockPos getHomePos() { -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 43052e3194812fe8d7aa6569c1c1c49d8ba25446..1712cf22d987a87c427f042a89a9fff90203b079 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -1395,10 +1395,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public abstract RecipeManager getRecipeManager(); - - public BlockPos getBlockRandomPos(int x, int y, int z, int l) { -+ // Paper start - allow use of mutable pos -+ BlockPos.MutableBlockPos ret = new BlockPos.MutableBlockPos(); -+ this.getRandomBlockPosition(x, y, z, l, ret); -+ return ret.immutable(); -+ } -+ public final BlockPos.MutableBlockPos getRandomBlockPosition(int x, int y, int z, int l, BlockPos.MutableBlockPos out) { -+ // Paper end - this.randValue = this.randValue * 3 + 1013904223; - int i1 = this.randValue >> 2; - -- return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); -+ out.set(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); // Paper - change to setValues call -+ return out; // Paper - } - - public boolean noSave() { -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -index 8852263cb6faec1b68326145aa30e5cd36d066e7..eb05c01e85825cbd5b7cf43bc6d261db0b871b92 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -25,6 +25,7 @@ public class LevelChunkSection { - public final PalettedContainer states; - // CraftBukkit start - read/write - private PalettedContainer> biomes; -+ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper - - public LevelChunkSection(PalettedContainer datapaletteblock, PalettedContainer> palettedcontainerro) { - // CraftBukkit end -@@ -77,6 +78,9 @@ public class LevelChunkSection { - --this.nonEmptyBlockCount; - if (iblockdata1.isRandomlyTicking()) { - --this.tickingBlockCount; -+ // Paper start -+ this.tickingList.remove(x, y, z); -+ // Paper end - } - } - -@@ -88,6 +92,9 @@ public class LevelChunkSection { - ++this.nonEmptyBlockCount; - if (state.isRandomlyTicking()) { - ++this.tickingBlockCount; -+ // Paper start -+ this.tickingList.add(x, y, z, state); -+ // Paper end - } - } - -@@ -115,40 +122,34 @@ public class LevelChunkSection { - } - - public void recalcBlockCounts() { -- class a implements PalettedContainer.CountConsumer { -- -- public int nonEmptyBlockCount; -- public int tickingBlockCount; -- public int tickingFluidCount; -- -- a() {} -- -- public void accept(BlockState iblockdata, int i) { -+ // Paper start - unfuck this -+ this.tickingList.clear(); -+ this.nonEmptyBlockCount = 0; -+ this.tickingBlockCount = 0; -+ this.tickingFluidCount = 0; -+ // Don't run this on clearly empty sections -+ if (this.maybeHas((BlockState state) -> !state.isAir() || !state.getFluidState().isEmpty())) { -+ this.states.forEachLocation((BlockState iblockdata, int i) -> { - FluidState fluid = iblockdata.getFluidState(); - - if (!iblockdata.isAir()) { -- this.nonEmptyBlockCount += i; -+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); - if (iblockdata.isRandomlyTicking()) { -- this.tickingBlockCount += i; -+ this.tickingBlockCount = (short)(this.tickingBlockCount + 1); -+ this.tickingList.add(i, iblockdata); - } - } - - if (!fluid.isEmpty()) { -- this.nonEmptyBlockCount += i; -+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); - if (fluid.isRandomlyTicking()) { -- this.tickingFluidCount += i; -+ this.tickingFluidCount = (short) (this.tickingFluidCount + 1); - } - } - -- } -+ }); - } -- -- a a0 = new a(); -- -- this.states.count(a0); -- this.nonEmptyBlockCount = (short) a0.nonEmptyBlockCount; -- this.tickingBlockCount = (short) a0.tickingBlockCount; -- this.tickingFluidCount = (short) a0.tickingFluidCount; -+ // Paper end - } - - public PalettedContainer getStates() { -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 0f930f8355ea99d1cb1a8d27edc1c224588f852f..983799520ce052d98c9231f4f7925492d4f7d5c9 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -386,6 +386,14 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - } - -+ // Paper start -+ public void forEachLocation(PalettedContainer.CountConsumer consumer) { -+ this.data.storage.forEach((int location, int data) -> { -+ consumer.accept(this.data.palette.valueFor(data), location); -+ }); -+ } -+ // Paper end -+ - @FunctionalInterface - public interface CountConsumer { - void accept(T object, int count); diff --git a/patches/server/1023-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch b/patches/server/1023-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch deleted file mode 100644 index 66b1297d83b8..000000000000 --- a/patches/server/1023-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch +++ /dev/null @@ -1,777 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 2 Feb 2020 02:25:10 -0800 -Subject: [PATCH] Attempt to recalculate regionfile header if it is corrupt - -Instead of trying to relocate the chunk, which is seems to never -be the correct choice, so we end up duplicating or swapping chunks, -we instead drop the current regionfile header and recalculate - -hoping that at least then we don't swap chunks, and maybe recover -them all. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 1ee35c828ea637e2954158060e1ad98f2649cedd..afc583d5726a572f606469bba3aaba4600827992 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -70,6 +70,18 @@ import net.minecraft.world.ticks.ProtoChunkTicks; - import org.slf4j.Logger; - - public class ChunkSerializer { -+ // Paper start - Attempt to recalculate regionfile header if it is corrupt -+ // TODO: Check on update -+ public static long getLastWorldSaveTime(CompoundTag chunkData) { -+ final int dataVersion = ChunkStorage.getVersion(chunkData); -+ if (dataVersion < 2842) { // Level tag is removed after this version -+ final CompoundTag levelData = chunkData.getCompound("Level"); -+ return levelData.getLong("LastUpdate"); -+ } else { -+ return chunkData.getLong("LastUpdate"); -+ } -+ } -+ // Paper end - Attempt to recalculate regionfile header if it is corrupt - - public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states - private static final Logger LOGGER = LogUtils.getLogger(); -@@ -455,7 +467,7 @@ public class ChunkSerializer { - nbttagcompound.putInt("xPos", chunkcoordintpair.x); - nbttagcompound.putInt("yPos", chunk.getMinSection()); - nbttagcompound.putInt("zPos", chunkcoordintpair.z); -- nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading -+ nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading // Paper - diff on change - nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime()); - nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString()); - BlendingData blendingdata = chunk.getBlendingData(); -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -index 645a1773237f2002c233ec1f3ff6f0ca46ca4024..d16d7c2fed89fb1347df7ddd95856e7f08c22e8a 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -38,7 +38,7 @@ public class ChunkStorage implements AutoCloseable { - - public ChunkStorage(Path directory, DataFixer dataFixer, boolean dsync) { - this.fixerUpper = dataFixer; -- this.regionFileCache = new RegionFileStorage(directory, dsync); // Paper - rewrite chunk system; async chunk IO -+ this.regionFileCache = new RegionFileStorage(directory, dsync, true); // Paper - rewrite chunk system; async chunk IO & Attempt to recalculate regionfile header if it is corrupt - } - - public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java -index c8298a597818227de33a4afce4698ec0666cf758..6762b0f71ea9e369bb77103b7f1938983cb77a44 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java -@@ -9,6 +9,27 @@ import java.util.BitSet; - public class RegionBitmap { - private final BitSet used = new BitSet(); - -+ // Paper start - Attempt to recalculate regionfile header if it is corrupt -+ public final void copyFrom(RegionBitmap other) { -+ BitSet thisBitset = this.used; -+ BitSet otherBitset = other.used; -+ -+ for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) { -+ thisBitset.set(i, otherBitset.get(i)); -+ } -+ } -+ -+ public final boolean tryAllocate(int from, int length) { -+ BitSet bitset = this.used; -+ int firstSet = bitset.nextSetBit(from); -+ if (firstSet > 0 && firstSet < (from + length)) { -+ return false; -+ } -+ bitset.set(from, from + length); -+ return true; -+ } -+ // Paper end - Attempt to recalculate regionfile header if it is corrupt -+ - public void force(int start, int size) { - this.used.set(start, start + size); - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -index 92ba75254f6ffca40abd5485dbb4789de59edebd..6cf83502a954cce9c562ec036bfeddb477d38b73 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -@@ -50,6 +50,355 @@ public class RegionFile implements AutoCloseable { - public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // Paper - public final Path regionFile; // Paper - -+ // Paper start - Attempt to recalculate regionfile header if it is corrupt -+ private static long roundToSectors(long bytes) { -+ long sectors = bytes >>> 12; // 4096 = 2^12 -+ long remainingBytes = bytes & 4095; -+ long sign = -remainingBytes; // sign is 1 if nonzero -+ return sectors + (sign >>> 63); -+ } -+ -+ private static final CompoundTag OVERSIZED_COMPOUND = new CompoundTag(); -+ -+ private CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException { -+ try { -+ if (chunkDataLength < 0) { -+ return null; -+ } -+ -+ long offset = sector * 4096L + 4L; // offset for chunk data -+ -+ if ((offset + chunkDataLength) > fileLength) { -+ return null; -+ } -+ -+ ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength); -+ if (chunkDataLength != this.file.read(chunkData, offset)) { -+ return null; -+ } -+ -+ ((java.nio.Buffer)chunkData).flip(); -+ -+ byte compressionType = chunkData.get(); -+ if (compressionType < 0) { // compressionType & 128 != 0 -+ // oversized chunk -+ return OVERSIZED_COMPOUND; -+ } -+ -+ RegionFileVersion compression = RegionFileVersion.fromId(compressionType); -+ if (compression == null) { -+ return null; -+ } -+ -+ InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position())); -+ -+ return NbtIo.read(new DataInputStream(input)); -+ } catch (Exception ex) { -+ return null; -+ } -+ } -+ -+ private int getLength(long sector) throws IOException { -+ ByteBuffer length = ByteBuffer.allocate(4); -+ if (4 != this.file.read(length, sector * 4096L)) { -+ return -1; -+ } -+ -+ return length.getInt(0); -+ } -+ -+ private void backupRegionFile() { -+ Path backup = this.regionFile.getParent().resolve(this.regionFile.getFileName() + "." + new java.util.Random().nextLong() + ".backup"); -+ this.backupRegionFile(backup); -+ } -+ -+ private void backupRegionFile(Path to) { -+ try { -+ this.file.force(true); -+ LOGGER.warn("Backing up regionfile \"" + this.regionFile.toAbsolutePath() + "\" to " + to.toAbsolutePath()); -+ java.nio.file.Files.copy(this.regionFile, to, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES); -+ LOGGER.warn("Backed up the regionfile to " + to.toAbsolutePath()); -+ } catch (IOException ex) { -+ LOGGER.error("Failed to backup to " + to.toAbsolutePath(), ex); -+ } -+ } -+ -+ private static boolean inSameRegionfile(ChunkPos first, ChunkPos second) { -+ return (first.x & ~31) == (second.x & ~31) && (first.z & ~31) == (second.z & ~31); -+ } -+ -+ // note: only call for CHUNK regionfiles -+ boolean recalculateHeader() throws IOException { -+ if (!this.canRecalcHeader) { -+ return false; -+ } -+ ChunkPos ourLowerLeftPosition = RegionFileStorage.getRegionFileCoordinates(this.regionFile); -+ if (ourLowerLeftPosition == null) { -+ LOGGER.error("Unable to get chunk location of regionfile " + this.regionFile.toAbsolutePath() + ", cannot recover header"); -+ return false; -+ } -+ synchronized (this) { -+ LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile " + this.regionFile.toAbsolutePath(), new Throwable()); -+ -+ // try to backup file so maybe it could be sent to us for further investigation -+ -+ this.backupRegionFile(); -+ CompoundTag[] compounds = new CompoundTag[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data) -+ int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes -+ int[] sectorOffsets = new int[32 * 32]; // in sectors -+ boolean[] hasAikarOversized = new boolean[32 * 32]; -+ -+ long fileLength = this.file.size(); -+ long totalSectors = roundToSectors(fileLength); -+ -+ // search the regionfile from start to finish for the most up-to-date chunk data -+ -+ for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip -+ int chunkDataLength = this.getLength(i); -+ CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength); -+ if (compound == null || compound == OVERSIZED_COMPOUND) { -+ continue; -+ } -+ -+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(compound); -+ if (!inSameRegionfile(ourLowerLeftPosition, chunkPos)) { -+ LOGGER.error("Ignoring absolute chunk " + chunkPos + " in regionfile as it is not contained in the bounds of the regionfile '" + this.regionFile.toAbsolutePath() + "'. It should be in regionfile (" + (chunkPos.x >> 5) + "," + (chunkPos.z >> 5) + ")"); -+ continue; -+ } -+ int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5); -+ -+ CompoundTag otherCompound = compounds[location]; -+ -+ if (otherCompound != null && ChunkSerializer.getLastWorldSaveTime(otherCompound) > ChunkSerializer.getLastWorldSaveTime(compound)) { -+ continue; // don't overwrite newer data. -+ } -+ -+ // aikar oversized? -+ Path aikarOversizedFile = this.getOversizedFile(chunkPos.x, chunkPos.z); -+ boolean isAikarOversized = false; -+ if (Files.exists(aikarOversizedFile)) { -+ try { -+ CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z); -+ if (ChunkSerializer.getLastWorldSaveTime(compound) == ChunkSerializer.getLastWorldSaveTime(aikarOversizedCompound)) { -+ // best we got for an id. hope it's good enough -+ isAikarOversized = true; -+ } -+ } catch (Exception ex) { -+ LOGGER.error("Failed to read aikar oversized data for absolute chunk (" + chunkPos.x + "," + chunkPos.z + ") in regionfile " + this.regionFile.toAbsolutePath() + ", oversized data for this chunk will be lost", ex); -+ // fall through, if we can't read aikar oversized we can't risk corrupting chunk data -+ } -+ } -+ -+ hasAikarOversized[location] = isAikarOversized; -+ compounds[location] = compound; -+ rawLengths[location] = chunkDataLength + 4; -+ sectorOffsets[location] = (int)i; -+ -+ int chunkSectorLength = (int)roundToSectors(rawLengths[location]); -+ i += chunkSectorLength; -+ --i; // gets incremented next iteration -+ } -+ -+ // forge style oversized data is already handled by the local search, and aikar data we just hope -+ // we get it right as aikar data has no identifiers we could use to try and find its corresponding -+ // local data compound -+ -+ java.nio.file.Path containingFolder = this.externalFileDir; -+ Path[] regionFiles = Files.list(containingFolder).toArray(Path[]::new); -+ boolean[] oversized = new boolean[32 * 32]; -+ RegionFileVersion[] oversizedCompressionTypes = new RegionFileVersion[32 * 32]; -+ -+ if (regionFiles != null) { -+ int lowerXBound = ourLowerLeftPosition.x; // inclusive -+ int lowerZBound = ourLowerLeftPosition.z; // inclusive -+ int upperXBound = lowerXBound + 32 - 1; // inclusive -+ int upperZBound = lowerZBound + 32 - 1; // inclusive -+ -+ // read mojang oversized data -+ for (Path regionFile : regionFiles) { -+ ChunkPos oversizedCoords = getOversizedChunkPair(regionFile); -+ if (oversizedCoords == null) { -+ continue; -+ } -+ -+ if ((oversizedCoords.x < lowerXBound || oversizedCoords.x > upperXBound) || (oversizedCoords.z < lowerZBound || oversizedCoords.z > upperZBound)) { -+ continue; // not in our regionfile -+ } -+ -+ // ensure oversized data is valid & is newer than data in the regionfile -+ -+ int location = (oversizedCoords.x & 31) | ((oversizedCoords.z & 31) << 5); -+ -+ byte[] chunkData; -+ try { -+ chunkData = Files.readAllBytes(regionFile); -+ } catch (Exception ex) { -+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", data will be lost", ex); -+ continue; -+ } -+ -+ CompoundTag compound = null; -+ -+ // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them -+ RegionFileVersion compression = null; -+ for (RegionFileVersion compressionType : RegionFileVersion.VERSIONS.values()) { -+ try { -+ DataInputStream in = new DataInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData))); // typical java -+ compound = NbtIo.read((java.io.DataInput)in); -+ compression = compressionType; -+ break; // reaches here iff readNBT does not throw -+ } catch (Exception ex) { -+ continue; -+ } -+ } -+ -+ if (compound == null) { -+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", it's corrupt. Its data will be lost"); -+ continue; -+ } -+ -+ if (!ChunkSerializer.getChunkCoordinate(compound).equals(oversizedCoords)) { -+ LOGGER.error("Can't use oversized chunk stored in " + regionFile.toAbsolutePath() + ", got absolute chunkpos: " + ChunkSerializer.getChunkCoordinate(compound) + ", expected " + oversizedCoords); -+ continue; -+ } -+ -+ if (compounds[location] == null || ChunkSerializer.getLastWorldSaveTime(compound) > ChunkSerializer.getLastWorldSaveTime(compounds[location])) { -+ oversized[location] = true; -+ oversizedCompressionTypes[location] = compression; -+ } -+ } -+ } -+ -+ // now we need to calculate a new offset header -+ -+ int[] calculatedOffsets = new int[32 * 32]; -+ RegionBitmap newSectorAllocations = new RegionBitmap(); -+ newSectorAllocations.force(0, 2); // make space for header -+ -+ // allocate sectors for normal chunks -+ -+ for (int chunkX = 0; chunkX < 32; ++chunkX) { -+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { -+ int location = chunkX | (chunkZ << 5); -+ -+ if (oversized[location]) { -+ continue; -+ } -+ -+ int rawLength = rawLengths[location]; // bytes -+ int sectorOffset = sectorOffsets[location]; // sectors -+ int sectorLength = (int)roundToSectors(rawLength); -+ -+ if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) { -+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized -+ } else { -+ LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + ", chunk will be regenerated"); -+ } -+ } -+ } -+ -+ // allocate sectors for oversized chunks -+ -+ for (int chunkX = 0; chunkX < 32; ++chunkX) { -+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { -+ int location = chunkX | (chunkZ << 5); -+ -+ if (!oversized[location]) { -+ continue; -+ } -+ -+ int sectorOffset = newSectorAllocations.allocate(1); -+ int sectorLength = 1; -+ -+ try { -+ this.file.write(this.createExternalStub(oversizedCompressionTypes[location]), sectorOffset * 4096); -+ // only allocate in the new offsets if the write succeeds -+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized -+ } catch (IOException ex) { -+ newSectorAllocations.free(sectorOffset, sectorLength); -+ LOGGER.error("Failed to write new oversized chunk data holder, local chunk at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + " will be regenerated"); -+ } -+ } -+ } -+ -+ // rewrite aikar oversized data -+ -+ this.oversizedCount = 0; -+ for (int chunkX = 0; chunkX < 32; ++chunkX) { -+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { -+ int location = chunkX | (chunkZ << 5); -+ int isAikarOversized = hasAikarOversized[location] ? 1 : 0; -+ -+ this.oversizedCount += isAikarOversized; -+ this.oversized[location] = (byte)isAikarOversized; -+ } -+ } -+ -+ if (this.oversizedCount > 0) { -+ try { -+ this.writeOversizedMeta(); -+ } catch (Exception ex) { -+ LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile " + this.regionFile.toAbsolutePath(), ex); -+ Files.deleteIfExists(this.getOversizedMetaFile()); -+ } -+ } else { -+ Files.deleteIfExists(this.getOversizedMetaFile()); -+ } -+ -+ this.usedSectors.copyFrom(newSectorAllocations); -+ -+ // before we overwrite the old sectors, print a summary of the chunks that got changed. -+ -+ LOGGER.info("Starting summary of changes for regionfile " + this.regionFile.toAbsolutePath()); -+ -+ for (int chunkX = 0; chunkX < 32; ++chunkX) { -+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { -+ int location = chunkX | (chunkZ << 5); -+ -+ int oldOffset = this.offsets.get(location); -+ int newOffset = calculatedOffsets[location]; -+ -+ if (oldOffset == newOffset) { -+ continue; -+ } -+ -+ this.offsets.put(location, newOffset); // overwrite incorrect offset -+ -+ if (oldOffset == 0) { -+ // found lost data -+ LOGGER.info("Found missing data for local chunk (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath()); -+ } else if (newOffset == 0) { -+ LOGGER.warn("Data for local chunk (" + chunkX + "," + chunkZ + ") could not be recovered in regionfile " + this.regionFile.toAbsolutePath() + ", it will be regenerated"); -+ } else { -+ LOGGER.info("Local chunk (" + chunkX + "," + chunkZ + ") changed to point to newer data or correct chunk in regionfile " + this.regionFile.toAbsolutePath()); -+ } -+ } -+ } -+ -+ LOGGER.info("End of change summary for regionfile " + this.regionFile.toAbsolutePath()); -+ -+ // simply destroy the timestamp header, it's not used -+ -+ for (int i = 0; i < 32 * 32; ++i) { -+ this.timestamps.put(i, calculatedOffsets[i] != 0 ? (int)System.currentTimeMillis() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this -+ } -+ -+ // write new header -+ try { -+ this.flush(); -+ this.file.force(true); // try to ensure it goes through... -+ LOGGER.info("Successfully wrote new header to disk for regionfile " + this.regionFile.toAbsolutePath()); -+ } catch (IOException ex) { -+ LOGGER.error("Failed to write new header to disk for regionfile " + this.regionFile.toAbsolutePath(), ex); -+ } -+ } -+ -+ return true; -+ } -+ -+ final boolean canRecalcHeader; // final forces compile fail on new constructor -+ // Paper end - Attempt to recalculate regionfile header if it is corrupt -+ - // Paper start - Cache chunk status - private final net.minecraft.world.level.chunk.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.ChunkStatus[32 * 32]; - -@@ -77,8 +426,19 @@ public class RegionFile implements AutoCloseable { - public RegionFile(Path file, Path directory, boolean dsync) throws IOException { - this(file, directory, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format - } -+ // Paper start - add can recalc flag -+ public RegionFile(Path file, Path directory, boolean dsync, boolean canRecalcHeader) throws IOException { -+ this(file, directory, RegionFileVersion.getCompressionFormat(), dsync, canRecalcHeader); -+ } -+ // Paper end - add can recalc flag - - public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException { -+ // Paper start - add can recalc flag -+ this(file, directory, outputChunkStreamVersion, dsync, false); -+ } -+ public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync, boolean canRecalcHeader) throws IOException { -+ this.canRecalcHeader = canRecalcHeader; -+ // Paper end - add can recalc flag - this.header = ByteBuffer.allocateDirect(8192); - this.regionFile = file; // Paper - initOversizedState(); // Paper -@@ -107,14 +467,16 @@ public class RegionFile implements AutoCloseable { - RegionFile.LOGGER.warn("Region file {} has truncated header: {}", file, i); - } - -- long j = Files.size(file); -+ final long j = Files.size(file); final long regionFileSize = j; // Paper - recalculate header on header corruption - -- for (int k = 0; k < 1024; ++k) { -- int l = this.offsets.get(k); -+ boolean needsHeaderRecalc = false; // Paper - recalculate header on header corruption -+ boolean hasBackedUp = false; // Paper - recalculate header on header corruption -+ for (int k = 0; k < 1024; ++k) { final int headerLocation = k; // Paper - we expect this to be the header location -+ final int l = this.offsets.get(k); - - if (l != 0) { -- int i1 = RegionFile.getSectorNumber(l); -- int j1 = RegionFile.getNumSectors(l); -+ final int i1 = RegionFile.getSectorNumber(l); final int offset = i1; // Paper - we expect this to be offset in file in sectors -+ int j1 = RegionFile.getNumSectors(l); final int sectorLength; // Paper - diff on change, we expect this to be sector length of region - watch out for reassignments - // Spigot start - if (j1 == 255) { - // We're maxed out, so we need to read the proper length from the section -@@ -123,32 +485,102 @@ public class RegionFile implements AutoCloseable { - j1 = (realLen.getInt(0) + 4) / 4096 + 1; - } - // Spigot end -+ sectorLength = j1; // Paper - diff on change, we expect this to be sector length of region - - if (i1 < 2) { - RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{file, k, i1}); -- this.offsets.put(k, 0); -+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change - } else if (j1 == 0) { - RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", file, k); -- this.offsets.put(k, 0); -+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change - } else if ((long) i1 * 4096L > j) { - RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", new Object[]{file, k, i1}); -- this.offsets.put(k, 0); -+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change - } else { -- this.usedSectors.force(i1, j1); -+ //this.usedSectors.force(i1, j1); // Paper - move this down so we can check if it fails to allocate -+ } -+ // Paper start - recalculate header on header corruption -+ if (offset < 2 || sectorLength <= 0 || ((long)offset * 4096L) > regionFileSize) { -+ if (canRecalcHeader) { -+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + "! Recalculating header..."); -+ needsHeaderRecalc = true; -+ break; -+ } else { -+ // location = chunkX | (chunkZ << 5); -+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + -+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header"); -+ if (!hasBackedUp) { -+ hasBackedUp = true; -+ this.backupRegionFile(); -+ } -+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too -+ this.offsets.put(headerLocation, 0); // delete the entry from header -+ continue; -+ } -+ } -+ boolean failedToAllocate = !this.usedSectors.tryAllocate(offset, sectorLength); -+ if (failedToAllocate) { -+ LOGGER.error("Overlapping allocation by local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") in regionfile " + this.regionFile.toAbsolutePath()); - } -+ if (failedToAllocate & !canRecalcHeader) { -+ // location = chunkX | (chunkZ << 5); -+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + -+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header"); -+ if (!hasBackedUp) { -+ hasBackedUp = true; -+ this.backupRegionFile(); -+ } -+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too -+ this.offsets.put(headerLocation, 0); // delete the entry from header -+ continue; -+ } -+ needsHeaderRecalc |= failedToAllocate; -+ // Paper end - recalculate header on header corruption - } - } -+ // Paper start - recalculate header on header corruption -+ // we move the recalc here so comparison to old header is correct when logging to console -+ if (needsHeaderRecalc) { // true if header gave us overlapping allocations or had other issues -+ LOGGER.error("Recalculating regionfile " + this.regionFile.toAbsolutePath() + ", header gave erroneous offsets & locations"); -+ this.recalculateHeader(); -+ } -+ // Paper end - } - - } - } - - private Path getExternalChunkPath(ChunkPos chunkPos) { -- String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; -+ String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; // Paper - diff on change - - return this.externalFileDir.resolve(s); - } - -+ // Paper start -+ private static ChunkPos getOversizedChunkPair(Path file) { -+ String fileName = file.getFileName().toString(); -+ -+ if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) { -+ return null; -+ } -+ -+ String[] split = fileName.split("\\."); -+ -+ if (split.length != 4) { -+ return null; -+ } -+ -+ try { -+ int x = Integer.parseInt(split[1]); -+ int z = Integer.parseInt(split[2]); -+ -+ return new ChunkPos(x, z); -+ } catch (NumberFormatException ex) { -+ return null; -+ } -+ } -+ // Paper end -+ - @Nullable - public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException { - int i = this.getOffset(pos); -@@ -172,6 +604,11 @@ public class RegionFile implements AutoCloseable { - ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error - if (bytebuffer.remaining() < 5) { - RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", new Object[]{pos, l, bytebuffer.remaining()}); -+ // Paper start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ // Paper end - recalculate header on regionfile corruption - return null; - } else { - int i1 = bytebuffer.getInt(); -@@ -179,6 +616,11 @@ public class RegionFile implements AutoCloseable { - - if (i1 == 0) { - RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", pos); -+ // Paper start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ // Paper end - recalculate header on regionfile corruption - return null; - } else { - int j1 = i1 - 1; -@@ -186,17 +628,44 @@ public class RegionFile implements AutoCloseable { - if (RegionFile.isExternalStreamChunk(b0)) { - if (j1 != 0) { - RegionFile.LOGGER.warn("Chunk has both internal and external streams"); -+ // Paper start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ // Paper end - recalculate header on regionfile corruption - } - -- return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0)); -+ // Paper start - recalculate header on regionfile corruption -+ final DataInputStream ret = this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0)); -+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ return ret; -+ // Paper end - recalculate header on regionfile corruption - } else if (j1 > bytebuffer.remaining()) { - RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", new Object[]{pos, j1, bytebuffer.remaining()}); -+ // Paper start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ // Paper end - recalculate header on regionfile corruption - return null; - } else if (j1 < 0) { - RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, pos); -+ // Paper start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ // Paper end - recalculate header on regionfile corruption - return null; - } else { -- return this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1)); -+ // Paper start - recalculate header on regionfile corruption -+ final DataInputStream ret = this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1)); -+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ return ret; -+ // Paper end - recalculate header on regionfile corruption - } - } - } -@@ -371,10 +840,15 @@ public class RegionFile implements AutoCloseable { - } - - private ByteBuffer createExternalStub() { -+ // Paper start - add version param -+ return this.createExternalStub(this.version); -+ } -+ private ByteBuffer createExternalStub(RegionFileVersion version) { -+ // Paper end - add version param - ByteBuffer bytebuffer = ByteBuffer.allocate(5); - - bytebuffer.putInt(1); -- bytebuffer.put((byte) (this.version.getId() | 128)); -+ bytebuffer.put((byte) (version.getId() | 128)); // Paper - replace with version param - ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error - return bytebuffer; - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index 42d37bee3a459adcd46408596ccf93abbcff51fe..fe312b1aef579cb4bf81bdd967cf72ff880d7505 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -24,6 +24,7 @@ public class RegionFileStorage implements AutoCloseable { - public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap(); - private final Path folder; - private final boolean sync; -+ private final boolean isChunkData; // Paper - - // Paper start - cache regionfile does not exist state - static final int MAX_NON_EXISTING_CACHE = 1024 * 64; -@@ -55,6 +56,12 @@ public class RegionFileStorage implements AutoCloseable { - // Paper end - cache regionfile does not exist state - - protected RegionFileStorage(Path directory, boolean dsync) { // Paper - protected constructor -+ // Paper start - add isChunkData param -+ this(directory, dsync, false); -+ } -+ RegionFileStorage(Path directory, boolean dsync, boolean isChunkData) { -+ this.isChunkData = isChunkData; -+ // Paper end - add isChunkData param - this.folder = directory; - this.sync = dsync; - } -@@ -122,7 +129,7 @@ public class RegionFileStorage implements AutoCloseable { - // Paper - only create directory if not existing only - moved down - Path path = this.folder; - int j = chunkcoordintpair.getRegionX(); -- Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); -+ Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Paper - diff on change - if (existingOnly && !java.nio.file.Files.exists(path1)) { // Paper start - cache regionfile does not exist state - this.markNonExisting(regionPos); - return null; // CraftBukkit -@@ -131,7 +138,7 @@ public class RegionFileStorage implements AutoCloseable { - } - // Paper end - cache regionfile does not exist state - FileUtil.createDirectoriesSafe(this.folder); // Paper - only create directory if not existing only - moved from above -- RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync); -+ RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header - - this.regionCache.putAndMoveToFirst(i, regionfile1); - // Paper start -@@ -188,6 +195,13 @@ public class RegionFileStorage implements AutoCloseable { - if (regionfile == null) { - return null; - } -+ // Paper start - Add regionfile parameter -+ return this.read(pos, regionfile); -+ } -+ public CompoundTag read(ChunkPos pos, RegionFile regionfile) throws IOException { -+ // We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile -+ // if we decide to re-read -+ // Paper end - // CraftBukkit end - try { // Paper - DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos); -@@ -204,6 +218,20 @@ public class RegionFileStorage implements AutoCloseable { - try { - if (datainputstream != null) { - nbttagcompound = NbtIo.read((DataInput) datainputstream); -+ // Paper start - recover from corrupt regionfile header -+ if (this.isChunkData) { -+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(nbttagcompound); -+ if (!chunkPos.equals(pos)) { -+ net.minecraft.server.MinecraftServer.LOGGER.error("Attempting to read chunk data at " + pos + " but got chunk data for " + chunkPos + " instead! Attempting regionfile recalculation for regionfile " + regionfile.regionFile.toAbsolutePath()); -+ if (regionfile.recalculateHeader()) { -+ regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once. -+ return this.read(pos, regionfile); -+ } -+ net.minecraft.server.MinecraftServer.LOGGER.error("Can't recalculate regionfile header, regenerating chunk " + pos + " for " + regionfile.regionFile.toAbsolutePath()); -+ return null; -+ } -+ } -+ // Paper end - recover from corrupt regionfile header - break label43; - } - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java -index 374ff77f15e339500714580673ae8077482ba247..6210a202d27788b1304e749b5bc2d9e2b88f5a63 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java -@@ -14,7 +14,7 @@ import javax.annotation.Nullable; - import net.minecraft.util.FastBufferedInputStream; - - public class RegionFileVersion { -- private static final Int2ObjectMap VERSIONS = new Int2ObjectOpenHashMap<>(); -+ public static final Int2ObjectMap VERSIONS = new Int2ObjectOpenHashMap<>(); // Paper - private -> public - public static final RegionFileVersion VERSION_GZIP = register(new RegionFileVersion(1, (stream) -> { - return new FastBufferedInputStream(new GZIPInputStream(stream)); - }, (stream) -> { diff --git a/patches/server/1029-Fix-entity-type-tags-suggestions-in-selectors.patch b/patches/server/1023-Fix-entity-type-tags-suggestions-in-selectors.patch similarity index 100% rename from patches/server/1029-Fix-entity-type-tags-suggestions-in-selectors.patch rename to patches/server/1023-Fix-entity-type-tags-suggestions-in-selectors.patch diff --git a/patches/server/1030-Add-Alternate-Current-redstone-implementation.patch b/patches/server/1024-Add-Alternate-Current-redstone-implementation.patch similarity index 100% rename from patches/server/1030-Add-Alternate-Current-redstone-implementation.patch rename to patches/server/1024-Add-Alternate-Current-redstone-implementation.patch diff --git a/patches/server/1024-Use-Velocity-compression-and-cipher-natives.patch b/patches/server/1024-Use-Velocity-compression-and-cipher-natives.patch deleted file mode 100644 index 8f8f34ad27f3..000000000000 --- a/patches/server/1024-Use-Velocity-compression-and-cipher-natives.patch +++ /dev/null @@ -1,360 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Mon, 26 Jul 2021 02:15:17 -0400 -Subject: [PATCH] Use Velocity compression and cipher natives - - -diff --git a/build.gradle.kts b/build.gradle.kts -index 7c563ef33d12b227856e65392905bffa5289285a..376e8983fdfdbb6c3e5fd8ad0f6a05e655b622bf 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -40,6 +40,11 @@ dependencies { - runtimeOnly("org.xerial:sqlite-jdbc:3.42.0.1") - runtimeOnly("com.mysql:mysql-connector-j:8.2.0") - runtimeOnly("com.lmax:disruptor:3.4.4") // Paper -+ // Paper start - Use Velocity cipher -+ implementation("com.velocitypowered:velocity-native:3.1.2-SNAPSHOT") { -+ isTransitive = false -+ } -+ // Paper end - Use Velocity cipher - - runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.6") - runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") -diff --git a/src/main/java/net/minecraft/network/CipherDecoder.java b/src/main/java/net/minecraft/network/CipherDecoder.java -index 778beb445eac5769b9e4e07b4d1294c50ae2602b..7b895b6a626da6297f07582e96d98ecfb6c8c951 100644 ---- a/src/main/java/net/minecraft/network/CipherDecoder.java -+++ b/src/main/java/net/minecraft/network/CipherDecoder.java -@@ -7,13 +7,29 @@ import java.util.List; - import javax.crypto.Cipher; - - public class CipherDecoder extends MessageToMessageDecoder { -- private final CipherBase cipher; -+ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper - Use Velocity cipher - -- public CipherDecoder(Cipher cipher) { -- this.cipher = new CipherBase(cipher); -+ public CipherDecoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper - Use Velocity cipher -+ this.cipher = cipher; // Paper - Use Velocity cipher - } - - protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { -- list.add(this.cipher.decipher(channelHandlerContext, byteBuf)); -+ // Paper start - Use Velocity cipher -+ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf); -+ try { -+ cipher.process(compatible); -+ list.add(compatible); -+ } catch (Exception e) { -+ compatible.release(); // compatible will never be used if we throw an exception -+ throw e; -+ } -+ // Paper end - Use Velocity cipher - } -+ -+ // Paper start - Use Velocity cipher -+ @Override -+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { -+ cipher.close(); -+ } -+ // Paper end - Use Velocity cipher - } -diff --git a/src/main/java/net/minecraft/network/CipherEncoder.java b/src/main/java/net/minecraft/network/CipherEncoder.java -index 0f3d502a9680006bcdcd7d272240a2e5c3b46790..ffa1c48585fbbc1d30826d435043527f6183a3ee 100644 ---- a/src/main/java/net/minecraft/network/CipherEncoder.java -+++ b/src/main/java/net/minecraft/network/CipherEncoder.java -@@ -4,15 +4,32 @@ import io.netty.buffer.ByteBuf; - import io.netty.channel.ChannelHandlerContext; - import io.netty.handler.codec.MessageToByteEncoder; - import javax.crypto.Cipher; -+import java.util.List; - --public class CipherEncoder extends MessageToByteEncoder { -- private final CipherBase cipher; -+public class CipherEncoder extends io.netty.handler.codec.MessageToMessageEncoder { // Paper - Use Velocity cipher; change superclass -+ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper - Use Velocity cipher - -- public CipherEncoder(Cipher cipher) { -- this.cipher = new CipherBase(cipher); -+ public CipherEncoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper - Use Velocity cipher -+ this.cipher = cipher; // Paper - Use Velocity cipher - } - -- protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { -- this.cipher.encipher(byteBuf, byteBuf2); -+ protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { -+ // Paper start - Use Velocity cipher -+ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf); -+ try { -+ cipher.process(compatible); -+ list.add(compatible); -+ } catch (Exception e) { -+ compatible.release(); // compatible will never be used if we throw an exception -+ throw e; -+ } -+ // Paper end - Use Velocity cipher - } -+ -+ // Paper start - Use Velocity cipher -+ @Override -+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { -+ cipher.close(); -+ } -+ // Paper end - Use Velocity cipher - } -diff --git a/src/main/java/net/minecraft/network/CompressionDecoder.java b/src/main/java/net/minecraft/network/CompressionDecoder.java -index 2758c257cb4e2b0497bd9243c635f8fe3dbc414c..2763052eb2d5b8eb432022ab00a417976f4b2927 100644 ---- a/src/main/java/net/minecraft/network/CompressionDecoder.java -+++ b/src/main/java/net/minecraft/network/CompressionDecoder.java -@@ -13,13 +13,20 @@ public class CompressionDecoder extends ByteToMessageDecoder { - public static final int MAXIMUM_COMPRESSED_LENGTH = 2097152; - public static final int MAXIMUM_UNCOMPRESSED_LENGTH = 8388608; - private final Inflater inflater; -+ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - Use Velocity cipher - private int threshold; - private boolean validateDecompressed; - -+ // Paper start - Use Velocity cipher - public CompressionDecoder(int compressionThreshold, boolean rejectsBadPackets) { -+ this(null, compressionThreshold, rejectsBadPackets); -+ } -+ public CompressionDecoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold, boolean rejectsBadPackets) { - this.threshold = compressionThreshold; - this.validateDecompressed = rejectsBadPackets; -- this.inflater = new Inflater(); -+ this.inflater = compressor == null ? new Inflater() : null; -+ this.compressor = compressor; -+ // Paper end - Use Velocity cipher - } - - protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { -@@ -38,14 +45,42 @@ public class CompressionDecoder extends ByteToMessageDecoder { - } - } - -+ if (inflater != null) { // Paper - Use Velocity cipher; fallback to vanilla inflater - this.setupInflaterInput(byteBuf); - ByteBuf byteBuf2 = this.inflate(channelHandlerContext, i); - this.inflater.reset(); - list.add(byteBuf2); -+ return; // Paper - Use Velocity cipher -+ } // Paper - use velocity compression -+ -+ // Paper start - Use Velocity cipher -+ int claimedUncompressedSize = i; // OBFHELPER -+ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf); -+ ByteBuf uncompressed = com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(channelHandlerContext.alloc(), this.compressor, claimedUncompressedSize); -+ try { -+ this.compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize); -+ list.add(uncompressed); -+ byteBuf.clear(); -+ } catch (Exception e) { -+ uncompressed.release(); -+ throw e; -+ } finally { -+ compatibleIn.release(); -+ } -+ // Paper end - Use Velocity cipher - } - } - } - -+ // Paper start - Use Velocity cipher -+ @Override -+ public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { -+ if (this.compressor != null) { -+ this.compressor.close(); -+ } -+ } -+ // Paper end - Use Velocity cipher -+ - private void setupInflaterInput(ByteBuf buf) { - ByteBuffer byteBuffer; - if (buf.nioBufferCount() > 0) { -diff --git a/src/main/java/net/minecraft/network/CompressionEncoder.java b/src/main/java/net/minecraft/network/CompressionEncoder.java -index 859af8c845bae9781a62fa4acae56c6e2d449e10..f67f59f287d9a5cdd685b6b56ed1daf3f091e099 100644 ---- a/src/main/java/net/minecraft/network/CompressionEncoder.java -+++ b/src/main/java/net/minecraft/network/CompressionEncoder.java -@@ -6,21 +6,36 @@ import io.netty.handler.codec.MessageToByteEncoder; - import java.util.zip.Deflater; - - public class CompressionEncoder extends MessageToByteEncoder { -- private final byte[] encodeBuf = new byte[8192]; -+ private final byte[] encodeBuf; // Paper - Use Velocity cipher - private final Deflater deflater; -+ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - Use Velocity cipher - private int threshold; - -+ // Paper start - Use Velocity cipher - public CompressionEncoder(int compressionThreshold) { -+ this(null, compressionThreshold); -+ } -+ public CompressionEncoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold) { - this.threshold = compressionThreshold; -- this.deflater = new Deflater(); -+ if (compressor == null) { -+ this.encodeBuf = new byte[8192]; -+ this.deflater = new Deflater(); -+ } else { -+ this.encodeBuf = null; -+ this.deflater = null; -+ } -+ this.compressor = compressor; -+ // Paper end - Use Velocity cipher - } - -- protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) { -+ protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { // Paper - Use Velocity cipher - int i = byteBuf.readableBytes(); - if (i < this.threshold) { - VarInt.write(byteBuf2, 0); - byteBuf2.writeBytes(byteBuf); - } else { -+ // Paper start - Use Velocity cipher -+ if (this.deflater != null) { - byte[] bs = new byte[i]; - byteBuf.readBytes(bs); - VarInt.write(byteBuf2, bs.length); -@@ -33,10 +48,48 @@ public class CompressionEncoder extends MessageToByteEncoder { - } - - this.deflater.reset(); -+ return; -+ } -+ -+ VarInt.write(byteBuf2, i); -+ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf); -+ try { -+ this.compressor.deflate(compatibleIn, byteBuf2); -+ } finally { -+ compatibleIn.release(); -+ } -+ // Paper end - Use Velocity cipher - } - - } - -+ // Paper start - Use Velocity cipher -+ @Override -+ protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception{ -+ if (this.compressor != null) { -+ // We allocate bytes to be compressed plus 1 byte. This covers two cases: -+ // -+ // - Compression -+ // According to https://github.com/ebiggers/libdeflate/blob/master/libdeflate.h#L103, -+ // if the data compresses well (and we do not have some pathological case) then the maximum -+ // size the compressed size will ever be is the input size minus one. -+ // - Uncompressed -+ // This is fairly obvious - we will then have one more than the uncompressed size. -+ int initialBufferSize = msg.readableBytes() + 1; -+ return com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(ctx.alloc(), this.compressor, initialBufferSize); -+ } -+ -+ return super.allocateBuffer(ctx, msg, preferDirect); -+ } -+ -+ @Override -+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { -+ if (this.compressor != null) { -+ this.compressor.close(); -+ } -+ } -+ // Paper end - Use Velocity cipher -+ - public int getThreshold() { - return this.threshold; - } -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index a0434b92615c10a319eb4528808a83d01df2c516..3a4d03a936bbbbb54cb3d63b57ad9c3490f98c83 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -734,11 +734,28 @@ public class Connection extends SimpleChannelInboundHandler> { - return networkmanager; - } - -- public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) { -- this.encrypted = true; -- this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher)); -- this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher)); -+ // Paper start - Use Velocity cipher -+// public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) { -+// this.encrypted = true; -+// this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher)); -+// this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher)); -+// } -+ -+ public void setupEncryption(javax.crypto.SecretKey key) throws net.minecraft.util.CryptException { -+ if (!this.encrypted) { -+ try { -+ com.velocitypowered.natives.encryption.VelocityCipher decryption = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key); -+ com.velocitypowered.natives.encryption.VelocityCipher encryption = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key); -+ -+ this.encrypted = true; -+ this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryption)); -+ this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryption)); -+ } catch (java.security.GeneralSecurityException e) { -+ throw new net.minecraft.util.CryptException(e); -+ } -+ } - } -+ // Paper end - Use Velocity cipher - - public boolean isEncrypted() { - return this.encrypted; -@@ -771,16 +788,17 @@ public class Connection extends SimpleChannelInboundHandler> { - - public void setupCompression(int compressionThreshold, boolean rejectsBadPackets) { - if (compressionThreshold >= 0) { -+ com.velocitypowered.natives.compression.VelocityCompressor compressor = com.velocitypowered.natives.util.Natives.compress.get().create(io.papermc.paper.configuration.GlobalConfiguration.get().misc.compressionLevel.or(-1)); // Paper - Use Velocity cipher - if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) { - ((CompressionDecoder) this.channel.pipeline().get("decompress")).setThreshold(compressionThreshold, rejectsBadPackets); - } else { -- this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressionThreshold, rejectsBadPackets)); -+ this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressor, compressionThreshold, rejectsBadPackets)); // Paper - Use Velocity cipher - } - - if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) { - ((CompressionEncoder) this.channel.pipeline().get("compress")).setThreshold(compressionThreshold); - } else { -- this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressionThreshold)); -+ this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressor, compressionThreshold)); // Paper - Use Velocity cipher - } - this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners - } else { -diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -index a4a29a7ea0035ecf4c61ee8547a9eb24acb667d0..586521a2cbb1d4dcfb912029f65e4363ec7674a7 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -@@ -106,6 +106,11 @@ public class ServerConnectionListener { - ServerConnectionListener.LOGGER.info("Using default channel type"); - } - -+ // Paper start - Use Velocity cipher -+ ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.compress.getLoadedVariant() + " compression from Velocity."); -+ ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity."); -+ // Paper end - Use Velocity cipher -+ - this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer() { - protected void initChannel(Channel channel) { - Connection.setInitialProtocolAttributes(channel); -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 42ae62bdbe11fdfbacebf621d64e7c4985bbd1c7..f92d240e2984b0b49d09662ff33f5c524605ed47 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -235,12 +235,14 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - } - - SecretKey secretkey = packet.getSecretKey(privatekey); -- Cipher cipher = Crypt.getCipher(2, secretkey); -- Cipher cipher1 = Crypt.getCipher(1, secretkey); -+ // Paper start - Use Velocity cipher -+// Cipher cipher = Crypt.getCipher(2, secretkey); -+// Cipher cipher1 = Crypt.getCipher(1, secretkey); -+ // Paper end - Use Velocity cipher - - s = (new BigInteger(Crypt.digestData("", this.server.getKeyPair().getPublic(), secretkey))).toString(16); - this.state = ServerLoginPacketListenerImpl.State.AUTHENTICATING; -- this.connection.setEncryptionKey(cipher, cipher1); -+ this.connection.setupEncryption(secretkey); // Paper - Use Velocity cipher - } catch (CryptException cryptographyexception) { - throw new IllegalStateException("Protocol error", cryptographyexception); - } diff --git a/patches/server/1025-Detail-more-information-in-watchdog-dumps.patch b/patches/server/1025-Detail-more-information-in-watchdog-dumps.patch deleted file mode 100644 index 36f45a25f6fb..000000000000 --- a/patches/server/1025-Detail-more-information-in-watchdog-dumps.patch +++ /dev/null @@ -1,296 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 26 Mar 2020 21:59:32 -0700 -Subject: [PATCH] Detail more information in watchdog dumps - -- Dump position, world, velocity, and uuid for currently ticking entities -- Dump player name, player uuid, position, and world for packet handling - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 3a4d03a936bbbbb54cb3d63b57ad9c3490f98c83..b189aeb8646b5385c7cca0c4babfcb071a642220 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -586,7 +586,13 @@ public class Connection extends SimpleChannelInboundHandler> { - if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener) - || loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING - || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) { -+ // Paper start - detailed watchdog information -+ net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener); -+ try { // Paper end - detailed watchdog information - tickablepacketlistener.tick(); -+ } finally { // Paper start - detailed watchdog information -+ net.minecraft.network.protocol.PacketUtils.packetProcessing.pop(); -+ } // Paper end - detailed watchdog information - } - // Paper end - Buffer joins to world - } -diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -index 454d0187ff8370a0d99cca051ee0a8c50b39cfb7..3e2d5dcd62775b6ed7c0ce0ba51a71b635b1d644 100644 ---- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java -+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -@@ -18,6 +18,24 @@ public class PacketUtils { - - private static final Logger LOGGER = LogUtils.getLogger(); - -+ // Paper start - detailed watchdog information -+ public static final java.util.concurrent.ConcurrentLinkedDeque packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>(); -+ static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong(); -+ -+ public static long getTotalProcessedPackets() { -+ return totalMainThreadPacketsProcessed.get(); -+ } -+ -+ public static java.util.List getCurrentPacketProcessors() { -+ java.util.List ret = new java.util.ArrayList<>(4); -+ for (PacketListener listener : packetProcessing) { -+ ret.add(listener); -+ } -+ -+ return ret; -+ } -+ // Paper end - detailed watchdog information -+ - public PacketUtils() {} - - public static void ensureRunningOnSameThread(Packet packet, T listener, ServerLevel world) throws RunningOnDifferentThreadException { -@@ -27,6 +45,8 @@ public class PacketUtils { - public static void ensureRunningOnSameThread(Packet packet, T listener, BlockableEventLoop engine) throws RunningOnDifferentThreadException { - if (!engine.isSameThread()) { - engine.execute(() -> { // Paper - Fix preemptive player kick on a server shutdown -+ packetProcessing.push(listener); // Paper - detailed watchdog information -+ try { // Paper - detailed watchdog information - if (MinecraftServer.getServer().hasStopped() || (listener instanceof ServerCommonPacketListenerImpl && ((ServerCommonPacketListenerImpl) listener).processedDisconnect)) return; // CraftBukkit, MC-142590 - if (listener.shouldHandleMessage(packet)) { - co.aikar.timings.Timing timing = co.aikar.timings.MinecraftTimings.getPacketTiming(packet); // Paper - timings -@@ -64,6 +84,12 @@ public class PacketUtils { - } else { - PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: {}", packet); - } -+ // Paper start - detailed watchdog information -+ } finally { -+ totalMainThreadPacketsProcessed.getAndIncrement(); -+ packetProcessing.pop(); -+ } -+ // Paper end - detailed watchdog information - - }); - throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD; -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index bac2e7c8178696859ff2d38f1e095d86557fc306..5eaf8585df1f885f4a08fdd06ff4bb730961e400 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1239,7 +1239,26 @@ public class ServerLevel extends Level implements WorldGenLevel { - - } - -+ // Paper start - log detailed entity tick information -+ // TODO replace with varhandle -+ static final java.util.concurrent.atomic.AtomicReference currentlyTickingEntity = new java.util.concurrent.atomic.AtomicReference<>(); -+ -+ public static List getCurrentlyTickingEntities() { -+ Entity ticking = currentlyTickingEntity.get(); -+ List ret = java.util.Arrays.asList(ticking == null ? new Entity[0] : new Entity[] { ticking }); -+ -+ return ret; -+ } -+ // Paper end - log detailed entity tick information -+ - public void tickNonPassenger(Entity entity) { -+ // Paper start - log detailed entity tick information -+ io.papermc.paper.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); -+ try { -+ if (currentlyTickingEntity.get() == null) { -+ currentlyTickingEntity.lazySet(entity); -+ } -+ // Paper end - log detailed entity tick information - ++TimingHistory.entityTicks; // Paper - timings - // Spigot start - co.aikar.timings.Timing timer; // Paper -@@ -1279,7 +1298,13 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.tickPassenger(entity, entity1); - } - // } finally { timer.stopTiming(); } // Paper - timings - move up -- -+ // Paper start - log detailed entity tick information -+ } finally { -+ if (currentlyTickingEntity.get() == entity) { -+ currentlyTickingEntity.lazySet(null); -+ } -+ } -+ // Paper end - log detailed entity tick information - } - - private void tickPassenger(Entity vehicle, Entity passenger) { -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index c206adff916594ec573459d193cc7f3eda4b4957..a6685b3f845aa08bf14fda63d66ce98e51e67678 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1051,8 +1051,43 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - return this.onGround; - } - -+ // Paper start - detailed watchdog information -+ public final Object posLock = new Object(); // Paper - log detailed entity tick information -+ -+ private Vec3 moveVector; -+ private double moveStartX; -+ private double moveStartY; -+ private double moveStartZ; -+ -+ public final Vec3 getMoveVector() { -+ return this.moveVector; -+ } -+ -+ public final double getMoveStartX() { -+ return this.moveStartX; -+ } -+ -+ public final double getMoveStartY() { -+ return this.moveStartY; -+ } -+ -+ public final double getMoveStartZ() { -+ return this.moveStartZ; -+ } -+ // Paper end - detailed watchdog information -+ - public void move(MoverType movementType, Vec3 movement) { - final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity -+ // Paper start - detailed watchdog information -+ io.papermc.paper.util.TickThread.ensureTickThread("Cannot move an entity off-main"); -+ synchronized (this.posLock) { -+ this.moveStartX = this.getX(); -+ this.moveStartY = this.getY(); -+ this.moveStartZ = this.getZ(); -+ this.moveVector = movement; -+ } -+ try { -+ // Paper end - detailed watchdog information - if (this.noPhysics) { - this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); - } else { -@@ -1222,6 +1257,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - this.level().getProfiler().pop(); - } - } -+ // Paper start - detailed watchdog information -+ } finally { -+ synchronized (this.posLock) { // Paper -+ this.moveVector = null; -+ } // Paper -+ } -+ // Paper end - detailed watchdog information - } - - private boolean isStateClimbable(BlockState state) { -@@ -4360,7 +4402,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - public void setDeltaMovement(Vec3 velocity) { -+ synchronized (this.posLock) { // Paper - this.deltaMovement = velocity; -+ } // Paper - } - - public void addDeltaMovement(Vec3 velocity) { -@@ -4463,7 +4507,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - // Paper end - Fix MC-4 - if (this.position.x != x || this.position.y != y || this.position.z != z) { -+ synchronized (this.posLock) { // Paper - this.position = new Vec3(x, y, z); -+ } // Paper - int i = Mth.floor(x); - int j = Mth.floor(y); - int k = Mth.floor(z); -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index 0234555978d1b13051f876a257e47bafad37b0f8..9e638f72f180ff5ef63ec3dd6cf548c53f7bd4a5 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -22,6 +22,78 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa - private volatile long lastTick; - private volatile boolean stopping; - -+ // Paper start - log detailed tick information -+ private void dumpEntity(net.minecraft.world.entity.Entity entity) { -+ Logger log = Bukkit.getServer().getLogger(); -+ double posX, posY, posZ; -+ net.minecraft.world.phys.Vec3 mot; -+ double moveStartX, moveStartY, moveStartZ; -+ net.minecraft.world.phys.Vec3 moveVec; -+ synchronized (entity.posLock) { -+ posX = entity.getX(); -+ posY = entity.getY(); -+ posZ = entity.getZ(); -+ mot = entity.getDeltaMovement(); -+ moveStartX = entity.getMoveStartX(); -+ moveStartY = entity.getMoveStartY(); -+ moveStartZ = entity.getMoveStartZ(); -+ moveVec = entity.getMoveVector(); -+ } -+ -+ String entityType = net.minecraft.world.entity.EntityType.getKey(entity.getType()).toString(); -+ java.util.UUID entityUUID = entity.getUUID(); -+ net.minecraft.world.level.Level world = entity.level(); -+ -+ log.log(Level.SEVERE, "Ticking entity: " + entityType + ", entity class: " + entity.getClass().getName()); -+ log.log(Level.SEVERE, "Entity status: removed: " + entity.isRemoved() + ", valid: " + entity.valid + ", alive: " + entity.isAlive() + ", is passenger: " + entity.isPassenger()); -+ log.log(Level.SEVERE, "Entity UUID: " + entityUUID); -+ log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")"); -+ log.log(Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)"); -+ log.log(Level.SEVERE, "Entity AABB: " + entity.getBoundingBox()); -+ if (moveVec != null) { -+ log.log(Level.SEVERE, "Move call information: "); -+ log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")"); -+ log.log(Level.SEVERE, "Move vector: " + moveVec.toString()); -+ } -+ } -+ -+ private void dumpTickingInfo() { -+ Logger log = Bukkit.getServer().getLogger(); -+ -+ // ticking entities -+ for (net.minecraft.world.entity.Entity entity : net.minecraft.server.level.ServerLevel.getCurrentlyTickingEntities()) { -+ this.dumpEntity(entity); -+ net.minecraft.world.entity.Entity vehicle = entity.getVehicle(); -+ if (vehicle != null) { -+ log.log(Level.SEVERE, "Detailing vehicle for above entity:"); -+ this.dumpEntity(vehicle); -+ } -+ } -+ -+ // packet processors -+ for (net.minecraft.network.PacketListener packetListener : net.minecraft.network.protocol.PacketUtils.getCurrentPacketProcessors()) { -+ if (packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl) { -+ net.minecraft.server.level.ServerPlayer player = ((net.minecraft.server.network.ServerGamePacketListenerImpl)packetListener).player; -+ long totalPackets = net.minecraft.network.protocol.PacketUtils.getTotalProcessedPackets(); -+ if (player == null) { -+ log.log(Level.SEVERE, "Handling packet for player connection or ticking player connection (null player): " + packetListener); -+ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); -+ } else { -+ this.dumpEntity(player); -+ net.minecraft.world.entity.Entity vehicle = player.getVehicle(); -+ if (vehicle != null) { -+ log.log(Level.SEVERE, "Detailing vehicle for above entity:"); -+ this.dumpEntity(vehicle); -+ } -+ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); -+ } -+ } else { -+ log.log(Level.SEVERE, "Handling packet for connection: " + packetListener); -+ } -+ } -+ } -+ // Paper end - log detailed tick information -+ - private WatchdogThread(long timeoutTime, boolean restart) - { - super( "Paper Watchdog Thread" ); -@@ -119,6 +191,7 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa - log.log( Level.SEVERE, "------------------------------" ); - log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper - io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(isLongTimeout); // Paper - rewrite chunk system -+ this.dumpTickingInfo(); // Paper - log detailed tick information - WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); - log.log( Level.SEVERE, "------------------------------" ); - // diff --git a/patches/server/1031-optimize-dirt-and-snow-spreading.patch b/patches/server/1025-optimize-dirt-and-snow-spreading.patch similarity index 100% rename from patches/server/1031-optimize-dirt-and-snow-spreading.patch rename to patches/server/1025-optimize-dirt-and-snow-spreading.patch diff --git a/patches/server/1026-Collision-optimisations.patch b/patches/server/1026-Collision-optimisations.patch deleted file mode 100644 index 8731ad016d32..000000000000 --- a/patches/server/1026-Collision-optimisations.patch +++ /dev/null @@ -1,4673 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 4 May 2020 10:06:24 -0700 -Subject: [PATCH] Collision optimisations - -The collision patch has been designed with the assumption that -most shapes are either a single AABB or an ArrayVoxelShape -(typical voxel bitset representation). Like previously, -single AABB shapes are treated as AABBs. Unlike previously, the -VoxelShape class has been changed to carry shape data that -ArrayVoxelShape would, except in a discrete manner rather -than abstracted away (not hidden behind DoubleList and -the poorly named DiscreteVoxelShape). - -VoxelShape now carries three important states: - 1. The voxel bitset + its sizes for the X, Y, and Z axis - 2. The voxel coordinates (represented as an array and an offset per axis) - 3. Single AABB representation, if possible - -Note that if the single AABB representation is present, -it is used instead of the voxel bitset representation as -the single AABB representation is a special case of the -voxel bitset representation and can be optimised as such. - -This effectively turns every VoxelShape instance, regardless of -actual class, into a typical voxel bitset representation. -This allows all VoxelShape operations to be optimised -for voxel bitset representations without dealing with the -abstraction and indirection that was imposed on VoxelShape -by Mojang. The patch now effectively optimises all VoxelShape -operations. Below is a list of some of the operations optimised: - - Shape merging/ORing - - Shape optimisation - - Occlusion checking - - Non-single AABB VoxelShape collisions/intersection - - Shape raytracing - - Empty VoxelShape testing - -This patch also includes optimisations for raytracing, -which mostly boil down to removing indirection caused by the -interface BlockGetter which allows chunk caching. - -diff --git a/src/main/java/io/papermc/paper/util/CachedLists.java b/src/main/java/io/papermc/paper/util/CachedLists.java -index be668387f65a633c6ac497fca632a4767a1bf3a2..e08f4e39db4ee3fed62e37364d17dcc5c5683504 100644 ---- a/src/main/java/io/papermc/paper/util/CachedLists.java -+++ b/src/main/java/io/papermc/paper/util/CachedLists.java -@@ -1,8 +1,57 @@ - package io.papermc.paper.util; - -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.phys.AABB; -+import org.bukkit.Bukkit; -+import org.bukkit.craftbukkit.util.UnsafeList; -+import java.util.List; -+ - public final class CachedLists { - -- public static void reset() { -+ // Paper start - optimise collisions -+ static final UnsafeList TEMP_COLLISION_LIST = new UnsafeList<>(1024); -+ static boolean tempCollisionListInUse; -+ -+ public static UnsafeList getTempCollisionList() { -+ if (!Bukkit.isPrimaryThread() || tempCollisionListInUse) { -+ return new UnsafeList<>(16); -+ } -+ tempCollisionListInUse = true; -+ return TEMP_COLLISION_LIST; -+ } -+ -+ public static void returnTempCollisionList(List list) { -+ if (list != TEMP_COLLISION_LIST) { -+ return; -+ } -+ ((UnsafeList)list).setSize(0); -+ tempCollisionListInUse = false; -+ } - -+ static final UnsafeList TEMP_GET_ENTITIES_LIST = new UnsafeList<>(1024); -+ static boolean tempGetEntitiesListInUse; -+ -+ public static UnsafeList getTempGetEntitiesList() { -+ if (!Bukkit.isPrimaryThread() || tempGetEntitiesListInUse) { -+ return new UnsafeList<>(16); -+ } -+ tempGetEntitiesListInUse = true; -+ return TEMP_GET_ENTITIES_LIST; -+ } -+ -+ public static void returnTempGetEntitiesList(List list) { -+ if (list != TEMP_GET_ENTITIES_LIST) { -+ return; -+ } -+ ((UnsafeList)list).setSize(0); -+ tempGetEntitiesListInUse = false; -+ } -+ // Paper end - optimise collisions -+ -+ public static void reset() { -+ // Paper start - optimise collisions -+ TEMP_COLLISION_LIST.completeReset(); -+ TEMP_GET_ENTITIES_LIST.completeReset(); -+ // Paper end - optimise collisions - } - } -diff --git a/src/main/java/io/papermc/paper/util/CollisionUtil.java b/src/main/java/io/papermc/paper/util/CollisionUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ee0331a6bc40cdde08d926fd8eb1dc642630c2e5 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/CollisionUtil.java -@@ -0,0 +1,1851 @@ -+package io.papermc.paper.util; -+ -+import io.papermc.paper.util.collisions.CachedShapeData; -+import it.unimi.dsi.fastutil.doubles.DoubleArrayList; -+import it.unimi.dsi.fastutil.doubles.DoubleList; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.server.level.ServerChunkCache; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.item.Item; -+import net.minecraft.world.level.CollisionGetter; -+import net.minecraft.world.level.EntityGetter; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.border.WorldBorder; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.PalettedContainer; -+import net.minecraft.world.level.material.FluidState; -+import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.Vec3; -+import net.minecraft.world.phys.shapes.ArrayVoxelShape; -+import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape; -+import net.minecraft.world.phys.shapes.BooleanOp; -+import net.minecraft.world.phys.shapes.CollisionContext; -+import net.minecraft.world.phys.shapes.DiscreteVoxelShape; -+import net.minecraft.world.phys.shapes.EntityCollisionContext; -+import net.minecraft.world.phys.shapes.OffsetDoubleList; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.Arrays; -+import java.util.List; -+import java.util.function.BiPredicate; -+import java.util.function.Predicate; -+ -+public final class CollisionUtil { -+ -+ public static final double COLLISION_EPSILON = 1.0E-7; -+ public static final DoubleArrayList ZERO_ONE = DoubleArrayList.wrap(new double[] { 0.0, 1.0 }); -+ -+ public static boolean isSpecialCollidingBlock(final net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase block) { -+ return block.hasLargeCollisionShape() || block.getBlock() == Blocks.MOVING_PISTON; -+ } -+ -+ public static boolean isEmpty(final AABB aabb) { -+ return (aabb.maxX - aabb.minX) < COLLISION_EPSILON || (aabb.maxY - aabb.minY) < COLLISION_EPSILON || (aabb.maxZ - aabb.minZ) < COLLISION_EPSILON; -+ } -+ -+ public static boolean isEmpty(final double minX, final double minY, final double minZ, -+ final double maxX, final double maxY, final double maxZ) { -+ return (maxX - minX) < COLLISION_EPSILON || (maxY - minY) < COLLISION_EPSILON || (maxZ - minZ) < COLLISION_EPSILON; -+ } -+ -+ public static AABB getBoxForChunk(final int chunkX, final int chunkZ) { -+ double x = (double)(chunkX << 4); -+ double z = (double)(chunkZ << 4); -+ // use a bounding box bigger than the chunk to prevent entities from entering it on move -+ return new AABB(x - 3*COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*COLLISION_EPSILON, -+ x + (16.0 + 3*COLLISION_EPSILON), Double.POSITIVE_INFINITY, z + (16.0 + 3*COLLISION_EPSILON), false); -+ } -+ -+ /* -+ A couple of rules for VoxelShape collisions: -+ Two shapes only intersect if they are actually more than EPSILON units into each other. This also applies to movement -+ checks. -+ If the two shapes strictly collide, then the return value of a collide call will return a value in the opposite -+ direction of the source move. However, this value will not be greater in magnitude than EPSILON. Collision code -+ will automatically round it to 0. -+ */ -+ -+ public static boolean voxelShapeIntersect(final double minX1, final double minY1, final double minZ1, final double maxX1, -+ final double maxY1, final double maxZ1, final double minX2, final double minY2, -+ final double minZ2, final double maxX2, final double maxY2, final double maxZ2) { -+ return (minX1 - maxX2) < -COLLISION_EPSILON && (maxX1 - minX2) > COLLISION_EPSILON && -+ (minY1 - maxY2) < -COLLISION_EPSILON && (maxY1 - minY2) > COLLISION_EPSILON && -+ (minZ1 - maxZ2) < -COLLISION_EPSILON && (maxZ1 - minZ2) > COLLISION_EPSILON; -+ } -+ -+ public static boolean voxelShapeIntersect(final AABB box, final double minX, final double minY, final double minZ, -+ final double maxX, final double maxY, final double maxZ) { -+ return (box.minX - maxX) < -COLLISION_EPSILON && (box.maxX - minX) > COLLISION_EPSILON && -+ (box.minY - maxY) < -COLLISION_EPSILON && (box.maxY - minY) > COLLISION_EPSILON && -+ (box.minZ - maxZ) < -COLLISION_EPSILON && (box.maxZ - minZ) > COLLISION_EPSILON; -+ } -+ -+ public static boolean voxelShapeIntersect(final AABB box1, final AABB box2) { -+ return (box1.minX - box2.maxX) < -COLLISION_EPSILON && (box1.maxX - box2.minX) > COLLISION_EPSILON && -+ (box1.minY - box2.maxY) < -COLLISION_EPSILON && (box1.maxY - box2.minY) > COLLISION_EPSILON && -+ (box1.minZ - box2.maxZ) < -COLLISION_EPSILON && (box1.maxZ - box2.minZ) > COLLISION_EPSILON; -+ } -+ -+ // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON -+ public static double collideX(final AABB target, final AABB source, final double source_move) { -+ if ((source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON && -+ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { -+ if (source_move >= 0.0) { -+ final double max_move = target.minX - source.maxX; // < 0.0 if no strict collision -+ if (max_move < -COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.min(max_move, source_move); -+ } else { -+ final double max_move = target.maxX - source.minX; // > 0.0 if no strict collision -+ if (max_move > COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.max(max_move, source_move); -+ } -+ } -+ return source_move; -+ } -+ -+ // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON -+ public static double collideY(final AABB target, final AABB source, final double source_move) { -+ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && -+ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { -+ if (source_move >= 0.0) { -+ final double max_move = target.minY - source.maxY; // < 0.0 if no strict collision -+ if (max_move < -COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.min(max_move, source_move); -+ } else { -+ final double max_move = target.maxY - source.minY; // > 0.0 if no strict collision -+ if (max_move > COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.max(max_move, source_move); -+ } -+ } -+ return source_move; -+ } -+ -+ // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON -+ public static double collideZ(final AABB target, final AABB source, final double source_move) { -+ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && -+ (source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON) { -+ if (source_move >= 0.0) { -+ final double max_move = target.minZ - source.maxZ; // < 0.0 if no strict collision -+ if (max_move < -COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.min(max_move, source_move); -+ } else { -+ final double max_move = target.maxZ - source.minZ; // > 0.0 if no strict collision -+ if (max_move > COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.max(max_move, source_move); -+ } -+ } -+ return source_move; -+ } -+ -+ // startIndex and endIndex inclusive -+ // assumes indices are in range of array -+ private static int findFloor(final double[] values, final double value, int startIndex, int endIndex) { -+ do { -+ final int middle = (startIndex + endIndex) >>> 1; -+ final double middleVal = values[middle]; -+ -+ if (value < middleVal) { -+ endIndex = middle - 1; -+ } else { -+ startIndex = middle + 1; -+ } -+ } while (startIndex <= endIndex); -+ -+ return startIndex - 1; -+ } -+ -+ public static boolean voxelShapeIntersectNoEmpty(final VoxelShape voxel, final AABB aabb) { -+ if (voxel.isEmpty()) { -+ return false; -+ } -+ -+ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true -+ -+ // offsets that should be applied to coords -+ final double off_x = voxel.offsetX(); -+ final double off_y = voxel.offsetY(); -+ final double off_z = voxel.offsetZ(); -+ -+ final double[] coords_x = voxel.rootCoordinatesX(); -+ final double[] coords_y = voxel.rootCoordinatesY(); -+ final double[] coords_z = voxel.rootCoordinatesZ(); -+ -+ final CachedShapeData cached_shape_data = voxel.getCachedVoxelData(); -+ -+ // note: size = coords.length - 1 -+ final int size_x = cached_shape_data.sizeX(); -+ final int size_y = cached_shape_data.sizeY(); -+ final int size_z = cached_shape_data.sizeZ(); -+ -+ // note: voxel bitset with set index (x, y, z) indicates that -+ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1]) -+ // is collidable. this is the fundamental principle of operation for the voxel collision operation -+ -+ // note: we should be offsetting coords, but we can also just subtract from source as well - which is -+ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that) -+ // note: for intersection, one we find the floor of the min we can use that as the start index -+ // for the next check as source max >= source min -+ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size, -+ // as this implies that coords[coords.length - 1] < source min -+ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max -+ -+ final int floor_min_x = Math.max( -+ 0, -+ findFloor(coords_x, (aabb.minX - off_x) + COLLISION_EPSILON, 0, size_x) -+ ); -+ if (floor_min_x >= size_x) { -+ // cannot intersect -+ return false; -+ } -+ -+ final int ceil_max_x = Math.min( -+ size_x, -+ findFloor(coords_x, (aabb.maxX - off_x) - COLLISION_EPSILON, floor_min_x, size_x) + 1 -+ ); -+ if (floor_min_x >= ceil_max_x) { -+ // cannot intersect -+ return false; -+ } -+ -+ final int floor_min_y = Math.max( -+ 0, -+ findFloor(coords_y, (aabb.minY - off_y) + COLLISION_EPSILON, 0, size_y) -+ ); -+ if (floor_min_y >= size_y) { -+ // cannot intersect -+ return false; -+ } -+ -+ final int ceil_max_y = Math.min( -+ size_y, -+ findFloor(coords_y, (aabb.maxY - off_y) - COLLISION_EPSILON, floor_min_y, size_y) + 1 -+ ); -+ if (floor_min_y >= ceil_max_y) { -+ // cannot intersect -+ return false; -+ } -+ -+ final int floor_min_z = Math.max( -+ 0, -+ findFloor(coords_z, (aabb.minZ - off_z) + COLLISION_EPSILON, 0, size_z) -+ ); -+ if (floor_min_z >= size_z) { -+ // cannot intersect -+ return false; -+ } -+ -+ final int ceil_max_z = Math.min( -+ size_z, -+ findFloor(coords_z, (aabb.maxZ - off_z) - COLLISION_EPSILON, floor_min_z, size_z) + 1 -+ ); -+ if (floor_min_z >= ceil_max_z) { -+ // cannot intersect -+ return false; -+ } -+ -+ final long[] bitset = cached_shape_data.voxelSet(); -+ -+ // check bitset to check if any shapes in range are full -+ -+ final int mul_x = size_y*size_z; -+ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { -+ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { -+ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { -+ final int index = curr_z + curr_y*size_z + curr_x*mul_x; -+ // note: JLS states long shift operators ANDS shift by 63 -+ if ((bitset[index >>> 6] & (1L << index)) != 0L) { -+ return true; -+ } -+ } -+ } -+ } -+ -+ return false; -+ } -+ -+ // assume !target.isEmpty() && abs(source_move) >= COLLISION_EPSILON -+ public static double collideX(final VoxelShape target, final AABB source, final double source_move) { -+ final AABB single_aabb = target.getSingleAABBRepresentation(); -+ if (single_aabb != null) { -+ return collideX(single_aabb, source, source_move); -+ } -+ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true -+ -+ // offsets that should be applied to coords -+ final double off_x = target.offsetX(); -+ final double off_y = target.offsetY(); -+ final double off_z = target.offsetZ(); -+ -+ final double[] coords_x = target.rootCoordinatesX(); -+ final double[] coords_y = target.rootCoordinatesY(); -+ final double[] coords_z = target.rootCoordinatesZ(); -+ -+ final CachedShapeData cached_shape_data = target.getCachedVoxelData(); -+ -+ // note: size = coords.length - 1 -+ final int size_x = cached_shape_data.sizeX(); -+ final int size_y = cached_shape_data.sizeY(); -+ final int size_z = cached_shape_data.sizeZ(); -+ -+ // note: voxel bitset with set index (x, y, z) indicates that -+ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1]) -+ // is collidable. this is the fundamental principle of operation for the voxel collision operation -+ -+ -+ // note: we should be offsetting coords, but we can also just subtract from source as well - which is -+ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that) -+ // note: for intersection, one we find the floor of the min we can use that as the start index -+ // for the next check as source max >= source min -+ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size, -+ // as this implies that coords[coords.length - 1] < source min -+ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max -+ -+ final int floor_min_y = Math.max( -+ 0, -+ findFloor(coords_y, (source.minY - off_y) + COLLISION_EPSILON, 0, size_y) -+ ); -+ if (floor_min_y >= size_y) { -+ // cannot intersect -+ return source_move; -+ } -+ -+ final int ceil_max_y = Math.min( -+ size_y, -+ findFloor(coords_y, (source.maxY - off_y) - COLLISION_EPSILON, floor_min_y, size_y) + 1 -+ ); -+ if (floor_min_y >= ceil_max_y) { -+ // cannot intersect -+ return source_move; -+ } -+ -+ final int floor_min_z = Math.max( -+ 0, -+ findFloor(coords_z, (source.minZ - off_z) + COLLISION_EPSILON, 0, size_z) -+ ); -+ if (floor_min_z >= size_z) { -+ // cannot intersect -+ return source_move; -+ } -+ -+ final int ceil_max_z = Math.min( -+ size_z, -+ findFloor(coords_z, (source.maxZ - off_z) - COLLISION_EPSILON, floor_min_z, size_z) + 1 -+ ); -+ if (floor_min_z >= ceil_max_z) { -+ // cannot intersect -+ return source_move; -+ } -+ -+ // index = z + y*size_z + x*(size_z*size_y) -+ -+ final long[] bitset = cached_shape_data.voxelSet(); -+ -+ if (source_move > 0.0) { -+ final double source_max = source.maxX - off_x; -+ final int ceil_max_x = findFloor( -+ coords_x, source_max - COLLISION_EPSILON, 0, size_x -+ ) + 1; // add one, we are not interested in (coords[i] + COLLISION_EPSILON) < max -+ -+ // note: only the order of the first loop matters -+ -+ // note: we cannot collide with the face at index size on the collision axis for forward movement -+ -+ final int mul_x = size_y*size_z; -+ for (int curr_x = ceil_max_x; curr_x < size_x; ++curr_x) { -+ double max_dist = coords_x[curr_x] - source_max; -+ if (max_dist >= source_move) { -+ // if we reach here, then we will never have a case where -+ // coords[curr + n] - source_max < source_move, as coords[curr + n] < coords[curr + n + 1] -+ // thus, we can return immediately -+ -+ // this optimization is important since this loop is bounded by size, and _not_ by -+ // a calculated max index based off of source_move - so it would be possible to check -+ // the whole intersected shape for collisions when we didn't need to! -+ return source_move; -+ } -+ if (max_dist >= -COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON -+ max_dist = Math.min(max_dist, source_move); -+ } -+ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { -+ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { -+ final int index = curr_z + curr_y*size_z + curr_x*mul_x; -+ // note: JLS states long shift operators ANDS shift by 63 -+ if ((bitset[index >>> 6] & (1L << index)) != 0L) { -+ return max_dist; -+ } -+ } -+ } -+ } -+ -+ return source_move; -+ } else { -+ final double source_min = source.minX - off_x; -+ final int floor_min_x = findFloor( -+ coords_x, source_min + COLLISION_EPSILON, 0, size_x -+ ); -+ -+ // note: only the order of the first loop matters -+ -+ // note: we cannot collide with the face at index 0 on the collision axis for backwards movement -+ -+ // note: we offset the collision axis by - 1 for the voxel bitset index, but use + 1 for the -+ // coordinate index as the voxelset stores whether the shape is solid for [index, index + 1] -+ // thus, we need to use the voxel index i-1 if we want to check that the face at index i is solid -+ final int mul_x = size_y*size_z; -+ for (int curr_x = floor_min_x - 1; curr_x >= 0; --curr_x) { -+ double max_dist = coords_x[curr_x + 1] - source_min; -+ if (max_dist <= source_move) { -+ // if we reach here, then we will never have a case where -+ // coords[curr + n] - source_max > source_move, as coords[curr + n] > coords[curr + n - 1] -+ // thus, we can return immediately -+ -+ // this optimization is important since this loop is possibly bounded by size, and _not_ by -+ // a calculated max index based off of source_move - so it would be possible to check -+ // the whole intersected shape for collisions when we didn't need to! -+ return source_move; -+ } -+ if (max_dist <= COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON -+ max_dist = Math.max(max_dist, source_move); -+ } -+ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { -+ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { -+ final int index = curr_z + curr_y*size_z + curr_x*mul_x; -+ // note: JLS states long shift operators ANDS shift by 63 -+ if ((bitset[index >>> 6] & (1L << index)) != 0L) { -+ return max_dist; -+ } -+ } -+ } -+ } -+ -+ return source_move; -+ } -+ } -+ -+ public static double collideY(final VoxelShape target, final AABB source, final double source_move) { -+ final AABB single_aabb = target.getSingleAABBRepresentation(); -+ if (single_aabb != null) { -+ return collideY(single_aabb, source, source_move); -+ } -+ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true -+ -+ // offsets that should be applied to coords -+ final double off_x = target.offsetX(); -+ final double off_y = target.offsetY(); -+ final double off_z = target.offsetZ(); -+ -+ final double[] coords_x = target.rootCoordinatesX(); -+ final double[] coords_y = target.rootCoordinatesY(); -+ final double[] coords_z = target.rootCoordinatesZ(); -+ -+ final CachedShapeData cached_shape_data = target.getCachedVoxelData(); -+ -+ // note: size = coords.length - 1 -+ final int size_x = cached_shape_data.sizeX(); -+ final int size_y = cached_shape_data.sizeY(); -+ final int size_z = cached_shape_data.sizeZ(); -+ -+ // note: voxel bitset with set index (x, y, z) indicates that -+ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1]) -+ // is collidable. this is the fundamental principle of operation for the voxel collision operation -+ -+ -+ // note: we should be offsetting coords, but we can also just subtract from source as well - which is -+ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that) -+ // note: for intersection, one we find the floor of the min we can use that as the start index -+ // for the next check as source max >= source min -+ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size, -+ // as this implies that coords[coords.length - 1] < source min -+ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max -+ -+ final int floor_min_x = Math.max( -+ 0, -+ findFloor(coords_x, (source.minX - off_x) + COLLISION_EPSILON, 0, size_x) -+ ); -+ if (floor_min_x >= size_x) { -+ // cannot intersect -+ return source_move; -+ } -+ -+ final int ceil_max_x = Math.min( -+ size_x, -+ findFloor(coords_x, (source.maxX - off_x) - COLLISION_EPSILON, floor_min_x, size_x) + 1 -+ ); -+ if (floor_min_x >= ceil_max_x) { -+ // cannot intersect -+ return source_move; -+ } -+ -+ final int floor_min_z = Math.max( -+ 0, -+ findFloor(coords_z, (source.minZ - off_z) + COLLISION_EPSILON, 0, size_z) -+ ); -+ if (floor_min_z >= size_z) { -+ // cannot intersect -+ return source_move; -+ } -+ -+ final int ceil_max_z = Math.min( -+ size_z, -+ findFloor(coords_z, (source.maxZ - off_z) - COLLISION_EPSILON, floor_min_z, size_z) + 1 -+ ); -+ if (floor_min_z >= ceil_max_z) { -+ // cannot intersect -+ return source_move; -+ } -+ -+ // index = z + y*size_z + x*(size_z*size_y) -+ -+ final long[] bitset = cached_shape_data.voxelSet(); -+ -+ if (source_move > 0.0) { -+ final double source_max = source.maxY - off_y; -+ final int ceil_max_y = findFloor( -+ coords_y, source_max - COLLISION_EPSILON, 0, size_y -+ ) + 1; // add one, we are not interested in (coords[i] + COLLISION_EPSILON) < max -+ -+ // note: only the order of the first loop matters -+ -+ // note: we cannot collide with the face at index size on the collision axis for forward movement -+ -+ final int mul_x = size_y*size_z; -+ for (int curr_y = ceil_max_y; curr_y < size_y; ++curr_y) { -+ double max_dist = coords_y[curr_y] - source_max; -+ if (max_dist >= source_move) { -+ // if we reach here, then we will never have a case where -+ // coords[curr + n] - source_max < source_move, as coords[curr + n] < coords[curr + n + 1] -+ // thus, we can return immediately -+ -+ // this optimization is important since this loop is bounded by size, and _not_ by -+ // a calculated max index based off of source_move - so it would be possible to check -+ // the whole intersected shape for collisions when we didn't need to! -+ return source_move; -+ } -+ if (max_dist >= -COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON -+ max_dist = Math.min(max_dist, source_move); -+ } -+ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { -+ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { -+ final int index = curr_z + curr_y*size_z + curr_x*mul_x; -+ // note: JLS states long shift operators ANDS shift by 63 -+ if ((bitset[index >>> 6] & (1L << index)) != 0L) { -+ return max_dist; -+ } -+ } -+ } -+ } -+ -+ return source_move; -+ } else { -+ final double source_min = source.minY - off_y; -+ final int floor_min_y = findFloor( -+ coords_y, source_min + COLLISION_EPSILON, 0, size_y -+ ); -+ -+ // note: only the order of the first loop matters -+ -+ // note: we cannot collide with the face at index 0 on the collision axis for backwards movement -+ -+ // note: we offset the collision axis by - 1 for the voxel bitset index, but use + 1 for the -+ // coordinate index as the voxelset stores whether the shape is solid for [index, index + 1] -+ // thus, we need to use the voxel index i-1 if we want to check that the face at index i is solid -+ final int mul_x = size_y*size_z; -+ for (int curr_y = floor_min_y - 1; curr_y >= 0; --curr_y) { -+ double max_dist = coords_y[curr_y + 1] - source_min; -+ if (max_dist <= source_move) { -+ // if we reach here, then we will never have a case where -+ // coords[curr + n] - source_max > source_move, as coords[curr + n] > coords[curr + n - 1] -+ // thus, we can return immediately -+ -+ // this optimization is important since this loop is possibly bounded by size, and _not_ by -+ // a calculated max index based off of source_move - so it would be possible to check -+ // the whole intersected shape for collisions when we didn't need to! -+ return source_move; -+ } -+ if (max_dist <= COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON -+ max_dist = Math.max(max_dist, source_move); -+ } -+ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { -+ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { -+ final int index = curr_z + curr_y*size_z + curr_x*mul_x; -+ // note: JLS states long shift operators ANDS shift by 63 -+ if ((bitset[index >>> 6] & (1L << index)) != 0L) { -+ return max_dist; -+ } -+ } -+ } -+ } -+ -+ return source_move; -+ } -+ } -+ -+ public static double collideZ(final VoxelShape target, final AABB source, final double source_move) { -+ final AABB single_aabb = target.getSingleAABBRepresentation(); -+ if (single_aabb != null) { -+ return collideZ(single_aabb, source, source_move); -+ } -+ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true -+ -+ // offsets that should be applied to coords -+ final double off_x = target.offsetX(); -+ final double off_y = target.offsetY(); -+ final double off_z = target.offsetZ(); -+ -+ final double[] coords_x = target.rootCoordinatesX(); -+ final double[] coords_y = target.rootCoordinatesY(); -+ final double[] coords_z = target.rootCoordinatesZ(); -+ -+ final CachedShapeData cached_shape_data = target.getCachedVoxelData(); -+ -+ // note: size = coords.length - 1 -+ final int size_x = cached_shape_data.sizeX(); -+ final int size_y = cached_shape_data.sizeY(); -+ final int size_z = cached_shape_data.sizeZ(); -+ -+ // note: voxel bitset with set index (x, y, z) indicates that -+ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1]) -+ // is collidable. this is the fundamental principle of operation for the voxel collision operation -+ -+ -+ // note: we should be offsetting coords, but we can also just subtract from source as well - which is -+ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that) -+ // note: for intersection, one we find the floor of the min we can use that as the start index -+ // for the next check as source max >= source min -+ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size, -+ // as this implies that coords[coords.length - 1] < source min -+ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max -+ -+ final int floor_min_x = Math.max( -+ 0, -+ findFloor(coords_x, (source.minX - off_x) + COLLISION_EPSILON, 0, size_x) -+ ); -+ if (floor_min_x >= size_x) { -+ // cannot intersect -+ return source_move; -+ } -+ -+ final int ceil_max_x = Math.min( -+ size_x, -+ findFloor(coords_x, (source.maxX - off_x) - COLLISION_EPSILON, floor_min_x, size_x) + 1 -+ ); -+ if (floor_min_x >= ceil_max_x) { -+ // cannot intersect -+ return source_move; -+ } -+ -+ final int floor_min_y = Math.max( -+ 0, -+ findFloor(coords_y, (source.minY - off_y) + COLLISION_EPSILON, 0, size_y) -+ ); -+ if (floor_min_y >= size_y) { -+ // cannot intersect -+ return source_move; -+ } -+ -+ final int ceil_max_y = Math.min( -+ size_y, -+ findFloor(coords_y, (source.maxY - off_y) - COLLISION_EPSILON, floor_min_y, size_y) + 1 -+ ); -+ if (floor_min_y >= ceil_max_y) { -+ // cannot intersect -+ return source_move; -+ } -+ -+ // index = z + y*size_z + x*(size_z*size_y) -+ -+ final long[] bitset = cached_shape_data.voxelSet(); -+ -+ if (source_move > 0.0) { -+ final double source_max = source.maxZ - off_z; -+ final int ceil_max_z = findFloor( -+ coords_z, source_max - COLLISION_EPSILON, 0, size_z -+ ) + 1; // add one, we are not interested in (coords[i] + COLLISION_EPSILON) < max -+ -+ // note: only the order of the first loop matters -+ -+ // note: we cannot collide with the face at index size on the collision axis for forward movement -+ -+ final int mul_x = size_y*size_z; -+ for (int curr_z = ceil_max_z; curr_z < size_z; ++curr_z) { -+ double max_dist = coords_z[curr_z] - source_max; -+ if (max_dist >= source_move) { -+ // if we reach here, then we will never have a case where -+ // coords[curr + n] - source_max < source_move, as coords[curr + n] < coords[curr + n + 1] -+ // thus, we can return immediately -+ -+ // this optimization is important since this loop is bounded by size, and _not_ by -+ // a calculated max index based off of source_move - so it would be possible to check -+ // the whole intersected shape for collisions when we didn't need to! -+ return source_move; -+ } -+ if (max_dist >= -COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON -+ max_dist = Math.min(max_dist, source_move); -+ } -+ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { -+ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { -+ final int index = curr_z + curr_y*size_z + curr_x*mul_x; -+ // note: JLS states long shift operators ANDS shift by 63 -+ if ((bitset[index >>> 6] & (1L << index)) != 0L) { -+ return max_dist; -+ } -+ } -+ } -+ } -+ -+ return source_move; -+ } else { -+ final double source_min = source.minZ - off_z; -+ final int floor_min_z = findFloor( -+ coords_z, source_min + COLLISION_EPSILON, 0, size_z -+ ); -+ -+ // note: only the order of the first loop matters -+ -+ // note: we cannot collide with the face at index 0 on the collision axis for backwards movement -+ -+ // note: we offset the collision axis by - 1 for the voxel bitset index, but use + 1 for the -+ // coordinate index as the voxelset stores whether the shape is solid for [index, index + 1] -+ // thus, we need to use the voxel index i-1 if we want to check that the face at index i is solid -+ final int mul_x = size_y*size_z; -+ for (int curr_z = floor_min_z - 1; curr_z >= 0; --curr_z) { -+ double max_dist = coords_z[curr_z + 1] - source_min; -+ if (max_dist <= source_move) { -+ // if we reach here, then we will never have a case where -+ // coords[curr + n] - source_max > source_move, as coords[curr + n] > coords[curr + n - 1] -+ // thus, we can return immediately -+ -+ // this optimization is important since this loop is possibly bounded by size, and _not_ by -+ // a calculated max index based off of source_move - so it would be possible to check -+ // the whole intersected shape for collisions when we didn't need to! -+ return source_move; -+ } -+ if (max_dist <= COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON -+ max_dist = Math.max(max_dist, source_move); -+ } -+ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { -+ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { -+ final int index = curr_z + curr_y*size_z + curr_x*mul_x; -+ // note: JLS states long shift operators ANDS shift by 63 -+ if ((bitset[index >>> 6] & (1L << index)) != 0L) { -+ return max_dist; -+ } -+ } -+ } -+ } -+ -+ return source_move; -+ } -+ } -+ -+ // does not use epsilon -+ public static boolean strictlyContains(final VoxelShape voxel, final Vec3 point) { -+ return strictlyContains(voxel, point.x, point.y, point.z); -+ } -+ -+ // does not use epsilon -+ public static boolean strictlyContains(final VoxelShape voxel, double x, double y, double z) { -+ final AABB single_aabb = voxel.getSingleAABBRepresentation(); -+ if (single_aabb != null) { -+ return single_aabb.contains(x, y, z); -+ } -+ -+ if (voxel.isEmpty()) { -+ // bitset is clear, no point in searching -+ return false; -+ } -+ -+ // offset input -+ x -= voxel.offsetX(); -+ y -= voxel.offsetY(); -+ z -= voxel.offsetZ(); -+ -+ final double[] coords_x = voxel.rootCoordinatesX(); -+ final double[] coords_y = voxel.rootCoordinatesY(); -+ final double[] coords_z = voxel.rootCoordinatesZ(); -+ -+ final CachedShapeData cached_shape_data = voxel.getCachedVoxelData(); -+ -+ // note: size = coords.length - 1 -+ final int size_x = cached_shape_data.sizeX(); -+ final int size_y = cached_shape_data.sizeY(); -+ final int size_z = cached_shape_data.sizeZ(); -+ -+ // note: should mirror AABB#contains, which is that for any point X that X >= min and X < max. -+ // specifically, it cannot collide on the max bounds of the shape -+ -+ final int index_x = findFloor(coords_x, x, 0, size_x); -+ if (index_x < 0 || index_x >= size_x) { -+ return false; -+ } -+ -+ final int index_y = findFloor(coords_y, y, 0, size_y); -+ if (index_y < 0 || index_y >= size_y) { -+ return false; -+ } -+ -+ final int index_z = findFloor(coords_z, z, 0, size_z); -+ if (index_z < 0 || index_z >= size_z) { -+ return false; -+ } -+ -+ // index = z + y*size_z + x*(size_z*size_y) -+ -+ final int index = index_z + index_y*size_z + index_x*(size_z*size_y); -+ -+ final long[] bitset = cached_shape_data.voxelSet(); -+ -+ return (bitset[index >>> 6] & (1L << index)) != 0L; -+ } -+ -+ private static int makeBitset(final boolean ft, final boolean tf, final boolean tt) { -+ // idx ff -> 0 -+ // idx ft -> 1 -+ // idx tf -> 2 -+ // idx tt -> 3 -+ return ((ft ? 1 : 0) << 1) | ((tf ? 1 : 0) << 2) | ((tt ? 1 : 0) << 3); -+ } -+ -+ private static BitSetDiscreteVoxelShape merge(final CachedShapeData shapeDataFirst, final CachedShapeData shapeDataSecond, -+ final MergedVoxelCoordinateList mergedX, final MergedVoxelCoordinateList mergedY, -+ final MergedVoxelCoordinateList mergedZ, -+ final int booleanOp) { -+ final int sizeX = mergedX.voxels; -+ final int sizeY = mergedY.voxels; -+ final int sizeZ = mergedZ.voxels; -+ -+ final long[] s1Voxels = shapeDataFirst.voxelSet(); -+ final long[] s2Voxels = shapeDataSecond.voxelSet(); -+ -+ final int s1Mul1 = shapeDataFirst.sizeZ(); -+ final int s1Mul2 = s1Mul1 * shapeDataFirst.sizeY(); -+ -+ final int s2Mul1 = shapeDataSecond.sizeZ(); -+ final int s2Mul2 = s2Mul1 * shapeDataSecond.sizeY(); -+ -+ // note: indices may contain -1, but nothing > size -+ final BitSetDiscreteVoxelShape ret = new BitSetDiscreteVoxelShape(sizeX, sizeY, sizeZ); -+ -+ boolean empty = true; -+ -+ int mergedIdx = 0; -+ for (int idxX = 0; idxX < sizeX; ++idxX) { -+ final int s1x = mergedX.firstIndices[idxX]; -+ final int s2x = mergedX.secondIndices[idxX]; -+ boolean setX = false; -+ for (int idxY = 0; idxY < sizeY; ++idxY) { -+ final int s1y = mergedY.firstIndices[idxY]; -+ final int s2y = mergedY.secondIndices[idxY]; -+ boolean setY = false; -+ for (int idxZ = 0; idxZ < sizeZ; ++idxZ) { -+ final int s1z = mergedZ.firstIndices[idxZ]; -+ final int s2z = mergedZ.secondIndices[idxZ]; -+ -+ int idx; -+ -+ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L); -+ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L); -+ -+ // idx ff -> 0 -+ // idx ft -> 1 -+ // idx tf -> 2 -+ // idx tt -> 3 -+ -+ final boolean res = (booleanOp & (1 << (isS2Full | (isS1Full << 1)))) != 0; -+ setY |= res; -+ setX |= res; -+ -+ if (res) { -+ empty = false; -+ // inline and optimize fill operation -+ ret.zMin = Math.min(ret.zMin, idxZ); -+ ret.zMax = Math.max(ret.zMax, idxZ + 1); -+ ret.storage.set(mergedIdx); -+ } -+ -+ ++mergedIdx; -+ } -+ if (setY) { -+ ret.yMin = Math.min(ret.yMin, idxY); -+ ret.yMax = Math.max(ret.yMax, idxY + 1); -+ } -+ } -+ if (setX) { -+ ret.xMin = Math.min(ret.xMin, idxX); -+ ret.xMax = Math.max(ret.xMax, idxX + 1); -+ } -+ } -+ -+ return empty ? null : ret; -+ } -+ -+ private static boolean isMergeEmpty(final CachedShapeData shapeDataFirst, final CachedShapeData shapeDataSecond, -+ final MergedVoxelCoordinateList mergedX, final MergedVoxelCoordinateList mergedY, -+ final MergedVoxelCoordinateList mergedZ, -+ final int booleanOp) { -+ final int sizeX = mergedX.voxels; -+ final int sizeY = mergedY.voxels; -+ final int sizeZ = mergedZ.voxels; -+ -+ final long[] s1Voxels = shapeDataFirst.voxelSet(); -+ final long[] s2Voxels = shapeDataSecond.voxelSet(); -+ -+ final int s1Mul1 = shapeDataFirst.sizeZ(); -+ final int s1Mul2 = s1Mul1 * shapeDataFirst.sizeY(); -+ -+ final int s2Mul1 = shapeDataSecond.sizeZ(); -+ final int s2Mul2 = s2Mul1 * shapeDataSecond.sizeY(); -+ -+ // note: indices may contain -1, but nothing > size -+ for (int idxX = 0; idxX < sizeX; ++idxX) { -+ final int s1x = mergedX.firstIndices[idxX]; -+ final int s2x = mergedX.secondIndices[idxX]; -+ for (int idxY = 0; idxY < sizeY; ++idxY) { -+ final int s1y = mergedY.firstIndices[idxY]; -+ final int s2y = mergedY.secondIndices[idxY]; -+ for (int idxZ = 0; idxZ < sizeZ; ++idxZ) { -+ final int s1z = mergedZ.firstIndices[idxZ]; -+ final int s2z = mergedZ.secondIndices[idxZ]; -+ -+ int idx; -+ -+ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L); -+ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L); -+ -+ // idx ff -> 0 -+ // idx ft -> 1 -+ // idx tf -> 2 -+ // idx tt -> 3 -+ -+ final boolean res = (booleanOp & (1 << (isS2Full | (isS1Full << 1)))) != 0; -+ -+ if (res) { -+ return false; -+ } -+ } -+ } -+ } -+ -+ return true; -+ } -+ -+ public static VoxelShape joinOptimized(final VoxelShape first, final VoxelShape second, final BooleanOp operator) { -+ return joinUnoptimized(first, second, operator).optimize(); -+ } -+ -+ public static VoxelShape joinUnoptimized(final VoxelShape first, final VoxelShape second, final BooleanOp operator) { -+ final boolean ff = operator.apply(false, false); -+ if (ff) { -+ // technically, should be an infinite box but that's clearly an error -+ throw new UnsupportedOperationException("Ambiguous operator: (false, false) -> true"); -+ } -+ -+ final boolean tt = operator.apply(true, true); -+ -+ if (first == second) { -+ return tt ? first : Shapes.empty(); -+ } -+ -+ final boolean ft = operator.apply(false, true); -+ final boolean tf = operator.apply(true, false); -+ -+ if (first.isEmpty()) { -+ return ft ? second : Shapes.empty(); -+ } -+ if (second.isEmpty()) { -+ return tf ? first : Shapes.empty(); -+ } -+ -+ if (!tt) { -+ // try to check for no intersection, since tt = false -+ final AABB aabbF = first.getSingleAABBRepresentation(); -+ final AABB aabbS = second.getSingleAABBRepresentation(); -+ -+ final boolean intersect; -+ -+ final boolean hasAABBF = aabbF != null; -+ final boolean hasAABBS = aabbS != null; -+ if (hasAABBF | hasAABBS) { -+ if (hasAABBF & hasAABBS) { -+ intersect = voxelShapeIntersect(aabbF, aabbS); -+ } else if (hasAABBF) { -+ intersect = voxelShapeIntersectNoEmpty(second, aabbF); -+ } else { -+ intersect = voxelShapeIntersectNoEmpty(first, aabbS); -+ } -+ } else { -+ // expect cached bounds -+ intersect = voxelShapeIntersect(first.bounds(), second.bounds()); -+ } -+ -+ if (!intersect) { -+ if (!tf & !ft) { -+ return Shapes.empty(); -+ } -+ if (!tf | !ft) { -+ return tf ? first : second; -+ } -+ } -+ } -+ -+ final MergedVoxelCoordinateList mergedX = MergedVoxelCoordinateList.merge( -+ first.rootCoordinatesX(), first.offsetX(), -+ second.rootCoordinatesX(), second.offsetX(), -+ ft, tf -+ ); -+ if (mergedX == MergedVoxelCoordinateList.EMPTY) { -+ return Shapes.empty(); -+ } -+ final MergedVoxelCoordinateList mergedY = MergedVoxelCoordinateList.merge( -+ first.rootCoordinatesY(), first.offsetY(), -+ second.rootCoordinatesY(), second.offsetY(), -+ ft, tf -+ ); -+ if (mergedY == MergedVoxelCoordinateList.EMPTY) { -+ return Shapes.empty(); -+ } -+ final MergedVoxelCoordinateList mergedZ = MergedVoxelCoordinateList.merge( -+ first.rootCoordinatesZ(), first.offsetZ(), -+ second.rootCoordinatesZ(), second.offsetZ(), -+ ft, tf -+ ); -+ if (mergedZ == MergedVoxelCoordinateList.EMPTY) { -+ return Shapes.empty(); -+ } -+ -+ final CachedShapeData shapeDataFirst = first.getCachedVoxelData(); -+ final CachedShapeData shapeDataSecond = second.getCachedVoxelData(); -+ -+ final BitSetDiscreteVoxelShape mergedShape = merge( -+ shapeDataFirst, shapeDataSecond, -+ mergedX, mergedY, mergedZ, -+ makeBitset(ft, tf, tt) -+ ); -+ -+ if (mergedShape == null) { -+ return Shapes.empty(); -+ } -+ -+ return new ArrayVoxelShape( -+ mergedShape, mergedX.wrapCoords(), mergedY.wrapCoords(), mergedZ.wrapCoords() -+ ); -+ } -+ -+ public static boolean isJoinNonEmpty(final VoxelShape first, final VoxelShape second, final BooleanOp operator) { -+ final boolean ff = operator.apply(false, false); -+ if (ff) { -+ // technically, should be an infinite box but that's clearly an error -+ throw new UnsupportedOperationException("Ambiguous operator: (false, false) -> true"); -+ } -+ final boolean firstEmpty = first.isEmpty(); -+ final boolean secondEmpty = second.isEmpty(); -+ if (firstEmpty | secondEmpty) { -+ return operator.apply(!firstEmpty, !secondEmpty); -+ } -+ -+ final boolean tt = operator.apply(true, true); -+ -+ if (first == second) { -+ return tt; -+ } -+ -+ final boolean ft = operator.apply(false, true); -+ final boolean tf = operator.apply(true, false); -+ -+ // try to check intersection -+ final AABB aabbF = first.getSingleAABBRepresentation(); -+ final AABB aabbS = second.getSingleAABBRepresentation(); -+ -+ final boolean intersect; -+ -+ final boolean hasAABBF = aabbF != null; -+ final boolean hasAABBS = aabbS != null; -+ if (hasAABBF | hasAABBS) { -+ if (hasAABBF & hasAABBS) { -+ intersect = voxelShapeIntersect(aabbF, aabbS); -+ } else if (hasAABBF) { -+ intersect = voxelShapeIntersectNoEmpty(second, aabbF); -+ } else { -+ // hasAABBS -> true -+ intersect = voxelShapeIntersectNoEmpty(first, aabbS); -+ } -+ -+ if (!intersect) { -+ // is only non-empty if we take from first or second, as there is no overlap AND both shapes are non-empty -+ return tf | ft; -+ } else if (tt) { -+ // intersect = true && tt = true -> non-empty merged shape -+ return true; -+ } -+ } else { -+ // expect cached bounds -+ intersect = voxelShapeIntersect(first.bounds(), second.bounds()); -+ if (!intersect) { -+ // is only non-empty if we take from first or second, as there is no intersection -+ return tf | ft; -+ } -+ } -+ -+ final MergedVoxelCoordinateList mergedX = MergedVoxelCoordinateList.merge( -+ first.rootCoordinatesX(), first.offsetX(), -+ second.rootCoordinatesX(), second.offsetX(), -+ ft, tf -+ ); -+ if (mergedX == MergedVoxelCoordinateList.EMPTY) { -+ return false; -+ } -+ final MergedVoxelCoordinateList mergedY = MergedVoxelCoordinateList.merge( -+ first.rootCoordinatesY(), first.offsetY(), -+ second.rootCoordinatesY(), second.offsetY(), -+ ft, tf -+ ); -+ if (mergedY == MergedVoxelCoordinateList.EMPTY) { -+ return false; -+ } -+ final MergedVoxelCoordinateList mergedZ = MergedVoxelCoordinateList.merge( -+ first.rootCoordinatesZ(), first.offsetZ(), -+ second.rootCoordinatesZ(), second.offsetZ(), -+ ft, tf -+ ); -+ if (mergedZ == MergedVoxelCoordinateList.EMPTY) { -+ return false; -+ } -+ -+ final CachedShapeData shapeDataFirst = first.getCachedVoxelData(); -+ final CachedShapeData shapeDataSecond = second.getCachedVoxelData(); -+ -+ return !isMergeEmpty( -+ shapeDataFirst, shapeDataSecond, -+ mergedX, mergedY, mergedZ, -+ makeBitset(ft, tf, tt) -+ ); -+ } -+ -+ private static final class MergedVoxelCoordinateList { -+ -+ private static final int[][] SIMPLE_INDICES_CACHE = new int[64][]; -+ static { -+ for (int i = 0; i < SIMPLE_INDICES_CACHE.length; ++i) { -+ SIMPLE_INDICES_CACHE[i] = getIndices(i); -+ } -+ } -+ -+ private static final MergedVoxelCoordinateList EMPTY = new MergedVoxelCoordinateList( -+ new double[] { 0.0 }, 0.0, new int[0], new int[0], 0 -+ ); -+ -+ private static int[] getIndices(final int length) { -+ final int[] ret = new int[length]; -+ -+ for (int i = 1; i < length; ++i) { -+ ret[i] = i; -+ } -+ -+ return ret; -+ } -+ -+ // indices above voxel size are always set to -1 -+ public final double[] coordinates; -+ public final double coordinateOffset; -+ public final int[] firstIndices; -+ public final int[] secondIndices; -+ public final int voxels; -+ -+ private MergedVoxelCoordinateList(final double[] coordinates, final double coordinateOffset, -+ final int[] firstIndices, final int[] secondIndices, final int voxels) { -+ this.coordinates = coordinates; -+ this.coordinateOffset = coordinateOffset; -+ this.firstIndices = firstIndices; -+ this.secondIndices = secondIndices; -+ this.voxels = voxels; -+ } -+ -+ public DoubleList wrapCoords() { -+ if (this.coordinateOffset == 0.0) { -+ return DoubleArrayList.wrap(this.coordinates, this.voxels + 1); -+ } -+ return new OffsetDoubleList(DoubleArrayList.wrap(this.coordinates, this.voxels + 1), this.coordinateOffset); -+ } -+ -+ // assume coordinates.length > 1 -+ public static MergedVoxelCoordinateList getForSingle(final double[] coordinates, final double offset) { -+ final int voxels = coordinates.length - 1; -+ final int[] indices = voxels < SIMPLE_INDICES_CACHE.length ? SIMPLE_INDICES_CACHE[voxels] : getIndices(voxels); -+ -+ return new MergedVoxelCoordinateList(coordinates, offset, indices, indices, voxels); -+ } -+ -+ // assume coordinates.length > 1 -+ public static MergedVoxelCoordinateList merge(final double[] firstCoordinates, final double firstOffset, -+ final double[] secondCoordinates, final double secondOffset, -+ final boolean ft, final boolean tf) { -+ if (firstCoordinates == secondCoordinates && firstOffset == secondOffset) { -+ return getForSingle(firstCoordinates, firstOffset); -+ } -+ -+ final int firstCount = firstCoordinates.length; -+ final int secondCount = secondCoordinates.length; -+ -+ final int voxelsFirst = firstCount - 1; -+ final int voxelsSecond = secondCount - 1; -+ -+ final int maxCount = firstCount + secondCount; -+ -+ final double[] coordinates = new double[maxCount]; -+ final int[] firstIndices = new int[maxCount]; -+ final int[] secondIndices = new int[maxCount]; -+ -+ final boolean notTF = !tf; -+ final boolean notFT = !ft; -+ -+ int firstIndex = 0; -+ int secondIndex = 0; -+ int resultSize = 0; -+ -+ // note: operations on NaN are false -+ double last = Double.NaN; -+ -+ for (;;) { -+ final boolean noneLeftFirst = firstIndex >= firstCount; -+ final boolean noneLeftSecond = secondIndex >= secondCount; -+ -+ if ((noneLeftFirst & noneLeftSecond) | (noneLeftSecond & notTF) | (noneLeftFirst & notFT)) { -+ break; -+ } -+ -+ final boolean firstZero = firstIndex == 0; -+ final boolean secondZero = secondIndex == 0; -+ -+ final double select; -+ -+ if (noneLeftFirst) { -+ // noneLeftSecond -> false -+ // notFT -> false -+ select = secondCoordinates[secondIndex] + secondOffset; -+ ++secondIndex; -+ } else if (noneLeftSecond) { -+ // noneLeftFirst -> false -+ // notTF -> false -+ select = firstCoordinates[firstIndex] + firstOffset; -+ ++firstIndex; -+ } else { -+ // noneLeftFirst | noneLeftSecond -> false -+ // notTF -> ?? -+ // notFT -> ?? -+ final boolean breakFirst = notTF & secondZero; -+ final boolean breakSecond = notFT & firstZero; -+ -+ final double first = firstCoordinates[firstIndex] + firstOffset; -+ final double second = secondCoordinates[secondIndex] + secondOffset; -+ final boolean useFirst = first < (second + COLLISION_EPSILON); -+ final boolean cont = (useFirst & breakFirst) | (!useFirst & breakSecond); -+ -+ select = useFirst ? first : second; -+ firstIndex += useFirst ? 1 : 0; -+ secondIndex += 1 ^ (useFirst ? 1 : 0); -+ -+ if (cont) { -+ continue; -+ } -+ } -+ -+ int prevFirst = firstIndex - 1; -+ prevFirst = prevFirst >= voxelsFirst ? -1 : prevFirst; -+ int prevSecond = secondIndex - 1; -+ prevSecond = prevSecond >= voxelsSecond ? -1 : prevSecond; -+ -+ if (last >= (select - COLLISION_EPSILON)) { -+ // note: any operations on NaN is false -+ firstIndices[resultSize - 1] = prevFirst; -+ secondIndices[resultSize - 1] = prevSecond; -+ } else { -+ firstIndices[resultSize] = prevFirst; -+ secondIndices[resultSize] = prevSecond; -+ coordinates[resultSize] = select; -+ -+ ++resultSize; -+ last = select; -+ } -+ } -+ -+ return resultSize <= 1 ? EMPTY : new MergedVoxelCoordinateList(coordinates, 0.0, firstIndices, secondIndices, resultSize - 1); -+ } -+ } -+ -+ public static AABB offsetX(final AABB box, final double dx) { -+ return new AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB offsetY(final AABB box, final double dy) { -+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); -+ } -+ -+ public static AABB offsetZ(final AABB box, final double dz) { -+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz, false); -+ } -+ -+ public static AABB expandRight(final AABB box, final double dx) { // dx > 0.0 -+ return new AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB expandLeft(final AABB box, final double dx) { // dx < 0.0 -+ return new AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); -+ } -+ -+ public static AABB expandUpwards(final AABB box, final double dy) { // dy > 0.0 -+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); -+ } -+ -+ public static AABB expandDownwards(final AABB box, final double dy) { // dy < 0.0 -+ return new AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB expandForwards(final AABB box, final double dz) { // dz > 0.0 -+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz, false); -+ } -+ -+ public static AABB expandBackwards(final AABB box, final double dz) { // dz < 0.0 -+ return new AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB cutRight(final AABB box, final double dx) { // dx > 0.0 -+ return new AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB cutLeft(final AABB box, final double dx) { // dx < 0.0 -+ return new AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB cutUpwards(final AABB box, final double dy) { // dy > 0.0 -+ return new AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); -+ } -+ -+ public static AABB cutDownwards(final AABB box, final double dy) { // dy < 0.0 -+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ, false); -+ } -+ -+ public static AABB cutForwards(final AABB box, final double dz) { // dz > 0.0 -+ return new AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz, false); -+ } -+ -+ public static AABB cutBackwards(final AABB box, final double dz) { // dz < 0.0 -+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ, false); -+ } -+ -+ public static double performAABBCollisionsX(final AABB currentBoundingBox, double value, final List potentialCollisions) { -+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { -+ if (Math.abs(value) < COLLISION_EPSILON) { -+ return 0.0; -+ } -+ final AABB target = potentialCollisions.get(i); -+ value = collideX(target, currentBoundingBox, value); -+ } -+ -+ return value; -+ } -+ -+ public static double performAABBCollisionsY(final AABB currentBoundingBox, double value, final List potentialCollisions) { -+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { -+ if (Math.abs(value) < COLLISION_EPSILON) { -+ return 0.0; -+ } -+ final AABB target = potentialCollisions.get(i); -+ value = collideY(target, currentBoundingBox, value); -+ } -+ -+ return value; -+ } -+ -+ public static double performAABBCollisionsZ(final AABB currentBoundingBox, double value, final List potentialCollisions) { -+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { -+ if (Math.abs(value) < COLLISION_EPSILON) { -+ return 0.0; -+ } -+ final AABB target = potentialCollisions.get(i); -+ value = collideZ(target, currentBoundingBox, value); -+ } -+ -+ return value; -+ } -+ -+ public static double performVoxelCollisionsX(final AABB currentBoundingBox, double value, final List potentialCollisions) { -+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { -+ if (Math.abs(value) < COLLISION_EPSILON) { -+ return 0.0; -+ } -+ final VoxelShape target = potentialCollisions.get(i); -+ value = collideX(target, currentBoundingBox, value); -+ } -+ -+ return value; -+ } -+ -+ public static double performVoxelCollisionsY(final AABB currentBoundingBox, double value, final List potentialCollisions) { -+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { -+ if (Math.abs(value) < COLLISION_EPSILON) { -+ return 0.0; -+ } -+ final VoxelShape target = potentialCollisions.get(i); -+ value = collideY(target, currentBoundingBox, value); -+ } -+ -+ return value; -+ } -+ -+ public static double performVoxelCollisionsZ(final AABB currentBoundingBox, double value, final List potentialCollisions) { -+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { -+ if (Math.abs(value) < COLLISION_EPSILON) { -+ return 0.0; -+ } -+ final VoxelShape target = potentialCollisions.get(i); -+ value = collideZ(target, currentBoundingBox, value); -+ } -+ -+ return value; -+ } -+ -+ public static Vec3 performVoxelCollisions(final Vec3 moveVector, AABB axisalignedbb, final List potentialCollisions) { -+ double x = moveVector.x; -+ double y = moveVector.y; -+ double z = moveVector.z; -+ -+ if (y != 0.0) { -+ y = performVoxelCollisionsY(axisalignedbb, y, potentialCollisions); -+ if (y != 0.0) { -+ axisalignedbb = offsetY(axisalignedbb, y); -+ } -+ } -+ -+ final boolean xSmaller = Math.abs(x) < Math.abs(z); -+ -+ if (xSmaller && z != 0.0) { -+ z = performVoxelCollisionsZ(axisalignedbb, z, potentialCollisions); -+ if (z != 0.0) { -+ axisalignedbb = offsetZ(axisalignedbb, z); -+ } -+ } -+ -+ if (x != 0.0) { -+ x = performVoxelCollisionsX(axisalignedbb, x, potentialCollisions); -+ if (!xSmaller && x != 0.0) { -+ axisalignedbb = offsetX(axisalignedbb, x); -+ } -+ } -+ -+ if (!xSmaller && z != 0.0) { -+ z = performVoxelCollisionsZ(axisalignedbb, z, potentialCollisions); -+ } -+ -+ return new Vec3(x, y, z); -+ } -+ -+ public static Vec3 performAABBCollisions(final Vec3 moveVector, AABB axisalignedbb, final List potentialCollisions) { -+ double x = moveVector.x; -+ double y = moveVector.y; -+ double z = moveVector.z; -+ -+ if (y != 0.0) { -+ y = performAABBCollisionsY(axisalignedbb, y, potentialCollisions); -+ if (y != 0.0) { -+ axisalignedbb = offsetY(axisalignedbb, y); -+ } -+ } -+ -+ final boolean xSmaller = Math.abs(x) < Math.abs(z); -+ -+ if (xSmaller && z != 0.0) { -+ z = performAABBCollisionsZ(axisalignedbb, z, potentialCollisions); -+ if (z != 0.0) { -+ axisalignedbb = offsetZ(axisalignedbb, z); -+ } -+ } -+ -+ if (x != 0.0) { -+ x = performAABBCollisionsX(axisalignedbb, x, potentialCollisions); -+ if (!xSmaller && x != 0.0) { -+ axisalignedbb = offsetX(axisalignedbb, x); -+ } -+ } -+ -+ if (!xSmaller && z != 0.0) { -+ z = performAABBCollisionsZ(axisalignedbb, z, potentialCollisions); -+ } -+ -+ return new Vec3(x, y, z); -+ } -+ -+ public static Vec3 performCollisions(final Vec3 moveVector, AABB axisalignedbb, -+ final List voxels, -+ final List aabbs) { -+ if (voxels.isEmpty()) { -+ // fast track only AABBs -+ return performAABBCollisions(moveVector, axisalignedbb, aabbs); -+ } -+ -+ double x = moveVector.x; -+ double y = moveVector.y; -+ double z = moveVector.z; -+ -+ if (y != 0.0) { -+ y = performAABBCollisionsY(axisalignedbb, y, aabbs); -+ y = performVoxelCollisionsY(axisalignedbb, y, voxels); -+ if (y != 0.0) { -+ axisalignedbb = offsetY(axisalignedbb, y); -+ } -+ } -+ -+ final boolean xSmaller = Math.abs(x) < Math.abs(z); -+ -+ if (xSmaller && z != 0.0) { -+ z = performAABBCollisionsZ(axisalignedbb, z, aabbs); -+ z = performVoxelCollisionsZ(axisalignedbb, z, voxels); -+ if (z != 0.0) { -+ axisalignedbb = offsetZ(axisalignedbb, z); -+ } -+ } -+ -+ if (x != 0.0) { -+ x = performAABBCollisionsX(axisalignedbb, x, aabbs); -+ x = performVoxelCollisionsX(axisalignedbb, x, voxels); -+ if (!xSmaller && x != 0.0) { -+ axisalignedbb = offsetX(axisalignedbb, x); -+ } -+ } -+ -+ if (!xSmaller && z != 0.0) { -+ z = performAABBCollisionsZ(axisalignedbb, z, aabbs); -+ z = performVoxelCollisionsZ(axisalignedbb, z, voxels); -+ } -+ -+ return new Vec3(x, y, z); -+ } -+ -+ public static boolean isCollidingWithBorder(final WorldBorder worldborder, final AABB boundingBox) { -+ return isCollidingWithBorder(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); -+ } -+ -+ public static boolean isCollidingWithBorder(final WorldBorder worldborder, final double boxMinX, final double boxMaxX, -+ final double boxMinZ, final double boxMaxZ) { -+ // border size is rounded like the collide voxel shape of the border -+ final double borderMinX = Math.floor(worldborder.getMinX()); // -X -+ final double borderMaxX = Math.ceil(worldborder.getMaxX()); // +X -+ -+ final double borderMinZ = Math.floor(worldborder.getMinZ()); // -Z -+ final double borderMaxZ = Math.ceil(worldborder.getMaxZ()); // +Z -+ -+ // inverted check for world border enclosing the specified box expanded by -EPSILON -+ return (borderMinX - boxMinX) > CollisionUtil.COLLISION_EPSILON || (borderMaxX - boxMaxX) < -CollisionUtil.COLLISION_EPSILON || -+ (borderMinZ - boxMinZ) > CollisionUtil.COLLISION_EPSILON || (borderMaxZ - boxMaxZ) < -CollisionUtil.COLLISION_EPSILON; -+ } -+ -+ /* Math.max/min specify that any NaN argument results in a NaN return, unlike these functions */ -+ private static double min(final double x, final double y) { -+ return x < y ? x : y; -+ } -+ -+ private static double max(final double x, final double y) { -+ return x > y ? x : y; -+ } -+ -+ public static final int COLLISION_FLAG_LOAD_CHUNKS = 1 << 0; -+ public static final int COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS = 1 << 1; -+ public static final int COLLISION_FLAG_CHECK_BORDER = 1 << 2; -+ public static final int COLLISION_FLAG_CHECK_ONLY = 1 << 3; -+ -+ public static boolean getCollisionsForBlocksOrWorldBorder(final Level world, final Entity entity, final AABB aabb, -+ final List intoVoxel, final List intoAABB, -+ final int collisionFlags, final BiPredicate predicate) { -+ final boolean checkOnly = (collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0; -+ boolean ret = false; -+ -+ if ((collisionFlags & COLLISION_FLAG_CHECK_BORDER) != 0) { -+ final WorldBorder worldBorder = world.getWorldBorder(); -+ if (CollisionUtil.isCollidingWithBorder(worldBorder, aabb) && entity != null && worldBorder.isInsideCloseToBorder(entity, aabb)) { -+ if (checkOnly) { -+ return true; -+ } else { -+ final VoxelShape borderShape = worldBorder.getCollisionShape(); -+ intoVoxel.add(borderShape); -+ ret = true; -+ } -+ } -+ } -+ -+ final int minSection = world.minSection; -+ -+ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; -+ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; -+ -+ final int minBlockY = Math.max((minSection << 4) - 1, Mth.floor(aabb.minY - COLLISION_EPSILON) - 1); -+ final int maxBlockY = Math.min((world.maxSection << 4) + 16, Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1); -+ -+ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; -+ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1; -+ -+ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); -+ final CollisionContext collisionShape = new LazyEntityCollisionContext(entity); -+ -+ // special cases: -+ if (minBlockY > maxBlockY) { -+ // no point in checking -+ return ret; -+ } -+ -+ final int minChunkX = minBlockX >> 4; -+ final int maxChunkX = maxBlockX >> 4; -+ -+ final int minChunkY = minBlockY >> 4; -+ final int maxChunkY = maxBlockY >> 4; -+ -+ final int minChunkZ = minBlockZ >> 4; -+ final int maxChunkZ = maxBlockZ >> 4; -+ -+ final boolean loadChunks = (collisionFlags & COLLISION_FLAG_LOAD_CHUNKS) != 0; -+ final ServerChunkCache chunkSource = (ServerChunkCache)world.getChunkSource(); -+ -+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { -+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { -+ final ChunkAccess chunk = loadChunks ? chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, true) : chunkSource.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); -+ -+ if (chunk == null) { -+ if ((collisionFlags & COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS) != 0) { -+ if (checkOnly) { -+ return true; -+ } else { -+ intoAABB.add(getBoxForChunk(currChunkX, currChunkZ)); -+ ret = true; -+ } -+ } -+ continue; -+ } -+ -+ final LevelChunkSection[] sections = chunk.getSections(); -+ -+ // bound y -+ for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) { -+ final int sectionIdx = currChunkY - minSection; -+ if (sectionIdx < 0 || sectionIdx >= sections.length) { -+ continue; -+ } -+ final LevelChunkSection section = sections[sectionIdx]; -+ if (section == null || section.hasOnlyAir()) { -+ // empty -+ continue; -+ } -+ -+ final boolean hasSpecial = section.getSpecialCollidingBlocks() != 0; -+ final int sectionAdjust = !hasSpecial ? 1 : 0; -+ -+ final PalettedContainer blocks = section.states; -+ -+ final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) + sectionAdjust : 0; -+ final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) - sectionAdjust : 15; -+ final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) + sectionAdjust : 0; -+ final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) - sectionAdjust : 15; -+ final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) + sectionAdjust : 0; -+ final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) - sectionAdjust : 15; -+ -+ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { -+ final int blockY = currY | (currChunkY << 4); -+ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) { -+ final int blockZ = currZ | (currChunkZ << 4); -+ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { -+ final int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8); -+ final int blockX = currX | (currChunkX << 4); -+ -+ final int edgeCount = hasSpecial ? ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + -+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + -+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0) : 0; -+ if (edgeCount == 3) { -+ continue; -+ } -+ -+ final BlockState blockData = blocks.get(localBlockIndex); -+ -+ if (blockData.emptyCollisionShape()) { -+ continue; -+ } -+ -+ if (edgeCount == 0 || ((edgeCount != 1 || blockData.hasLargeCollisionShape()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON))) { -+ VoxelShape blockCollision = blockData.getConstantCollisionShape(); -+ -+ if (blockCollision == null) { -+ mutablePos.set(blockX, blockY, blockZ); -+ blockCollision = blockData.getCollisionShape(world, mutablePos, collisionShape); -+ } -+ -+ AABB singleAABB = blockCollision.getSingleAABBRepresentation(); -+ if (singleAABB != null) { -+ singleAABB = singleAABB.move((double)blockX, (double)blockY, (double)blockZ); -+ if (!voxelShapeIntersect(aabb, singleAABB)) { -+ continue; -+ } -+ -+ if (predicate != null) { -+ mutablePos.set(blockX, blockY, blockZ); -+ if (!predicate.test(blockData, mutablePos)) { -+ continue; -+ } -+ } -+ -+ if (checkOnly) { -+ return true; -+ } else { -+ ret = true; -+ intoAABB.add(singleAABB); -+ continue; -+ } -+ } -+ -+ if (blockCollision.isEmpty()) { -+ continue; -+ } -+ -+ final VoxelShape blockCollisionOffset = blockCollision.move((double)blockX, (double)blockY, (double)blockZ); -+ -+ if (!voxelShapeIntersectNoEmpty(blockCollisionOffset, aabb)) { -+ continue; -+ } -+ -+ if (predicate != null) { -+ mutablePos.set(blockX, blockY, blockZ); -+ if (!predicate.test(blockData, mutablePos)) { -+ continue; -+ } -+ } -+ -+ if (checkOnly) { -+ return true; -+ } else { -+ ret = true; -+ intoVoxel.add(blockCollisionOffset); -+ continue; -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public static boolean getEntityHardCollisions(final CollisionGetter getter, final Entity entity, AABB aabb, -+ final List into, final int collisionFlags, final Predicate predicate) { -+ final boolean checkOnly = (collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0; -+ if (!(getter instanceof EntityGetter entityGetter)) { -+ return false; -+ } -+ -+ boolean ret = false; -+ -+ // to comply with vanilla intersection rules, expand by -epsilon so that we only get stuff we definitely collide with. -+ // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems -+ // specifically with boat collisions. -+ aabb = aabb.inflate(-COLLISION_EPSILON, -COLLISION_EPSILON, -COLLISION_EPSILON); -+ final List entities; -+ if (entity != null && entity.hardCollides()) { -+ entities = entityGetter.getEntities(entity, aabb, predicate); -+ } else { -+ entities = entityGetter.getHardCollidingEntities(entity, aabb, predicate); -+ } -+ -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ final Entity otherEntity = entities.get(i); -+ -+ if (otherEntity.isSpectator()) { -+ continue; -+ } -+ -+ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) { -+ if (checkOnly) { -+ return true; -+ } else { -+ into.add(otherEntity.getBoundingBox()); -+ ret = true; -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public static boolean getCollisions(final Level world, final Entity entity, final AABB aabb, -+ final List intoVoxel, final List intoAABB, final int collisionFlags, -+ final BiPredicate blockPredicate, -+ final Predicate entityPredicate) { -+ if ((collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0) { -+ return getCollisionsForBlocksOrWorldBorder(world, entity, aabb, intoVoxel, intoAABB, collisionFlags, blockPredicate) -+ || getEntityHardCollisions(world, entity, aabb, intoAABB, collisionFlags, entityPredicate); -+ } else { -+ return getCollisionsForBlocksOrWorldBorder(world, entity, aabb, intoVoxel, intoAABB, collisionFlags, blockPredicate) -+ | getEntityHardCollisions(world, entity, aabb, intoAABB, collisionFlags, entityPredicate); -+ } -+ } -+ -+ public static final class LazyEntityCollisionContext extends EntityCollisionContext { -+ -+ private CollisionContext delegate; -+ private boolean delegated; -+ -+ public LazyEntityCollisionContext(final Entity entity) { -+ super(false, 0.0, null, null, entity); -+ } -+ -+ public boolean isDelegated() { -+ final boolean delegated = this.delegated; -+ this.delegated = false; -+ return delegated; -+ } -+ -+ public CollisionContext getDelegate() { -+ this.delegated = true; -+ final Entity entity = this.getEntity(); -+ return this.delegate == null ? this.delegate = (entity == null ? CollisionContext.empty() : CollisionContext.of(entity)) : this.delegate; -+ } -+ -+ @Override -+ public boolean isDescending() { -+ return this.getDelegate().isDescending(); -+ } -+ -+ @Override -+ public boolean isAbove(final VoxelShape shape, final BlockPos pos, final boolean defaultValue) { -+ return this.getDelegate().isAbove(shape, pos, defaultValue); -+ } -+ -+ @Override -+ public boolean isHoldingItem(final Item item) { -+ return this.getDelegate().isHoldingItem(item); -+ } -+ -+ @Override -+ public boolean canStandOnFluid(final FluidState state, final FluidState fluidState) { -+ return this.getDelegate().canStandOnFluid(state, fluidState); -+ } -+ } -+ -+ private CollisionUtil() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/util/collisions/CachedShapeData.java b/src/main/java/io/papermc/paper/util/collisions/CachedShapeData.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1cb96b09375770f92f3e494ce2f28d9ff8699581 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/collisions/CachedShapeData.java -@@ -0,0 +1,10 @@ -+package io.papermc.paper.util.collisions; -+ -+public record CachedShapeData( -+ int sizeX, int sizeY, int sizeZ, -+ long[] voxelSet, -+ int minFullX, int minFullY, int minFullZ, -+ int maxFullX, int maxFullY, int maxFullZ, -+ boolean isEmpty, boolean hasSingleAABB -+) { -+} -diff --git a/src/main/java/io/papermc/paper/util/collisions/CachedToAABBs.java b/src/main/java/io/papermc/paper/util/collisions/CachedToAABBs.java -new file mode 100644 -index 0000000000000000000000000000000000000000..85c448a775f60ca4b4a4f2baf17487ef45bdd383 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/collisions/CachedToAABBs.java -@@ -0,0 +1,39 @@ -+package io.papermc.paper.util.collisions; -+ -+import net.minecraft.world.phys.AABB; -+import java.util.ArrayList; -+import java.util.List; -+ -+public record CachedToAABBs( -+ List aabbs, -+ boolean isOffset, -+ double offX, double offY, double offZ -+) { -+ -+ public CachedToAABBs removeOffset() { -+ final List toOffset = this.aabbs; -+ final double offX = this.offX; -+ final double offY = this.offY; -+ final double offZ = this.offZ; -+ -+ final List ret = new ArrayList<>(toOffset.size()); -+ -+ for (int i = 0, len = toOffset.size(); i < len; ++i) { -+ ret.add(toOffset.get(i).move(offX, offY, offZ)); -+ } -+ -+ return new CachedToAABBs(ret, false, 0.0, 0.0, 0.0); -+ } -+ -+ public static CachedToAABBs offset(final CachedToAABBs cache, final double offX, final double offY, final double offZ) { -+ if (offX == 0.0 && offY == 0.0 && offZ == 0.0) { -+ return cache; -+ } -+ -+ final double resX = cache.offX + offX; -+ final double resY = cache.offY + offY; -+ final double resZ = cache.offZ + offZ; -+ -+ return new CachedToAABBs(cache.aabbs, true, resX, resY, resZ); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/util/collisions/FlatBitsetUtil.java b/src/main/java/io/papermc/paper/util/collisions/FlatBitsetUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ff9d2dad39dcc02b2371458b7b5f64c6090e8012 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/collisions/FlatBitsetUtil.java -@@ -0,0 +1,109 @@ -+package io.papermc.paper.util.collisions; -+ -+import java.util.Objects; -+ -+public final class FlatBitsetUtil { -+ -+ private static final int LOG2_LONG = 6; -+ private static final long ALL_SET = -1L; -+ private static final int BITS_PER_LONG = Long.SIZE; -+ -+ // from inclusive -+ // to exclusive -+ public static int firstSet(final long[] bitset, final int from, final int to) { -+ if ((from | to | (to - from)) < 0) { -+ throw new IndexOutOfBoundsException(); -+ } -+ -+ int bitsetIdx = from >>> LOG2_LONG; -+ int bitIdx = from & ~(BITS_PER_LONG - 1); -+ -+ long tmp = bitset[bitsetIdx] & (ALL_SET << from); -+ for (;;) { -+ if (tmp != 0L) { -+ final int ret = bitIdx | Long.numberOfTrailingZeros(tmp); -+ return ret >= to ? -1 : ret; -+ } -+ -+ bitIdx += BITS_PER_LONG; -+ -+ if (bitIdx >= to) { -+ return -1; -+ } -+ -+ tmp = bitset[++bitsetIdx]; -+ } -+ } -+ -+ // from inclusive -+ // to exclusive -+ public static int firstClear(final long[] bitset, final int from, final int to) { -+ if ((from | to | (to - from)) < 0) { -+ throw new IndexOutOfBoundsException(); -+ } -+ // like firstSet, but invert the bitset -+ -+ int bitsetIdx = from >>> LOG2_LONG; -+ int bitIdx = from & ~(BITS_PER_LONG - 1); -+ -+ long tmp = (~bitset[bitsetIdx]) & (ALL_SET << from); -+ for (;;) { -+ if (tmp != 0L) { -+ final int ret = bitIdx | Long.numberOfTrailingZeros(tmp); -+ return ret >= to ? -1 : ret; -+ } -+ -+ bitIdx += BITS_PER_LONG; -+ -+ if (bitIdx >= to) { -+ return -1; -+ } -+ -+ tmp = ~bitset[++bitsetIdx]; -+ } -+ } -+ -+ // from inclusive -+ // to exclusive -+ public static void clearRange(final long[] bitset, final int from, int to) { -+ if ((from | to | (to - from)) < 0) { -+ throw new IndexOutOfBoundsException(); -+ } -+ -+ if (from == to) { -+ return; -+ } -+ -+ --to; -+ -+ final int fromBitsetIdx = from >>> LOG2_LONG; -+ final int toBitsetIdx = to >>> LOG2_LONG; -+ -+ final long keepFirst = ~(ALL_SET << from); -+ final long keepLast = ~(ALL_SET >>> ((BITS_PER_LONG - 1) ^ to)); -+ -+ Objects.checkFromToIndex(fromBitsetIdx, toBitsetIdx, bitset.length); -+ -+ if (fromBitsetIdx == toBitsetIdx) { -+ // special case: need to keep both first and last -+ bitset[fromBitsetIdx] &= (keepFirst | keepLast); -+ } else { -+ bitset[fromBitsetIdx] &= keepFirst; -+ -+ for (int i = fromBitsetIdx + 1; i < toBitsetIdx; ++i) { -+ bitset[i] = 0L; -+ } -+ -+ bitset[toBitsetIdx] &= keepLast; -+ } -+ } -+ -+ // from inclusive -+ // to exclusive -+ public static boolean isRangeSet(final long[] bitset, final int from, final int to) { -+ return firstClear(bitset, from, to) == -1; -+ } -+ -+ -+ private FlatBitsetUtil() {} -+} -diff --git a/src/main/java/io/papermc/paper/util/collisions/MergedORCache.java b/src/main/java/io/papermc/paper/util/collisions/MergedORCache.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1f42bdfdb052056e62a939ab0d1944f8a064fe6c ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/collisions/MergedORCache.java -@@ -0,0 +1,10 @@ -+package io.papermc.paper.util.collisions; -+ -+import net.minecraft.world.phys.shapes.VoxelShape; -+ -+public record MergedORCache( -+ VoxelShape key, -+ VoxelShape result -+) { -+ -+} -diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java -index 073c717bb676b9e99aada00c349fb7eee91df1e7..2a9fc1f1dfc0c5894c1e74dad5a79ae9b02ac74f 100644 ---- a/src/main/java/net/minecraft/core/Direction.java -+++ b/src/main/java/net/minecraft/core/Direction.java -@@ -57,6 +57,21 @@ public enum Direction implements StringRepresentable { - private final int adjY; - private final int adjZ; - // Paper end - Perf: Inline shift direction fields -+ // Paper start - optimise collisions -+ private static final int RANDOM_OFFSET = 2017601568; -+ private Direction opposite; -+ static { -+ for (final Direction direction : VALUES) { -+ direction.opposite = from3DDataValue(direction.oppositeIndex); -+ } -+ } -+ -+ private final int id = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(this.ordinal() + RANDOM_OFFSET) + RANDOM_OFFSET); -+ -+ public final int uniqueId() { -+ return this.id; -+ } -+ // Paper end - optimise collisions - - private Direction(int id, int idOpposite, int idHorizontal, String name, Direction.AxisDirection direction, Direction.Axis axis, Vec3i vector) { - this.data3d = id; -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 803ed79940af00b49aec4b1414ddfacb57ff4f3f..569dbd5a1479b41b2604aacd351bf6d33054de29 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -496,7 +496,7 @@ public class ServerPlayer extends Player { - - if (blockposition1 != null) { - this.moveTo(blockposition1, world.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored -- if (world.noCollision((Entity) this)) { -+ if (world.noCollision(this, this.getBoundingBox(), true)) { // Paper - make sure this loads chunks, we default to NOT loading now - break; - } - } -@@ -504,7 +504,7 @@ public class ServerPlayer extends Player { - } else { - this.moveTo(blockposition, world.getSharedSpawnAngle(), 0.0F); // Paper - MC-200092 - fix first spawn pos yaw being ignored - -- while (!world.noCollision((Entity) this) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { -+ while (!world.noCollision(this, this.getBoundingBox(), true) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { // Paper - make sure this loads chunks, we default to NOT loading now - this.setPos(this.getX(), this.getY() + 1.0D, this.getZ()); - } - } -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index cb81990877f918a5305478b5e8ac7dde6a78f238..4816897a82c569717bf7ea139a55ab3fd931a63e 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -935,7 +935,7 @@ public abstract class PlayerList { - entityplayer1.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); - - worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper -- while (avoidSuffocation && !worldserver1.noCollision((Entity) entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { -+ while (avoidSuffocation && !worldserver1.noCollision(entityplayer1, entityplayer1.getBoundingBox(), true) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { // Paper - make sure this loads chunks, we default to NOT loading now - // CraftBukkit end - entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); - } -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index bea396af243ad6c8649832587d9c443afe84bb92..9ee1e3da1cb16291ff3e37829e25227a6b97a177 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1238,9 +1238,44 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - float f = this.getBlockSpeedFactor(); - - this.setDeltaMovement(this.getDeltaMovement().multiply((double) f, 1.0D, (double) f)); -- if (this.level().getBlockStatesIfLoaded(this.getBoundingBox().deflate(1.0E-6D)).noneMatch((iblockdata2) -> { -- return iblockdata2.is(BlockTags.FIRE) || iblockdata2.is(Blocks.LAVA); -- })) { -+ // Paper start - remove expensive streams from here -+ boolean noneMatch = true; -+ AABB fireSearchBox = this.getBoundingBox().deflate(1.0E-6D); -+ { -+ int minX = Mth.floor(fireSearchBox.minX); -+ int minY = Mth.floor(fireSearchBox.minY); -+ int minZ = Mth.floor(fireSearchBox.minZ); -+ int maxX = Mth.floor(fireSearchBox.maxX); -+ int maxY = Mth.floor(fireSearchBox.maxY); -+ int maxZ = Mth.floor(fireSearchBox.maxZ); -+ fire_search_loop: -+ for (int fz = minZ; fz <= maxZ; ++fz) { -+ for (int fx = minX; fx <= maxX; ++fx) { -+ for (int fy = minY; fy <= maxY; ++fy) { -+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)this.level.getChunkIfLoadedImmediately(fx >> 4, fz >> 4); -+ if (chunk == null) { -+ // Vanilla rets an empty stream if all the chunks are not loaded, so noneMatch will be true -+ // even if we're in lava/fire -+ noneMatch = true; -+ break fire_search_loop; -+ } -+ if (!noneMatch) { -+ // don't do get type, we already know we're in fire - we just need to check the chunks -+ // loaded state -+ continue; -+ } -+ -+ BlockState type = chunk.getBlockStateFinal(fx, fy, fz); -+ if (type.is(BlockTags.FIRE) || type.is(Blocks.LAVA)) { -+ noneMatch = false; -+ // can't break, we need to retain vanilla behavior by ensuring ALL chunks are loaded -+ } -+ } -+ } -+ } -+ } -+ if (noneMatch) { -+ // Paper end - remove expensive streams from here - if (this.remainingFireTicks <= 0) { - this.setRemainingFireTicks(-this.getFireImmuneTicks()); - } -@@ -1420,32 +1455,82 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - } - - private Vec3 collide(Vec3 movement) { -- AABB axisalignedbb = this.getBoundingBox(); -- List list = this.level().getEntityCollisions(this, axisalignedbb.expandTowards(movement)); -- Vec3 vec3d1 = movement.lengthSqr() == 0.0D ? movement : Entity.collideBoundingBox(this, movement, axisalignedbb, this.level(), list); -- boolean flag = movement.x != vec3d1.x; -- boolean flag1 = movement.y != vec3d1.y; -- boolean flag2 = movement.z != vec3d1.z; -- boolean flag3 = this.onGround() || flag1 && movement.y < 0.0D; -+ // Paper start - optimise collisions -+ final boolean xZero = movement.x == 0.0; -+ final boolean yZero = movement.y == 0.0; -+ final boolean zZero = movement.z == 0.0; -+ if (xZero & yZero & zZero) { -+ return movement; -+ } -+ -+ final Level world = this.level; -+ final AABB currBoundingBox = this.getBoundingBox(); -+ -+ if (io.papermc.paper.util.CollisionUtil.isEmpty(currBoundingBox)) { -+ return movement; -+ } -+ -+ final List potentialCollisionsBB = new java.util.ArrayList<>(); -+ final List potentialCollisionsVoxel = new java.util.ArrayList<>(); -+ final double stepHeight = (double)this.maxUpStep(); -+ final AABB collisionBox; -+ final boolean onGround = this.onGround; -+ -+ if (xZero & zZero) { -+ if (movement.y > 0.0) { -+ collisionBox = io.papermc.paper.util.CollisionUtil.cutUpwards(currBoundingBox, movement.y); -+ } else { -+ collisionBox = io.papermc.paper.util.CollisionUtil.cutDownwards(currBoundingBox, movement.y); -+ } -+ } else { -+ // note: xZero == false or zZero == false -+ if (stepHeight > 0.0 && (onGround || (movement.y < 0.0))) { -+ // don't bother getting the collisions if we don't need them. -+ if (movement.y <= 0.0) { -+ collisionBox = io.papermc.paper.util.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(movement.x, movement.y, movement.z), stepHeight); -+ } else { -+ collisionBox = currBoundingBox.expandTowards(movement.x, Math.max(stepHeight, movement.y), movement.z); -+ } -+ } else { -+ collisionBox = currBoundingBox.expandTowards(movement.x, movement.y, movement.z); -+ } -+ } -+ -+ io.papermc.paper.util.CollisionUtil.getCollisions( -+ world, this, collisionBox, potentialCollisionsVoxel, potentialCollisionsBB, -+ io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, -+ null, null -+ ); -+ -+ if (potentialCollisionsVoxel.isEmpty() && potentialCollisionsBB.isEmpty()) { -+ return movement; -+ } - -- if (this.maxUpStep() > 0.0F && flag3 && (flag || flag2)) { -- Vec3 vec3d2 = Entity.collideBoundingBox(this, new Vec3(movement.x, (double) this.maxUpStep(), movement.z), axisalignedbb, this.level(), list); -- Vec3 vec3d3 = Entity.collideBoundingBox(this, new Vec3(0.0D, (double) this.maxUpStep(), 0.0D), axisalignedbb.expandTowards(movement.x, 0.0D, movement.z), this.level(), list); -+ final Vec3 limitedMoveVector = io.papermc.paper.util.CollisionUtil.performCollisions(movement, currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); - -- if (vec3d3.y < (double) this.maxUpStep()) { -- Vec3 vec3d4 = Entity.collideBoundingBox(this, new Vec3(movement.x, 0.0D, movement.z), axisalignedbb.move(vec3d3), this.level(), list).add(vec3d3); -+ if (stepHeight > 0.0 -+ && (onGround || (limitedMoveVector.y != movement.y && movement.y < 0.0)) -+ && (limitedMoveVector.x != movement.x || limitedMoveVector.z != movement.z)) { -+ Vec3 vec3d2 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, stepHeight, movement.z), currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); -+ final Vec3 vec3d3 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(movement.x, 0.0, movement.z), potentialCollisionsVoxel, potentialCollisionsBB); -+ -+ if (vec3d3.y < stepHeight) { -+ final Vec3 vec3d4 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, 0.0D, movement.z), currBoundingBox.move(vec3d3), potentialCollisionsVoxel, potentialCollisionsBB).add(vec3d3); - - if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) { - vec3d2 = vec3d4; - } - } - -- if (vec3d2.horizontalDistanceSqr() > vec3d1.horizontalDistanceSqr()) { -- return vec3d2.add(Entity.collideBoundingBox(this, new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), axisalignedbb.move(vec3d2), this.level(), list)); -+ if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) { -+ return vec3d2.add(io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisionsVoxel, potentialCollisionsBB)); - } -- } - -- return vec3d1; -+ return limitedMoveVector; -+ } else { -+ return limitedMoveVector; -+ } -+ // Paper end - optimise collisions - } - - public static Vec3 collideBoundingBox(@Nullable Entity entity, Vec3 movement, AABB entityBoundingBox, Level world, List collisions) { -@@ -2687,11 +2772,70 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S - float f = this.dimensions.width * 0.8F; - AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f); - -- return BlockPos.betweenClosedStream(axisalignedbb).anyMatch((blockposition) -> { -- BlockState iblockdata = this.level().getBlockState(blockposition); -+ // Paper start - optimise collisions -+ if (io.papermc.paper.util.CollisionUtil.isEmpty(axisalignedbb)) { -+ return false; -+ } - -- return !iblockdata.isAir() && iblockdata.isSuffocating(this.level(), blockposition) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level(), blockposition).move((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()), Shapes.create(axisalignedbb), BooleanOp.AND); -- }); -+ final BlockPos.MutableBlockPos tempPos = new BlockPos.MutableBlockPos(); -+ -+ final int minX = Mth.floor(axisalignedbb.minX); -+ final int minY = Mth.floor(axisalignedbb.minY); -+ final int minZ = Mth.floor(axisalignedbb.minZ); -+ final int maxX = Mth.floor(axisalignedbb.maxX); -+ final int maxY = Mth.floor(axisalignedbb.maxY); -+ final int maxZ = Mth.floor(axisalignedbb.maxZ); -+ -+ final net.minecraft.server.level.ServerChunkCache chunkProvider = (net.minecraft.server.level.ServerChunkCache)this.level.getChunkSource(); -+ -+ long lastChunkKey = ChunkPos.INVALID_CHUNK_POS; -+ net.minecraft.world.level.chunk.LevelChunk lastChunk = null; -+ for (int fz = minZ; fz <= maxZ; ++fz) { -+ tempPos.setZ(fz); -+ for (int fx = minX; fx <= maxX; ++fx) { -+ final int newChunkX = fx >> 4; -+ final int newChunkZ = fz >> 4; -+ final net.minecraft.world.level.chunk.LevelChunk chunk = lastChunkKey == (lastChunkKey = io.papermc.paper.util.CoordinateUtils.getChunkKey(newChunkX, newChunkZ)) ? -+ lastChunk : (lastChunk = chunkProvider.getChunkAtIfLoadedImmediately(newChunkX, newChunkZ)); -+ tempPos.setX(fx); -+ if (chunk == null) { -+ continue; -+ } -+ for (int fy = minY; fy <= maxY; ++fy) { -+ tempPos.setY(fy); -+ -+ final BlockState state = chunk.getBlockState(tempPos); -+ -+ if (state.emptyCollisionShape() || !state.isSuffocating(this.level, tempPos)) { -+ continue; -+ } -+ -+ // Yes, it does not use the Entity context stuff. -+ final VoxelShape collisionShape = state.getCollisionShape(this.level, tempPos); -+ -+ if (collisionShape.isEmpty()) { -+ continue; -+ } -+ -+ final AABB toCollide = axisalignedbb.move(-(double)fx, -(double)fy, -(double)fz); -+ -+ final AABB singleAABB = collisionShape.getSingleAABBRepresentation(); -+ if (singleAABB != null) { -+ if (io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(singleAABB, toCollide)) { -+ return true; -+ } -+ continue; -+ } -+ -+ if (io.papermc.paper.util.CollisionUtil.voxelShapeIntersectNoEmpty(collisionShape, toCollide)) { -+ return true; -+ } -+ continue; -+ } -+ } -+ } -+ // Paper end - optimise collisions -+ return false; - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -index 6dfcc296ff7e59ecbebc5446973fabc9eff3cb43..94a30a0c1266bf919d1dc4ca2b19489edd54a7fa 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -353,7 +353,7 @@ public class ArmorStand extends LivingEntity { - @Override - protected void pushEntities() { - if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups -- List list = this.level().getEntities((Entity) this, this.getBoundingBox(), ArmorStand.RIDABLE_MINECARTS); -+ List list = this.level().getEntitiesOfClass(AbstractMinecart.class, this.getBoundingBox(), ArmorStand.RIDABLE_MINECARTS); // Paper - optimise collisions - Iterator iterator = list.iterator(); - - while (iterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java -index ffa4f34d964fbcc53e2dfe11677832db21a6eb93..7618364e5373fe17cfe45a5a4ee9ab25e591581c 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Spider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java -@@ -86,7 +86,7 @@ public class Spider extends Monster { - public void tick() { - super.tick(); - if (!this.level().isClientSide) { -- this.setClimbing(this.horizontalCollision && (this.level().paperConfig().entities.behavior.allowSpiderWorldBorderClimbing)); // Paper - Add config option for spider worldborder climbing -+ this.setClimbing(this.horizontalCollision && (this.level().paperConfig().entities.behavior.allowSpiderWorldBorderClimbing || !io.papermc.paper.util.CollisionUtil.isCollidingWithBorder(this.level().getWorldBorder(), this.getBoundingBox().inflate(io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)))); // Paper - Add config option for spider worldborder climbing & Inflate by +EPSILON as collision will just barely place us outside border - } - - } -diff --git a/src/main/java/net/minecraft/world/level/BlockCollisions.java b/src/main/java/net/minecraft/world/level/BlockCollisions.java -index a3eaf80b020c3bbc0306c5d17659ee661dfd275b..1b6f72932fbdd567a1534bcf15e8a610b00f974d 100644 ---- a/src/main/java/net/minecraft/world/level/BlockCollisions.java -+++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java -@@ -105,7 +105,7 @@ public class BlockCollisions extends AbstractIterator { - - VoxelShape voxelShape = blockState.getCollisionShape(this.collisionGetter, this.pos, this.context); - if (voxelShape == Shapes.block()) { -- if (!this.box.intersects((double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) { -+ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(this.box, (double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) { // Paper - keep vanilla behavior for voxelshape intersection - See comment in CollisionUtil - continue; - } - -diff --git a/src/main/java/net/minecraft/world/level/ClipContext.java b/src/main/java/net/minecraft/world/level/ClipContext.java -index 86a4f30c8784c602436ecf1c78efb0bdca4b7089..b0bea28e9261767c60d30fb0e76f4f3af8a5634e 100644 ---- a/src/main/java/net/minecraft/world/level/ClipContext.java -+++ b/src/main/java/net/minecraft/world/level/ClipContext.java -@@ -17,8 +17,8 @@ public class ClipContext { - - private final Vec3 from; - private final Vec3 to; -- private final ClipContext.Block block; -- private final ClipContext.Fluid fluid; -+ public final ClipContext.Block block; // Paper - optimise collisions - public -+ public final ClipContext.Fluid fluid; // Paper - optimise collisions - public - private final CollisionContext collisionContext; - - public ClipContext(Vec3 start, Vec3 end, ClipContext.Block shapeType, ClipContext.Fluid fluidHandling, Entity entity) { -diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java -index c476e37df8a75d77f5093b2a449e04f25ef2c2dd..5d66aadae51db1ae760812849bfc8740b82af9a9 100644 ---- a/src/main/java/net/minecraft/world/level/CollisionGetter.java -+++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java -@@ -35,6 +35,12 @@ public interface CollisionGetter extends BlockGetter { - return this.isUnobstructed(entity, Shapes.create(entity.getBoundingBox())); - } - -+ // Paper start - optimise collisions -+ default boolean noCollision(Entity entity, AABB box, boolean loadChunks) { -+ return this.noCollision(entity, box); -+ } -+ // Paper end - optimise collisions -+ - default boolean noCollision(AABB box) { - return this.noCollision((Entity)null, box); - } -diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index cc888bbcd6a50124fa553bc4a8ffd1e8885d3856..f42dd9602805e9d538506ee4e3eac7e2811a3da6 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -45,17 +45,36 @@ public interface EntityGetter { - } - - default boolean isUnobstructed(@Nullable Entity except, VoxelShape shape) { -+ // Paper start - optimise collisions - if (shape.isEmpty()) { -- return true; -- } else { -- for(Entity entity : this.getEntities(except, shape.bounds())) { -- if (!entity.isRemoved() && entity.blocksBuilding && (except == null || !entity.isPassengerOfSameVehicle(except)) && Shapes.joinIsNotEmpty(shape, Shapes.create(entity.getBoundingBox()), BooleanOp.AND)) { -- return false; -+ return false; -+ } -+ -+ final AABB singleAABB = shape.getSingleAABBRepresentation(); -+ final List entities = this.getEntities( -+ except, -+ singleAABB == null ? shape.bounds() : singleAABB.inflate(-io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON, -io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON, -io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) -+ ); -+ -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ final Entity otherEntity = entities.get(i); -+ -+ if (otherEntity.isRemoved() || !otherEntity.blocksBuilding || (except != null && otherEntity.isPassengerOfSameVehicle(except))) { -+ continue; -+ } -+ -+ if (singleAABB == null) { -+ final AABB entityBB = otherEntity.getBoundingBox(); -+ if (io.papermc.paper.util.CollisionUtil.isEmpty(entityBB) || !io.papermc.paper.util.CollisionUtil.voxelShapeIntersectNoEmpty(shape, entityBB)) { -+ continue; - } - } - -- return true; -+ return false; - } -+ -+ return true; -+ // Paper end - optimise collisions - } - - default List getEntitiesOfClass(Class entityClass, AABB box) { -@@ -63,23 +82,41 @@ public interface EntityGetter { - } - - default List getEntityCollisions(@Nullable Entity entity, AABB box) { -- if (box.getSize() < 1.0E-7D) { -- return List.of(); -+ // Paper start - optimise collisions -+ // first behavior change is to correctly check for empty AABB -+ if (io.papermc.paper.util.CollisionUtil.isEmpty(box)) { -+ // reduce indirection by always returning type with same class -+ return new java.util.ArrayList<>(); -+ } -+ -+ // to comply with vanilla intersection rules, expand by -epsilon so that we only get stuff we definitely collide with. -+ // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems -+ // specifically with boat collisions. -+ box = box.inflate(-io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON, -io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON, -io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON); -+ -+ final List entities; -+ if (entity != null && entity.hardCollides()) { -+ entities = this.getEntities(entity, box, null); - } else { -- Predicate predicate = entity == null ? EntitySelector.CAN_BE_COLLIDED_WITH : EntitySelector.NO_SPECTATORS.and(entity::canCollideWith); -- List list = this.getEntities(entity, box.inflate(1.0E-7D), predicate); -- if (list.isEmpty()) { -- return List.of(); -- } else { -- ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(list.size()); -- -- for(Entity entity2 : list) { -- builder.add(Shapes.create(entity2.getBoundingBox())); -- } -+ entities = this.getHardCollidingEntities(entity, box, null); -+ } - -- return builder.build(); -+ final List ret = new java.util.ArrayList<>(Math.min(25, entities.size())); -+ -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ final Entity otherEntity = entities.get(i); -+ -+ if (otherEntity.isSpectator()) { -+ continue; -+ } -+ -+ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) { -+ ret.add(Shapes.create(otherEntity.getBoundingBox())); - } - } -+ -+ return ret; -+ // Paper end - optimise collisions - } - - // Paper start - Affects Spawning API -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 1712cf22d987a87c427f042a89a9fff90203b079..f21f1b3fcab47f18cbf26a9797b1b7b9a5dccfc9 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -294,6 +294,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime); - this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); - this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray -+ // Paper start - optimise collisions -+ this.minSection = io.papermc.paper.util.WorldUtil.getMinSection(this); -+ this.maxSection = io.papermc.paper.util.WorldUtil.getMaxSection(this); -+ // Paper end - optimise collisions - } - - // Paper start - Cancel hit for vanished players -@@ -335,6 +339,366 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return true; - } - // Paper end - Cancel hit for vanished players -+ // Paper start - optimise collisions -+ public final int minSection; -+ public final int maxSection; -+ -+ @Override -+ public final boolean isUnobstructed(final Entity entity) { -+ final AABB boundingBox = entity.getBoundingBox(); -+ if (io.papermc.paper.util.CollisionUtil.isEmpty(boundingBox)) { -+ return false; -+ } -+ -+ final List entities = this.getEntities( -+ entity, -+ boundingBox.inflate(-io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON, -io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON, -io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON), -+ null -+ ); -+ -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ final Entity otherEntity = entities.get(i); -+ -+ if (otherEntity.isSpectator() || otherEntity.isRemoved() || !otherEntity.blocksBuilding || otherEntity.isPassengerOfSameVehicle(entity)) { -+ continue; -+ } -+ -+ return false; -+ } -+ -+ return true; -+ } -+ -+ private static net.minecraft.world.phys.BlockHitResult miss(final ClipContext clipContext) { -+ final Vec3 to = clipContext.getTo(); -+ final Vec3 from = clipContext.getFrom(); -+ -+ return net.minecraft.world.phys.BlockHitResult.miss(to, Direction.getNearest(from.x - to.x, from.y - to.y, from.z - to.z), BlockPos.containing(to.x, to.y, to.z)); -+ } -+ -+ private static final FluidState AIR_FLUIDSTATE = Fluids.EMPTY.defaultFluidState(); -+ -+ private static net.minecraft.world.phys.BlockHitResult fastClip(final Vec3 from, final Vec3 to, final Level level, -+ final ClipContext clipContext) { -+ final double adjX = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.x - to.x); -+ final double adjY = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.y - to.y); -+ final double adjZ = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.z - to.z); -+ -+ if (adjX == 0.0 && adjY == 0.0 && adjZ == 0.0) { -+ return miss(clipContext); -+ } -+ -+ final double toXAdj = to.x - adjX; -+ final double toYAdj = to.y - adjY; -+ final double toZAdj = to.z - adjZ; -+ final double fromXAdj = from.x + adjX; -+ final double fromYAdj = from.y + adjY; -+ final double fromZAdj = from.z + adjZ; -+ -+ int currX = Mth.floor(fromXAdj); -+ int currY = Mth.floor(fromYAdj); -+ int currZ = Mth.floor(fromZAdj); -+ -+ final BlockPos.MutableBlockPos currPos = new BlockPos.MutableBlockPos(); -+ -+ final double diffX = toXAdj - fromXAdj; -+ final double diffY = toYAdj - fromYAdj; -+ final double diffZ = toZAdj - fromZAdj; -+ -+ final double dxDouble = Math.signum(diffX); -+ final double dyDouble = Math.signum(diffY); -+ final double dzDouble = Math.signum(diffZ); -+ -+ final int dx = (int)dxDouble; -+ final int dy = (int)dyDouble; -+ final int dz = (int)dzDouble; -+ -+ final double normalizedDiffX = diffX == 0.0 ? Double.MAX_VALUE : dxDouble / diffX; -+ final double normalizedDiffY = diffY == 0.0 ? Double.MAX_VALUE : dyDouble / diffY; -+ final double normalizedDiffZ = diffZ == 0.0 ? Double.MAX_VALUE : dzDouble / diffZ; -+ -+ double normalizedCurrX = normalizedDiffX * (diffX > 0.0 ? (1.0 - Mth.frac(fromXAdj)) : Mth.frac(fromXAdj)); -+ double normalizedCurrY = normalizedDiffY * (diffY > 0.0 ? (1.0 - Mth.frac(fromYAdj)) : Mth.frac(fromYAdj)); -+ double normalizedCurrZ = normalizedDiffZ * (diffZ > 0.0 ? (1.0 - Mth.frac(fromZAdj)) : Mth.frac(fromZAdj)); -+ -+ net.minecraft.world.level.chunk.LevelChunkSection[] lastChunk = null; -+ net.minecraft.world.level.chunk.PalettedContainer lastSection = null; -+ int lastChunkX = Integer.MIN_VALUE; -+ int lastChunkY = Integer.MIN_VALUE; -+ int lastChunkZ = Integer.MIN_VALUE; -+ -+ final int minSection = level.minSection; -+ final net.minecraft.server.level.ServerChunkCache chunkProvider = (net.minecraft.server.level.ServerChunkCache)level.getChunkSource(); -+ -+ for (;;) { -+ currPos.set(currX, currY, currZ); -+ -+ final int newChunkX = currX >> 4; -+ final int newChunkY = currY >> 4; -+ final int newChunkZ = currZ >> 4; -+ -+ final int chunkDiff = ((newChunkX ^ lastChunkX) | (newChunkZ ^ lastChunkZ)); -+ final int chunkYDiff = newChunkY ^ lastChunkY; -+ -+ if ((chunkDiff | chunkYDiff) != 0) { -+ if (chunkDiff != 0) { -+ LevelChunk chunk = chunkProvider.getChunkAtIfLoadedImmediately(newChunkX, newChunkZ); -+ lastChunk = chunk == null ? null : chunk.getSections(); // diff: don't load chunks for this -+ } -+ final int sectionY = newChunkY - minSection; -+ lastSection = lastChunk != null && sectionY >= 0 && sectionY < lastChunk.length ? lastChunk[sectionY].states : null; -+ -+ lastChunkX = newChunkX; -+ lastChunkY = newChunkY; -+ lastChunkZ = newChunkZ; -+ } -+ -+ final BlockState blockState; -+ if (lastSection != null && !(blockState = lastSection.get((currX & 15) | ((currZ & 15) << 4) | ((currY & 15) << (4+4)))).isAir()) { -+ final net.minecraft.world.phys.shapes.VoxelShape blockCollision = clipContext.getBlockShape(blockState, level, currPos); -+ -+ final net.minecraft.world.phys.BlockHitResult blockHit = blockCollision.isEmpty() ? null : level.clipWithInteractionOverride(from, to, currPos, blockCollision, blockState); -+ -+ final net.minecraft.world.phys.shapes.VoxelShape fluidCollision; -+ final FluidState fluidState; -+ if (clipContext.fluid != ClipContext.Fluid.NONE && (fluidState = blockState.getFluidState()) != AIR_FLUIDSTATE) { -+ fluidCollision = clipContext.getFluidShape(fluidState, level, currPos); -+ -+ final net.minecraft.world.phys.BlockHitResult fluidHit = fluidCollision.clip(from, to, currPos); -+ -+ if (fluidHit != null) { -+ if (blockHit == null) { -+ return fluidHit; -+ } -+ -+ return from.distanceToSqr(blockHit.getLocation()) <= from.distanceToSqr(fluidHit.getLocation()) ? blockHit : fluidHit; -+ } -+ } -+ -+ if (blockHit != null) { -+ return blockHit; -+ } -+ } // else: usually fall here -+ -+ if (normalizedCurrX > 1.0 && normalizedCurrY > 1.0 && normalizedCurrZ > 1.0) { -+ return miss(clipContext); -+ } -+ -+ // inc the smallest normalized coordinate -+ -+ if (normalizedCurrX < normalizedCurrY) { -+ if (normalizedCurrX < normalizedCurrZ) { -+ currX += dx; -+ normalizedCurrX += normalizedDiffX; -+ } else { -+ // x < y && x >= z <--> z < y && z <= x -+ currZ += dz; -+ normalizedCurrZ += normalizedDiffZ; -+ } -+ } else if (normalizedCurrY < normalizedCurrZ) { -+ // y <= x && y < z -+ currY += dy; -+ normalizedCurrY += normalizedDiffY; -+ } else { -+ // y <= x && z <= y <--> z <= y && z <= x -+ currZ += dz; -+ normalizedCurrZ += normalizedDiffZ; -+ } -+ } -+ } -+ -+ @Override -+ public final net.minecraft.world.phys.BlockHitResult clip(final ClipContext clipContext) { -+ // can only do this in this class, as not everything that implements BlockGetter can retrieve chunks -+ return fastClip(clipContext.getFrom(), clipContext.getTo(), this, clipContext); -+ } -+ -+ @Override -+ public final boolean noCollision(final Entity entity, final AABB box, final boolean loadChunks) { -+ int flags = io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_ONLY; -+ if (entity != null) { -+ flags |= io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_BORDER; -+ } -+ if (loadChunks) { -+ flags |= io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_LOAD_CHUNKS; -+ } -+ if (io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, null, flags, null)) { -+ return false; -+ } -+ -+ return !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, flags, null); -+ } -+ -+ @Override -+ public final boolean collidesWithSuffocatingBlock(final Entity entity, final AABB box) { -+ return io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, null, -+ io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_ONLY, -+ (final BlockState state, final BlockPos pos) -> { -+ return state.isSuffocating(Level.this, pos); -+ } -+ ); -+ } -+ -+ private static net.minecraft.world.phys.shapes.VoxelShape inflateAABBToVoxel(final AABB aabb, final double x, final double y, final double z) { -+ return net.minecraft.world.phys.shapes.Shapes.create( -+ aabb.minX - x, -+ aabb.minY - y, -+ aabb.minZ - z, -+ -+ aabb.maxX + x, -+ aabb.maxY + y, -+ aabb.maxZ + z -+ ); -+ } -+ -+ @Override -+ public final java.util.Optional findFreePosition(final Entity entity, final net.minecraft.world.phys.shapes.VoxelShape boundsShape, final Vec3 fromPosition, -+ final double rangeX, final double rangeY, final double rangeZ) { -+ if (boundsShape.isEmpty()) { -+ return java.util.Optional.empty(); -+ } -+ -+ final double expandByX = rangeX * 0.5; -+ final double expandByY = rangeY * 0.5; -+ final double expandByZ = rangeZ * 0.5; -+ -+ // note: it is useless to look at shapes outside of range / 2.0 -+ final AABB collectionVolume = boundsShape.bounds().inflate(expandByX, expandByY, expandByZ); -+ -+ final List aabbs = new java.util.ArrayList<>(); -+ final List voxels = new java.util.ArrayList<>(); -+ -+ io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder( -+ this, entity, collectionVolume, voxels, aabbs, -+ io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, -+ null -+ ); -+ -+ // push voxels into aabbs -+ for (int i = 0, len = voxels.size(); i < len; ++i) { -+ aabbs.addAll(voxels.get(i).toAabbs()); -+ } -+ -+ // expand AABBs -+ final net.minecraft.world.phys.shapes.VoxelShape first = aabbs.isEmpty() ? net.minecraft.world.phys.shapes.Shapes.empty() : inflateAABBToVoxel(aabbs.get(0), expandByX, expandByY, expandByZ); -+ final net.minecraft.world.phys.shapes.VoxelShape[] rest = new net.minecraft.world.phys.shapes.VoxelShape[Math.max(0, aabbs.size() - 1)]; -+ -+ for (int i = 1, len = aabbs.size(); i < len; ++i) { -+ rest[i - 1] = inflateAABBToVoxel(aabbs.get(i), expandByX, expandByY, expandByZ); -+ } -+ -+ // use optimized implementation of ORing the shapes together -+ final net.minecraft.world.phys.shapes.VoxelShape joined = net.minecraft.world.phys.shapes.Shapes.or(first, rest); -+ -+ // find free space -+ // can use unoptimized join here (instead of join()), as closestPointTo uses toAabbs() -+ final net.minecraft.world.phys.shapes.VoxelShape freeSpace = net.minecraft.world.phys.shapes.Shapes.joinUnoptimized( -+ boundsShape, joined, net.minecraft.world.phys.shapes.BooleanOp.ONLY_FIRST -+ ); -+ -+ return freeSpace.closestPointTo(fromPosition); -+ } -+ -+ @Override -+ public final java.util.Optional findSupportingBlock(final Entity entity, final AABB aabb) { -+ final int minBlockX = Mth.floor(aabb.minX - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) - 1; -+ final int maxBlockX = Mth.floor(aabb.maxX + io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) + 1; -+ -+ final int minBlockY = Mth.floor(aabb.minY - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) - 1; -+ final int maxBlockY = Mth.floor(aabb.maxY + io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) + 1; -+ -+ final int minBlockZ = Mth.floor(aabb.minZ - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) - 1; -+ final int maxBlockZ = Mth.floor(aabb.maxZ + io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) + 1; -+ -+ io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext collisionContext = null; -+ -+ final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); -+ BlockPos selected = null; -+ double selectedDistance = Double.MAX_VALUE; -+ -+ final Vec3 entityPos = entity.position(); -+ -+ LevelChunk lastChunk = null; -+ int lastChunkX = Integer.MIN_VALUE; -+ int lastChunkZ = Integer.MIN_VALUE; -+ -+ final net.minecraft.server.level.ServerChunkCache chunkProvider = (net.minecraft.server.level.ServerChunkCache)this.getChunkSource(); -+ -+ for (int currZ = minBlockZ; currZ <= maxBlockZ; ++currZ) { -+ pos.setZ(currZ); -+ for (int currX = minBlockX; currX <= maxBlockX; ++currX) { -+ pos.setX(currX); -+ -+ final int newChunkX = currX >> 4; -+ final int newChunkZ = currZ >> 4; -+ -+ final int chunkDiff = ((newChunkX ^ lastChunkX) | (newChunkZ ^ lastChunkZ)); -+ -+ if (chunkDiff != 0) { -+ lastChunk = chunkProvider.getChunkAtIfLoadedImmediately(newChunkX, newChunkZ); -+ } -+ -+ if (lastChunk == null) { -+ continue; -+ } -+ for (int currY = minBlockY; currY <= maxBlockY; ++currY) { -+ int edgeCount = ((currX == minBlockX || currX == maxBlockX) ? 1 : 0) + -+ ((currY == minBlockY || currY == maxBlockY) ? 1 : 0) + -+ ((currZ == minBlockZ || currZ == maxBlockZ) ? 1 : 0); -+ if (edgeCount == 3) { -+ continue; -+ } -+ -+ pos.setY(currY); -+ -+ final double distance = pos.distToCenterSqr(entityPos); -+ if (distance > selectedDistance || (distance == selectedDistance && selected.compareTo(pos) >= 0)) { -+ continue; -+ } -+ -+ final BlockState state = lastChunk.getBlockState(currX, currY, currZ); -+ if (state.emptyCollisionShape()) { -+ continue; -+ } -+ -+ if ((edgeCount != 1 || state.hasLargeCollisionShape()) && (edgeCount != 2 || state.getBlock() == Blocks.MOVING_PISTON)) { -+ if (collisionContext == null) { -+ collisionContext = new io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext(entity); -+ } -+ final net.minecraft.world.phys.shapes.VoxelShape blockCollision = state.getCollisionShape(lastChunk, pos, collisionContext); -+ if (blockCollision.isEmpty()) { -+ continue; -+ } -+ -+ // avoid VoxelShape#move by shifting the entity collision shape instead -+ final AABB shiftedAABB = aabb.move(-(double)currX, -(double)currY, -(double)currZ); -+ -+ final AABB singleAABB = blockCollision.getSingleAABBRepresentation(); -+ if (singleAABB != null) { -+ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(singleAABB, shiftedAABB)) { -+ continue; -+ } -+ -+ selected = pos.immutable(); -+ selectedDistance = distance; -+ continue; -+ } -+ -+ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersectNoEmpty(blockCollision, shiftedAABB)) { -+ continue; -+ } -+ -+ selected = pos.immutable(); -+ selectedDistance = distance; -+ continue; -+ } -+ } -+ } -+ } -+ -+ return java.util.Optional.ofNullable(selected); -+ } -+ // Paper end - optimise collisions - @Override - public boolean isClientSide() { - return this.isClientSide; -@@ -958,7 +1322,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - @Override - public boolean noCollision(@Nullable Entity entity, AABB box) { - if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return false; -- return LevelAccessor.super.noCollision(entity, box); -+ // Paper start - optimise collisions -+ int flags = io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_ONLY; -+ if (entity != null) { -+ flags |= io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_BORDER; -+ } -+ if (io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, null, flags, null)) { -+ return false; -+ } -+ -+ return !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, flags, null); -+ // Paper end - optimise collisions - } - // Paper end - Option to prevent armor stands from doing entity lookups - -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 b60a52788e73de3dcb086c1a4628466b25c9d3ef..22036ed3ea0629bc12981a8d91a03e55cc2117d6 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -284,7 +284,7 @@ public class Block extends BlockBehaviour implements ItemLike { - } - - public static boolean isShapeFullBlock(VoxelShape shape) { -- return (Boolean) Block.SHAPE_FULL_BLOCK_CACHE.getUnchecked(shape); -+ return shape.isFullBlock(); // Paper - optimise collisions - } - - public boolean propagatesSkylightDown(BlockState state, BlockGetter world, BlockPos pos) { -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index e493b34aa8726ed48f8e5db2ae8ea561cc5b1f75..2892e586146cbc560f0bcf4b9af6d0575cb0a82e 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -882,6 +882,10 @@ public abstract class BlockBehaviour implements FeatureElement { - this.instrument = blockbase_info.instrument; - this.replaceable = blockbase_info.replaceable; - this.conditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion; // Paper -+ // Paper start - optimise collisions -+ this.id1 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET); -+ this.id2 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET); -+ // Paper end - optimise collisions - } - // Paper start - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time - private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; -@@ -930,6 +934,52 @@ public abstract class BlockBehaviour implements FeatureElement { - return this.conditionallyFullOpaque; - } - // Paper end - starlight -+ // Paper start - optimise collisions -+ private static final int RANDOM_OFFSET = 704237939; -+ private static final Direction[] DIRECTIONS_CACHED = Direction.values(); -+ private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger(); -+ private final int id1, id2; -+ private boolean occludesFullBlock; -+ private boolean emptyCollisionShape; -+ private VoxelShape constantCollisionShape; -+ private AABB constantAABBCollision; -+ private static void initCaches(final VoxelShape shape) { -+ shape.isFullBlock(); -+ shape.occludesFullBlock(); -+ shape.toAabbs(); -+ if (!shape.isEmpty()) { -+ shape.bounds(); -+ } -+ } -+ -+ public final boolean hasCache() { -+ return this.cache != null; -+ } -+ -+ public final boolean occludesFullBlock() { -+ return this.occludesFullBlock; -+ } -+ -+ public final boolean emptyCollisionShape() { -+ return this.emptyCollisionShape; -+ } -+ -+ public final int uniqueId1() { -+ return this.id1; -+ } -+ -+ public final int uniqueId2() { -+ return this.id2; -+ } -+ -+ public final VoxelShape getConstantCollisionShape() { -+ return this.constantCollisionShape; -+ } -+ -+ public final AABB getConstantCollisionAABB() { -+ return this.constantAABBCollision; -+ } -+ // Paper end - optimise collisions - - public void initCache() { - this.fluidState = ((Block) this.owner).getFluidState(this.asState()); -@@ -941,6 +991,39 @@ public abstract class BlockBehaviour implements FeatureElement { - this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - starlight - cache opacity for light - - this.legacySolid = this.calculateSolid(); -+ // Paper start - optimise collisions -+ if (this.cache != null) { -+ final VoxelShape collisionShape = this.cache.collisionShape; -+ try { -+ this.constantCollisionShape = this.getCollisionShape(null, null, null); -+ this.constantAABBCollision = this.constantCollisionShape == null ? null : this.constantCollisionShape.getSingleAABBRepresentation(); -+ } catch (final Throwable throwable) { -+ this.constantCollisionShape = null; -+ this.constantAABBCollision = null; -+ } -+ this.occludesFullBlock = collisionShape.occludesFullBlock(); -+ this.emptyCollisionShape = collisionShape.isEmpty(); -+ // init caches -+ initCaches(collisionShape); -+ if (collisionShape != Shapes.empty() && collisionShape != Shapes.block()) { -+ for (final Direction direction : DIRECTIONS_CACHED) { -+ // initialise the directional face shape cache as well -+ final VoxelShape shape = Shapes.getFaceShape(collisionShape, direction); -+ initCaches(shape); -+ } -+ } -+ if (this.cache.occlusionShapes != null) { -+ for (final VoxelShape shape : this.cache.occlusionShapes) { -+ initCaches(shape); -+ } -+ } -+ } else { -+ this.occludesFullBlock = false; -+ this.emptyCollisionShape = false; -+ this.constantCollisionShape = null; -+ this.constantAABBCollision = null; -+ } -+ // Paper end - optimise collisions - } - - public Block getBlock() { -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -index eb05c01e85825cbd5b7cf43bc6d261db0b871b92..796bbef3544e06b8e7aac7e8ac5f740a2613f4bd 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -26,6 +26,22 @@ public class LevelChunkSection { - // CraftBukkit start - read/write - private PalettedContainer> biomes; - public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper -+ // Paper start - optimise collisions -+ private int specialCollidingBlocks; -+ -+ private void updateBlockCallback(final int x, final int y, final int z, final BlockState oldState, final BlockState newState) { -+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(newState)) { -+ ++this.specialCollidingBlocks; -+ } -+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(oldState)) { -+ --this.specialCollidingBlocks; -+ } -+ } -+ -+ public final int getSpecialCollidingBlocks() { -+ return this.specialCollidingBlocks; -+ } -+ // Paper end - optimise collisions - - public LevelChunkSection(PalettedContainer datapaletteblock, PalettedContainer> palettedcontainerro) { - // CraftBukkit end -@@ -62,8 +78,8 @@ public class LevelChunkSection { - return this.setBlockState(x, y, z, state, true); - } - -- public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) { -- BlockState iblockdata1; -+ public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) { // Paper - state -> new state -+ BlockState iblockdata1; // Paper - iblockdata1 -> oldState - - if (lock) { - iblockdata1 = (BlockState) this.states.getAndSet(x, y, z, state); -@@ -102,6 +118,7 @@ public class LevelChunkSection { - ++this.tickingFluidCount; - } - -+ this.updateBlockCallback(x, y, z, iblockdata1, state); // Paper - optimise collisions - return iblockdata1; - } - -@@ -147,6 +164,11 @@ public class LevelChunkSection { - } - } - -+ // Paper start - optimise collisions -+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(iblockdata)) { -+ ++this.specialCollidingBlocks; -+ } -+ // Paper end - optimise collisions - }); - } - // Paper end -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 a98ab20814cc29a25e9d29adfbb7e70d46768df2..6d8ff6c06af5545634f255ed17dc1e489ece2548 100644 ---- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -@@ -240,6 +240,17 @@ public abstract class FlowingFluid extends Fluid { - } - - private boolean canPassThroughWall(Direction face, BlockGetter world, BlockPos pos, BlockState state, BlockPos fromPos, BlockState fromState) { -+ // Paper start - optimise collisions -+ if (state.emptyCollisionShape() & fromState.emptyCollisionShape()) { -+ // don't even try to cache simple cases -+ return true; -+ } -+ -+ if (state.occludesFullBlock() | fromState.occludesFullBlock()) { -+ // don't even try to cache simple cases -+ return false; -+ } -+ // Paper end - optimise collisions - Object2ByteLinkedOpenHashMap object2bytelinkedopenhashmap; - - if (!state.getBlock().hasDynamicShape() && !fromState.getBlock().hasDynamicShape()) { -diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java -index b8443953de15066f32f629c0dd7e24bad750f558..67d595f75e0c3bffdb27b85b25ccd1f0bf1427d5 100644 ---- a/src/main/java/net/minecraft/world/phys/AABB.java -+++ b/src/main/java/net/minecraft/world/phys/AABB.java -@@ -25,6 +25,17 @@ public class AABB { - this.maxZ = Math.max(z1, z2); - } - -+ // Paper start -+ public AABB(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, boolean dummy) { -+ this.minX = minX; -+ this.minY = minY; -+ this.minZ = minZ; -+ this.maxX = maxX; -+ this.maxY = maxY; -+ this.maxZ = maxZ; -+ } -+ // Paper end -+ - public AABB(BlockPos pos) { - this((double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)(pos.getX() + 1), (double)(pos.getY() + 1), (double)(pos.getZ() + 1)); - } -@@ -305,7 +316,7 @@ public class AABB { - } - - @Nullable -- private static Direction getDirection(AABB box, Vec3 intersectingVector, double[] traceDistanceResult, @Nullable Direction approachDirection, double deltaX, double deltaY, double deltaZ) { -+ public static Direction getDirection(AABB box, Vec3 intersectingVector, double[] traceDistanceResult, @Nullable Direction approachDirection, double deltaX, double deltaY, double deltaZ) { // Paper - optimise collisions - public - if (deltaX > 1.0E-7D) { - approachDirection = clipPoint(traceDistanceResult, approachDirection, deltaX, deltaY, deltaZ, box.minX, box.minY, box.maxY, box.minZ, box.maxZ, Direction.WEST, intersectingVector.x, intersectingVector.y, intersectingVector.z); - } else if (deltaX < -1.0E-7D) { -diff --git a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java -index 9d627b8e6bf3140b894d38b9a720896e2d776369..a232b9396a41c11579a4d691b05717b16473513e 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java -@@ -15,7 +15,7 @@ public class ArrayVoxelShape extends VoxelShape { - this(shape, (DoubleList)DoubleArrayList.wrap(Arrays.copyOf(xPoints, shape.getXSize() + 1)), (DoubleList)DoubleArrayList.wrap(Arrays.copyOf(yPoints, shape.getYSize() + 1)), (DoubleList)DoubleArrayList.wrap(Arrays.copyOf(zPoints, shape.getZSize() + 1))); - } - -- ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) { -+ public ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) { // Paper - optimise collisions - public - super(shape); - int i = shape.getXSize() + 1; - int j = shape.getYSize() + 1; -@@ -27,6 +27,7 @@ public class ArrayVoxelShape extends VoxelShape { - } else { - throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape.")); - } -+ this.initCache(); // Paper - optimise collisions - } - - @Override -@@ -42,4 +43,5 @@ public class ArrayVoxelShape extends VoxelShape { - throw new IllegalArgumentException(); - } - } -+ - } -diff --git a/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java -index c25f409d63a50c5de1434db1d6b298935f106221..6f532d9aa613ecb0f5695b108ec6d7ed3598ca82 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java -@@ -4,13 +4,13 @@ import java.util.BitSet; - import net.minecraft.core.Direction; - - public final class BitSetDiscreteVoxelShape extends DiscreteVoxelShape { -- private final BitSet storage; -- private int xMin; -- private int yMin; -- private int zMin; -- private int xMax; -- private int yMax; -- private int zMax; -+ public final BitSet storage; // Paper - optimise collisions - public -+ public int xMin; // Paper - optimise collisions - public -+ public int yMin; // Paper - optimise collisions - public -+ public int zMin; // Paper - optimise collisions - public -+ public int xMax; // Paper - optimise collisions - public -+ public int yMax; // Paper - optimise collisions - public -+ public int zMax; // Paper - optimise collisions - public - - public BitSetDiscreteVoxelShape(int sizeX, int sizeY, int sizeZ) { - super(sizeX, sizeY, sizeZ); -@@ -150,46 +150,106 @@ public final class BitSetDiscreteVoxelShape extends DiscreteVoxelShape { - } - - protected static void forAllBoxes(DiscreteVoxelShape voxelSet, DiscreteVoxelShape.IntLineConsumer callback, boolean coalesce) { -- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = new BitSetDiscreteVoxelShape(voxelSet); -+ // Paper start - optimise collisions -+ // called with the shape of a VoxelShape, so we can expect the cache to exist -+ final io.papermc.paper.util.collisions.CachedShapeData cache = voxelSet.getOrCreateCachedShapeData(); -+ -+ final int sizeX = cache.sizeX(); -+ final int sizeY = cache.sizeY(); -+ final int sizeZ = cache.sizeZ(); -+ -+ int indexX; -+ int indexY = 0; -+ int indexZ; -+ -+ int incY = sizeZ; -+ int incX = sizeZ*sizeY; -+ -+ long[] bitset = cache.voxelSet(); -+ -+ // index = z + y*size_z + x*(size_z*size_y) -+ -+ if (!coalesce) { -+ // due to the odd selection of loop order (which does affect behavior, unfortunately) we can't simply -+ // increment an index in the Z loop, and have to perform this trash (keeping track of 3 counters) to avoid -+ // the multiplication -+ for (int y = 0; y < sizeY; ++y, indexY += incY) { -+ indexX = indexY; -+ for (int x = 0; x < sizeX; ++x, indexX += incX) { -+ indexZ = indexX; -+ for (int z = 0; z < sizeZ; ++z, ++indexZ) { -+ if ((bitset[indexZ >>> 6] & (1L << indexZ)) != 0L) { -+ callback.consume(x, y, z, x + 1, y + 1, z + 1); -+ } -+ } -+ } -+ } -+ } else { -+ // same notes about loop order as the above -+ // this branch is actually important to optimise, as it affects uncached toAabbs() (which affects optimize()) - -- for(int i = 0; i < bitSetDiscreteVoxelShape.ySize; ++i) { -- for(int j = 0; j < bitSetDiscreteVoxelShape.xSize; ++j) { -- int k = -1; -+ // only clone when we may write to it -+ bitset = bitset.clone(); - -- for(int l = 0; l <= bitSetDiscreteVoxelShape.zSize; ++l) { -- if (bitSetDiscreteVoxelShape.isFullWide(j, i, l)) { -- if (coalesce) { -- if (k == -1) { -- k = l; -- } -- } else { -- callback.consume(j, i, l, j + 1, i + 1, l + 1); -+ for (int y = 0; y < sizeY; ++y, indexY += incY) { -+ indexX = indexY; -+ for (int x = 0; x < sizeX; ++x, indexX += incX) { -+ for (int zIdx = indexX, endIndex = indexX + sizeZ; zIdx < endIndex;) { -+ final int firstSetZ = io.papermc.paper.util.collisions.FlatBitsetUtil.firstSet(bitset, zIdx, endIndex); -+ -+ if (firstSetZ == -1) { -+ break; - } -- } else if (k != -1) { -- int m = j; -- int n = i; -- bitSetDiscreteVoxelShape.clearZStrip(k, l, j, i); -- -- while(bitSetDiscreteVoxelShape.isZStripFull(k, l, m + 1, i)) { -- bitSetDiscreteVoxelShape.clearZStrip(k, l, m + 1, i); -- ++m; -+ -+ int lastSetZ = io.papermc.paper.util.collisions.FlatBitsetUtil.firstClear(bitset, firstSetZ, endIndex); -+ if (lastSetZ == -1) { -+ lastSetZ = endIndex; - } - -- while(bitSetDiscreteVoxelShape.isXZRectangleFull(j, m + 1, k, l, n + 1)) { -- for(int o = j; o <= m; ++o) { -- bitSetDiscreteVoxelShape.clearZStrip(k, l, o, n + 1); -+ io.papermc.paper.util.collisions.FlatBitsetUtil.clearRange(bitset, firstSetZ, lastSetZ); -+ -+ // try to merge neighbouring on the X axis -+ int endX = x + 1; // exclusive -+ for (int neighbourIdxStart = firstSetZ + incX, neighbourIdxEnd = lastSetZ + incX; -+ endX < sizeX && io.papermc.paper.util.collisions.FlatBitsetUtil.isRangeSet(bitset, neighbourIdxStart, neighbourIdxEnd); -+ neighbourIdxStart += incX, neighbourIdxEnd += incX) { -+ -+ ++endX; -+ io.papermc.paper.util.collisions.FlatBitsetUtil.clearRange(bitset, neighbourIdxStart, neighbourIdxEnd); -+ } -+ -+ // try to merge neighbouring on the Y axis -+ -+ int endY; // exclusive -+ int firstSetZY, lastSetZY; -+ y_merge: -+ for (endY = y + 1, firstSetZY = firstSetZ + incY, lastSetZY = lastSetZ + incY; endY < sizeY; -+ firstSetZY += incY, lastSetZY += incY) { -+ -+ // test the whole XZ range -+ for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX; -+ ++testX, start += incX, end += incX) { -+ if (!io.papermc.paper.util.collisions.FlatBitsetUtil.isRangeSet(bitset, start, end)) { -+ break y_merge; -+ } - } - -- ++n; -+ ++endY; -+ -+ // passed, so we can clear it -+ for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX; -+ ++testX, start += incX, end += incX) { -+ io.papermc.paper.util.collisions.FlatBitsetUtil.clearRange(bitset, start, end); -+ } - } - -- callback.consume(j, i, k, m + 1, n + 1, l); -- k = -1; -+ callback.consume(x, y, firstSetZ - indexX, endX, endY, lastSetZ - indexX); -+ zIdx = lastSetZ; - } - } - } - } -- -+ // Paper end - optimise collisions - } - - private boolean isZStripFull(int z1, int z2, int x, int y) { -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 68e89dbd79171627046e89699057964e44c40e7d..110405e6e70d980d3e09f04d79562b32a7413071 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java -@@ -7,6 +7,7 @@ import net.minecraft.util.Mth; - public final class CubeVoxelShape extends VoxelShape { - protected CubeVoxelShape(DiscreteVoxelShape voxels) { - super(voxels); -+ this.initCache(); // Paper - optimise collisions - } - - @Override -diff --git a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java -index b27ed92b2a87d4c20c1aa300202adfab896c99ea..d0a4547f95ee24283af77cfa130979d05915292f 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java -@@ -9,6 +9,71 @@ public abstract class DiscreteVoxelShape { - protected final int ySize; - protected final int zSize; - -+ // Paper start - optimise collisions -+ private io.papermc.paper.util.collisions.CachedShapeData cachedShapeData; -+ -+ public final io.papermc.paper.util.collisions.CachedShapeData getOrCreateCachedShapeData() { -+ if (this.cachedShapeData != null) { -+ return this.cachedShapeData; -+ } -+ -+ final DiscreteVoxelShape discreteVoxelShape = (DiscreteVoxelShape)(Object)this; -+ -+ final int sizeX = discreteVoxelShape.getXSize(); -+ final int sizeY = discreteVoxelShape.getYSize(); -+ final int sizeZ = discreteVoxelShape.getZSize(); -+ -+ final int maxIndex = sizeX * sizeY * sizeZ; // exclusive -+ -+ final int longsRequired = (maxIndex + (Long.SIZE - 1)) >>> 6; -+ long[] voxelSet; -+ -+ final boolean isEmpty = discreteVoxelShape.isEmpty(); -+ -+ if (discreteVoxelShape instanceof BitSetDiscreteVoxelShape bitsetShape) { -+ voxelSet = bitsetShape.storage.toLongArray(); -+ if (voxelSet.length < longsRequired) { -+ // happens when the later long values are 0L, so we need to resize -+ voxelSet = java.util.Arrays.copyOf(voxelSet, longsRequired); -+ } -+ } else { -+ voxelSet = new long[longsRequired]; -+ if (!isEmpty) { -+ final int mulX = sizeZ * sizeY; -+ for (int x = 0; x < sizeX; ++x) { -+ for (int y = 0; y < sizeY; ++y) { -+ for (int z = 0; z < sizeZ; ++z) { -+ if (discreteVoxelShape.isFull(x, y, z)) { -+ // index = z + y*size_z + x*(size_z*size_y) -+ final int index = z + y * sizeZ + x * mulX; -+ -+ voxelSet[index >>> 6] |= 1L << index; -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ final boolean hasSingleAABB = sizeX == 1 && sizeY == 1 && sizeZ == 1 && !isEmpty && discreteVoxelShape.isFull(0, 0, 0); -+ -+ final int minFullX = discreteVoxelShape.firstFull(Direction.Axis.X); -+ final int minFullY = discreteVoxelShape.firstFull(Direction.Axis.Y); -+ final int minFullZ = discreteVoxelShape.firstFull(Direction.Axis.Z); -+ -+ final int maxFullX = discreteVoxelShape.lastFull(Direction.Axis.X); -+ final int maxFullY = discreteVoxelShape.lastFull(Direction.Axis.Y); -+ final int maxFullZ = discreteVoxelShape.lastFull(Direction.Axis.Z); -+ -+ return this.cachedShapeData = new io.papermc.paper.util.collisions.CachedShapeData( -+ sizeX, sizeY, sizeZ, voxelSet, -+ minFullX, minFullY, minFullZ, -+ maxFullX, maxFullY, maxFullZ, -+ isEmpty, hasSingleAABB -+ ); -+ } -+ // Paper end - optimise collisions -+ - protected DiscreteVoxelShape(int sizeX, int sizeY, int sizeZ) { - if (sizeX >= 0 && sizeY >= 0 && sizeZ >= 0) { - this.xSize = sizeX; -diff --git a/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java b/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java -index 7ec02a7849437a18860aa0df7d9ddd71b2447d4c..5e45e49ab09344cb95736f4124b1c6e002ef5b82 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java -@@ -4,8 +4,8 @@ import it.unimi.dsi.fastutil.doubles.AbstractDoubleList; - import it.unimi.dsi.fastutil.doubles.DoubleList; - - public class OffsetDoubleList extends AbstractDoubleList { -- private final DoubleList delegate; -- private final double offset; -+ public final DoubleList delegate; // Paper - optimise collisions - public -+ public final double offset; // Paper - optimise collisions - public - - public OffsetDoubleList(DoubleList oldList, double offset) { - this.delegate = oldList; -diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -index 9176735c08a75854209f24113b0e78332249dc4d..17785f7c709073a01928e8e6a57702f8357900f4 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -@@ -16,13 +16,43 @@ public final class Shapes { - public static final double EPSILON = 1.0E-7D; - public static final double BIG_EPSILON = 1.0E-6D; - private static final VoxelShape BLOCK = Util.make(() -> { -- DiscreteVoxelShape discreteVoxelShape = new BitSetDiscreteVoxelShape(1, 1, 1); -- discreteVoxelShape.fill(0, 0, 0); -- return new CubeVoxelShape(discreteVoxelShape); -+ // Paper start - optimise collisions - force arrayvoxelshape -+ final DiscreteVoxelShape shape = new BitSetDiscreteVoxelShape(1, 1, 1); -+ shape.fill(0, 0, 0); -+ -+ return new ArrayVoxelShape( -+ shape, -+ io.papermc.paper.util.CollisionUtil.ZERO_ONE, io.papermc.paper.util.CollisionUtil.ZERO_ONE, io.papermc.paper.util.CollisionUtil.ZERO_ONE -+ ); -+ // Paper end - optimise collisions - force arrayvoxelshape - }); - public static final VoxelShape INFINITY = box(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); - private static final VoxelShape EMPTY = new ArrayVoxelShape(new BitSetDiscreteVoxelShape(0, 0, 0), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D}))); - -+ // Paper start - optimise collisions - force arrayvoxelshape -+ private static final DoubleArrayList[] PARTS_BY_BITS = new DoubleArrayList[] { -+ DoubleArrayList.wrap(generateCubeParts(1 << 0)), -+ DoubleArrayList.wrap(generateCubeParts(1 << 1)), -+ DoubleArrayList.wrap(generateCubeParts(1 << 2)), -+ DoubleArrayList.wrap(generateCubeParts(1 << 3)) -+ }; -+ -+ private static double[] generateCubeParts(final int parts) { -+ // note: parts is a power of two, so we do not need to worry about loss of precision here -+ // note: parts is from [2^0, 2^3] -+ final double inc = 1.0 / (double)parts; -+ -+ final double[] ret = new double[parts + 1]; -+ double val = 0.0; -+ for (int i = 0; i <= parts; ++i) { -+ ret[i] = val; -+ val += inc; -+ } -+ -+ return ret; -+ } -+ // Paper end - optimise collisions - force arrayvoxelshape -+ - public static VoxelShape empty() { - return EMPTY; - } -@@ -41,22 +71,39 @@ public final class Shapes { - - public static VoxelShape create(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { - if (!(maxX - minX < 1.0E-7D) && !(maxY - minY < 1.0E-7D) && !(maxZ - minZ < 1.0E-7D)) { -- int i = findBits(minX, maxX); -- int j = findBits(minY, maxY); -- int k = findBits(minZ, maxZ); -- if (i >= 0 && j >= 0 && k >= 0) { -- if (i == 0 && j == 0 && k == 0) { -- return block(); -+ // Paper start - optimise collisions -+ // force ArrayVoxelShape in every case -+ final int bitsX = findBits(minX, maxX); -+ final int bitsY = findBits(minY, maxY); -+ final int bitsZ = findBits(minZ, maxZ); -+ if (bitsX >= 0 && bitsY >= 0 && bitsZ >= 0) { -+ if (bitsX == 0 && bitsY == 0 && bitsZ == 0) { -+ return BLOCK; - } else { -- int l = 1 << i; -- int m = 1 << j; -- int n = 1 << k; -- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.withFilledBounds(l, m, n, (int)Math.round(minX * (double)l), (int)Math.round(minY * (double)m), (int)Math.round(minZ * (double)n), (int)Math.round(maxX * (double)l), (int)Math.round(maxY * (double)m), (int)Math.round(maxZ * (double)n)); -- return new CubeVoxelShape(bitSetDiscreteVoxelShape); -+ final int sizeX = 1 << bitsX; -+ final int sizeY = 1 << bitsY; -+ final int sizeZ = 1 << bitsZ; -+ final BitSetDiscreteVoxelShape shape = BitSetDiscreteVoxelShape.withFilledBounds( -+ sizeX, sizeY, sizeZ, -+ (int)Math.round(minX * (double)sizeX), (int)Math.round(minY * (double)sizeY), (int)Math.round(minZ * (double)sizeZ), -+ (int)Math.round(maxX * (double)sizeX), (int)Math.round(maxY * (double)sizeY), (int)Math.round(maxZ * (double)sizeZ) -+ ); -+ return new ArrayVoxelShape( -+ shape, -+ PARTS_BY_BITS[bitsX], -+ PARTS_BY_BITS[bitsY], -+ PARTS_BY_BITS[bitsZ] -+ ); - } - } else { -- return new ArrayVoxelShape(BLOCK.shape, (DoubleList)DoubleArrayList.wrap(new double[]{minX, maxX}), (DoubleList)DoubleArrayList.wrap(new double[]{minY, maxY}), (DoubleList)DoubleArrayList.wrap(new double[]{minZ, maxZ})); -+ return new ArrayVoxelShape( -+ BLOCK.shape, -+ minX == 0.0 && maxX == 1.0 ? io.papermc.paper.util.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minX, maxX }), -+ minY == 0.0 && maxY == 1.0 ? io.papermc.paper.util.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minY, maxY }), -+ minZ == 0.0 && maxZ == 1.0 ? io.papermc.paper.util.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minZ, maxZ }) -+ ); - } -+ // Paper end - optimise collisions - } else { - return empty(); - } -@@ -95,67 +142,53 @@ public final class Shapes { - } - - public static VoxelShape or(VoxelShape first, VoxelShape... others) { -- return Arrays.stream(others).reduce(first, Shapes::or); -+ // Paper start - optimise collisions -+ int size = others.length; -+ if (size == 0) { -+ return first; -+ } -+ -+ // reduce complexity of joins by splitting the merges -+ -+ // add extra slot for first shape -+ ++size; -+ final VoxelShape[] tmp = Arrays.copyOf(others, size); -+ // insert first shape -+ tmp[size - 1] = first; -+ -+ while (size > 1) { -+ int newSize = 0; -+ for (int i = 0; i < size; i += 2) { -+ final int next = i + 1; -+ if (next >= size) { -+ // nothing to merge with, so leave it for next iteration -+ tmp[newSize++] = tmp[i]; -+ break; -+ } else { -+ // merge with adjacent -+ final VoxelShape one = tmp[i]; -+ final VoxelShape second = tmp[next]; -+ -+ tmp[newSize++] = Shapes.or(one, second); -+ } -+ } -+ size = newSize; -+ } -+ -+ return tmp[0]; -+ // Paper end - optimise collisions - } - - public static VoxelShape join(VoxelShape first, VoxelShape second, BooleanOp function) { -- return joinUnoptimized(first, second, function).optimize(); -+ return io.papermc.paper.util.CollisionUtil.joinOptimized(first, second, function); // Paper - optimise collisions - } - - public static VoxelShape joinUnoptimized(VoxelShape one, VoxelShape two, BooleanOp function) { -- if (function.apply(false, false)) { -- throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException()); -- } else if (one == two) { -- return function.apply(true, true) ? one : empty(); -- } else { -- boolean bl = function.apply(true, false); -- boolean bl2 = function.apply(false, true); -- if (one.isEmpty()) { -- return bl2 ? two : empty(); -- } else if (two.isEmpty()) { -- return bl ? one : empty(); -- } else { -- IndexMerger indexMerger = createIndexMerger(1, one.getCoords(Direction.Axis.X), two.getCoords(Direction.Axis.X), bl, bl2); -- IndexMerger indexMerger2 = createIndexMerger(indexMerger.size() - 1, one.getCoords(Direction.Axis.Y), two.getCoords(Direction.Axis.Y), bl, bl2); -- IndexMerger indexMerger3 = createIndexMerger((indexMerger.size() - 1) * (indexMerger2.size() - 1), one.getCoords(Direction.Axis.Z), two.getCoords(Direction.Axis.Z), bl, bl2); -- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.join(one.shape, two.shape, indexMerger, indexMerger2, indexMerger3, function); -- return (VoxelShape)(indexMerger instanceof DiscreteCubeMerger && indexMerger2 instanceof DiscreteCubeMerger && indexMerger3 instanceof DiscreteCubeMerger ? new CubeVoxelShape(bitSetDiscreteVoxelShape) : new ArrayVoxelShape(bitSetDiscreteVoxelShape, indexMerger.getList(), indexMerger2.getList(), indexMerger3.getList())); -- } -- } -+ return io.papermc.paper.util.CollisionUtil.joinUnoptimized(one, two, function); // Paper - optimise collisions - } - - public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) { -- if (predicate.apply(false, false)) { -- throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException()); -- } else { -- boolean bl = shape1.isEmpty(); -- boolean bl2 = shape2.isEmpty(); -- if (!bl && !bl2) { -- if (shape1 == shape2) { -- return predicate.apply(true, true); -- } else { -- boolean bl3 = predicate.apply(true, false); -- boolean bl4 = predicate.apply(false, true); -- -- for(Direction.Axis axis : AxisCycle.AXIS_VALUES) { -- if (shape1.max(axis) < shape2.min(axis) - 1.0E-7D) { -- return bl3 || bl4; -- } -- -- if (shape2.max(axis) < shape1.min(axis) - 1.0E-7D) { -- return bl3 || bl4; -- } -- } -- -- IndexMerger indexMerger = createIndexMerger(1, shape1.getCoords(Direction.Axis.X), shape2.getCoords(Direction.Axis.X), bl3, bl4); -- IndexMerger indexMerger2 = createIndexMerger(indexMerger.size() - 1, shape1.getCoords(Direction.Axis.Y), shape2.getCoords(Direction.Axis.Y), bl3, bl4); -- IndexMerger indexMerger3 = createIndexMerger((indexMerger.size() - 1) * (indexMerger2.size() - 1), shape1.getCoords(Direction.Axis.Z), shape2.getCoords(Direction.Axis.Z), bl3, bl4); -- return joinIsNotEmpty(indexMerger, indexMerger2, indexMerger3, shape1.shape, shape2.shape, predicate); -- } -- } else { -- return predicate.apply(!bl, !bl2); -- } -- } -+ return io.papermc.paper.util.CollisionUtil.isJoinNonEmpty(shape1, shape2, predicate); // Paper - optimise collisions - } - - private static boolean joinIsNotEmpty(IndexMerger mergedX, IndexMerger mergedY, IndexMerger mergedZ, DiscreteVoxelShape shape1, DiscreteVoxelShape shape2, BooleanOp predicate) { -@@ -181,69 +214,119 @@ public final class Shapes { - } - - public static boolean blockOccudes(VoxelShape shape, VoxelShape neighbor, Direction direction) { -- if (shape == block() && neighbor == block()) { -+ // Paper start - optimise collisions -+ final boolean firstBlock = shape == BLOCK; -+ final boolean secondBlock = neighbor == BLOCK; -+ -+ if (firstBlock & secondBlock) { - return true; -- } else if (neighbor.isEmpty()) { -+ } -+ -+ if (shape.isEmpty() | neighbor.isEmpty()) { - return false; -- } else { -- Direction.Axis axis = direction.getAxis(); -- Direction.AxisDirection axisDirection = direction.getAxisDirection(); -- VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? shape : neighbor; -- VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? neighbor : shape; -- BooleanOp booleanOp = axisDirection == Direction.AxisDirection.POSITIVE ? BooleanOp.ONLY_FIRST : BooleanOp.ONLY_SECOND; -- return DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0D, 1.0E-7D) && DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0D, 1.0E-7D) && !joinIsNotEmpty(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), booleanOp); - } -+ -+ // we optimise getOpposite, so we can use it -+ // secondly, use our cache to retrieve sliced shape -+ final VoxelShape newFirst = shape.getFaceShapeClamped(direction); -+ if (newFirst.isEmpty()) { -+ return false; -+ } -+ final VoxelShape newSecond = neighbor.getFaceShapeClamped(direction.getOpposite()); -+ if (newSecond.isEmpty()) { -+ return false; -+ } -+ -+ return !joinIsNotEmpty(newFirst, newSecond, BooleanOp.ONLY_FIRST); -+ // Paper end - optimise collisions - } - - public static VoxelShape getFaceShape(VoxelShape shape, Direction direction) { -- if (shape == block()) { -- return block(); -- } else { -- Direction.Axis axis = direction.getAxis(); -- boolean bl; -- int i; -- if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) { -- bl = DoubleMath.fuzzyEquals(shape.max(axis), 1.0D, 1.0E-7D); -- i = shape.shape.getSize(axis) - 1; -- } else { -- bl = DoubleMath.fuzzyEquals(shape.min(axis), 0.0D, 1.0E-7D); -- i = 0; -- } -+ return shape.getFaceShapeClamped(direction); // Paper - optimise collisions -+ } - -- return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i)); -- } -+ // Paper start - optimise collisions -+ private static boolean mergedMayOccludeBlock(final VoxelShape shape1, final VoxelShape shape2) { -+ // if the combined bounds of the two shapes cannot occlude, then neither can the merged -+ final AABB bounds1 = shape1.bounds(); -+ final AABB bounds2 = shape2.bounds(); -+ -+ final double minX = Math.min(bounds1.minX, bounds2.minX); -+ final double minY = Math.min(bounds1.minY, bounds2.minY); -+ final double minZ = Math.min(bounds1.minZ, bounds2.minZ); -+ -+ final double maxX = Math.max(bounds1.maxX, bounds2.maxX); -+ final double maxY = Math.max(bounds1.maxY, bounds2.maxY); -+ final double maxZ = Math.max(bounds1.maxZ, bounds2.maxZ); -+ -+ return (minX <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && maxX >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) && -+ (minY <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && maxY >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) && -+ (minZ <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && maxZ >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)); - } -+ // Paper end - optimise collisions - - public static boolean mergedFaceOccludes(VoxelShape one, VoxelShape two, Direction direction) { -- if (one != block() && two != block()) { -- Direction.Axis axis = direction.getAxis(); -- Direction.AxisDirection axisDirection = direction.getAxisDirection(); -- VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? one : two; -- VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? two : one; -- if (!DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0D, 1.0E-7D)) { -- voxelShape = empty(); -- } -+ // Paper start - optimise collisions -+ // see if any of the shapes on their own occludes, only if cached -+ if (one.occludesFullBlockIfCached() || two.occludesFullBlockIfCached()) { -+ return true; -+ } - -- if (!DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0D, 1.0E-7D)) { -- voxelShape2 = empty(); -- } -+ if (one.isEmpty() & two.isEmpty()) { -+ return false; -+ } - -- return !joinIsNotEmpty(block(), joinUnoptimized(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), BooleanOp.OR), BooleanOp.ONLY_FIRST); -- } else { -+ // we optimise getOpposite, so we can use it -+ // secondly, use our cache to retrieve sliced shape -+ final VoxelShape newFirst = one.getFaceShapeClamped(direction); -+ final VoxelShape newSecond = two.getFaceShapeClamped(direction.getOpposite()); -+ -+ // see if any of the shapes on their own occludes, only if cached -+ if (newFirst.occludesFullBlockIfCached() || newSecond.occludesFullBlockIfCached()) { - return true; - } -+ -+ final boolean firstEmpty = newFirst.isEmpty(); -+ final boolean secondEmpty = newSecond.isEmpty(); -+ -+ if (firstEmpty & secondEmpty) { -+ return false; -+ } -+ -+ if (firstEmpty | secondEmpty) { -+ return secondEmpty ? newFirst.occludesFullBlock() : newSecond.occludesFullBlock(); -+ } -+ -+ if (newFirst == newSecond) { -+ return newFirst.occludesFullBlock(); -+ } -+ -+ return mergedMayOccludeBlock(newFirst, newSecond) && newFirst.orUnoptimized(newSecond).occludesFullBlock(); -+ // Paper end - optimise collisions - } - - public static boolean faceShapeOccludes(VoxelShape one, VoxelShape two) { -- if (one != block() && two != block()) { -- if (one.isEmpty() && two.isEmpty()) { -- return false; -- } else { -- return !joinIsNotEmpty(block(), joinUnoptimized(one, two, BooleanOp.OR), BooleanOp.ONLY_FIRST); -- } -- } else { -+ // Paper start - optimise collisions -+ if (one.occludesFullBlockIfCached() || two.occludesFullBlockIfCached()) { - return true; - } -+ -+ final boolean s1Empty = one.isEmpty(); -+ final boolean s2Empty = two.isEmpty(); -+ if (s1Empty & s2Empty) { -+ return false; -+ } -+ -+ if (s1Empty | s2Empty) { -+ return s2Empty ? one.occludesFullBlock() : two.occludesFullBlock(); -+ } -+ -+ if (one == two) { -+ return one.occludesFullBlock(); -+ } -+ -+ return mergedMayOccludeBlock(one, two) && (one.orUnoptimized(two)).occludesFullBlock(); -+ // Paper end - optimise collisions - } - - @VisibleForTesting -diff --git a/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java b/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java -index cf469f9daa81da8bc330c9cac7e813db87f9f9af..d9256710e815a5cb55409a80d59df2029b98c0d7 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java -@@ -12,6 +12,7 @@ public class SliceShape extends VoxelShape { - super(makeSlice(shape.shape, axis, sliceWidth)); - this.delegate = shape; - this.axis = axis; -+ this.initCache(); // Paper - optimise collisions - } - - private static DiscreteVoxelShape makeSlice(DiscreteVoxelShape voxelSet, Direction.Axis axis, int sliceWidth) { -diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java -index 15e2dfa9a17b4f19768c0cde0ad8031f0122cd33..6bd6385ad82481a099f3556ed2dbd3744888fc34 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java -@@ -16,30 +16,438 @@ import net.minecraft.world.phys.BlockHitResult; - import net.minecraft.world.phys.Vec3; - - public abstract class VoxelShape { -- protected final DiscreteVoxelShape shape; -+ public final DiscreteVoxelShape shape; // Paper - optimise collisions - public - @Nullable - private VoxelShape[] faces; - -- VoxelShape(DiscreteVoxelShape voxels) { -+ // Paper start - optimise collisions -+ private double offsetX; -+ private double offsetY; -+ private double offsetZ; -+ private AABB singleAABBRepresentation; -+ private double[] rootCoordinatesX; -+ private double[] rootCoordinatesY; -+ private double[] rootCoordinatesZ; -+ -+ private io.papermc.paper.util.collisions.CachedShapeData cachedShapeData; -+ private boolean isEmpty; -+ -+ private io.papermc.paper.util.collisions.CachedToAABBs cachedToAABBs; -+ private AABB cachedBounds; -+ -+ private Boolean isFullBlock; -+ -+ private Boolean occludesFullBlock; -+ -+ // must be power of two -+ private static final int MERGED_CACHE_SIZE = 16; -+ -+ private io.papermc.paper.util.collisions.MergedORCache[] mergedORCache; -+ -+ public final double offsetX() { -+ return this.offsetX; -+ } -+ -+ public final double offsetY() { -+ return this.offsetY; -+ } -+ -+ public final double offsetZ() { -+ return this.offsetZ; -+ } -+ -+ public final AABB getSingleAABBRepresentation() { -+ return this.singleAABBRepresentation; -+ } -+ -+ public final double[] rootCoordinatesX() { -+ return this.rootCoordinatesX; -+ } -+ -+ public final double[] rootCoordinatesY() { -+ return this.rootCoordinatesY; -+ } -+ -+ public final double[] rootCoordinatesZ() { -+ return this.rootCoordinatesZ; -+ } -+ -+ private static double[] extractRawArray(final DoubleList list) { -+ if (list instanceof it.unimi.dsi.fastutil.doubles.DoubleArrayList rawList) { -+ final double[] raw = rawList.elements(); -+ final int expected = rawList.size(); -+ if (raw.length == expected) { -+ return raw; -+ } else { -+ return java.util.Arrays.copyOf(raw, expected); -+ } -+ } else { -+ return list.toDoubleArray(); -+ } -+ } -+ -+ public final void initCache() { -+ this.cachedShapeData = this.shape.getOrCreateCachedShapeData(); -+ this.isEmpty = this.cachedShapeData.isEmpty(); -+ -+ final DoubleList xList = this.getCoords(Direction.Axis.X); -+ final DoubleList yList = this.getCoords(Direction.Axis.Y); -+ final DoubleList zList = this.getCoords(Direction.Axis.Z); -+ -+ if (xList instanceof OffsetDoubleList offsetDoubleList) { -+ this.offsetX = offsetDoubleList.offset; -+ this.rootCoordinatesX = extractRawArray(offsetDoubleList.delegate); -+ } else { -+ this.rootCoordinatesX = extractRawArray(xList); -+ } -+ -+ if (yList instanceof OffsetDoubleList offsetDoubleList) { -+ this.offsetY = offsetDoubleList.offset; -+ this.rootCoordinatesY = extractRawArray(offsetDoubleList.delegate); -+ } else { -+ this.rootCoordinatesY = extractRawArray(yList); -+ } -+ -+ if (zList instanceof OffsetDoubleList offsetDoubleList) { -+ this.offsetZ = offsetDoubleList.offset; -+ this.rootCoordinatesZ = extractRawArray(offsetDoubleList.delegate); -+ } else { -+ this.rootCoordinatesZ = extractRawArray(zList); -+ } -+ -+ if (this.cachedShapeData.hasSingleAABB()) { -+ this.singleAABBRepresentation = new AABB( -+ this.rootCoordinatesX[0] + this.offsetX, this.rootCoordinatesY[0] + this.offsetY, this.rootCoordinatesZ[0] + this.offsetZ, -+ this.rootCoordinatesX[1] + this.offsetX, this.rootCoordinatesY[1] + this.offsetY, this.rootCoordinatesZ[1] + this.offsetZ -+ ); -+ this.cachedBounds = this.singleAABBRepresentation; -+ } -+ } -+ -+ public final io.papermc.paper.util.collisions.CachedShapeData getCachedVoxelData() { -+ return this.cachedShapeData; -+ } -+ -+ private VoxelShape[] faceShapeClampedCache; -+ -+ public final VoxelShape getFaceShapeClamped(final Direction direction) { -+ if (this.isEmpty) { -+ return (VoxelShape)(Object)this; -+ } -+ if ((VoxelShape)(Object)this == Shapes.block()) { -+ return (VoxelShape)(Object)this; -+ } -+ -+ VoxelShape[] cache = this.faceShapeClampedCache; -+ if (cache != null) { -+ final VoxelShape ret = cache[direction.ordinal()]; -+ if (ret != null) { -+ return ret; -+ } -+ } -+ -+ -+ if (cache == null) { -+ this.faceShapeClampedCache = cache = new VoxelShape[6]; -+ } -+ -+ final Direction.Axis axis = direction.getAxis(); -+ -+ final VoxelShape ret; -+ -+ if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) { -+ if (DoubleMath.fuzzyEquals(this.max(axis), 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) { -+ ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, this.shape.getSize(axis) - 1)); -+ } else { -+ ret = Shapes.empty(); -+ } -+ } else { -+ if (DoubleMath.fuzzyEquals(this.min(axis), 0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) { -+ ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, 0)); -+ } else { -+ ret = Shapes.empty(); -+ } -+ } -+ -+ cache[direction.ordinal()] = ret; -+ -+ return ret; -+ } -+ -+ private static VoxelShape tryForceBlock(final VoxelShape other) { -+ if (other == Shapes.block()) { -+ return other; -+ } -+ -+ final AABB otherAABB = other.getSingleAABBRepresentation(); -+ if (otherAABB == null) { -+ return other; -+ } -+ -+ if (Shapes.block().getSingleAABBRepresentation().equals(otherAABB)) { -+ return Shapes.block(); -+ } -+ -+ return other; -+ } -+ -+ private boolean computeOccludesFullBlock() { -+ if (this.isEmpty) { -+ this.occludesFullBlock = Boolean.FALSE; -+ return false; -+ } -+ -+ if (this.isFullBlock()) { -+ this.occludesFullBlock = Boolean.TRUE; -+ return true; -+ } -+ -+ final AABB singleAABB = this.singleAABBRepresentation; -+ if (singleAABB != null) { -+ // check if the bounding box encloses the full cube -+ final boolean ret = -+ (singleAABB.minY <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && singleAABB.maxY >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) && -+ (singleAABB.minX <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && singleAABB.maxX >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) && -+ (singleAABB.minZ <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && singleAABB.maxZ >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)); -+ this.occludesFullBlock = Boolean.valueOf(ret); -+ return ret; -+ } -+ -+ final boolean ret = !Shapes.joinIsNotEmpty(Shapes.block(), ((VoxelShape)(Object)this), BooleanOp.ONLY_FIRST); -+ this.occludesFullBlock = Boolean.valueOf(ret); -+ return ret; -+ } -+ -+ public final boolean occludesFullBlock() { -+ final Boolean ret = this.occludesFullBlock; -+ if (ret != null) { -+ return ret.booleanValue(); -+ } -+ -+ return this.computeOccludesFullBlock(); -+ } -+ -+ public final boolean occludesFullBlockIfCached() { -+ final Boolean ret = this.occludesFullBlock; -+ return ret != null ? ret.booleanValue() : false; -+ } -+ -+ private static int hash(final VoxelShape key) { -+ return it.unimi.dsi.fastutil.HashCommon.mix(System.identityHashCode(key)); -+ } -+ -+ public final VoxelShape orUnoptimized(final VoxelShape other) { -+ // don't cache simple cases -+ if (((VoxelShape)(Object)this) == other) { -+ return other; -+ } -+ -+ if (this.isEmpty) { -+ return other; -+ } -+ -+ if (other.isEmpty()) { -+ return (VoxelShape)(Object)this; -+ } -+ -+ // try this cache first -+ final int thisCacheKey = hash(other) & (MERGED_CACHE_SIZE - 1); -+ final io.papermc.paper.util.collisions.MergedORCache cached = this.mergedORCache == null ? null : this.mergedORCache[thisCacheKey]; -+ if (cached != null && cached.key() == other) { -+ return cached.result(); -+ } -+ -+ // try other cache -+ final int otherCacheKey = hash(this) & (MERGED_CACHE_SIZE - 1); -+ final io.papermc.paper.util.collisions.MergedORCache otherCache = other.mergedORCache == null ? null : other.mergedORCache[otherCacheKey]; -+ if (otherCache != null && otherCache.key() == this) { -+ return otherCache.result(); -+ } -+ -+ // note: unsure if joinUnoptimized(1, 2, OR) == joinUnoptimized(2, 1, OR) for all cases -+ final VoxelShape result = Shapes.joinUnoptimized(this, other, BooleanOp.OR); -+ -+ if (cached != null && otherCache == null) { -+ // try to use second cache instead of replacing an entry in this cache -+ if (other.mergedORCache == null) { -+ other.mergedORCache = new io.papermc.paper.util.collisions.MergedORCache[MERGED_CACHE_SIZE]; -+ } -+ other.mergedORCache[otherCacheKey] = new io.papermc.paper.util.collisions.MergedORCache(this, result); -+ } else { -+ // line is not occupied or other cache line is full -+ // always bias to replace this cache, as this cache is the first we check -+ if (this.mergedORCache == null) { -+ this.mergedORCache = new io.papermc.paper.util.collisions.MergedORCache[MERGED_CACHE_SIZE]; -+ } -+ this.mergedORCache[thisCacheKey] = new io.papermc.paper.util.collisions.MergedORCache(other, result); -+ } -+ -+ return result; -+ } -+ -+ private boolean computeFullBlock() { -+ Boolean ret; -+ if (this.isEmpty) { -+ ret = Boolean.FALSE; -+ } else if ((VoxelShape)(Object)this == Shapes.block()) { -+ ret = Boolean.TRUE; -+ } else { -+ final AABB singleAABB = this.singleAABBRepresentation; -+ if (singleAABB == null) { -+ final io.papermc.paper.util.collisions.CachedShapeData shapeData = this.cachedShapeData; -+ final int sMinX = shapeData.minFullX(); -+ final int sMinY = shapeData.minFullY(); -+ final int sMinZ = shapeData.minFullZ(); -+ -+ final int sMaxX = shapeData.maxFullX(); -+ final int sMaxY = shapeData.maxFullY(); -+ final int sMaxZ = shapeData.maxFullZ(); -+ -+ if (Math.abs(this.rootCoordinatesX[sMinX] + this.offsetX) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && -+ Math.abs(this.rootCoordinatesY[sMinY] + this.offsetY) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && -+ Math.abs(this.rootCoordinatesZ[sMinZ] + this.offsetZ) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && -+ -+ Math.abs(1.0 - (this.rootCoordinatesX[sMaxX] + this.offsetX)) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && -+ Math.abs(1.0 - (this.rootCoordinatesY[sMaxY] + this.offsetY)) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && -+ Math.abs(1.0 - (this.rootCoordinatesZ[sMaxZ] + this.offsetZ)) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) { -+ -+ // index = z + y*sizeZ + x*(sizeZ*sizeY) -+ -+ final int sizeY = shapeData.sizeY(); -+ final int sizeZ = shapeData.sizeZ(); -+ -+ final long[] bitset = shapeData.voxelSet(); -+ -+ ret = Boolean.TRUE; -+ -+ check_full: -+ for (int x = sMinX; x < sMaxX; ++x) { -+ for (int y = sMinY; y < sMaxY; ++y) { -+ final int baseIndex = y*sizeZ + x*(sizeZ*sizeY); -+ if (!io.papermc.paper.util.collisions.FlatBitsetUtil.isRangeSet(bitset, baseIndex + sMinZ, baseIndex + sMaxZ)) { -+ ret = Boolean.FALSE; -+ break check_full; -+ } -+ } -+ } -+ } else { -+ ret = Boolean.FALSE; -+ } -+ } else { -+ ret = Boolean.valueOf( -+ Math.abs(singleAABB.minX) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && -+ Math.abs(singleAABB.minY) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && -+ Math.abs(singleAABB.minZ) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && -+ -+ Math.abs(1.0 - singleAABB.maxX) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && -+ Math.abs(1.0 - singleAABB.maxY) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && -+ Math.abs(1.0 - singleAABB.maxZ) <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON -+ ); -+ } -+ } -+ -+ this.isFullBlock = ret; -+ -+ return ret.booleanValue(); -+ } -+ -+ public boolean isFullBlock() { -+ final Boolean ret = this.isFullBlock; -+ -+ if (ret != null) { -+ return ret.booleanValue(); -+ } -+ -+ return this.computeFullBlock(); -+ } -+ // Paper end - optimise collisions -+ -+ protected VoxelShape(DiscreteVoxelShape voxels) { // Paper - protected - this.shape = voxels; - } - - public double min(Direction.Axis axis) { -- int i = this.shape.firstFull(axis); -- return i >= this.shape.getSize(axis) ? Double.POSITIVE_INFINITY : this.get(axis, i); -+ // Paper start - optimise collisions -+ final io.papermc.paper.util.collisions.CachedShapeData shapeData = this.cachedShapeData; -+ switch (axis) { -+ case X: { -+ final int idx = shapeData.minFullX(); -+ return idx >= shapeData.sizeX() ? Double.POSITIVE_INFINITY : (this.rootCoordinatesX[idx] + this.offsetX); -+ } -+ case Y: { -+ final int idx = shapeData.minFullY(); -+ return idx >= shapeData.sizeY() ? Double.POSITIVE_INFINITY : (this.rootCoordinatesY[idx] + this.offsetY); -+ } -+ case Z: { -+ final int idx = shapeData.minFullZ(); -+ return idx >= shapeData.sizeZ() ? Double.POSITIVE_INFINITY : (this.rootCoordinatesZ[idx] + this.offsetZ); -+ } -+ default: { -+ // should never get here -+ return Double.POSITIVE_INFINITY; -+ } -+ } -+ // Paper end - optimise collisions - } - - public double max(Direction.Axis axis) { -- int i = this.shape.lastFull(axis); -- return i <= 0 ? Double.NEGATIVE_INFINITY : this.get(axis, i); -+ // Paper start - optimise collisions -+ final io.papermc.paper.util.collisions.CachedShapeData shapeData = this.cachedShapeData; -+ switch (axis) { -+ case X: { -+ final int idx = shapeData.maxFullX(); -+ return idx <= 0 ? Double.NEGATIVE_INFINITY : (this.rootCoordinatesX[idx] + this.offsetX); -+ } -+ case Y: { -+ final int idx = shapeData.maxFullY(); -+ return idx <= 0 ? Double.NEGATIVE_INFINITY : (this.rootCoordinatesY[idx] + this.offsetY); -+ } -+ case Z: { -+ final int idx = shapeData.maxFullZ(); -+ return idx <= 0 ? Double.NEGATIVE_INFINITY : (this.rootCoordinatesZ[idx] + this.offsetZ); -+ } -+ default: { -+ // should never get here -+ return Double.NEGATIVE_INFINITY; -+ } -+ } -+ // Paper end - optimise collisions - } - - public AABB bounds() { -- if (this.isEmpty()) { -- throw (UnsupportedOperationException)Util.pauseInIde(new UnsupportedOperationException("No bounds for empty shape.")); -- } else { -- return new AABB(this.min(Direction.Axis.X), this.min(Direction.Axis.Y), this.min(Direction.Axis.Z), this.max(Direction.Axis.X), this.max(Direction.Axis.Y), this.max(Direction.Axis.Z)); -+ // Paper start - optimise collisions -+ if (this.isEmpty) { -+ throw Util.pauseInIde(new UnsupportedOperationException("No bounds for empty shape.")); - } -+ AABB cached = this.cachedBounds; -+ if (cached != null) { -+ return cached; -+ } -+ -+ final io.papermc.paper.util.collisions.CachedShapeData shapeData = this.cachedShapeData; -+ -+ final double[] coordsX = this.rootCoordinatesX; -+ final double[] coordsY = this.rootCoordinatesY; -+ final double[] coordsZ = this.rootCoordinatesZ; -+ -+ final double offX = this.offsetX; -+ final double offY = this.offsetY; -+ final double offZ = this.offsetZ; -+ -+ // note: if not empty, then there is one full AABB so no bounds checks are needed on the minFull/maxFull indices -+ cached = new AABB( -+ coordsX[shapeData.minFullX()] + offX, -+ coordsY[shapeData.minFullY()] + offY, -+ coordsZ[shapeData.minFullZ()] + offZ, -+ -+ coordsX[shapeData.maxFullX()] + offX, -+ coordsY[shapeData.maxFullY()] + offY, -+ coordsZ[shapeData.maxFullZ()] + offZ -+ ); -+ -+ this.cachedBounds = cached; -+ return cached; -+ // Paper end - optimise collisions - } - - public VoxelShape singleEncompassing() { -@@ -53,19 +461,106 @@ public abstract class VoxelShape { - protected abstract DoubleList getCoords(Direction.Axis axis); - - public boolean isEmpty() { -- return this.shape.isEmpty(); -+ return this.isEmpty; // Paper - optimise collisions - } - -+ // Paper start - optimise collisions -+ private static DoubleList offsetList(final DoubleList src, final double by) { -+ if (src instanceof OffsetDoubleList offsetDoubleList) { -+ return new OffsetDoubleList(offsetDoubleList.delegate, by + offsetDoubleList.offset); -+ } -+ return new OffsetDoubleList(src, by); -+ } -+ // Paper end - optimise collisions -+ - public VoxelShape move(double x, double y, double z) { -- return (VoxelShape)(this.isEmpty() ? Shapes.empty() : new ArrayVoxelShape(this.shape, (DoubleList)(new OffsetDoubleList(this.getCoords(Direction.Axis.X), x)), (DoubleList)(new OffsetDoubleList(this.getCoords(Direction.Axis.Y), y)), (DoubleList)(new OffsetDoubleList(this.getCoords(Direction.Axis.Z), z)))); -+ // Paper start - optimise collisions -+ if (this.isEmpty) { -+ return Shapes.empty(); -+ } -+ -+ final ArrayVoxelShape ret = new ArrayVoxelShape( -+ this.shape, -+ offsetList(this.getCoords(Direction.Axis.X), x), -+ offsetList(this.getCoords(Direction.Axis.Y), y), -+ offsetList(this.getCoords(Direction.Axis.Z), z) -+ ); -+ -+ final io.papermc.paper.util.collisions.CachedToAABBs cachedToAABBs = this.cachedToAABBs; -+ if (cachedToAABBs != null) { -+ ((VoxelShape)ret).cachedToAABBs = io.papermc.paper.util.collisions.CachedToAABBs.offset(cachedToAABBs, x, y, z); -+ } -+ -+ return ret; -+ // Paper end - optimise collisions - } - - public VoxelShape optimize() { -- VoxelShape[] voxelShapes = new VoxelShape[]{Shapes.empty()}; -- this.forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> { -- voxelShapes[0] = Shapes.joinUnoptimized(voxelShapes[0], Shapes.box(minX, minY, minZ, maxX, maxY, maxZ), BooleanOp.OR); -- }); -- return voxelShapes[0]; -+ // Paper start - optimise collisions -+ // Optimise merge strategy to increase the number of simple joins, and additionally forward the toAabbs cache -+ // to result -+ if (this.isEmpty) { -+ return Shapes.empty(); -+ } -+ -+ if (this.singleAABBRepresentation != null) { -+ // note: the isFullBlock() is fuzzy, and Shapes.create() is also fuzzy which would return block() -+ return this.isFullBlock() ? Shapes.block() : this; -+ } -+ -+ final List aabbs = this.toAabbs(); -+ -+ if (aabbs.size() == 1) { -+ final AABB singleAABB = aabbs.get(0); -+ final VoxelShape ret = Shapes.create(singleAABB); -+ -+ // forward AABB cache -+ if (ret.cachedToAABBs == null) { -+ ret.cachedToAABBs = this.cachedToAABBs; -+ } -+ -+ return ret; -+ } else { -+ // reduce complexity of joins by splitting the merges (old complexity: n^2, new: nlogn) -+ -+ // set up flat array so that this merge is done in-place -+ final VoxelShape[] tmp = new VoxelShape[aabbs.size()]; -+ -+ // initialise as unmerged -+ for (int i = 0, len = aabbs.size(); i < len; ++i) { -+ tmp[i] = Shapes.create(aabbs.get(i)); -+ } -+ -+ int size = aabbs.size(); -+ while (size > 1) { -+ int newSize = 0; -+ for (int i = 0; i < size; i += 2) { -+ final int next = i + 1; -+ if (next >= size) { -+ // nothing to merge with, so leave it for next iteration -+ tmp[newSize++] = tmp[i]; -+ break; -+ } else { -+ // merge with adjacent -+ final VoxelShape first = tmp[i]; -+ final VoxelShape second = tmp[next]; -+ -+ tmp[newSize++] = Shapes.joinUnoptimized(first, second, BooleanOp.OR); -+ } -+ } -+ size = newSize; -+ } -+ -+ final VoxelShape ret = tmp[0]; -+ -+ // forward AABB cache -+ if (ret.cachedToAABBs == null) { -+ ret.cachedToAABBs = this.cachedToAABBs; -+ } -+ -+ return ret; -+ } -+ // Paper end - optimise collisions - } - - public void forAllEdges(Shapes.DoubleLineConsumer consumer) { -@@ -83,12 +578,43 @@ public abstract class VoxelShape { - }, true); - } - -+ // Paper start - optimise collisions -+ private List toAabbsUncached() { -+ final List ret = new java.util.ArrayList<>(); -+ if (this.singleAABBRepresentation != null) { -+ ret.add(this.singleAABBRepresentation); -+ } else { -+ this.forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> { -+ ret.add(new AABB(minX, minY, minZ, maxX, maxY, maxZ)); -+ }); -+ } -+ -+ // cache result -+ this.cachedToAABBs = new io.papermc.paper.util.collisions.CachedToAABBs(ret, false, 0.0, 0.0, 0.0); -+ -+ return ret; -+ } -+ // Paper end - optimise collisions -+ - public List toAabbs() { -- List list = Lists.newArrayList(); -- this.forAllBoxes((x1, y1, z1, x2, y2, z2) -> { -- list.add(new AABB(x1, y1, z1, x2, y2, z2)); -- }); -- return list; -+ // Paper start - optimise collisions -+ io.papermc.paper.util.collisions.CachedToAABBs cachedToAABBs = this.cachedToAABBs; -+ if (cachedToAABBs != null) { -+ if (!cachedToAABBs.isOffset()) { -+ return cachedToAABBs.aabbs(); -+ } -+ -+ // all we need to do is offset the cache -+ cachedToAABBs = cachedToAABBs.removeOffset(); -+ // update cache -+ this.cachedToAABBs = cachedToAABBs; -+ -+ return cachedToAABBs.aabbs(); -+ } -+ -+ // make new cache -+ return this.toAabbsUncached(); -+ // Paper end - optimise collisions - } - - public double min(Direction.Axis axis, double from, double to) { -@@ -115,37 +641,85 @@ public abstract class VoxelShape { - }) - 1; - } - -+ // Paper start - optimise collisions -+ /** -+ * Copy of AABB#clip but for one AABB -+ */ -+ private static BlockHitResult clip(final AABB aabb, final Vec3 from, final Vec3 to, final BlockPos offset) { -+ final double[] minDistanceArr = new double[] { 1.0 }; -+ final double diffX = to.x - from.x; -+ final double diffY = to.y - from.y; -+ final double diffZ = to.z - from.z; -+ -+ final Direction direction = AABB.getDirection(aabb.move(offset), from, minDistanceArr, null, diffX, diffY, diffZ); -+ -+ if (direction == null) { -+ return null; -+ } -+ -+ final double minDistance = minDistanceArr[0]; -+ return new BlockHitResult(from.add(minDistance * diffX, minDistance * diffY, minDistance * diffZ), direction, offset, false); -+ } -+ // Paper end - optimise collisions -+ - @Nullable - public BlockHitResult clip(Vec3 start, Vec3 end, BlockPos pos) { -- if (this.isEmpty()) { -+ // Paper start - optimise collisions -+ if (this.isEmpty) { - return null; -- } else { -- Vec3 vec3 = end.subtract(start); -- if (vec3.lengthSqr() < 1.0E-7D) { -- return null; -- } else { -- Vec3 vec32 = start.add(vec3.scale(0.001D)); -- return this.shape.isFullWide(this.findIndex(Direction.Axis.X, vec32.x - (double)pos.getX()), this.findIndex(Direction.Axis.Y, vec32.y - (double)pos.getY()), this.findIndex(Direction.Axis.Z, vec32.z - (double)pos.getZ())) ? new BlockHitResult(vec32, Direction.getNearest(vec3.x, vec3.y, vec3.z).getOpposite(), pos, true) : AABB.clip(this.toAabbs(), start, end, pos); -+ } -+ -+ final Vec3 directionOpposite = end.subtract(start); -+ if (directionOpposite.lengthSqr() < io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) { -+ return null; -+ } -+ -+ final Vec3 fromBehind = start.add(directionOpposite.scale(0.001)); -+ final double fromBehindOffsetX = fromBehind.x - (double)pos.getX(); -+ final double fromBehindOffsetY = fromBehind.y - (double)pos.getY(); -+ final double fromBehindOffsetZ = fromBehind.z - (double)pos.getZ(); -+ -+ final AABB singleAABB = this.singleAABBRepresentation; -+ if (singleAABB != null) { -+ if (singleAABB.contains(fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) { -+ return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), pos, true); - } -+ return clip(singleAABB, start, end, pos); - } -+ -+ if (io.papermc.paper.util.CollisionUtil.strictlyContains(this, fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) { -+ return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), pos, true); -+ } -+ -+ return AABB.clip(this.toAabbs(), start, end, pos); -+ // Paper end - optimise collisions - } - - public Optional closestPointTo(Vec3 target) { -- if (this.isEmpty()) { -+ // Paper start - optimise collisions -+ if (this.isEmpty) { - return Optional.empty(); -- } else { -- Vec3[] vec3s = new Vec3[1]; -- this.forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> { -- double d = Mth.clamp(target.x(), minX, maxX); -- double e = Mth.clamp(target.y(), minY, maxY); -- double f = Mth.clamp(target.z(), minZ, maxZ); -- if (vec3s[0] == null || target.distanceToSqr(d, e, f) < target.distanceToSqr(vec3s[0])) { -- vec3s[0] = new Vec3(d, e, f); -- } -+ } - -- }); -- return Optional.of(vec3s[0]); -+ Vec3 ret = null; -+ double retDistance = Double.MAX_VALUE; -+ -+ final List aabbs = this.toAabbs(); -+ for (int i = 0, len = aabbs.size(); i < len; ++i) { -+ final AABB aabb = aabbs.get(i); -+ final double x = Mth.clamp(target.x, aabb.minX, aabb.maxX); -+ final double y = Mth.clamp(target.y, aabb.minY, aabb.maxY); -+ final double z = Mth.clamp(target.z, aabb.minZ, aabb.maxZ); -+ -+ double dist = target.distanceToSqr(x, y, z); -+ if (dist < retDistance) { -+ ret = new Vec3(x, y, z); -+ retDistance = dist; -+ } - } -+ -+ return Optional.ofNullable(ret); -+ // Paper end - optimise collisions - } - - public VoxelShape getFaceShape(Direction facing) { -@@ -180,7 +754,28 @@ public abstract class VoxelShape { - } - - public double collide(Direction.Axis axis, AABB box, double maxDist) { -- return this.collideX(AxisCycle.between(axis, Direction.Axis.X), box, maxDist); -+ // Paper start - optimise collisions -+ if (this.isEmpty) { -+ return maxDist; -+ } -+ if (Math.abs(maxDist) < io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) { -+ return 0.0; -+ } -+ switch (axis) { -+ case X: { -+ return io.papermc.paper.util.CollisionUtil.collideX(this, box, maxDist); -+ } -+ case Y: { -+ return io.papermc.paper.util.CollisionUtil.collideY(this, box, maxDist); -+ } -+ case Z: { -+ return io.papermc.paper.util.CollisionUtil.collideZ(this, box, maxDist); -+ } -+ default: { -+ throw new RuntimeException("Unknown axis: " + axis); -+ } -+ } -+ // Paper end - optimise collisions - } - - protected double collideX(AxisCycle axisCycle, AABB box, double maxDist) { diff --git a/patches/server/1026-Properly-resend-entities.patch b/patches/server/1026-Properly-resend-entities.patch new file mode 100644 index 000000000000..24ae108e3c77 --- /dev/null +++ b/patches/server/1026-Properly-resend-entities.patch @@ -0,0 +1,216 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Wed, 7 Dec 2022 17:25:19 -0500 +Subject: [PATCH] Properly resend entities + +This resolves some issues which caused entities to not be resent correctly. +Entities that are interacted with need to be resent to the client, so we resend all the entity +data to the player whilst making sure not to clear dirty entries from the tracker. This makes +sure that values will be correctly updated to other players. + +This also adds utilities to aid in further preventing entity desyncs. + +This also also fixes the bug causing cancelling PlayerInteractEvent to cause items to continue +to be used despite being cancelled on the server. + +For example, items being consumed but never finishing, shields being put up, etc. +The underlying issue of this is that the client modifies their synced data values, +and so we have to (forcibly) resend them in order for the client to reset their using item state. + +See: https://github.com/PaperMC/Paper/pull/1896 + +== AT == +public net.minecraft.server.level.ChunkMap$TrackedEntity serverEntity + +diff --git a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java +index 07a362f9e485d0d507f16f1dda1ac84ade07ab27..58b602e550258c1062ee940bc46538dac95d8979 100644 +--- a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java ++++ b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java +@@ -275,14 +275,63 @@ public class SynchedEntityData { + // CraftBukkit start + public void refresh(ServerPlayer to) { + if (!this.isEmpty()) { +- List> list = this.getNonDefaultValues(); ++ List> list = this.packAll(); // Paper - Update EVERYTHING not just not default + + if (list != null) { ++ if (to.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper + to.connection.send(new ClientboundSetEntityDataPacket(this.entity.getId(), list)); ++ } // Paper + } + } + } + // CraftBukkit end ++ // Paper start ++ // We need to pack all as we cannot rely on "non default values" or "dirty" ones. ++ // Because these values can possibly be desynced on the client. ++ @Nullable ++ private List> packAll() { ++ if (this.isEmpty()) { ++ return null; ++ } ++ ++ List> list = new ArrayList<>(); ++ for (DataItem dataItem : this.itemsById.values()) { ++ list.add(dataItem.value()); ++ } ++ ++ return list; ++ } ++ ++ // This method should only be used if the data of an entity could have became desynced ++ // due to interactions on the client. ++ public void resendPossiblyDesyncedEntity(ServerPlayer player) { ++ if (this.entity.tracker == null) { ++ return; ++ } ++ ++ if (player.getBukkitEntity().canSee(entity.getBukkitEntity())) { ++ net.minecraft.server.level.ServerEntity serverEntity = this.entity.tracker.serverEntity; ++ ++ List> list = new ArrayList<>(); ++ serverEntity.sendPairingData(player, list::add); ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundBundlePacket(list)); ++ } ++ } ++ ++ // This method allows you to specifically resend certain data accessor keys to the client ++ public void resendPossiblyDesyncedDataValues(List> keys, ServerPlayer to) { ++ if (!to.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { ++ return; ++ } ++ List> values = new ArrayList<>(keys.size()); ++ for (EntityDataAccessor key : keys) { ++ SynchedEntityData.DataItem synchedValue = this.getItem(key); ++ values.add(synchedValue.value()); ++ } ++ ++ to.connection.send(new ClientboundSetEntityDataPacket(this.entity.getId(), values)); ++ } ++ // Paper end + + public static class DataItem { + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 692a01b52a71e26887ee42cbd5fd64b0a81bfc99..ef3048a4748113538a0ee0af5b526b2cd51d5c29 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -561,6 +561,7 @@ public class ServerPlayerGameMode { + } + // Paper end - extend Player Interact cancellation + player.getBukkitEntity().updateInventory(); // SPIGOT-2867 ++ this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items + enuminteractionresult = (event.useItemInHand() != Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS; + } else if (this.gameModeForPlayer == GameType.SPECTATOR) { + MenuProvider itileinventory = iblockdata.getMenuProvider(world, blockposition); +@@ -604,6 +605,11 @@ public class ServerPlayerGameMode { + + return enuminteractionresult1; + } ++ // Paper start - Properly cancel usable items; Cancel only if cancelled + if the interact result is different from default response ++ else if (this.interactResult && this.interactResult != cancelledItem) { ++ this.player.resyncUsingItem(this.player); ++ } ++ // Paper end - Properly cancel usable items + } + return enuminteractionresult; + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 9a3d38365754cf40d8a18aabd1ad1cf27c576599..2f9c62f2c4c4356a896f7004b77f12a595f9c6dc 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1990,6 +1990,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + if (cancelled) { ++ this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items + this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524 + return; + } +@@ -2704,7 +2705,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a + if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { +- ServerGamePacketListenerImpl.this.send(new ClientboundAddEntityPacket(entity)); ++ entity.getEntityData().resendPossiblyDesyncedEntity(player); // Paper - The entire mob gets deleted, so resend it. + ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); + } + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 4816897a82c569717bf7ea139a55ab3fd931a63e..91feb12732564c90656da487664dbc12e55397fc 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -390,7 +390,7 @@ public abstract class PlayerList { + ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now + // CraftBukkit end + +- player.getEntityData().refresh(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn ++ //player.getEntityData().refresh(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn Paper - THIS IS NOT NEEDED ANYMORE + + this.sendLevelInfo(player, worldserver1); + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 9f7cb7a2780836c4ce5a6971e9f6004a91509490..578141c88eccaf1c8fd830d8166176f9dab8ed04 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3836,6 +3836,11 @@ public abstract class LivingEntity extends Entity implements Attackable { + return ((Byte) this.entityData.get(LivingEntity.DATA_LIVING_ENTITY_FLAGS) & 2) > 0 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND; + } + ++ // Paper start - Properly cancel usable items ++ public void resyncUsingItem(ServerPlayer serverPlayer) { ++ this.getEntityData().resendPossiblyDesyncedDataValues(java.util.List.of(DATA_LIVING_ENTITY_FLAGS), serverPlayer); ++ } ++ // Paper end - Properly cancel usable items + private void updatingUsingItem() { + if (this.isUsingItem()) { + if (ItemStack.isSameItem(this.getItemInHand(this.getUsedItemHand()), this.useItem)) { +diff --git a/src/main/java/net/minecraft/world/entity/animal/Bucketable.java b/src/main/java/net/minecraft/world/entity/animal/Bucketable.java +index 37596c7b65f280be00e8e59ae18bd1aceae21080..eca18540aeb0b0d4098477d73b14c78a7bf9f455 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bucketable.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bucketable.java +@@ -109,8 +109,7 @@ public interface Bucketable { + itemstack1 = CraftItemStack.asNMSCopy(playerBucketFishEvent.getEntityBucket()); + if (playerBucketFishEvent.isCancelled()) { + ((ServerPlayer) player).containerMenu.sendAllDataToRemote(); // We need to update inventory to resync client's bucket +- ((ServerPlayer) player).connection.send(new ClientboundAddEntityPacket(entity)); // We need to play out these packets as the client assumes the fish is gone +- entity.getEntityData().refresh((ServerPlayer) player); // Need to send data such as the display name to client ++ entity.getEntityData().resendPossiblyDesyncedEntity((ServerPlayer) player); // Paper + return Optional.of(InteractionResult.FAIL); + } + entity.playSound(((Bucketable) entity).getPickupSound(), 1.0F, 1.0F); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 44dd60c1f31b578e7630673433f3850f392b7a0d..8698104e3eb98e2cc5da5de87a8f538860c1d91d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -999,7 +999,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return; + } + +- entityTracker.broadcast(this.getHandle().getAddEntityPacket()); ++ // Paper start, resend possibly desynced entity instead of add entity packet ++ for (ServerPlayerConnection playerConnection : entityTracker.seenBy) { ++ this.getHandle().getEntityData().resendPossiblyDesyncedEntity(playerConnection.getPlayer()); ++ } ++ // Paper end + } + + private static PermissibleBase getPermissibleBase() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java +index 0801bcdee8fcff0d388d302387e4f1d587e0a283..2fcd9b836d42e3549a3b6b921c57a4c103146dff 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java +@@ -39,9 +39,11 @@ public class CraftItemFrame extends CraftHanging implements ItemFrame { + protected void update() { + super.update(); + ++ // Paper start, don't mark as dirty as this is handled in super.update() + // mark dirty, so that the client gets updated with item and rotation +- this.getHandle().getEntityData().markDirty(net.minecraft.world.entity.decoration.ItemFrame.DATA_ITEM); +- this.getHandle().getEntityData().markDirty(net.minecraft.world.entity.decoration.ItemFrame.DATA_ROTATION); ++ //this.getHandle().getEntityData().markDirty(net.minecraft.world.entity.decoration.ItemFrame.DATA_ITEM); ++ //this.getHandle().getEntityData().markDirty(net.minecraft.world.entity.decoration.ItemFrame.DATA_ROTATION); ++ // Paper end + + // update redstone + if (!this.getHandle().generation) { diff --git a/patches/server/1027-Optimise-collision-checking-in-player-move-packet-ha.patch b/patches/server/1027-Optimise-collision-checking-in-player-move-packet-ha.patch deleted file mode 100644 index a795c6dc3b72..000000000000 --- a/patches/server/1027-Optimise-collision-checking-in-player-move-packet-ha.patch +++ /dev/null @@ -1,170 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 2 Jul 2020 12:02:43 -0700 -Subject: [PATCH] Optimise collision checking in player move packet handling - -Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision - -CHECK ME - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 9e8b37f446a382204bc9ad61efed913f70a99b90..9a080c64f0478081a3ef5ae601978063ec3756da 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -552,7 +552,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - return; - } - -- boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); -+ AABB oldBox = entity.getBoundingBox(); // Paper - copy from player movement packet - - d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above - d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; // Paper - diff on change, used for checking large move vectors above -@@ -568,6 +568,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - entity.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); -+ boolean didCollide = toX != entity.getX() || toY != entity.getY() || toZ != entity.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be... - double d11 = d7; - - d6 = d3 - entity.getX(); -@@ -581,15 +582,23 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - boolean flag2 = false; - - if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot -- flag2 = true; -+ flag2 = true; // Paper - diff on change, this should be moved wrongly - ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", new Object[]{entity.getName().getString(), this.player.getName().getString(), Math.sqrt(d10)}); - } - - entity.absMoveTo(d3, d4, d5, f, f1); - this.player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit -- boolean flag3 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); - -- if (flag && (flag2 || !flag3)) { -+ // Paper start - optimise out extra getCubes -+ boolean teleportBack = flag2; // violating this is always a fail -+ if (!teleportBack) { -+ // note: only call after setLocation, or else getBoundingBox is wrong -+ AABB newBox = entity.getBoundingBox(); -+ if (didCollide || !oldBox.equals(newBox)) { -+ teleportBack = this.hasNewCollision(worldserver, entity, oldBox, newBox); -+ } // else: no collision at all detected, why do we care? -+ } -+ if (teleportBack) { // Paper end - optimise out extra getCubes - entity.absMoveTo(d0, d1, d2, f, f1); - this.player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit - this.send(new ClientboundMoveVehiclePacket(entity)); -@@ -668,7 +677,32 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - private boolean noBlocksAround(Entity entity) { -- return entity.level().getBlockStates(entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D)).allMatch(BlockBehaviour.BlockStateBase::isAir); -+ // Paper start - stop using streams, this is already a known fixed problem in Entity#move -+ AABB box = entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D); -+ int minX = Mth.floor(box.minX); -+ int minY = Mth.floor(box.minY); -+ int minZ = Mth.floor(box.minZ); -+ int maxX = Mth.floor(box.maxX); -+ int maxY = Mth.floor(box.maxY); -+ int maxZ = Mth.floor(box.maxZ); -+ -+ Level world = entity.level(); -+ BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); -+ -+ for (int y = minY; y <= maxY; ++y) { -+ for (int z = minZ; z <= maxZ; ++z) { -+ for (int x = minX; x <= maxX; ++x) { -+ pos.set(x, y, z); -+ BlockState type = world.getBlockStateIfLoaded(pos); -+ if (type != null && !type.isAir()) { -+ return false; -+ } -+ } -+ } -+ } -+ -+ return true; -+ // Paper end - stop using streams, this is already a known fixed problem in Entity#move - } - - @Override -@@ -1282,7 +1316,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - if (this.awaitingPositionFromClient != null) { -- if (this.tickCount - this.awaitingTeleportTime > 20) { -+ if (false && this.tickCount - this.awaitingTeleportTime > 20) { // Paper - this will greatly screw with clients with > 1000ms RTT - this.awaitingTeleportTime = this.tickCount; - this.teleport(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); - } -@@ -1389,7 +1423,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - } - -- AABB axisalignedbb = this.player.getBoundingBox(); -+ AABB axisalignedbb = this.player.getBoundingBox(); // Paper - diff on change, should be old AABB - - d6 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above - d7 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above -@@ -1431,6 +1465,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - this.player.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); - this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move -+ boolean didCollide = toX != this.player.getX() || toY != this.player.getY() || toZ != this.player.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be... - // Paper start - prevent position desync - if (this.awaitingPositionFromClient != null) { - return; // ... thanks Mojang for letting move calls teleport across dimensions. -@@ -1459,7 +1494,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - } - -- boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2)); -+ // Paper start - optimise out extra getCubes -+ boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && movedWrongly; -+ this.player.absMoveTo(d0, d1, d2, f, f1); // prevent desync by tping to the set position, dropped for unknown reasons by mojang -+ if (!this.player.noPhysics && !this.player.isSleeping() && !teleportBack) { -+ AABB newBox = this.player.getBoundingBox(); -+ if (didCollide || !axisalignedbb.equals(newBox)) { -+ // note: only call after setLocation, or else getBoundingBox is wrong -+ teleportBack = this.hasNewCollision(worldserver, this.player, axisalignedbb, newBox); -+ } // else: no collision at all detected, why do we care? -+ } -+ // Paper end - optimise out extra getCubes - if (teleportBack) { - io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK, - toX, toY, toZ, toYaw, toPitch, false); -@@ -1559,6 +1604,33 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - } - -+ // Paper start - optimise out extra getCubes -+ private boolean hasNewCollision(final ServerLevel world, final Entity entity, final AABB oldBox, final AABB newBox) { -+ final List collisionsBB = new java.util.ArrayList<>(); -+ final List collisionsVoxel = new java.util.ArrayList<>(); -+ io.papermc.paper.util.CollisionUtil.getCollisions( -+ world, entity, newBox, collisionsVoxel, collisionsBB, -+ io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS | io.papermc.paper.util.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, -+ null, null -+ ); -+ -+ for (int i = 0, len = collisionsBB.size(); i < len; ++i) { -+ final AABB box = collisionsBB.get(i); -+ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(box, oldBox)) { -+ return true; -+ } -+ } -+ -+ for (int i = 0, len = collisionsVoxel.size(); i < len; ++i) { -+ final VoxelShape voxel = collisionsVoxel.get(i); -+ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersectNoEmpty(voxel, oldBox)) { -+ return true; -+ } -+ } -+ -+ return false; -+ } -+ // Paper end - optimise out extra getCubes - private boolean isPlayerCollidingWithAnythingNew(LevelReader world, AABB box, double newX, double newY, double newZ) { - AABB axisalignedbb1 = this.player.getBoundingBox().move(newX - this.player.getX(), newY - this.player.getY(), newZ - this.player.getZ()); - Iterable iterable = world.getCollisions(this.player, axisalignedbb1.deflate(9.999999747378752E-6D)); diff --git a/patches/server/1027-Optimize-Hoppers.patch b/patches/server/1027-Optimize-Hoppers.patch new file mode 100644 index 000000000000..74069c3d7c54 --- /dev/null +++ b/patches/server/1027-Optimize-Hoppers.patch @@ -0,0 +1,750 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 27 Apr 2016 22:09:52 -0400 +Subject: [PATCH] Optimize Hoppers + +* Removes unnecessary extra calls to .update() that are very expensive +* Lots of itemstack cloning removed. Only clone if the item is actually moved +* Return true when a plugin cancels inventory move item event instead of false, as false causes pulls to cycle through all items. + However, pushes do not exhibit the same behavior, so this is not something plugins could of been relying on. +* Add option (Default on) to cooldown hoppers when they fail to move an item due to full inventory +* Skip subsequent InventoryMoveItemEvents if a plugin does not use the item after first event fire for an iteration by tracking changes to the event via an internal event implementation. +* Don't check for Entities with Inventories if the block above us is also occluding (not just Inventoried) +* Remove Streams from Item Suck In and restore restore 1.12 AABB checks which is simpler and no voxel allocations (was doing TWO Item Suck ins) + +diff --git a/src/main/java/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java b/src/main/java/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5c42823726e70ce6c9d0121d074315488e8b3f60 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java +@@ -0,0 +1,31 @@ ++package io.papermc.paper.event.inventory; ++ ++import org.bukkit.event.inventory.InventoryMoveItemEvent; ++import org.bukkit.inventory.Inventory; ++import org.bukkit.inventory.ItemStack; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.jetbrains.annotations.NotNull; ++ ++@DefaultQualifier(NonNull.class) ++public class PaperInventoryMoveItemEvent extends InventoryMoveItemEvent { ++ ++ public boolean calledSetItem; ++ public boolean calledGetItem; ++ ++ public PaperInventoryMoveItemEvent(final @NotNull Inventory sourceInventory, final @NotNull ItemStack itemStack, final @NotNull Inventory destinationInventory, final boolean didSourceInitiate) { ++ super(sourceInventory, itemStack, destinationInventory, didSourceInitiate); ++ } ++ ++ @Override ++ public ItemStack getItem() { ++ this.calledGetItem = true; ++ return super.getItem(); ++ } ++ ++ @Override ++ public void setItem(final ItemStack itemStack) { ++ super.setItem(itemStack); ++ this.calledSetItem = true; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index d2526c39c91dd62ad676f04afc45332d774528d1..f12bf36a247a47b8d831536a4fefd2a2ce424494 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1692,6 +1692,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent + worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent ++ net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers + + this.profiler.push(() -> { + return worldserver + " " + worldserver.dimension().location(); +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 06dc04a1fbb91a5a20d662aeee168b6a319551d0..1ad126d992d95062a3db08374db7a927f23a0cac 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -752,10 +752,16 @@ public final class ItemStack { + } + + public ItemStack copy() { +- if (this.isEmpty()) { ++ // Paper start - Perf: Optimize Hoppers ++ return this.copy(false); ++ } ++ ++ public ItemStack copy(boolean originalItem) { ++ if (!originalItem && this.isEmpty()) { ++ // Paper end - Perf: Optimize Hoppers + return ItemStack.EMPTY; + } else { +- ItemStack itemstack = new ItemStack(this.getItem(), this.count); ++ ItemStack itemstack = new ItemStack(originalItem ? this.item : this.getItem(), this.count); // Paper - Perf: Optimize Hoppers + + itemstack.setPopTime(this.getPopTime()); + if (this.tag != null) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index 20201430ee8f28245aa845acb172d0f5d80458ff..9ea74d37cd951e0dc76d20ed8234b5871035566c 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -26,6 +26,7 @@ import co.aikar.timings.MinecraftTimings; // Paper + import co.aikar.timings.Timing; // Paper + + public abstract class BlockEntity { ++ static boolean ignoreTileUpdates; // Paper - Perf: Optimize Hoppers + + public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper + // CraftBukkit start - data containers +@@ -161,6 +162,7 @@ public abstract class BlockEntity { + + public void setChanged() { + if (this.level != null) { ++ if (ignoreTileUpdates) return; // Paper - Perf: Optimize Hoppers + BlockEntity.setChanged(this.level, this.worldPosition, this.blockState); + } + +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 321d30f1c0d3838b9c3d210eedb03aa59e0761d8..a61d7cd2b078fe511ff00344197b6ea11feebfb2 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 +@@ -151,6 +151,43 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + } + ++ // Paper start - Perf: Optimize Hoppers ++ private static final int HOPPER_EMPTY = 0; ++ private static final int HOPPER_HAS_ITEMS = 1; ++ private static final int HOPPER_IS_FULL = 2; ++ ++ private static int getFullState(final HopperBlockEntity tileEntity) { ++ tileEntity.unpackLootTable(null); ++ ++ final List hopperItems = tileEntity.getItems(); ++ ++ boolean empty = true; ++ boolean full = true; ++ ++ for (int i = 0, len = hopperItems.size(); i < len; ++i) { ++ final ItemStack stack = hopperItems.get(i); ++ if (stack.isEmpty()) { ++ full = false; ++ continue; ++ } ++ ++ if (!full) { ++ // can't be full ++ return HOPPER_HAS_ITEMS; ++ } ++ ++ empty = false; ++ ++ if (stack.getCount() != stack.getMaxStackSize()) { ++ // can't be full or empty ++ return HOPPER_HAS_ITEMS; ++ } ++ } ++ ++ return empty ? HOPPER_EMPTY : (full ? HOPPER_IS_FULL : HOPPER_HAS_ITEMS); ++ } ++ // Paper end - Perf: Optimize Hoppers ++ + private static boolean tryMoveItems(Level world, BlockPos pos, BlockState state, HopperBlockEntity blockEntity, BooleanSupplier booleansupplier) { + if (world.isClientSide) { + return false; +@@ -158,11 +195,13 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + if (!blockEntity.isOnCooldown() && (Boolean) state.getValue(HopperBlock.ENABLED)) { + boolean flag = false; + +- if (!blockEntity.isEmpty()) { ++ int fullState = getFullState(blockEntity); // Paper - Perf: Optimize Hoppers ++ ++ if (fullState != HOPPER_EMPTY) { // Paper - Perf: Optimize Hopperss + flag = HopperBlockEntity.ejectItems(world, pos, state, (Container) blockEntity, blockEntity); // CraftBukkit + } + +- if (!blockEntity.inventoryFull()) { ++ if (fullState != HOPPER_IS_FULL || flag) { // Paper - Perf: Optimize Hoppers + flag |= booleansupplier.getAsBoolean(); + } + +@@ -193,6 +232,202 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + return false; + } + ++ // Paper start - Perf: Optimize Hoppers ++ private static boolean skipPullModeEventFire; ++ private static boolean skipPushModeEventFire; ++ public static boolean skipHopperEvents; ++ ++ private static boolean hopperPush(final Level level, final Container destination, final Direction direction, final HopperBlockEntity hopper) { ++ skipPushModeEventFire = skipHopperEvents; ++ boolean foundItem = false; ++ for (int i = 0; i < hopper.getContainerSize(); ++i) { ++ final ItemStack item = hopper.getItem(i); ++ if (!item.isEmpty()) { ++ foundItem = true; ++ ItemStack origItemStack = item; ++ ItemStack movedItem = origItemStack; ++ ++ final int originalItemCount = origItemStack.getCount(); ++ final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); ++ origItemStack.setCount(movedItemCount); ++ ++ // We only need to fire the event once to give protection plugins a chance to cancel this event ++ // Because nothing uses getItem, every event call should end up the same result. ++ if (!skipPushModeEventFire) { ++ movedItem = callPushMoveEvent(destination, movedItem, hopper); ++ if (movedItem == null) { // cancelled ++ origItemStack.setCount(originalItemCount); ++ return false; ++ } ++ } ++ ++ final ItemStack remainingItem = addItem(hopper, destination, movedItem, direction); ++ final int remainingItemCount = remainingItem.getCount(); ++ if (remainingItemCount != movedItemCount) { ++ origItemStack = origItemStack.copy(true); ++ origItemStack.setCount(originalItemCount); ++ if (!origItemStack.isEmpty()) { ++ origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); ++ } ++ hopper.setItem(i, origItemStack); ++ destination.setChanged(); ++ return true; ++ } ++ origItemStack.setCount(originalItemCount); ++ } ++ } ++ if (foundItem && level.paperConfig().hopper.cooldownWhenFull) { // Inventory was full - cooldown ++ hopper.setCooldown(level.spigotConfig.hopperTransfer); ++ } ++ return false; ++ } ++ ++ private static boolean hopperPull(final Level level, final Hopper hopper, final Container container, ItemStack origItemStack, final int i) { ++ ItemStack movedItem = origItemStack; ++ final int originalItemCount = origItemStack.getCount(); ++ final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); ++ container.setChanged(); // original logic always marks source inv as changed even if no move happens. ++ movedItem.setCount(movedItemCount); ++ ++ if (!skipPullModeEventFire) { ++ movedItem = callPullMoveEvent(hopper, container, movedItem); ++ if (movedItem == null) { // cancelled ++ origItemStack.setCount(originalItemCount); ++ // Drastically improve performance by returning true. ++ // No plugin could of relied on the behavior of false as the other call ++ // site for IMIE did not exhibit the same behavior ++ return true; ++ } ++ } ++ ++ final ItemStack remainingItem = addItem(container, hopper, movedItem, null); ++ final int remainingItemCount = remainingItem.getCount(); ++ if (remainingItemCount != movedItemCount) { ++ origItemStack = origItemStack.copy(true); ++ origItemStack.setCount(originalItemCount); ++ if (!origItemStack.isEmpty()) { ++ origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); ++ } ++ ++ ignoreTileUpdates = true; ++ container.setItem(i, origItemStack); ++ ignoreTileUpdates = false; ++ container.setChanged(); ++ return true; ++ } ++ origItemStack.setCount(originalItemCount); ++ ++ if (level.paperConfig().hopper.cooldownWhenFull) { ++ cooldownHopper(hopper); ++ } ++ ++ return false; ++ } ++ ++ @Nullable ++ private static ItemStack callPushMoveEvent(Container iinventory, ItemStack itemstack, HopperBlockEntity hopper) { ++ final Inventory destinationInventory = getInventory(iinventory); ++ final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(hopper.getOwner(false).getInventory(), ++ CraftItemStack.asCraftMirror(itemstack), destinationInventory, true); ++ final boolean result = event.callEvent(); ++ if (!event.calledGetItem && !event.calledSetItem) { ++ skipPushModeEventFire = true; ++ } ++ if (!result) { ++ cooldownHopper(hopper); ++ return null; ++ } ++ ++ if (event.calledSetItem) { ++ return CraftItemStack.asNMSCopy(event.getItem()); ++ } else { ++ return itemstack; ++ } ++ } ++ ++ @Nullable ++ private static ItemStack callPullMoveEvent(final Hopper hopper, final Container container, final ItemStack itemstack) { ++ final Inventory sourceInventory = getInventory(container); ++ final Inventory destination = getInventory(hopper); ++ ++ // Mirror is safe as no plugins ever use this item ++ final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(sourceInventory, CraftItemStack.asCraftMirror(itemstack), destination, false); ++ final boolean result = event.callEvent(); ++ if (!event.calledGetItem && !event.calledSetItem) { ++ skipPullModeEventFire = true; ++ } ++ if (!result) { ++ cooldownHopper(hopper); ++ return null; ++ } ++ ++ if (event.calledSetItem) { ++ return CraftItemStack.asNMSCopy(event.getItem()); ++ } else { ++ return itemstack; ++ } ++ } ++ ++ private static Inventory getInventory(final Container container) { ++ final Inventory sourceInventory; ++ if (container instanceof CompoundContainer compoundContainer) { ++ // Have to special-case large chests as they work oddly ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); ++ } else if (container instanceof BlockEntity blockEntity) { ++ sourceInventory = blockEntity.getOwner(false).getInventory(); ++ } else if (container.getOwner() != null) { ++ sourceInventory = container.getOwner().getInventory(); ++ } else { ++ sourceInventory = new CraftInventory(container); ++ } ++ return sourceInventory; ++ } ++ ++ private static void cooldownHopper(final Hopper hopper) { ++ if (hopper instanceof HopperBlockEntity blockEntity && blockEntity.getLevel() != null) { ++ blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer); ++ } ++ } ++ ++ private static boolean allMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate test) { ++ if (iinventory instanceof WorldlyContainer) { ++ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) { ++ if (!test.test(iinventory.getItem(i), i)) { ++ return false; ++ } ++ } ++ } else { ++ int size = iinventory.getContainerSize(); ++ for (int i = 0; i < size; i++) { ++ if (!test.test(iinventory.getItem(i), i)) { ++ return false; ++ } ++ } ++ } ++ return true; ++ } ++ ++ private static boolean anyMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate test) { ++ if (iinventory instanceof WorldlyContainer) { ++ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) { ++ if (test.test(iinventory.getItem(i), i)) { ++ return true; ++ } ++ } ++ } else { ++ int size = iinventory.getContainerSize(); ++ for (int i = 0; i < size; i++) { ++ if (test.test(iinventory.getItem(i), i)) { ++ return true; ++ } ++ } ++ } ++ return true; ++ } ++ private static final java.util.function.BiPredicate STACK_SIZE_TEST = (itemstack, i) -> itemstack.getCount() >= itemstack.getMaxStackSize(); ++ private static final java.util.function.BiPredicate IS_EMPTY_TEST = (itemstack, i) -> itemstack.isEmpty(); ++ // Paper end - Perf: Optimize Hoppers ++ + private static boolean ejectItems(Level world, BlockPos blockposition, BlockState iblockdata, Container iinventory, HopperBlockEntity hopper) { // CraftBukkit + Container iinventory1 = HopperBlockEntity.getAttachedContainer(world, blockposition, iblockdata); + +@@ -204,46 +439,49 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + if (HopperBlockEntity.isFullContainer(iinventory1, enumdirection)) { + return false; + } else { +- for (int i = 0; i < iinventory.getContainerSize(); ++i) { +- if (!iinventory.getItem(i).isEmpty()) { +- ItemStack itemstack = iinventory.getItem(i).copy(); +- // ItemStack itemstack1 = addItem(iinventory, iinventory1, iinventory.removeItem(i, 1), enumdirection); +- +- // CraftBukkit start - Call event when pushing items into other inventories +- CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot +- +- Inventory destinationInventory; +- // Have to special case large chests as they work oddly +- if (iinventory1 instanceof CompoundContainer) { +- destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory1); +- } else if (iinventory1.getOwner() != null) { +- destinationInventory = iinventory1.getOwner().getInventory(); +- } else { +- destinationInventory = new CraftInventory(iinventory); +- } +- +- InventoryMoveItemEvent event = new InventoryMoveItemEvent(iinventory.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true); +- world.getCraftServer().getPluginManager().callEvent(event); +- if (event.isCancelled()) { +- hopper.setItem(i, itemstack); +- hopper.setCooldown(world.spigotConfig.hopperTransfer); // Spigot +- return false; +- } +- int origCount = event.getItem().getAmount(); // Spigot +- ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, iinventory1, CraftItemStack.asNMSCopy(event.getItem()), enumdirection); ++ // Paper start - replace logic; MAKE SURE TO CHECK FOR DIFFS ON UPDATES ++ return hopperPush(world, iinventory1, enumdirection, hopper); ++ // for (int i = 0; i < iinventory.getContainerSize(); ++i) { ++ // if (!iinventory.getItem(i).isEmpty()) { ++ // ItemStack itemstack = iinventory.getItem(i).copy(); ++ // // ItemStack itemstack1 = addItem(iinventory, iinventory1, iinventory.removeItem(i, 1), enumdirection); ++ ++ // // CraftBukkit start - Call event when pushing items into other inventories ++ // CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot ++ ++ // Inventory destinationInventory; ++ // // Have to special case large chests as they work oddly ++ // if (iinventory1 instanceof CompoundContainer) { ++ // destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory1); ++ // } else if (iinventory1.getOwner() != null) { ++ // destinationInventory = iinventory1.getOwner().getInventory(); ++ // } else { ++ // destinationInventory = new CraftInventory(iinventory); ++ // } ++ ++ // InventoryMoveItemEvent event = new InventoryMoveItemEvent(iinventory.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true); ++ // world.getCraftServer().getPluginManager().callEvent(event); ++ // if (event.isCancelled()) { ++ // hopper.setItem(i, itemstack); ++ // hopper.setCooldown(world.spigotConfig.hopperTransfer); // Spigot ++ // return false; ++ // } ++ // int origCount = event.getItem().getAmount(); // Spigot ++ // ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, iinventory1, CraftItemStack.asNMSCopy(event.getItem()), enumdirection); + // CraftBukkit end + +- if (itemstack1.isEmpty()) { +- iinventory1.setChanged(); +- return true; +- } ++ // if (itemstack1.isEmpty()) { ++ // iinventory1.setChanged(); ++ // return true; ++ // } + +- itemstack.shrink(origCount - itemstack1.getCount()); // Spigot +- iinventory.setItem(i, itemstack); +- } +- } ++ // itemstack.shrink(origCount - itemstack1.getCount()); // Spigot ++ // iinventory.setItem(i, itemstack); ++ // } ++ // } + +- return false; ++ // return false; ++ // Paper end - Perf: Optimize Hoppers + } + } + } +@@ -253,17 +491,29 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + + private static boolean isFullContainer(Container inventory, Direction direction) { +- return HopperBlockEntity.getSlots(inventory, direction).allMatch((i) -> { +- ItemStack itemstack = inventory.getItem(i); +- +- return itemstack.getCount() >= itemstack.getMaxStackSize(); +- }); ++ // Paper start - Perf: Optimize Hoppers ++ if (inventory instanceof WorldlyContainer worldlyContainer) { ++ for (final int slot : worldlyContainer.getSlotsForFace(direction)) { ++ final ItemStack stack = inventory.getItem(slot); ++ if (stack.getCount() < stack.getMaxStackSize()) { ++ return false; ++ } ++ } ++ return true; ++ } else { ++ for (int slot = 0, max = inventory.getContainerSize(); slot < max; ++slot) { ++ final ItemStack stack = inventory.getItem(slot); ++ if (stack.getCount() < stack.getMaxStackSize()) { ++ return false; ++ } ++ } ++ return true; ++ } ++ // Paper end - Perf: Optimize Hoppers + } + + private static boolean isEmptyContainer(Container inv, Direction facing) { +- return HopperBlockEntity.getSlots(inv, facing).allMatch((i) -> { +- return inv.getItem(i).isEmpty(); +- }); ++ return allMatch(inv, facing, IS_EMPTY_TEST); // Paper - Perf: Optimize Hoppers + } + + public static boolean suckInItems(Level world, Hopper hopper) { +@@ -272,9 +522,33 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + if (iinventory != null) { + Direction enumdirection = Direction.DOWN; + +- return HopperBlockEntity.isEmptyContainer(iinventory, enumdirection) ? false : HopperBlockEntity.getSlots(iinventory, enumdirection).anyMatch((i) -> { +- return HopperBlockEntity.a(hopper, iinventory, i, enumdirection, world); // Spigot +- }); ++ // Paper start - Perf: Optimize Hoppers ++ skipPullModeEventFire = skipHopperEvents; ++ // merge container isEmpty check and move logic into one loop ++ if (iinventory instanceof WorldlyContainer worldlyContainer) { ++ for (final int slot : worldlyContainer.getSlotsForFace(enumdirection)) { ++ ItemStack item = worldlyContainer.getItem(slot); ++ if (item.isEmpty() || !canTakeItemFromContainer(hopper, iinventory, item, slot, enumdirection)) { ++ continue; ++ } ++ if (hopperPull(world, hopper, iinventory, item, slot)) { ++ return true; ++ } ++ } ++ return false; ++ } else { ++ for (int slot = 0, max = iinventory.getContainerSize(); slot < max; ++slot) { ++ ItemStack item = iinventory.getItem(slot); ++ if (item.isEmpty() || !canTakeItemFromContainer(hopper, iinventory, item, slot, enumdirection)) { ++ continue; ++ } ++ if (hopperPull(world, hopper, iinventory, item, slot)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ // Paper end - Perf: Optimize Hoppers + } else { + Iterator iterator = HopperBlockEntity.getItemsAtAndAbove(world, hopper).iterator(); + +@@ -292,48 +566,52 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + } + ++ @io.papermc.paper.annotation.DoNotUse // Paper - method unused as logic is inlined above + private static boolean a(Hopper ihopper, Container iinventory, int i, Direction enumdirection, Level world) { // Spigot + ItemStack itemstack = iinventory.getItem(i); + +- if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(ihopper, iinventory, itemstack, i, enumdirection)) { +- ItemStack itemstack1 = itemstack.copy(); +- // ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.removeItem(i, 1), (EnumDirection) null); +- // CraftBukkit start - Call event on collection of items from inventories into the hopper +- CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot +- +- Inventory sourceInventory; +- // Have to special case large chests as they work oddly +- if (iinventory instanceof CompoundContainer) { +- sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); +- } else if (iinventory.getOwner() != null) { +- sourceInventory = iinventory.getOwner().getInventory(); +- } else { +- sourceInventory = new CraftInventory(iinventory); +- } +- +- InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), ihopper.getOwner().getInventory(), false); +- +- Bukkit.getServer().getPluginManager().callEvent(event); +- if (event.isCancelled()) { +- iinventory.setItem(i, itemstack1); +- +- if (ihopper instanceof HopperBlockEntity) { +- ((HopperBlockEntity) ihopper).setCooldown(world.spigotConfig.hopperTransfer); // Spigot +- } +- +- return false; +- } +- int origCount = event.getItem().getAmount(); // Spigot +- ItemStack itemstack2 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null); +- // CraftBukkit end +- +- if (itemstack2.isEmpty()) { +- iinventory.setChanged(); +- return true; +- } +- +- itemstack1.shrink(origCount - itemstack2.getCount()); // Spigot +- iinventory.setItem(i, itemstack1); ++ // Paper start - Perf: Optimize Hoppers; replace pull logic; MAKE SURE TO CHECK FOR DIFFS WHEN UPDATING ++ if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(ihopper, iinventory, itemstack, i, enumdirection)) { // If this logic changes, update above. this is left unused incase reflective plugins ++ return hopperPull(world, ihopper, iinventory, itemstack, i); ++ // ItemStack itemstack1 = itemstack.copy(); ++ // // ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.removeItem(i, 1), (EnumDirection) null); ++ // // CraftBukkit start - Call event on collection of items from inventories into the hopper ++ // CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot ++ ++ // Inventory sourceInventory; ++ // // Have to special case large chests as they work oddly ++ // if (iinventory instanceof CompoundContainer) { ++ // sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); ++ // } else if (iinventory.getOwner() != null) { ++ // sourceInventory = iinventory.getOwner().getInventory(); ++ // } else { ++ // sourceInventory = new CraftInventory(iinventory); ++ // } ++ ++ // InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), ihopper.getOwner().getInventory(), false); ++ ++ // Bukkit.getServer().getPluginManager().callEvent(event); ++ // if (event.isCancelled()) { ++ // iinventory.setItem(i, itemstack1); ++ ++ // if (ihopper instanceof HopperBlockEntity) { ++ // ((HopperBlockEntity) ihopper).setCooldown(world.spigotConfig.hopperTransfer); // Spigot ++ // } ++ ++ // return false; ++ // } ++ // int origCount = event.getItem().getAmount(); // Spigot ++ // ItemStack itemstack2 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null); ++ // // CraftBukkit end ++ ++ // if (itemstack2.isEmpty()) { ++ // iinventory.setChanged(); ++ // return true; ++ // } ++ ++ // itemstack1.shrink(origCount - itemstack2.getCount()); // Spigot ++ // iinventory.setItem(i, itemstack1); ++ // Paper end - Perf: Optimize Hoppers + } + + return false; +@@ -342,12 +620,14 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + public static boolean addItem(Container inventory, ItemEntity itemEntity) { + boolean flag = false; + // CraftBukkit start +- InventoryPickupItemEvent event = new InventoryPickupItemEvent(inventory.getOwner().getInventory(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); ++ if (InventoryPickupItemEvent.getHandlerList().getRegisteredListeners().length > 0) { // Paper - optimize hoppers ++ InventoryPickupItemEvent event = new InventoryPickupItemEvent(getInventory(inventory), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); // Paper - Perf: Optimize Hoppers; use getInventory() to avoid snapshot creation + itemEntity.level().getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; + } + // CraftBukkit end ++ } // Paper - Perf: Optimize Hoppers + ItemStack itemstack = itemEntity.getItem().copy(); + ItemStack itemstack1 = HopperBlockEntity.addItem((Container) null, inventory, itemstack, (Direction) null); + +@@ -443,7 +723,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + stack = stack.split(to.getMaxStackSize()); + } + // Spigot end ++ ignoreTileUpdates = true; // Paper - Perf: Optimize Hoppers + to.setItem(slot, stack); ++ ignoreTileUpdates = false; // Paper - Perf: Optimize Hoppers + stack = leftover; // Paper - Make hoppers respect inventory max stack size + flag = true; + } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) { +@@ -517,19 +799,47 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + // CraftBukkit end + } + ++ // Paper start - Perf: Optimize Hoppers ++ static final AABB HOPPER_ITEM_SUCK_OVERALL = Hopper.SUCK.bounds(); ++ static final AABB[] HOPPER_ITEM_SUCK_INDIVIDUAL = Hopper.SUCK.toAabbs().toArray(new AABB[0]); ++ // Paper end - Perf: Optimize Hoppers ++ + public static List getItemsAtAndAbove(Level world, Hopper hopper) { +- return (List) hopper.getSuckShape().toAabbs().stream().flatMap((axisalignedbb) -> { +- return world.getEntitiesOfClass(ItemEntity.class, axisalignedbb.move(hopper.getLevelX() - 0.5D, hopper.getLevelY() - 0.5D, hopper.getLevelZ() - 0.5D), EntitySelector.ENTITY_STILL_ALIVE).stream(); +- }).collect(Collectors.toList()); ++ // Paper start - Perf: Optimize Hoppers ++ // eliminate multiple getEntitiesOfClass() but maintain the voxelshape collision by moving ++ // the individual AABB checks into the predicate ++ final double shiftX = hopper.getLevelX() - 0.5D; ++ final double shiftY = hopper.getLevelY() - 0.5D; ++ final double shiftZ = hopper.getLevelZ() - 0.5D; ++ return world.getEntitiesOfClass(ItemEntity.class, HOPPER_ITEM_SUCK_OVERALL.move(shiftX, shiftY, shiftZ), (final Entity entity) -> { ++ if (!entity.isAlive()) { // EntitySelector.ENTITY_STILL_ALIVE ++ return false; ++ } ++ ++ for (final AABB aabb : HOPPER_ITEM_SUCK_INDIVIDUAL) { ++ if (aabb.move(shiftX, shiftY, shiftZ).intersects(entity.getBoundingBox())) { ++ return true; ++ } ++ } ++ ++ return false; ++ }); ++ // Paper end - Perf: Optimize Hoppers + } + + @Nullable + public static Container getContainerAt(Level world, BlockPos pos) { +- return HopperBlockEntity.getContainerAt(world, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D); ++ return HopperBlockEntity.getContainerAt(world, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, true); // Paper - Perf: Optimize Hoppers + } + + @Nullable + private static Container getContainerAt(Level world, double x, double y, double z) { ++ // Paper start - Perf: Optimize Hoppers ++ return HopperBlockEntity.getContainerAt(world, x, y, z, false); ++ } ++ @Nullable ++ private static Container getContainerAt(Level world, double x, double y, double z, final boolean optimizeEntities) { ++ // Paper end - Perf: Optimize Hoppers + Object object = null; + BlockPos blockposition = BlockPos.containing(x, y, z); + if ( !world.spigotConfig.hopperCanLoadChunks && !world.hasChunkAt( blockposition ) ) return null; // Spigot +@@ -549,8 +859,8 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + } + +- if (object == null) { +- List list = world.getEntities((Entity) null, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); ++ if (object == null && (!optimizeEntities || !world.paperConfig().hopper.ignoreOccludingBlocks || !iblockdata.getBukkitMaterial().isOccluding())) { // Paper - Perf: Optimize Hoppers ++ List list = world.getEntitiesOfClass((Class)Container.class, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper - Perf: Optimize Hoppers + + if (!list.isEmpty()) { + object = (Container) list.get(world.random.nextInt(list.size())); +@@ -561,7 +871,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + + private static boolean canMergeItems(ItemStack first, ItemStack second) { +- return first.getCount() <= first.getMaxStackSize() && ItemStack.isSameItemSameTags(first, second); ++ return first.getCount() < first.getMaxStackSize() && first.is(second.getItem()) && first.getDamageValue() == second.getDamageValue() && ((first.isEmpty() && second.isEmpty()) || java.util.Objects.equals(first.getTag(), second.getTag())); // Paper - Perf: Optimize Hoppers; used to return true for full itemstacks?! + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +index 7cbd403f9e96e7ce35475c8102cd9f9c04819c27..a94300a457b25f0e33a8eeabba6dd5720ca9ab1e 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +@@ -93,12 +93,19 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc + @Override + public boolean isEmpty() { + this.unpackLootTable((Player)null); +- return this.getItems().stream().allMatch(ItemStack::isEmpty); ++ // Paper start - Perf: Optimize Hoppers ++ for (final ItemStack itemStack : this.getItems()) { ++ if (!itemStack.isEmpty()) { ++ return false; ++ } ++ } ++ return true; ++ // Paper end - Perf: Optimize Hoppers + } + + @Override + public ItemStack getItem(int slot) { +- this.unpackLootTable((Player)null); ++ if (slot == 0) this.unpackLootTable((Player) null); // Paper - Perf: Optimize Hoppers + return this.getItems().get(slot); + } + diff --git a/patches/server/1034-Improve-performance-of-mass-crafts.patch b/patches/server/1028-Improve-performance-of-mass-crafts.patch similarity index 100% rename from patches/server/1034-Improve-performance-of-mass-crafts.patch rename to patches/server/1028-Improve-performance-of-mass-crafts.patch diff --git a/patches/server/1029-Actually-optimise-explosions.patch b/patches/server/1029-Actually-optimise-explosions.patch new file mode 100644 index 000000000000..3f44b4ce7826 --- /dev/null +++ b/patches/server/1029-Actually-optimise-explosions.patch @@ -0,0 +1,521 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 12 Sep 2023 06:50:16 -0700 +Subject: [PATCH] Actually optimise explosions + +The vast majority of blocks an explosion of power ~4 tries +to destroy are duplicates. The core of the block destroying +part of this patch is to cache the block state, resistance, and +whether it should explode - as those will not change. + +The other part of this patch is to optimise the visibility +percentage calculation. The new visibility calculation takes +advantage of the block caching already done by the explosion logic. +It continues to update the cache as the visibility calculation +uses many rays which can overlap significantly. + +Effectively, the patch uses a lot of caching to eliminate +redundant operations. + +Performance benchmarking explosions is challenging, as it varies +depending on the power, the number of nearby entities, and the +nearby terrain. This means that no benchmark can cover all the cases. +I decided to test a giant block of TNT, as that's where the optimisations +would be needed the most. + +I tested using a 50x10x50 block of TNT above ground +and determined the following: + +Vanilla time per explosion: 2.27ms +Lithium time per explosion: 1.07ms +This patch time per explosion: 0.45ms + +The results indicate that this logic is 5 times faster than Vanilla +and 2.3 times faster than Lithium. + +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 6894366ebedc461e1e6703317d83f91fb8d4f09b..f0fbde839a527481314f54a1aefa0fc317ba2221 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -111,6 +111,271 @@ public class Explosion { + this.yield = this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F; // CraftBukkit + } + ++ // Paper start - optimise collisions ++ private static final double[] CACHED_RAYS; ++ static { ++ final it.unimi.dsi.fastutil.doubles.DoubleArrayList rayCoords = new it.unimi.dsi.fastutil.doubles.DoubleArrayList(); ++ ++ for (int x = 0; x <= 15; ++x) { ++ for (int y = 0; y <= 15; ++y) { ++ for (int z = 0; z <= 15; ++z) { ++ if ((x == 0 || x == 15) || (y == 0 || y == 15) || (z == 0 || z == 15)) { ++ double xDir = (double)((float)x / 15.0F * 2.0F - 1.0F); ++ double yDir = (double)((float)y / 15.0F * 2.0F - 1.0F); ++ double zDir = (double)((float)z / 15.0F * 2.0F - 1.0F); ++ ++ double mag = Math.sqrt( ++ xDir * xDir + yDir * yDir + zDir * zDir ++ ); ++ ++ rayCoords.add((xDir / mag) * (double)0.3F); ++ rayCoords.add((yDir / mag) * (double)0.3F); ++ rayCoords.add((zDir / mag) * (double)0.3F); ++ } ++ } ++ } ++ } ++ ++ CACHED_RAYS = rayCoords.toDoubleArray(); ++ } ++ ++ private static final int CHUNK_CACHE_SHIFT = 2; ++ private static final int CHUNK_CACHE_MASK = (1 << CHUNK_CACHE_SHIFT) - 1; ++ private static final int CHUNK_CACHE_WIDTH = 1 << CHUNK_CACHE_SHIFT; ++ ++ private static final int BLOCK_EXPLOSION_CACHE_SHIFT = 3; ++ private static final int BLOCK_EXPLOSION_CACHE_MASK = (1 << BLOCK_EXPLOSION_CACHE_SHIFT) - 1; ++ private static final int BLOCK_EXPLOSION_CACHE_WIDTH = 1 << BLOCK_EXPLOSION_CACHE_SHIFT; ++ ++ // resistance = (res + 0.3F) * 0.3F; ++ // so for resistance = 0, we need res = -0.3F ++ private static final Float ZERO_RESISTANCE = Float.valueOf(-0.3f); ++ private it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap blockCache = null; ++ ++ public static final class ExplosionBlockCache { ++ ++ public final long key; ++ public final BlockPos immutablePos; ++ public final BlockState blockState; ++ public final FluidState fluidState; ++ public final float resistance; ++ public final boolean outOfWorld; ++ public Boolean shouldExplode; // null -> not called yet ++ public net.minecraft.world.phys.shapes.VoxelShape cachedCollisionShape; ++ ++ public ExplosionBlockCache(long key, BlockPos immutablePos, BlockState blockState, FluidState fluidState, float resistance, ++ boolean outOfWorld) { ++ this.key = key; ++ this.immutablePos = immutablePos; ++ this.blockState = blockState; ++ this.fluidState = fluidState; ++ this.resistance = resistance; ++ this.outOfWorld = outOfWorld; ++ } ++ } ++ ++ private long[] chunkPosCache = null; ++ private net.minecraft.world.level.chunk.LevelChunk[] chunkCache = null; ++ ++ private ExplosionBlockCache getOrCacheExplosionBlock(final int x, final int y, final int z, ++ final long key, final boolean calculateResistance) { ++ ExplosionBlockCache ret = this.blockCache.get(key); ++ if (ret != null) { ++ return ret; ++ } ++ ++ BlockPos pos = new BlockPos(x, y, z); ++ ++ if (!this.level.isInWorldBounds(pos)) { ++ ret = new ExplosionBlockCache(key, pos, null, null, 0.0f, true); ++ } else { ++ net.minecraft.world.level.chunk.LevelChunk chunk; ++ long chunkKey = io.papermc.paper.util.CoordinateUtils.getChunkKey(x >> 4, z >> 4); ++ int chunkCacheKey = ((x >> 4) & CHUNK_CACHE_MASK) | (((z >> 4) << CHUNK_CACHE_SHIFT) & (CHUNK_CACHE_MASK << CHUNK_CACHE_SHIFT)); ++ if (this.chunkPosCache[chunkCacheKey] == chunkKey) { ++ chunk = this.chunkCache[chunkCacheKey]; ++ } else { ++ this.chunkPosCache[chunkCacheKey] = chunkKey; ++ this.chunkCache[chunkCacheKey] = chunk = this.level.getChunk(x >> 4, z >> 4); ++ } ++ ++ BlockState blockState = chunk.getBlockStateFinal(x, y, z); ++ FluidState fluidState = blockState.getFluidState(); ++ ++ Optional resistance = !calculateResistance ? Optional.empty() : this.damageCalculator.getBlockExplosionResistance((Explosion)(Object)this, this.level, pos, blockState, fluidState); ++ ++ ret = new ExplosionBlockCache( ++ key, pos, blockState, fluidState, ++ (resistance.orElse(ZERO_RESISTANCE).floatValue() + 0.3f) * 0.3f, ++ false ++ ); ++ } ++ ++ this.blockCache.put(key, ret); ++ ++ return ret; ++ } ++ ++ private boolean clipsAnything(final Vec3 from, final Vec3 to, ++ final io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext context, ++ final ExplosionBlockCache[] blockCache, ++ final BlockPos.MutableBlockPos currPos) { ++ // assume that context.delegated = false ++ final double adjX = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.x - to.x); ++ final double adjY = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.y - to.y); ++ final double adjZ = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.z - to.z); ++ ++ if (adjX == 0.0 && adjY == 0.0 && adjZ == 0.0) { ++ return false; ++ } ++ ++ final double toXAdj = to.x - adjX; ++ final double toYAdj = to.y - adjY; ++ final double toZAdj = to.z - adjZ; ++ final double fromXAdj = from.x + adjX; ++ final double fromYAdj = from.y + adjY; ++ final double fromZAdj = from.z + adjZ; ++ ++ int currX = Mth.floor(fromXAdj); ++ int currY = Mth.floor(fromYAdj); ++ int currZ = Mth.floor(fromZAdj); ++ ++ final double diffX = toXAdj - fromXAdj; ++ final double diffY = toYAdj - fromYAdj; ++ final double diffZ = toZAdj - fromZAdj; ++ ++ final double dxDouble = Math.signum(diffX); ++ final double dyDouble = Math.signum(diffY); ++ final double dzDouble = Math.signum(diffZ); ++ ++ final int dx = (int)dxDouble; ++ final int dy = (int)dyDouble; ++ final int dz = (int)dzDouble; ++ ++ final double normalizedDiffX = diffX == 0.0 ? Double.MAX_VALUE : dxDouble / diffX; ++ final double normalizedDiffY = diffY == 0.0 ? Double.MAX_VALUE : dyDouble / diffY; ++ final double normalizedDiffZ = diffZ == 0.0 ? Double.MAX_VALUE : dzDouble / diffZ; ++ ++ double normalizedCurrX = normalizedDiffX * (diffX > 0.0 ? (1.0 - Mth.frac(fromXAdj)) : Mth.frac(fromXAdj)); ++ double normalizedCurrY = normalizedDiffY * (diffY > 0.0 ? (1.0 - Mth.frac(fromYAdj)) : Mth.frac(fromYAdj)); ++ double normalizedCurrZ = normalizedDiffZ * (diffZ > 0.0 ? (1.0 - Mth.frac(fromZAdj)) : Mth.frac(fromZAdj)); ++ ++ for (;;) { ++ currPos.set(currX, currY, currZ); ++ ++ // ClipContext.Block.COLLIDER -> BlockBehaviour.BlockStateBase::getCollisionShape ++ // ClipContext.Fluid.NONE -> ignore fluids ++ ++ // read block from cache ++ final long key = BlockPos.asLong(currX, currY, currZ); ++ ++ final int cacheKey = ++ (currX & BLOCK_EXPLOSION_CACHE_MASK) | ++ (currY & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) | ++ (currZ & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT); ++ ExplosionBlockCache cachedBlock = blockCache[cacheKey]; ++ if (cachedBlock == null || cachedBlock.key != key) { ++ blockCache[cacheKey] = cachedBlock = this.getOrCacheExplosionBlock(currX, currY, currZ, key, false); ++ } ++ ++ final BlockState blockState = cachedBlock.blockState; ++ if (blockState != null && !blockState.emptyCollisionShape()) { ++ net.minecraft.world.phys.shapes.VoxelShape collision = cachedBlock.cachedCollisionShape; ++ if (collision == null) { ++ collision = blockState.getConstantCollisionShape(); ++ if (collision == null) { ++ collision = blockState.getCollisionShape(this.level, currPos, context); ++ if (!context.isDelegated()) { ++ // if it was not delegated during this call, assume that for any future ones it will not be delegated ++ // again, and cache the result ++ cachedBlock.cachedCollisionShape = collision; ++ } ++ } else { ++ cachedBlock.cachedCollisionShape = collision; ++ } ++ } ++ ++ if (!collision.isEmpty() && collision.clip(from, to, currPos) != null) { ++ return true; ++ } ++ } ++ ++ if (normalizedCurrX > 1.0 && normalizedCurrY > 1.0 && normalizedCurrZ > 1.0) { ++ return false; ++ } ++ ++ // inc the smallest normalized coordinate ++ ++ if (normalizedCurrX < normalizedCurrY) { ++ if (normalizedCurrX < normalizedCurrZ) { ++ currX += dx; ++ normalizedCurrX += normalizedDiffX; ++ } else { ++ // x < y && x >= z <--> z < y && z <= x ++ currZ += dz; ++ normalizedCurrZ += normalizedDiffZ; ++ } ++ } else if (normalizedCurrY < normalizedCurrZ) { ++ // y <= x && y < z ++ currY += dy; ++ normalizedCurrY += normalizedDiffY; ++ } else { ++ // y <= x && z <= y <--> z <= y && z <= x ++ currZ += dz; ++ normalizedCurrZ += normalizedDiffZ; ++ } ++ } ++ } ++ ++ private float getSeenFraction(final Vec3 source, final Entity target, ++ final ExplosionBlockCache[] blockCache, ++ final BlockPos.MutableBlockPos blockPos) { ++ final AABB boundingBox = target.getBoundingBox(); ++ final double diffX = boundingBox.maxX - boundingBox.minX; ++ final double diffY = boundingBox.maxY - boundingBox.minY; ++ final double diffZ = boundingBox.maxZ - boundingBox.minZ; ++ ++ final double incX = 1.0 / (diffX * 2.0 + 1.0); ++ final double incY = 1.0 / (diffY * 2.0 + 1.0); ++ final double incZ = 1.0 / (diffZ * 2.0 + 1.0); ++ ++ if (incX < 0.0 || incY < 0.0 || incZ < 0.0) { ++ return 0.0f; ++ } ++ ++ final double offX = (1.0 - Math.floor(1.0 / incX) * incX) * 0.5 + boundingBox.minX; ++ final double offY = boundingBox.minY; ++ final double offZ = (1.0 - Math.floor(1.0 / incZ) * incZ) * 0.5 + boundingBox.minZ; ++ ++ final io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext context = new io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext(target); ++ ++ int totalRays = 0; ++ int missedRays = 0; ++ ++ for (double dx = 0.0; dx <= 1.0; dx += incX) { ++ final double fromX = Math.fma(dx, diffX, offX); ++ for (double dy = 0.0; dy <= 1.0; dy += incY) { ++ final double fromY = Math.fma(dy, diffY, offY); ++ for (double dz = 0.0; dz <= 1.0; dz += incZ) { ++ ++totalRays; ++ ++ final Vec3 from = new Vec3( ++ fromX, ++ fromY, ++ Math.fma(dz, diffZ, offZ) ++ ); ++ ++ if (!this.clipsAnything(from, source, context, blockCache, blockPos)) { ++ ++missedRays; ++ } ++ } ++ } ++ } ++ ++ return (float)missedRays / (float)totalRays; ++ } ++ // Paper end - optimise collisions ++ + private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) { + return (ExplosionDamageCalculator) (entity == null ? Explosion.EXPLOSION_DAMAGE_CALCULATOR : new EntityBasedExplosionDamageCalculator(entity)); + } +@@ -171,40 +436,88 @@ public class Explosion { + int i; + int j; + +- for (int k = 0; k < 16; ++k) { +- for (i = 0; i < 16; ++i) { +- for (j = 0; j < 16; ++j) { +- if (k == 0 || k == 15 || i == 0 || i == 15 || j == 0 || j == 15) { +- double d0 = (double) ((float) k / 15.0F * 2.0F - 1.0F); +- double d1 = (double) ((float) i / 15.0F * 2.0F - 1.0F); +- double d2 = (double) ((float) j / 15.0F * 2.0F - 1.0F); +- double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2); +- +- d0 /= d3; +- d1 /= d3; +- d2 /= d3; ++ // Paper start - optimise explosions ++ this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); ++ ++ this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; ++ java.util.Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS); ++ ++ this.chunkCache = new net.minecraft.world.level.chunk.LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; ++ ++ final ExplosionBlockCache[] blockCache = new ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH]; ++ // use initial cache value that is most likely to be used: the source position ++ final ExplosionBlockCache initialCache; ++ { ++ final int blockX = Mth.floor(this.x); ++ final int blockY = Mth.floor(this.y); ++ final int blockZ = Mth.floor(this.z); ++ ++ final long key = BlockPos.asLong(blockX, blockY, blockZ); ++ ++ initialCache = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true); ++ } ++ // only ~1/3rd of the loop iterations in vanilla will result in a ray, as it is iterating the perimeter of ++ // a 16x16x16 cube ++ // we can cache the rays and their normals as well, so that we eliminate the excess iterations / checks and ++ // calculations in one go ++ // additional aggressive caching of block retrieval is very significant, as at low power (i.e tnt) most ++ // block retrievals are not unique ++ for (int ray = 0, len = CACHED_RAYS.length; ray < len;) { ++ { ++ { ++ { ++ ExplosionBlockCache cachedBlock = initialCache; ++ ++ double d0 = CACHED_RAYS[ray]; ++ double d1 = CACHED_RAYS[ray + 1]; ++ double d2 = CACHED_RAYS[ray + 2]; ++ ray += 3; ++ // Paper end - optimise explosions + float f = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F); + double d4 = this.x; + double d5 = this.y; + double d6 = this.z; + + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { +- BlockPos blockposition = BlockPos.containing(d4, d5, d6); +- BlockState iblockdata = this.level.getBlockState(blockposition); +- if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed +- FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions ++ // Paper start - optimise explosions ++ final int blockX = Mth.floor(d4); ++ final int blockY = Mth.floor(d5); ++ final int blockZ = Mth.floor(d6); ++ ++ final long key = BlockPos.asLong(blockX, blockY, blockZ); ++ ++ if (cachedBlock.key != key) { ++ final int cacheKey = ++ (blockX & BLOCK_EXPLOSION_CACHE_MASK) | ++ (blockY & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) | ++ (blockZ & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT); ++ cachedBlock = blockCache[cacheKey]; ++ if (cachedBlock == null || cachedBlock.key != key) { ++ blockCache[cacheKey] = cachedBlock = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true); ++ } ++ } + +- if (!this.level.isInWorldBounds(blockposition)) { ++ if (cachedBlock.outOfWorld) { + break; + } + +- Optional optional = this.damageCalculator.getBlockExplosionResistance(this, this.level, blockposition, iblockdata, fluid); ++ BlockPos blockposition = cachedBlock.immutablePos; ++ BlockState iblockdata = cachedBlock.blockState; ++ // Paper end - optimise explosions + +- if (optional.isPresent()) { +- f -= ((Float) optional.get() + 0.3F) * 0.3F; +- } ++ if (!iblockdata.isDestroyable()) continue; // Paper ++ // Paper - optimise explosions + +- if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) { ++ f -= cachedBlock.resistance; // Paper - optimise explosions ++ ++ if (f > 0.0F && cachedBlock.shouldExplode == null) { // Paper - optimise explosions ++ // Paper start - optimise explosions ++ // note: we expect shouldBlockExplode to be pure with respect to power, as Vanilla currently is. ++ // basically, it is unused, which allows us to cache the result ++ final boolean shouldExplode = this.damageCalculator.shouldBlockExplode(this, this.level, cachedBlock.immutablePos, cachedBlock.blockState, f); ++ cachedBlock.shouldExplode = shouldExplode ? Boolean.TRUE : Boolean.FALSE; ++ if (shouldExplode && (this.fire || !cachedBlock.blockState.isAir())) { ++ // Paper end - optimise explosions + set.add(blockposition); + // Paper start - prevent headless pistons from forming + if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { +@@ -215,11 +528,12 @@ public class Explosion { + } + } + // Paper end - prevent headless pistons from forming ++ } // Paper - optimise explosions + } + +- d4 += d0 * 0.30000001192092896D; +- d5 += d1 * 0.30000001192092896D; +- d6 += d2 * 0.30000001192092896D; ++ d4 += d0; // Paper - optimise explosions ++ d5 += d1; // Paper - optimise explosions ++ d6 += d2; // Paper - optimise explosions + } + } + } +@@ -239,6 +553,8 @@ public class Explosion { + Vec3 vec3d = new Vec3(this.x, this.y, this.z); + Iterator iterator = list.iterator(); + ++ final BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(); // Paper - optimise explosions ++ + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); + +@@ -274,11 +590,11 @@ public class Explosion { + for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) { + // Calculate damage separately for each EntityComplexPart + if (list.contains(entityComplexPart)) { +- entityComplexPart.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity)); ++ entityComplexPart.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entityComplexPart, getSeenFraction(vec3d, entityComplexPart, blockCache, blockPos))); // Paper - actually optimise explosions and use the right entity to calculate the damage + } + } + } else { +- entity.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity)); ++ entity.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, getSeenFraction(vec3d, entity, blockCache, blockPos))); // Paper - actually optimise explosions + } + + if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled +@@ -287,7 +603,7 @@ public class Explosion { + // CraftBukkit end + } + +- double d12 = (1.0D - d7) * this.getBlockDensity(vec3d, entity); // Paper - Optimize explosions ++ double d12 = (1.0D - d7) * this.getBlockDensity(vec3d, entity, blockCache, blockPos); // Paper - Optimize explosions + double d13; + + if (entity instanceof LivingEntity) { +@@ -324,6 +640,9 @@ public class Explosion { + } + } + ++ this.blockCache = null; // Paper - optimise explosions ++ this.chunkPosCache = null; // Paper - optimise explosions ++ this.chunkCache = null; // Paper - optimise explosions + } + + public void finalizeExplosion(boolean particles) { +@@ -537,14 +856,14 @@ public class Explosion { + private BlockInteraction() {} + } + // Paper start - Optimize explosions +- private float getBlockDensity(Vec3 vec3d, Entity entity) { ++ private float getBlockDensity(Vec3 vec3d, Entity entity, ExplosionBlockCache[] blockCache, BlockPos.MutableBlockPos blockPos) { // Paper - optimise explosions + if (!this.level.paperConfig().environment.optimizeExplosions) { +- return getSeenPercent(vec3d, entity); ++ return this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise explosions + } + CacheKey key = new CacheKey(this, entity.getBoundingBox()); + Float blockDensity = this.level.explosionDensityCache.get(key); + if (blockDensity == null) { +- blockDensity = getSeenPercent(vec3d, entity); ++ blockDensity = this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise explosions; + this.level.explosionDensityCache.put(key, blockDensity); + } + +diff --git a/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java b/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java +index 24dba1eb6f5dc71e5d1ce2d150930eaefc83f811..f529f5d0f28533ec89f3ee712e59745991d068ee 100644 +--- a/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java ++++ b/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java +@@ -20,11 +20,17 @@ public class ExplosionDamageCalculator { + return true; + } + ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper + public float getEntityDamageAmount(Explosion explosion, Entity entity) { ++ // Paper start - actually optimise explosions ++ return this.getEntityDamageAmount(explosion, entity, Explosion.getSeenPercent(explosion.center(), entity)); ++ } ++ public float getEntityDamageAmount(Explosion explosion, Entity entity, double seenPercent) { ++ // Paper end - actually optimise explosions + float f = explosion.radius() * 2.0F; + Vec3 vec3 = explosion.center(); + double d = Math.sqrt(entity.distanceToSqr(vec3)) / (double)f; +- double e = (1.0D - d) * (double)Explosion.getSeenPercent(vec3, entity); ++ double e = (1.0D - d) * seenPercent; // Paper - actually optimise explosions + return (float)((e * e + e) / 2.0D * 7.0D * (double)f + 1.0D); + } + } diff --git a/patches/server/1030-Optimise-chunk-tick-iteration.patch b/patches/server/1030-Optimise-chunk-tick-iteration.patch new file mode 100644 index 000000000000..600f4520b424 --- /dev/null +++ b/patches/server/1030-Optimise-chunk-tick-iteration.patch @@ -0,0 +1,380 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 23 Sep 2023 21:36:36 -0700 +Subject: [PATCH] Optimise chunk tick iteration + +When per-player mob spawning is enabled we do not need to randomly +shuffle the chunk list. Additionally, we can use the NearbyPlayers +class to quickly retrieve nearby players instead of possible +searching all players on the server. + +diff --git a/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java b/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java +index c3ce8a42dddd76b7189ad5685b23f9d9f8ccadb3..f164256d59b761264876ca0c85f812d101bfd5de 100644 +--- a/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java ++++ b/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java +@@ -17,7 +17,8 @@ public final class NearbyPlayers { + GENERAL_SMALL, + GENERAL_REALLY_SMALL, + TICK_VIEW_DISTANCE, +- VIEW_DISTANCE; ++ VIEW_DISTANCE, // Paper - optimise chunk iteration ++ SPAWN_RANGE, // Paper - optimise chunk iteration + } + + private static final NearbyMapType[] MOB_TYPES = NearbyMapType.values(); +@@ -26,10 +27,12 @@ public final class NearbyPlayers { + private static final int GENERAL_AREA_VIEW_DISTANCE = 33; + private static final int GENERAL_SMALL_VIEW_DISTANCE = 10; + private static final int GENERAL_REALLY_SMALL_VIEW_DISTANCE = 3; ++ private static final int SPAWN_RANGE_VIEW_DISTANCE = net.minecraft.server.level.DistanceManager.MOB_SPAWN_RANGE; // Paper - optimise chunk iteration + + public static final int GENERAL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_AREA_VIEW_DISTANCE << 4); + public static final int GENERAL_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_SMALL_VIEW_DISTANCE << 4); + public static final int GENERAL_REALLY_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_REALLY_SMALL_VIEW_DISTANCE << 4); ++ public static final int SPAWN_RANGE_VIEW_DISTANCE_BLOCKS = (SPAWN_RANGE_VIEW_DISTANCE << 4); // Paper - optimise chunk iteration + + private final ServerLevel world; + private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); +@@ -80,6 +83,7 @@ public final class NearbyPlayers { + players[NearbyMapType.GENERAL_REALLY_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_REALLY_SMALL_VIEW_DISTANCE); + players[NearbyMapType.TICK_VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getTickViewDistance(player)); + players[NearbyMapType.VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getLoadViewDistance(player)); ++ players[NearbyMapType.SPAWN_RANGE.ordinal()].update(chunk.x, chunk.z, SPAWN_RANGE_VIEW_DISTANCE); // Paper - optimise chunk iteration + } + + public TrackedChunk getChunk(final ChunkPos pos) { +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 2b998bdbe49bf8211b755e0eb7c1bf13ac280eab..627a88ec8c3b215b19b55a6d461c8754b4fcd1e8 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -79,11 +79,19 @@ public class ChunkHolder { + + // Paper start + public void onChunkAdd() { +- ++ // Paper start - optimise chunk tick iteration ++ if (this.needsBroadcastChanges()) { ++ this.chunkMap.needsChangeBroadcasting.add(this); ++ } ++ // Paper end - optimise chunk tick iteration + } + + public void onChunkRemove() { +- ++ // Paper start - optimise chunk tick iteration ++ if (this.needsBroadcastChanges()) { ++ this.chunkMap.needsChangeBroadcasting.remove(this); ++ } ++ // Paper end - optimise chunk tick iteration + } + // Paper end + +@@ -230,7 +238,7 @@ public class ChunkHolder { + + if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296 + if (this.changedBlocksPerSection[i] == null) { +- this.hasChangedSections = true; ++ this.hasChangedSections = true; this.addToBroadcastMap(); // Paper - optimise chunk tick iteration + this.changedBlocksPerSection[i] = new ShortOpenHashSet(); + } + +@@ -254,6 +262,7 @@ public class ChunkHolder { + int k = this.lightEngine.getMaxLightSection(); + + if (y >= j && y <= k) { ++ this.addToBroadcastMap(); // Paper - optimise chunk tick iteration + int l = y - j; + + if (lightType == LightLayer.SKY) { +@@ -268,8 +277,19 @@ public class ChunkHolder { + } + } + ++ // Paper start - optimise chunk tick iteration ++ public final boolean needsBroadcastChanges() { ++ return this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty(); ++ } ++ ++ private void addToBroadcastMap() { ++ io.papermc.paper.util.TickThread.ensureTickThread(this.chunkMap.level, this.pos, "Asynchronous ChunkHolder update is not allowed"); ++ this.chunkMap.needsChangeBroadcasting.add(this); ++ } ++ // Paper end - optimise chunk tick iteration ++ + public void broadcastChanges(LevelChunk chunk) { +- if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) { ++ if (this.needsBroadcastChanges()) { // Paper - optimise chunk tick iteration; moved into above, other logic needs to call + Level world = chunk.getLevel(); + List list; + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 7e5a8789e06a5ea1d2657ea8ee5c0460da92aaeb..5a7278b093e37b95fb005ad5cc3cac90ac36f8fb 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -191,6 +191,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.playerEntityTrackerTrackMaps[i].remove(player); + } + // Paper end - use distance map to optimise tracker ++ this.playerMobSpawnMap.remove(player); // Paper - optimise chunk tick iteration + } + + void updateMaps(ServerPlayer player) { +@@ -240,6 +241,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + public final io.papermc.paper.util.player.NearbyPlayers nearbyPlayers; + // Paper end ++ // Paper start - optimise chunk tick iteration ++ public final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet needsChangeBroadcasting = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); ++ // Paper end - optimise chunk tick iteration + + public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { + super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); +@@ -408,7 +413,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + // Paper end - Optional per player mob spawns + +- private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { ++ public static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { // Paper - optimise chunk iteration; public + double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8); + double d1 = (double) SectionPos.sectionToBlockCoord(pos.z, 8); + double d2 = d0 - entity.getX(); +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index c80a625f7289e3bb33c6851d2072957e153ca1fb..7c425ac50c83757b66a2178bc19d4c920b82f12f 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -50,7 +50,7 @@ public abstract class DistanceManager { + private static final int INITIAL_TICKET_LIST_CAPACITY = 4; + final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); + // Paper - rewrite chunk system +- private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); ++ public static final int MOB_SPAWN_RANGE = 8; //private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); // Paper - optimise chunk tick iteration + // Paper - rewrite chunk system + private final ChunkMap chunkMap; // Paper + +@@ -135,7 +135,7 @@ public abstract class DistanceManager { + long i = chunkcoordintpair.toLong(); + + // Paper - no longer used +- this.naturalSpawnChunkCounter.update(i, 0, true); ++ //this.naturalSpawnChunkCounter.update(i, 0, true); // Paper - optimise chunk tick iteration + //this.playerTicketManager.update(i, 0, true); // Paper - no longer used + //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used + } +@@ -149,7 +149,7 @@ public abstract class DistanceManager { + if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully + if (objectset == null || objectset.isEmpty()) { // Paper + this.playersPerChunk.remove(i); +- this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); ++ //this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); // Paper - optimise chunk tick iteration + //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used + //this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used + } +@@ -191,13 +191,11 @@ public abstract class DistanceManager { + } + + public int getNaturalSpawnChunkCount() { +- this.naturalSpawnChunkCounter.runAllUpdates(); +- return this.naturalSpawnChunkCounter.chunks.size(); ++ return this.chunkMap.playerMobSpawnMap.size(); // Paper - optimise chunk tick iteration + } + + public boolean hasPlayersNearby(long chunkPos) { +- this.naturalSpawnChunkCounter.runAllUpdates(); +- return this.naturalSpawnChunkCounter.chunks.containsKey(chunkPos); ++ return this.chunkMap.playerMobSpawnMap.getObjectsInRange(chunkPos) != null; // Paper - optimise chunk tick iteration + } + + public String getDebugStatus() { +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 2b33a3d8fdb86024acb2a3ee9d0a4a7dd4989c98..366c0c9b45a819f7f94ebe3e49b8ab7f9edf9ce7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -508,18 +508,10 @@ public class ServerChunkCache extends ChunkSource { + + gameprofilerfiller.push("pollingChunks"); + gameprofilerfiller.push("filteringLoadedChunks"); +- List list = Lists.newArrayListWithCapacity(this.chunkMap.size()); +- Iterator iterator = this.chunkMap.getChunks().iterator(); ++ // Paper - optimise chunk tick iteration + if (this.level.getServer().tickRateManager().runsNormally()) this.level.timings.chunkTicks.startTiming(); // Paper + +- while (iterator.hasNext()) { +- ChunkHolder playerchunk = (ChunkHolder) iterator.next(); +- LevelChunk chunk = playerchunk.getTickingChunk(); +- +- if (chunk != null) { +- list.add(new ServerChunkCache.ChunkAndHolder(chunk, playerchunk)); +- } +- } ++ // Paper - optimise chunk tick iteration + + if (this.level.getServer().tickRateManager().runsNormally()) { + gameprofilerfiller.popPush("naturalSpawnCount"); +@@ -554,38 +546,109 @@ public class ServerChunkCache extends ChunkSource { + gameprofilerfiller.popPush("spawnAndTick"); + boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit + +- Util.shuffle(list, this.level.random); +- // Paper start - PlayerNaturallySpawnCreaturesEvent +- int chunkRange = level.spigotConfig.mobSpawnRange; +- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; +- chunkRange = Math.min(chunkRange, 8); +- for (ServerPlayer entityPlayer : this.level.players()) { +- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); +- entityPlayer.playerNaturallySpawnedEvent.callEvent(); ++ // Paper start - optimise chunk tick iteration ++ ChunkMap playerChunkMap = this.chunkMap; ++ for (ServerPlayer player : this.level.players) { ++ if (!player.affectsSpawning || player.isSpectator()) { ++ playerChunkMap.playerMobSpawnMap.remove(player); ++ player.playerNaturallySpawnedEvent = null; ++ player.lastEntitySpawnRadiusSquared = -1.0; ++ continue; ++ } ++ ++ int viewDistance = io.papermc.paper.chunk.system.ChunkSystem.getTickViewDistance(player); ++ ++ // copied and modified from isOutisdeRange ++ int chunkRange = (int)level.spigotConfig.mobSpawnRange; ++ chunkRange = (chunkRange > viewDistance) ? viewDistance : chunkRange; ++ chunkRange = (chunkRange > DistanceManager.MOB_SPAWN_RANGE) ? DistanceManager.MOB_SPAWN_RANGE : chunkRange; ++ ++ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(player.getBukkitEntity(), (byte)chunkRange); ++ event.callEvent(); ++ if (event.isCancelled() || event.getSpawnRadius() < 0) { ++ playerChunkMap.playerMobSpawnMap.remove(player); ++ player.playerNaturallySpawnedEvent = null; ++ player.lastEntitySpawnRadiusSquared = -1.0; ++ continue; ++ } ++ ++ int range = Math.min(event.getSpawnRadius(), DistanceManager.MOB_SPAWN_RANGE); // limit to max spawn range ++ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkCoordinate(player.getX()); ++ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkCoordinate(player.getZ()); ++ ++ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range); ++ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in anyPlayerCloseEnoughForSpawning ++ player.playerNaturallySpawnedEvent = event; + } +- // Paper end - PlayerNaturallySpawnCreaturesEvent ++ // Paper end - optimise chunk tick iteration + int l = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); + boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit +- Iterator iterator1 = list.iterator(); ++ // Paper - optimise chunk tick iteration + + int chunksTicked = 0; // Paper +- while (iterator1.hasNext()) { +- ServerChunkCache.ChunkAndHolder chunkproviderserver_a = (ServerChunkCache.ChunkAndHolder) iterator1.next(); +- LevelChunk chunk1 = chunkproviderserver_a.chunk; ++ // Paper start - optimise chunk tick iteration ++ io.papermc.paper.util.player.NearbyPlayers nearbyPlayers = this.chunkMap.getNearbyPlayers(); // Paper - optimise chunk tick iteration ++ Iterator chunkIterator; ++ if (this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { ++ chunkIterator = this.tickingChunks.iterator(); ++ } else { ++ chunkIterator = this.tickingChunks.unsafeIterator(); ++ List shuffled = Lists.newArrayListWithCapacity(this.tickingChunks.size()); ++ while (chunkIterator.hasNext()) { ++ shuffled.add(chunkIterator.next()); ++ } ++ Util.shuffle(shuffled, this.level.random); ++ chunkIterator = shuffled.iterator(); ++ } ++ try { ++ // Paper end - optimise chunk tick iteration ++ while (chunkIterator.hasNext()) { ++ LevelChunk chunk1 = chunkIterator.next(); ++ // Paper end - optimise chunk tick iteration + ChunkPos chunkcoordintpair = chunk1.getPos(); + +- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) { ++ // Paper start - optimise chunk tick iteration ++ com.destroystokyo.paper.util.maplist.ReferenceList playersNearby ++ = nearbyPlayers.getPlayers(chunkcoordintpair, io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.SPAWN_RANGE); ++ if (playersNearby == null) { ++ continue; ++ } ++ Object[] rawData = playersNearby.getRawData(); ++ boolean spawn = false; ++ boolean tick = false; ++ for (int itr = 0, len = playersNearby.size(); itr < len; ++itr) { ++ ServerPlayer player = (ServerPlayer)rawData[itr]; ++ if (player.isSpectator()) { ++ continue; ++ } ++ ++ double distance = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, player); ++ spawn |= player.lastEntitySpawnRadiusSquared >= distance; ++ tick |= ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) * ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) >= distance; ++ if (spawn & tick) { ++ break; ++ } ++ } ++ if (tick && chunk1.chunkStatus.isOrAfter(net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING)) { ++ // Paper end - optimise chunk tick iteration + chunk1.incrementInhabitedTime(j); +- if (flag && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot ++ if (spawn && flag && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { // Spigot // Paper - optimise chunk tick iteration + NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); + } + +- if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { ++ if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - optimise chunk tick iteration + this.level.tickChunk(chunk1, l); + if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper + } + } + } ++ // Paper start - optimise chunk tick iteration ++ } finally { ++ if (chunkIterator instanceof io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator safeIterator) { ++ safeIterator.finishedIterating(); ++ } ++ } ++ // Paper end - optimise chunk tick iteration + this.level.timings.chunkTicks.stopTiming(); // Paper + + gameprofilerfiller.popPush("customSpawners"); +@@ -597,11 +660,23 @@ public class ServerChunkCache extends ChunkSource { + } + + gameprofilerfiller.popPush("broadcast"); +- list.forEach((chunkproviderserver_a1) -> { ++ // Paper - optimise chunk tick iteration + this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing +- chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk); ++ // Paper start - optimise chunk tick iteration ++ if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { ++ it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); ++ this.chunkMap.needsChangeBroadcasting.clear(); ++ for (ChunkHolder holder : copy) { ++ holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded ++ if (holder.needsBroadcastChanges()) { ++ // I DON'T want to KNOW what DUMB plugins might be doing. ++ this.chunkMap.needsChangeBroadcasting.add(holder); ++ } ++ } ++ } ++ // Paper end - optimise chunk tick iteration + this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing +- }); ++ // Paper - optimise chunk tick iteration + gameprofilerfiller.pop(); + gameprofilerfiller.pop(); + } +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index c99d2f2d64b73179e4e27b63030e26a07953041b..58591bf2f63b9c5e97d9ce4188dff3366968a178 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -325,6 +325,9 @@ public class ServerPlayer extends Player { + }); + } + // Paper end - replace player chunk loader ++ // Paper start - optimise chunk tick iteration ++ public double lastEntitySpawnRadiusSquared = -1.0; ++ // Paper end - optimise chunk tick iteration + + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { + super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); diff --git a/patches/server/1031-Lag-compensation-ticks.patch b/patches/server/1031-Lag-compensation-ticks.patch new file mode 100644 index 000000000000..80f563072062 --- /dev/null +++ b/patches/server/1031-Lag-compensation-ticks.patch @@ -0,0 +1,129 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 23 Sep 2023 22:05:35 -0700 +Subject: [PATCH] Lag compensation ticks + +Areas affected by lag comepnsation: + - Block breaking and destroying + - Eating food items + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index f12bf36a247a47b8d831536a4fefd2a2ce424494..d06185566b447c432d4dc2e3ba04d121bcdbc71b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -311,6 +311,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { + AtomicReference atomicreference = new AtomicReference(); +@@ -1693,6 +1694,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent + worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent + net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers ++ worldserver.updateLagCompensationTick(); // Paper - lag compensation + + this.profiler.push(() -> { + return worldserver + " " + worldserver.dimension().location(); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index ab2e84f85da7931e133ad5f0d2686cd1738f6ea1..5bbfb1af24e13a9e6a02ad8c36bb504a17f06398 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -565,6 +565,17 @@ public class ServerLevel extends Level implements WorldGenLevel { + return player != null && player.level() == this ? player : null; + } + // Paper end - optimise getPlayerByUUID ++ // Paper start - lag compensation ++ private long lagCompensationTick = net.minecraft.server.MinecraftServer.SERVER_INIT; ++ ++ public long getLagCompensationTick() { ++ return this.lagCompensationTick; ++ } ++ ++ public void updateLagCompensationTick() { ++ this.lagCompensationTick = (System.nanoTime() - net.minecraft.server.MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L)); ++ } ++ // Paper end - lag compensation + + // Add env and gen to constructor, IWorldDataServer -> WorldDataServer + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index ef3048a4748113538a0ee0af5b526b2cd51d5c29..a7b217ddbcbf92513bd38101fdfca2075505e267 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -124,7 +124,7 @@ public class ServerPlayerGameMode { + } + + public void tick() { +- this.gameTicks = MinecraftServer.currentTick; // CraftBukkit; ++ this.gameTicks = (int)this.level.getLagCompensationTick(); // CraftBukkit; // Paper - lag compensation + BlockState iblockdata; + + if (this.hasDelayedDestroy) { +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 578141c88eccaf1c8fd830d8166176f9dab8ed04..902c0e7f2a167845f46adef4578bc71ca8cabfe8 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3841,6 +3841,10 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.getEntityData().resendPossiblyDesyncedDataValues(java.util.List.of(DATA_LIVING_ENTITY_FLAGS), serverPlayer); + } + // Paper end - Properly cancel usable items ++ // Paper start - lag compensate eating ++ protected long eatStartTime; ++ protected int totalEatTimeTicks; ++ // Paper end - lag compensate eating + private void updatingUsingItem() { + if (this.isUsingItem()) { + if (ItemStack.isSameItem(this.getItemInHand(this.getUsedItemHand()), this.useItem)) { +@@ -3859,7 +3863,12 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.triggerItemUseEffects(stack, 5); + } + +- if (--this.useItemRemaining == 0 && !this.level().isClientSide && !stack.useOnRelease()) { ++ // Paper start - lag compensate eating ++ // we add 1 to the expected time to avoid lag compensating when we should not ++ boolean shouldLagCompensate = this.useItem.getItem().isEdible() && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1 + this.totalEatTimeTicks) * 50 * (1000 * 1000)); ++ if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level().isClientSide && !stack.useOnRelease()) { ++ this.useItemRemaining = 0; ++ // Paper end - lag compensate eating + this.completeUsingItem(); + } + +@@ -3907,7 +3916,10 @@ public abstract class LivingEntity extends Entity implements Attackable { + + if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack + this.useItem = itemstack; +- this.useItemRemaining = itemstack.getUseDuration(); ++ // Paper start - lag compensate eating ++ this.useItemRemaining = this.totalEatTimeTicks = itemstack.getUseDuration(); ++ this.eatStartTime = System.nanoTime(); ++ // Paper end - lag compensate eating + if (!this.level().isClientSide) { + this.setLivingEntityFlag(1, true); + this.setLivingEntityFlag(2, hand == InteractionHand.OFF_HAND); +@@ -3932,7 +3944,10 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + } else if (!this.isUsingItem() && !this.useItem.isEmpty()) { + this.useItem = ItemStack.EMPTY; +- this.useItemRemaining = 0; ++ // Paper start - lag compensate eating ++ this.useItemRemaining = this.totalEatTimeTicks = 0; ++ this.eatStartTime = -1L; ++ // Paper end - lag compensate eating + } + } + +@@ -4067,7 +4082,10 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + this.useItem = ItemStack.EMPTY; +- this.useItemRemaining = 0; ++ // Paper start - lag compensate eating ++ this.useItemRemaining = this.totalEatTimeTicks = 0; ++ this.eatStartTime = -1L; ++ // Paper end - lag compensate eating + } + + public boolean isBlocking() { diff --git a/patches/server/1038-Optimise-nearby-player-retrieval.patch b/patches/server/1032-Optimise-nearby-player-retrieval.patch similarity index 100% rename from patches/server/1038-Optimise-nearby-player-retrieval.patch rename to patches/server/1032-Optimise-nearby-player-retrieval.patch diff --git a/patches/server/1032-Properly-resend-entities.patch b/patches/server/1032-Properly-resend-entities.patch deleted file mode 100644 index f8a035f11281..000000000000 --- a/patches/server/1032-Properly-resend-entities.patch +++ /dev/null @@ -1,216 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Wed, 7 Dec 2022 17:25:19 -0500 -Subject: [PATCH] Properly resend entities - -This resolves some issues which caused entities to not be resent correctly. -Entities that are interacted with need to be resent to the client, so we resend all the entity -data to the player whilst making sure not to clear dirty entries from the tracker. This makes -sure that values will be correctly updated to other players. - -This also adds utilities to aid in further preventing entity desyncs. - -This also also fixes the bug causing cancelling PlayerInteractEvent to cause items to continue -to be used despite being cancelled on the server. - -For example, items being consumed but never finishing, shields being put up, etc. -The underlying issue of this is that the client modifies their synced data values, -and so we have to (forcibly) resend them in order for the client to reset their using item state. - -See: https://github.com/PaperMC/Paper/pull/1896 - -== AT == -public net.minecraft.server.level.ChunkMap$TrackedEntity serverEntity - -diff --git a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java -index 07a362f9e485d0d507f16f1dda1ac84ade07ab27..58b602e550258c1062ee940bc46538dac95d8979 100644 ---- a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java -+++ b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java -@@ -275,14 +275,63 @@ public class SynchedEntityData { - // CraftBukkit start - public void refresh(ServerPlayer to) { - if (!this.isEmpty()) { -- List> list = this.getNonDefaultValues(); -+ List> list = this.packAll(); // Paper - Update EVERYTHING not just not default - - if (list != null) { -+ if (to.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - to.connection.send(new ClientboundSetEntityDataPacket(this.entity.getId(), list)); -+ } // Paper - } - } - } - // CraftBukkit end -+ // Paper start -+ // We need to pack all as we cannot rely on "non default values" or "dirty" ones. -+ // Because these values can possibly be desynced on the client. -+ @Nullable -+ private List> packAll() { -+ if (this.isEmpty()) { -+ return null; -+ } -+ -+ List> list = new ArrayList<>(); -+ for (DataItem dataItem : this.itemsById.values()) { -+ list.add(dataItem.value()); -+ } -+ -+ return list; -+ } -+ -+ // This method should only be used if the data of an entity could have became desynced -+ // due to interactions on the client. -+ public void resendPossiblyDesyncedEntity(ServerPlayer player) { -+ if (this.entity.tracker == null) { -+ return; -+ } -+ -+ if (player.getBukkitEntity().canSee(entity.getBukkitEntity())) { -+ net.minecraft.server.level.ServerEntity serverEntity = this.entity.tracker.serverEntity; -+ -+ List> list = new ArrayList<>(); -+ serverEntity.sendPairingData(player, list::add); -+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundBundlePacket(list)); -+ } -+ } -+ -+ // This method allows you to specifically resend certain data accessor keys to the client -+ public void resendPossiblyDesyncedDataValues(List> keys, ServerPlayer to) { -+ if (!to.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { -+ return; -+ } -+ List> values = new ArrayList<>(keys.size()); -+ for (EntityDataAccessor key : keys) { -+ SynchedEntityData.DataItem synchedValue = this.getItem(key); -+ values.add(synchedValue.value()); -+ } -+ -+ to.connection.send(new ClientboundSetEntityDataPacket(this.entity.getId(), values)); -+ } -+ // Paper end - - public static class DataItem { - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 692a01b52a71e26887ee42cbd5fd64b0a81bfc99..ef3048a4748113538a0ee0af5b526b2cd51d5c29 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -561,6 +561,7 @@ public class ServerPlayerGameMode { - } - // Paper end - extend Player Interact cancellation - player.getBukkitEntity().updateInventory(); // SPIGOT-2867 -+ this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items - enuminteractionresult = (event.useItemInHand() != Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS; - } else if (this.gameModeForPlayer == GameType.SPECTATOR) { - MenuProvider itileinventory = iblockdata.getMenuProvider(world, blockposition); -@@ -604,6 +605,11 @@ public class ServerPlayerGameMode { - - return enuminteractionresult1; - } -+ // Paper start - Properly cancel usable items; Cancel only if cancelled + if the interact result is different from default response -+ else if (this.interactResult && this.interactResult != cancelledItem) { -+ this.player.resyncUsingItem(this.player); -+ } -+ // Paper end - Properly cancel usable items - } - return enuminteractionresult; - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 9a080c64f0478081a3ef5ae601978063ec3756da..9285346c99a15f133e985f217c4d8f75e8f2726f 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1990,6 +1990,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - if (cancelled) { -+ this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items - this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524 - return; - } -@@ -2704,7 +2705,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a - if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { -- ServerGamePacketListenerImpl.this.send(new ClientboundAddEntityPacket(entity)); -+ entity.getEntityData().resendPossiblyDesyncedEntity(player); // Paper - The entire mob gets deleted, so resend it. - ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); - } - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 4816897a82c569717bf7ea139a55ab3fd931a63e..91feb12732564c90656da487664dbc12e55397fc 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -390,7 +390,7 @@ public abstract class PlayerList { - ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now - // CraftBukkit end - -- player.getEntityData().refresh(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn -+ //player.getEntityData().refresh(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn Paper - THIS IS NOT NEEDED ANYMORE - - this.sendLevelInfo(player, worldserver1); - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 7d9890670b47a51839784914b0b96a994c1c6ace..1ce4cf3b601cc3b002c1453cc105c1278264cc31 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3823,6 +3823,11 @@ public abstract class LivingEntity extends Entity implements Attackable { - return ((Byte) this.entityData.get(LivingEntity.DATA_LIVING_ENTITY_FLAGS) & 2) > 0 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND; - } - -+ // Paper start - Properly cancel usable items -+ public void resyncUsingItem(ServerPlayer serverPlayer) { -+ this.getEntityData().resendPossiblyDesyncedDataValues(java.util.List.of(DATA_LIVING_ENTITY_FLAGS), serverPlayer); -+ } -+ // Paper end - Properly cancel usable items - private void updatingUsingItem() { - if (this.isUsingItem()) { - if (ItemStack.isSameItem(this.getItemInHand(this.getUsedItemHand()), this.useItem)) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/Bucketable.java b/src/main/java/net/minecraft/world/entity/animal/Bucketable.java -index 37596c7b65f280be00e8e59ae18bd1aceae21080..eca18540aeb0b0d4098477d73b14c78a7bf9f455 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bucketable.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bucketable.java -@@ -109,8 +109,7 @@ public interface Bucketable { - itemstack1 = CraftItemStack.asNMSCopy(playerBucketFishEvent.getEntityBucket()); - if (playerBucketFishEvent.isCancelled()) { - ((ServerPlayer) player).containerMenu.sendAllDataToRemote(); // We need to update inventory to resync client's bucket -- ((ServerPlayer) player).connection.send(new ClientboundAddEntityPacket(entity)); // We need to play out these packets as the client assumes the fish is gone -- entity.getEntityData().refresh((ServerPlayer) player); // Need to send data such as the display name to client -+ entity.getEntityData().resendPossiblyDesyncedEntity((ServerPlayer) player); // Paper - return Optional.of(InteractionResult.FAIL); - } - entity.playSound(((Bucketable) entity).getPickupSound(), 1.0F, 1.0F); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 44dd60c1f31b578e7630673433f3850f392b7a0d..8698104e3eb98e2cc5da5de87a8f538860c1d91d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -999,7 +999,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return; - } - -- entityTracker.broadcast(this.getHandle().getAddEntityPacket()); -+ // Paper start, resend possibly desynced entity instead of add entity packet -+ for (ServerPlayerConnection playerConnection : entityTracker.seenBy) { -+ this.getHandle().getEntityData().resendPossiblyDesyncedEntity(playerConnection.getPlayer()); -+ } -+ // Paper end - } - - private static PermissibleBase getPermissibleBase() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java -index 0801bcdee8fcff0d388d302387e4f1d587e0a283..2fcd9b836d42e3549a3b6b921c57a4c103146dff 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java -@@ -39,9 +39,11 @@ public class CraftItemFrame extends CraftHanging implements ItemFrame { - protected void update() { - super.update(); - -+ // Paper start, don't mark as dirty as this is handled in super.update() - // mark dirty, so that the client gets updated with item and rotation -- this.getHandle().getEntityData().markDirty(net.minecraft.world.entity.decoration.ItemFrame.DATA_ITEM); -- this.getHandle().getEntityData().markDirty(net.minecraft.world.entity.decoration.ItemFrame.DATA_ROTATION); -+ //this.getHandle().getEntityData().markDirty(net.minecraft.world.entity.decoration.ItemFrame.DATA_ITEM); -+ //this.getHandle().getEntityData().markDirty(net.minecraft.world.entity.decoration.ItemFrame.DATA_ROTATION); -+ // Paper end - - // update redstone - if (!this.getHandle().generation) { diff --git a/patches/server/1039-Distance-manager-tick-timings.patch b/patches/server/1033-Distance-manager-tick-timings.patch similarity index 100% rename from patches/server/1039-Distance-manager-tick-timings.patch rename to patches/server/1033-Distance-manager-tick-timings.patch diff --git a/patches/server/1033-Optimize-Hoppers.patch b/patches/server/1033-Optimize-Hoppers.patch deleted file mode 100644 index 369c4bee43de..000000000000 --- a/patches/server/1033-Optimize-Hoppers.patch +++ /dev/null @@ -1,750 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 27 Apr 2016 22:09:52 -0400 -Subject: [PATCH] Optimize Hoppers - -* Removes unnecessary extra calls to .update() that are very expensive -* Lots of itemstack cloning removed. Only clone if the item is actually moved -* Return true when a plugin cancels inventory move item event instead of false, as false causes pulls to cycle through all items. - However, pushes do not exhibit the same behavior, so this is not something plugins could of been relying on. -* Add option (Default on) to cooldown hoppers when they fail to move an item due to full inventory -* Skip subsequent InventoryMoveItemEvents if a plugin does not use the item after first event fire for an iteration by tracking changes to the event via an internal event implementation. -* Don't check for Entities with Inventories if the block above us is also occluding (not just Inventoried) -* Remove Streams from Item Suck In and restore restore 1.12 AABB checks which is simpler and no voxel allocations (was doing TWO Item Suck ins) - -diff --git a/src/main/java/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java b/src/main/java/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5c42823726e70ce6c9d0121d074315488e8b3f60 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java -@@ -0,0 +1,31 @@ -+package io.papermc.paper.event.inventory; -+ -+import org.bukkit.event.inventory.InventoryMoveItemEvent; -+import org.bukkit.inventory.Inventory; -+import org.bukkit.inventory.ItemStack; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+import org.jetbrains.annotations.NotNull; -+ -+@DefaultQualifier(NonNull.class) -+public class PaperInventoryMoveItemEvent extends InventoryMoveItemEvent { -+ -+ public boolean calledSetItem; -+ public boolean calledGetItem; -+ -+ public PaperInventoryMoveItemEvent(final @NotNull Inventory sourceInventory, final @NotNull ItemStack itemStack, final @NotNull Inventory destinationInventory, final boolean didSourceInitiate) { -+ super(sourceInventory, itemStack, destinationInventory, didSourceInitiate); -+ } -+ -+ @Override -+ public ItemStack getItem() { -+ this.calledGetItem = true; -+ return super.getItem(); -+ } -+ -+ @Override -+ public void setItem(final ItemStack itemStack) { -+ super.setItem(itemStack); -+ this.calledSetItem = true; -+ } -+} -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index d2526c39c91dd62ad676f04afc45332d774528d1..f12bf36a247a47b8d831536a4fefd2a2ce424494 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1692,6 +1692,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent - worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent -+ net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers - - this.profiler.push(() -> { - return worldserver + " " + worldserver.dimension().location(); -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 2470acc82292bedd930be404a2e1d1f8fad700e1..ed27a963223bfe18310ad5adabf461b5e307ef9c 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -751,10 +751,16 @@ public final class ItemStack { - } - - public ItemStack copy() { -- if (this.isEmpty()) { -+ // Paper start - Perf: Optimize Hoppers -+ return this.copy(false); -+ } -+ -+ public ItemStack copy(boolean originalItem) { -+ if (!originalItem && this.isEmpty()) { -+ // Paper end - Perf: Optimize Hoppers - return ItemStack.EMPTY; - } else { -- ItemStack itemstack = new ItemStack(this.getItem(), this.count); -+ ItemStack itemstack = new ItemStack(originalItem ? this.item : this.getItem(), this.count); // Paper - Perf: Optimize Hoppers - - itemstack.setPopTime(this.getPopTime()); - if (this.tag != null) { -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -index 20201430ee8f28245aa845acb172d0f5d80458ff..9ea74d37cd951e0dc76d20ed8234b5871035566c 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -@@ -26,6 +26,7 @@ import co.aikar.timings.MinecraftTimings; // Paper - import co.aikar.timings.Timing; // Paper - - public abstract class BlockEntity { -+ static boolean ignoreTileUpdates; // Paper - Perf: Optimize Hoppers - - public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper - // CraftBukkit start - data containers -@@ -161,6 +162,7 @@ public abstract class BlockEntity { - - public void setChanged() { - if (this.level != null) { -+ if (ignoreTileUpdates) return; // Paper - Perf: Optimize Hoppers - BlockEntity.setChanged(this.level, this.worldPosition, this.blockState); - } - -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 321d30f1c0d3838b9c3d210eedb03aa59e0761d8..a61d7cd2b078fe511ff00344197b6ea11feebfb2 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 -@@ -151,6 +151,43 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - - } - -+ // Paper start - Perf: Optimize Hoppers -+ private static final int HOPPER_EMPTY = 0; -+ private static final int HOPPER_HAS_ITEMS = 1; -+ private static final int HOPPER_IS_FULL = 2; -+ -+ private static int getFullState(final HopperBlockEntity tileEntity) { -+ tileEntity.unpackLootTable(null); -+ -+ final List hopperItems = tileEntity.getItems(); -+ -+ boolean empty = true; -+ boolean full = true; -+ -+ for (int i = 0, len = hopperItems.size(); i < len; ++i) { -+ final ItemStack stack = hopperItems.get(i); -+ if (stack.isEmpty()) { -+ full = false; -+ continue; -+ } -+ -+ if (!full) { -+ // can't be full -+ return HOPPER_HAS_ITEMS; -+ } -+ -+ empty = false; -+ -+ if (stack.getCount() != stack.getMaxStackSize()) { -+ // can't be full or empty -+ return HOPPER_HAS_ITEMS; -+ } -+ } -+ -+ return empty ? HOPPER_EMPTY : (full ? HOPPER_IS_FULL : HOPPER_HAS_ITEMS); -+ } -+ // Paper end - Perf: Optimize Hoppers -+ - private static boolean tryMoveItems(Level world, BlockPos pos, BlockState state, HopperBlockEntity blockEntity, BooleanSupplier booleansupplier) { - if (world.isClientSide) { - return false; -@@ -158,11 +195,13 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - if (!blockEntity.isOnCooldown() && (Boolean) state.getValue(HopperBlock.ENABLED)) { - boolean flag = false; - -- if (!blockEntity.isEmpty()) { -+ int fullState = getFullState(blockEntity); // Paper - Perf: Optimize Hoppers -+ -+ if (fullState != HOPPER_EMPTY) { // Paper - Perf: Optimize Hopperss - flag = HopperBlockEntity.ejectItems(world, pos, state, (Container) blockEntity, blockEntity); // CraftBukkit - } - -- if (!blockEntity.inventoryFull()) { -+ if (fullState != HOPPER_IS_FULL || flag) { // Paper - Perf: Optimize Hoppers - flag |= booleansupplier.getAsBoolean(); - } - -@@ -193,6 +232,202 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - return false; - } - -+ // Paper start - Perf: Optimize Hoppers -+ private static boolean skipPullModeEventFire; -+ private static boolean skipPushModeEventFire; -+ public static boolean skipHopperEvents; -+ -+ private static boolean hopperPush(final Level level, final Container destination, final Direction direction, final HopperBlockEntity hopper) { -+ skipPushModeEventFire = skipHopperEvents; -+ boolean foundItem = false; -+ for (int i = 0; i < hopper.getContainerSize(); ++i) { -+ final ItemStack item = hopper.getItem(i); -+ if (!item.isEmpty()) { -+ foundItem = true; -+ ItemStack origItemStack = item; -+ ItemStack movedItem = origItemStack; -+ -+ final int originalItemCount = origItemStack.getCount(); -+ final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); -+ origItemStack.setCount(movedItemCount); -+ -+ // We only need to fire the event once to give protection plugins a chance to cancel this event -+ // Because nothing uses getItem, every event call should end up the same result. -+ if (!skipPushModeEventFire) { -+ movedItem = callPushMoveEvent(destination, movedItem, hopper); -+ if (movedItem == null) { // cancelled -+ origItemStack.setCount(originalItemCount); -+ return false; -+ } -+ } -+ -+ final ItemStack remainingItem = addItem(hopper, destination, movedItem, direction); -+ final int remainingItemCount = remainingItem.getCount(); -+ if (remainingItemCount != movedItemCount) { -+ origItemStack = origItemStack.copy(true); -+ origItemStack.setCount(originalItemCount); -+ if (!origItemStack.isEmpty()) { -+ origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); -+ } -+ hopper.setItem(i, origItemStack); -+ destination.setChanged(); -+ return true; -+ } -+ origItemStack.setCount(originalItemCount); -+ } -+ } -+ if (foundItem && level.paperConfig().hopper.cooldownWhenFull) { // Inventory was full - cooldown -+ hopper.setCooldown(level.spigotConfig.hopperTransfer); -+ } -+ return false; -+ } -+ -+ private static boolean hopperPull(final Level level, final Hopper hopper, final Container container, ItemStack origItemStack, final int i) { -+ ItemStack movedItem = origItemStack; -+ final int originalItemCount = origItemStack.getCount(); -+ final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); -+ container.setChanged(); // original logic always marks source inv as changed even if no move happens. -+ movedItem.setCount(movedItemCount); -+ -+ if (!skipPullModeEventFire) { -+ movedItem = callPullMoveEvent(hopper, container, movedItem); -+ if (movedItem == null) { // cancelled -+ origItemStack.setCount(originalItemCount); -+ // Drastically improve performance by returning true. -+ // No plugin could of relied on the behavior of false as the other call -+ // site for IMIE did not exhibit the same behavior -+ return true; -+ } -+ } -+ -+ final ItemStack remainingItem = addItem(container, hopper, movedItem, null); -+ final int remainingItemCount = remainingItem.getCount(); -+ if (remainingItemCount != movedItemCount) { -+ origItemStack = origItemStack.copy(true); -+ origItemStack.setCount(originalItemCount); -+ if (!origItemStack.isEmpty()) { -+ origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); -+ } -+ -+ ignoreTileUpdates = true; -+ container.setItem(i, origItemStack); -+ ignoreTileUpdates = false; -+ container.setChanged(); -+ return true; -+ } -+ origItemStack.setCount(originalItemCount); -+ -+ if (level.paperConfig().hopper.cooldownWhenFull) { -+ cooldownHopper(hopper); -+ } -+ -+ return false; -+ } -+ -+ @Nullable -+ private static ItemStack callPushMoveEvent(Container iinventory, ItemStack itemstack, HopperBlockEntity hopper) { -+ final Inventory destinationInventory = getInventory(iinventory); -+ final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(hopper.getOwner(false).getInventory(), -+ CraftItemStack.asCraftMirror(itemstack), destinationInventory, true); -+ final boolean result = event.callEvent(); -+ if (!event.calledGetItem && !event.calledSetItem) { -+ skipPushModeEventFire = true; -+ } -+ if (!result) { -+ cooldownHopper(hopper); -+ return null; -+ } -+ -+ if (event.calledSetItem) { -+ return CraftItemStack.asNMSCopy(event.getItem()); -+ } else { -+ return itemstack; -+ } -+ } -+ -+ @Nullable -+ private static ItemStack callPullMoveEvent(final Hopper hopper, final Container container, final ItemStack itemstack) { -+ final Inventory sourceInventory = getInventory(container); -+ final Inventory destination = getInventory(hopper); -+ -+ // Mirror is safe as no plugins ever use this item -+ final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(sourceInventory, CraftItemStack.asCraftMirror(itemstack), destination, false); -+ final boolean result = event.callEvent(); -+ if (!event.calledGetItem && !event.calledSetItem) { -+ skipPullModeEventFire = true; -+ } -+ if (!result) { -+ cooldownHopper(hopper); -+ return null; -+ } -+ -+ if (event.calledSetItem) { -+ return CraftItemStack.asNMSCopy(event.getItem()); -+ } else { -+ return itemstack; -+ } -+ } -+ -+ private static Inventory getInventory(final Container container) { -+ final Inventory sourceInventory; -+ if (container instanceof CompoundContainer compoundContainer) { -+ // Have to special-case large chests as they work oddly -+ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); -+ } else if (container instanceof BlockEntity blockEntity) { -+ sourceInventory = blockEntity.getOwner(false).getInventory(); -+ } else if (container.getOwner() != null) { -+ sourceInventory = container.getOwner().getInventory(); -+ } else { -+ sourceInventory = new CraftInventory(container); -+ } -+ return sourceInventory; -+ } -+ -+ private static void cooldownHopper(final Hopper hopper) { -+ if (hopper instanceof HopperBlockEntity blockEntity && blockEntity.getLevel() != null) { -+ blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer); -+ } -+ } -+ -+ private static boolean allMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate test) { -+ if (iinventory instanceof WorldlyContainer) { -+ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) { -+ if (!test.test(iinventory.getItem(i), i)) { -+ return false; -+ } -+ } -+ } else { -+ int size = iinventory.getContainerSize(); -+ for (int i = 0; i < size; i++) { -+ if (!test.test(iinventory.getItem(i), i)) { -+ return false; -+ } -+ } -+ } -+ return true; -+ } -+ -+ private static boolean anyMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate test) { -+ if (iinventory instanceof WorldlyContainer) { -+ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) { -+ if (test.test(iinventory.getItem(i), i)) { -+ return true; -+ } -+ } -+ } else { -+ int size = iinventory.getContainerSize(); -+ for (int i = 0; i < size; i++) { -+ if (test.test(iinventory.getItem(i), i)) { -+ return true; -+ } -+ } -+ } -+ return true; -+ } -+ private static final java.util.function.BiPredicate STACK_SIZE_TEST = (itemstack, i) -> itemstack.getCount() >= itemstack.getMaxStackSize(); -+ private static final java.util.function.BiPredicate IS_EMPTY_TEST = (itemstack, i) -> itemstack.isEmpty(); -+ // Paper end - Perf: Optimize Hoppers -+ - private static boolean ejectItems(Level world, BlockPos blockposition, BlockState iblockdata, Container iinventory, HopperBlockEntity hopper) { // CraftBukkit - Container iinventory1 = HopperBlockEntity.getAttachedContainer(world, blockposition, iblockdata); - -@@ -204,46 +439,49 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - if (HopperBlockEntity.isFullContainer(iinventory1, enumdirection)) { - return false; - } else { -- for (int i = 0; i < iinventory.getContainerSize(); ++i) { -- if (!iinventory.getItem(i).isEmpty()) { -- ItemStack itemstack = iinventory.getItem(i).copy(); -- // ItemStack itemstack1 = addItem(iinventory, iinventory1, iinventory.removeItem(i, 1), enumdirection); -- -- // CraftBukkit start - Call event when pushing items into other inventories -- CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot -- -- Inventory destinationInventory; -- // Have to special case large chests as they work oddly -- if (iinventory1 instanceof CompoundContainer) { -- destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory1); -- } else if (iinventory1.getOwner() != null) { -- destinationInventory = iinventory1.getOwner().getInventory(); -- } else { -- destinationInventory = new CraftInventory(iinventory); -- } -- -- InventoryMoveItemEvent event = new InventoryMoveItemEvent(iinventory.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true); -- world.getCraftServer().getPluginManager().callEvent(event); -- if (event.isCancelled()) { -- hopper.setItem(i, itemstack); -- hopper.setCooldown(world.spigotConfig.hopperTransfer); // Spigot -- return false; -- } -- int origCount = event.getItem().getAmount(); // Spigot -- ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, iinventory1, CraftItemStack.asNMSCopy(event.getItem()), enumdirection); -+ // Paper start - replace logic; MAKE SURE TO CHECK FOR DIFFS ON UPDATES -+ return hopperPush(world, iinventory1, enumdirection, hopper); -+ // for (int i = 0; i < iinventory.getContainerSize(); ++i) { -+ // if (!iinventory.getItem(i).isEmpty()) { -+ // ItemStack itemstack = iinventory.getItem(i).copy(); -+ // // ItemStack itemstack1 = addItem(iinventory, iinventory1, iinventory.removeItem(i, 1), enumdirection); -+ -+ // // CraftBukkit start - Call event when pushing items into other inventories -+ // CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot -+ -+ // Inventory destinationInventory; -+ // // Have to special case large chests as they work oddly -+ // if (iinventory1 instanceof CompoundContainer) { -+ // destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory1); -+ // } else if (iinventory1.getOwner() != null) { -+ // destinationInventory = iinventory1.getOwner().getInventory(); -+ // } else { -+ // destinationInventory = new CraftInventory(iinventory); -+ // } -+ -+ // InventoryMoveItemEvent event = new InventoryMoveItemEvent(iinventory.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true); -+ // world.getCraftServer().getPluginManager().callEvent(event); -+ // if (event.isCancelled()) { -+ // hopper.setItem(i, itemstack); -+ // hopper.setCooldown(world.spigotConfig.hopperTransfer); // Spigot -+ // return false; -+ // } -+ // int origCount = event.getItem().getAmount(); // Spigot -+ // ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, iinventory1, CraftItemStack.asNMSCopy(event.getItem()), enumdirection); - // CraftBukkit end - -- if (itemstack1.isEmpty()) { -- iinventory1.setChanged(); -- return true; -- } -+ // if (itemstack1.isEmpty()) { -+ // iinventory1.setChanged(); -+ // return true; -+ // } - -- itemstack.shrink(origCount - itemstack1.getCount()); // Spigot -- iinventory.setItem(i, itemstack); -- } -- } -+ // itemstack.shrink(origCount - itemstack1.getCount()); // Spigot -+ // iinventory.setItem(i, itemstack); -+ // } -+ // } - -- return false; -+ // return false; -+ // Paper end - Perf: Optimize Hoppers - } - } - } -@@ -253,17 +491,29 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - } - - private static boolean isFullContainer(Container inventory, Direction direction) { -- return HopperBlockEntity.getSlots(inventory, direction).allMatch((i) -> { -- ItemStack itemstack = inventory.getItem(i); -- -- return itemstack.getCount() >= itemstack.getMaxStackSize(); -- }); -+ // Paper start - Perf: Optimize Hoppers -+ if (inventory instanceof WorldlyContainer worldlyContainer) { -+ for (final int slot : worldlyContainer.getSlotsForFace(direction)) { -+ final ItemStack stack = inventory.getItem(slot); -+ if (stack.getCount() < stack.getMaxStackSize()) { -+ return false; -+ } -+ } -+ return true; -+ } else { -+ for (int slot = 0, max = inventory.getContainerSize(); slot < max; ++slot) { -+ final ItemStack stack = inventory.getItem(slot); -+ if (stack.getCount() < stack.getMaxStackSize()) { -+ return false; -+ } -+ } -+ return true; -+ } -+ // Paper end - Perf: Optimize Hoppers - } - - private static boolean isEmptyContainer(Container inv, Direction facing) { -- return HopperBlockEntity.getSlots(inv, facing).allMatch((i) -> { -- return inv.getItem(i).isEmpty(); -- }); -+ return allMatch(inv, facing, IS_EMPTY_TEST); // Paper - Perf: Optimize Hoppers - } - - public static boolean suckInItems(Level world, Hopper hopper) { -@@ -272,9 +522,33 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - if (iinventory != null) { - Direction enumdirection = Direction.DOWN; - -- return HopperBlockEntity.isEmptyContainer(iinventory, enumdirection) ? false : HopperBlockEntity.getSlots(iinventory, enumdirection).anyMatch((i) -> { -- return HopperBlockEntity.a(hopper, iinventory, i, enumdirection, world); // Spigot -- }); -+ // Paper start - Perf: Optimize Hoppers -+ skipPullModeEventFire = skipHopperEvents; -+ // merge container isEmpty check and move logic into one loop -+ if (iinventory instanceof WorldlyContainer worldlyContainer) { -+ for (final int slot : worldlyContainer.getSlotsForFace(enumdirection)) { -+ ItemStack item = worldlyContainer.getItem(slot); -+ if (item.isEmpty() || !canTakeItemFromContainer(hopper, iinventory, item, slot, enumdirection)) { -+ continue; -+ } -+ if (hopperPull(world, hopper, iinventory, item, slot)) { -+ return true; -+ } -+ } -+ return false; -+ } else { -+ for (int slot = 0, max = iinventory.getContainerSize(); slot < max; ++slot) { -+ ItemStack item = iinventory.getItem(slot); -+ if (item.isEmpty() || !canTakeItemFromContainer(hopper, iinventory, item, slot, enumdirection)) { -+ continue; -+ } -+ if (hopperPull(world, hopper, iinventory, item, slot)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ // Paper end - Perf: Optimize Hoppers - } else { - Iterator iterator = HopperBlockEntity.getItemsAtAndAbove(world, hopper).iterator(); - -@@ -292,48 +566,52 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - } - } - -+ @io.papermc.paper.annotation.DoNotUse // Paper - method unused as logic is inlined above - private static boolean a(Hopper ihopper, Container iinventory, int i, Direction enumdirection, Level world) { // Spigot - ItemStack itemstack = iinventory.getItem(i); - -- if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(ihopper, iinventory, itemstack, i, enumdirection)) { -- ItemStack itemstack1 = itemstack.copy(); -- // ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.removeItem(i, 1), (EnumDirection) null); -- // CraftBukkit start - Call event on collection of items from inventories into the hopper -- CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot -- -- Inventory sourceInventory; -- // Have to special case large chests as they work oddly -- if (iinventory instanceof CompoundContainer) { -- sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); -- } else if (iinventory.getOwner() != null) { -- sourceInventory = iinventory.getOwner().getInventory(); -- } else { -- sourceInventory = new CraftInventory(iinventory); -- } -- -- InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), ihopper.getOwner().getInventory(), false); -- -- Bukkit.getServer().getPluginManager().callEvent(event); -- if (event.isCancelled()) { -- iinventory.setItem(i, itemstack1); -- -- if (ihopper instanceof HopperBlockEntity) { -- ((HopperBlockEntity) ihopper).setCooldown(world.spigotConfig.hopperTransfer); // Spigot -- } -- -- return false; -- } -- int origCount = event.getItem().getAmount(); // Spigot -- ItemStack itemstack2 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null); -- // CraftBukkit end -- -- if (itemstack2.isEmpty()) { -- iinventory.setChanged(); -- return true; -- } -- -- itemstack1.shrink(origCount - itemstack2.getCount()); // Spigot -- iinventory.setItem(i, itemstack1); -+ // Paper start - Perf: Optimize Hoppers; replace pull logic; MAKE SURE TO CHECK FOR DIFFS WHEN UPDATING -+ if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(ihopper, iinventory, itemstack, i, enumdirection)) { // If this logic changes, update above. this is left unused incase reflective plugins -+ return hopperPull(world, ihopper, iinventory, itemstack, i); -+ // ItemStack itemstack1 = itemstack.copy(); -+ // // ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.removeItem(i, 1), (EnumDirection) null); -+ // // CraftBukkit start - Call event on collection of items from inventories into the hopper -+ // CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot -+ -+ // Inventory sourceInventory; -+ // // Have to special case large chests as they work oddly -+ // if (iinventory instanceof CompoundContainer) { -+ // sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); -+ // } else if (iinventory.getOwner() != null) { -+ // sourceInventory = iinventory.getOwner().getInventory(); -+ // } else { -+ // sourceInventory = new CraftInventory(iinventory); -+ // } -+ -+ // InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), ihopper.getOwner().getInventory(), false); -+ -+ // Bukkit.getServer().getPluginManager().callEvent(event); -+ // if (event.isCancelled()) { -+ // iinventory.setItem(i, itemstack1); -+ -+ // if (ihopper instanceof HopperBlockEntity) { -+ // ((HopperBlockEntity) ihopper).setCooldown(world.spigotConfig.hopperTransfer); // Spigot -+ // } -+ -+ // return false; -+ // } -+ // int origCount = event.getItem().getAmount(); // Spigot -+ // ItemStack itemstack2 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null); -+ // // CraftBukkit end -+ -+ // if (itemstack2.isEmpty()) { -+ // iinventory.setChanged(); -+ // return true; -+ // } -+ -+ // itemstack1.shrink(origCount - itemstack2.getCount()); // Spigot -+ // iinventory.setItem(i, itemstack1); -+ // Paper end - Perf: Optimize Hoppers - } - - return false; -@@ -342,12 +620,14 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - public static boolean addItem(Container inventory, ItemEntity itemEntity) { - boolean flag = false; - // CraftBukkit start -- InventoryPickupItemEvent event = new InventoryPickupItemEvent(inventory.getOwner().getInventory(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); -+ if (InventoryPickupItemEvent.getHandlerList().getRegisteredListeners().length > 0) { // Paper - optimize hoppers -+ InventoryPickupItemEvent event = new InventoryPickupItemEvent(getInventory(inventory), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); // Paper - Perf: Optimize Hoppers; use getInventory() to avoid snapshot creation - itemEntity.level().getCraftServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { - return false; - } - // CraftBukkit end -+ } // Paper - Perf: Optimize Hoppers - ItemStack itemstack = itemEntity.getItem().copy(); - ItemStack itemstack1 = HopperBlockEntity.addItem((Container) null, inventory, itemstack, (Direction) null); - -@@ -443,7 +723,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - stack = stack.split(to.getMaxStackSize()); - } - // Spigot end -+ ignoreTileUpdates = true; // Paper - Perf: Optimize Hoppers - to.setItem(slot, stack); -+ ignoreTileUpdates = false; // Paper - Perf: Optimize Hoppers - stack = leftover; // Paper - Make hoppers respect inventory max stack size - flag = true; - } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) { -@@ -517,19 +799,47 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - // CraftBukkit end - } - -+ // Paper start - Perf: Optimize Hoppers -+ static final AABB HOPPER_ITEM_SUCK_OVERALL = Hopper.SUCK.bounds(); -+ static final AABB[] HOPPER_ITEM_SUCK_INDIVIDUAL = Hopper.SUCK.toAabbs().toArray(new AABB[0]); -+ // Paper end - Perf: Optimize Hoppers -+ - public static List getItemsAtAndAbove(Level world, Hopper hopper) { -- return (List) hopper.getSuckShape().toAabbs().stream().flatMap((axisalignedbb) -> { -- return world.getEntitiesOfClass(ItemEntity.class, axisalignedbb.move(hopper.getLevelX() - 0.5D, hopper.getLevelY() - 0.5D, hopper.getLevelZ() - 0.5D), EntitySelector.ENTITY_STILL_ALIVE).stream(); -- }).collect(Collectors.toList()); -+ // Paper start - Perf: Optimize Hoppers -+ // eliminate multiple getEntitiesOfClass() but maintain the voxelshape collision by moving -+ // the individual AABB checks into the predicate -+ final double shiftX = hopper.getLevelX() - 0.5D; -+ final double shiftY = hopper.getLevelY() - 0.5D; -+ final double shiftZ = hopper.getLevelZ() - 0.5D; -+ return world.getEntitiesOfClass(ItemEntity.class, HOPPER_ITEM_SUCK_OVERALL.move(shiftX, shiftY, shiftZ), (final Entity entity) -> { -+ if (!entity.isAlive()) { // EntitySelector.ENTITY_STILL_ALIVE -+ return false; -+ } -+ -+ for (final AABB aabb : HOPPER_ITEM_SUCK_INDIVIDUAL) { -+ if (aabb.move(shiftX, shiftY, shiftZ).intersects(entity.getBoundingBox())) { -+ return true; -+ } -+ } -+ -+ return false; -+ }); -+ // Paper end - Perf: Optimize Hoppers - } - - @Nullable - public static Container getContainerAt(Level world, BlockPos pos) { -- return HopperBlockEntity.getContainerAt(world, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D); -+ return HopperBlockEntity.getContainerAt(world, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, true); // Paper - Perf: Optimize Hoppers - } - - @Nullable - private static Container getContainerAt(Level world, double x, double y, double z) { -+ // Paper start - Perf: Optimize Hoppers -+ return HopperBlockEntity.getContainerAt(world, x, y, z, false); -+ } -+ @Nullable -+ private static Container getContainerAt(Level world, double x, double y, double z, final boolean optimizeEntities) { -+ // Paper end - Perf: Optimize Hoppers - Object object = null; - BlockPos blockposition = BlockPos.containing(x, y, z); - if ( !world.spigotConfig.hopperCanLoadChunks && !world.hasChunkAt( blockposition ) ) return null; // Spigot -@@ -549,8 +859,8 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - } - } - -- if (object == null) { -- List list = world.getEntities((Entity) null, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); -+ if (object == null && (!optimizeEntities || !world.paperConfig().hopper.ignoreOccludingBlocks || !iblockdata.getBukkitMaterial().isOccluding())) { // Paper - Perf: Optimize Hoppers -+ List list = world.getEntitiesOfClass((Class)Container.class, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper - Perf: Optimize Hoppers - - if (!list.isEmpty()) { - object = (Container) list.get(world.random.nextInt(list.size())); -@@ -561,7 +871,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - } - - private static boolean canMergeItems(ItemStack first, ItemStack second) { -- return first.getCount() <= first.getMaxStackSize() && ItemStack.isSameItemSameTags(first, second); -+ return first.getCount() < first.getMaxStackSize() && first.is(second.getItem()) && first.getDamageValue() == second.getDamageValue() && ((first.isEmpty() && second.isEmpty()) || java.util.Objects.equals(first.getTag(), second.getTag())); // Paper - Perf: Optimize Hoppers; used to return true for full itemstacks?! - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java -index 7cbd403f9e96e7ce35475c8102cd9f9c04819c27..a94300a457b25f0e33a8eeabba6dd5720ca9ab1e 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java -@@ -93,12 +93,19 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc - @Override - public boolean isEmpty() { - this.unpackLootTable((Player)null); -- return this.getItems().stream().allMatch(ItemStack::isEmpty); -+ // Paper start - Perf: Optimize Hoppers -+ for (final ItemStack itemStack : this.getItems()) { -+ if (!itemStack.isEmpty()) { -+ return false; -+ } -+ } -+ return true; -+ // Paper end - Perf: Optimize Hoppers - } - - @Override - public ItemStack getItem(int slot) { -- this.unpackLootTable((Player)null); -+ if (slot == 0) this.unpackLootTable((Player) null); // Paper - Perf: Optimize Hoppers - return this.getItems().get(slot); - } - diff --git a/patches/server/1040-Handle-Oversized-block-entities-in-chunks.patch b/patches/server/1034-Handle-Oversized-block-entities-in-chunks.patch similarity index 100% rename from patches/server/1040-Handle-Oversized-block-entities-in-chunks.patch rename to patches/server/1034-Handle-Oversized-block-entities-in-chunks.patch diff --git a/patches/server/1035-Actually-optimise-explosions.patch b/patches/server/1035-Actually-optimise-explosions.patch deleted file mode 100644 index da42c9e1953e..000000000000 --- a/patches/server/1035-Actually-optimise-explosions.patch +++ /dev/null @@ -1,521 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 12 Sep 2023 06:50:16 -0700 -Subject: [PATCH] Actually optimise explosions - -The vast majority of blocks an explosion of power ~4 tries -to destroy are duplicates. The core of the block destroying -part of this patch is to cache the block state, resistance, and -whether it should explode - as those will not change. - -The other part of this patch is to optimise the visibility -percentage calculation. The new visibility calculation takes -advantage of the block caching already done by the explosion logic. -It continues to update the cache as the visibility calculation -uses many rays which can overlap significantly. - -Effectively, the patch uses a lot of caching to eliminate -redundant operations. - -Performance benchmarking explosions is challenging, as it varies -depending on the power, the number of nearby entities, and the -nearby terrain. This means that no benchmark can cover all the cases. -I decided to test a giant block of TNT, as that's where the optimisations -would be needed the most. - -I tested using a 50x10x50 block of TNT above ground -and determined the following: - -Vanilla time per explosion: 2.27ms -Lithium time per explosion: 1.07ms -This patch time per explosion: 0.45ms - -The results indicate that this logic is 5 times faster than Vanilla -and 2.3 times faster than Lithium. - -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index f54219d2b973136ad00a0f03cbd99f6b82ecee65..28ef910885dbd48965fba6f08cec412697b1b7f0 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -111,6 +111,271 @@ public class Explosion { - this.yield = this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F; // CraftBukkit - } - -+ // Paper start - optimise collisions -+ private static final double[] CACHED_RAYS; -+ static { -+ final it.unimi.dsi.fastutil.doubles.DoubleArrayList rayCoords = new it.unimi.dsi.fastutil.doubles.DoubleArrayList(); -+ -+ for (int x = 0; x <= 15; ++x) { -+ for (int y = 0; y <= 15; ++y) { -+ for (int z = 0; z <= 15; ++z) { -+ if ((x == 0 || x == 15) || (y == 0 || y == 15) || (z == 0 || z == 15)) { -+ double xDir = (double)((float)x / 15.0F * 2.0F - 1.0F); -+ double yDir = (double)((float)y / 15.0F * 2.0F - 1.0F); -+ double zDir = (double)((float)z / 15.0F * 2.0F - 1.0F); -+ -+ double mag = Math.sqrt( -+ xDir * xDir + yDir * yDir + zDir * zDir -+ ); -+ -+ rayCoords.add((xDir / mag) * (double)0.3F); -+ rayCoords.add((yDir / mag) * (double)0.3F); -+ rayCoords.add((zDir / mag) * (double)0.3F); -+ } -+ } -+ } -+ } -+ -+ CACHED_RAYS = rayCoords.toDoubleArray(); -+ } -+ -+ private static final int CHUNK_CACHE_SHIFT = 2; -+ private static final int CHUNK_CACHE_MASK = (1 << CHUNK_CACHE_SHIFT) - 1; -+ private static final int CHUNK_CACHE_WIDTH = 1 << CHUNK_CACHE_SHIFT; -+ -+ private static final int BLOCK_EXPLOSION_CACHE_SHIFT = 3; -+ private static final int BLOCK_EXPLOSION_CACHE_MASK = (1 << BLOCK_EXPLOSION_CACHE_SHIFT) - 1; -+ private static final int BLOCK_EXPLOSION_CACHE_WIDTH = 1 << BLOCK_EXPLOSION_CACHE_SHIFT; -+ -+ // resistance = (res + 0.3F) * 0.3F; -+ // so for resistance = 0, we need res = -0.3F -+ private static final Float ZERO_RESISTANCE = Float.valueOf(-0.3f); -+ private it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap blockCache = null; -+ -+ public static final class ExplosionBlockCache { -+ -+ public final long key; -+ public final BlockPos immutablePos; -+ public final BlockState blockState; -+ public final FluidState fluidState; -+ public final float resistance; -+ public final boolean outOfWorld; -+ public Boolean shouldExplode; // null -> not called yet -+ public net.minecraft.world.phys.shapes.VoxelShape cachedCollisionShape; -+ -+ public ExplosionBlockCache(long key, BlockPos immutablePos, BlockState blockState, FluidState fluidState, float resistance, -+ boolean outOfWorld) { -+ this.key = key; -+ this.immutablePos = immutablePos; -+ this.blockState = blockState; -+ this.fluidState = fluidState; -+ this.resistance = resistance; -+ this.outOfWorld = outOfWorld; -+ } -+ } -+ -+ private long[] chunkPosCache = null; -+ private net.minecraft.world.level.chunk.LevelChunk[] chunkCache = null; -+ -+ private ExplosionBlockCache getOrCacheExplosionBlock(final int x, final int y, final int z, -+ final long key, final boolean calculateResistance) { -+ ExplosionBlockCache ret = this.blockCache.get(key); -+ if (ret != null) { -+ return ret; -+ } -+ -+ BlockPos pos = new BlockPos(x, y, z); -+ -+ if (!this.level.isInWorldBounds(pos)) { -+ ret = new ExplosionBlockCache(key, pos, null, null, 0.0f, true); -+ } else { -+ net.minecraft.world.level.chunk.LevelChunk chunk; -+ long chunkKey = io.papermc.paper.util.CoordinateUtils.getChunkKey(x >> 4, z >> 4); -+ int chunkCacheKey = ((x >> 4) & CHUNK_CACHE_MASK) | (((z >> 4) << CHUNK_CACHE_SHIFT) & (CHUNK_CACHE_MASK << CHUNK_CACHE_SHIFT)); -+ if (this.chunkPosCache[chunkCacheKey] == chunkKey) { -+ chunk = this.chunkCache[chunkCacheKey]; -+ } else { -+ this.chunkPosCache[chunkCacheKey] = chunkKey; -+ this.chunkCache[chunkCacheKey] = chunk = this.level.getChunk(x >> 4, z >> 4); -+ } -+ -+ BlockState blockState = chunk.getBlockStateFinal(x, y, z); -+ FluidState fluidState = blockState.getFluidState(); -+ -+ Optional resistance = !calculateResistance ? Optional.empty() : this.damageCalculator.getBlockExplosionResistance((Explosion)(Object)this, this.level, pos, blockState, fluidState); -+ -+ ret = new ExplosionBlockCache( -+ key, pos, blockState, fluidState, -+ (resistance.orElse(ZERO_RESISTANCE).floatValue() + 0.3f) * 0.3f, -+ false -+ ); -+ } -+ -+ this.blockCache.put(key, ret); -+ -+ return ret; -+ } -+ -+ private boolean clipsAnything(final Vec3 from, final Vec3 to, -+ final io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext context, -+ final ExplosionBlockCache[] blockCache, -+ final BlockPos.MutableBlockPos currPos) { -+ // assume that context.delegated = false -+ final double adjX = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.x - to.x); -+ final double adjY = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.y - to.y); -+ final double adjZ = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.z - to.z); -+ -+ if (adjX == 0.0 && adjY == 0.0 && adjZ == 0.0) { -+ return false; -+ } -+ -+ final double toXAdj = to.x - adjX; -+ final double toYAdj = to.y - adjY; -+ final double toZAdj = to.z - adjZ; -+ final double fromXAdj = from.x + adjX; -+ final double fromYAdj = from.y + adjY; -+ final double fromZAdj = from.z + adjZ; -+ -+ int currX = Mth.floor(fromXAdj); -+ int currY = Mth.floor(fromYAdj); -+ int currZ = Mth.floor(fromZAdj); -+ -+ final double diffX = toXAdj - fromXAdj; -+ final double diffY = toYAdj - fromYAdj; -+ final double diffZ = toZAdj - fromZAdj; -+ -+ final double dxDouble = Math.signum(diffX); -+ final double dyDouble = Math.signum(diffY); -+ final double dzDouble = Math.signum(diffZ); -+ -+ final int dx = (int)dxDouble; -+ final int dy = (int)dyDouble; -+ final int dz = (int)dzDouble; -+ -+ final double normalizedDiffX = diffX == 0.0 ? Double.MAX_VALUE : dxDouble / diffX; -+ final double normalizedDiffY = diffY == 0.0 ? Double.MAX_VALUE : dyDouble / diffY; -+ final double normalizedDiffZ = diffZ == 0.0 ? Double.MAX_VALUE : dzDouble / diffZ; -+ -+ double normalizedCurrX = normalizedDiffX * (diffX > 0.0 ? (1.0 - Mth.frac(fromXAdj)) : Mth.frac(fromXAdj)); -+ double normalizedCurrY = normalizedDiffY * (diffY > 0.0 ? (1.0 - Mth.frac(fromYAdj)) : Mth.frac(fromYAdj)); -+ double normalizedCurrZ = normalizedDiffZ * (diffZ > 0.0 ? (1.0 - Mth.frac(fromZAdj)) : Mth.frac(fromZAdj)); -+ -+ for (;;) { -+ currPos.set(currX, currY, currZ); -+ -+ // ClipContext.Block.COLLIDER -> BlockBehaviour.BlockStateBase::getCollisionShape -+ // ClipContext.Fluid.NONE -> ignore fluids -+ -+ // read block from cache -+ final long key = BlockPos.asLong(currX, currY, currZ); -+ -+ final int cacheKey = -+ (currX & BLOCK_EXPLOSION_CACHE_MASK) | -+ (currY & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) | -+ (currZ & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT); -+ ExplosionBlockCache cachedBlock = blockCache[cacheKey]; -+ if (cachedBlock == null || cachedBlock.key != key) { -+ blockCache[cacheKey] = cachedBlock = this.getOrCacheExplosionBlock(currX, currY, currZ, key, false); -+ } -+ -+ final BlockState blockState = cachedBlock.blockState; -+ if (blockState != null && !blockState.emptyCollisionShape()) { -+ net.minecraft.world.phys.shapes.VoxelShape collision = cachedBlock.cachedCollisionShape; -+ if (collision == null) { -+ collision = blockState.getConstantCollisionShape(); -+ if (collision == null) { -+ collision = blockState.getCollisionShape(this.level, currPos, context); -+ if (!context.isDelegated()) { -+ // if it was not delegated during this call, assume that for any future ones it will not be delegated -+ // again, and cache the result -+ cachedBlock.cachedCollisionShape = collision; -+ } -+ } else { -+ cachedBlock.cachedCollisionShape = collision; -+ } -+ } -+ -+ if (!collision.isEmpty() && collision.clip(from, to, currPos) != null) { -+ return true; -+ } -+ } -+ -+ if (normalizedCurrX > 1.0 && normalizedCurrY > 1.0 && normalizedCurrZ > 1.0) { -+ return false; -+ } -+ -+ // inc the smallest normalized coordinate -+ -+ if (normalizedCurrX < normalizedCurrY) { -+ if (normalizedCurrX < normalizedCurrZ) { -+ currX += dx; -+ normalizedCurrX += normalizedDiffX; -+ } else { -+ // x < y && x >= z <--> z < y && z <= x -+ currZ += dz; -+ normalizedCurrZ += normalizedDiffZ; -+ } -+ } else if (normalizedCurrY < normalizedCurrZ) { -+ // y <= x && y < z -+ currY += dy; -+ normalizedCurrY += normalizedDiffY; -+ } else { -+ // y <= x && z <= y <--> z <= y && z <= x -+ currZ += dz; -+ normalizedCurrZ += normalizedDiffZ; -+ } -+ } -+ } -+ -+ private float getSeenFraction(final Vec3 source, final Entity target, -+ final ExplosionBlockCache[] blockCache, -+ final BlockPos.MutableBlockPos blockPos) { -+ final AABB boundingBox = target.getBoundingBox(); -+ final double diffX = boundingBox.maxX - boundingBox.minX; -+ final double diffY = boundingBox.maxY - boundingBox.minY; -+ final double diffZ = boundingBox.maxZ - boundingBox.minZ; -+ -+ final double incX = 1.0 / (diffX * 2.0 + 1.0); -+ final double incY = 1.0 / (diffY * 2.0 + 1.0); -+ final double incZ = 1.0 / (diffZ * 2.0 + 1.0); -+ -+ if (incX < 0.0 || incY < 0.0 || incZ < 0.0) { -+ return 0.0f; -+ } -+ -+ final double offX = (1.0 - Math.floor(1.0 / incX) * incX) * 0.5 + boundingBox.minX; -+ final double offY = boundingBox.minY; -+ final double offZ = (1.0 - Math.floor(1.0 / incZ) * incZ) * 0.5 + boundingBox.minZ; -+ -+ final io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext context = new io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext(target); -+ -+ int totalRays = 0; -+ int missedRays = 0; -+ -+ for (double dx = 0.0; dx <= 1.0; dx += incX) { -+ final double fromX = Math.fma(dx, diffX, offX); -+ for (double dy = 0.0; dy <= 1.0; dy += incY) { -+ final double fromY = Math.fma(dy, diffY, offY); -+ for (double dz = 0.0; dz <= 1.0; dz += incZ) { -+ ++totalRays; -+ -+ final Vec3 from = new Vec3( -+ fromX, -+ fromY, -+ Math.fma(dz, diffZ, offZ) -+ ); -+ -+ if (!this.clipsAnything(from, source, context, blockCache, blockPos)) { -+ ++missedRays; -+ } -+ } -+ } -+ } -+ -+ return (float)missedRays / (float)totalRays; -+ } -+ // Paper end - optimise collisions -+ - private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) { - return (ExplosionDamageCalculator) (entity == null ? Explosion.EXPLOSION_DAMAGE_CALCULATOR : new EntityBasedExplosionDamageCalculator(entity)); - } -@@ -171,40 +436,88 @@ public class Explosion { - int i; - int j; - -- for (int k = 0; k < 16; ++k) { -- for (i = 0; i < 16; ++i) { -- for (j = 0; j < 16; ++j) { -- if (k == 0 || k == 15 || i == 0 || i == 15 || j == 0 || j == 15) { -- double d0 = (double) ((float) k / 15.0F * 2.0F - 1.0F); -- double d1 = (double) ((float) i / 15.0F * 2.0F - 1.0F); -- double d2 = (double) ((float) j / 15.0F * 2.0F - 1.0F); -- double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2); -- -- d0 /= d3; -- d1 /= d3; -- d2 /= d3; -+ // Paper start - optimise explosions -+ this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); -+ -+ this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; -+ java.util.Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS); -+ -+ this.chunkCache = new net.minecraft.world.level.chunk.LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; -+ -+ final ExplosionBlockCache[] blockCache = new ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH]; -+ // use initial cache value that is most likely to be used: the source position -+ final ExplosionBlockCache initialCache; -+ { -+ final int blockX = Mth.floor(this.x); -+ final int blockY = Mth.floor(this.y); -+ final int blockZ = Mth.floor(this.z); -+ -+ final long key = BlockPos.asLong(blockX, blockY, blockZ); -+ -+ initialCache = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true); -+ } -+ // only ~1/3rd of the loop iterations in vanilla will result in a ray, as it is iterating the perimeter of -+ // a 16x16x16 cube -+ // we can cache the rays and their normals as well, so that we eliminate the excess iterations / checks and -+ // calculations in one go -+ // additional aggressive caching of block retrieval is very significant, as at low power (i.e tnt) most -+ // block retrievals are not unique -+ for (int ray = 0, len = CACHED_RAYS.length; ray < len;) { -+ { -+ { -+ { -+ ExplosionBlockCache cachedBlock = initialCache; -+ -+ double d0 = CACHED_RAYS[ray]; -+ double d1 = CACHED_RAYS[ray + 1]; -+ double d2 = CACHED_RAYS[ray + 2]; -+ ray += 3; -+ // Paper end - optimise explosions - float f = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F); - double d4 = this.x; - double d5 = this.y; - double d6 = this.z; - - for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { -- BlockPos blockposition = BlockPos.containing(d4, d5, d6); -- BlockState iblockdata = this.level.getBlockState(blockposition); -- if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed -- FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions -+ // Paper start - optimise explosions -+ final int blockX = Mth.floor(d4); -+ final int blockY = Mth.floor(d5); -+ final int blockZ = Mth.floor(d6); -+ -+ final long key = BlockPos.asLong(blockX, blockY, blockZ); -+ -+ if (cachedBlock.key != key) { -+ final int cacheKey = -+ (blockX & BLOCK_EXPLOSION_CACHE_MASK) | -+ (blockY & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) | -+ (blockZ & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT); -+ cachedBlock = blockCache[cacheKey]; -+ if (cachedBlock == null || cachedBlock.key != key) { -+ blockCache[cacheKey] = cachedBlock = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true); -+ } -+ } - -- if (!this.level.isInWorldBounds(blockposition)) { -+ if (cachedBlock.outOfWorld) { - break; - } - -- Optional optional = this.damageCalculator.getBlockExplosionResistance(this, this.level, blockposition, iblockdata, fluid); -+ BlockPos blockposition = cachedBlock.immutablePos; -+ BlockState iblockdata = cachedBlock.blockState; -+ // Paper end - optimise explosions - -- if (optional.isPresent()) { -- f -= ((Float) optional.get() + 0.3F) * 0.3F; -- } -+ if (!iblockdata.isDestroyable()) continue; // Paper -+ // Paper - optimise explosions - -- if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) { -+ f -= cachedBlock.resistance; // Paper - optimise explosions -+ -+ if (f > 0.0F && cachedBlock.shouldExplode == null) { // Paper - optimise explosions -+ // Paper start - optimise explosions -+ // note: we expect shouldBlockExplode to be pure with respect to power, as Vanilla currently is. -+ // basically, it is unused, which allows us to cache the result -+ final boolean shouldExplode = this.damageCalculator.shouldBlockExplode(this, this.level, cachedBlock.immutablePos, cachedBlock.blockState, f); -+ cachedBlock.shouldExplode = shouldExplode ? Boolean.TRUE : Boolean.FALSE; -+ if (shouldExplode && (this.fire || !cachedBlock.blockState.isAir())) { -+ // Paper end - optimise explosions - set.add(blockposition); - // Paper start - prevent headless pistons from forming - if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { -@@ -215,11 +528,12 @@ public class Explosion { - } - } - // Paper end - prevent headless pistons from forming -+ } // Paper - optimise explosions - } - -- d4 += d0 * 0.30000001192092896D; -- d5 += d1 * 0.30000001192092896D; -- d6 += d2 * 0.30000001192092896D; -+ d4 += d0; // Paper - optimise explosions -+ d5 += d1; // Paper - optimise explosions -+ d6 += d2; // Paper - optimise explosions - } - } - } -@@ -239,6 +553,8 @@ public class Explosion { - Vec3 vec3d = new Vec3(this.x, this.y, this.z); - Iterator iterator = list.iterator(); - -+ final BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(); // Paper - optimise explosions -+ - while (iterator.hasNext()) { - Entity entity = (Entity) iterator.next(); - -@@ -275,11 +591,11 @@ public class Explosion { - for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) { - // Calculate damage separately for each EntityComplexPart - if (list.contains(entityComplexPart)) { -- entityComplexPart.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity)); -+ entityComplexPart.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entityComplexPart, getSeenFraction(vec3d, entityComplexPart, blockCache, blockPos))); // Paper - actually optimise explosions and use the right entity to calculate the damage - } - } - } else { -- entity.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity)); -+ entity.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, getSeenFraction(vec3d, entity, blockCache, blockPos))); // Paper - actually optimise explosions - } - - CraftEventFactory.entityDamage = null; -@@ -289,7 +605,7 @@ public class Explosion { - // CraftBukkit end - } - -- double d12 = (1.0D - d7) * this.getBlockDensity(vec3d, entity); // Paper - Optimize explosions -+ double d12 = (1.0D - d7) * this.getBlockDensity(vec3d, entity, blockCache, blockPos); // Paper - Optimize explosions - double d13; - - if (entity instanceof LivingEntity) { -@@ -318,6 +634,9 @@ public class Explosion { - } - } - -+ this.blockCache = null; // Paper - optimise explosions -+ this.chunkPosCache = null; // Paper - optimise explosions -+ this.chunkCache = null; // Paper - optimise explosions - } - - public void finalizeExplosion(boolean particles) { -@@ -531,14 +850,14 @@ public class Explosion { - private BlockInteraction() {} - } - // Paper start - Optimize explosions -- private float getBlockDensity(Vec3 vec3d, Entity entity) { -+ private float getBlockDensity(Vec3 vec3d, Entity entity, ExplosionBlockCache[] blockCache, BlockPos.MutableBlockPos blockPos) { // Paper - optimise explosions - if (!this.level.paperConfig().environment.optimizeExplosions) { -- return getSeenPercent(vec3d, entity); -+ return this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise explosions - } - CacheKey key = new CacheKey(this, entity.getBoundingBox()); - Float blockDensity = this.level.explosionDensityCache.get(key); - if (blockDensity == null) { -- blockDensity = getSeenPercent(vec3d, entity); -+ blockDensity = this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise explosions; - this.level.explosionDensityCache.put(key, blockDensity); - } - -diff --git a/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java b/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java -index 24dba1eb6f5dc71e5d1ce2d150930eaefc83f811..f529f5d0f28533ec89f3ee712e59745991d068ee 100644 ---- a/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java -+++ b/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java -@@ -20,11 +20,17 @@ public class ExplosionDamageCalculator { - return true; - } - -+ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - public float getEntityDamageAmount(Explosion explosion, Entity entity) { -+ // Paper start - actually optimise explosions -+ return this.getEntityDamageAmount(explosion, entity, Explosion.getSeenPercent(explosion.center(), entity)); -+ } -+ public float getEntityDamageAmount(Explosion explosion, Entity entity, double seenPercent) { -+ // Paper end - actually optimise explosions - float f = explosion.radius() * 2.0F; - Vec3 vec3 = explosion.center(); - double d = Math.sqrt(entity.distanceToSqr(vec3)) / (double)f; -- double e = (1.0D - d) * (double)Explosion.getSeenPercent(vec3, entity); -+ double e = (1.0D - d) * seenPercent; // Paper - actually optimise explosions - return (float)((e * e + e) / 2.0D * 7.0D * (double)f + 1.0D); - } - } diff --git a/patches/server/1041-Send-full-pos-packets-for-hard-colliding-entities.patch b/patches/server/1035-Send-full-pos-packets-for-hard-colliding-entities.patch similarity index 100% rename from patches/server/1041-Send-full-pos-packets-for-hard-colliding-entities.patch rename to patches/server/1035-Send-full-pos-packets-for-hard-colliding-entities.patch diff --git a/patches/server/1042-Add-ShulkerDuplicateEvent.patch b/patches/server/1036-Add-ShulkerDuplicateEvent.patch similarity index 100% rename from patches/server/1042-Add-ShulkerDuplicateEvent.patch rename to patches/server/1036-Add-ShulkerDuplicateEvent.patch diff --git a/patches/server/1036-Optimise-chunk-tick-iteration.patch b/patches/server/1036-Optimise-chunk-tick-iteration.patch deleted file mode 100644 index 9aeb33c382ae..000000000000 --- a/patches/server/1036-Optimise-chunk-tick-iteration.patch +++ /dev/null @@ -1,380 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 23 Sep 2023 21:36:36 -0700 -Subject: [PATCH] Optimise chunk tick iteration - -When per-player mob spawning is enabled we do not need to randomly -shuffle the chunk list. Additionally, we can use the NearbyPlayers -class to quickly retrieve nearby players instead of possible -searching all players on the server. - -diff --git a/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java b/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java -index c3ce8a42dddd76b7189ad5685b23f9d9f8ccadb3..f164256d59b761264876ca0c85f812d101bfd5de 100644 ---- a/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java -+++ b/src/main/java/io/papermc/paper/util/player/NearbyPlayers.java -@@ -17,7 +17,8 @@ public final class NearbyPlayers { - GENERAL_SMALL, - GENERAL_REALLY_SMALL, - TICK_VIEW_DISTANCE, -- VIEW_DISTANCE; -+ VIEW_DISTANCE, // Paper - optimise chunk iteration -+ SPAWN_RANGE, // Paper - optimise chunk iteration - } - - private static final NearbyMapType[] MOB_TYPES = NearbyMapType.values(); -@@ -26,10 +27,12 @@ public final class NearbyPlayers { - private static final int GENERAL_AREA_VIEW_DISTANCE = 33; - private static final int GENERAL_SMALL_VIEW_DISTANCE = 10; - private static final int GENERAL_REALLY_SMALL_VIEW_DISTANCE = 3; -+ private static final int SPAWN_RANGE_VIEW_DISTANCE = net.minecraft.server.level.DistanceManager.MOB_SPAWN_RANGE; // Paper - optimise chunk iteration - - public static final int GENERAL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_AREA_VIEW_DISTANCE << 4); - public static final int GENERAL_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_SMALL_VIEW_DISTANCE << 4); - public static final int GENERAL_REALLY_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_REALLY_SMALL_VIEW_DISTANCE << 4); -+ public static final int SPAWN_RANGE_VIEW_DISTANCE_BLOCKS = (SPAWN_RANGE_VIEW_DISTANCE << 4); // Paper - optimise chunk iteration - - private final ServerLevel world; - private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); -@@ -80,6 +83,7 @@ public final class NearbyPlayers { - players[NearbyMapType.GENERAL_REALLY_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_REALLY_SMALL_VIEW_DISTANCE); - players[NearbyMapType.TICK_VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getTickViewDistance(player)); - players[NearbyMapType.VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getLoadViewDistance(player)); -+ players[NearbyMapType.SPAWN_RANGE.ordinal()].update(chunk.x, chunk.z, SPAWN_RANGE_VIEW_DISTANCE); // Paper - optimise chunk iteration - } - - public TrackedChunk getChunk(final ChunkPos pos) { -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 2b998bdbe49bf8211b755e0eb7c1bf13ac280eab..627a88ec8c3b215b19b55a6d461c8754b4fcd1e8 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -79,11 +79,19 @@ public class ChunkHolder { - - // Paper start - public void onChunkAdd() { -- -+ // Paper start - optimise chunk tick iteration -+ if (this.needsBroadcastChanges()) { -+ this.chunkMap.needsChangeBroadcasting.add(this); -+ } -+ // Paper end - optimise chunk tick iteration - } - - public void onChunkRemove() { -- -+ // Paper start - optimise chunk tick iteration -+ if (this.needsBroadcastChanges()) { -+ this.chunkMap.needsChangeBroadcasting.remove(this); -+ } -+ // Paper end - optimise chunk tick iteration - } - // Paper end - -@@ -230,7 +238,7 @@ public class ChunkHolder { - - if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296 - if (this.changedBlocksPerSection[i] == null) { -- this.hasChangedSections = true; -+ this.hasChangedSections = true; this.addToBroadcastMap(); // Paper - optimise chunk tick iteration - this.changedBlocksPerSection[i] = new ShortOpenHashSet(); - } - -@@ -254,6 +262,7 @@ public class ChunkHolder { - int k = this.lightEngine.getMaxLightSection(); - - if (y >= j && y <= k) { -+ this.addToBroadcastMap(); // Paper - optimise chunk tick iteration - int l = y - j; - - if (lightType == LightLayer.SKY) { -@@ -268,8 +277,19 @@ public class ChunkHolder { - } - } - -+ // Paper start - optimise chunk tick iteration -+ public final boolean needsBroadcastChanges() { -+ return this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty(); -+ } -+ -+ private void addToBroadcastMap() { -+ io.papermc.paper.util.TickThread.ensureTickThread(this.chunkMap.level, this.pos, "Asynchronous ChunkHolder update is not allowed"); -+ this.chunkMap.needsChangeBroadcasting.add(this); -+ } -+ // Paper end - optimise chunk tick iteration -+ - public void broadcastChanges(LevelChunk chunk) { -- if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) { -+ if (this.needsBroadcastChanges()) { // Paper - optimise chunk tick iteration; moved into above, other logic needs to call - Level world = chunk.getLevel(); - List list; - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 7e5a8789e06a5ea1d2657ea8ee5c0460da92aaeb..5a7278b093e37b95fb005ad5cc3cac90ac36f8fb 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -191,6 +191,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerEntityTrackerTrackMaps[i].remove(player); - } - // Paper end - use distance map to optimise tracker -+ this.playerMobSpawnMap.remove(player); // Paper - optimise chunk tick iteration - } - - void updateMaps(ServerPlayer player) { -@@ -240,6 +241,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - public final io.papermc.paper.util.player.NearbyPlayers nearbyPlayers; - // Paper end -+ // Paper start - optimise chunk tick iteration -+ public final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet needsChangeBroadcasting = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); -+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); -+ // Paper end - optimise chunk tick iteration - - public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { - super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); -@@ -408,7 +413,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - // Paper end - Optional per player mob spawns - -- private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { -+ public static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { // Paper - optimise chunk iteration; public - double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8); - double d1 = (double) SectionPos.sectionToBlockCoord(pos.z, 8); - double d2 = d0 - entity.getX(); -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index c80a625f7289e3bb33c6851d2072957e153ca1fb..7c425ac50c83757b66a2178bc19d4c920b82f12f 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -50,7 +50,7 @@ public abstract class DistanceManager { - private static final int INITIAL_TICKET_LIST_CAPACITY = 4; - final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); - // Paper - rewrite chunk system -- private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); -+ public static final int MOB_SPAWN_RANGE = 8; //private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); // Paper - optimise chunk tick iteration - // Paper - rewrite chunk system - private final ChunkMap chunkMap; // Paper - -@@ -135,7 +135,7 @@ public abstract class DistanceManager { - long i = chunkcoordintpair.toLong(); - - // Paper - no longer used -- this.naturalSpawnChunkCounter.update(i, 0, true); -+ //this.naturalSpawnChunkCounter.update(i, 0, true); // Paper - optimise chunk tick iteration - //this.playerTicketManager.update(i, 0, true); // Paper - no longer used - //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used - } -@@ -149,7 +149,7 @@ public abstract class DistanceManager { - if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully - if (objectset == null || objectset.isEmpty()) { // Paper - this.playersPerChunk.remove(i); -- this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); -+ //this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); // Paper - optimise chunk tick iteration - //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used - //this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used - } -@@ -191,13 +191,11 @@ public abstract class DistanceManager { - } - - public int getNaturalSpawnChunkCount() { -- this.naturalSpawnChunkCounter.runAllUpdates(); -- return this.naturalSpawnChunkCounter.chunks.size(); -+ return this.chunkMap.playerMobSpawnMap.size(); // Paper - optimise chunk tick iteration - } - - public boolean hasPlayersNearby(long chunkPos) { -- this.naturalSpawnChunkCounter.runAllUpdates(); -- return this.naturalSpawnChunkCounter.chunks.containsKey(chunkPos); -+ return this.chunkMap.playerMobSpawnMap.getObjectsInRange(chunkPos) != null; // Paper - optimise chunk tick iteration - } - - public String getDebugStatus() { -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 2b33a3d8fdb86024acb2a3ee9d0a4a7dd4989c98..366c0c9b45a819f7f94ebe3e49b8ab7f9edf9ce7 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -508,18 +508,10 @@ public class ServerChunkCache extends ChunkSource { - - gameprofilerfiller.push("pollingChunks"); - gameprofilerfiller.push("filteringLoadedChunks"); -- List list = Lists.newArrayListWithCapacity(this.chunkMap.size()); -- Iterator iterator = this.chunkMap.getChunks().iterator(); -+ // Paper - optimise chunk tick iteration - if (this.level.getServer().tickRateManager().runsNormally()) this.level.timings.chunkTicks.startTiming(); // Paper - -- while (iterator.hasNext()) { -- ChunkHolder playerchunk = (ChunkHolder) iterator.next(); -- LevelChunk chunk = playerchunk.getTickingChunk(); -- -- if (chunk != null) { -- list.add(new ServerChunkCache.ChunkAndHolder(chunk, playerchunk)); -- } -- } -+ // Paper - optimise chunk tick iteration - - if (this.level.getServer().tickRateManager().runsNormally()) { - gameprofilerfiller.popPush("naturalSpawnCount"); -@@ -554,38 +546,109 @@ public class ServerChunkCache extends ChunkSource { - gameprofilerfiller.popPush("spawnAndTick"); - boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit - -- Util.shuffle(list, this.level.random); -- // Paper start - PlayerNaturallySpawnCreaturesEvent -- int chunkRange = level.spigotConfig.mobSpawnRange; -- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; -- chunkRange = Math.min(chunkRange, 8); -- for (ServerPlayer entityPlayer : this.level.players()) { -- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); -- entityPlayer.playerNaturallySpawnedEvent.callEvent(); -+ // Paper start - optimise chunk tick iteration -+ ChunkMap playerChunkMap = this.chunkMap; -+ for (ServerPlayer player : this.level.players) { -+ if (!player.affectsSpawning || player.isSpectator()) { -+ playerChunkMap.playerMobSpawnMap.remove(player); -+ player.playerNaturallySpawnedEvent = null; -+ player.lastEntitySpawnRadiusSquared = -1.0; -+ continue; -+ } -+ -+ int viewDistance = io.papermc.paper.chunk.system.ChunkSystem.getTickViewDistance(player); -+ -+ // copied and modified from isOutisdeRange -+ int chunkRange = (int)level.spigotConfig.mobSpawnRange; -+ chunkRange = (chunkRange > viewDistance) ? viewDistance : chunkRange; -+ chunkRange = (chunkRange > DistanceManager.MOB_SPAWN_RANGE) ? DistanceManager.MOB_SPAWN_RANGE : chunkRange; -+ -+ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(player.getBukkitEntity(), (byte)chunkRange); -+ event.callEvent(); -+ if (event.isCancelled() || event.getSpawnRadius() < 0) { -+ playerChunkMap.playerMobSpawnMap.remove(player); -+ player.playerNaturallySpawnedEvent = null; -+ player.lastEntitySpawnRadiusSquared = -1.0; -+ continue; -+ } -+ -+ int range = Math.min(event.getSpawnRadius(), DistanceManager.MOB_SPAWN_RANGE); // limit to max spawn range -+ int chunkX = io.papermc.paper.util.CoordinateUtils.getChunkCoordinate(player.getX()); -+ int chunkZ = io.papermc.paper.util.CoordinateUtils.getChunkCoordinate(player.getZ()); -+ -+ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range); -+ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in anyPlayerCloseEnoughForSpawning -+ player.playerNaturallySpawnedEvent = event; - } -- // Paper end - PlayerNaturallySpawnCreaturesEvent -+ // Paper end - optimise chunk tick iteration - int l = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); - boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit -- Iterator iterator1 = list.iterator(); -+ // Paper - optimise chunk tick iteration - - int chunksTicked = 0; // Paper -- while (iterator1.hasNext()) { -- ServerChunkCache.ChunkAndHolder chunkproviderserver_a = (ServerChunkCache.ChunkAndHolder) iterator1.next(); -- LevelChunk chunk1 = chunkproviderserver_a.chunk; -+ // Paper start - optimise chunk tick iteration -+ io.papermc.paper.util.player.NearbyPlayers nearbyPlayers = this.chunkMap.getNearbyPlayers(); // Paper - optimise chunk tick iteration -+ Iterator chunkIterator; -+ if (this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { -+ chunkIterator = this.tickingChunks.iterator(); -+ } else { -+ chunkIterator = this.tickingChunks.unsafeIterator(); -+ List shuffled = Lists.newArrayListWithCapacity(this.tickingChunks.size()); -+ while (chunkIterator.hasNext()) { -+ shuffled.add(chunkIterator.next()); -+ } -+ Util.shuffle(shuffled, this.level.random); -+ chunkIterator = shuffled.iterator(); -+ } -+ try { -+ // Paper end - optimise chunk tick iteration -+ while (chunkIterator.hasNext()) { -+ LevelChunk chunk1 = chunkIterator.next(); -+ // Paper end - optimise chunk tick iteration - ChunkPos chunkcoordintpair = chunk1.getPos(); - -- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) { -+ // Paper start - optimise chunk tick iteration -+ com.destroystokyo.paper.util.maplist.ReferenceList playersNearby -+ = nearbyPlayers.getPlayers(chunkcoordintpair, io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.SPAWN_RANGE); -+ if (playersNearby == null) { -+ continue; -+ } -+ Object[] rawData = playersNearby.getRawData(); -+ boolean spawn = false; -+ boolean tick = false; -+ for (int itr = 0, len = playersNearby.size(); itr < len; ++itr) { -+ ServerPlayer player = (ServerPlayer)rawData[itr]; -+ if (player.isSpectator()) { -+ continue; -+ } -+ -+ double distance = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, player); -+ spawn |= player.lastEntitySpawnRadiusSquared >= distance; -+ tick |= ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) * ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) >= distance; -+ if (spawn & tick) { -+ break; -+ } -+ } -+ if (tick && chunk1.chunkStatus.isOrAfter(net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING)) { -+ // Paper end - optimise chunk tick iteration - chunk1.incrementInhabitedTime(j); -- if (flag && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot -+ if (spawn && flag && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { // Spigot // Paper - optimise chunk tick iteration - NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); - } - -- if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { -+ if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - optimise chunk tick iteration - this.level.tickChunk(chunk1, l); - if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper - } - } - } -+ // Paper start - optimise chunk tick iteration -+ } finally { -+ if (chunkIterator instanceof io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator safeIterator) { -+ safeIterator.finishedIterating(); -+ } -+ } -+ // Paper end - optimise chunk tick iteration - this.level.timings.chunkTicks.stopTiming(); // Paper - - gameprofilerfiller.popPush("customSpawners"); -@@ -597,11 +660,23 @@ public class ServerChunkCache extends ChunkSource { - } - - gameprofilerfiller.popPush("broadcast"); -- list.forEach((chunkproviderserver_a1) -> { -+ // Paper - optimise chunk tick iteration - this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing -- chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk); -+ // Paper start - optimise chunk tick iteration -+ if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { -+ it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); -+ this.chunkMap.needsChangeBroadcasting.clear(); -+ for (ChunkHolder holder : copy) { -+ holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded -+ if (holder.needsBroadcastChanges()) { -+ // I DON'T want to KNOW what DUMB plugins might be doing. -+ this.chunkMap.needsChangeBroadcasting.add(holder); -+ } -+ } -+ } -+ // Paper end - optimise chunk tick iteration - this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing -- }); -+ // Paper - optimise chunk tick iteration - gameprofilerfiller.pop(); - gameprofilerfiller.pop(); - } -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 569dbd5a1479b41b2604aacd351bf6d33054de29..0dba30c41affafe7b1d585b515925043b37712fa 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -325,6 +325,9 @@ public class ServerPlayer extends Player { - }); - } - // Paper end - replace player chunk loader -+ // Paper start - optimise chunk tick iteration -+ public double lastEntitySpawnRadiusSquared = -1.0; -+ // Paper end - optimise chunk tick iteration - - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { - super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); diff --git a/patches/server/1037-Add-api-for-spawn-egg-texture-colors.patch b/patches/server/1037-Add-api-for-spawn-egg-texture-colors.patch new file mode 100644 index 000000000000..fcb89ff9a8f2 --- /dev/null +++ b/patches/server/1037-Add-api-for-spawn-egg-texture-colors.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Luis +Date: Thu, 11 Jan 2024 19:58:23 +0100 +Subject: [PATCH] Add api for spawn egg texture colors + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 1562e9832df8bf3f81fb37983a303da5bd9ceee6..37da4477a04c3675bf6b7210bb107289e6ba6f88 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -660,6 +660,15 @@ public final class CraftMagicNumbers implements UnsafeValues { + } + // Paper end + ++ // Paper start - spawn egg color visibility ++ @Override ++ public org.bukkit.Color getSpawnEggLayerColor(final EntityType entityType, final int layer) { ++ final net.minecraft.world.entity.EntityType nmsType = org.bukkit.craftbukkit.entity.CraftEntityType.bukkitToMinecraft(entityType); ++ final net.minecraft.world.item.SpawnEggItem eggItem = net.minecraft.world.item.SpawnEggItem.byId(nmsType); ++ return eggItem == null ? null : org.bukkit.Color.fromRGB(eggItem.getColor(layer)); ++ } ++ // Paper end - spawn egg color visibility ++ + /** + * This helper class represents the different NBT Tags. + *

    diff --git a/patches/server/1037-Lag-compensation-ticks.patch b/patches/server/1037-Lag-compensation-ticks.patch deleted file mode 100644 index 98c9f61cae4a..000000000000 --- a/patches/server/1037-Lag-compensation-ticks.patch +++ /dev/null @@ -1,129 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 23 Sep 2023 22:05:35 -0700 -Subject: [PATCH] Lag compensation ticks - -Areas affected by lag comepnsation: - - Block breaking and destroying - - Eating food items - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index f12bf36a247a47b8d831536a4fefd2a2ce424494..d06185566b447c432d4dc2e3ba04d121bcdbc71b 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -311,6 +311,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { - AtomicReference atomicreference = new AtomicReference(); -@@ -1693,6 +1694,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent - worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent - net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers -+ worldserver.updateLagCompensationTick(); // Paper - lag compensation - - this.profiler.push(() -> { - return worldserver + " " + worldserver.dimension().location(); -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index ab2e84f85da7931e133ad5f0d2686cd1738f6ea1..5bbfb1af24e13a9e6a02ad8c36bb504a17f06398 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -565,6 +565,17 @@ public class ServerLevel extends Level implements WorldGenLevel { - return player != null && player.level() == this ? player : null; - } - // Paper end - optimise getPlayerByUUID -+ // Paper start - lag compensation -+ private long lagCompensationTick = net.minecraft.server.MinecraftServer.SERVER_INIT; -+ -+ public long getLagCompensationTick() { -+ return this.lagCompensationTick; -+ } -+ -+ public void updateLagCompensationTick() { -+ this.lagCompensationTick = (System.nanoTime() - net.minecraft.server.MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L)); -+ } -+ // Paper end - lag compensation - - // Add env and gen to constructor, IWorldDataServer -> WorldDataServer - public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index ef3048a4748113538a0ee0af5b526b2cd51d5c29..a7b217ddbcbf92513bd38101fdfca2075505e267 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -124,7 +124,7 @@ public class ServerPlayerGameMode { - } - - public void tick() { -- this.gameTicks = MinecraftServer.currentTick; // CraftBukkit; -+ this.gameTicks = (int)this.level.getLagCompensationTick(); // CraftBukkit; // Paper - lag compensation - BlockState iblockdata; - - if (this.hasDelayedDestroy) { -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 1ce4cf3b601cc3b002c1453cc105c1278264cc31..6523795e715e5d472739e9bc6433143115c3de8f 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3828,6 +3828,10 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.getEntityData().resendPossiblyDesyncedDataValues(java.util.List.of(DATA_LIVING_ENTITY_FLAGS), serverPlayer); - } - // Paper end - Properly cancel usable items -+ // Paper start - lag compensate eating -+ protected long eatStartTime; -+ protected int totalEatTimeTicks; -+ // Paper end - lag compensate eating - private void updatingUsingItem() { - if (this.isUsingItem()) { - if (ItemStack.isSameItem(this.getItemInHand(this.getUsedItemHand()), this.useItem)) { -@@ -3846,7 +3850,12 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.triggerItemUseEffects(stack, 5); - } - -- if (--this.useItemRemaining == 0 && !this.level().isClientSide && !stack.useOnRelease()) { -+ // Paper start - lag compensate eating -+ // we add 1 to the expected time to avoid lag compensating when we should not -+ boolean shouldLagCompensate = this.useItem.getItem().isEdible() && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1 + this.totalEatTimeTicks) * 50 * (1000 * 1000)); -+ if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level().isClientSide && !stack.useOnRelease()) { -+ this.useItemRemaining = 0; -+ // Paper end - lag compensate eating - this.completeUsingItem(); - } - -@@ -3894,7 +3903,10 @@ public abstract class LivingEntity extends Entity implements Attackable { - - if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper - Prevent consuming the wrong itemstack - this.useItem = itemstack; -- this.useItemRemaining = itemstack.getUseDuration(); -+ // Paper start - lag compensate eating -+ this.useItemRemaining = this.totalEatTimeTicks = itemstack.getUseDuration(); -+ this.eatStartTime = System.nanoTime(); -+ // Paper end - lag compensate eating - if (!this.level().isClientSide) { - this.setLivingEntityFlag(1, true); - this.setLivingEntityFlag(2, hand == InteractionHand.OFF_HAND); -@@ -3919,7 +3931,10 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - } else if (!this.isUsingItem() && !this.useItem.isEmpty()) { - this.useItem = ItemStack.EMPTY; -- this.useItemRemaining = 0; -+ // Paper start - lag compensate eating -+ this.useItemRemaining = this.totalEatTimeTicks = 0; -+ this.eatStartTime = -1L; -+ // Paper end - lag compensate eating - } - } - -@@ -4054,7 +4069,10 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - this.useItem = ItemStack.EMPTY; -- this.useItemRemaining = 0; -+ // Paper start - lag compensate eating -+ this.useItemRemaining = this.totalEatTimeTicks = 0; -+ this.eatStartTime = -1L; -+ // Paper end - lag compensate eating - } - - public boolean isBlocking() { diff --git a/patches/server/1044-Disable-memory-reserve-allocating.patch b/patches/server/1038-Disable-memory-reserve-allocating.patch similarity index 100% rename from patches/server/1044-Disable-memory-reserve-allocating.patch rename to patches/server/1038-Disable-memory-reserve-allocating.patch diff --git a/patches/server/1039-Improve-tag-parser-handling.patch b/patches/server/1039-Improve-tag-parser-handling.patch new file mode 100644 index 000000000000..2dabc4807825 --- /dev/null +++ b/patches/server/1039-Improve-tag-parser-handling.patch @@ -0,0 +1,143 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Mon, 5 Feb 2024 11:54:04 +0100 +Subject: [PATCH] Improve tag parser handling + + +diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java +index 92848b64a78fce7a92e1657c2da6fc5ee53eea44..5d0e8f4f3ad61a27452675277380e27d3d28d133 100644 +--- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java ++++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java +@@ -307,6 +307,10 @@ public class CommandDispatcher { + try { + try { + child.parse(reader, context); ++ // Paper start - Handle non-reoverable exceptions; Rethrow NbtAccounterException so it can be caught properly and immediately ++ } catch (final net.minecraft.nbt.NbtAccounterException e) { ++ throw e; ++ // Paper end - Handle non-reoverable exceptions + } catch (final RuntimeException ex) { + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException().createWithContext(reader, ex.getMessage()); + } +diff --git a/src/main/java/net/minecraft/nbt/TagParser.java b/src/main/java/net/minecraft/nbt/TagParser.java +index 5bec54239a2b185284c10d58854e5a13e33daae5..9ecd0b7ddaa8376f3c1448f810f7757c9ba1b90a 100644 +--- a/src/main/java/net/minecraft/nbt/TagParser.java ++++ b/src/main/java/net/minecraft/nbt/TagParser.java +@@ -48,6 +48,7 @@ public class TagParser { + } + }, CompoundTag::toString); + private final StringReader reader; ++ private int depth; // Paper + + public static CompoundTag parseTag(String string) throws CommandSyntaxException { + return (new TagParser(new StringReader(string))).readSingleStruct(); +@@ -156,6 +157,7 @@ public class TagParser { + + public CompoundTag readStruct() throws CommandSyntaxException { + this.expect('{'); ++ this.increaseDepth(); // Paper + CompoundTag compoundTag = new CompoundTag(); + this.reader.skipWhitespace(); + +@@ -179,6 +181,7 @@ public class TagParser { + } + + this.expect('}'); ++ this.depth--; // Paper + return compoundTag; + } + +@@ -188,6 +191,7 @@ public class TagParser { + if (!this.reader.canRead()) { + throw ERROR_EXPECTED_VALUE.createWithContext(this.reader); + } else { ++ this.increaseDepth(); // Paper + ListTag listTag = new ListTag(); + TagType tagType = null; + +@@ -213,6 +217,7 @@ public class TagParser { + } + + this.expect(']'); ++ this.depth--; // Paper + return listTag; + } + } +@@ -251,11 +256,11 @@ public class TagParser { + } + + if (typeReader == ByteTag.TYPE) { +- list.add((T)((NumericTag)tag).getAsByte()); ++ list.add((T)((NumericTag)tag).getAsNumber()); // Paper - decompile fix + } else if (typeReader == LongTag.TYPE) { +- list.add((T)((NumericTag)tag).getAsLong()); ++ list.add((T)((NumericTag)tag).getAsNumber()); // Paper - decompile fix + } else { +- list.add((T)((NumericTag)tag).getAsInt()); ++ list.add((T)((NumericTag)tag).getAsNumber()); // Paper - decompile fix + } + + if (this.hasElementSeparator()) { +@@ -286,4 +291,11 @@ public class TagParser { + this.reader.skipWhitespace(); + this.reader.expect(c); + } ++ ++ private void increaseDepth() { ++ this.depth++; ++ if (this.depth > 512) { ++ throw new net.minecraft.nbt.NbtAccounterException("NBT tag is too complex, depth > 512"); ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java +index a5e438a834826161c52ca9db57d234d9ff80a591..4766994cce060564370b0d24836a7da8b5e4a8a1 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java +@@ -14,7 +14,7 @@ public class ServerboundCommandSuggestionPacket implements Packet 64 && ((index = packet.getCommand().indexOf(' ')) == -1 || index >= 64)) { ++ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); ++ return; ++ } ++ // Paper end + // Paper start - AsyncTabCompleteEvent + TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet)); + } +@@ -823,7 +830,18 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + + private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringreader) { + // Paper end - AsyncTabCompleteEvent +- ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); ++ // Paper start - Handle non-reoverable exceptions ++ ParseResults parseresults; ++ try { ++ parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); ++ } catch (final Throwable e) { // This is fine:tm: ++ if (LOGGER.isDebugEnabled()) { ++ LOGGER.error("Exception parsing command", e); ++ } ++ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); ++ return; ++ } ++ // Paper end - Handle non-reoverable exceptions + + this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { + // Paper start - Don't tab-complete namespaced commands if send-namespaced is false diff --git a/patches/server/1040-Add-Lifecycle-Event-system.patch b/patches/server/1040-Add-Lifecycle-Event-system.patch new file mode 100644 index 000000000000..bf79acac9c30 --- /dev/null +++ b/patches/server/1040-Add-Lifecycle-Event-system.patch @@ -0,0 +1,781 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 18 Jul 2023 17:49:38 -0700 +Subject: [PATCH] Add Lifecycle Event system + +This event system is separate from Bukkit's event system and is +meant for managing resources across reloads and from points in the +PluginBootstrap. + +diff --git a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java +index 30b50e6294c6eaade5e17cfaf34600d122e6251c..0bb7694188d5fb75bb756ce75d0060ea980027ee 100644 +--- a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java ++++ b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java +@@ -1,6 +1,8 @@ + package io.papermc.paper.plugin.bootstrap; + + import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; ++import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEventManager; + import io.papermc.paper.plugin.provider.PluginProvider; + import java.nio.file.Path; + import net.kyori.adventure.text.logger.slf4j.ComponentLogger; +@@ -12,6 +14,10 @@ public final class PluginBootstrapContextImpl implements BootstrapContext { + private final Path dataFolder; + private final ComponentLogger logger; + private final Path pluginSource; ++ // Paper start - lifecycle events ++ private boolean allowsLifecycleRegistration = true; ++ private final PaperLifecycleEventManager lifecycleEventManager = new PaperLifecycleEventManager<>(this, () -> this.allowsLifecycleRegistration); // Paper - lifecycle events ++ // Paper end - lifecycle events + + public PluginBootstrapContextImpl(PluginMeta config, Path dataFolder, ComponentLogger logger, Path pluginSource) { + this.config = config; +@@ -45,4 +51,20 @@ public final class PluginBootstrapContextImpl implements BootstrapContext { + public @NotNull Path getPluginSource() { + return this.pluginSource; + } ++ ++ // Paper start - lifecycle event system ++ @Override ++ public @NotNull PluginMeta getPluginMeta() { ++ return this.config; ++ } ++ ++ @Override ++ public LifecycleEventManager getLifecycleManager() { ++ return this.lifecycleEventManager; ++ } ++ ++ public void lockLifecycleEventRegistration() { ++ this.allowsLifecycleRegistration = false; ++ } ++ // Paper end + } +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f84c9c80e701231e5c33ac3c5573f1093e80f38b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java +@@ -0,0 +1,110 @@ ++package io.papermc.paper.plugin.lifecycle.event; ++ ++import com.google.common.base.Suppliers; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.lifecycle.event.registrar.PaperRegistrar; ++import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEvent; ++import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEventImpl; ++import io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent; ++import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; ++import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; ++import io.papermc.paper.plugin.lifecycle.event.types.OwnerAwareLifecycleEvent; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Set; ++import java.util.function.Predicate; ++import java.util.function.Supplier; ++import org.bukkit.plugin.Plugin; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.slf4j.Logger; ++ ++@DefaultQualifier(NonNull.class) ++public class LifecycleEventRunner { ++ ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ private static final Supplier>> BLOCKS_RELOADING = Suppliers.memoize(() -> Set.of( // lazy due to cyclic initialization ++ )); ++ public static final LifecycleEventRunner INSTANCE = new LifecycleEventRunner(); ++ ++ private final List> lifecycleEventTypes = new ArrayList<>(); ++ private boolean blockPluginReloading = false; ++ ++ public void checkRegisteredHandler(final LifecycleEventOwner owner, final LifecycleEventType eventType) { ++ /* ++ Lifecycle event handlers for reloadable events that are registered from the BootstrapContext prevent ++ the server from reloading plugins. This is because reloading plugins requires disabling all the plugins, ++ running the reload logic (which would include places where these events should fire) and then re-enabling plugins. ++ */ ++ if (owner instanceof BootstrapContext && BLOCKS_RELOADING.get().contains(eventType)) { ++ this.blockPluginReloading = true; ++ } ++ } ++ ++ public boolean blocksPluginReloading() { ++ return this.blockPluginReloading; ++ } ++ ++ public > ET addEventType(final ET eventType) { ++ this.lifecycleEventTypes.add(eventType); ++ return eventType; ++ } ++ ++ public void callEvent(final LifecycleEventType eventType, final E event) { ++ this.callEvent(eventType, event, $ -> true); ++ } ++ ++ public void callEvent(final LifecycleEventType eventType, final E event, final Predicate ownerPredicate) { ++ final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) eventType; ++ lifecycleEventType.forEachHandler(registeredHandler -> { ++ try { ++ if (event instanceof final OwnerAwareLifecycleEvent ownerAwareEvent) { ++ ownerAwareGenericHelper(ownerAwareEvent, registeredHandler.owner()); ++ } ++ registeredHandler.lifecycleEventHandler().run(event); ++ } catch (final Throwable ex) { ++ LOGGER.error("Could not run '{}' lifecycle event handler from {}", lifecycleEventType.name(), registeredHandler.owner().getPluginMeta().getDisplayName(), ex); ++ } finally { ++ if (event instanceof final OwnerAwareLifecycleEvent ownerAwareEvent) { ++ ownerAwareEvent.setOwner(null); ++ } ++ } ++ }, handler -> ownerPredicate.test(handler.owner())); ++ event.invalidate(); ++ } ++ ++ private static void ownerAwareGenericHelper(final OwnerAwareLifecycleEvent event, final LifecycleEventOwner possibleOwner) { ++ final @Nullable O owner = event.castOwner(possibleOwner); ++ if (owner != null) { ++ event.setOwner(owner); ++ } else { ++ throw new IllegalStateException("Found invalid owner " + possibleOwner + " for event " + event); ++ } ++ } ++ ++ public void unregisterAllEventHandlersFor(final Plugin plugin) { ++ for (final LifecycleEventType lifecycleEventType : this.lifecycleEventTypes) { ++ this.removeEventHandlersOwnedBy(lifecycleEventType, plugin); ++ } ++ } ++ ++ private void removeEventHandlersOwnedBy(final LifecycleEventType eventType, final Plugin possibleOwner) { ++ final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) eventType; ++ lifecycleEventType.removeMatching(registeredHandler -> registeredHandler.owner().getPluginMeta().getName().equals(possibleOwner.getPluginMeta().getName())); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public > void callStaticRegistrarEvent(final LifecycleEventType, ?> lifecycleEventType, final R registrar, final Class ownerClass) { ++ this.callEvent((LifecycleEventType, ?>) lifecycleEventType, new RegistrarEventImpl<>(registrar, ownerClass), ownerClass::isInstance); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public > void callReloadableRegistrarEvent(final LifecycleEventType, ?> lifecycleEventType, final R registrar, final Class ownerClass, final ReloadableRegistrarEvent.Cause cause) { ++ this.callEvent((LifecycleEventType, ?>) lifecycleEventType, new RegistrarEventImpl.ReloadableImpl<>(registrar, ownerClass, cause), ownerClass::isInstance); ++ } ++ ++ private LifecycleEventRunner() { ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e941405269a773e8a77e26ffd1afd84f53fadff5 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java +@@ -0,0 +1,10 @@ ++package io.papermc.paper.plugin.lifecycle.event; ++ ++public interface PaperLifecycleEvent extends LifecycleEvent { ++ ++ // called after all handlers have been run. Can be ++ // used to invalid various contexts to plugins can't ++ // try to re-use them by storing them from the event ++ default void invalidate() { ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f1be5b9a29435bae0afd2bd951bfe88d1669e7eb +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java +@@ -0,0 +1,26 @@ ++package io.papermc.paper.plugin.lifecycle.event; ++ ++import com.google.common.base.Preconditions; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration; ++import java.util.function.BooleanSupplier; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class PaperLifecycleEventManager implements LifecycleEventManager { ++ ++ private final O owner; ++ public final BooleanSupplier registrationCheck; ++ ++ public PaperLifecycleEventManager(final O owner, final BooleanSupplier registrationCheck) { ++ this.owner = owner; ++ this.registrationCheck = registrationCheck; ++ } ++ ++ @Override ++ public void registerEventHandler(final LifecycleEventHandlerConfiguration handlerConfiguration) { ++ Preconditions.checkState(this.registrationCheck.getAsBoolean(), "Cannot register lifecycle event handlers"); ++ ((AbstractLifecycleEventHandlerConfiguration) handlerConfiguration).registerFrom(this.owner); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6a85a4f581612efff04c1a955493aa2e32476277 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java +@@ -0,0 +1,26 @@ ++package io.papermc.paper.plugin.lifecycle.event.handler.configuration; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public abstract class AbstractLifecycleEventHandlerConfiguration> implements LifecycleEventHandlerConfiguration { ++ ++ private final LifecycleEventHandler handler; ++ private final AbstractLifecycleEventType type; ++ ++ protected AbstractLifecycleEventHandlerConfiguration(final LifecycleEventHandler handler, final AbstractLifecycleEventType type) { ++ this.handler = handler; ++ this.type = type; ++ } ++ ++ public abstract CI config(); ++ ++ public final void registerFrom(final O owner) { ++ this.type.tryRegister(owner, this.handler, this.config()); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e0699fcd0a098abc5e1206e7c0fa80b96eca7884 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java +@@ -0,0 +1,33 @@ ++package io.papermc.paper.plugin.lifecycle.event.handler.configuration; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class MonitorLifecycleEventHandlerConfigurationImpl extends AbstractLifecycleEventHandlerConfiguration> implements MonitorLifecycleEventHandlerConfiguration { ++ ++ private boolean monitor = false; ++ ++ public MonitorLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler handler, final AbstractLifecycleEventType> eventType) { ++ super(handler, eventType); ++ } ++ ++ @Override ++ public MonitorLifecycleEventHandlerConfigurationImpl config() { ++ return this; ++ } ++ ++ public boolean isMonitor() { ++ return this.monitor; ++ } ++ ++ @Override ++ public MonitorLifecycleEventHandlerConfiguration monitor() { ++ this.monitor = true; ++ return this; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c1d0070fc1594f7a7c29d7dc679da7b347a7140b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java +@@ -0,0 +1,43 @@ ++package io.papermc.paper.plugin.lifecycle.event.handler.configuration; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; ++import java.util.OptionalInt; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class PrioritizedLifecycleEventHandlerConfigurationImpl extends AbstractLifecycleEventHandlerConfiguration> implements PrioritizedLifecycleEventHandlerConfiguration { ++ ++ private static final OptionalInt DEFAULT_PRIORITY = OptionalInt.of(0); ++ private static final OptionalInt MONITOR_PRIORITY = OptionalInt.empty(); ++ ++ private OptionalInt priority = DEFAULT_PRIORITY; ++ ++ public PrioritizedLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler handler, final AbstractLifecycleEventType> eventType) { ++ super(handler, eventType); ++ } ++ ++ @Override ++ public PrioritizedLifecycleEventHandlerConfigurationImpl config() { ++ return this; ++ } ++ ++ public OptionalInt priority() { ++ return this.priority; ++ } ++ ++ @Override ++ public PrioritizedLifecycleEventHandlerConfiguration priority(final int priority) { ++ this.priority = OptionalInt.of(priority); ++ return this; ++ } ++ ++ @Override ++ public PrioritizedLifecycleEventHandlerConfiguration monitor() { ++ this.priority = MONITOR_PRIORITY; ++ return this; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b2586c881988fbabe07eef1b43eb1b55f2d3fa52 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java +@@ -0,0 +1,15 @@ ++package io.papermc.paper.plugin.lifecycle.event.registrar; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public interface PaperRegistrar extends Registrar { ++ ++ void setCurrentContext(@Nullable O owner); ++ ++ default void invalidate() { ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6d530c52aaf0dc2cdfe3bd56af557274a7f44256 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java +@@ -0,0 +1,70 @@ ++package io.papermc.paper.plugin.lifecycle.event.registrar; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.types.OwnerAwareLifecycleEvent; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class RegistrarEventImpl, O extends LifecycleEventOwner> implements PaperLifecycleEvent, OwnerAwareLifecycleEvent, RegistrarEvent { ++ ++ private final R registrar; ++ private final Class ownerClass; ++ ++ public RegistrarEventImpl(final R registrar, final Class ownerClass) { ++ this.registrar = registrar; ++ this.ownerClass = ownerClass; ++ } ++ ++ @Override ++ public R registrar() { ++ return this.registrar; ++ } ++ ++ @Override ++ public final void setOwner(final @Nullable O owner) { ++ this.registrar.setCurrentContext(owner); ++ } ++ ++ @Override ++ public final @Nullable O castOwner(final LifecycleEventOwner owner) { ++ return this.ownerClass.isInstance(owner) ? this.ownerClass.cast(owner) : null; ++ } ++ ++ @Override ++ public void invalidate() { ++ this.registrar.invalidate(); ++ } ++ ++ @Override ++ public String toString() { ++ return "RegistrarEventImpl{" + ++ "registrar=" + this.registrar + ++ ", ownerClass=" + this.ownerClass + ++ '}'; ++ } ++ ++ public static class ReloadableImpl, O extends LifecycleEventOwner> extends RegistrarEventImpl implements ReloadableRegistrarEvent { ++ ++ private final ReloadableRegistrarEvent.Cause cause; ++ ++ public ReloadableImpl(final R registrar, final Class ownerClass, final Cause cause) { ++ super(registrar, ownerClass); ++ this.cause = cause; ++ } ++ ++ @Override ++ public Cause cause() { ++ return this.cause; ++ } ++ ++ @Override ++ public String toString() { ++ return "ReloadableImpl{" + ++ "cause=" + this.cause + ++ "} " + super.toString(); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a65fb37f4a729e2fe9fb81af822db626ec7e6d7b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java +@@ -0,0 +1,50 @@ ++package io.papermc.paper.plugin.lifecycle.event.types; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public abstract class AbstractLifecycleEventType, CI extends AbstractLifecycleEventHandlerConfiguration> implements LifecycleEventType { ++ ++ private final String name; ++ private final Class ownerType; ++ ++ protected AbstractLifecycleEventType(final String name, final Class ownerType) { ++ this.name = name; ++ this.ownerType = ownerType; ++ } ++ ++ @Override ++ public String name() { ++ return this.name; ++ } ++ ++ private void verifyOwner(final O owner) { ++ if (!this.ownerType.isInstance(owner)) { ++ throw new IllegalArgumentException("You cannot register the lifecycle event '" + this.name + "' on " + owner); ++ } ++ } ++ ++ public abstract void forEachHandler(Consumer> consumer, Predicate> predicate); ++ ++ public abstract void removeMatching(Predicate> predicate); ++ ++ protected abstract void register(O owner, LifecycleEventHandler handler, CI config); ++ ++ public final void tryRegister(final O owner, final LifecycleEventHandler handler, final CI config) { ++ this.verifyOwner(owner); ++ LifecycleEventRunner.INSTANCE.checkRegisteredHandler(owner, this); ++ this.register(owner, handler, config); ++ } ++ ++ public record RegisteredHandler(O owner, LifecycleEventHandler lifecycleEventHandler) { ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0886edad92b40276f268bd745b31bac359fd28af +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java +@@ -0,0 +1,25 @@ ++package io.papermc.paper.plugin.lifecycle.event.types; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class LifecycleEventTypeProviderImpl implements LifecycleEventTypeProvider { ++ ++ public static LifecycleEventTypeProviderImpl instance() { ++ return (LifecycleEventTypeProviderImpl) LifecycleEventTypeProvider.PROVIDER; ++ } ++ ++ @Override ++ public LifecycleEventType.Monitorable monitor(final String name, final Class ownerType) { ++ return LifecycleEventRunner.INSTANCE.addEventType(new MonitorableLifecycleEventType<>(name, ownerType)); ++ } ++ ++ @Override ++ public LifecycleEventType.Prioritizable prioritized(final String name, final Class ownerType) { ++ return LifecycleEventRunner.INSTANCE.addEventType(new PrioritizableLifecycleEventType<>(name, ownerType)); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6d92c1d3adf220154dfe7cba3a3f8158356c3e3c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java +@@ -0,0 +1,54 @@ ++package io.papermc.paper.plugin.lifecycle.event.types; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfiguration; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfigurationImpl; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class MonitorableLifecycleEventType extends AbstractLifecycleEventType, MonitorLifecycleEventHandlerConfigurationImpl> implements LifecycleEventType.Monitorable { ++ ++ final List> handlers = new ArrayList<>(); ++ int nonMonitorIdx = 0; ++ ++ public MonitorableLifecycleEventType(final String name, final Class ownerType) { ++ super(name, ownerType); ++ } ++ ++ @Override ++ public MonitorLifecycleEventHandlerConfigurationImpl newHandler(final LifecycleEventHandler handler) { ++ return new MonitorLifecycleEventHandlerConfigurationImpl<>(handler, this); ++ } ++ ++ @Override ++ protected void register(final O owner, final LifecycleEventHandler handler, final MonitorLifecycleEventHandlerConfigurationImpl config) { ++ final RegisteredHandler registeredHandler = new RegisteredHandler<>(owner, handler); ++ if (!config.isMonitor()) { ++ this.handlers.add(this.nonMonitorIdx, registeredHandler); ++ this.nonMonitorIdx++; ++ } else { ++ this.handlers.add(registeredHandler); ++ } ++ } ++ ++ @Override ++ public void forEachHandler(final Consumer> consumer, final Predicate> predicate) { ++ for (final RegisteredHandler handler : this.handlers) { ++ if (predicate.test(handler)) { ++ consumer.accept(handler); ++ } ++ } ++ } ++ ++ @Override ++ public void removeMatching(final Predicate> predicate) { ++ this.handlers.removeIf(predicate); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3e7e7474f301c0725fa2bcd6e19e476fc35f2d5a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java +@@ -0,0 +1,15 @@ ++package io.papermc.paper.plugin.lifecycle.event.types; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public interface OwnerAwareLifecycleEvent extends LifecycleEvent { ++ ++ void setOwner(@Nullable O owner); ++ ++ @Nullable O castOwner(LifecycleEventOwner owner); ++} +diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6629f7fabf66ce761024268043cc30076ba8a3f1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java +@@ -0,0 +1,64 @@ ++package io.papermc.paper.plugin.lifecycle.event.types; ++ ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfigurationImpl; ++import java.util.ArrayList; ++import java.util.Comparator; ++import java.util.List; ++import java.util.OptionalInt; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public class PrioritizableLifecycleEventType extends AbstractLifecycleEventType, PrioritizedLifecycleEventHandlerConfigurationImpl> implements LifecycleEventType.Prioritizable { ++ ++ private static final Comparator> COMPARATOR = Comparator.comparing(PrioritizedHandler::priority, (o1, o2) -> { ++ if (o1.equals(o2)) { ++ return 0; ++ } else if (o1.isEmpty()) { ++ return 1; ++ } else if (o2.isEmpty()) { ++ return -1; ++ } else { ++ return Integer.compare(o1.getAsInt(), o2.getAsInt()); ++ } ++ }); ++ ++ private final List> handlers = new ArrayList<>(); ++ ++ public PrioritizableLifecycleEventType(final String name, final Class ownerType) { ++ super(name, ownerType); ++ } ++ ++ @Override ++ public PrioritizedLifecycleEventHandlerConfiguration newHandler(final LifecycleEventHandler handler) { ++ return new PrioritizedLifecycleEventHandlerConfigurationImpl<>(handler, this); ++ } ++ ++ @Override ++ protected void register(final O owner, final LifecycleEventHandler handler, final PrioritizedLifecycleEventHandlerConfigurationImpl config) { ++ this.handlers.add(new PrioritizedHandler<>(new RegisteredHandler<>(owner, handler), config.priority())); ++ this.handlers.sort(COMPARATOR); ++ } ++ ++ @Override ++ public void forEachHandler(final Consumer> consumer, final Predicate> predicate) { ++ for (final PrioritizedHandler handler : this.handlers) { ++ if (predicate.test(handler.handler())) { ++ consumer.accept(handler.handler()); ++ } ++ } ++ } ++ ++ @Override ++ public void removeMatching(final Predicate> predicate) { ++ this.handlers.removeIf(prioritizedHandler -> predicate.test(prioritizedHandler.handler())); ++ } ++ ++ private record PrioritizedHandler(RegisteredHandler handler, OptionalInt priority) {} ++} +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +index eeea1e6f7b1ed64567a3f90d8eb2e2cfd53e5912..eedbf46e04b5ae420f9bedcbc2bbb10643ba7e22 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +@@ -279,6 +279,15 @@ class PaperPluginInstanceManager { + + pluginName + " (Is it up to date?)", ex, plugin); // Paper + } + ++ // Paper start - lifecycle event system ++ try { ++ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.unregisterAllEventHandlersFor(plugin); ++ } catch (Throwable ex) { ++ this.handlePluginException("Error occurred (in the plugin loader) while unregistering lifecycle event handlers for " ++ + pluginName + " (Is it up to date?)", ex, plugin); ++ } ++ // Paper end ++ + try { + this.server.getMessenger().unregisterIncomingPluginChannel(plugin); + this.server.getMessenger().unregisterOutgoingPluginChannel(plugin); +diff --git a/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java +index 2e96308696e131f3f013469a395e5ddda2c5d529..65a66e484c1c39c5f41d97db52f31c67b4479d20 100644 +--- a/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java ++++ b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java +@@ -32,8 +32,9 @@ public class BootstrapProviderStorage extends SimpleProviderStorage provider, PluginBootstrap provided) { + try { +- BootstrapContext context = PluginBootstrapContextImpl.create(provider, PluginInitializerManager.instance().pluginDirectoryPath()); ++ PluginBootstrapContextImpl context = PluginBootstrapContextImpl.create(provider, PluginInitializerManager.instance().pluginDirectoryPath()); // Paper - lifecycle events + provided.bootstrap(context); ++ context.lockLifecycleEventRegistration(); // Paper - lifecycle events + return true; + } catch (Throwable e) { + LOGGER.error("Failed to run bootstrapper for %s. This plugin will not be loaded.".formatted(provider.getSource()), e); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index ab7bc27e870227e6746b77a7b5e109e2cf198b5f..9f7ed337463cc9bb370a5541d9de5cd8f9c1a78a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1017,6 +1017,11 @@ public final class CraftServer implements Server { + + @Override + public void reload() { ++ // Paper start - lifecycle events ++ if (io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.blocksPluginReloading()) { ++ throw new IllegalStateException("A lifecycle event handler has been registered which makes reloading plugins not possible"); ++ } ++ // Paper end - lifecycle events + org.spigotmc.WatchdogThread.hasStarted = false; // Paper - Disable watchdog early timeout on reload + this.reloadCount++; + this.configuration = YamlConfiguration.loadConfiguration(this.getConfigFile()); +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java +index d96399e9bf1a58db5a4a22e58abb99e7660e0694..66bdac50130f523f9dc4379b103b7a469f9ca36b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java +@@ -143,4 +143,11 @@ public class MinecraftInternalPlugin extends PluginBase { + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + throw new UnsupportedOperationException("Not supported."); + } ++ ++ // Paper start - lifecycle events ++ @Override ++ public @NotNull io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager getLifecycleManager() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ // Paper end - lifecycle events + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 37da4477a04c3675bf6b7210bb107289e6ba6f88..31d9764b2f6a52088ab7c8f7c0ec41cc7a86846d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -669,6 +669,13 @@ public final class CraftMagicNumbers implements UnsafeValues { + } + // Paper end - spawn egg color visibility + ++ // Paper start - lifecycle event API ++ @Override ++ public io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager createPluginLifecycleEventManager(final org.bukkit.plugin.java.JavaPlugin plugin, final java.util.function.BooleanSupplier registrationCheck) { ++ return new io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEventManager<>(plugin, registrationCheck); ++ } ++ // Paper end - lifecycle event API ++ + /** + * This helper class represents the different NBT Tags. + *

    +diff --git a/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider b/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider +new file mode 100644 +index 0000000000000000000000000000000000000000..808b1192b60348ad05f0bfbdeda6f94df4876743 +--- /dev/null ++++ b/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider +@@ -0,0 +1 @@ ++io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProviderImpl +diff --git a/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java +index 1d14f530ef888102e47eeeaf0d1a6076e51871c4..90cf0c702ca2ff9de64d9718ecba5f2d128953a6 100644 +--- a/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java ++++ b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java +@@ -143,4 +143,11 @@ public class PaperTestPlugin extends PluginBase { + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + throw new UnsupportedOperationException("Not supported."); + } ++ ++ // Paper start - lifecycle events ++ @Override ++ public io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager getLifecycleManager() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ // Paper end - lifecycle events + } diff --git a/patches/server/1041-Conduit-API.patch b/patches/server/1041-Conduit-API.patch new file mode 100644 index 000000000000..9df9ff716be2 --- /dev/null +++ b/patches/server/1041-Conduit-API.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tamion <70228790+notTamion@users.noreply.github.com> +Date: Sat, 27 Jan 2024 20:46:40 +0100 +Subject: [PATCH] Conduit API + +== AT == +public net.minecraft.world.level.block.entity.ConduitBlockEntity effectBlocks +public net.minecraft.world.level.block.entity.ConduitBlockEntity destroyTarget + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java +index 761d009cdeea28b6fd593c5bf1e4dcfa45f3fc27..37e0b762b86e74f607a4541ecb7b24ad7a591d0e 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java +@@ -187,7 +187,7 @@ public class ConduitBlockEntity extends BlockEntity { + + private static void applyEffects(Level world, BlockPos pos, List activatingBlocks) { + int i = activatingBlocks.size(); +- int j = i / 7 * 16; ++ int j = i / 7 * 16; // Paper - Conduit API; diff on change + int k = pos.getX(); + int l = pos.getY(); + int i1 = pos.getZ(); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java +index 29bcac10a7edf53015941e4c28c4f2d9a5a3db56..f0b0348e105fb27c829ec29e638433c57bfd5f64 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java +@@ -18,4 +18,23 @@ public class CraftConduit extends CraftBlockEntityState impl + public CraftConduit copy() { + return new CraftConduit(this); + } ++ ++ // Paper start - Conduit API ++ @Override ++ public boolean isActive() { ++ requirePlaced(); ++ return this.getTileEntity().isActive(); ++ } ++ ++ @Override ++ public int getRange() { ++ requirePlaced(); ++ return this.getTileEntity().effectBlocks.size() / 7 * 16; ++ } ++ ++ @Override ++ public org.bukkit.entity.LivingEntity getTarget() { ++ return this.getTileEntity().destroyTarget == null ? null : this.getTileEntity().destroyTarget.getBukkitLivingEntity(); ++ } ++ // Paper end - Conduit API + } diff --git a/patches/server/1042-ItemStack-Tooltip-API.patch b/patches/server/1042-ItemStack-Tooltip-API.patch new file mode 100644 index 000000000000..5d03327e9dae --- /dev/null +++ b/patches/server/1042-ItemStack-Tooltip-API.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yannick Lamprecht +Date: Mon, 22 Jan 2024 13:27:30 +0100 +Subject: [PATCH] ItemStack Tooltip API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 31d9764b2f6a52088ab7c8f7c0ec41cc7a86846d..1324f05de8106032ce290e928cf106fb4f450517 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -659,6 +659,18 @@ public final class CraftMagicNumbers implements UnsafeValues { + return org.bukkit.craftbukkit.CraftStatistic.getNMSStatistic(statistic).getName(); + } + // Paper end ++ // Paper start - expose itemstack tooltip lines ++ @Override ++ public List computeTooltipLines(final ItemStack itemStack, final io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, final org.bukkit.entity.Player player) { ++ Preconditions.checkArgument(tooltipContext != null, "tooltipContext cannot be null"); ++ net.minecraft.world.item.TooltipFlag.Default flag = tooltipContext.isAdvanced() ? net.minecraft.world.item.TooltipFlag.ADVANCED : net.minecraft.world.item.TooltipFlag.NORMAL; ++ if (tooltipContext.isCreative()) { ++ flag = flag.asCreative(); ++ } ++ final List lines = CraftItemStack.asNMSCopy(itemStack).getTooltipLines(player == null ? null : ((org.bukkit.craftbukkit.entity.CraftPlayer) player).getHandle(), flag); ++ return lines.stream().map(io.papermc.paper.adventure.PaperAdventure::asAdventure).toList(); ++ } ++ // Paper end - expose itemstack tooltip lines + + // Paper start - spawn egg color visibility + @Override diff --git a/patches/server/1043-Add-api-for-spawn-egg-texture-colors.patch b/patches/server/1043-Add-api-for-spawn-egg-texture-colors.patch deleted file mode 100644 index 58fb31c7148d..000000000000 --- a/patches/server/1043-Add-api-for-spawn-egg-texture-colors.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Luis -Date: Thu, 11 Jan 2024 19:58:23 +0100 -Subject: [PATCH] Add api for spawn egg texture colors - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 4e1390b9244aeb745ffd3fd1257bc74248722515..ca5312febcdd467889ad725c0263367bc5fe69f6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -645,6 +645,15 @@ public final class CraftMagicNumbers implements UnsafeValues { - } - // Paper end - -+ // Paper start - spawn egg color visibility -+ @Override -+ public org.bukkit.Color getSpawnEggLayerColor(final EntityType entityType, final int layer) { -+ final net.minecraft.world.entity.EntityType nmsType = org.bukkit.craftbukkit.entity.CraftEntityType.bukkitToMinecraft(entityType); -+ final net.minecraft.world.item.SpawnEggItem eggItem = net.minecraft.world.item.SpawnEggItem.byId(nmsType); -+ return eggItem == null ? null : org.bukkit.Color.fromRGB(eggItem.getColor(layer)); -+ } -+ // Paper end - spawn egg color visibility -+ - /** - * This helper class represents the different NBT Tags. - *

    diff --git a/patches/server/1049-Fix-possible-StackOverflowError-for-some-dispenses.patch b/patches/server/1043-Fix-possible-StackOverflowError-for-some-dispenses.patch similarity index 100% rename from patches/server/1049-Fix-possible-StackOverflowError-for-some-dispenses.patch rename to patches/server/1043-Fix-possible-StackOverflowError-for-some-dispenses.patch diff --git a/patches/server/1050-Properly-track-the-changed-item-from-dispense-events.patch b/patches/server/1044-Properly-track-the-changed-item-from-dispense-events.patch similarity index 100% rename from patches/server/1050-Properly-track-the-changed-item-from-dispense-events.patch rename to patches/server/1044-Properly-track-the-changed-item-from-dispense-events.patch diff --git a/patches/server/1045-Add-getChunkSnapshot-includeLightData-parameter.patch b/patches/server/1045-Add-getChunkSnapshot-includeLightData-parameter.patch new file mode 100644 index 000000000000..bb830096ecf3 --- /dev/null +++ b/patches/server/1045-Add-getChunkSnapshot-includeLightData-parameter.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Sat, 10 Feb 2024 10:03:48 +0100 +Subject: [PATCH] Add getChunkSnapshot includeLightData parameter + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index 105f925fb1a78879d2eb618f0c672c8b9a759dd9..dca5f25cf331b5550e9be491b4e8a3466531e021 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -283,12 +283,21 @@ public class CraftChunk implements Chunk { + + @Override + public ChunkSnapshot getChunkSnapshot(boolean includeMaxBlockY, boolean includeBiome, boolean includeBiomeTempRain) { ++ // Paper start - Add getChunkSnapshot includeLightData parameter ++ return getChunkSnapshot(includeMaxBlockY, includeBiome, includeBiomeTempRain, true); ++ } ++ ++ @Override ++ public ChunkSnapshot getChunkSnapshot(boolean includeMaxBlockY, boolean includeBiome, boolean includeBiomeTempRain, boolean includeLightData) { ++ // Paper end - Add getChunkSnapshot includeLightData parameter + ChunkAccess chunk = this.getHandle(ChunkStatus.FULL); + + LevelChunkSection[] cs = chunk.getSections(); + PalettedContainer[] sectionBlockIDs = new PalettedContainer[cs.length]; +- byte[][] sectionSkyLights = new byte[cs.length][]; +- byte[][] sectionEmitLights = new byte[cs.length][]; ++ // Paper start - Add getChunkSnapshot includeLightData parameter ++ byte[][] sectionSkyLights = includeLightData ? new byte[cs.length][] : null; ++ byte[][] sectionEmitLights = includeLightData ? new byte[cs.length][] : null; ++ // Paper end - Add getChunkSnapshot includeLightData parameter + boolean[] sectionEmpty = new boolean[cs.length]; + PalettedContainerRO>[] biome = (includeBiome || includeBiomeTempRain) ? new PalettedContainer[cs.length] : null; + +@@ -305,6 +314,7 @@ public class CraftChunk implements Chunk { + } + // Paper end - Fix ChunkSnapshot#isSectionEmpty(int) + ++ if (includeLightData) { // Paper - Add getChunkSnapshot includeLightData parameter + LevelLightEngine lightengine = this.worldServer.getLightEngine(); + DataLayer skyLightArray = lightengine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(this.x, chunk.getSectionYFromSectionIndex(i), this.z)); // SPIGOT-7498: Convert section index + if (skyLightArray == null) { +@@ -320,6 +330,7 @@ public class CraftChunk implements Chunk { + sectionEmitLights[i] = new byte[2048]; + System.arraycopy(emitLightArray.getData(), 0, sectionEmitLights[i], 0, 2048); + } ++ } // Paper - Add getChunkSnapshot includeLightData parameter + + if (biome != null) { + biome[i] = ((PalettedContainer>) cs[i].getBiomes()).copy(); // Paper - Perf: use copy instead of round tripping with codecs +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +index 85029f1acfdbb411d9ebdf95838d6db3898f4e58..0756b5adb3039997feadeb94afb10b596abd9424 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +@@ -118,6 +118,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { + + @Override + public final int getBlockSkyLight(int x, int y, int z) { ++ Preconditions.checkState(this.skylight != null, "ChunkSnapshot created without light data. Please call getSnapshot with includeLightData=true"); // Paper - Add getChunkSnapshot includeLightData parameter + this.validateChunkCoordinates(x, y, z); + + int off = ((y & 0xF) << 7) | (z << 3) | (x >> 1); +@@ -126,6 +127,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { + + @Override + public final int getBlockEmittedLight(int x, int y, int z) { ++ Preconditions.checkState(this.emitlight != null, "ChunkSnapshot created without light data. Please call getSnapshot with includeLightData=true"); // Paper - Add getChunkSnapshot includeLightData parameter + this.validateChunkCoordinates(x, y, z); + + int off = ((y & 0xF) << 7) | (z << 3) | (x >> 1); diff --git a/patches/server/1045-Improve-tag-parser-handling.patch b/patches/server/1045-Improve-tag-parser-handling.patch deleted file mode 100644 index cbb6f2ead208..000000000000 --- a/patches/server/1045-Improve-tag-parser-handling.patch +++ /dev/null @@ -1,143 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Mon, 5 Feb 2024 11:54:04 +0100 -Subject: [PATCH] Improve tag parser handling - - -diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java -index 92848b64a78fce7a92e1657c2da6fc5ee53eea44..5d0e8f4f3ad61a27452675277380e27d3d28d133 100644 ---- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java -+++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java -@@ -307,6 +307,10 @@ public class CommandDispatcher { - try { - try { - child.parse(reader, context); -+ // Paper start - Handle non-reoverable exceptions; Rethrow NbtAccounterException so it can be caught properly and immediately -+ } catch (final net.minecraft.nbt.NbtAccounterException e) { -+ throw e; -+ // Paper end - Handle non-reoverable exceptions - } catch (final RuntimeException ex) { - throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException().createWithContext(reader, ex.getMessage()); - } -diff --git a/src/main/java/net/minecraft/nbt/TagParser.java b/src/main/java/net/minecraft/nbt/TagParser.java -index 5bec54239a2b185284c10d58854e5a13e33daae5..9ecd0b7ddaa8376f3c1448f810f7757c9ba1b90a 100644 ---- a/src/main/java/net/minecraft/nbt/TagParser.java -+++ b/src/main/java/net/minecraft/nbt/TagParser.java -@@ -48,6 +48,7 @@ public class TagParser { - } - }, CompoundTag::toString); - private final StringReader reader; -+ private int depth; // Paper - - public static CompoundTag parseTag(String string) throws CommandSyntaxException { - return (new TagParser(new StringReader(string))).readSingleStruct(); -@@ -156,6 +157,7 @@ public class TagParser { - - public CompoundTag readStruct() throws CommandSyntaxException { - this.expect('{'); -+ this.increaseDepth(); // Paper - CompoundTag compoundTag = new CompoundTag(); - this.reader.skipWhitespace(); - -@@ -179,6 +181,7 @@ public class TagParser { - } - - this.expect('}'); -+ this.depth--; // Paper - return compoundTag; - } - -@@ -188,6 +191,7 @@ public class TagParser { - if (!this.reader.canRead()) { - throw ERROR_EXPECTED_VALUE.createWithContext(this.reader); - } else { -+ this.increaseDepth(); // Paper - ListTag listTag = new ListTag(); - TagType tagType = null; - -@@ -213,6 +217,7 @@ public class TagParser { - } - - this.expect(']'); -+ this.depth--; // Paper - return listTag; - } - } -@@ -251,11 +256,11 @@ public class TagParser { - } - - if (typeReader == ByteTag.TYPE) { -- list.add((T)((NumericTag)tag).getAsByte()); -+ list.add((T)((NumericTag)tag).getAsNumber()); // Paper - decompile fix - } else if (typeReader == LongTag.TYPE) { -- list.add((T)((NumericTag)tag).getAsLong()); -+ list.add((T)((NumericTag)tag).getAsNumber()); // Paper - decompile fix - } else { -- list.add((T)((NumericTag)tag).getAsInt()); -+ list.add((T)((NumericTag)tag).getAsNumber()); // Paper - decompile fix - } - - if (this.hasElementSeparator()) { -@@ -286,4 +291,11 @@ public class TagParser { - this.reader.skipWhitespace(); - this.reader.expect(c); - } -+ -+ private void increaseDepth() { -+ this.depth++; -+ if (this.depth > 512) { -+ throw new net.minecraft.nbt.NbtAccounterException("NBT tag is too complex, depth > 512"); -+ } -+ } - } -diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java -index a5e438a834826161c52ca9db57d234d9ff80a591..4766994cce060564370b0d24836a7da8b5e4a8a1 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java -@@ -14,7 +14,7 @@ public class ServerboundCommandSuggestionPacket implements Packet 64 && ((index = packet.getCommand().indexOf(' ')) == -1 || index >= 64)) { -+ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); -+ return; -+ } -+ // Paper end - // Paper start - AsyncTabCompleteEvent - TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet)); - } -@@ -823,7 +830,18 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringreader) { - // Paper end - AsyncTabCompleteEvent -- ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); -+ // Paper start - Handle non-reoverable exceptions -+ ParseResults parseresults; -+ try { -+ parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); -+ } catch (final Throwable e) { // This is fine:tm: -+ if (LOGGER.isDebugEnabled()) { -+ LOGGER.error("Exception parsing command", e); -+ } -+ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); -+ return; -+ } -+ // Paper end - Handle non-reoverable exceptions - - this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { - // Paper start - Don't tab-complete namespaced commands if send-namespaced is false diff --git a/patches/server/1046-Add-Lifecycle-Event-system.patch b/patches/server/1046-Add-Lifecycle-Event-system.patch deleted file mode 100644 index cd457d61d5e5..000000000000 --- a/patches/server/1046-Add-Lifecycle-Event-system.patch +++ /dev/null @@ -1,781 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 18 Jul 2023 17:49:38 -0700 -Subject: [PATCH] Add Lifecycle Event system - -This event system is separate from Bukkit's event system and is -meant for managing resources across reloads and from points in the -PluginBootstrap. - -diff --git a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java -index 30b50e6294c6eaade5e17cfaf34600d122e6251c..0bb7694188d5fb75bb756ce75d0060ea980027ee 100644 ---- a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java -+++ b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java -@@ -1,6 +1,8 @@ - package io.papermc.paper.plugin.bootstrap; - - import io.papermc.paper.plugin.configuration.PluginMeta; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; -+import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEventManager; - import io.papermc.paper.plugin.provider.PluginProvider; - import java.nio.file.Path; - import net.kyori.adventure.text.logger.slf4j.ComponentLogger; -@@ -12,6 +14,10 @@ public final class PluginBootstrapContextImpl implements BootstrapContext { - private final Path dataFolder; - private final ComponentLogger logger; - private final Path pluginSource; -+ // Paper start - lifecycle events -+ private boolean allowsLifecycleRegistration = true; -+ private final PaperLifecycleEventManager lifecycleEventManager = new PaperLifecycleEventManager<>(this, () -> this.allowsLifecycleRegistration); // Paper - lifecycle events -+ // Paper end - lifecycle events - - public PluginBootstrapContextImpl(PluginMeta config, Path dataFolder, ComponentLogger logger, Path pluginSource) { - this.config = config; -@@ -45,4 +51,20 @@ public final class PluginBootstrapContextImpl implements BootstrapContext { - public @NotNull Path getPluginSource() { - return this.pluginSource; - } -+ -+ // Paper start - lifecycle event system -+ @Override -+ public @NotNull PluginMeta getPluginMeta() { -+ return this.config; -+ } -+ -+ @Override -+ public LifecycleEventManager getLifecycleManager() { -+ return this.lifecycleEventManager; -+ } -+ -+ public void lockLifecycleEventRegistration() { -+ this.allowsLifecycleRegistration = false; -+ } -+ // Paper end - } -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f84c9c80e701231e5c33ac3c5573f1093e80f38b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java -@@ -0,0 +1,110 @@ -+package io.papermc.paper.plugin.lifecycle.event; -+ -+import com.google.common.base.Suppliers; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.plugin.bootstrap.BootstrapContext; -+import io.papermc.paper.plugin.lifecycle.event.registrar.PaperRegistrar; -+import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEvent; -+import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEventImpl; -+import io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent; -+import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; -+import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; -+import io.papermc.paper.plugin.lifecycle.event.types.OwnerAwareLifecycleEvent; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.Set; -+import java.util.function.Predicate; -+import java.util.function.Supplier; -+import org.bukkit.plugin.Plugin; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+import org.slf4j.Logger; -+ -+@DefaultQualifier(NonNull.class) -+public class LifecycleEventRunner { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ private static final Supplier>> BLOCKS_RELOADING = Suppliers.memoize(() -> Set.of( // lazy due to cyclic initialization -+ )); -+ public static final LifecycleEventRunner INSTANCE = new LifecycleEventRunner(); -+ -+ private final List> lifecycleEventTypes = new ArrayList<>(); -+ private boolean blockPluginReloading = false; -+ -+ public void checkRegisteredHandler(final LifecycleEventOwner owner, final LifecycleEventType eventType) { -+ /* -+ Lifecycle event handlers for reloadable events that are registered from the BootstrapContext prevent -+ the server from reloading plugins. This is because reloading plugins requires disabling all the plugins, -+ running the reload logic (which would include places where these events should fire) and then re-enabling plugins. -+ */ -+ if (owner instanceof BootstrapContext && BLOCKS_RELOADING.get().contains(eventType)) { -+ this.blockPluginReloading = true; -+ } -+ } -+ -+ public boolean blocksPluginReloading() { -+ return this.blockPluginReloading; -+ } -+ -+ public > ET addEventType(final ET eventType) { -+ this.lifecycleEventTypes.add(eventType); -+ return eventType; -+ } -+ -+ public void callEvent(final LifecycleEventType eventType, final E event) { -+ this.callEvent(eventType, event, $ -> true); -+ } -+ -+ public void callEvent(final LifecycleEventType eventType, final E event, final Predicate ownerPredicate) { -+ final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) eventType; -+ lifecycleEventType.forEachHandler(registeredHandler -> { -+ try { -+ if (event instanceof final OwnerAwareLifecycleEvent ownerAwareEvent) { -+ ownerAwareGenericHelper(ownerAwareEvent, registeredHandler.owner()); -+ } -+ registeredHandler.lifecycleEventHandler().run(event); -+ } catch (final Throwable ex) { -+ LOGGER.error("Could not run '{}' lifecycle event handler from {}", lifecycleEventType.name(), registeredHandler.owner().getPluginMeta().getDisplayName(), ex); -+ } finally { -+ if (event instanceof final OwnerAwareLifecycleEvent ownerAwareEvent) { -+ ownerAwareEvent.setOwner(null); -+ } -+ } -+ }, handler -> ownerPredicate.test(handler.owner())); -+ event.invalidate(); -+ } -+ -+ private static void ownerAwareGenericHelper(final OwnerAwareLifecycleEvent event, final LifecycleEventOwner possibleOwner) { -+ final @Nullable O owner = event.castOwner(possibleOwner); -+ if (owner != null) { -+ event.setOwner(owner); -+ } else { -+ throw new IllegalStateException("Found invalid owner " + possibleOwner + " for event " + event); -+ } -+ } -+ -+ public void unregisterAllEventHandlersFor(final Plugin plugin) { -+ for (final LifecycleEventType lifecycleEventType : this.lifecycleEventTypes) { -+ this.removeEventHandlersOwnedBy(lifecycleEventType, plugin); -+ } -+ } -+ -+ private void removeEventHandlersOwnedBy(final LifecycleEventType eventType, final Plugin possibleOwner) { -+ final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) eventType; -+ lifecycleEventType.removeMatching(registeredHandler -> registeredHandler.owner().getPluginMeta().getName().equals(possibleOwner.getPluginMeta().getName())); -+ } -+ -+ @SuppressWarnings("unchecked") -+ public > void callStaticRegistrarEvent(final LifecycleEventType, ?> lifecycleEventType, final R registrar, final Class ownerClass) { -+ this.callEvent((LifecycleEventType, ?>) lifecycleEventType, new RegistrarEventImpl<>(registrar, ownerClass), ownerClass::isInstance); -+ } -+ -+ @SuppressWarnings("unchecked") -+ public > void callReloadableRegistrarEvent(final LifecycleEventType, ?> lifecycleEventType, final R registrar, final Class ownerClass, final ReloadableRegistrarEvent.Cause cause) { -+ this.callEvent((LifecycleEventType, ?>) lifecycleEventType, new RegistrarEventImpl.ReloadableImpl<>(registrar, ownerClass, cause), ownerClass::isInstance); -+ } -+ -+ private LifecycleEventRunner() { -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e941405269a773e8a77e26ffd1afd84f53fadff5 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java -@@ -0,0 +1,10 @@ -+package io.papermc.paper.plugin.lifecycle.event; -+ -+public interface PaperLifecycleEvent extends LifecycleEvent { -+ -+ // called after all handlers have been run. Can be -+ // used to invalid various contexts to plugins can't -+ // try to re-use them by storing them from the event -+ default void invalidate() { -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f1be5b9a29435bae0afd2bd951bfe88d1669e7eb ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java -@@ -0,0 +1,26 @@ -+package io.papermc.paper.plugin.lifecycle.event; -+ -+import com.google.common.base.Preconditions; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration; -+import java.util.function.BooleanSupplier; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public final class PaperLifecycleEventManager implements LifecycleEventManager { -+ -+ private final O owner; -+ public final BooleanSupplier registrationCheck; -+ -+ public PaperLifecycleEventManager(final O owner, final BooleanSupplier registrationCheck) { -+ this.owner = owner; -+ this.registrationCheck = registrationCheck; -+ } -+ -+ @Override -+ public void registerEventHandler(final LifecycleEventHandlerConfiguration handlerConfiguration) { -+ Preconditions.checkState(this.registrationCheck.getAsBoolean(), "Cannot register lifecycle event handlers"); -+ ((AbstractLifecycleEventHandlerConfiguration) handlerConfiguration).registerFrom(this.owner); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6a85a4f581612efff04c1a955493aa2e32476277 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java -@@ -0,0 +1,26 @@ -+package io.papermc.paper.plugin.lifecycle.event.handler.configuration; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; -+import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public abstract class AbstractLifecycleEventHandlerConfiguration> implements LifecycleEventHandlerConfiguration { -+ -+ private final LifecycleEventHandler handler; -+ private final AbstractLifecycleEventType type; -+ -+ protected AbstractLifecycleEventHandlerConfiguration(final LifecycleEventHandler handler, final AbstractLifecycleEventType type) { -+ this.handler = handler; -+ this.type = type; -+ } -+ -+ public abstract CI config(); -+ -+ public final void registerFrom(final O owner) { -+ this.type.tryRegister(owner, this.handler, this.config()); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e0699fcd0a098abc5e1206e7c0fa80b96eca7884 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java -@@ -0,0 +1,33 @@ -+package io.papermc.paper.plugin.lifecycle.event.handler.configuration; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; -+import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class MonitorLifecycleEventHandlerConfigurationImpl extends AbstractLifecycleEventHandlerConfiguration> implements MonitorLifecycleEventHandlerConfiguration { -+ -+ private boolean monitor = false; -+ -+ public MonitorLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler handler, final AbstractLifecycleEventType> eventType) { -+ super(handler, eventType); -+ } -+ -+ @Override -+ public MonitorLifecycleEventHandlerConfigurationImpl config() { -+ return this; -+ } -+ -+ public boolean isMonitor() { -+ return this.monitor; -+ } -+ -+ @Override -+ public MonitorLifecycleEventHandlerConfiguration monitor() { -+ this.monitor = true; -+ return this; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c1d0070fc1594f7a7c29d7dc679da7b347a7140b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java -@@ -0,0 +1,43 @@ -+package io.papermc.paper.plugin.lifecycle.event.handler.configuration; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; -+import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; -+import java.util.OptionalInt; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class PrioritizedLifecycleEventHandlerConfigurationImpl extends AbstractLifecycleEventHandlerConfiguration> implements PrioritizedLifecycleEventHandlerConfiguration { -+ -+ private static final OptionalInt DEFAULT_PRIORITY = OptionalInt.of(0); -+ private static final OptionalInt MONITOR_PRIORITY = OptionalInt.empty(); -+ -+ private OptionalInt priority = DEFAULT_PRIORITY; -+ -+ public PrioritizedLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler handler, final AbstractLifecycleEventType> eventType) { -+ super(handler, eventType); -+ } -+ -+ @Override -+ public PrioritizedLifecycleEventHandlerConfigurationImpl config() { -+ return this; -+ } -+ -+ public OptionalInt priority() { -+ return this.priority; -+ } -+ -+ @Override -+ public PrioritizedLifecycleEventHandlerConfiguration priority(final int priority) { -+ this.priority = OptionalInt.of(priority); -+ return this; -+ } -+ -+ @Override -+ public PrioritizedLifecycleEventHandlerConfiguration monitor() { -+ this.priority = MONITOR_PRIORITY; -+ return this; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b2586c881988fbabe07eef1b43eb1b55f2d3fa52 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java -@@ -0,0 +1,15 @@ -+package io.papermc.paper.plugin.lifecycle.event.registrar; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public interface PaperRegistrar extends Registrar { -+ -+ void setCurrentContext(@Nullable O owner); -+ -+ default void invalidate() { -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6d530c52aaf0dc2cdfe3bd56af557274a7f44256 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java -@@ -0,0 +1,70 @@ -+package io.papermc.paper.plugin.lifecycle.event.registrar; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.types.OwnerAwareLifecycleEvent; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class RegistrarEventImpl, O extends LifecycleEventOwner> implements PaperLifecycleEvent, OwnerAwareLifecycleEvent, RegistrarEvent { -+ -+ private final R registrar; -+ private final Class ownerClass; -+ -+ public RegistrarEventImpl(final R registrar, final Class ownerClass) { -+ this.registrar = registrar; -+ this.ownerClass = ownerClass; -+ } -+ -+ @Override -+ public R registrar() { -+ return this.registrar; -+ } -+ -+ @Override -+ public final void setOwner(final @Nullable O owner) { -+ this.registrar.setCurrentContext(owner); -+ } -+ -+ @Override -+ public final @Nullable O castOwner(final LifecycleEventOwner owner) { -+ return this.ownerClass.isInstance(owner) ? this.ownerClass.cast(owner) : null; -+ } -+ -+ @Override -+ public void invalidate() { -+ this.registrar.invalidate(); -+ } -+ -+ @Override -+ public String toString() { -+ return "RegistrarEventImpl{" + -+ "registrar=" + this.registrar + -+ ", ownerClass=" + this.ownerClass + -+ '}'; -+ } -+ -+ public static class ReloadableImpl, O extends LifecycleEventOwner> extends RegistrarEventImpl implements ReloadableRegistrarEvent { -+ -+ private final ReloadableRegistrarEvent.Cause cause; -+ -+ public ReloadableImpl(final R registrar, final Class ownerClass, final Cause cause) { -+ super(registrar, ownerClass); -+ this.cause = cause; -+ } -+ -+ @Override -+ public Cause cause() { -+ return this.cause; -+ } -+ -+ @Override -+ public String toString() { -+ return "ReloadableImpl{" + -+ "cause=" + this.cause + -+ "} " + super.toString(); -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a65fb37f4a729e2fe9fb81af822db626ec7e6d7b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java -@@ -0,0 +1,50 @@ -+package io.papermc.paper.plugin.lifecycle.event.types; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner; -+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration; -+import java.util.function.Consumer; -+import java.util.function.Predicate; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public abstract class AbstractLifecycleEventType, CI extends AbstractLifecycleEventHandlerConfiguration> implements LifecycleEventType { -+ -+ private final String name; -+ private final Class ownerType; -+ -+ protected AbstractLifecycleEventType(final String name, final Class ownerType) { -+ this.name = name; -+ this.ownerType = ownerType; -+ } -+ -+ @Override -+ public String name() { -+ return this.name; -+ } -+ -+ private void verifyOwner(final O owner) { -+ if (!this.ownerType.isInstance(owner)) { -+ throw new IllegalArgumentException("You cannot register the lifecycle event '" + this.name + "' on " + owner); -+ } -+ } -+ -+ public abstract void forEachHandler(Consumer> consumer, Predicate> predicate); -+ -+ public abstract void removeMatching(Predicate> predicate); -+ -+ protected abstract void register(O owner, LifecycleEventHandler handler, CI config); -+ -+ public final void tryRegister(final O owner, final LifecycleEventHandler handler, final CI config) { -+ this.verifyOwner(owner); -+ LifecycleEventRunner.INSTANCE.checkRegisteredHandler(owner, this); -+ this.register(owner, handler, config); -+ } -+ -+ public record RegisteredHandler(O owner, LifecycleEventHandler lifecycleEventHandler) { -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0886edad92b40276f268bd745b31bac359fd28af ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java -@@ -0,0 +1,25 @@ -+package io.papermc.paper.plugin.lifecycle.event.types; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public final class LifecycleEventTypeProviderImpl implements LifecycleEventTypeProvider { -+ -+ public static LifecycleEventTypeProviderImpl instance() { -+ return (LifecycleEventTypeProviderImpl) LifecycleEventTypeProvider.PROVIDER; -+ } -+ -+ @Override -+ public LifecycleEventType.Monitorable monitor(final String name, final Class ownerType) { -+ return LifecycleEventRunner.INSTANCE.addEventType(new MonitorableLifecycleEventType<>(name, ownerType)); -+ } -+ -+ @Override -+ public LifecycleEventType.Prioritizable prioritized(final String name, final Class ownerType) { -+ return LifecycleEventRunner.INSTANCE.addEventType(new PrioritizableLifecycleEventType<>(name, ownerType)); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6d92c1d3adf220154dfe7cba3a3f8158356c3e3c ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java -@@ -0,0 +1,54 @@ -+package io.papermc.paper.plugin.lifecycle.event.types; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfiguration; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfigurationImpl; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.function.Consumer; -+import java.util.function.Predicate; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class MonitorableLifecycleEventType extends AbstractLifecycleEventType, MonitorLifecycleEventHandlerConfigurationImpl> implements LifecycleEventType.Monitorable { -+ -+ final List> handlers = new ArrayList<>(); -+ int nonMonitorIdx = 0; -+ -+ public MonitorableLifecycleEventType(final String name, final Class ownerType) { -+ super(name, ownerType); -+ } -+ -+ @Override -+ public MonitorLifecycleEventHandlerConfigurationImpl newHandler(final LifecycleEventHandler handler) { -+ return new MonitorLifecycleEventHandlerConfigurationImpl<>(handler, this); -+ } -+ -+ @Override -+ protected void register(final O owner, final LifecycleEventHandler handler, final MonitorLifecycleEventHandlerConfigurationImpl config) { -+ final RegisteredHandler registeredHandler = new RegisteredHandler<>(owner, handler); -+ if (!config.isMonitor()) { -+ this.handlers.add(this.nonMonitorIdx, registeredHandler); -+ this.nonMonitorIdx++; -+ } else { -+ this.handlers.add(registeredHandler); -+ } -+ } -+ -+ @Override -+ public void forEachHandler(final Consumer> consumer, final Predicate> predicate) { -+ for (final RegisteredHandler handler : this.handlers) { -+ if (predicate.test(handler)) { -+ consumer.accept(handler); -+ } -+ } -+ } -+ -+ @Override -+ public void removeMatching(final Predicate> predicate) { -+ this.handlers.removeIf(predicate); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3e7e7474f301c0725fa2bcd6e19e476fc35f2d5a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java -@@ -0,0 +1,15 @@ -+package io.papermc.paper.plugin.lifecycle.event.types; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public interface OwnerAwareLifecycleEvent extends LifecycleEvent { -+ -+ void setOwner(@Nullable O owner); -+ -+ @Nullable O castOwner(LifecycleEventOwner owner); -+} -diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6629f7fabf66ce761024268043cc30076ba8a3f1 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java -@@ -0,0 +1,64 @@ -+package io.papermc.paper.plugin.lifecycle.event.types; -+ -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent; -+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner; -+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration; -+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfigurationImpl; -+import java.util.ArrayList; -+import java.util.Comparator; -+import java.util.List; -+import java.util.OptionalInt; -+import java.util.function.Consumer; -+import java.util.function.Predicate; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public class PrioritizableLifecycleEventType extends AbstractLifecycleEventType, PrioritizedLifecycleEventHandlerConfigurationImpl> implements LifecycleEventType.Prioritizable { -+ -+ private static final Comparator> COMPARATOR = Comparator.comparing(PrioritizedHandler::priority, (o1, o2) -> { -+ if (o1.equals(o2)) { -+ return 0; -+ } else if (o1.isEmpty()) { -+ return 1; -+ } else if (o2.isEmpty()) { -+ return -1; -+ } else { -+ return Integer.compare(o1.getAsInt(), o2.getAsInt()); -+ } -+ }); -+ -+ private final List> handlers = new ArrayList<>(); -+ -+ public PrioritizableLifecycleEventType(final String name, final Class ownerType) { -+ super(name, ownerType); -+ } -+ -+ @Override -+ public PrioritizedLifecycleEventHandlerConfiguration newHandler(final LifecycleEventHandler handler) { -+ return new PrioritizedLifecycleEventHandlerConfigurationImpl<>(handler, this); -+ } -+ -+ @Override -+ protected void register(final O owner, final LifecycleEventHandler handler, final PrioritizedLifecycleEventHandlerConfigurationImpl config) { -+ this.handlers.add(new PrioritizedHandler<>(new RegisteredHandler<>(owner, handler), config.priority())); -+ this.handlers.sort(COMPARATOR); -+ } -+ -+ @Override -+ public void forEachHandler(final Consumer> consumer, final Predicate> predicate) { -+ for (final PrioritizedHandler handler : this.handlers) { -+ if (predicate.test(handler.handler())) { -+ consumer.accept(handler.handler()); -+ } -+ } -+ } -+ -+ @Override -+ public void removeMatching(final Predicate> predicate) { -+ this.handlers.removeIf(prioritizedHandler -> predicate.test(prioritizedHandler.handler())); -+ } -+ -+ private record PrioritizedHandler(RegisteredHandler handler, OptionalInt priority) {} -+} -diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -index eeea1e6f7b1ed64567a3f90d8eb2e2cfd53e5912..eedbf46e04b5ae420f9bedcbc2bbb10643ba7e22 100644 ---- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -@@ -279,6 +279,15 @@ class PaperPluginInstanceManager { - + pluginName + " (Is it up to date?)", ex, plugin); // Paper - } - -+ // Paper start - lifecycle event system -+ try { -+ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.unregisterAllEventHandlersFor(plugin); -+ } catch (Throwable ex) { -+ this.handlePluginException("Error occurred (in the plugin loader) while unregistering lifecycle event handlers for " -+ + pluginName + " (Is it up to date?)", ex, plugin); -+ } -+ // Paper end -+ - try { - this.server.getMessenger().unregisterIncomingPluginChannel(plugin); - this.server.getMessenger().unregisterOutgoingPluginChannel(plugin); -diff --git a/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java -index 2e96308696e131f3f013469a395e5ddda2c5d529..65a66e484c1c39c5f41d97db52f31c67b4479d20 100644 ---- a/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java -+++ b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java -@@ -32,8 +32,9 @@ public class BootstrapProviderStorage extends SimpleProviderStorage provider, PluginBootstrap provided) { - try { -- BootstrapContext context = PluginBootstrapContextImpl.create(provider, PluginInitializerManager.instance().pluginDirectoryPath()); -+ PluginBootstrapContextImpl context = PluginBootstrapContextImpl.create(provider, PluginInitializerManager.instance().pluginDirectoryPath()); // Paper - lifecycle events - provided.bootstrap(context); -+ context.lockLifecycleEventRegistration(); // Paper - lifecycle events - return true; - } catch (Throwable e) { - LOGGER.error("Failed to run bootstrapper for %s. This plugin will not be loaded.".formatted(provider.getSource()), e); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 4df3b94c8126f00188f5e125757411a0359728fa..14d3986ae6ec721f07dc82b37d62d3bea484ad15 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1005,6 +1005,11 @@ public final class CraftServer implements Server { - - @Override - public void reload() { -+ // Paper start - lifecycle events -+ if (io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.blocksPluginReloading()) { -+ throw new IllegalStateException("A lifecycle event handler has been registered which makes reloading plugins not possible"); -+ } -+ // Paper end - lifecycle events - org.spigotmc.WatchdogThread.hasStarted = false; // Paper - Disable watchdog early timeout on reload - this.reloadCount++; - this.configuration = YamlConfiguration.loadConfiguration(this.getConfigFile()); -diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java -index d96399e9bf1a58db5a4a22e58abb99e7660e0694..66bdac50130f523f9dc4379b103b7a469f9ca36b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java -@@ -143,4 +143,11 @@ public class MinecraftInternalPlugin extends PluginBase { - public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { - throw new UnsupportedOperationException("Not supported."); - } -+ -+ // Paper start - lifecycle events -+ @Override -+ public @NotNull io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager getLifecycleManager() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ // Paper end - lifecycle events - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index ca5312febcdd467889ad725c0263367bc5fe69f6..f276c5163d29d56cf4ed081d8e75cbcfd28d892f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -654,6 +654,13 @@ public final class CraftMagicNumbers implements UnsafeValues { - } - // Paper end - spawn egg color visibility - -+ // Paper start - lifecycle event API -+ @Override -+ public io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager createPluginLifecycleEventManager(final org.bukkit.plugin.java.JavaPlugin plugin, final java.util.function.BooleanSupplier registrationCheck) { -+ return new io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEventManager<>(plugin, registrationCheck); -+ } -+ // Paper end - lifecycle event API -+ - /** - * This helper class represents the different NBT Tags. - *

    -diff --git a/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider b/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider -new file mode 100644 -index 0000000000000000000000000000000000000000..808b1192b60348ad05f0bfbdeda6f94df4876743 ---- /dev/null -+++ b/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider -@@ -0,0 +1 @@ -+io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProviderImpl -diff --git a/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java -index 1d14f530ef888102e47eeeaf0d1a6076e51871c4..90cf0c702ca2ff9de64d9718ecba5f2d128953a6 100644 ---- a/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java -+++ b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java -@@ -143,4 +143,11 @@ public class PaperTestPlugin extends PluginBase { - public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { - throw new UnsupportedOperationException("Not supported."); - } -+ -+ // Paper start - lifecycle events -+ @Override -+ public io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager getLifecycleManager() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ // Paper end - lifecycle events - } diff --git a/patches/server/1047-Conduit-API.patch b/patches/server/1047-Conduit-API.patch deleted file mode 100644 index 820c5e29f382..000000000000 --- a/patches/server/1047-Conduit-API.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Tamion <70228790+notTamion@users.noreply.github.com> -Date: Sat, 27 Jan 2024 20:46:40 +0100 -Subject: [PATCH] Conduit API - -== AT == -public net.minecraft.world.level.block.entity.ConduitBlockEntity effectBlocks -public net.minecraft.world.level.block.entity.ConduitBlockEntity destroyTarget - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java -index 1f493708b01ede8d54f9eb8243695fe70e7af3a1..58f2619fab37a1e2d2093ca89f66f3a8bb47d192 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java -@@ -191,7 +191,7 @@ public class ConduitBlockEntity extends BlockEntity { - - private static void applyEffects(Level world, BlockPos pos, List activatingBlocks) { - int i = activatingBlocks.size(); -- int j = i / 7 * 16; -+ int j = i / 7 * 16; // Paper - Conduit API; diff on change - int k = pos.getX(); - int l = pos.getY(); - int i1 = pos.getZ(); -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java -index 29bcac10a7edf53015941e4c28c4f2d9a5a3db56..f0b0348e105fb27c829ec29e638433c57bfd5f64 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java -@@ -18,4 +18,23 @@ public class CraftConduit extends CraftBlockEntityState impl - public CraftConduit copy() { - return new CraftConduit(this); - } -+ -+ // Paper start - Conduit API -+ @Override -+ public boolean isActive() { -+ requirePlaced(); -+ return this.getTileEntity().isActive(); -+ } -+ -+ @Override -+ public int getRange() { -+ requirePlaced(); -+ return this.getTileEntity().effectBlocks.size() / 7 * 16; -+ } -+ -+ @Override -+ public org.bukkit.entity.LivingEntity getTarget() { -+ return this.getTileEntity().destroyTarget == null ? null : this.getTileEntity().destroyTarget.getBukkitLivingEntity(); -+ } -+ // Paper end - Conduit API - } diff --git a/patches/server/1048-ItemStack-Tooltip-API.patch b/patches/server/1048-ItemStack-Tooltip-API.patch deleted file mode 100644 index 0f761e26a760..000000000000 --- a/patches/server/1048-ItemStack-Tooltip-API.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Yannick Lamprecht -Date: Mon, 22 Jan 2024 13:27:30 +0100 -Subject: [PATCH] ItemStack Tooltip API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index f276c5163d29d56cf4ed081d8e75cbcfd28d892f..8036ed91714d638eb2a8e8c2bea4bf62bc18cb57 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -644,6 +644,18 @@ public final class CraftMagicNumbers implements UnsafeValues { - return org.bukkit.craftbukkit.CraftStatistic.getNMSStatistic(statistic).getName(); - } - // Paper end -+ // Paper start - expose itemstack tooltip lines -+ @Override -+ public List computeTooltipLines(final ItemStack itemStack, final io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, final org.bukkit.entity.Player player) { -+ Preconditions.checkArgument(tooltipContext != null, "tooltipContext cannot be null"); -+ net.minecraft.world.item.TooltipFlag.Default flag = tooltipContext.isAdvanced() ? net.minecraft.world.item.TooltipFlag.ADVANCED : net.minecraft.world.item.TooltipFlag.NORMAL; -+ if (tooltipContext.isCreative()) { -+ flag = flag.asCreative(); -+ } -+ final List lines = CraftItemStack.asNMSCopy(itemStack).getTooltipLines(player == null ? null : ((org.bukkit.craftbukkit.entity.CraftPlayer) player).getHandle(), flag); -+ return lines.stream().map(io.papermc.paper.adventure.PaperAdventure::asAdventure).toList(); -+ } -+ // Paper end - expose itemstack tooltip lines - - // Paper start - spawn egg color visibility - @Override diff --git a/patches/server/1051-Add-getChunkSnapshot-includeLightData-parameter.patch b/patches/server/1051-Add-getChunkSnapshot-includeLightData-parameter.patch deleted file mode 100644 index e11721d136aa..000000000000 --- a/patches/server/1051-Add-getChunkSnapshot-includeLightData-parameter.patch +++ /dev/null @@ -1,70 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Warrior <50800980+Warriorrrr@users.noreply.github.com> -Date: Sat, 10 Feb 2024 10:03:48 +0100 -Subject: [PATCH] Add getChunkSnapshot includeLightData parameter - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index fd702027e62eb38d51fb7c46ef268e9bb94e1e92..21d4f3686d5fb7799b4a19b9f6b1941b527e52cc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -281,12 +281,21 @@ public class CraftChunk implements Chunk { - - @Override - public ChunkSnapshot getChunkSnapshot(boolean includeMaxBlockY, boolean includeBiome, boolean includeBiomeTempRain) { -+ // Paper start - Add getChunkSnapshot includeLightData parameter -+ return getChunkSnapshot(includeMaxBlockY, includeBiome, includeBiomeTempRain, true); -+ } -+ -+ @Override -+ public ChunkSnapshot getChunkSnapshot(boolean includeMaxBlockY, boolean includeBiome, boolean includeBiomeTempRain, boolean includeLightData) { -+ // Paper end - Add getChunkSnapshot includeLightData parameter - ChunkAccess chunk = this.getHandle(ChunkStatus.FULL); - - LevelChunkSection[] cs = chunk.getSections(); - PalettedContainer[] sectionBlockIDs = new PalettedContainer[cs.length]; -- byte[][] sectionSkyLights = new byte[cs.length][]; -- byte[][] sectionEmitLights = new byte[cs.length][]; -+ // Paper start - Add getChunkSnapshot includeLightData parameter -+ byte[][] sectionSkyLights = includeLightData ? new byte[cs.length][] : null; -+ byte[][] sectionEmitLights = includeLightData ? new byte[cs.length][] : null; -+ // Paper end - Add getChunkSnapshot includeLightData parameter - boolean[] sectionEmpty = new boolean[cs.length]; - PalettedContainerRO>[] biome = (includeBiome || includeBiomeTempRain) ? new PalettedContainer[cs.length] : null; - -@@ -303,6 +312,7 @@ public class CraftChunk implements Chunk { - } - // Paper end - Fix ChunkSnapshot#isSectionEmpty(int) - -+ if (includeLightData) { // Paper - Add getChunkSnapshot includeLightData parameter - LevelLightEngine lightengine = this.worldServer.getLightEngine(); - DataLayer skyLightArray = lightengine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(this.x, chunk.getSectionYFromSectionIndex(i), this.z)); // SPIGOT-7498: Convert section index - if (skyLightArray == null) { -@@ -318,6 +328,7 @@ public class CraftChunk implements Chunk { - sectionEmitLights[i] = new byte[2048]; - System.arraycopy(emitLightArray.getData(), 0, sectionEmitLights[i], 0, 2048); - } -+ } // Paper - Add getChunkSnapshot includeLightData parameter - - if (biome != null) { - biome[i] = ((PalettedContainer>) cs[i].getBiomes()).copy(); // Paper - Perf: use copy instead of round tripping with codecs -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -index 85029f1acfdbb411d9ebdf95838d6db3898f4e58..0756b5adb3039997feadeb94afb10b596abd9424 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -@@ -118,6 +118,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { - - @Override - public final int getBlockSkyLight(int x, int y, int z) { -+ Preconditions.checkState(this.skylight != null, "ChunkSnapshot created without light data. Please call getSnapshot with includeLightData=true"); // Paper - Add getChunkSnapshot includeLightData parameter - this.validateChunkCoordinates(x, y, z); - - int off = ((y & 0xF) << 7) | (z << 3) | (x >> 1); -@@ -126,6 +127,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { - - @Override - public final int getBlockEmittedLight(int x, int y, int z) { -+ Preconditions.checkState(this.emitlight != null, "ChunkSnapshot created without light data. Please call getSnapshot with includeLightData=true"); // Paper - Add getChunkSnapshot includeLightData parameter - this.validateChunkCoordinates(x, y, z); - - int off = ((y & 0xF) << 7) | (z << 3) | (x >> 1); diff --git a/work/Bukkit b/work/Bukkit index 1d5228782e11..a6a9d2a41f83 160000 --- a/work/Bukkit +++ b/work/Bukkit @@ -1 +1 @@ -Subproject commit 1d5228782e11c20c984621d26ea05822e46b3d3f +Subproject commit a6a9d2a41f83bf0196ab664da0ef748865c805c4 diff --git a/work/CraftBukkit b/work/CraftBukkit index 292ec79e09e3..38fd4bd5034e 160000 --- a/work/CraftBukkit +++ b/work/CraftBukkit @@ -1 +1 @@ -Subproject commit 292ec79e09e3b90358560dd9e571452c9da7500f +Subproject commit 38fd4bd5034e9adcc0a3122e43eb8d0273d1bc51